Module @streamflow/distributor - v9.1.0

This package allows you to create, claim, clawback a Token Distributor.

Token Distributor essentially that allows you to Airdrop tokens to multiple (thousands or even millions) of recipients with constant fees for the Sender. Recipient will pay gas fees when claiming tokens.

You can also use getClaims and getDistributors to fetch active claims and distributors respectively.


npm i -s @streamflow/common @streamflow/distributor

or

yarn add @streamflow/common @streamflow/distributor

More Documentation available here: docs site β†’

Public API documentation: API docs β†’

Automated Airdrop Creation: notion doc β†’

Most common imports:

import { ICluster } from "@streamflow/common";
import { SolanaDistributorClient } from "@streamflow/distributor/solana";

Check the SDK for other types and utility functions.

const client = new SolanaDistributorClient({
clusterUrl: "https://api.mainnet-beta.solana.com",
cluster: ICluster.Mainnet,
});
const now = Math.floor(Date.now() / 1000);

const createData = {
mint: "Gssm3vfi8s65R31SBdmQRq6cKeYojGgup7whkw4VCiQj", // mint
name: "My Airdrop", // airdrop name
file: csvFile, // CSV file, File | Blob
}

/* The CSV file should have the following format (headers can be omitted)
pubkey,amount_unlocked,amount_locked,category
1111111QLbz7JHiBTspS962RLKV8GndWFwiEaqKM,0,1,Staker
1111111ogCyDbaRMvkdsHB3qfdyFYaG1WtRUAfdh,0,1,Staker
11111112D1oxKts8YPdTJRG5FzxTNpMtWmq8hkVx3,0,1,Staker
11111112cMQwSC9qirWGjZM6gLGwW69X22mqwLLGP,0,1,Staker

Where:
- amount_unlocked - amount of tokens unlocked right at the start of vesting (cliff);
- amount_locked - amount to be distributed;
- category - set to `Staker` as constant, it’s not used right now;

Amounts are in raw format - you need to account for decimal places -> 1.5 tokens with 6 decimals will be 1500000
*/

const solanaParams = {
invoker: wallet, // SignerWalletAdapter or Keypair of Sender account
isNative: // [optional] [WILL CREATE A wSOL Airdrop] Needed only if you need to Airdrop Solana native token
};

const distributorParams = {
mint: "Gssm3vfi8s65R31SBdmQRq6cKeYojGgup7whkw4VCiQj", // mint
unlockPeriod: 1, // Unlock period in seconds
startVestingTs: 0, // Timestamp when Airdrop starts, 0 for immediately upon creation
endVestingTs: now + 3600 * 24 * 7, // Timestamp when Airdrop ends
clawbackStartTs: now + 5, // Timestamp after which Airdrop can be clawed back to the Sender address
claimsClosableByAdmin: false, // Whether individual Claims can be closed by the Sender
claimsClosableByClaimant: false, // Whether the Recipient can close their own Claim
claimsLimit: null, // The number of times a Recipient can Claim the Airdrop - 0 or null for no limit on claims
};

/* You can use our public API for generating a merkleTree or use your own implementation
The public API is accessed with an auth token passed in in the x-api-key header of the request
The tokens are provided manually by Streamflow by reaching out to us with the
wallet address that will create the Airdrop
*/
const { data: merkleResponse } = await httpClient.post(
"https://api-public.streamflow.finance/v2/api/airdrops/",
createData,
headers: {
"Content-Type": "multipart/form-data",
"x-api-key": "<your-auth-token>"
}
)

// You can prepare the instructions for more granular control of execution
const { ixs } = await client.prepareCreateInstructions(
{
...merkleResponse,
root: merkleResponse.merkleRoot,
maxNumNodes: new BN(merkleResponse.maxNumNodes).toString(),
maxTotalClaim: new BN(merkleResponse.maxTotalClaim),
...distributorData,
},
solanaParams,
);

// Or you can call the create function which will trigger the transaction execution for you
const res = await client.create(
{
...merkleResponse,
root: merkleResponse.merkleRoot,
maxNumNodes: new BN(merkleResponse.maxNumNodes).toString(),
maxTotalClaim: new BN(merkleResponse.maxTotalClaim),
...distributorData,
},
solanaParams,
);
const solanaParams = {
invoker: recipient, // SignerWalletAdapter or Keypair of Recipient account
};

const claimantAddress = "s3pWmY359mDrNRnDBZ3v5TrrqqxvxiW2t4U2WZyxRoA" // Wallet that will perform the claim
const ditributorAddress = res.metadataId; // address of the distributor account

// Use our public api or your own solution
const claimantData = await httpClient.get(
`https://api-public.streamflow.finance/v2/api/airdrops/${distributorAddress}/claimants/${claimantAddress}`,
headers: { "x-api-key": "<your-auth-token>" }
)

const claimantParams = {
id: claimantData.distributorAddress,
proof: claimantData.proof, // Merkle Proof used to verify claim
amountUnlocked: new BN(claimantData.amountUnlocked), // Total amount unlocked for a Recipient
amountLocked: new BN(claimantData.amountLocked), // Total amount locked for a Recipient
},

// Prepare the claim instructions
const { ixs } = client.prepareClaimInstructions(
claimantParams,
solanaParams,
),

// Or use the SDK to trigger the transaction
const claimRes = await client.claim(
claimantParams,
solanaParams,
);
const solanaParams = {
invoker: recipient, // SignerWalletAdapter or Keypair of Recipient account
};

// By Admin of the Airdrop, no need for proof in this case
const closeRes = await client.closeClaim(
{
id: res.metadataId, // address of the Distributor Account
claimant: "s3pWmY359mDrNRnDBZ3v5TrrqqxvxiW2t4U2WZyxRoA" // address of the Recipient/Claimant
},
solanaParams,
);



// By Claimant
const claimantAddress = "s3pWmY359mDrNRnDBZ3v5TrrqqxvxiW2t4U2WZyxRoA" // Wallet that will perform the claim
const ditributorAddress = res.metadataId; // address of the distributor account

// Use our public api or your own solution
const claimantData = await httpClient.get(
`https://api-public.streamflow.finance/v2/api/airdrops/${distributorAddress}/claimants/${claimantAddress}`,
headers: { "x-api-key": "<your-auth-token>" }
)

const closeRes = await client.closeClaim(
{
id: claimantData.distributorAddress,
proof: claimantData.proof, // Merkle Proof used to verify claim
amountUnlocked: new BN(claimantData.amountUnlocked), // Total amount unlocked for a Recipient
amountLocked: new BN(claimantData.amountLocked), // Total amount locked for a Recipient
claimant: claimantAddress // address of the Recipient/Claimant
},
solanaParams,
);

Returns all funds to the original Sender.

  • only Sender can clawback funds before Airdrop has ended;
  • anyone can clawback funds a day after Airdrop has ended - funds will still go to the original Sender;
const solanaParams = {
invoker: sender, // SignerWalletAdapter or Keypair of Invoker account
};
const clawbackRes = await client.clawback({ id: res.metadataId }, solanaParams);

As all the data is stored on-chain you can also search Airdrops with searchDistributors method:

// All parameters are optional, so in theory you can just fetch all Distributors
const params = {
mint: "BZLbGTNCSFfoth2GYDtwr7e4imWzpR5jqcUuGEwr646K",
admin: "s3pWmY359mDrNRnDBZ3v5TrrqqxvxiW2t4U2WZyxRoA"
};
// Return an Array of objects {publicKey: PublicKey, account: Distributor}
const disributors = await client.searchDistributors(params);

Streamflow Distributor protocol program IDs

Solana
Devet MErKy6nZVoVAkryxAejJz2juifQ4ArgLgHmaJCQkU7N
Mainnet

Modules

index
solana
solana/descriptor/aligned_distributor
solana/descriptor/merkle_distributor