Auto Claim
This is an auto-claim demo using Anchor in Node.js, you can refer to it and expand upon it.
import * as dotenv from 'dotenv';
import {
Connection,
Keypair,
PublicKey,
} from '@solana/web3.js';
import bs58 from 'bs58';
import {
TOKEN_2022_PROGRAM_ID,
} from '@solana/spl-token';
import { AnchorProvider, Program, Wallet } from '@coral-xyz/anchor';
import idl from './claim.json' with { type: 'json' };
import { PROGRAM_ID } from './utils/constants.js';
import BN from "bn.js";
import { delay, formatBNAmount } from './utils/tools.js';
import { userClaim } from './z_send_user_claim.js';
dotenv.config();
const connection = new Connection(process.env.CLAIM_RPC_URL, { commitment: "confirmed" });
export async function claim() {
// Important Warning: If your wallet's private key is exposed in any way, all your assets will be lost.
// .env file content like this: SOLANA_PRIVATE_KEYS=private_key1,private_key2,private_key3...
const privateKeysString = process.env.SOLANA_PRIVATE_KEYS;
const privateKeys = privateKeysString.split(',').map(key => key.trim());
// Build an anchor program for each wallet
let walletPrograms = [];
for (const privateKey of privateKeys) {
try {
const keypair = Keypair.fromSecretKey(bs58.decode(privateKey));
const wallet = new Wallet(keypair);
const provider = new AnchorProvider(connection, wallet, { commitment: "confirmed" });
const program = new Program(idl, provider);
walletPrograms.push({ wallet, program });
} catch (error) {
console.error(`Error processing key: ${privateKey}`, error);
}
}
// Concurrent processing of each wallet
await Promise.all(walletPrograms.map(async (walletProgram) => {
await startMonitoring(walletProgram);
}));
}
// Start monitoring and claims for a wallet
async function startMonitoring(walletProgram) {
const walletPubkey = walletProgram.wallet.publicKey;
console.log(`Starting monitoring for wallet: ${walletPubkey.toString()}`);
// Maps and arrays for state
const currentTokens = new Map(); // mint => info
const abortFunctions = new Map(); // mint => abort
let subscriptionId; // Only one subscription for wallet
const triggerUpdate = () => {
updateHoldings().then(() => {
}).catch(error => {
console.error('Error in updateHoldings:', error);
});
};
const updateHoldings = async () => {
try {
const newTokensList = await fetchUserHoldTokens(connection, walletPubkey);
const newTokens = new Map(newTokensList.map(t => [t.mintAddress, { tokenAccount: t.tokenAccount, uiAmount: t.uiAmount }]));
// Find removed mints
for (const mint of currentTokens.keys()) {
if (!newTokens.has(mint)) {
const abort = abortFunctions.get(mint);
if (abort) {
abort();
abortFunctions.delete(mint);
console.log(`Delete claim for wallet: ${walletPubkey.toString()}, token: ${mint}`);
}
}
}
// Find new mints
for (const [mint, info] of newTokens) {
if (!currentTokens.has(mint)) {
// Start new claim loop
console.log(`Starting new claim for wallet: ${walletPubkey.toString()}, token: ${mint}`);
const mintPubkey = new PublicKey(mint);
const [claimAuth] = PublicKey.findProgramAddressSync(
[Buffer.from("auth_seed"), mintPubkey.toBuffer()],
PROGRAM_ID
);
const claimAuthorityInfo = await walletProgram.program.account.claimAuthority.getAccountInfo(claimAuth);
if (claimAuthorityInfo) {
const claimAuthorityData = walletProgram.program.coder.accounts.decode('claimAuthority', claimAuthorityInfo.data);
const abort = await claimOne(walletProgram, claimAuthorityData, info.tokenAccount);
abortFunctions.set(mint, abort);
} else {
console.log(`No claim authority for token: ${mint}`);
}
}
}
// Update current
currentTokens.clear();
for (const [k, v] of newTokens) {
currentTokens.set(k, v);
}
} catch (error) {
console.error(`Error updating holdings for wallet: ${walletPubkey.toString()}`, error);
}
};
// Initial setup
await updateHoldings();
// Subscribe only to wallet main account
subscriptionId = connection.onAccountChange(walletPubkey, triggerUpdate, { commitment: 'confirmed' });
// Cleanup on exit (optional, for long-running process)
process.on('SIGINT', () => {
if (subscriptionId !== undefined) {
connection.removeAccountChangeListener(subscriptionId);
}
process.exit(0);
});
}
// Check which token-2022 your wallet holds in.
const fetchUserHoldTokens = async (connection, walletPublicKey) => {
const tokenAccounts = await connection.getParsedTokenAccountsByOwner(
walletPublicKey,
{
programId: TOKEN_2022_PROGRAM_ID,
},
'confirmed'
);
// Use Map to store the account with the maximum uiAmount for each mint
const mintMap = new Map();
tokenAccounts.value.forEach((account) => {
const { mint, tokenAmount } = account.account.data.parsed.info;
const uiAmount = tokenAmount.uiAmount;
if (uiAmount > 100000) {
const current = mintMap.get(mint);
if (!current || uiAmount > current.uiAmount) {
mintMap.set(mint, {
tokenAccount: account.pubkey,
mintAddress: mint,
uiAmount: uiAmount
});
}
}
});
return Array.from(mintMap.values());
};
// claim transaction
const claimOne = async (walletProgram, claimAuthorityData, tokenAccount) => {
let aborted = false;
const controller = new AbortController();
const abort = () => { controller.abort(); aborted = true; };
(async () => {
let isFirst = true;
while (!aborted) {
try {
// This PDA stores the data from the user's claim. It is created when the user first claims.
// You can close it by calling the `close_user_state` directive, thus releasing the SOL,
// but this must be done after `claim_at + claim_interval`.
const [userState] = PublicKey.findProgramAddressSync(
[
Buffer.from("user_state_seed"),
walletProgram.wallet.publicKey.toBuffer(),
claimAuthorityData.mint.toBuffer() // This is the same token address
],
PROGRAM_ID
);
const userStateInfo = await walletProgram.program.account.userState.getAccountInfo(userState);
if (userStateInfo) {
const userStateData = walletProgram.program.coder.accounts.decode('userState', userStateInfo.data);
// This only prints some prompts.
if (!isFirst) {
if (userStateData.claimed && userStateData.claimAmount.gt(new BN(0))) {
console.log(`Claim ${formatBNAmount(userStateData.claimAmount, claimAuthorityData.claimMintDecimals)} ${claimAuthorityData.claimFeedSymbol}. wallet: ${walletProgram.wallet.publicKey.toString()}, token: ${claimAuthorityData.mint.toString()}`)
} else {
console.log(`Claim nothing. wallet: ${walletProgram.wallet.publicKey.toString()}, token: ${claimAuthorityData.mint.toString()}`)
}
}
const now = Math.floor(Date.now() / 1000);
const nextClaimTime = userStateData.validAt.toNumber();
const waitTime = nextClaimTime - now;
if (waitTime > 0) {
await delay(waitTime + 1, controller.signal).catch(() => { });
if (aborted) break;
}
}
await userClaim(connection, walletProgram, claimAuthorityData, tokenAccount);
isFirst = false;
} catch (error) {
console.error('Error in claimOne loop:', error);
await delay(1);
if (aborted) break;
}
}
})();
return abort;
}
claim().catch(console.error);
Last updated