前言
最近在写一个自动转账的脚本,经过了一番搜寻查阅及实战编写,总结了一些心得和见解,写出来希望能帮助到更多有需要的人。
以太币及合约币转账概念
一个以太坊账号包含三个部分,助记词(Mnemonic Phrase)、私钥(Private Key) 以及 地址 ,其中助记词与私钥是可以相互转换的。
由于私钥64位,长得太难看,没有可读性,而私钥的备份在电脑上复制起来容易,手抄下来就比较麻烦,但私钥保存在联网的电脑上毕竟不安全,有被其他人看到的风险,于是有了助记词工具,利用某种算法可以将64位私钥转换成十多个常见的英文单词,这些单词都来源于一个固定词库,根据一定算法得来。私钥与助记词之间的转换是互通的,助记词只是你的私钥的另一种外貌体现。
以太坊是一个分布式的智能合约平台,有一套 ERC-20 标准,通过此标准可以发行自己的合约币(Token),最早叫代币,后来统一翻译为通证。以太币是以太坊发行的币,主要是用来结算合约币(Token)交易费用。
以太坊有一套核心 EVM(以太坊虚拟机) 运行在分布式的智能合约平台,这套核心在区块链上每个参与的节点上运行,开发者可以在其上开发各种应用。与比特币的脚本引擎不同,以太坊的 EVM 功能非常强大,号称“图灵完备”。运行在 EVM 上的脚本称作 DApp(Decentralized Application),当我们发送一条交易时,这条交易会广播一条消息,收到这条消息的账户会运行消息相应的一系列指令,而运行指令的过程会消耗 gas,当整个交易完成后会根据总共运行的指令量计算出 gasUsed,gas 的单位为 Gwei,运行不同的指令会干不同的或活,干不同的活所消耗的资源也不尽相同,这个表格 列举了以太坊的指令所对应消耗的 gas 量。
gas 这个名字起的非常贴切,翻译过来就是 汽油 的意思。如果把以太坊比做一台汽车,运行需要汽油驱动。汽油的价格称作 gasPrice,车跑的过程所消耗的油量称作 gasUsed,可以通过 gasLimit 来限制 gasUsed 的最大值,当超过这个值时就会终止,在终止之前所消耗的 gasUsed 依然会被扣除。如果直到交易完成也没触发或者恰好等于 gasLimit,那么这个交易就会成功,交易完成后只扣除 gasUsed。这里的 gasPrice 不像现实的汽油一样可以由自己控制,这是因为当你设置 gasPrice 油价越高时,你的交易就会被提前处理,举个不恰当的例子:相当于出高价可以买 98 的油,出低价只能买 92 的油一样。
gas 常用单位为 Gwei,还有比它小的 Wei,gas 在交易完成后会转换为 Ether 进行结算,具体转换如下:
1 | 1 Gwei = 1,000,000,000 wei |
每笔交易所消耗的
gas不能提前计算获得,只能交易完成后才能确定。虽然不能提前知道,但是可以根据最近转账所使用的gasUsed大概预估出来。
通过以上的学习我们可以做个小练习,在以太坊浏览器 https://etherscan.io 随便找个交易然后计算它的 gas 消耗,比如拿这个交易进行测试 0x20b727…866df6,只需要关注以下四个字段中的三个就能套用以上教程进行推导计算
| key | value |
|---|---|
| Transaction Fee | 0.000393081 Ether ($0.06) |
| Gas Limit | 30,237 |
| Gas Used by Transaction | 30,237 (100%) |
| Gas Price | 0.000000013 Ether (13 Gwei) |
1 | const gasPrice = 13; // Gwei |
最终结果:0.000393081 Ether,与 Transaction Fee 字段结果一样,说明我们的算法是没毛病的。
第三方接口及主要依赖库
转账业务逻辑编写
安装依赖包
1 | $ npm install bip39 eth-json-rpc-infura ethereumjs-wallet web3 |
创建 ./provider.js,让 web3 支持 infura,支持后可以链接到同步以太坊 mainnet 主网络
1 | const createInfuraProvider = require('eth-json-rpc-infura/src/createProvider'); |
创建 ./client.js,初始化 web3 对象
1 | const Web3 = require('web3'); |
转账需要密钥,如果你使用的是助记词(Mnemonic Phrase),需要先转换成密钥:
1 | const bip39 = require('bip39'); |
以太币转账逻辑
1 | const privateKey = Buffer.from('YOUR_PRIVATE_KEY', 'hex'); // 私钥 |
使用合约币(Token)转账,注意这里的 amount 合约币的转账数量需要乘上合约币发行时设置的精度(decimals)
1 | // 合约币地址 |
txParams 参数说明:
| key | description |
|---|---|
| nonce | 发送者发送交易数的计数,按当前账号下的交易进行递增(从 0 开始),使用相同的 nonce 则可覆盖 pending 状态的交易,为了保险起见调用 web3.eth.getTransactionCount 方法获取当前账号交易计数 |
| gasPrice | 发送者愿意支付执行交易所需的每个gas的Wei数量 |
| gasLimit | 发送者愿意为执行交易支付gas数量的最大值。这个数量被设置之后在任何计算完成之前就会被提前扣掉 |
| to | 接收者的地址。在合约创建交易中,合约账户的地址还没有存在,所以值先空着 |
| value | 从发送者转移到接收者的Wei数量。在合约创建交易中,value作为新建合约账户的开始余额 |
| init | 用来初始化新合约账户的EVM代码片段(只有在合约创建交易中存在)。init值会执行一次,然后就会被丢弃。当init第一次执行的时候,它返回一个账户代码体,也就是永久与合约账户关联的一段代码。 |
| data | 消息通话中的输入数据,也就是参数(可选域,只有在消息通信中存在)例如,如果智能合约就是一个域名注册服务,那么调用合约可能就会期待输入域例如域名和IP地址 |
| v,r,s | 用于产生标识交易发生着的签名 |
txParams.nonce 说明:
- 当nonce太小,交易会被直接拒绝。
- 当nonce太大,交易会一直处于队列之中,这也就是导致我们上面描述的问题的原因。
- 当发送一个比较大的nonce值,然后补齐开始nonce到那个值之间的nonce,那么交易依旧可以被执行。
- 当交易处于queue中时停止geth客户端,那么交易queue中的交易会被清除掉。
使用合约币转账注意事项:
txParams.to地址要写合约币地址txParams.data真正接收者的地址转换后写到这里
预估当前的转账价格
通过以下三种方式获取预估 gasPrice:
- 使用第三方 API:https://ethgasstation.info/json/ethgasAPI.json
- 使用官方的 API,单位为 Gwei:https://www.etherchain.org/api/gasPriceOracle
- 调用的
web3.eth.getGasPrice()方法获取,单位为 Wei,除于1e9可以转换为 Gwei。这种方法只能获取的相当于以上第一种的average字段与第二种的standard字段,转账快,费用高。
这里讲下第一种方式,因为这种获取到的结果比较丰富一些,方便应对五花八门的需求,学会了这种另外两种自然也就会了,请求接口:https://ethgasstation.info/json/ethgasAPI.json,返回 JSON 中有以下字段:
| 字段 | 参考值 | 单位 | 说明 |
|---|---|---|---|
| fastest | 200 | 除 10 后为 Gwei | 转账速度很快,价格相对较贵 |
| fast | 100 | 同上 | 转账速度一般,价格相对一般 |
| safeLow | 30 | 同上 | 转账速度很慢,价格相对较低 |
| fastestWait | 14.2 | 分钟 | 快速转账预计花费时间 |
| fastWait | 2.2 | 同上 | 一般转账预计花费时间 |
| safeLowWait | 2.2 | 同上 | 较慢转账预计花费时间 |
比如这里使用 safeLow 较慢转账的费用计算:
1 | const params = {from: myAddress, to: toAddress}; |
如果是合约币需换成以下 params:
1 | const parmas = { |
或者直接使用上边初始化过的合约对象 contract 来获取会更简单一些:
1 | const estimategasUsed = contract.methods.transfer(toAddress, amount).estimateGas({from: myAddress}); |
合约币要注意的是
amount不能大于账户实际的余额,要不然estimateGas请求会得到一个 code 为-32000的错误提示。
Ether(以太币)转法币的实时汇率可以通过这个接口获取:https://api.infura.io/v1/ticker/symbols,接口会返回一个所支持对法币的列表:
1 | { |
然后找个支持的 symbol 来获取具体的汇率,例如使用以太币对美元的 symbol 为 ethusd,拼接后的 API 为:https://api.infura.io/v1/ticker/ethusd,返回示例:
1 | { |
因为数字货币的波动很大,这里有一个最新撮合的 bid (买单价格)、 ask (卖单价格),我们提取取 bid 作为汇率即可。
小结
- 如果 gasPrice 设置的小或者网络繁忙的话,一般超过 10 小时就会 drop 掉,如果需要保证转账成功率的话需要检测区块状态,
dropped状态的要重新提交转账申请。 - 比特币为 10 分钟出一个块,一个块大概有 1M 左右;以太坊为 15 秒出一个块,块大小无限制。
至此结束,感谢阅读。