Skip to Content
API ReferenceProcess Airdrop

Process Airdrop

Send MOVE tokens to a recipient address.

Endpoint

POST /api/airdrop

Authentication

Required headers:

  • Authorization: Bearer YOUR_BEARER_TOKEN
  • X-Signature: v1,t=timestamp,s=signature

Request Body

{ "address": "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef", "amount": "100", "uniqueHash": "a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2" }

Parameters

FieldTypeRequiredDescription
addressstringYesRecipient wallet address (0x + exactly 64 hex characters)
amountstring/numberYesAmount in whole MOVE units (not octas)
uniqueHashstringYesSHA256 hash (64 hex characters) to prevent duplicates

Each uniqueHash can only be used once. Generate a new unique hash for each transaction.

Response

Success (200 OK)

{ "success": true, "transactionHash": "0xabc123def456...", "recipientAddress": "0x1234567890abcdef...", "amount": "100", "timestamp": "2025-11-18T12:00:00Z", "uniqueHash": "a1b2c3d4..." }

Response Fields

FieldTypeDescription
successbooleanAlways true for successful requests
transactionHashstringBlockchain transaction hash for verification. Omitted when alreadyProcessed is true
alreadyProcessedbooleanPresent and true when this uniqueHash was already distributed on-chain (idempotent retry). See below
recipientAddressstringEcho of the recipient address
amountstringEcho of the amount sent
timestampstringISO 8601 timestamp
uniqueHashstringEcho of the unique hash

Transaction Confirmed: When you receive a 200 OK response, the transaction has been confirmed on-chain. No need for additional verification - the transaction is finalized and irreversible.

The response time includes blockchain confirmation (~1-1.5 seconds), so you can trust that the funds have been transferred.

Idempotent retries (200 OK with alreadyProcessed)

Retrying a request with a uniqueHash that was already distributed returns success, not an error:

{ "success": true, "alreadyProcessed": true, "recipientAddress": "0x1234567890abcdef...", "amount": "100", "timestamp": "2025-11-18T12:00:00Z", "uniqueHash": "a1b2c3d4..." }

This makes safe retries possible: if you never received a response, resend the same request with the same uniqueHash — you’ll get either a fresh transactionHash or alreadyProcessed: true. Either way the funds moved exactly once.

The chain records only the hash, so alreadyProcessed: true does not verify that the original distribution used the same recipient and amount as this request. Never reuse a uniqueHash for a different payment. The original transactionHash is not returned in this case — find it via a blockchain explorer/indexer using the DistributeEvent if needed.

Error Responses

400 Bad Request

Invalid input parameters:

{ "success": false, "error": { "code": "INVALID_ADDRESS", "message": "Invalid wallet address format. Must be 0x followed by exactly 64 hex characters." } }

Common validation errors:

  • INVALID_ADDRESS - Must be 0x + exactly 64 hex characters
  • INVALID_AMOUNT - Amount must be a positive decimal with at most 8 decimal places
  • INVALID_HASH - Must be 64-character SHA256 hex string

A duplicate uniqueHash is no longer an error. It returns 200 OK with alreadyProcessed: true (see above).

401 Unauthorized

Authentication failed:

{ "success": false, "error": { "code": "INVALID_TOKEN", "message": "Invalid authentication token" } }

Common authentication errors:

  • MISSING_AUTH_HEADER - Authorization header not provided
  • INVALID_TOKEN - Bearer token is invalid
  • UNAUTHORIZED - Invalid or missing signature

429 Too Many Requests

Rate limit exceeded:

{ "success": false, "error": { "code": "RATE_LIMIT_EXCEEDED", "message": "Rate limit exceeded. Please try again later." } }

Response Headers:

  • X-RateLimit-Limit - Maximum requests allowed (600/min)
  • X-RateLimit-Remaining - Requests remaining in current window
  • X-RateLimit-Reset - Unix timestamp when the limit resets

The API uses a fixed window rate limit of 600 requests per minute per IP address. If you exceed this limit, you’ll receive a 429 response and must wait until the window resets.

503 Service Unavailable

API is temporarily paused for maintenance:

{ "success": false, "error": { "code": "AIRDROP_PAUSED", "message": "Airdrop API is currently paused. Please try again later." } }

When you receive a 503 response, the API has been intentionally paused by administrators. This is typically for maintenance or emergency situations. Implement retry logic with exponential backoff and check back periodically.

500 Internal Server Error

Service or blockchain error:

{ "success": false, "error": { "code": "INSUFFICIENT_BALANCE", "message": "Insufficient treasury balance to process this airdrop" } }

Common server errors:

  • INSUFFICIENT_BALANCE - Treasury wallet has insufficient funds
  • TRANSACTION_FAILED - Blockchain transaction failed (verified as NOT landed on-chain)
  • CONTRACT_ERROR - Smart contract execution error
  • TRANSACTION_STATUS_UNKNOWN - The outcome could not be confirmed in time. The funds may have moved.

TRANSACTION_STATUS_UNKNOWN requires reconciliation. Do not treat it as a failure and do not retry with a new uniqueHash. Poll GET /api/airdrop/status?uniqueHash=... until you get a definitive answer: processed: true means the distribution landed; processed: false (after allowing time for in-flight transactions to expire, ~60s) means it is safe to resend the same request. The same applies any time you never receive a response at all (network timeout, dropped connection).

Code Example

import crypto from 'crypto'; async function processAirdrop( apiKey: string, signingSecret: string, internalTxId: string, // Your internal transaction ID userId: string, // Your internal user ID address: string, amount: string ): Promise<string> { // Generate unique hash using YOUR internal identifiers // IMPORTANT: Customize this based on your system's IDs const uniqueHash = crypto .createHash('sha256') .update(`txn:${internalTxId}:user:${userId}:${address}:${amount}`) .digest('hex'); const requestBody = { address, amount, uniqueHash }; // Generate signature const timestamp = Math.floor(Date.now() / 1000); const bodyString = JSON.stringify(requestBody); const payload = `v1.${timestamp}.POST./api/airdrop.${bodyString}`; const signature = crypto.createHmac('sha256', signingSecret).update(payload).digest('hex'); const response = await fetch('https://YOUR_API_DOMAIN/api/airdrop', { method: 'POST', headers: { 'Authorization': `Bearer ${apiKey}`, 'X-Signature': `v1,t=${timestamp},s=${signature}`, 'Content-Type': 'application/json' }, body: bodyString }); const data = await response.json(); if (!data.success) { throw new Error(data.error.message); } return data.transactionHash; } // Usage const txHash = await processAirdrop( process.env.API_KEY!, process.env.SIGNING_SECRET!, 'order-12345', // Your internal transaction/order ID 'user-abc-789', // Your internal user ID '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef', '100' ); console.log('Success! Transaction:', txHash);

Best Practices

Unique Hash Generation: Use your internal identifiers (transaction ID, user ID, campaign ID, etc.) combined with the recipient address and amount. This ensures uniqueness in your system and prevents accidental duplicates. The smart contract will reject duplicate hashes automatically.

  1. Handle errors gracefully - Check success field and handle error object
  2. Use proper signatures - All requests require HMAC-SHA256 signatures in the X-Signature header
  3. Verify transactions - Use the transaction hash to verify on the blockchain explorer
  4. Test on testnet first - Use your testnet bearer token when testing your integration

Transaction Verification

Verify your transaction using the Movement Network RPC:

// After successful airdrop const result = await processAirdrop(address, amount, uniqueHash); const txHash = result.transactionHash; // Verify via RPC const rpcUrl = 'https://mainnet.movementnetwork.xyz/v1'; // or testnet const response = await fetch(`${rpcUrl}/transactions/by_hash/${txHash}`); const tx = await response.json(); console.log('Success:', tx.success); console.log('Block:', tx.version); console.log('Timestamp:', tx.timestamp);

See Transaction Verification for complete details.

Last updated on