Advertisement
Guest User

BankrClubAirdrop.js

a guest
Mar 30th, 2025
115
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 10.16 KB | Cryptocurrency | 0 0
  1. // hi from kieran.base.eth
  2.  
  3. // make sure node + visual studio is installed
  4. // make a new folder, open a visual studio in that folder
  5. // type 'npm init -y' into the terminal
  6. // create file called index.js and paste this code inside it
  7.  
  8.  
  9. // these are the deps you'll need
  10. const fetch = require("node-fetch"); // you'll have to type
  11. const { Web3 } = require('web3');
  12. require('dotenv').config();
  13. // you'll have to type the following command in terminal to install them
  14. // npm install node-fetch web3 dotenv
  15.  
  16.  
  17.  
  18.  
  19. // IMPORTANT INFO!!!
  20. // the private key is the exact same as your 12 words that grant access to your wallet
  21. // as such you should create a new wallet for this project
  22. // never save private keys like this if they have funds on them
  23. const privateKey = `0xprivatekeyItsAlongPhraseAboutThisWide00000`; // `0x${process.env.keycangoinenvfile}`;
  24. // you can find your private key in the wallet section or you can generate one offline with web3.js (ask grok/chat gpt how!)
  25. let me = '0xa825094B04D5a3710bd41C4fbC902F75cF333333'; // put your address here from the matching private key
  26.  
  27.  
  28.  
  29.  
  30. const TOTAL_TOKENS = BigInt(100 * 10 ** 18); // how many tokens are you airdropping? * 10 ** 18 handles wei format
  31. let tokenAddress = "0x96A07274aBf958Aa04a05b87434DF4cd1BC77e06"; // CA of the token you're airdropping
  32.  
  33. const ALCHEMY_API_KEY = "asdgfqasdgasddgasdgasdgasgdsdg"; // get an alchemy api key at https://www.alchemy.com/ and paste it here
  34.  
  35.  
  36.  
  37. // from here down you shouldn't have to edit, but feel free!
  38. // at the bottom is instructions on running the code.
  39.  
  40.  
  41.  
  42.  
  43.  
  44.  
  45.  
  46.  
  47.  
  48.  
  49.  
  50.  
  51.  
  52.  
  53.  
  54.  
  55.  
  56.  
  57.  
  58.  
  59.  
  60. const CONTRACT_ADDRESS = "0x9fab8c51f911f0ba6dab64fd6e979bcf6424ce82"; // bankr club NFT address
  61. const BASE_URL = `https://base-mainnet.g.alchemy.com/v2/${ALCHEMY_API_KEY}`;
  62.  
  63. const web3Base = new Web3(BASE_URL);
  64.  
  65.  
  66.  
  67.  
  68.  
  69.  
  70. const OnchainServer = web3Base.eth.accounts.privateKeyToAccount(privateKey);
  71. web3Base.eth.accounts.wallet.add(OnchainServer);
  72.  
  73.  
  74.  
  75.  
  76. let gasPrice = 0;
  77. let airdropAddress = '0x3dAf148e3436eDED962FB0E552bb5fC42706a14f'; // this is a airdrop contract I made, you can audit the code if you like
  78. const MAX_BATCH_SIZE = 100;
  79.  
  80.  
  81. // this is the airdropper ABI for the airdrop function
  82. // this just helps create the contract but i think we skip using it anyways lmao
  83. const airdropABI =[
  84. {
  85. "inputs": [],
  86. "stateMutability": "nonpayable",
  87. "type": "constructor"
  88. },
  89. {
  90. "inputs": [
  91. {
  92. "internalType": "address",
  93. "name": "_tokenAddress",
  94. "type": "address"
  95. },
  96. {
  97. "internalType": "address[]",
  98. "name": "recipients",
  99. "type": "address[]"
  100. },
  101. {
  102. "internalType": "uint256[]",
  103. "name": "amounts",
  104. "type": "uint256[]"
  105. }
  106. ],
  107. "name": "airdrop",
  108. "outputs": [],
  109. "stateMutability": "nonpayable",
  110. "type": "function"
  111. }
  112. ];
  113.  
  114.  
  115.  
  116. module.exports = abi;
  117. const Airdropper = new web3Base.eth.Contract(airdropABI, airdropAddress);
  118.  
  119. // Chunk array utility function
  120. const chunkArray = (array, size) => {
  121. const chunked = [];
  122. for (let i = 0; i < array.length; i += size) {
  123. chunked.push(array.slice(i, i + size));
  124. }
  125. return chunked;
  126. };
  127.  
  128. // Fetch gas price
  129. const fetchGas = async () => {
  130. try {
  131. const gas = await web3Base.eth.getGasPrice();
  132. if (gas && gas > 0) {
  133. let numGas = BigInt(gas);
  134. let fastGas = numGas * BigInt(105) / BigInt(100); // 15% increase
  135. gasPrice = fastGas.toString();
  136. console.log("Fast Gas Price (wei):", gasPrice);
  137. return gasPrice;
  138. }
  139. return null;
  140. } catch (error) {
  141. console.error("Error fetching gas price:", error);
  142. return null;
  143. }
  144. };
  145.  
  146.  
  147. // this function calculates how many tokens each holder recieves
  148. // based on the amount airdropped and their balance of NFTs + the rairty of each
  149. async function getNFTHolders() {
  150. const url = `${BASE_URL}/getOwnersForCollection?contractAddress=${CONTRACT_ADDRESS}&withTokenBalances=true`;
  151.  
  152.  
  153. try {
  154. const response = await fetch(url);
  155. const data = await response.json();
  156.  
  157. if (!data || !data.ownerAddresses) {
  158. console.error("Error: No owners found.");
  159. return [];
  160. }
  161.  
  162. const ownersWithCount = await Promise.all(data.ownerAddresses.map(async (owner) => {
  163. let totalPoints = 0;
  164.  
  165. // Loop through each token to check type attribute
  166. for (const token of owner.tokenBalances) {
  167. try {
  168. const metadataUrl = `${BASE_URL}/getNFTMetadata?contractAddress=${CONTRACT_ADDRESS}&tokenId=${token.tokenId}`;
  169. const metadataResponse = await fetch(metadataUrl);
  170. const metadata = await metadataResponse.json();
  171.  
  172. // Log the attributes for debugging
  173. // console.log(`Attributes for token ${token.tokenId} owned by ${owner.ownerAddress}:`, metadata.metadata.attributes);
  174.  
  175. // Find the type attribute (lowercase 'type')
  176. const typeAttribute = metadata.metadata.attributes.find(attr => attr.trait_type === 'type');
  177. const nftType = typeAttribute ? typeAttribute.value : 'unknown';
  178.  
  179. // console.log(`Token ${token.tokenId} owned by ${owner.ownerAddress} has type: ${nftType}`);
  180.  
  181. // Assign points based on type (lowercase values)
  182. if (nftType === 'yearly') {
  183. totalPoints += 5; // Yearly gets 5x points
  184. } else if (nftType === 'monthly') {
  185. totalPoints += 1; // Monthly gets 1 point
  186. }
  187. } catch (metadataError) {
  188. console.error(`Error fetching metadata for token ${token.tokenId}:`, metadataError);
  189. }
  190. }
  191.  
  192. return {
  193. address: owner.ownerAddress,
  194. nftCount: owner.tokenBalances.length,
  195. totalPoints
  196. };
  197. }));
  198.  
  199. // Calculate total points instead of total NFTs
  200. const totalPoints = ownersWithCount.reduce((sum, owner) => sum + owner.totalPoints, 0);
  201. const tokensPerPoint = TOTAL_TOKENS / BigInt(totalPoints);
  202.  
  203. return ownersWithCount.map(owner => ({
  204. address: owner.address,
  205. nftCount: owner.nftCount,
  206. tokenAmount: (BigInt(owner.totalPoints) * tokensPerPoint).toString()
  207. }));
  208.  
  209. } catch (error) {
  210. console.error("Error fetching NFT holders:", error);
  211. return [];
  212. }
  213. }
  214.  
  215.  
  216. // this just formats transaction data
  217. async function processAirdrop(objectArray) {
  218. const addressList = objectArray.map(item => item.address);
  219. const amounts = objectArray.map(item => item.tokenAmount);
  220.  
  221. const addressChunks = chunkArray(addressList, MAX_BATCH_SIZE);
  222. const amountChunks = chunkArray(amounts, MAX_BATCH_SIZE);
  223.  
  224. const currentGasPrice = await fetchGas();
  225. if (!currentGasPrice) {
  226. throw new Error("Failed to fetch gas price");
  227. }
  228.  
  229. let nonce = await web3Base.eth.getTransactionCount(me, 'latest');
  230. const transactions = [];
  231.  
  232. for (let i = 0; i < addressChunks.length; i++) {
  233. const currentAddresses = addressChunks[i];
  234. const currentAmounts = amountChunks[i];
  235.  
  236. const txObject = {
  237. from: me,
  238. to: airdropAddress,
  239. data: Airdropper.methods.airdrop(tokenAddress, currentAddresses, currentAmounts).encodeABI(),
  240. gasPrice: currentGasPrice,
  241. nonce: nonce
  242. };
  243.  
  244. try {
  245. const gasEstimate = await web3Base.eth.estimateGas(txObject);
  246. txObject.gas = Math.floor(Number(gasEstimate) * 1.1).toString(); // 10% buffer
  247.  
  248. const signedTx = await web3Base.eth.accounts.signTransaction(txObject, privateKey);
  249. transactions.push({
  250. txObject,
  251. signedTx: signedTx.rawTransaction
  252. });
  253.  
  254. } catch (error) {
  255. console.error(`Error preparing transaction batch ${i}:`, error);
  256. break;
  257. }
  258.  
  259. nonce++;
  260. }
  261.  
  262. return transactions;
  263. }
  264.  
  265.  
  266. // 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
  267. // so don't cancel until its done!!!!
  268.  
  269.  
  270. async function executeAirdrop() {
  271. try {
  272. const holders = await getNFTHolders();
  273. if (holders.length === 0) {
  274. console.log("No holders found to process");
  275. return;
  276. }
  277.  
  278. // Sum up all tokenAmounts
  279. const totalAllocated = holders.reduce((sum, holder) => {
  280. const tokenAmountBigInt = BigInt(holder.tokenAmount);
  281. return sum + tokenAmountBigInt;
  282. }, BigInt(0));
  283.  
  284.  
  285.  
  286. // Log the results
  287. console.log(`Total allocated tokens: ${totalAllocated.toString()}`);
  288. console.log(`Expected total tokens: ${TOTAL_TOKENS.toString()}`);
  289.  
  290. // Check if the allocation matches the expected total
  291. if (totalAllocated === TOTAL_TOKENS) {
  292. console.log("Airdrop allocation is correct!");
  293. // this probably will never happen due to floating point rounding errors
  294. } else {
  295. console.log("Airdrop allocation mismatch - checking accuracy...");
  296. console.log(`Difference: ${(TOTAL_TOKENS - totalAllocated).toString()}`);
  297. let diff = TOTAL_TOKENS - totalAllocated;
  298. if (diff >= 1*10**14) {
  299. console.log("Difference was too high so we canceled the transaction");
  300. // this code will never allow an airdrop if the difference is higher than 0.0001 of a token
  301. return;
  302. }
  303.  
  304.  
  305. }
  306.  
  307. //console.log(holders);
  308.  
  309. const transactions = await processAirdrop(holders);
  310.  
  311. console.log(`Prepared ${transactions.length} transaction batches`);
  312.  
  313. // Send transactions
  314. for (let i = 0; i < transactions.length; i++) {
  315. try {
  316. const receipt = await web3Base.eth.sendSignedTransaction(transactions[i].signedTx);
  317. console.log(`Batch ${i + 1} Transaction hash:`, receipt.transactionHash);
  318. console.log(`Batch ${i + 1} Receipt:`, receipt);
  319. } catch (error) {
  320. console.error(`Error sending batch ${i + 1}:`, error);
  321. break;
  322. }
  323. }
  324.  
  325. } catch (error) {
  326. console.error("Error in airdrop execution:", error);
  327. }
  328. }
  329.  
  330. // Execute the airdrop
  331. executeAirdrop();
  332.  
  333.  
  334. // hi if you set everything up then you should be able to in the terminal type 'npm index.js' and this will run
  335. // if not paste the ENTIRE thing EXCEPT YOUR PRIVATE KEY LMAO into grok/chat gpt for debugging help
  336. // 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