Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- // hi from kieran.base.eth
- // make sure node + visual studio is installed
- // make a new folder, open a visual studio in that folder
- // type 'npm init -y' into the terminal
- // create file called index.js and paste this code inside it
- // these are the deps you'll need
- const fetch = require("node-fetch"); // you'll have to type
- const { Web3 } = require('web3');
- require('dotenv').config();
- // you'll have to type the following command in terminal to install them
- // npm install node-fetch web3 dotenv
- // IMPORTANT INFO!!!
- // the private key is the exact same as your 12 words that grant access to your wallet
- // as such you should create a new wallet for this project
- // never save private keys like this if they have funds on them
- const privateKey = `0xprivatekeyItsAlongPhraseAboutThisWide00000`; // `0x${process.env.keycangoinenvfile}`;
- // you can find your private key in the wallet section or you can generate one offline with web3.js (ask grok/chat gpt how!)
- let me = '0xa825094B04D5a3710bd41C4fbC902F75cF333333'; // put your address here from the matching private key
- const TOTAL_TOKENS = BigInt(100 * 10 ** 18); // how many tokens are you airdropping? * 10 ** 18 handles wei format
- let tokenAddress = "0x96A07274aBf958Aa04a05b87434DF4cd1BC77e06"; // CA of the token you're airdropping
- const ALCHEMY_API_KEY = "asdgfqasdgasddgasdgasdgasgdsdg"; // get an alchemy api key at https://www.alchemy.com/ and paste it here
- // from here down you shouldn't have to edit, but feel free!
- // at the bottom is instructions on running the code.
- const CONTRACT_ADDRESS = "0x9fab8c51f911f0ba6dab64fd6e979bcf6424ce82"; // bankr club NFT address
- const BASE_URL = `https://base-mainnet.g.alchemy.com/v2/${ALCHEMY_API_KEY}`;
- const web3Base = new Web3(BASE_URL);
- const OnchainServer = web3Base.eth.accounts.privateKeyToAccount(privateKey);
- web3Base.eth.accounts.wallet.add(OnchainServer);
- let gasPrice = 0;
- let airdropAddress = '0x3dAf148e3436eDED962FB0E552bb5fC42706a14f'; // this is a airdrop contract I made, you can audit the code if you like
- const MAX_BATCH_SIZE = 100;
- // this is the airdropper ABI for the airdrop function
- // this just helps create the contract but i think we skip using it anyways lmao
- const airdropABI =[
- {
- "inputs": [],
- "stateMutability": "nonpayable",
- "type": "constructor"
- },
- {
- "inputs": [
- {
- "internalType": "address",
- "name": "_tokenAddress",
- "type": "address"
- },
- {
- "internalType": "address[]",
- "name": "recipients",
- "type": "address[]"
- },
- {
- "internalType": "uint256[]",
- "name": "amounts",
- "type": "uint256[]"
- }
- ],
- "name": "airdrop",
- "outputs": [],
- "stateMutability": "nonpayable",
- "type": "function"
- }
- ];
- module.exports = abi;
- const Airdropper = new web3Base.eth.Contract(airdropABI, airdropAddress);
- // Chunk array utility function
- const chunkArray = (array, size) => {
- const chunked = [];
- for (let i = 0; i < array.length; i += size) {
- chunked.push(array.slice(i, i + size));
- }
- return chunked;
- };
- // Fetch gas price
- const fetchGas = async () => {
- try {
- const gas = await web3Base.eth.getGasPrice();
- if (gas && gas > 0) {
- let numGas = BigInt(gas);
- let fastGas = numGas * BigInt(105) / BigInt(100); // 15% increase
- gasPrice = fastGas.toString();
- console.log("Fast Gas Price (wei):", gasPrice);
- return gasPrice;
- }
- return null;
- } catch (error) {
- console.error("Error fetching gas price:", error);
- return null;
- }
- };
- // this function calculates how many tokens each holder recieves
- // based on the amount airdropped and their balance of NFTs + the rairty of each
- async function getNFTHolders() {
- const url = `${BASE_URL}/getOwnersForCollection?contractAddress=${CONTRACT_ADDRESS}&withTokenBalances=true`;
- try {
- const response = await fetch(url);
- const data = await response.json();
- if (!data || !data.ownerAddresses) {
- console.error("Error: No owners found.");
- return [];
- }
- const ownersWithCount = await Promise.all(data.ownerAddresses.map(async (owner) => {
- let totalPoints = 0;
- // Loop through each token to check type attribute
- for (const token of owner.tokenBalances) {
- try {
- const metadataUrl = `${BASE_URL}/getNFTMetadata?contractAddress=${CONTRACT_ADDRESS}&tokenId=${token.tokenId}`;
- const metadataResponse = await fetch(metadataUrl);
- const metadata = await metadataResponse.json();
- // Log the attributes for debugging
- // console.log(`Attributes for token ${token.tokenId} owned by ${owner.ownerAddress}:`, metadata.metadata.attributes);
- // Find the type attribute (lowercase 'type')
- const typeAttribute = metadata.metadata.attributes.find(attr => attr.trait_type === 'type');
- const nftType = typeAttribute ? typeAttribute.value : 'unknown';
- // console.log(`Token ${token.tokenId} owned by ${owner.ownerAddress} has type: ${nftType}`);
- // Assign points based on type (lowercase values)
- if (nftType === 'yearly') {
- totalPoints += 5; // Yearly gets 5x points
- } else if (nftType === 'monthly') {
- totalPoints += 1; // Monthly gets 1 point
- }
- } catch (metadataError) {
- console.error(`Error fetching metadata for token ${token.tokenId}:`, metadataError);
- }
- }
- return {
- address: owner.ownerAddress,
- nftCount: owner.tokenBalances.length,
- totalPoints
- };
- }));
- // Calculate total points instead of total NFTs
- const totalPoints = ownersWithCount.reduce((sum, owner) => sum + owner.totalPoints, 0);
- const tokensPerPoint = TOTAL_TOKENS / BigInt(totalPoints);
- return ownersWithCount.map(owner => ({
- address: owner.address,
- nftCount: owner.nftCount,
- tokenAmount: (BigInt(owner.totalPoints) * tokensPerPoint).toString()
- }));
- } catch (error) {
- console.error("Error fetching NFT holders:", error);
- return [];
- }
- }
- // this just formats transaction data
- async function processAirdrop(objectArray) {
- const addressList = objectArray.map(item => item.address);
- const amounts = objectArray.map(item => item.tokenAmount);
- const addressChunks = chunkArray(addressList, MAX_BATCH_SIZE);
- const amountChunks = chunkArray(amounts, MAX_BATCH_SIZE);
- const currentGasPrice = await fetchGas();
- if (!currentGasPrice) {
- throw new Error("Failed to fetch gas price");
- }
- let nonce = await web3Base.eth.getTransactionCount(me, 'latest');
- const transactions = [];
- for (let i = 0; i < addressChunks.length; i++) {
- const currentAddresses = addressChunks[i];
- const currentAmounts = amountChunks[i];
- const txObject = {
- from: me,
- to: airdropAddress,
- data: Airdropper.methods.airdrop(tokenAddress, currentAddresses, currentAmounts).encodeABI(),
- gasPrice: currentGasPrice,
- nonce: nonce
- };
- try {
- const gasEstimate = await web3Base.eth.estimateGas(txObject);
- txObject.gas = Math.floor(Number(gasEstimate) * 1.1).toString(); // 10% buffer
- const signedTx = await web3Base.eth.accounts.signTransaction(txObject, privateKey);
- transactions.push({
- txObject,
- signedTx: signedTx.rawTransaction
- });
- } catch (error) {
- console.error(`Error preparing transaction batch ${i}:`, error);
- break;
- }
- nonce++;
- }
- return transactions;
- }
- // this sends the airdrop onchain - this will be done in batches of 100 at a time so expect this to fire off 10 transactions in a row
- // so don't cancel until its done!!!!
- async function executeAirdrop() {
- try {
- const holders = await getNFTHolders();
- if (holders.length === 0) {
- console.log("No holders found to process");
- return;
- }
- // Sum up all tokenAmounts
- const totalAllocated = holders.reduce((sum, holder) => {
- const tokenAmountBigInt = BigInt(holder.tokenAmount);
- return sum + tokenAmountBigInt;
- }, BigInt(0));
- // Log the results
- console.log(`Total allocated tokens: ${totalAllocated.toString()}`);
- console.log(`Expected total tokens: ${TOTAL_TOKENS.toString()}`);
- // Check if the allocation matches the expected total
- if (totalAllocated === TOTAL_TOKENS) {
- console.log("Airdrop allocation is correct!");
- // this probably will never happen due to floating point rounding errors
- } else {
- console.log("Airdrop allocation mismatch - checking accuracy...");
- console.log(`Difference: ${(TOTAL_TOKENS - totalAllocated).toString()}`);
- let diff = TOTAL_TOKENS - totalAllocated;
- if (diff >= 1*10**14) {
- console.log("Difference was too high so we canceled the transaction");
- // this code will never allow an airdrop if the difference is higher than 0.0001 of a token
- return;
- }
- }
- //console.log(holders);
- const transactions = await processAirdrop(holders);
- console.log(`Prepared ${transactions.length} transaction batches`);
- // Send transactions
- for (let i = 0; i < transactions.length; i++) {
- try {
- const receipt = await web3Base.eth.sendSignedTransaction(transactions[i].signedTx);
- console.log(`Batch ${i + 1} Transaction hash:`, receipt.transactionHash);
- console.log(`Batch ${i + 1} Receipt:`, receipt);
- } catch (error) {
- console.error(`Error sending batch ${i + 1}:`, error);
- break;
- }
- }
- } catch (error) {
- console.error("Error in airdrop execution:", error);
- }
- }
- // Execute the airdrop
- executeAirdrop();
- // hi if you set everything up then you should be able to in the terminal type 'npm index.js' and this will run
- // if not paste the ENTIRE thing EXCEPT YOUR PRIVATE KEY LMAO into grok/chat gpt for debugging help
- // this codebase was made by kieran.base.eth if you can't get it to run i'd be happy to airdrop for you :)
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement