分类 Blockchain 下的文章

写在开头:ethers的官方文档不够详细,如果看不懂,可以去web3的官方文档找对应的函数查看解释

  • 首先需要npm安裝ethers
  • 然后再在项目里引入

    const ethers = require("ethers") // nodejs
    import ethers from "ethers" // front-end
    // 如果是非webpack前端,也可以直接用script标签引入.js文件
  • 关联钱包

    const wallet = new ethers.Wallet('your private key') // 这个必须用私钥。还有另一个方式是用助记词
  • 将钱包关联到contract,这样就可以发起签名,进行transfer、approve等操作

    const account = wallet.connect(provider)
    const someContract = new ethers.Contract(
    'contract address for example, USDT, BUSD, panca etc...',
    ['function approve(address spender, uint amount) public returns(bool)',
    'function allowance(address owner, address spender) view returns(uint256)'], // 这个数组放你需要用到的abi
    account
    )
  • 配置provider,用于链上交互

    const provider = new ethers.providers.WebSocketProvider("wss://bsc-ws-node.nariox.org:443") // 示例里的链接为bsc主网的社区wss,测试用可以,大型项目建议使用付费的或者自建full node
  • 例子1:发起approve。注意,await需要在async类型的函数里运行

    const gasPrice = await provider.getGasPrice(); // bsc大多时候返回5000000000 (5e9)
    const approveResult = await someContract.approve(
    "spender's address", // 允许让谁调用,可以是别人的账号,也可以是诸如pancakeswap的router address等
    123456, // 最高0xffff(64个F)
    { gasPrice: gasPrice, gasLimit: "450000" } // 为什么gasPrice>gasLimit?
    )
    // approveResult包含了此次approve事件的hash等信息
  • 例子2:查看某个approve的授权额

    const allowance = await someContract.allowance(wallet.address, "spender's address")
    // 返回值为uni256的BigNumber,如果是0,用0===allowance依然false,但0==allowance则为true
    // 如果从来没approve过,则allowance为0
  • 例子3:取消某个approve:

    • 方法跟例子1一样,只不过第二个参数授权额度设置为0即可
  • 例子4:根据token0和token1获取它们在LP的pair address

    // 想要获取pair address需要先构造对应AMM的factory contract
    const factory = new ethers.Contract(
    "AMM's factory address", 
    ["function getPair(address tokenA, address tokenB) external view returns (address pair)"], // factory的abi不止这个,详情查看对应的factory.sol,如果是bsc则在bscscan上查AMM's factory address应该也能查到
    account
    )
    const pairAddress = await factory.getPair("token0 address", "token1 address") // 两个address顺序调换不影响
  • 例子5:获取某个token对在LP里的储量

    // 想要获得token0和token1在LP里的储量,需要先构造pair contract
    const pair = new ethers.Contract(
    pairAddress,
    ["function getReserves() external view returns (uint112 reserve0, uint112 reserve1, uint32 blockTimestampLast)"],
    // 如果想要更多其它abi,假设是bsc链的pairAddress,可以去bscscan搜,里面直接列出了所有的function
    account
    )
    const reserves = await pair.getReserves()
    // reserves.reserve0和reserves.reserve1就是token0和token1在LP里的储量,具体顺序可能需要打印log才知道
    // 此时我们就得到大名鼎鼎的X*Y=K了,reserve0和reserve1分别就是X和Y
    // 利用reserve0/reserve1或者reserve1/reserve0即可得到他们之间互相的swap价格,如果不考虑swap数量的话。如果考虑数量对价格的impact,需要用到下面[例子7]的方式
  • 例子6:ethers.utils类的使用:直接看官方文档吧,这个包含了wei和ether等单位转换之类的小工具
  • 例子7:根据tokenIn的数量获取swap价格

    // 首先要构建router contract
    const router = new ethers.Contract(
    'router contract of AMM, for example pancake',
    [
      "function getAmountsOut(uint amountIn, address[] memory path) public view returns (uint[] memory amounts)",
      "function swapExactTokensForTokens(uint amountIn, uint amountOutMin, address[] calldata path, address to, uint deadline) external returns (uint[] memory amounts)",
    ],
    account
    )
    // 然后再设置tokenIn的数量。变量里的in和out分别指放入LP(sell)和从LP取出(buy)
    const amountIn = ethers.utils.parseEther("123"); // parseEther表示精度为18的token,如果精度不为18需要用另一个parse 
    const amounts = await router.getAmountsOut(amountIn, [
    'tokenIn contract address',
    'tokenOut contract',
    ]);
    // amounts是一个数组,amounts[0]和amounts[1]跟tokenIn和tokenOut的对应关系可以自己打log测试一下
    // 为什么不直接用X/Y去计算价格?因为amountIn会导致X和Y数量的变化,价格也跟着变,如果K大还好,K小的话,影响很大
  • 例子8:发起swap,接上例

    const tx = await router.swapExactTokensForTokens(
    amountIn,
    amounts[1].sub(amounts[1].div(10)), // 这个参数称为amountOutMin,如果最终swap得到的tokenOut数量低于这个值系统就不swap
    [tokenIn address, tokenOut address],
    wallet.address,
    Date.now() + 1000 * 60 * 10, // 链上排队?超过10分钟则放弃swap
    {
      gasPrice: gasPrice,
      gasLimit: "450000", // gasCost = gasPrice * min( gasLimit, gasUsed )
      nonce: null, // ???
    }
    )
    const receipt = await tx.wait();
    // receipt包含了本次swap的hash等信息

    2021.10.05补充:

  • ethers.Contract abi如果包含两个同名函数,该函数名会无法调用,即使函数的参数不一样。同名函数参数不一样,这个在solidity是允许的,并且web3.js调用也正常。这个应该是ethers.js的bug

2021.09.28补充:

  • 如果需要做event listening 或者 get past events, ethers.js没有web3.js方便,特地去看了ethers.js作者的github,作者好像也没打算对这部分进行优化,所以如果用到这两个功能,建议还是web3.js

  • ankr的免费api挺好用的,支持wss和https

TRC20主网

TRC20测试网shasta

TRC20测试网nile

TRC10主网

TRC10测试网shasta

TRC10测试网nile

总结

  • 判断token是否存在都用0==total
  • TRC20主网和测试网获取方式一样
  • TRC10主网和测试网获取方式一样

目的:用于设计合理的数据库字段存储特定地址的历史

获取最近num个区块的方式:api.trongrid.io/wallet/getblockbylatestnum?num=num

以下例子基于区块ID为32378435的数据

TRX

示例:

"ret": [
    {
        "contractRet": "SUCCESS"
    }
],
"signature": [
    "910f1ba80fc530124c909a1d046251a356fa743c0e91fc6c77f3ec2e8eef1fc30be292ba42b1dc3bb6bb7572ac234a096fe9d37c2599ffc83b3cca64fd35723a01"
],
"txID": "196d9cb5072d05dd6c5ffe89b0cde71789800abfda4ce3adfb6bd7f1cfeb7aea",
"raw_data": {
    "data": "e4b893e6b3a8e4ba8ee58cbae59d97e993bee9a1b9e79baee58c85e8a385e7ad96e58892e8bf90e890a5e4b880e7ab99e5bc8fe69c8de58aa1efbc8ce4b880e994aee58f91e5b881efbc8c44415050e5bc80e58f91efbc8ce689b9e9878fe8bdace8b4a6efbc8ce6b5b7e58685e5a496e8b4a2e7bb8fe5aa92e4bd93e5aea3e58f91efbc8ce6b3a2e59cbae58aa9e6898b68747470733a2f2f74726f6e68656c702e696fe5aea2e69c8de5beaee4bfa174726f6e617373697374616e742f74726f6e61696465",
    "contract": [
        {
            "parameter": {
                "value": {
                    "amount": 1,
                    "owner_address": "410ac5e852004d831a3ba6e6c84f290bd39e189b5e",
                    "to_address": "410986687edcd493d062fd05c326de17a64eebd89a"
                },
                "type_url": "type.googleapis.com/protocol.TransferContract"
            },
            "type": "TransferContract"
        }
    ],
    "ref_block_bytes": "0e2f",
    "ref_block_hash": "443029efb0bf917c",
    "expiration": 1627611975000,
    "timestamp": 1627611917575
},
"raw_data_hex": "0a020e2f2208443029efb0bf917c40d89aefa9af2f52c601e4b893e6b3a8e4ba8ee58cbae59d97e993bee9a1b9e79baee58c85e8a385e7ad96e58892e8bf90e890a5e4b880e7ab99e5bc8fe69c8de58aa1efbc8ce4b880e994aee58f91e5b881efbc8c44415050e5bc80e58f91efbc8ce689b9e9878fe8bdace8b4a6efbc8ce6b5b7e58685e5a496e8b4a2e7bb8fe5aa92e4bd93e5aea3e58f91efbc8ce6b3a2e59cbae58aa9e6898b68747470733a2f2f74726f6e68656c702e696fe5aea2e69c8de5beaee4bfa174726f6e617373697374616e742f74726f6e616964655a65080112610a2d747970652e676f6f676c65617069732e636f6d2f70726f746f636f6c2e5472616e73666572436f6e747261637412300a15410ac5e852004d831a3ba6e6c84f290bd39e189b5e1215410986687edcd493d062fd05c326de17a64eebd89a18017087daeba9af2f"

字段含义:

  • "type": "TransferContract"表示这是一个TRX Transfer
  • "contractRet": "SUCCESS"表示成功
  • "amount": 1为发送数额,需要除以10^精度 才是真实值,TRX精度是6
  • owner_address为发送者地址的HEX格式
  • to_address为接收者地址的HEX格式
  • data是备注,转为Utf8即可看到备注文本,如果Transfer没备注则没这个字段

TRC10

示例

"ret": [
    {
        "contractRet": "SUCCESS"
    }
],
"signature": [
    "6be003e9ae8bc24b7c320ebf716d65bbf27d77339fa2223b5e87055fc20a6d49612e2e8d481cadb8941f3bc1ed14638ef374c2fdbe338723938a5fef9412d1a900"
],
"txID": "18f7ada1eac34435357a38b12baa188fae1914d15d24c7b129663fcee0838663",
"raw_data": {
    "data": "53707265616420756e636f6e646974696f6e616c206c6f76652c206d616b65206120686170707920776f726c64",
    "contract": [
        {
            "parameter": {
                "value": {
                    "amount": 7618406,
                    "asset_name": "31303034303331",
                    "owner_address": "41ef3ebcf4eeb3f16e511d7c94dced8f318d6625ec",
                    "to_address": "4119db9504e434508bfc5f29be11a69d387780d694"
                },
                "type_url": "type.googleapis.com/protocol.TransferAssetContract"
            },
            "type": "TransferAssetContract"
        }
    ],
    "ref_block_bytes": "0e2f",
    "ref_block_hash": "443029efb0bf917c",
    "expiration": 1627611927306,
    "timestamp": 1627611867306
},
"raw_data_hex": "0a020e2f2208443029efb0bf917c408aa6eca9af2f522d53707265616420756e636f6e646974696f6e616c206c6f76652c206d616b65206120686170707920776f726c645a76080212720a32747970652e676f6f676c65617069732e636f6d2f70726f746f636f6c2e5472616e736665724173736574436f6e7472616374123c0a0731303034303331121541ef3ebcf4eeb3f16e511d7c94dced8f318d6625ec1a154119db9504e434508bfc5f29be11a69d387780d69420e6fed00370aad1e8a9af2f"

字段含义

  • "type": "TransferAssetContract"表示这是一个TRC10 Transfer
  • "contractRet": "SUCCESS"表示成功,除了SUCCESS之外还有OUT_OF_ENERGY,表示能量不足。
  • "asset_name": "31303034303331"为TRC10通证ID的HEX格式,将31303034303331转为ASCII或者Utf8字符串可得到1004031,1004031就是通证ID
  • "amount": 7618406为Transfer数额,需要除以通证的10^精度 才是真实值
  • owner_address为发送者地址的HEX格式
  • to_address为接收者地址的HEX格式
  • data是备注,转为Utf8即可看到备注文本,如果转账没备注则没这个字段
  • 遗留问题:通证的精度从哪里解析?目前只能根据通证ID再去查一遍

TRC20

示例

"ret": [
    {
        "contractRet": "SUCCESS"
    }
],
"signature": [
    "cce3f2ae7f1ed71f8221a9f1c1e17e0d6e5522acf6d9ee1a3e6947548e9dc000665eb09ede7c2918552a69ec7b995fe8c4d0c913e4c07eadce1f3fb4691a0da800"
],
"txID": "24190ffd0ef9aeac092cdbc1cb72608aa6878100b60df376e44d41772edc8b56",
"raw_data": {
    "data": "6d656d6f",
    "contract": [
        {
            "parameter": {
                "value": {
                    "data": "a9059cbb00000000000000000000000045c1098b64c646b2766ba322a704d12e709d7ba800000000000000000000000000000000000000000000001ba5abf9e779380000",
                    "owner_address": "41568b3b208d915cce7d0d785b4437e177cd2181c5",
                    "contract_address": "411fc796d0601456996b9a9c85a2fcc09a91c51065"
                },
                "type_url": "type.googleapis.com/protocol.TriggerSmartContract"
            },
            "type": "TriggerSmartContract"
        }
    ],
    "ref_block_bytes": "0e42",
    "ref_block_hash": "0d97e93a1faed6fa",
    "expiration": 1627647918000,
    "scripts": "73637269707473",
    "fee_limit": 10000000,
    "timestamp": 1627611918000
},
"raw_data_hex": "0a020e4222080d97e93a1faed6fa40b0ff80bbaf2f52046d656d6f5aae01081f12a9010a31747970652e676f6f676c65617069732e636f6d2f70726f746f636f6c2e54726967676572536d617274436f6e747261637412740a1541568b3b208d915cce7d0d785b4437e177cd2181c51215411fc796d0601456996b9a9c85a2fcc09a91c510652244a9059cbb00000000000000000000000045c1098b64c646b2766ba322a704d12e709d7ba800000000000000000000000000000000000000000000001ba5abf9e77938000062077363726970747370b0ddeba9af2f900180ade204"

字段含义:

  • raw_data_hex包含2244a9059cbb为transfer方法转账,不包含则可能是其它function的智能合约触发
  • "type": "TriggerSmartContract"表示当前为一个TRC20 Transfer
  • "contractRet": "SUCCESS"表示成功,除了SUCCESS之外还有OUT_OF_ENERGY,表示能量不足。
  • 第一个data是备注,转为Utf8即可看到备注文本,如果转账没备注则没这个字段
  • owner_address为发送者地址的HEX格式
  • contract_address为TRC20发行合约的HEX格式
  • 第二个data参数,从第33位开始连续40位抽取出来(45c1098b64c646b2766ba322a704d12e709d7ba8),前面增加“41”即为收款人的地址HEX(4145c1098b64c646b2766ba322a704d12e709d7ba8)
  • 第二个data参数,倒数64位为Transfer数量的16进制,转10进制除以该合约的10^精度 可得到真实数量

其它注意事项

  • 有些TRC20 Transfer没有timestamp字段,例如:
    703f7451e23293a629f8a5ddd62305bb2173d5efece482aef5d58d1f641752cb
    这个时候可以用expiration这个时间戳
  • 有些TRC20转账多了个call_value字段,这种不是常规的TRC20转账,第二个data字段也无法解析出相应信息,例如:
    b00b60c2c25ec540b7a65456c4001881ceb36ee36992e57cd56da104c0ac8e5e
    这个时候就放弃该条历史

如何获取所有区块上的接收者地址?

  • 遍历,如果是TRX或TRC10,则直接把to_address转base58;如果是TRC20,判断不存在call_value后,第二个data参数,从第33位开始连续40位抽取出来,前面增加“41”再转base58即为接收者的地址
  • 得到所有base58地址和对应的txID后,去跟数据库的地址取交集即需要记录历史信息的地址

前几天根据官方的教程,编译了一个maven本地仓库版本
https://gateway.pinata.cloud/ipfs/QmQxEMFLsJXZqrgyVgkjA4f5d5xAB43fx1oVbVSwvBziAX?filename=maven-tradient-0.1.3.zip

解压后的文件放本地仓库的相应位置即可

然后在项目的pom里引入:

<dependency>
    <groupId>org.tron.trident</groupId>
    <artifactId>abi</artifactId>
    <version>0.1.3</version>
</dependency>
<dependency>
    <groupId>org.tron.trident</groupId>
    <artifactId>utils</artifactId>
    <version>0.1.3</version>
</dependency>
<dependency>
    <groupId>org.tron.trident</groupId>
    <artifactId>core</artifactId>
    <version>0.1.3</version>
</dependency>

主意,不要放pom,否则无法使用

如果没引入com.google.protobuf有时会报错,建议也引入一下:

<dependency>
    <groupId>com.google.protobuf</groupId>
    <artifactId>protobuf-java</artifactId>
    <version>3.12.0</version>
</dependency>

关于如何从官方的github源码编译jar,改天有空再更新