Process Airdrop
Send MOVE tokens to a recipient address.
Endpoint
POST /api/airdropAuthentication
Required headers:
Authorization: Bearer YOUR_BEARER_TOKENX-Signature: v1,t=timestamp,s=signature
Request Body
{
"address": "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef",
"amount": "100",
"uniqueHash": "a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2"
}Parameters
| Field | Type | Required | Description |
|---|---|---|---|
address | string | Yes | Recipient wallet address (0x + exactly 64 hex characters) |
amount | string/number | Yes | Amount in whole MOVE units (not octas) |
uniqueHash | string | Yes | SHA256 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
| Field | Type | Description |
|---|---|---|
success | boolean | Always true for successful requests |
transactionHash | string | Blockchain transaction hash for verification. Omitted when alreadyProcessed is true |
alreadyProcessed | boolean | Present and true when this uniqueHash was already distributed on-chain (idempotent retry). See below |
recipientAddress | string | Echo of the recipient address |
amount | string | Echo of the amount sent |
timestamp | string | ISO 8601 timestamp |
uniqueHash | string | Echo 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 be0x+ exactly 64 hex charactersINVALID_AMOUNT- Amount must be a positive decimal with at most 8 decimal placesINVALID_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 providedINVALID_TOKEN- Bearer token is invalidUNAUTHORIZED- 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 windowX-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 fundsTRANSACTION_FAILED- Blockchain transaction failed (verified as NOT landed on-chain)CONTRACT_ERROR- Smart contract execution errorTRANSACTION_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.
- Handle errors gracefully - Check
successfield and handleerrorobject - Use proper signatures - All requests require HMAC-SHA256 signatures in the
X-Signatureheader - Verify transactions - Use the transaction hash to verify on the blockchain explorer
- 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.
Related
- GET /api/health - Check API health
- Transaction Verification - Verify transactions