Skip to content

Checking access...

Airdrop API Reference ​

Version: 1.0 Date: 2026-04-06 Canister: airdrop Staging Canister ID: xtt27-syaaa-aaaao-batwq-caiCandid Interface: airdrop.did


Overview ​

The airdrop canister distributes DOM tokens to verified DAO members after a mandatory 72-hour waiting period. It enforces five gates before a claim succeeds and exposes governance controls so the DAO can pause distribution, resume it, or adjust the per-claim amount without a canister upgrade.

Key Features ​

  • 5-Gate Validation: Active SBT, 72-hour activation wait, not yet claimed, not paused, pool funded
  • One Claim per Member: Each principal can claim exactly once; subsequent calls are rejected
  • Governance Kill Switch: pause_airdrop(), resume_airdrop(), set_airdrop_amount() require governance-level authorization
  • Pool Balance Visibility: Anyone can query the current pool balance and total claims issued
  • Upgrade Safety: Claim records and config survive canister upgrades

Claim Gates ​

All five conditions must pass for claim_airdrop() to succeed:

GateCheck
1 — Active SBTCaller's MembershipMetadata.status == Active
2 — 72-hour waitactivated_at + 259_200_000_000_000 ns ≤ ic_cdk::api::time()
3 — Not claimedCaller principal not in claimed_by set
4 — Not pausedpaused == false
5 — Pool fundedpool_balance >= airdrop_amount

Authentication ​

All update methods use ic_cdk::caller(). Query methods are public.

Access Levels ​

LevelWho
PublicAnyone (query methods, health)
MemberActive DAO member with SBT (claim_airdrop)
GovernanceGovernance canister principal (pause_airdrop, resume_airdrop, set_airdrop_amount)
AdminCanister controller (set_governance_principal, top_up_pool)

Data Types ​

ClaimStatus ​

Result of get_claim_status() or get_my_claim().

candid
type ClaimStatus = variant {
    NotEligible : text;          // Reason string
    EligibleNotClaimed;          // Can claim now
    Claimed : record {
        tx_index   : nat64;
        claimed_at : nat64;      // nanoseconds
        amount     : nat;
    };
    PendingActivation : record {
        activated_at    : nat64;
        eligible_at     : nat64; // activated_at + 72h
    };
};

Methods ​

claim_airdrop ​

Claim the airdrop distribution for the calling member.

Signature:

candid
claim_airdrop : () -> (variant { Ok : nat64; Err : text });

Access: Active members only

Returns: Result<nat64, text> — DOM token transaction index on success

Example:

javascript
const result = await airdrop.claim_airdrop();

if ('Ok' in result) {
  console.log(`Claimed! Transaction: ${result.Ok}`);
} else {
  console.error(`Claim failed: ${result.Err}`);
}

Possible errors:

javascript
{ Err: "No active SBT found for caller" }
{ Err: "72-hour activation wait has not elapsed" }
{ Err: "Already claimed" }
{ Err: "Airdrop is paused" }
{ Err: "Insufficient pool balance" }

Flow:

  1. Fetch MembershipMetadata for caller from membership canister
  2. Verify status == Active
  3. Verify activated_at + 72h ≤ now
  4. Verify caller not in claimed_by set
  5. Verify paused == false
  6. Fetch dom-token pool balance and verify >= airdrop_amount
  7. Call dom-token.icrc1_transfer(caller, airdrop_amount)
  8. Record claim (principal, tx_index, claimed_at)
  9. Return Ok(tx_index)

get_claim_status ​

Check the claim status for any principal.

Signature:

candid
get_claim_status : (who : principal) -> (ClaimStatus) query;

Access: Public

Parameters:

  • who: Principal to check

Example:

javascript
const status = await airdrop.get_claim_status(Principal.fromText("..."));

if ('Claimed' in status) {
  console.log(`Claimed at ${status.Claimed.claimed_at}, tx ${status.Claimed.tx_index}`);
} else if ('EligibleNotClaimed' in status) {
  console.log('Ready to claim!');
} else if ('PendingActivation' in status) {
  const eligibleAt = new Date(Number(status.PendingActivation.eligible_at / 1_000_000n));
  console.log(`Eligible at ${eligibleAt.toISOString()}`);
} else {
  console.log(`Not eligible: ${status.NotEligible}`);
}

get_my_claim ​

Convenience method — same as get_claim_status for ic_cdk::caller().

Signature:

candid
get_my_claim : () -> (ClaimStatus) query;

Access: Public


get_pool_balance ​

Query how many DOM tokens remain in the airdrop pool.

Signature:

candid
get_pool_balance : () -> (nat) query;

Access: Public

Returns: Pool balance in smallest DOM units (8 decimals)

Example:

javascript
const balance = await airdrop.get_pool_balance();
const dom = Number(balance) / 1e8;
console.log(`Pool: ${dom.toFixed(2)} DOM`);

get_claim_count ​

Total number of successful claims since deployment.

Signature:

candid
get_claim_count : () -> (nat64) query;

Access: Public


is_paused ​

Query whether the airdrop is currently paused.

Signature:

candid
is_paused : () -> (bool) query;

Access: Public


pause_airdrop ​

Pause all new claims. Existing claims are unaffected.

Signature:

candid
pause_airdrop : () -> (variant { Ok; Err : text });

Access: Governance canister only

Example:

javascript
// Called from governance canister via inter-canister call
const result = await airdrop.pause_airdrop();
// { Ok: null }

resume_airdrop ​

Resume claims after a pause.

Signature:

candid
resume_airdrop : () -> (variant { Ok; Err : text });

Access: Governance canister only


set_airdrop_amount ​

Update the per-claim DOM amount without a canister upgrade.

Signature:

candid
set_airdrop_amount : (amount : nat) -> (variant { Ok; Err : text });

Access: Governance canister only

Parameters:

  • amount: New per-claim amount in smallest DOM units

Example:

javascript
// Set to 100 DOM
await airdrop.set_airdrop_amount(10_000_000_000n);

Error Handling ​

Error StringCause
"No active SBT found for caller"Caller is not an active member or has no SBT
"72-hour activation wait has not elapsed"Member joined < 72 hours ago
"Already claimed"This principal already claimed once
"Airdrop is paused"Governance paused the airdrop
"Insufficient pool balance"Pool needs to be topped up
"Governance only"Caller is not the governance canister
"Admin only"Caller is not the canister controller

Frontend Integration ​

typescript
import { Actor, HttpAgent } from '@dfinity/agent';
import { Principal } from '@dfinity/principal';

const agent = new HttpAgent({ host: 'https://ic0.app' });
const airdrop = Actor.createActor(idlFactory, {
  agent,
  canisterId: 'xtt27-syaaa-aaaao-batwq-cai',
});

// Check eligibility before claiming
async function checkAndClaim() {
  const status = await airdrop.get_my_claim();

  if ('EligibleNotClaimed' in status) {
    const result = await airdrop.claim_airdrop();
    if ('Ok' in result) {
      console.log(`Airdrop claimed! TX: ${result.Ok}`);
    }
  } else if ('PendingActivation' in status) {
    const eligibleAt = new Date(Number(status.PendingActivation.eligible_at / 1_000_000n));
    console.log(`Check back after ${eligibleAt.toLocaleString()}`);
  } else if ('Claimed' in status) {
    console.log('Already claimed');
  }
}


Changelog ​

Version 1.0 (2026-04-06) ​

  • Initial API documentation
  • All 9 canister methods documented
  • 5-gate validation logic explained
  • Frontend integration example included
  • ClaimStatus type documented

Last Updated: 2026-04-06 API Version: 1.0 Maintainer: API Team

Hello World DAO