airdrop-tool
Version:
Easy to use token airdrop tool that integrates with Saturn Network
194 lines (171 loc) • 6.51 kB
JavaScript
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()
})