如何为4万名订阅者编写自动空投脚本

在本文/教程中,我将介绍编写node.js脚本的过程,该脚本执行自动代币分发/空投到以太坊地址列表。我将使用Polymath代币分发过程编写的代码(这是一个非常标准的ERC20令牌),并检查我处理代币的自动分发而构建的脚本。

最初我打算通过Infura运行此脚本,因此不必在本地运行全节点。这就要求离线签署交易记录,这是我使用最新版本的web3中的几个便捷函数完成的。不幸的是,即使这在testrpc和Ropsten上像魔咒一样工作,在Mainnet上还是一场灾难。交易没有得到处理,还极其缓慢,昂贵且不可靠。

代币和代币分发的合约

我在Polymath的首要任务之一是帮助团队制定代币和代币分发智能合约,我们将在未来几天使用这些合约来推出POLY代币,并向订阅该平台的4万人执行空投活动。

以下是有关polytoken.sol和polydribution.sol智能合约的一些值得一提的内容,这将有助于理解本教程的其余部分:

· PolyToken.sol是POLY代币的合约,这是一个相当固定的标准ERC20代币合约。

· PolyDistribution.sol是将处理代币初始分配的合约。我们将预售投资者,顾问,创始人等的代币分配从空投中分离出来,因为其过程是非常不同的。在我们的案例中,我们将使用1000万个代币(已发行的10亿个代币)进行空投,向40,000人分发250个代币。 对于本教程而言,最重要的功能是airdropTokens(),让我们对其进行回顾:

function airdropTokens(address[] _recipient) public onlyOwnerOrAdmin {
    require(now >= startTime);
    uint airdropped;
    for(uint i = 0; i< _recipient.length; i++)
    {
        if (!airdrops[_recipient[i]]) {
          airdrops[_recipient[i]] = true;
          require(POLY.transfer(_recipient[i], 250 * decimalFactor));
          airdropped = airdropped.add(250 * decimalFactor);
        }
    }
    AVAILABLE_AIRDROP_SUPPLY = AVAILABLE_AIRDROP_SUPPLY.sub(airdropped);
    AVAILABLE_TOTAL_SUPPLY = AVAILABLE_TOTAL_SUPPLY.sub(airdropped);
    grandTotalClaimed = grandTotalClaimed.add(airdropped);
  }

airdropTokens()的基本作用是将250个POLY代币(通常是ERC20代币)分发(调用ERC20的transfer()函数)到一个地址数组中。 对于我们收到的每个地址,只要他们尚未收到分配,我们就会向他们转移250 POLY。 流程完成后,我们将更新可用的供应并跟踪已经分配了多少代币。

在本教程中,我们将仅关注分发给空投接收者的代币。从上面可以看出,这些代币的分配和转让没有行权期或悬崖期对于其他类型的分配。对于其他类型的分配,情况则不同,那些分配具有一些特殊条件,必须先满足一些特殊条件,才能转让/出售它们。

如果您有兴趣了解其余分配的完成方式,可以查看PolyDistribution.sol中的setAllocation()和transferTokens()函数。

代币分配事件

当代币分配日期到来时,我们的团队需要做的就是将代币分配给每个注册空投的帐户。这些数据是过去几个月从公司网站上收集的,其中包含注册空投并在KYC验证过程中成功验证的每个帐户的地址。空投的过程所需收集的数据存储在CSV文件中,该文件只有1列:每个订阅的以太坊地址。

请注意,可以很容易地修改脚本,使其不仅包含订阅用户的地址,还包含应传输的代币数量。 在这种情况下,由于我们决定将250 POLY分配给每个人,因此这不是必需的,我们选择在分发智能合约中对该数字进行硬编码。

从理论上讲,进行空投非常简单。 我们需要做的就是为我们收集的每个地址调用ERC20令牌的transfer()函数。

如果我们只有很少的订阅者,则可以通过手动执行transfer()函数来完成上述操作,但是潜在的成千上万的人希望在启动时立即拥有其代币,逐个执行上述操作将非常耗时。

通过Node.js脚本自动执行代币分发过程。

在解释了代币和分发合约的工作原理之后,让我们深入研究JS代码。为了使代币分配过程自动化,我们需要做一些事情:

· 我们必须读取CSV文件并对其进行处理,以删除空白或无效的条目。我们假设某些数据会丢失或某些地址可能会错误,因此我们确保在将它们发送到区块链之前将其删除。

· 我们会将地址打包成多个数组,每个数组包含80个地址。为什么是80?经过多次测试,考虑到转让代币的气体成本,这是理想的数字。根据您要对每个条目进行的操作,每次交易可能会花费更多或更少的气体,因此您应该相应地打包条目,以使交易不会耗尽气体并回滚。

· 有了数组集后,我们将每个数组传递给智能合约上的airdropTokens()函数,该函数将遍历数组并为每个订阅者调用transfer()方法向其发送代币。

· 之后,我们将运行另一个过程来获取分发合约生成的所有“转移”事件,以便我们可以检查分发情况是否良好。(我们将对分发的代币进行汇总,该代币应与我们存档的数据相匹配)。

让我们从设置项目开始:

设置

运行以下命令以设置全新项目并安装所需的依赖项:

$ mkdir distributionTutorial
$ npm init
$ truffle init
$ npm install web3 fast-csv truffle-contract ethereumjs-testrpc  --save

对于这个项目,我们将使用一些库和框架:

· truffle:它允许我们从javascript轻松编译、迁移和与我们的合约交互。
· Fast-csv:从CSV文件读取和处理数据。

您还应该安装Parity并将其同步到Ropsten(或您喜欢的任何testnet / mainnet)上。 以下命令对我来说效果很好:

parity — chain ropsten — rpcapi “eth,net,web3,personal,parity” 
— unlock — password $HOME/password.file

接下来,将Polymath Distribution智能合约复制到项目的合约文件夹中。 可以在这里找到文件:https://github.com/PolymathNetwork/polymath-token-distribution/tree/master/contracts

打开truffle.js并将其内容替换为以下代码:

module.exports = {
  networks: {
   development: {
      host: 'localhost',
      port: 8545,
      network_id: '*', // Match any network id
      gas: 3500000,
    }, 
   ropsten: {
      host: 'localhost',
      port: 8545,
      network_id: '3', // Match any network id
      gas: 3500000,
      gasPrice: 50000000000
    },
  },
  solc: {
    optimizer: {
      enabled: true,
      runs: 200,
    },
  },
};

上面的代码将允许我们运行truffle migrate——network-ropsten来将合同部署到ropsten testnet。 在能够将合约部署到Ropsten之前,我们需要创建用于truffle的部署脚本。 使用以下代码在migrations文件夹中创建一个名为2_deploy_contracts.js的新文件:

var PolyToken = artifacts.require('./PolyToken.sol');
var PolyDistribution = artifacts.require('./PolyDistribution.sol');module.exports = async (deployer, network) => {
  let _now = Date.now();
  let _fromNow = 60 * 5 * 1000; // Start distribution in 1 hour
  let _startTime = (_now + _fromNow) / 1000;
  await deployer.deploy(PolyDistribution, _startTime);
  console.log(`
    ---------------------------------------------------------------
    --------- POLYMATH (POLY) TOKEN SUCCESSFULLY DEPLOYED ---------
    ---------------------------------------------------------------
    - Contract address: ${PolyDistribution.address}
    - Distribution starts in: ${_fromNow/1000/60} minutes
    - Local Time: ${new Date(_now + _fromNow)}
    ---------------------------------------------------------------
  `);
};

上面的代码将在执行truffle migrate--network ropsten时运行它将把PolyDistribution合约部署到Ropsten(它也处理POLY Token合约的部署),将startTime设置为5分钟后确保已正确设置了“starttime”变量,并且在到达“starttime”后尝试空投,否则执行将失败。我们正在使用starttime防止人们在代币分发事件开始之前撤回代币。

继续运行truffle migrate--network ropsten如果一切顺利,您应该会在控制台上看到类似的输出:

TX哈希值和合约地址对您来说会有所不同。

如果看不到此输出或出现错误,请确保您正在运行Parity并且已完全同步。 另外,请确保帐户中有足够的以太币,用于在Ropsten测试网上部署合约。

记下我们刚刚部署的Poly Distribution合约的地址,稍后我们将使用它。

读取CSV文件

让我们开始编写脚本,该脚本将自动将poly代币分配到注册空投的地址。

首先,创建一个名为scripts的新文件夹,并在该文件夹中创建一个名为csv_allocation.js的新文件。该文件将包含用于运行分配过程的所有代码。

在继续读取和处理CSV文件的代码之前,让我们将文件添加到项目中。 我们需要一个名为airdrop.csv的1列CSV文件,该文件的每个地址都有一个接收代币的地址。 创建此文件并将其添加到scripts / data文件夹。

如果要轻松测试空投,可以使用您控制的“随机”地址自己生成此文件。 一种简单的方法是运行testrpc并指定要创建的帐户数,如下所示:

testrpc -m "word1 word2 word3..." -a 300

上面的命令将从您提供的助记符中生成300个帐户。 将地址复制到airdrop.csv。

回到我们的csv_allocation.js脚本中,让我们添加必要的代码以能够读取airdrop.csv。 将以下代码添加到csv_allocation.js中:

var fs = require('fs');
var csv = require('fast-csv');
var BigNumber = require('bignumber.js');let polyDistributionAddress = process.argv.slice(2)[0];
let BATCH_SIZE = process.argv.slice(2)[1];
if(!BATCH_SIZE) BATCH_SIZE = 80;
let distribData = new Array();
let allocData = new Array();function readFile() {
  var stream = fs.createReadStream("scripts/data/airdrop.csv");let index = 0;
let batch = 0;console.log(`
    --------------------------------------------
    --------- Parsing distrib.csv file ---------
    --------------------------------------------******** Removing beneficiaries without address data
  `);var csvStream = csv()
      .on("data", function(data){
          let isAddress = web3.utils.isAddress(data[0]);
          if(isAddress && data[0]!=null && data[0]!='' ){
            allocData.push(data[0]);index++;
            if(index >= BATCH_SIZE)
            {
              distribData.push(allocData);
              allocData = [];
              index = 0;
            }}
      })
      .on("end", function(){
           //Add last remainder batch
           distribData.push(allocData);
           allocData = [];
           setAllocation();
      });  stream.pipe(csvStream);
}if(polyDistributionAddress){
  console.log("Processing airdrop. Batch size is",BATCH_SIZE, "accounts per transaction");
  readFile();
}else{
  console.log("Please run the script by providing the address of the PolyDistribution contract");
}

您现在可以执行以下操作来运行脚本:

$ node scripts/csv_allocation.js 0x0... 80
// Where 0x0... is the address of the PolyDistribution contract we previously deployed to Ropsten.
// 80 is the batch size we want to process. (How many accounts per array we want to process and send to the airdropTokens function) Can be omitted, defaults to 80.

让我们回顾一下代码:

首先,我们导入允许我们读取文件和处理csv文件的库。

然后,如果您查看代码的最后几行,将会看到我们正在访问运行脚本时传递的参数,并且如果有PolyDistribution合约的地址,我们将调用readFile()函数。

readFile()函数的作用是访问airdrop.csv文件,并逐行读取它。 在每一行中,我们确保该值不为null或为空,并且还使用web3的isAddress()函数来验证传递的地址是否有效。 如果地址正确,我们会将其添加到一个数组中,该数组包含用于构建每个以太坊交易的已处理数据。

数据全部处理完毕并到达文件末尾后,我们将调用该函数,我们调用函数来获取80个地址的每个数组并对它们进行处理。

请注意,此函数非常简单,可以进一步改进,可以检测超过poly供应量的代币数量、重复地址等。所有这些情况仍在合约中处理,但如果我们可以节省一些对ethereum的事务调用,那就太好了。

处理代币分配

现在我们已经将数据处理到一个数组中了-我们应该将名为distribData的数组包含几个数组,每个数组最多具有80个地址addresses-我们将从智能合约中为每个数组调用airdropTokens()函数 。

const delay = ms => new Promise(resolve => setTimeout(resolve, ms));async function setAllocation() {console.log(`
    --------------------------------------------
    ---------Performing allocations ------------
    --------------------------------------------
  `);let accounts = await web3.eth.getAccounts();let polyDistribution = await PolyDistribution.at(polyDistributionAddress);  for(var i = 0;i< distribData.length;i++){try{
      let gPrice = 50000000000;
      console.log("Attempting to allocate 250 POLYs to accounts:",distribData[i],"\n\n");
      let r = await polyDistribution.airdropTokens(distribData[i],{from:accounts[0], gas:4500000, gasPrice:gPrice});
      console.log("---------- ---------- ---------- ----------");
      console.log("Allocation + transfer was successful.", r.receipt.gasUsed, "gas used. Spent:",r.receipt.gasUsed * gPrice,"wei");
      console.log("---------- ---------- ---------- ----------\n\n")
    } catch (err){
      console.log("ERROR:",err);
    } }
}

让我们仔细看看这个函数。 JS脚本中的setAllocation()所做的只是遍历用CSV文件中处理后的数据填充的distribData数组,然后对于每个条目数组,我们继续在智能合约上执行airdropTokens(),并传递该数组。

对于我们处理的每一批地址,我们都检索事件日志并打印花费了多少气体,以确保过程成功。

每批气体消耗量应大致相同。 如果有一批天然气的成本更低,则意味着该批中的某些地址未转移代币,或者可能是因为它们之前已经转移了代币。

从ERC20令牌读取转移事件以验证交易

在每天调用它之前,我们可以做的最后一件事是访问erc20 poly token transfer()函数的事件日志,以便我们可以快速检查有多少帐户获得了代币。

在setAllocation()函数的末尾添加以下行:

console.log("Distribution script finished successfully.")
  console.log("Waiting 2 minutes for transactions to be mined...")
  await delay(90000);
  console.log("Retrieving logs to inform total amount of tokens distributed so far. This may take a while...")let polytokenAddress = await polyDistribution.POLY({from:accounts[0]});
  let polyToken = await PolyToken.at(polytokenAddress);var sumAccounts = 0;
  var sumTokens = 0;var events = await polyToken.Transfer({from: polyDistribution.address},{fromBlock: 0, toBlock: 'latest'});
  events.get(function(error, log) {
      event_data = log;
      //console.log(log);
      for (var i=0; i
          //let tokens = event_data[i].args.value.times(10 ** -18).toString(10);
          //let addressB = event_data[i].args.to;
          sumTokens += event_data[i].args.value.times(10 ** -18).toNumber();
          sumAccounts +=1;
          //console.log(`Distributed ${tokens} POLY to address ${addressB}`);}
      console.log(`A total of ${sumTokens} POLY tokens have been distributed to ${sumAccounts} accounts so far.`);
  });

上面的代码添加了一个超时函数,因此我们给事务留出一些时间来完成对事务的确认,然后我们获得POLY代币的Transfer()事件,并通过作为PolyDistribution合约的from字段过滤事件。

然后我们计算事件以及分配了多少大笔。我们可以使用该数据将其与原始文件进行比较。如果我们想花哨的话,我们也可以列出每个获得代币的地址,或者添加一个将CSV文件与事件日志数据进行比较的函数。

脚本执行

就这样! 让我们尝试一下脚本。 运行以下命令:

$ node scripts/csv_allocation.js 0x0...
// Replace 0x0... with the address of the PolyDistribution contract you deployed to Ropsten

如果一切顺利,您应该在控制台上看到以下内容:

而且,如果您转到Etherscan并输入已部署的PolyDistribution合同的地址,则应该看到类似以下内容:

如果您可以看到csv文件中每个帐户的transfer()事件,那么恭喜!

你的空投成功了!

到此就结束了,感谢你的耐心阅读!

分享到:

相关文章