UNPKG

airdrop-tool

Version:

Easy to use token airdrop tool that integrates with Saturn Network

194 lines (171 loc) 6.51 kB
#!/usr/bin/env node const axios = require('axios') const chalk = require('chalk') const program = require('commander') const fs = require('fs') const ethers = require('ethers') const _ = require('lodash') const _cliProgress = require('cli-progress') let config = require('./config.json') let abi = require('./airdropabi.json') const version = require('./package.json').version const pipeline = async (funcs, bar1) => { bar1.start(funcs.length, 0) let counter = 0 return await funcs.reduce((promise, func) => { return promise.then(result => { bar1.update(++counter) return func().then(Array.prototype.concat.bind(result)) }) }, Promise.resolve([])) } function validateChain(chain) { let allChains = ['ETC', 'ETH'] let valid = _.includes(allChains, chain) if (!valid) { console.error('Unsupported chain', chain) process.exit(1) } return chain } function validateTokenStandard(std) { let allStandards = ['ERC20', 'ERC223'] let valid = _.includes(allStandards, std) if (!valid) { console.error('Unknown token standard', std) process.exit(1) } return std } function getChainId(chain) { if (chain === 'ETC') { return 61 } if (chain === 'ETH') { return 1 } console.log('Unknown chainId for chain', chain) process.exit(1) } async function getGasPrice(chain) { if (chain === 'ETC') { return 1000000 } let gasApiUrl = 'https://www.ethgasstationapi.com/api/standard' return (await axios.get(gasApiUrl)).data * 1000000000 } function sleep(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } async function ensureApproval(token, program, wallet) { if (program.standard === 'ERC223') { return true } let allowance = await token.allowance(wallet.address, config[program.chain].address) let totalSupply = await token.totalSupply() if (allowance.toString() != totalSupply.toString()) { console.log(`🐖 Activating ERC20 token airdrop system...`) let approvetx = await token.approve(config[program.chain].address, totalSupply.toString()) await approvetx.wait() console.log(`🥓 ERC20 airdrop system activated! `) } return true } async function confirmTransaction(hash) { if (program.chain === 'ETC') { await sleep(100) return true } if (program.chain === 'ETH') { await sleep(100) return true } console.error('Unknown chain ', program.chain) process.exit(1) } program .version(version, '-v, --version') .description('A utility that simplifies token airdrops. Works across multiple chains and token standards. More details are available at ' + chalk.underline.red('https://forum.saturn.network/t/airdrop-tool-guides/2340')) .option('-c, --chain <chain>', 'The chain on which you want to conduct the airdrop', validateChain, 'ETC') .option('-p, --pkey [pkey]', 'Private key of the wallet with the tokens you wish to airdrop') .option('-m, --mnemonic [mnemonic]', 'Mnemonic (i.e. from Saturn Wallet) of the wallet with the tokens you with to airdrop') .option('-t, --token <tokenaddress>', 'Address of the token smart contract') .option('-s, --standard <standard>', 'Token standard', validateTokenStandard, 'ERC223') .option('-j, --json <json>', 'A json file specifying addresses and token allocations') .option('-g, --gas <gas>', 'Optional amount of gas to be used for each transaction.') .option('-n, --rpcnode [rpcnode]', 'Optional url to the JSONRPC node to be used for the airdrop') .parse(process.argv) if (!program.mnemonic && !program.pkey) { console.error('At least one of [pkey], [mnemonic] must be supplied') process.exit(1) } if (program.mnemonic && program.pkey) { console.error('Only one of [pkey], [mnemonic] must be supplied') process.exit(1) } if (!program.token) { console.error('Must specify token address') process.exit(1) } let benefactors if (program.json) { console.log(chalk.green('Loading', program.json)) benefactors = require(program.json) } else { console.log(chalk.green('Using saturn-genesis.json')) benefactors = require('./saturn-genesis.json') } let rpcnode = program.rpcnode || config[program.chain].rpcnode let chainId = getChainId(program.chain) let provider = new ethers.providers.JsonRpcProvider(rpcnode, { chainId: chainId, name: program.chain }) let wallet if (program.mnemonic) { wallet = ethers.Wallet.fromMnemonic(program.mnemonic).connect(provider) } else { wallet = new ethers.Wallet(program.pkey, provider) } let token = new ethers.Contract(program.token, [ "function balanceOf(address) view returns(uint)", "function transfer(address,uint,bytes) returns(bool)", "function approve(address,uint256) returns(bool)", "function totalSupply() view returns(uint256)", "function allowance(address,address) view returns(uint256)" ], wallet) let airdrop = new ethers.Contract(config[program.chain].address, abi, wallet) let gift if (program.standard === 'ERC223') { gift = async function(token, amount, to, nonce, gasPrice) { let txparams = { gasPrice: gasPrice, nonce: nonce } if (program.gas) { txparams.gasLimit = ethers.utils.bigNumberify(program.gas) } let tx = await token.transfer( config[program.chain].address, ethers.utils.bigNumberify(amount), to.toLowerCase(), txparams ) await confirmTransaction(tx.hash) return tx.hash } } else { gift = async function(token, amount, to, nonce, gasPrice) { let txparams = { gasPrice: gasPrice, nonce: nonce } if (program.gas) { txparams.gasLimit = ethers.utils.bigNumberify(program.gas) } let tx = await airdrop.giftERC20( to.toLowerCase(), ethers.utils.bigNumberify(amount), program.token, txparams ) await confirmTransaction(tx.hash) return tx.hash } } ensureApproval(token, program, wallet).then(async (truth) => { let nonce = await wallet.getTransactionCount() let gasPrice = await getGasPrice(program.chain) let airdrops = _.map(benefactors, function(user, idx) { return async () => await gift(token, user.tokens, user.address, nonce + idx, gasPrice) }) console.log('Airdropping like it\'s hot 🔥 to', airdrops.length, 'wallets') const bar1 = new _cliProgress.Bar({}, _cliProgress.Presets.shades_classic) await pipeline(airdrops, bar1).catch((err) => { bar1.stop() console.error(chalk.red('Something went wrong! Please ask for help and provide this error message.')) console.error(err) process.exit(1) }) bar1.stop() })