Airdrops
AirdropRecipient lets the protocol owner stitch arbitrary external
contract calls into the Locker / collection lifecycle, then later distribute
the resulting payouts to CollectionToken holders via merkle proofs. It
covers ERC-20, ERC-721, ERC-1155, and native-ETH drops in one entry-point
set.
The four entry-points form a strict pipeline:
requestAirdrop (admin)
Calls _contract with _payload, optionally forwarding _value ETH.
Used to subscribe to upstream airdrops, claim points, etc.
Prerequisites
- Caller is the
AirdropRecipientowner. _contractis on the owner’s allowlist (theAirdropTargettable in the subgraph mirrors this).
Inputs
function requestAirdrop(address _contract, uint _value, bytes calldata _payload)
external payable
returns (bool success_, bytes memory data_);Code
import { airdropRecipientAbi } from "@nftx/contracts";
import { NFTX_CONTRACTS } from "@nftx/config";
import { encodeFunctionData } from "viem";
const recipient = NFTX_CONTRACTS[base.id].airdropRecipient;
const payload = encodeFunctionData({
abi: targetAbi,
functionName: "claim",
args: [merkleProof, leaf],
});
await walletClient.writeContract({
address: recipient,
abi: airdropRecipientAbi,
functionName: "requestAirdrop",
args: [targetContract, 0n, payload],
});Failure modes
| Error | Cause |
|---|---|
ExternalCallFailed | _contract.call(_payload) reverted |
setAirdropTarget (admin)
Manages the _contract allowlist consumed by requestAirdrop.
Subgraph
AirdropTargetSet(target, allowed) →
AirdropTarget { allowed }.
distributeAirdrop (admin)
Publishes a merkle root and ClaimType so holders can begin claiming.
Inputs
function distributeAirdrop(bytes32 _merkle, Enums.ClaimType _claimType) external;Code
await walletClient.writeContract({
address: recipient,
abi: airdropRecipientAbi,
functionName: "distributeAirdrop",
args: [merkleRoot, /* ClaimType.ERC20 */ 0],
});Failure modes
| Error | Cause |
|---|---|
MerkleAlreadyExists | (_merkle, _claimType) was already published |
MerkleRootNotValid | _merkle == bytes32(0) |
Subgraph
AirdropDistributed(merkle, claimType) →
new AirdropDistribution { id: chainId-merkle-claimType, createdAt }.
claimAirdrop
Holder-side entry. The merkle proof is generated off-chain (by the FE proof service) from the published distribution.
Prerequisites
- The corresponding
AirdropDistributionexists and has not yet been fully claimed for the holder’s leaf (isClaimed(merkle, leaf) == false). - The leaf hashes to a node included in the merkle proof.
Inputs
struct MerkleClaim {
address recipient;
address target; // payout token / NFT contract
uint tokenId; // NFT id or 0 for fungible
uint amount;
}
function claimAirdrop(
bytes32 _merkle,
Enums.ClaimType _claimType,
MerkleClaim calldata _node,
bytes32[] calldata _merkleProof
) external;Code
await walletClient.writeContract({
address: recipient,
abi: airdropRecipientAbi,
functionName: "claimAirdrop",
args: [
merkleRoot,
/* ClaimType.ERC20 */ 0,
{
recipient: account,
target: tokenAddress,
tokenId: 0n,
amount: 1_000_000n,
},
proof,
],
});Failure modes
| Error | Cause |
|---|---|
InvalidClaimNode | Leaf doesn’t match (recipient, target, tokenId, amount) |
AirdropAlreadyClaimed | Holder already claimed this leaf |
TransferFailed | The downstream payout transfer failed (e.g. ERC-20 reverts on transfer) |
Subgraph
AirdropClaimed(merkle, claimType, recipient, target, tokenId, amount) →
AirdropClaim { id, recipient, target, tokenId, amount, txHash, claimedAt }
linked back to the AirdropDistribution.
Combined view: a recipient’s airdrops
The subgraph indexes claimed rows. To list eligible-but-unclaimed
airdrops you also need the off-chain proof service to enumerate that
recipient’s leaves; then filter out anything that already appears in
AirdropClaim.
query AirdropsForRecipient($chainId: Int!, $recipient: String!) {
AirdropClaim(
where: {
chainId: { _eq: $chainId }
recipient: { _eq: $recipient }
}
order_by: { claimedAt: desc }
) {
airdrop { merkle claimType }
target
tokenId
amount
txHash
claimedAt
}
}Native (ETH) airdrop note
For ClaimType.NATIVE the leaf’s target is address(0); the contract
forwards _node.amount of ETH from AirdropRecipient’s balance to
recipient. Any failure in that low-level transfer surfaces as
TransferFailed.