Type/to search
8
Follow
1364
Followers
使用FMZ轻松入门基于以太坊的web3开发
Original
Created 2023-03-28 13:32:48  Updated 2024-11-11 22:28:24
 0
 4372

img

EtherEaseWithFMZ教程

使用FMZ轻松入门基于以太坊的web3开发

以太坊是一个基于区块链技术的智能合约平台,它提供了一种去中心化的方式来编写和部署智能合约。智能合约是一种特殊的计算机程序,它可以在区块链上自动执行,并且可以在不需要信任第三方的情况下实现各种业务逻辑。

发明者量化交易平台(FMZ.COM)提供了易于使用的 API,使得开发人员可以更轻松地与以太坊区块链及其生态系统进行交互。实现接入去中心化交易所(DEX),获取链上数据,发送交易等功能。

本篇教程中的例子使用JavaScript语言编写,测试环境使用以太坊主网Goerli测试网。在FMZ平台的API文档中也可以查看教程中使用到的API接口以及相关描述、代码范例。


FMZ使用入门

在学习使用FMZ量化交易平台之前,我们需要熟悉几个基本概念:

1、FMZ量化交易平台架构

在FMZ量化交易平台官网(https://www.fmz.com)注册、登录后就可以使用平台的各种功能了。FMZ网站是整个系统的管理端,用户编写的程序实际上是运行在托管者上。托管者这个软件程序可以部署在各种设备上,例如服务器、电脑等。当用户在FMZ网站上编写好程序创建运行实例时,FMZ平台就会和托管者通信,在托管者上启动一个程序实例。

img

2、托管者

如果想要运行程序实例,就必须要部署一个托管者,托管者部署也非常简单,在平台上有部署教程。也可以使用FMZ上提供的「一键部署托管者」使用FMZ代为租用的服务器自动部署。

  • 在个人设备上部署托管者

    可以在服务器、个人电脑等设备上部署运行托管者程序,只要确保网络正常即可(需要能访问到对应的目标,例如某交易所接口,节点地址等)。部署的主要步骤为:

    1、登录或者打开要部署托管者程序的设备,例如登录到服务器或者打开电脑进入操作系统
    2、下载对应版本的托管者程序(根据设备操作系统而定),下载页面:https://www.fmz.com/m/add-node
    img
    3、下载的是一个压缩包,需要解压缩。
    4、运行这个托管者程序,托管者程序是一个名为robot的可执行文件。配置托管者通信地址,这个通信地址是每个FMZ账号唯一的,登录FMZ后在https://www.fmz.com/m/add-node页面可以查看到自己的地址(即./robot -s node.fmz.com/xxxxx这一串地址,这里xxxxx位置的内容,每个FMZ账号显示都不同)。最后还需要输入FMZ账号的密码,配置好这些之后运行托管者程序即可。

  • 使用FMZ平台的「一键部署托管者」功能

    在FMZ平台上添加托管者页面,地址:https://www.fmz.com/m/add-node

    img

3、调试工具

FMZ量化交易平台提供了一个免费的调试工具,支持JavaScriptTypeScript,页面是:https://www.fmz.com/m/debug ,因为创建实例运行是计费的。初学期间可以使用这个调试工具进行测试、学习。调试工具除了限制运行时间最长为3分钟,其它方面和创建实例运行没有区别。

使用TypeScript语言时,需要在代码第一行书写// @ts-check用于切换到TypeScript模式,不切换默认是JavaScript语言。

4、交易所

在FMZ上「交易所」是一个泛指概念,对于CEX交易所来说就是指某个具体的交易所账户配置。对于web3来说,这个交易所指的是一个配置信息,包含节点地址、私钥配置。

在登录FMZ平台的状态下,在https://www.fmz.com/m/add-platform页面,可以配置交易所信息,这里交易所是泛指概念。

img

选择Web3,配置RPC节点地址,配置私钥即可,可以点击右下角「敏感信息使用独立私钥加密保存」查看安全机制。

节点可以用自建的节点,也可以用节点服务商提供的节点。节点服务商有很多,例如:Infura。注册后,可以查看到自己账号的节点地址。主网、测试网都有,比较方便,把这个节点地址配置在上图中Rpc Address的控件中。标签可以自己取名,用来区分配置的交易所对象。

img

图中https://mainnet.infura.io/v3/xxxxxxxxxxxxx就是私有的Infura的ETH主网RPC节点地址。


使用FMZ和以太坊交互

在部署好托管者程序、配置好交易所对象的前提下,就可以使用FMZ.COM的「调试工具」进行测试了。调用以太坊RPC方法和以太坊交互,除了本章节列举介绍的几个RPC方法,其它RPC方法可以查询资料了解,例如https://www.quicknode.com/docs

我们列举几个简单例子,从基础开始讲起。对于各种语言、工具来说都有访问web3的方式,如图:

img

在FMZ上对于RPC方法调用也做了封装,这些功能封装在了FMZ的API函数exchange.IO中。调用方式为exchange.IO("api", "eth", ...)。第一个参数固定传入"api",第二个参数固定传入"eth",其它参数根据具体调用的RPC方法而定。

输出信息我们就使用FMZ平台的Log函数,Log函数可以传入多个参数,然后输出在FMZ平台「调试工具」或者「实盘」页面中的日志区域,「调试工具」页面将是我们测试的主要工具。

eth_getBalance

以太坊的eth_getBalance方法用来查询某个以太坊上地址的ETH余额,这个方法需要传入2个参数。

  • 需要查询的地址。
  • 标签,一般使用"latest"。

我们来查询一下以太坊创始人V神的ETH钱包地址,已知地址为:0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045

javascript
function main() { let ethBalance = exchange.IO("api", "eth", "eth_getBalance", "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045", "latest") Log("ethBalance:", ethBalance) }

已经部署托管者(图中:linux/amd64 ...)并且配置好交易所对象(图中:Web3 test),在调试工具测试代码:

img

点击「执行」按钮,运行这段代码,显示结果:

ethBalance: 0x117296558f185bbc4c6

Log函数打印出来的ethBalance变量值为:0x117296558f185bbc4c6,是字符串类型。是十六进制值的 ETH余额,以wei为单位,1e18 wei为1ETH。所以还需要转换,才能变成可读的十进制ETH余额。

ethBalance转换为可读的数据:

javascript
function main() { let ethBalance = exchange.IO("api", "eth", "eth_getBalance", "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045", "latest") Log("ethBalance:", ethBalance) // 将ethBalance转换为可读的数据 let vitalikEthBalance = parseInt(ethBalance.substring(2), 16) / 1e18 Log("vitalikEthBalance:", vitalikEthBalance) }

img

在上https://etherscan.io/查询:

img

不过这样处理由于语言本身精度问题会有偏差,所以FMZ平台内置了两个函数用于处理数据:

  • BigInt :把十六进制字符串转换成BigInt对象。
  • BigDecimal :把数值类型对象转换成可以运算的BigDecimal对象。

再次调整代码:

javascript
function main() { let ethBalance = exchange.IO("api", "eth", "eth_getBalance", "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045", "latest") // ETH的精度单位为1e18 let ethDecimal = 18 Log("vitalikEthBalance:", Number((BigDecimal(BigInt(ethBalance)) / BigDecimal(Math.pow(10, ethDecimal))).toString())) }

vitalikEthBalance: 5149.6244846875215

eth_chainId

eth_chainIdnet_version用途差不多,所以放在一起测试。这两个函数都返回当前RPC节点接入的区块链的Id,区别是net_version返回十进制的Id,eth_chainId返回十六进制的Id。

链Id对应的网络名称

desc
1 - ethereum mainnet 2 - morden testnet (deprecated) 3 - ropsten testnet 4 - rinkeby testnet 5 - goerli testnet 11155111 - sepolia testnet 10 - optimism mainnet 69 - optimism kovan testnet 42 - kovan testnet 137 - matic/polygon mainnet 80001 - matic/polygon mumbai testnet 250 - fantom mainnet 100 - xdai mainnet 56 - bsc mainnet

img

使用配置好的以太坊测试网goerli节点测试:

javascript
function main() { let netVersionId = exchange.IO("api", "eth", "net_version") let ethChainId = exchange.IO("api", "eth", "eth_chainId") Log("netVersionId:", netVersionId) Log("ethChainId:", ethChainId, " ,转换:", parseInt(ethChainId.substring(2), 16)) }

img

eth_gasPrice

调用eth_gasPrice方法,查询当前链上的gas price

javascript
function toAmount(s, decimals) { return Number((BigDecimal(BigInt(s)) / BigDecimal(Math.pow(10, decimals))).toString()) } function main() { let gasPrice = exchange.IO("api", "eth", "eth_gasPrice") Log("gasPrice:", gasPrice, " ,转换:", toAmount(gasPrice, 0)) }

这里我们把十六进制字符串转换为可读数值的操作写成一个函数:toAmount。另外需要注意的是gasPrice的单位是wei,所以把形参decimals对应的实参传数值0即可。

eth_blockNumbe

eth_blockNumbe用来查询区块高度。

javascript
function toAmount(s, decimals) { return Number((BigDecimal(BigInt(s)) / BigDecimal(Math.pow(10, decimals))).toString()) } function main() { let blockNumber = exchange.IO("api", "eth", "eth_blockNumber") Log(toAmount(blockNumber, 0)) }

调试工具中运行:

img

https://etherscan.io/上查询:

img

eth_getBlockByNumber

查询区块信息。

javascript
function main() { let blockNumber = exchange.IO("api", "eth", "eth_blockNumber") Log(blockNumber) let blockMsg = exchange.IO("api", "eth", "eth_getBlockByNumber", blockNumber, true) Log(typeof(blockMsg), blockMsg) // 由于Log输出的内容过多,会自动截断,所以遍历返回的区块信息各个字段,逐个打印 for (let key in blockMsg) { Log("key:", key, ", val:", blockMsg[key]) } }

「调试工具」中执行可以获取以下信息:

img


读取合约信息

以太坊上运行着数量众多的智能合约应用,ENS便是其中之一。ENS,即以太坊域名服务(Ethereum Name Service),是基于以太坊区块链的去中心化域名解析服务。
还记得教程中我们查询以太坊创始人V神的钱包余额的例子吗?V神的一个钱包地址为:0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045。那么我们是如何知道这个地址的呢?其实可以通过ENS智能合约,使用一个直观的名称vitalik.eth(vitalik即V神的名字)来进行查询。

本章节以下内容使用以太坊主网环境,根据ENS文档可知需要对查询的以太坊域名进行Hashing Names,使用以下代码对vitalik.eth名称进行处理。

javascript
function nameHash(name) { if (name == "") { return "0000000000000000000000000000000000000000000000000000000000000000" } else { let arr = name.split(".") let label = arr[0] arr.shift() let remainder = arr.join(".") return Encode("sha3.keccak256", "hex", "hex", nameHash(remainder) + Encode("sha3.keccak256", "raw", "hex", label)) } }

以上代码例子中我们又看到了一个陌生的函数Encode,这个函数是FMZ平台的API函数,是专门用来在FMZ平台上进行编码操作的,该函数支持多种编码方式,支持多种哈希算法。

Encode(algo, inputFormat, outputFormat, data, keyFormat, key string)

根据ENS文档中的描述,使用sha3.keccak256算法对数据进行处理。

调用nameHash函数,例如:Log(nameHash("vitalik.eth")),可得:ee6c4522aab0003e8d14cd40a6af439055fd2577951148c14b6cea9a53475835,需要再加上"0x"前缀。0xee6c4522aab0003e8d14cd40a6af439055fd2577951148c14b6cea9a53475835作为ENS智能合约的resolver方法的参数。

javascript
let ensNode = "0x" + nameHash("vitalik.eth") // 准备好调用resolver方法的参数ensNode

查询ENS文档可知,ENS智能合约应用的合约地址为:0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e。在调用智能合约的resolver方法前,我们还需要准备好合约的ABI

注册ABI

学习到这里不禁会问,什么是智能合约的ABI呢?

desc
ABI,即应用程序二进制接口(Application Binary Interface),是智能合约与外部世界进行通信的接口标准。 智能合约的 ABI 定义了合约的函数接口、参数类型、返回值等信息,以及调用合约的方式和参数传递方式等规范。 智能合约的 ABI 通常以 JSON 格式存储,包含以下信息: 合约的函数接口:函数名、参数列表、返回值等信息。 函数参数类型:如 uint256、bool、string 等。 函数的输入参数和输出参数的编码方式:智能合约使用一种称为 Solidity ABI 的编码方式来编码函数的输入参数和输出参数, 以便与以太坊网络进行交互。 在以太坊网络中,使用智能合约的 ABI 来调用合约的函数。当需要调用合约函数时,需要提供函数名和函数参数,以及将函数参数按照 ABI 编码方式编码后的字节码。 以太坊节点会将这些信息打包成一笔交易,并将交易发送到以太坊网络中执行。 智能合约的 ABI 在 Solidity 语言中可以通过 interface 关键字来定义。以太坊开发工具如 Remix IDE、Truffle 等也提供了 ABI 编辑和生成工具, 使得开发者可以方便地创建和使用智能合约的 ABI。

摘出ENS的ABI中的resolver方法的部分,也可以使用完整的ABI,可以在https://etherscan.io/上查询到合约的ABI,或者通过其它途径获取ABI(例如:相关项目文档)。

img

javascript
let abiENS_resolver = `[{"constant":true,"inputs":[{"internalType":"bytes32","name":"node","type":"bytes32"}],"name":"resolver","outputs":[{"internalType":"address","name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"}]`

这里我们又要学到在FMZ平台上一个新的调用方法,exchange.IO("abi", address, abiContent),使用该方法注册ABI,address参数是智能合约的地址,abiContent参数是对应的智能合约ABI(字符串)。

javascript
let abiENS_resolver = `[{"constant":true,"inputs":[{"internalType":"bytes32","name":"node","type":"bytes32"}],"name":"resolver","outputs":[{"internalType":"address","name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"}]` exchange.IO("abi", "0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e", abiENS_resolver) // 0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e 是在以太坊主网上部署的ENS智能合约的地址

调用智能合约的方法

接下来就可以调用ENS智能合约的resolver方法了,该方法返回ENS: Public Resolver合约的地址。

img

javascript
let resolverAddress = exchange.IO("api", "0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e", "resolver", ensNode)

使用ENS: Public Resolver合约的addr方法获取V神的钱包地址。要调用ENS: Public Resolver合约依然需要先注册ABI。这个智能合约的ABI信息依然可以从https://etherscan.io/获取。

javascript
let abiENSPublicResolver = `[{"inputs":[{"internalType":"contract ENS","name":"_ens","type":"address"}],"payable":false,"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"node","type":"bytes32"},{"indexed":true,"internalType":"uint256","name":"contentType","type":"uint256"}],"name":"ABIChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"node","type":"bytes32"},{"indexed":false,"internalType":"address","name":"a","type":"address"}],"name":"AddrChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"node","type":"bytes32"},{"indexed":false,"internalType":"uint256","name":"coinType","type":"uint256"},{"indexed":false,"internalType":"bytes","name":"newAddress","type":"bytes"}],"name":"AddressChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"node","type":"bytes32"},{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"target","type":"address"},{"indexed":false,"internalType":"bool","name":"isAuthorised","type":"bool"}],"name":"AuthorisationChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"node","type":"bytes32"},{"indexed":false,"internalType":"bytes","name":"hash","type":"bytes"}],"name":"ContenthashChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"node","type":"bytes32"},{"indexed":false,"internalType":"bytes","name":"name","type":"bytes"},{"indexed":false,"internalType":"uint16","name":"resource","type":"uint16"},{"indexed":false,"internalType":"bytes","name":"record","type":"bytes"}],"name":"DNSRecordChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"node","type":"bytes32"},{"indexed":false,"internalType":"bytes","name":"name","type":"bytes"},{"indexed":false,"internalType":"uint16","name":"resource","type":"uint16"}],"name":"DNSRecordDeleted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"node","type":"bytes32"}],"name":"DNSZoneCleared","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"node","type":"bytes32"},{"indexed":true,"internalType":"bytes4","name":"interfaceID","type":"bytes4"},{"indexed":false,"internalType":"address","name":"implementer","type":"address"}],"name":"InterfaceChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"node","type":"bytes32"},{"indexed":false,"internalType":"string","name":"name","type":"string"}],"name":"NameChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"node","type":"bytes32"},{"indexed":false,"internalType":"bytes32","name":"x","type":"bytes32"},{"indexed":false,"internalType":"bytes32","name":"y","type":"bytes32"}],"name":"PubkeyChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"node","type":"bytes32"},{"indexed":true,"internalType":"string","name":"indexedKey","type":"string"},{"indexed":false,"internalType":"string","name":"key","type":"string"}],"name":"TextChanged","type":"event"},{"constant":true,"inputs":[{"internalType":"bytes32","name":"node","type":"bytes32"},{"internalType":"uint256","name":"contentTypes","type":"uint256"}],"name":"ABI","outputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bytes","name":"","type":"bytes"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"internalType":"bytes32","name":"node","type":"bytes32"}],"name":"addr","outputs":[{"internalType":"address payable","name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"internalType":"bytes32","name":"node","type":"bytes32"},{"internalType":"uint256","name":"coinType","type":"uint256"}],"name":"addr","outputs":[{"internalType":"bytes","name":"","type":"bytes"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"internalType":"bytes32","name":"","type":"bytes32"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"authorisations","outputs":[{"internalType":"bool","name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"internalType":"bytes32","name":"node","type":"bytes32"}],"name":"clearDNSZone","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"internalType":"bytes32","name":"node","type":"bytes32"}],"name":"contenthash","outputs":[{"internalType":"bytes","name":"","type":"bytes"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"internalType":"bytes32","name":"node","type":"bytes32"},{"internalType":"bytes32","name":"name","type":"bytes32"},{"internalType":"uint16","name":"resource","type":"uint16"}],"name":"dnsRecord","outputs":[{"internalType":"bytes","name":"","type":"bytes"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"internalType":"bytes32","name":"node","type":"bytes32"},{"internalType":"bytes32","name":"name","type":"bytes32"}],"name":"hasDNSRecords","outputs":[{"internalType":"bool","name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"internalType":"bytes32","name":"node","type":"bytes32"},{"internalType":"bytes4","name":"interfaceID","type":"bytes4"}],"name":"interfaceImplementer","outputs":[{"internalType":"address","name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"internalType":"bytes[]","name":"data","type":"bytes[]"}],"name":"multicall","outputs":[{"internalType":"bytes[]","name":"results","type":"bytes[]"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"internalType":"bytes32","name":"node","type":"bytes32"}],"name":"name","outputs":[{"internalType":"string","name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"internalType":"bytes32","name":"node","type":"bytes32"}],"name":"pubkey","outputs":[{"internalType":"bytes32","name":"x","type":"bytes32"},{"internalType":"bytes32","name":"y","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"internalType":"bytes32","name":"node","type":"bytes32"},{"internalType":"uint256","name":"contentType","type":"uint256"},{"internalType":"bytes","name":"data","type":"bytes"}],"name":"setABI","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"internalType":"bytes32","name":"node","type":"bytes32"},{"internalType":"uint256","name":"coinType","type":"uint256"},{"internalType":"bytes","name":"a","type":"bytes"}],"name":"setAddr","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"internalType":"bytes32","name":"node","type":"bytes32"},{"internalType":"address","name":"a","type":"address"}],"name":"setAddr","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"internalType":"bytes32","name":"node","type":"bytes32"},{"internalType":"address","name":"target","type":"address"},{"internalType":"bool","name":"isAuthorised","type":"bool"}],"name":"setAuthorisation","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"internalType":"bytes32","name":"node","type":"bytes32"},{"internalType":"bytes","name":"hash","type":"bytes"}],"name":"setContenthash","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"internalType":"bytes32","name":"node","type":"bytes32"},{"internalType":"bytes","name":"data","type":"bytes"}],"name":"setDNSRecords","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"internalType":"bytes32","name":"node","type":"bytes32"},{"internalType":"bytes4","name":"interfaceID","type":"bytes4"},{"internalType":"address","name":"implementer","type":"address"}],"name":"setInterface","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"internalType":"bytes32","name":"node","type":"bytes32"},{"internalType":"string","name":"name","type":"string"}],"name":"setName","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"internalType":"bytes32","name":"node","type":"bytes32"},{"internalType":"bytes32","name":"x","type":"bytes32"},{"internalType":"bytes32","name":"y","type":"bytes32"}],"name":"setPubkey","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"internalType":"bytes32","name":"node","type":"bytes32"},{"internalType":"string","name":"key","type":"string"},{"internalType":"string","name":"value","type":"string"}],"name":"setText","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"internalType":"bytes4","name":"interfaceID","type":"bytes4"}],"name":"supportsInterface","outputs":[{"internalType":"bool","name":"","type":"bool"}],"payable":false,"stateMutability":"pure","type":"function"},{"constant":true,"inputs":[{"internalType":"bytes32","name":"node","type":"bytes32"},{"internalType":"string","name":"key","type":"string"}],"name":"text","outputs":[{"internalType":"string","name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"}]` exchange.IO("abi", resolverAddress, abiENSPublicResolver)

img

最后调用ENS: Public Resolver合约的addr方法,参数依然为ensNode

javascript
let vitalikAddress = exchange.IO("api", resolverAddress, "addr", ensNode) Log("vitalikAddress:", vitalikAddress)

Log函数输出:

img

run
vitalikAddress: 0xd8da6bf26964af9d7eed9e03e53415d37aa96045

调用ENS的完整代码

javascript
function nameHash(name) { if (name == "") { return "0000000000000000000000000000000000000000000000000000000000000000" } else { let arr = name.split(".") let label = arr[0] arr.shift() let remainder = arr.join(".") return Encode("sha3.keccak256", "hex", "hex", nameHash(remainder) + Encode("sha3.keccak256", "raw", "hex", label)) } } function main() { // 计算名称 let ensNode = "0x" + nameHash("vitalik.eth") // 注册ENS合约 let abiENS_resolver = `[{"constant":true,"inputs":[{"internalType":"bytes32","name":"node","type":"bytes32"}],"name":"resolver","outputs":[{"internalType":"address","name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"}]` exchange.IO("abi", "0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e", abiENS_resolver) let resolverAddress = exchange.IO("api", "0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e", "resolver", ensNode) // 注册ENS Public Resolver合约 let abiENSPublicResolver = `[{"inputs":[{"internalType":"contract ENS","name":"_ens","type":"address"}],"payable":false,"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"node","type":"bytes32"},{"indexed":true,"internalType":"uint256","name":"contentType","type":"uint256"}],"name":"ABIChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"node","type":"bytes32"},{"indexed":false,"internalType":"address","name":"a","type":"address"}],"name":"AddrChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"node","type":"bytes32"},{"indexed":false,"internalType":"uint256","name":"coinType","type":"uint256"},{"indexed":false,"internalType":"bytes","name":"newAddress","type":"bytes"}],"name":"AddressChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"node","type":"bytes32"},{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"target","type":"address"},{"indexed":false,"internalType":"bool","name":"isAuthorised","type":"bool"}],"name":"AuthorisationChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"node","type":"bytes32"},{"indexed":false,"internalType":"bytes","name":"hash","type":"bytes"}],"name":"ContenthashChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"node","type":"bytes32"},{"indexed":false,"internalType":"bytes","name":"name","type":"bytes"},{"indexed":false,"internalType":"uint16","name":"resource","type":"uint16"},{"indexed":false,"internalType":"bytes","name":"record","type":"bytes"}],"name":"DNSRecordChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"node","type":"bytes32"},{"indexed":false,"internalType":"bytes","name":"name","type":"bytes"},{"indexed":false,"internalType":"uint16","name":"resource","type":"uint16"}],"name":"DNSRecordDeleted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"node","type":"bytes32"}],"name":"DNSZoneCleared","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"node","type":"bytes32"},{"indexed":true,"internalType":"bytes4","name":"interfaceID","type":"bytes4"},{"indexed":false,"internalType":"address","name":"implementer","type":"address"}],"name":"InterfaceChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"node","type":"bytes32"},{"indexed":false,"internalType":"string","name":"name","type":"string"}],"name":"NameChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"node","type":"bytes32"},{"indexed":false,"internalType":"bytes32","name":"x","type":"bytes32"},{"indexed":false,"internalType":"bytes32","name":"y","type":"bytes32"}],"name":"PubkeyChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"node","type":"bytes32"},{"indexed":true,"internalType":"string","name":"indexedKey","type":"string"},{"indexed":false,"internalType":"string","name":"key","type":"string"}],"name":"TextChanged","type":"event"},{"constant":true,"inputs":[{"internalType":"bytes32","name":"node","type":"bytes32"},{"internalType":"uint256","name":"contentTypes","type":"uint256"}],"name":"ABI","outputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bytes","name":"","type":"bytes"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"internalType":"bytes32","name":"node","type":"bytes32"}],"name":"addr","outputs":[{"internalType":"address payable","name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"internalType":"bytes32","name":"node","type":"bytes32"},{"internalType":"uint256","name":"coinType","type":"uint256"}],"name":"addr","outputs":[{"internalType":"bytes","name":"","type":"bytes"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"internalType":"bytes32","name":"","type":"bytes32"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"authorisations","outputs":[{"internalType":"bool","name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"internalType":"bytes32","name":"node","type":"bytes32"}],"name":"clearDNSZone","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"internalType":"bytes32","name":"node","type":"bytes32"}],"name":"contenthash","outputs":[{"internalType":"bytes","name":"","type":"bytes"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"internalType":"bytes32","name":"node","type":"bytes32"},{"internalType":"bytes32","name":"name","type":"bytes32"},{"internalType":"uint16","name":"resource","type":"uint16"}],"name":"dnsRecord","outputs":[{"internalType":"bytes","name":"","type":"bytes"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"internalType":"bytes32","name":"node","type":"bytes32"},{"internalType":"bytes32","name":"name","type":"bytes32"}],"name":"hasDNSRecords","outputs":[{"internalType":"bool","name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"internalType":"bytes32","name":"node","type":"bytes32"},{"internalType":"bytes4","name":"interfaceID","type":"bytes4"}],"name":"interfaceImplementer","outputs":[{"internalType":"address","name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"internalType":"bytes[]","name":"data","type":"bytes[]"}],"name":"multicall","outputs":[{"internalType":"bytes[]","name":"results","type":"bytes[]"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"internalType":"bytes32","name":"node","type":"bytes32"}],"name":"name","outputs":[{"internalType":"string","name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"internalType":"bytes32","name":"node","type":"bytes32"}],"name":"pubkey","outputs":[{"internalType":"bytes32","name":"x","type":"bytes32"},{"internalType":"bytes32","name":"y","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"internalType":"bytes32","name":"node","type":"bytes32"},{"internalType":"uint256","name":"contentType","type":"uint256"},{"internalType":"bytes","name":"data","type":"bytes"}],"name":"setABI","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"internalType":"bytes32","name":"node","type":"bytes32"},{"internalType":"uint256","name":"coinType","type":"uint256"},{"internalType":"bytes","name":"a","type":"bytes"}],"name":"setAddr","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"internalType":"bytes32","name":"node","type":"bytes32"},{"internalType":"address","name":"a","type":"address"}],"name":"setAddr","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"internalType":"bytes32","name":"node","type":"bytes32"},{"internalType":"address","name":"target","type":"address"},{"internalType":"bool","name":"isAuthorised","type":"bool"}],"name":"setAuthorisation","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"internalType":"bytes32","name":"node","type":"bytes32"},{"internalType":"bytes","name":"hash","type":"bytes"}],"name":"setContenthash","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"internalType":"bytes32","name":"node","type":"bytes32"},{"internalType":"bytes","name":"data","type":"bytes"}],"name":"setDNSRecords","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"internalType":"bytes32","name":"node","type":"bytes32"},{"internalType":"bytes4","name":"interfaceID","type":"bytes4"},{"internalType":"address","name":"implementer","type":"address"}],"name":"setInterface","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"internalType":"bytes32","name":"node","type":"bytes32"},{"internalType":"string","name":"name","type":"string"}],"name":"setName","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"internalType":"bytes32","name":"node","type":"bytes32"},{"internalType":"bytes32","name":"x","type":"bytes32"},{"internalType":"bytes32","name":"y","type":"bytes32"}],"name":"setPubkey","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"internalType":"bytes32","name":"node","type":"bytes32"},{"internalType":"string","name":"key","type":"string"},{"internalType":"string","name":"value","type":"string"}],"name":"setText","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"internalType":"bytes4","name":"interfaceID","type":"bytes4"}],"name":"supportsInterface","outputs":[{"internalType":"bool","name":"","type":"bool"}],"payable":false,"stateMutability":"pure","type":"function"},{"constant":true,"inputs":[{"internalType":"bytes32","name":"node","type":"bytes32"},{"internalType":"string","name":"key","type":"string"}],"name":"text","outputs":[{"internalType":"string","name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"}]` exchange.IO("abi", resolverAddress, abiENSPublicResolver) let vitalikAddress = exchange.IO("api", resolverAddress, "addr", ensNode) Log("vitalikAddress:", vitalikAddress) }

发送ETH

在之前的课程章节中我们已经学过了如何配置私钥,对于配置完成的交易所对象我们如何知道这个私钥对应的钱包地址呢?在FMZ上可以使用exchange.IO("address")函数获取配置的私钥对应的钱包地址。

由于本章节以下内容使用Goerli测试网环境,所以节点我使用的是:https://goerli.infura.io/v3/*******,infura给每个注册用户分配的节点地址都不同,这里*******隐藏了具体内容。

javascript
function main() { let walletAddress = exchange.IO("address") Log("测试网 goerli 钱包地址:", walletAddress) }

img

知道了自己的钱包地址之后,就可以使用以太坊的RPC方法eth_getTransactionCount查询钱包地址的交易计数。在以太坊中这个计数十分常用,其实就是转账操作时需要传入的nonce参数,在以太坊中,nonce是用来确保每个交易都是唯一的数字。它是一个递增的数字,每次发送一个新的交易时就会自动增加。因此,当您向智能合约发送交易时,需要提供一个nonce,以确保交易是唯一的并且顺序正确。在一些资料、文档中我们就可以查询到:

https://goethereumbook.org/en/

img

这里Go语言的以太坊库中的PendingNonceAt函数实际就是调用的eth_getTransactionCount方法。在之前的课程中我们也学过了如何调用RPC方法,我们这里再次使用exchange.IO("api", "eth", ...)函数。

javascript
function toAmount(s, decimals) { return Number((BigDecimal(BigInt(s)) / BigDecimal(Math.pow(10, decimals))).toString()) } function main() { let walletAddress = exchange.IO("address") Log("测试网 goerli 钱包地址:", walletAddress) /** * eth_getTransactionCount * @param address - string - The address from which the transaction count to be checked. * @param blockNumber - string - The block number as a string in hexadecimal format or tags. * @returns The integer of the number of transactions sent from an address encoded as hexadecimal. */ let nonce = exchange.IO("api", "eth", "eth_getTransactionCount", walletAddress, "pending") Log("钱包地址:", walletAddress, "当前的 nonce:", nonce, ",转换为10进制:", toAmount(nonce, 0)) }

在讲解转账操作之前,我们简单了解一些概念,在以太坊上转账时会消耗一定的ETH代币(作为gas费用)。这个gas费用具体由两个参数决定:

  • gasPrice

    然而,以太坊网络上的燃气费用始终根据市场需求和用户愿意支付的费用而波动,因此在代码中写入固定的燃气费用有时不是理想的选择。可以使用我们之前学过的eth_gasPrice方法,该方法可以获取平均燃气价格。

  • gasLimit

    一个标准的以太币转账的燃气限制是21000个单位。

了解了noncegasPricegasLimit这些概念,就可以测试转账了。在FMZ上封装非常简单易用的转账函数。

javascript
exchange.IO("api", "eth", "send", toAddress, toAmount)

作为转账使用时,exchange.IO的第三个参数固定写为"send",toAddress参数为转账时接收ETH的地址,toAmount为转账的ETH数量。

noncegasPricegasLimit这些参数在FMZ上都可以使用系统默认自动获取的值。也可以指定:

javascript
exchange.IO("api", "eth", "send", toAddress, toAmount, {gasPrice: 5000000000, gasLimit: 21000, nonce: 100})

接下来我们在测试网 goerli 上向某个地址转账一定的ETH:

javascript
function toInnerAmount(s, decimals) { return (BigDecimal(s)*BigDecimal(Math.pow(10, decimals))).toFixed(0) } function main() { let walletAddress = exchange.IO("address") Log("测试网 goerli 钱包地址:", walletAddress) let ret = exchange.IO("api", "eth", "send", "0x4D75a08E870674E68cAE611f329A27f446A66813", toInnerAmount(0.01, 18)) return ret // 返回Transaction Hash : 0xa6f9f51b00d8ae850b0f204380b59da98f4bbce34b813577d3d948f61de4734e }

因为以太坊转账数量的单位是wei,需要使用一个自定义函数toInnerAmount处理为以wei为单位的数值。

https://etherscan.io/上查询Transaction Hash:0xa6f9f51b00d8ae850b0f204380b59da98f4bbce34b813577d3d948f61de4734e

img

也可以编写代码查询转账哈希0xa6f9f51b00d8ae850b0f204380b59da98f4bbce34b813577d3d948f61de4734e,使用eth_getTransactionReceipt方法进行查询。

javascript
function main() { let transHash = "0xa6f9f51b00d8ae850b0f204380b59da98f4bbce34b813577d3d948f61de4734e" let info = exchange.IO("api", "eth", "eth_getTransactionReceipt", transHash) return info }

查询结果:

run
{ "cumulativeGasUsed": "0x200850", "effectiveGasPrice": "0x1748774421", "transactionHash": "0xa6f9f51b00d8ae850b0f204380b59da98f4bbce34b813577d3d948f61de4734e", "type": "0x0", "blockHash": "0x6bdde8b0f0453ecd24eecf7c634d65306f05511e0e8f09f9ed3f59eee2d06ac7", "contractAddress": null, "blockNumber": "0x868a50", "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "gasUsed": "0x5208", "to": "0x4d75a08e870674e68cae611f329a27f446a66813", "status": "0x1", "transactionIndex": "0x23", "from": "0x6b3f11d807809b0b1e5e3243df04a280d9f94bf4", "logs": [] }

每个字段对应的描述:

desc
blockHash - 该交易所在区块的哈希值 blockNumber - 以十六进制编码的该交易所在区块的块号 contractAddress - 如果是合约创建,该合约的地址;否则为null cumulativeGasUsed - 该交易在区块中执行时使用的总燃气量 effectiveGasPrice - 每单位燃气的总基础费用加小费 from - 发送者的地址 gasUsed - 该特定交易使用的燃气量 logs - 生成该交易的日志对象数组 address - 生成该日志的地址 topics - 0到4个32字节索引日志参数的数据数组。在Solidity中,第一个主题是事件签名的哈希值(例如Deposit(address,bytes32,uint256)),除非你使用匿名说明符声明该事件 data - 日志的32字节非索引参数 blockNumber - 该日志所在区块的块号 transactionHash - 该日志创建时的交易哈希值。如果该日志处于待定状态,则为null transactionIndex - 该日志创建时的交易索引位置。如果该日志处于待定状态,则为null blockHash - 该日志所在区块的哈希值 logIndex - 该日志在区块中的索引位置,以十六进制编码的整数。如果该日志处于待定状态,则为null removed - 如果该日志已被删除,则为true,由于链重组而被删除;如果是有效的日志,则为false logsBloom - 用于检索相关日志的布隆过滤器 status - 以十六进制编码的值,它要么是1(成功),要么是0(失败) to - 接收者的地址。如果是合约创建交易,则为null transactionHash - 该交易的哈希值 transactionIndex - 以十六进制编码的该交易在区块中的索引位置 type - 值的类型

调用以太坊智能合约

我们在读取合约信息这一章节中通过一个完整的例子,调用以太坊上部署的ENS合约的方法获取了V神的钱包地址。这些方法属于Read方法,调用这些方法是不需要gas的(还记得我们之前讲的gas是什么吗?)。本章节我们将调用一些以太坊上的智能合约的Write方法并且支付gas。这些操作将由整个网络上的每个节点以及矿工验证,并改变区块链状态。

ERC20

对于ERC20合约来说(ERC20代币合约),FMZ平台把ERC20合约的ABI列为常用的ABI直接内置在系统中,省去了注册ABI这一步。对于ABI我们在之前的教程中也有所了解,我们调用ENS合约方法时先注册了ENS合约的ABI。

为了更加清楚的了解ABI,在使用前可以先查看一下,以下是ERC20合约的ABI:

javascript
[{"constant":true,"inputs":[],"name":"name","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"guy","type":"address"},{"name":"wad","type":"uint256"}],"name":"approve","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"totalSupply","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"src","type":"address"},{"name":"dst","type":"address"},{"name":"wad","type":"uint256"}],"name":"transferFrom","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"wad","type":"uint256"}],"name":"withdraw","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"decimals","outputs":[{"name":"","type":"uint8"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"","type":"address"}],"name":"balanceOf","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"symbol","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"dst","type":"address"},{"name":"wad","type":"uint256"}],"name":"transfer","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[],"name":"deposit","outputs":[],"payable":true,"stateMutability":"payable","type":"function"},{"constant":true,"inputs":[{"name":"","type":"address"},{"name":"","type":"address"}],"name":"allowance","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"payable":true,"stateMutability":"payable","type":"fallback"},{"anonymous":false,"inputs":[{"indexed":true,"name":"src","type":"address"},{"indexed":true,"name":"guy","type":"address"},{"indexed":false,"name":"wad","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"src","type":"address"},{"indexed":true,"name":"dst","type":"address"},{"indexed":false,"name":"wad","type":"uint256"}],"name":"Transfer","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"dst","type":"address"},{"indexed":false,"name":"wad","type":"uint256"}],"name":"Deposit","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"src","type":"address"},{"indexed":false,"name":"wad","type":"uint256"}],"name":"Withdrawal","type":"event"}]

本章节以下内容使用Goerli测试网环境。

balanceOf

接下来我们重新再练习一次如何调用合约的Read方法读取合约信息,调用ERC20合约的balanceOf方法查询代币余额,balanceOf方法只有一个参数,但是没有命名,通过类型可以看到是一个地址(即查询的代币地址)。由于返回的数据并不是以一个代币为单位的,所以还需要代币的精度数据来换算一下,代币的精度可以用ERC20合约的decimals方法获取。我们使用以太坊测试网goerli进行测试,注意在不同的链上代币合约地址可能也不同。

javascript
function toAmount(s, decimals) { return Number((BigDecimal(BigInt(s)) / BigDecimal(Math.pow(10, decimals))).toString()) } function main() { let walletAddress = exchange.IO("address") // goerli WETH address let wethAddress = "0xB4FBF271143F4FBf7B91A5ded31805e42b2208d6" // goerli LINK address let linkAddress = "0x326C977E6efc84E512bB9C30f76E30c160eD06FB" // 由于是ERC20合约,FMZ已经内置ABI注册,所以这里不用注册ERC20 ABI let wethDecimals = exchange.IO("api", wethAddress, "decimals") let linkDecimals = exchange.IO("api", linkAddress, "decimals") let wethBalance = exchange.IO("api", wethAddress, "balanceOf", walletAddress) let linkBalance = exchange.IO("api", linkAddress, "balanceOf", walletAddress) Log("WETH精度:", wethDecimals, "wethBalance:", toAmount(wethBalance, wethDecimals)) Log("LINK精度:", linkDecimals, "linkBalance:", toAmount(linkBalance, linkDecimals)) }

运行以上代码,可以查询到当前钱包的WETH、LINK代币余额:

WETH精度: 18 wethBalance: 0
LINK精度: 18 linkBalance: 51.41374973681053

deposit

可以看到这个钱包中WETH代币的余额是0,接下来我们继续和WETH代币的ERC20智能合约交互,这次我们调用Write类型的方法:deposit
deposit方法的作用简单来说就是可以把一定量的ETH换为WETH,需要注意deposit方法没有参数(可以通过查看ABI观察到),该方法的属性payabletrue,所以调用时需要设置转账ETH(payableAmount)数量,用来指定存入的ETH数量。

调用智能合约方法时注意:
如果调用的方法具有payable属性,需要在方法名后加一个转账ETH的值(exchange.IO函数的第四个参数),可以是数值类型,也可以传字符串形式的数值。

javascript
function toAmount(s, decimals) { return Number((BigDecimal(BigInt(s)) / BigDecimal(Math.pow(10, decimals))).toString()) } function toInnerAmount(s, decimals) { return (BigDecimal(s)*BigDecimal(Math.pow(10, decimals))).toFixed(0) } function main() { let walletAddress = exchange.IO("address") // goerli WETH address let wethAddress = "0xB4FBF271143F4FBf7B91A5ded31805e42b2208d6" // 由于是ERC20合约,FMZ已经内置ABI注册,所以这里不用注册ERC20 ABI let wethDecimals = exchange.IO("api", wethAddress, "decimals") let wethBalance = exchange.IO("api", wethAddress, "balanceOf", walletAddress) Log("WETH精度:", wethDecimals, "wethBalance:", toAmount(wethBalance, wethDecimals)) let ethBalance = exchange.IO("api", "eth", "eth_getBalance", walletAddress, "latest") Log("ETH精度:", 18, "ethBalance:", toAmount(ethBalance, 18)) // 调用deposit方法,由于deposit是ERC20标准之外的方法,所以这里要注册这个方法的ABI let abiWETH = `[{"constant":true,"inputs":[],"name":"name","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"guy","type":"address"},{"name":"wad","type":"uint256"}],"name":"approve","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"totalSupply","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"src","type":"address"},{"name":"dst","type":"address"},{"name":"wad","type":"uint256"}],"name":"transferFrom","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"wad","type":"uint256"}],"name":"withdraw","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"decimals","outputs":[{"name":"","type":"uint8"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"","type":"address"}],"name":"balanceOf","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"symbol","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"dst","type":"address"},{"name":"wad","type":"uint256"}],"name":"transfer","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[],"name":"deposit","outputs":[],"payable":true,"stateMutability":"payable","type":"function"},{"constant":true,"inputs":[{"name":"","type":"address"},{"name":"","type":"address"}],"name":"allowance","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"payable":true,"stateMutability":"payable","type":"fallback"},{"anonymous":false,"inputs":[{"indexed":true,"name":"src","type":"address"},{"indexed":true,"name":"guy","type":"address"},{"indexed":false,"name":"wad","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"src","type":"address"},{"indexed":true,"name":"dst","type":"address"},{"indexed":false,"name":"wad","type":"uint256"}],"name":"Transfer","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"dst","type":"address"},{"indexed":false,"name":"wad","type":"uint256"}],"name":"Deposit","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"src","type":"address"},{"indexed":false,"name":"wad","type":"uint256"}],"name":"Withdrawal","type":"event"}]` exchange.IO("abi", wethAddress, abiWETH) let payableAmount = toInnerAmount(0.01, 18) let ret = exchange.IO("api", wethAddress, "deposit", payableAmount) Log("Transaction Hash:", ret) }

WETH精度: 18 wethBalance: 0
ETH精度: 18 ethBalance: 0.14333094664072302
Transaction Hash: 0xaf15b0b0e25a81eda583295e82b249e2d02e4eafebecc906470ccc2c89e23563

img

再次查询WETH和ETH的余额:

WETH精度: 18 wethBalance: 0.01
ETH精度: 18 ethBalance: 0.1333309358060905

调用deposit之前WETH是0,ETH是0.14333094664072302,调用deposit之后WETH是0.01,ETH是0.1333309358060905。可以看到已经成功的把0.01ETH换为了WETH

transfer

ERC20代币也可以转账,使用transfer方法转给这个地址0x4D75a08E870674E68cAE611f329A27f446A668130.01个WETH代币。transfer方法有两个参数,第一个参数dst是转账接收方的钱包地址,第二个参数wad是转账数额。

javascript
function toAmount(s, decimals) { return Number((BigDecimal(BigInt(s)) / BigDecimal(Math.pow(10, decimals))).toString()) } function toInnerAmount(s, decimals) { return (BigDecimal(s)*BigDecimal(Math.pow(10, decimals))).toFixed(0) } function waitMined (tx) { for (var i = 0 ; i < 10 ; i++) { Sleep(5000) let info = exchange.IO("api", "eth", "eth_getTransactionReceipt", tx) if (info && info.gasUsed) { Log(info) return true } Log('Transaction not yet mined', tx) } return false } function main() { let walletAddress = exchange.IO("address") // goerli WETH address let wethAddress = "0xB4FBF271143F4FBf7B91A5ded31805e42b2208d6" // 由于是ERC20合约,FMZ已经内置ABI注册,所以这里不用注册ERC20 ABI let wethDecimals = exchange.IO("api", wethAddress, "decimals") let wethBalance = exchange.IO("api", wethAddress, "balanceOf", walletAddress) Log("WETH精度:", wethDecimals, "wethBalance:", toAmount(wethBalance, wethDecimals)) let dst = "0x4D75a08E870674E68cAE611f329A27f446A66813" let wad = toInnerAmount(0.01, wethDecimals) let tx = exchange.IO("api", wethAddress, "transfer", dst, wad) Log("Transaction Hash:", tx) waitMined(tx) wethBalance = exchange.IO("api", wethAddress, "balanceOf", walletAddress) Log("WETH精度:", wethDecimals, "wethBalance:", toAmount(wethBalance, wethDecimals)) }

img

以上例子中使用了一个自定义函数waitMined,其实这个函数的功能我们也并不陌生,还记得eth_getTransactionReceipt方法吗?这个waitMined自定义函数的作用就是等待获取传入的Transaction Hash的结果。

对于这次WETH转账,有兴趣的读者也可以查询该Transaction Hash0x2fafb62b548a1fffb0f3189429e3c5a4f57ddafb0acbc0678d8b3cf0a2f7c92c来查看细节(注意这个是在测试网goerli)。

withdraw

这次我们把WETH换回ETH,使用withdraw方法,该方法只有一个参数wad,简单来说就是用来设置换回多少ETH。

javascript
function toAmount(s, decimals) { return Number((BigDecimal(BigInt(s)) / BigDecimal(Math.pow(10, decimals))).toString()) } function toInnerAmount(s, decimals) { return (BigDecimal(s)*BigDecimal(Math.pow(10, decimals))).toFixed(0) } function main() { let walletAddress = exchange.IO("address") // goerli WETH address let wethAddress = "0xB4FBF271143F4FBf7B91A5ded31805e42b2208d6" // 由于是ERC20合约,FMZ已经内置ABI注册,所以这里不用注册ERC20 ABI let wethDecimals = exchange.IO("api", wethAddress, "decimals") let wethBalance = exchange.IO("api", wethAddress, "balanceOf", walletAddress) Log("WETH精度:", wethDecimals, "wethBalance:", toAmount(wethBalance, wethDecimals)) let ethBalance = exchange.IO("api", "eth", "eth_getBalance", walletAddress, "latest") Log("ETH精度:", 18, "ethBalance:", toAmount(ethBalance, 18)) let abiWETH = `[{"constant":true,"inputs":[],"name":"name","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"guy","type":"address"},{"name":"wad","type":"uint256"}],"name":"approve","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"totalSupply","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"src","type":"address"},{"name":"dst","type":"address"},{"name":"wad","type":"uint256"}],"name":"transferFrom","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"wad","type":"uint256"}],"name":"withdraw","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"decimals","outputs":[{"name":"","type":"uint8"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"","type":"address"}],"name":"balanceOf","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"symbol","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"dst","type":"address"},{"name":"wad","type":"uint256"}],"name":"transfer","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[],"name":"deposit","outputs":[],"payable":true,"stateMutability":"payable","type":"function"},{"constant":true,"inputs":[{"name":"","type":"address"},{"name":"","type":"address"}],"name":"allowance","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"payable":true,"stateMutability":"payable","type":"fallback"},{"anonymous":false,"inputs":[{"indexed":true,"name":"src","type":"address"},{"indexed":true,"name":"guy","type":"address"},{"indexed":false,"name":"wad","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"src","type":"address"},{"indexed":true,"name":"dst","type":"address"},{"indexed":false,"name":"wad","type":"uint256"}],"name":"Transfer","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"dst","type":"address"},{"indexed":false,"name":"wad","type":"uint256"}],"name":"Deposit","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"src","type":"address"},{"indexed":false,"name":"wad","type":"uint256"}],"name":"Withdrawal","type":"event"}]` exchange.IO("abi", wethAddress, abiWETH) let wad = toInnerAmount(0.01, 18) let ret = exchange.IO("api", wethAddress, "withdraw", wad) Log("Transaction Hash:", ret) }

Transaction Hash: 0x446423c841451a8d04428a075b556eb5564186b09926da915f5da1c9837d2af4

可以看到以上代码和之前的deposit方法的例子基本一样,只是最后一步调用的方法换成了withdraw,调用前:

WETH精度: 18 wethBalance: 0.01
ETH精度: 18 ethBalance: 0.11322979983231546

再次查询:

WETH精度: 18 wethBalance: 0
ETH精度: 18 ethBalance: 0.1231207156449464

可以看到又把0.01个WETH换回了ETH

Uniswap V3

Uniswap V3是一种去中心化的交易协议,构建在以太坊区块链上,用于实现加密货币的交易和流动性提供。它由一系列智能合约组成,包括核心合约、池合约、工厂合约、路由合约等。

在使用Uniswap查询当前兑换价格、执行兑换操作时主要使用以下合约:

  • Router(路由合约)
    这是用于执行交易的合约,允许用户通过指定交易路径和参数来进行交易操作。
  • Pool(池合约)
    池合约是Uniswap中的关键组成部分,用于存储和管理特定资产对的流动性。每个资产对都有一个对应的池合约,其中包含了流动性提供者提供的资金、价格范围、手续费设置等信息。池合约负责处理交易、计算资金池的状态,并确保交易的顺利执行。
  • Factory(工厂合约)
    工厂合约是用于创建和管理Uniswap池合约的合约。当用户希望创建新的资产对时,他们通过与工厂合约交互来部署新的池合约。工厂合约负责协调池合约的创建和初始化,使用户能够动态地创建新的资产对。

根据我们之前学习的内容,我们来重新复习一下在FMZ量化交易平台策略代码中如何注册智能合约的ABI,智能合约的ABI如何获取在之前的课程中我们已经讲解过,这里不在赘述。
需要注册三个Uniswap的智能合约ABI,使用以太坊主网为例:

javascript
var abiRoute = '[{"inputs":[{"internalType":"address","name":"_factoryV2","type":"address"},{"internalType":"address","name":"factoryV3","type":"address"},{"internalType":"address","name":"_positionManager","type":"address"},{"internalType":"address","name":"_WETH9","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"WETH9","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"}],"name":"approveMax","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"}],"name":"approveMaxMinusOne","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"}],"name":"approveZeroThenMax","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"}],"name":"approveZeroThenMaxMinusOne","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"bytes","name":"data","type":"bytes"}],"name":"callPositionManager","outputs":[{"internalType":"bytes","name":"result","type":"bytes"}],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"bytes[]","name":"paths","type":"bytes[]"},{"internalType":"uint128[]","name":"amounts","type":"uint128[]"},{"internalType":"uint24","name":"maximumTickDivergence","type":"uint24"},{"internalType":"uint32","name":"secondsAgo","type":"uint32"}],"name":"checkOracleSlippage","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes","name":"path","type":"bytes"},{"internalType":"uint24","name":"maximumTickDivergence","type":"uint24"},{"internalType":"uint32","name":"secondsAgo","type":"uint32"}],"name":"checkOracleSlippage","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"components":[{"internalType":"bytes","name":"path","type":"bytes"},{"internalType":"address","name":"recipient","type":"address"},{"internalType":"uint256","name":"amountIn","type":"uint256"},{"internalType":"uint256","name":"amountOutMinimum","type":"uint256"}],"internalType":"struct IV3SwapRouter.ExactInputParams","name":"params","type":"tuple"}],"name":"exactInput","outputs":[{"internalType":"uint256","name":"amountOut","type":"uint256"}],"stateMutability":"payable","type":"function"},{"inputs":[{"components":[{"internalType":"address","name":"tokenIn","type":"address"},{"internalType":"address","name":"tokenOut","type":"address"},{"internalType":"uint24","name":"fee","type":"uint24"},{"internalType":"address","name":"recipient","type":"address"},{"internalType":"uint256","name":"amountIn","type":"uint256"},{"internalType":"uint256","name":"amountOutMinimum","type":"uint256"},{"internalType":"uint160","name":"sqrtPriceLimitX96","type":"uint160"}],"internalType":"struct IV3SwapRouter.ExactInputSingleParams","name":"params","type":"tuple"}],"name":"exactInputSingle","outputs":[{"internalType":"uint256","name":"amountOut","type":"uint256"}],"stateMutability":"payable","type":"function"},{"inputs":[{"components":[{"internalType":"bytes","name":"path","type":"bytes"},{"internalType":"address","name":"recipient","type":"address"},{"internalType":"uint256","name":"amountOut","type":"uint256"},{"internalType":"uint256","name":"amountInMaximum","type":"uint256"}],"internalType":"struct IV3SwapRouter.ExactOutputParams","name":"params","type":"tuple"}],"name":"exactOutput","outputs":[{"internalType":"uint256","name":"amountIn","type":"uint256"}],"stateMutability":"payable","type":"function"},{"inputs":[{"components":[{"internalType":"address","name":"tokenIn","type":"address"},{"internalType":"address","name":"tokenOut","type":"address"},{"internalType":"uint24","name":"fee","type":"uint24"},{"internalType":"address","name":"recipient","type":"address"},{"internalType":"uint256","name":"amountOut","type":"uint256"},{"internalType":"uint256","name":"amountInMaximum","type":"uint256"},{"internalType":"uint160","name":"sqrtPriceLimitX96","type":"uint160"}],"internalType":"struct IV3SwapRouter.ExactOutputSingleParams","name":"params","type":"tuple"}],"name":"exactOutputSingle","outputs":[{"internalType":"uint256","name":"amountIn","type":"uint256"}],"stateMutability":"payable","type":"function"},{"inputs":[],"name":"factory","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"factoryV2","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"getApprovalType","outputs":[{"internalType":"enum IApproveAndCall.ApprovalType","name":"","type":"uint8"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"components":[{"internalType":"address","name":"token0","type":"address"},{"internalType":"address","name":"token1","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"uint256","name":"amount0Min","type":"uint256"},{"internalType":"uint256","name":"amount1Min","type":"uint256"}],"internalType":"struct IApproveAndCall.IncreaseLiquidityParams","name":"params","type":"tuple"}],"name":"increaseLiquidity","outputs":[{"internalType":"bytes","name":"result","type":"bytes"}],"stateMutability":"payable","type":"function"},{"inputs":[{"components":[{"internalType":"address","name":"token0","type":"address"},{"internalType":"address","name":"token1","type":"address"},{"internalType":"uint24","name":"fee","type":"uint24"},{"internalType":"int24","name":"tickLower","type":"int24"},{"internalType":"int24","name":"tickUpper","type":"int24"},{"internalType":"uint256","name":"amount0Min","type":"uint256"},{"internalType":"uint256","name":"amount1Min","type":"uint256"},{"internalType":"address","name":"recipient","type":"address"}],"internalType":"struct IApproveAndCall.MintParams","name":"params","type":"tuple"}],"name":"mint","outputs":[{"internalType":"bytes","name":"result","type":"bytes"}],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"previousBlockhash","type":"bytes32"},{"internalType":"bytes[]","name":"data","type":"bytes[]"}],"name":"multicall","outputs":[{"internalType":"bytes[]","name":"","type":"bytes[]"}],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint256","name":"deadline","type":"uint256"},{"internalType":"bytes[]","name":"data","type":"bytes[]"}],"name":"multicall","outputs":[{"internalType":"bytes[]","name":"","type":"bytes[]"}],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"bytes[]","name":"data","type":"bytes[]"}],"name":"multicall","outputs":[{"internalType":"bytes[]","name":"results","type":"bytes[]"}],"stateMutability":"payable","type":"function"},{"inputs":[],"name":"positionManager","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"value","type":"uint256"}],"name":"pull","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[],"name":"refundETH","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"value","type":"uint256"},{"internalType":"uint256","name":"deadline","type":"uint256"},{"internalType":"uint8","name":"v","type":"uint8"},{"internalType":"bytes32","name":"r","type":"bytes32"},{"internalType":"bytes32","name":"s","type":"bytes32"}],"name":"selfPermit","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"nonce","type":"uint256"},{"internalType":"uint256","name":"expiry","type":"uint256"},{"internalType":"uint8","name":"v","type":"uint8"},{"internalType":"bytes32","name":"r","type":"bytes32"},{"internalType":"bytes32","name":"s","type":"bytes32"}],"name":"selfPermitAllowed","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"nonce","type":"uint256"},{"internalType":"uint256","name":"expiry","type":"uint256"},{"internalType":"uint8","name":"v","type":"uint8"},{"internalType":"bytes32","name":"r","type":"bytes32"},{"internalType":"bytes32","name":"s","type":"bytes32"}],"name":"selfPermitAllowedIfNecessary","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"value","type":"uint256"},{"internalType":"uint256","name":"deadline","type":"uint256"},{"internalType":"uint8","name":"v","type":"uint8"},{"internalType":"bytes32","name":"r","type":"bytes32"},{"internalType":"bytes32","name":"s","type":"bytes32"}],"name":"selfPermitIfNecessary","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint256","name":"amountIn","type":"uint256"},{"internalType":"uint256","name":"amountOutMin","type":"uint256"},{"internalType":"address[]","name":"path","type":"address[]"},{"internalType":"address","name":"to","type":"address"}],"name":"swapExactTokensForTokens","outputs":[{"internalType":"uint256","name":"amountOut","type":"uint256"}],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint256","name":"amountOut","type":"uint256"},{"internalType":"uint256","name":"amountInMax","type":"uint256"},{"internalType":"address[]","name":"path","type":"address[]"},{"internalType":"address","name":"to","type":"address"}],"name":"swapTokensForExactTokens","outputs":[{"internalType":"uint256","name":"amountIn","type":"uint256"}],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"amountMinimum","type":"uint256"},{"internalType":"address","name":"recipient","type":"address"}],"name":"sweepToken","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"amountMinimum","type":"uint256"}],"name":"sweepToken","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"amountMinimum","type":"uint256"},{"internalType":"uint256","name":"feeBips","type":"uint256"},{"internalType":"address","name":"feeRecipient","type":"address"}],"name":"sweepTokenWithFee","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"amountMinimum","type":"uint256"},{"internalType":"address","name":"recipient","type":"address"},{"internalType":"uint256","name":"feeBips","type":"uint256"},{"internalType":"address","name":"feeRecipient","type":"address"}],"name":"sweepTokenWithFee","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"int256","name":"amount0Delta","type":"int256"},{"internalType":"int256","name":"amount1Delta","type":"int256"},{"internalType":"bytes","name":"_data","type":"bytes"}],"name":"uniswapV3SwapCallback","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"amountMinimum","type":"uint256"},{"internalType":"address","name":"recipient","type":"address"}],"name":"unwrapWETH9","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint256","name":"amountMinimum","type":"uint256"}],"name":"unwrapWETH9","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint256","name":"amountMinimum","type":"uint256"},{"internalType":"address","name":"recipient","type":"address"},{"internalType":"uint256","name":"feeBips","type":"uint256"},{"internalType":"address","name":"feeRecipient","type":"address"}],"name":"unwrapWETH9WithFee","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint256","name":"amountMinimum","type":"uint256"},{"internalType":"uint256","name":"feeBips","type":"uint256"},{"internalType":"address","name":"feeRecipient","type":"address"}],"name":"unwrapWETH9WithFee","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint256","name":"value","type":"uint256"}],"name":"wrapETH","outputs":[],"stateMutability":"payable","type":"function"},{"stateMutability":"payable","type":"receive"}]'; var abiPool = '[{\"inputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"owner\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"int24\",\"name\":\"tickLower\",\"type\":\"int24\"},{\"indexed\":true,\"internalType\":\"int24\",\"name\":\"tickUpper\",\"type\":\"int24\"},{\"indexed\":false,\"internalType\":\"uint128\",\"name\":\"amount\",\"type\":\"uint128\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"amount0\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"amount1\",\"type\":\"uint256\"}],\"name\":\"Burn\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"owner\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"address\",\"name\":\"recipient\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"int24\",\"name\":\"tickLower\",\"type\":\"int24\"},{\"indexed\":true,\"internalType\":\"int24\",\"name\":\"tickUpper\",\"type\":\"int24\"},{\"indexed\":false,\"internalType\":\"uint128\",\"name\":\"amount0\",\"type\":\"uint128\"},{\"indexed\":false,\"internalType\":\"uint128\",\"name\":\"amount1\",\"type\":\"uint128\"}],\"name\":\"Collect\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"sender\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"recipient\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint128\",\"name\":\"amount0\",\"type\":\"uint128\"},{\"indexed\":false,\"internalType\":\"uint128\",\"name\":\"amount1\",\"type\":\"uint128\"}],\"name\":\"CollectProtocol\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"sender\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"recipient\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"amount0\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"amount1\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"paid0\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"paid1\",\"type\":\"uint256\"}],\"name\":\"Flash\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"uint16\",\"name\":\"observationCardinalityNextOld\",\"type\":\"uint16\"},{\"indexed\":false,\"internalType\":\"uint16\",\"name\":\"observationCardinalityNextNew\",\"type\":\"uint16\"}],\"name\":\"IncreaseObservationCardinalityNext\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"uint160\",\"name\":\"sqrtPriceX96\",\"type\":\"uint160\"},{\"indexed\":false,\"internalType\":\"int24\",\"name\":\"tick\",\"type\":\"int24\"}],\"name\":\"Initialize\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"address\",\"name\":\"sender\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"owner\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"int24\",\"name\":\"tickLower\",\"type\":\"int24\"},{\"indexed\":true,\"internalType\":\"int24\",\"name\":\"tickUpper\",\"type\":\"int24\"},{\"indexed\":false,\"internalType\":\"uint128\",\"name\":\"amount\",\"type\":\"uint128\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"amount0\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"amount1\",\"type\":\"uint256\"}],\"name\":\"Mint\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"uint8\",\"name\":\"feeProtocol0Old\",\"type\":\"uint8\"},{\"indexed\":false,\"internalType\":\"uint8\",\"name\":\"feeProtocol1Old\",\"type\":\"uint8\"},{\"indexed\":false,\"internalType\":\"uint8\",\"name\":\"feeProtocol0New\",\"type\":\"uint8\"},{\"indexed\":false,\"internalType\":\"uint8\",\"name\":\"feeProtocol1New\",\"type\":\"uint8\"}],\"name\":\"SetFeeProtocol\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"sender\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"recipient\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"int256\",\"name\":\"amount0\",\"type\":\"int256\"},{\"indexed\":false,\"internalType\":\"int256\",\"name\":\"amount1\",\"type\":\"int256\"},{\"indexed\":false,\"internalType\":\"uint160\",\"name\":\"sqrtPriceX96\",\"type\":\"uint160\"},{\"indexed\":false,\"internalType\":\"uint128\",\"name\":\"liquidity\",\"type\":\"uint128\"},{\"indexed\":false,\"internalType\":\"int24\",\"name\":\"tick\",\"type\":\"int24\"}],\"name\":\"Swap\",\"type\":\"event\"},{\"inputs\":[{\"internalType\":\"int24\",\"name\":\"tickLower\",\"type\":\"int24\"},{\"internalType\":\"int24\",\"name\":\"tickUpper\",\"type\":\"int24\"},{\"internalType\":\"uint128\",\"name\":\"amount\",\"type\":\"uint128\"}],\"name\":\"burn\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"amount0\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"amount1\",\"type\":\"uint256\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"recipient\",\"type\":\"address\"},{\"internalType\":\"int24\",\"name\":\"tickLower\",\"type\":\"int24\"},{\"internalType\":\"int24\",\"name\":\"tickUpper\",\"type\":\"int24\"},{\"internalType\":\"uint128\",\"name\":\"amount0Requested\",\"type\":\"uint128\"},{\"internalType\":\"uint128\",\"name\":\"amount1Requested\",\"type\":\"uint128\"}],\"name\":\"collect\",\"outputs\":[{\"internalType\":\"uint128\",\"name\":\"amount0\",\"type\":\"uint128\"},{\"internalType\":\"uint128\",\"name\":\"amount1\",\"type\":\"uint128\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"recipient\",\"type\":\"address\"},{\"internalType\":\"uint128\",\"name\":\"amount0Requested\",\"type\":\"uint128\"},{\"internalType\":\"uint128\",\"name\":\"amount1Requested\",\"type\":\"uint128\"}],\"name\":\"collectProtocol\",\"outputs\":[{\"internalType\":\"uint128\",\"name\":\"amount0\",\"type\":\"uint128\"},{\"internalType\":\"uint128\",\"name\":\"amount1\",\"type\":\"uint128\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"factory\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"fee\",\"outputs\":[{\"internalType\":\"uint24\",\"name\":\"\",\"type\":\"uint24\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"feeGrowthGlobal0X128\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"feeGrowthGlobal1X128\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"recipient\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amount0\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"amount1\",\"type\":\"uint256\"},{\"internalType\":\"bytes\",\"name\":\"data\",\"type\":\"bytes\"}],\"name\":\"flash\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint16\",\"name\":\"observationCardinalityNext\",\"type\":\"uint16\"}],\"name\":\"increaseObservationCardinalityNext\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint160\",\"name\":\"sqrtPriceX96\",\"type\":\"uint160\"}],\"name\":\"initialize\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"liquidity\",\"outputs\":[{\"internalType\":\"uint128\",\"name\":\"\",\"type\":\"uint128\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"maxLiquidityPerTick\",\"outputs\":[{\"internalType\":\"uint128\",\"name\":\"\",\"type\":\"uint128\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"recipient\",\"type\":\"address\"},{\"internalType\":\"int24\",\"name\":\"tickLower\",\"type\":\"int24\"},{\"internalType\":\"int24\",\"name\":\"tickUpper\",\"type\":\"int24\"},{\"internalType\":\"uint128\",\"name\":\"amount\",\"type\":\"uint128\"},{\"internalType\":\"bytes\",\"name\":\"data\",\"type\":\"bytes\"}],\"name\":\"mint\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"amount0\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"amount1\",\"type\":\"uint256\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"name\":\"observations\",\"outputs\":[{\"internalType\":\"uint32\",\"name\":\"blockTimestamp\",\"type\":\"uint32\"},{\"internalType\":\"int56\",\"name\":\"tickCumulative\",\"type\":\"int56\"},{\"internalType\":\"uint160\",\"name\":\"secondsPerLiquidityCumulativeX128\",\"type\":\"uint160\"},{\"internalType\":\"bool\",\"name\":\"initialized\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint32[]\",\"name\":\"secondsAgos\",\"type\":\"uint32[]\"}],\"name\":\"observe\",\"outputs\":[{\"internalType\":\"int56[]\",\"name\":\"tickCumulatives\",\"type\":\"int56[]\"},{\"internalType\":\"uint160[]\",\"name\":\"secondsPerLiquidityCumulativeX128s\",\"type\":\"uint160[]\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"name\":\"positions\",\"outputs\":[{\"internalType\":\"uint128\",\"name\":\"liquidity\",\"type\":\"uint128\"},{\"internalType\":\"uint256\",\"name\":\"feeGrowthInside0LastX128\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"feeGrowthInside1LastX128\",\"type\":\"uint256\"},{\"internalType\":\"uint128\",\"name\":\"tokensOwed0\",\"type\":\"uint128\"},{\"internalType\":\"uint128\",\"name\":\"tokensOwed1\",\"type\":\"uint128\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"protocolFees\",\"outputs\":[{\"internalType\":\"uint128\",\"name\":\"token0\",\"type\":\"uint128\"},{\"internalType\":\"uint128\",\"name\":\"token1\",\"type\":\"uint128\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint8\",\"name\":\"feeProtocol0\",\"type\":\"uint8\"},{\"internalType\":\"uint8\",\"name\":\"feeProtocol1\",\"type\":\"uint8\"}],\"name\":\"setFeeProtocol\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"slot0\",\"outputs\":[{\"internalType\":\"uint160\",\"name\":\"sqrtPriceX96\",\"type\":\"uint160\"},{\"internalType\":\"int24\",\"name\":\"tick\",\"type\":\"int24\"},{\"internalType\":\"uint16\",\"name\":\"observationIndex\",\"type\":\"uint16\"},{\"internalType\":\"uint16\",\"name\":\"observationCardinality\",\"type\":\"uint16\"},{\"internalType\":\"uint16\",\"name\":\"observationCardinalityNext\",\"type\":\"uint16\"},{\"internalType\":\"uint8\",\"name\":\"feeProtocol\",\"type\":\"uint8\"},{\"internalType\":\"bool\",\"name\":\"unlocked\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"int24\",\"name\":\"tickLower\",\"type\":\"int24\"},{\"internalType\":\"int24\",\"name\":\"tickUpper\",\"type\":\"int24\"}],\"name\":\"snapshotCumulativesInside\",\"outputs\":[{\"internalType\":\"int56\",\"name\":\"tickCumulativeInside\",\"type\":\"int56\"},{\"internalType\":\"uint160\",\"name\":\"secondsPerLiquidityInsideX128\",\"type\":\"uint160\"},{\"internalType\":\"uint32\",\"name\":\"secondsInside\",\"type\":\"uint32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"recipient\",\"type\":\"address\"},{\"internalType\":\"bool\",\"name\":\"zeroForOne\",\"type\":\"bool\"},{\"internalType\":\"int256\",\"name\":\"amountSpecified\",\"type\":\"int256\"},{\"internalType\":\"uint160\",\"name\":\"sqrtPriceLimitX96\",\"type\":\"uint160\"},{\"internalType\":\"bytes\",\"name\":\"data\",\"type\":\"bytes\"}],\"name\":\"swap\",\"outputs\":[{\"internalType\":\"int256\",\"name\":\"amount0\",\"type\":\"int256\"},{\"internalType\":\"int256\",\"name\":\"amount1\",\"type\":\"int256\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"int16\",\"name\":\"\",\"type\":\"int16\"}],\"name\":\"tickBitmap\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"tickSpacing\",\"outputs\":[{\"internalType\":\"int24\",\"name\":\"\",\"type\":\"int24\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"int24\",\"name\":\"\",\"type\":\"int24\"}],\"name\":\"ticks\",\"outputs\":[{\"internalType\":\"uint128\",\"name\":\"liquidityGross\",\"type\":\"uint128\"},{\"internalType\":\"int128\",\"name\":\"liquidityNet\",\"type\":\"int128\"},{\"internalType\":\"uint256\",\"name\":\"feeGrowthOutside0X128\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"feeGrowthOutside1X128\",\"type\":\"uint256\"},{\"internalType\":\"int56\",\"name\":\"tickCumulativeOutside\",\"type\":\"int56\"},{\"internalType\":\"uint160\",\"name\":\"secondsPerLiquidityOutsideX128\",\"type\":\"uint160\"},{\"internalType\":\"uint32\",\"name\":\"secondsOutside\",\"type\":\"uint32\"},{\"internalType\":\"bool\",\"name\":\"initialized\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"token0\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"token1\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"}]' var abiFactory = '[{\"inputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"uint24\",\"name\":\"fee\",\"type\":\"uint24\"},{\"indexed\":true,\"internalType\":\"int24\",\"name\":\"tickSpacing\",\"type\":\"int24\"}],\"name\":\"FeeAmountEnabled\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"oldOwner\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"newOwner\",\"type\":\"address\"}],\"name\":\"OwnerChanged\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"token0\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"token1\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"uint24\",\"name\":\"fee\",\"type\":\"uint24\"},{\"indexed\":false,\"internalType\":\"int24\",\"name\":\"tickSpacing\",\"type\":\"int24\"},{\"indexed\":false,\"internalType\":\"address\",\"name\":\"pool\",\"type\":\"address\"}],\"name\":\"PoolCreated\",\"type\":\"event\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"tokenA\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"tokenB\",\"type\":\"address\"},{\"internalType\":\"uint24\",\"name\":\"fee\",\"type\":\"uint24\"}],\"name\":\"createPool\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"pool\",\"type\":\"address\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint24\",\"name\":\"fee\",\"type\":\"uint24\"},{\"internalType\":\"int24\",\"name\":\"tickSpacing\",\"type\":\"int24\"}],\"name\":\"enableFeeAmount\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint24\",\"name\":\"\",\"type\":\"uint24\"}],\"name\":\"feeAmountTickSpacing\",\"outputs\":[{\"internalType\":\"int24\",\"name\":\"\",\"type\":\"int24\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"},{\"internalType\":\"uint24\",\"name\":\"\",\"type\":\"uint24\"}],\"name\":\"getPool\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"owner\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"parameters\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"factory\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"token0\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"token1\",\"type\":\"address\"},{\"internalType\":\"uint24\",\"name\":\"fee\",\"type\":\"uint24\"},{\"internalType\":\"int24\",\"name\":\"tickSpacing\",\"type\":\"int24\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_owner\",\"type\":\"address\"}],\"name\":\"setOwner\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"}]' var contractV3FactoryAddress = "0x1F98431c8aD98523631AE4a59f267346ea31F984" var contractV3SwapRouterV2Address = "0x68b3465833fb72A70ecDF485E0e4C7bD8665Fc45" function main() { // 注册Uniswap工厂合约的ABI exchange.IO("abi", contractV3FactoryAddress, abiFactory) // 测试Factory合约的owner方法 var owner = exchange.IO("api", contractV3FactoryAddress, "owner") Log("owner:", owner) // 返回值应当为: 0x1a9C8182C09F50C8318d769245beA52c32BE35BC // 注册Uniswap路由合约的ABI exchange.IO("abi", contractV3SwapRouterV2Address, abiRoute) // 测试Uniswap V3 Router V2合约的factory方法 var factoryOfRouter = exchange.IO("api", contractV3SwapRouterV2Address, "factory") Log("factoryOfRouter:", factoryOfRouter) // 返回值应当为: 0x1F98431c8aD98523631AE4a59f267346ea31F984 // 获取交易对的池地址 var tokenIn = {name : "DAI", address: "0x6b175474e89094c44da98b954eedeac495271d0f"} var tokenOut = {name : "USDT", address: "0xdac17f958d2ee523a2206206994597c13d831ec7"} var poolAddress = exchange.IO("api", contractV3FactoryAddress, "getPool", tokenIn.address, tokenOut.address, 3000) var pair = "tokenIn:" + tokenIn.name + ", tokenOut:" + tokenOut.name + ", fee:" + 3000 Log("使用工厂合约的getPool方法获取", pair, "池地址:", poolAddress) // 注册池合约ABI exchange.IO("abi", poolAddress, abiPool) // 测试池合约的 var factoryOfPool = exchange.IO("api", poolAddress, "factory") Log("factoryOfPool:", factoryOfPool) // 返回值应当为: 0x1F98431c8aD98523631AE4a59f267346ea31F984 }

以上代码注册了工厂合约、路由合约、池合约的ABI,并且做了一些测试。可以观察到路由合约和池合约的factory方法都返回了0x1F98431c8aD98523631AE4a59f267346ea31F984这个地址,这个地址就是Uniswap工厂合约(代码中contractV3FactoryAddress变量)的地址。

javascript
var contractV3FactoryAddress = "0x1F98431c8aD98523631AE4a59f267346ea31F984"

注册好了智能合约的ABI,接下来我们先来看使用Uniswap V3的兑换操作,以下内容我们使用ERC20代币的兑换(在以太坊主网)作为具体场景。

查询Uniswap V3路由合约授权

ERC20合约中,allowance函数用于查询授权给其他地址的代币数量。它可以查询特定地址授权给另一个地址(通常是合约地址)可以从自己账户中转移的代币数量。
对于Uniswap V3路由合约来说,如果你已经执行过approve函数授权给路由合约的地址,那么你可以使用ERC20代币的allowance函数来查询该授权数量。

还记得之前的教程中我们演示的ERC20合约方法的调用吗?allowanceapprove都是ERC20合约的方法,这些方法与我们之前讲解过的balanceOftransfer方法都是同等地位。

在兑换之前,首先我们要检查是否授权给Uniswap路由合约足够数量的token来执行兑换,假如我们要兑换的动作为:1INCH->ETH,费率设置为10000。这个费率设置为10000表示费率为1%,Uniswap的兑换池可能有多种费率,一般为Uniswap规定的几种选择。例如费率设置为3000,表示费率为0.3%,具体可以查询Uniswap文档、相关资料。

这里需要注意,实际兑换池为1INCH/WETH,ETH和WETH是以太坊中的两种不同的代币。

  • ETH(以太币):
    ETH是以太坊区块链的原生加密货币,也是以太坊网络的主要资产和计算单位。ETH是以太坊上的原生代币,具有普遍的可流通性和使用性,可用于支付交易费用、参与智能合约交互、存储价值等。
  • WETH(包装的以太币):
    WETH是一种将ETH封装为ERC20标准代币的形式。它是以太坊上的一个智能合约,目的是使ETH能够与ERC20代币在智能合约中进行无缝交互。WETH可以通过将ETH存入WETH合约地址来进行创建和获取,而且可以随时将WETH转回ETH。WETH在以太坊生态系统中的主要作用是为ETH提供与其他ERC20代币相同的标准接口和兼容性,使ETH能够参与去中心化交易、流动性池和其他DeFi协议。

因此,WETH是ETH的一种封装形式,使得ETH能够在以太坊智能合约中以ERC20代币的形式进行交互。典型情况下,当需要在智能合约中使用ETH时,可以将ETH转换为WETH,进行交易或参与DeFi操作,然后根据需要将WETH重新转换回ETH。

使用ERC20allowance方法获取授权数量:

javascript
var abiRoute = '[{"inputs":[{"internalType":"address","name":"_factoryV2","type":"address"},{"internalType":"address","name":"factoryV3","type":"address"},{"internalType":"address","name":"_positionManager","type":"address"},{"internalType":"address","name":"_WETH9","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"WETH9","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"}],"name":"approveMax","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"}],"name":"approveMaxMinusOne","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"}],"name":"approveZeroThenMax","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"}],"name":"approveZeroThenMaxMinusOne","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"bytes","name":"data","type":"bytes"}],"name":"callPositionManager","outputs":[{"internalType":"bytes","name":"result","type":"bytes"}],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"bytes[]","name":"paths","type":"bytes[]"},{"internalType":"uint128[]","name":"amounts","type":"uint128[]"},{"internalType":"uint24","name":"maximumTickDivergence","type":"uint24"},{"internalType":"uint32","name":"secondsAgo","type":"uint32"}],"name":"checkOracleSlippage","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes","name":"path","type":"bytes"},{"internalType":"uint24","name":"maximumTickDivergence","type":"uint24"},{"internalType":"uint32","name":"secondsAgo","type":"uint32"}],"name":"checkOracleSlippage","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"components":[{"internalType":"bytes","name":"path","type":"bytes"},{"internalType":"address","name":"recipient","type":"address"},{"internalType":"uint256","name":"amountIn","type":"uint256"},{"internalType":"uint256","name":"amountOutMinimum","type":"uint256"}],"internalType":"struct IV3SwapRouter.ExactInputParams","name":"params","type":"tuple"}],"name":"exactInput","outputs":[{"internalType":"uint256","name":"amountOut","type":"uint256"}],"stateMutability":"payable","type":"function"},{"inputs":[{"components":[{"internalType":"address","name":"tokenIn","type":"address"},{"internalType":"address","name":"tokenOut","type":"address"},{"internalType":"uint24","name":"fee","type":"uint24"},{"internalType":"address","name":"recipient","type":"address"},{"internalType":"uint256","name":"amountIn","type":"uint256"},{"internalType":"uint256","name":"amountOutMinimum","type":"uint256"},{"internalType":"uint160","name":"sqrtPriceLimitX96","type":"uint160"}],"internalType":"struct IV3SwapRouter.ExactInputSingleParams","name":"params","type":"tuple"}],"name":"exactInputSingle","outputs":[{"internalType":"uint256","name":"amountOut","type":"uint256"}],"stateMutability":"payable","type":"function"},{"inputs":[{"components":[{"internalType":"bytes","name":"path","type":"bytes"},{"internalType":"address","name":"recipient","type":"address"},{"internalType":"uint256","name":"amountOut","type":"uint256"},{"internalType":"uint256","name":"amountInMaximum","type":"uint256"}],"internalType":"struct IV3SwapRouter.ExactOutputParams","name":"params","type":"tuple"}],"name":"exactOutput","outputs":[{"internalType":"uint256","name":"amountIn","type":"uint256"}],"stateMutability":"payable","type":"function"},{"inputs":[{"components":[{"internalType":"address","name":"tokenIn","type":"address"},{"internalType":"address","name":"tokenOut","type":"address"},{"internalType":"uint24","name":"fee","type":"uint24"},{"internalType":"address","name":"recipient","type":"address"},{"internalType":"uint256","name":"amountOut","type":"uint256"},{"internalType":"uint256","name":"amountInMaximum","type":"uint256"},{"internalType":"uint160","name":"sqrtPriceLimitX96","type":"uint160"}],"internalType":"struct IV3SwapRouter.ExactOutputSingleParams","name":"params","type":"tuple"}],"name":"exactOutputSingle","outputs":[{"internalType":"uint256","name":"amountIn","type":"uint256"}],"stateMutability":"payable","type":"function"},{"inputs":[],"name":"factory","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"factoryV2","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"getApprovalType","outputs":[{"internalType":"enum IApproveAndCall.ApprovalType","name":"","type":"uint8"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"components":[{"internalType":"address","name":"token0","type":"address"},{"internalType":"address","name":"token1","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"uint256","name":"amount0Min","type":"uint256"},{"internalType":"uint256","name":"amount1Min","type":"uint256"}],"internalType":"struct IApproveAndCall.IncreaseLiquidityParams","name":"params","type":"tuple"}],"name":"increaseLiquidity","outputs":[{"internalType":"bytes","name":"result","type":"bytes"}],"stateMutability":"payable","type":"function"},{"inputs":[{"components":[{"internalType":"address","name":"token0","type":"address"},{"internalType":"address","name":"token1","type":"address"},{"internalType":"uint24","name":"fee","type":"uint24"},{"internalType":"int24","name":"tickLower","type":"int24"},{"internalType":"int24","name":"tickUpper","type":"int24"},{"internalType":"uint256","name":"amount0Min","type":"uint256"},{"internalType":"uint256","name":"amount1Min","type":"uint256"},{"internalType":"address","name":"recipient","type":"address"}],"internalType":"struct IApproveAndCall.MintParams","name":"params","type":"tuple"}],"name":"mint","outputs":[{"internalType":"bytes","name":"result","type":"bytes"}],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"previousBlockhash","type":"bytes32"},{"internalType":"bytes[]","name":"data","type":"bytes[]"}],"name":"multicall","outputs":[{"internalType":"bytes[]","name":"","type":"bytes[]"}],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint256","name":"deadline","type":"uint256"},{"internalType":"bytes[]","name":"data","type":"bytes[]"}],"name":"multicall","outputs":[{"internalType":"bytes[]","name":"","type":"bytes[]"}],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"bytes[]","name":"data","type":"bytes[]"}],"name":"multicall","outputs":[{"internalType":"bytes[]","name":"results","type":"bytes[]"}],"stateMutability":"payable","type":"function"},{"inputs":[],"name":"positionManager","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"value","type":"uint256"}],"name":"pull","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[],"name":"refundETH","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"value","type":"uint256"},{"internalType":"uint256","name":"deadline","type":"uint256"},{"internalType":"uint8","name":"v","type":"uint8"},{"internalType":"bytes32","name":"r","type":"bytes32"},{"internalType":"bytes32","name":"s","type":"bytes32"}],"name":"selfPermit","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"nonce","type":"uint256"},{"internalType":"uint256","name":"expiry","type":"uint256"},{"internalType":"uint8","name":"v","type":"uint8"},{"internalType":"bytes32","name":"r","type":"bytes32"},{"internalType":"bytes32","name":"s","type":"bytes32"}],"name":"selfPermitAllowed","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"nonce","type":"uint256"},{"internalType":"uint256","name":"expiry","type":"uint256"},{"internalType":"uint8","name":"v","type":"uint8"},{"internalType":"bytes32","name":"r","type":"bytes32"},{"internalType":"bytes32","name":"s","type":"bytes32"}],"name":"selfPermitAllowedIfNecessary","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"value","type":"uint256"},{"internalType":"uint256","name":"deadline","type":"uint256"},{"internalType":"uint8","name":"v","type":"uint8"},{"internalType":"bytes32","name":"r","type":"bytes32"},{"internalType":"bytes32","name":"s","type":"bytes32"}],"name":"selfPermitIfNecessary","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint256","name":"amountIn","type":"uint256"},{"internalType":"uint256","name":"amountOutMin","type":"uint256"},{"internalType":"address[]","name":"path","type":"address[]"},{"internalType":"address","name":"to","type":"address"}],"name":"swapExactTokensForTokens","outputs":[{"internalType":"uint256","name":"amountOut","type":"uint256"}],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint256","name":"amountOut","type":"uint256"},{"internalType":"uint256","name":"amountInMax","type":"uint256"},{"internalType":"address[]","name":"path","type":"address[]"},{"internalType":"address","name":"to","type":"address"}],"name":"swapTokensForExactTokens","outputs":[{"internalType":"uint256","name":"amountIn","type":"uint256"}],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"amountMinimum","type":"uint256"},{"internalType":"address","name":"recipient","type":"address"}],"name":"sweepToken","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"amountMinimum","type":"uint256"}],"name":"sweepToken","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"amountMinimum","type":"uint256"},{"internalType":"uint256","name":"feeBips","type":"uint256"},{"internalType":"address","name":"feeRecipient","type":"address"}],"name":"sweepTokenWithFee","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"amountMinimum","type":"uint256"},{"internalType":"address","name":"recipient","type":"address"},{"internalType":"uint256","name":"feeBips","type":"uint256"},{"internalType":"address","name":"feeRecipient","type":"address"}],"name":"sweepTokenWithFee","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"int256","name":"amount0Delta","type":"int256"},{"internalType":"int256","name":"amount1Delta","type":"int256"},{"internalType":"bytes","name":"_data","type":"bytes"}],"name":"uniswapV3SwapCallback","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"amountMinimum","type":"uint256"},{"internalType":"address","name":"recipient","type":"address"}],"name":"unwrapWETH9","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint256","name":"amountMinimum","type":"uint256"}],"name":"unwrapWETH9","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint256","name":"amountMinimum","type":"uint256"},{"internalType":"address","name":"recipient","type":"address"},{"internalType":"uint256","name":"feeBips","type":"uint256"},{"internalType":"address","name":"feeRecipient","type":"address"}],"name":"unwrapWETH9WithFee","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint256","name":"amountMinimum","type":"uint256"},{"internalType":"uint256","name":"feeBips","type":"uint256"},{"internalType":"address","name":"feeRecipient","type":"address"}],"name":"unwrapWETH9WithFee","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint256","name":"value","type":"uint256"}],"name":"wrapETH","outputs":[],"stateMutability":"payable","type":"function"},{"stateMutability":"payable","type":"receive"}]'; var abiPool = '[{\"inputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"owner\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"int24\",\"name\":\"tickLower\",\"type\":\"int24\"},{\"indexed\":true,\"internalType\":\"int24\",\"name\":\"tickUpper\",\"type\":\"int24\"},{\"indexed\":false,\"internalType\":\"uint128\",\"name\":\"amount\",\"type\":\"uint128\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"amount0\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"amount1\",\"type\":\"uint256\"}],\"name\":\"Burn\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"owner\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"address\",\"name\":\"recipient\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"int24\",\"name\":\"tickLower\",\"type\":\"int24\"},{\"indexed\":true,\"internalType\":\"int24\",\"name\":\"tickUpper\",\"type\":\"int24\"},{\"indexed\":false,\"internalType\":\"uint128\",\"name\":\"amount0\",\"type\":\"uint128\"},{\"indexed\":false,\"internalType\":\"uint128\",\"name\":\"amount1\",\"type\":\"uint128\"}],\"name\":\"Collect\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"sender\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"recipient\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint128\",\"name\":\"amount0\",\"type\":\"uint128\"},{\"indexed\":false,\"internalType\":\"uint128\",\"name\":\"amount1\",\"type\":\"uint128\"}],\"name\":\"CollectProtocol\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"sender\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"recipient\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"amount0\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"amount1\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"paid0\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"paid1\",\"type\":\"uint256\"}],\"name\":\"Flash\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"uint16\",\"name\":\"observationCardinalityNextOld\",\"type\":\"uint16\"},{\"indexed\":false,\"internalType\":\"uint16\",\"name\":\"observationCardinalityNextNew\",\"type\":\"uint16\"}],\"name\":\"IncreaseObservationCardinalityNext\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"uint160\",\"name\":\"sqrtPriceX96\",\"type\":\"uint160\"},{\"indexed\":false,\"internalType\":\"int24\",\"name\":\"tick\",\"type\":\"int24\"}],\"name\":\"Initialize\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"address\",\"name\":\"sender\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"owner\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"int24\",\"name\":\"tickLower\",\"type\":\"int24\"},{\"indexed\":true,\"internalType\":\"int24\",\"name\":\"tickUpper\",\"type\":\"int24\"},{\"indexed\":false,\"internalType\":\"uint128\",\"name\":\"amount\",\"type\":\"uint128\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"amount0\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"amount1\",\"type\":\"uint256\"}],\"name\":\"Mint\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"uint8\",\"name\":\"feeProtocol0Old\",\"type\":\"uint8\"},{\"indexed\":false,\"internalType\":\"uint8\",\"name\":\"feeProtocol1Old\",\"type\":\"uint8\"},{\"indexed\":false,\"internalType\":\"uint8\",\"name\":\"feeProtocol0New\",\"type\":\"uint8\"},{\"indexed\":false,\"internalType\":\"uint8\",\"name\":\"feeProtocol1New\",\"type\":\"uint8\"}],\"name\":\"SetFeeProtocol\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"sender\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"recipient\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"int256\",\"name\":\"amount0\",\"type\":\"int256\"},{\"indexed\":false,\"internalType\":\"int256\",\"name\":\"amount1\",\"type\":\"int256\"},{\"indexed\":false,\"internalType\":\"uint160\",\"name\":\"sqrtPriceX96\",\"type\":\"uint160\"},{\"indexed\":false,\"internalType\":\"uint128\",\"name\":\"liquidity\",\"type\":\"uint128\"},{\"indexed\":false,\"internalType\":\"int24\",\"name\":\"tick\",\"type\":\"int24\"}],\"name\":\"Swap\",\"type\":\"event\"},{\"inputs\":[{\"internalType\":\"int24\",\"name\":\"tickLower\",\"type\":\"int24\"},{\"internalType\":\"int24\",\"name\":\"tickUpper\",\"type\":\"int24\"},{\"internalType\":\"uint128\",\"name\":\"amount\",\"type\":\"uint128\"}],\"name\":\"burn\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"amount0\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"amount1\",\"type\":\"uint256\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"recipient\",\"type\":\"address\"},{\"internalType\":\"int24\",\"name\":\"tickLower\",\"type\":\"int24\"},{\"internalType\":\"int24\",\"name\":\"tickUpper\",\"type\":\"int24\"},{\"internalType\":\"uint128\",\"name\":\"amount0Requested\",\"type\":\"uint128\"},{\"internalType\":\"uint128\",\"name\":\"amount1Requested\",\"type\":\"uint128\"}],\"name\":\"collect\",\"outputs\":[{\"internalType\":\"uint128\",\"name\":\"amount0\",\"type\":\"uint128\"},{\"internalType\":\"uint128\",\"name\":\"amount1\",\"type\":\"uint128\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"recipient\",\"type\":\"address\"},{\"internalType\":\"uint128\",\"name\":\"amount0Requested\",\"type\":\"uint128\"},{\"internalType\":\"uint128\",\"name\":\"amount1Requested\",\"type\":\"uint128\"}],\"name\":\"collectProtocol\",\"outputs\":[{\"internalType\":\"uint128\",\"name\":\"amount0\",\"type\":\"uint128\"},{\"internalType\":\"uint128\",\"name\":\"amount1\",\"type\":\"uint128\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"factory\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"fee\",\"outputs\":[{\"internalType\":\"uint24\",\"name\":\"\",\"type\":\"uint24\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"feeGrowthGlobal0X128\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"feeGrowthGlobal1X128\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"recipient\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amount0\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"amount1\",\"type\":\"uint256\"},{\"internalType\":\"bytes\",\"name\":\"data\",\"type\":\"bytes\"}],\"name\":\"flash\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint16\",\"name\":\"observationCardinalityNext\",\"type\":\"uint16\"}],\"name\":\"increaseObservationCardinalityNext\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint160\",\"name\":\"sqrtPriceX96\",\"type\":\"uint160\"}],\"name\":\"initialize\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"liquidity\",\"outputs\":[{\"internalType\":\"uint128\",\"name\":\"\",\"type\":\"uint128\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"maxLiquidityPerTick\",\"outputs\":[{\"internalType\":\"uint128\",\"name\":\"\",\"type\":\"uint128\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"recipient\",\"type\":\"address\"},{\"internalType\":\"int24\",\"name\":\"tickLower\",\"type\":\"int24\"},{\"internalType\":\"int24\",\"name\":\"tickUpper\",\"type\":\"int24\"},{\"internalType\":\"uint128\",\"name\":\"amount\",\"type\":\"uint128\"},{\"internalType\":\"bytes\",\"name\":\"data\",\"type\":\"bytes\"}],\"name\":\"mint\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"amount0\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"amount1\",\"type\":\"uint256\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"name\":\"observations\",\"outputs\":[{\"internalType\":\"uint32\",\"name\":\"blockTimestamp\",\"type\":\"uint32\"},{\"internalType\":\"int56\",\"name\":\"tickCumulative\",\"type\":\"int56\"},{\"internalType\":\"uint160\",\"name\":\"secondsPerLiquidityCumulativeX128\",\"type\":\"uint160\"},{\"internalType\":\"bool\",\"name\":\"initialized\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint32[]\",\"name\":\"secondsAgos\",\"type\":\"uint32[]\"}],\"name\":\"observe\",\"outputs\":[{\"internalType\":\"int56[]\",\"name\":\"tickCumulatives\",\"type\":\"int56[]\"},{\"internalType\":\"uint160[]\",\"name\":\"secondsPerLiquidityCumulativeX128s\",\"type\":\"uint160[]\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"name\":\"positions\",\"outputs\":[{\"internalType\":\"uint128\",\"name\":\"liquidity\",\"type\":\"uint128\"},{\"internalType\":\"uint256\",\"name\":\"feeGrowthInside0LastX128\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"feeGrowthInside1LastX128\",\"type\":\"uint256\"},{\"internalType\":\"uint128\",\"name\":\"tokensOwed0\",\"type\":\"uint128\"},{\"internalType\":\"uint128\",\"name\":\"tokensOwed1\",\"type\":\"uint128\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"protocolFees\",\"outputs\":[{\"internalType\":\"uint128\",\"name\":\"token0\",\"type\":\"uint128\"},{\"internalType\":\"uint128\",\"name\":\"token1\",\"type\":\"uint128\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint8\",\"name\":\"feeProtocol0\",\"type\":\"uint8\"},{\"internalType\":\"uint8\",\"name\":\"feeProtocol1\",\"type\":\"uint8\"}],\"name\":\"setFeeProtocol\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"slot0\",\"outputs\":[{\"internalType\":\"uint160\",\"name\":\"sqrtPriceX96\",\"type\":\"uint160\"},{\"internalType\":\"int24\",\"name\":\"tick\",\"type\":\"int24\"},{\"internalType\":\"uint16\",\"name\":\"observationIndex\",\"type\":\"uint16\"},{\"internalType\":\"uint16\",\"name\":\"observationCardinality\",\"type\":\"uint16\"},{\"internalType\":\"uint16\",\"name\":\"observationCardinalityNext\",\"type\":\"uint16\"},{\"internalType\":\"uint8\",\"name\":\"feeProtocol\",\"type\":\"uint8\"},{\"internalType\":\"bool\",\"name\":\"unlocked\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"int24\",\"name\":\"tickLower\",\"type\":\"int24\"},{\"internalType\":\"int24\",\"name\":\"tickUpper\",\"type\":\"int24\"}],\"name\":\"snapshotCumulativesInside\",\"outputs\":[{\"internalType\":\"int56\",\"name\":\"tickCumulativeInside\",\"type\":\"int56\"},{\"internalType\":\"uint160\",\"name\":\"secondsPerLiquidityInsideX128\",\"type\":\"uint160\"},{\"internalType\":\"uint32\",\"name\":\"secondsInside\",\"type\":\"uint32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"recipient\",\"type\":\"address\"},{\"internalType\":\"bool\",\"name\":\"zeroForOne\",\"type\":\"bool\"},{\"internalType\":\"int256\",\"name\":\"amountSpecified\",\"type\":\"int256\"},{\"internalType\":\"uint160\",\"name\":\"sqrtPriceLimitX96\",\"type\":\"uint160\"},{\"internalType\":\"bytes\",\"name\":\"data\",\"type\":\"bytes\"}],\"name\":\"swap\",\"outputs\":[{\"internalType\":\"int256\",\"name\":\"amount0\",\"type\":\"int256\"},{\"internalType\":\"int256\",\"name\":\"amount1\",\"type\":\"int256\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"int16\",\"name\":\"\",\"type\":\"int16\"}],\"name\":\"tickBitmap\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"tickSpacing\",\"outputs\":[{\"internalType\":\"int24\",\"name\":\"\",\"type\":\"int24\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"int24\",\"name\":\"\",\"type\":\"int24\"}],\"name\":\"ticks\",\"outputs\":[{\"internalType\":\"uint128\",\"name\":\"liquidityGross\",\"type\":\"uint128\"},{\"internalType\":\"int128\",\"name\":\"liquidityNet\",\"type\":\"int128\"},{\"internalType\":\"uint256\",\"name\":\"feeGrowthOutside0X128\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"feeGrowthOutside1X128\",\"type\":\"uint256\"},{\"internalType\":\"int56\",\"name\":\"tickCumulativeOutside\",\"type\":\"int56\"},{\"internalType\":\"uint160\",\"name\":\"secondsPerLiquidityOutsideX128\",\"type\":\"uint160\"},{\"internalType\":\"uint32\",\"name\":\"secondsOutside\",\"type\":\"uint32\"},{\"internalType\":\"bool\",\"name\":\"initialized\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"token0\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"token1\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"}]' var abiFactory = '[{\"inputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"uint24\",\"name\":\"fee\",\"type\":\"uint24\"},{\"indexed\":true,\"internalType\":\"int24\",\"name\":\"tickSpacing\",\"type\":\"int24\"}],\"name\":\"FeeAmountEnabled\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"oldOwner\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"newOwner\",\"type\":\"address\"}],\"name\":\"OwnerChanged\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"token0\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"token1\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"uint24\",\"name\":\"fee\",\"type\":\"uint24\"},{\"indexed\":false,\"internalType\":\"int24\",\"name\":\"tickSpacing\",\"type\":\"int24\"},{\"indexed\":false,\"internalType\":\"address\",\"name\":\"pool\",\"type\":\"address\"}],\"name\":\"PoolCreated\",\"type\":\"event\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"tokenA\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"tokenB\",\"type\":\"address\"},{\"internalType\":\"uint24\",\"name\":\"fee\",\"type\":\"uint24\"}],\"name\":\"createPool\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"pool\",\"type\":\"address\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint24\",\"name\":\"fee\",\"type\":\"uint24\"},{\"internalType\":\"int24\",\"name\":\"tickSpacing\",\"type\":\"int24\"}],\"name\":\"enableFeeAmount\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint24\",\"name\":\"\",\"type\":\"uint24\"}],\"name\":\"feeAmountTickSpacing\",\"outputs\":[{\"internalType\":\"int24\",\"name\":\"\",\"type\":\"int24\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"},{\"internalType\":\"uint24\",\"name\":\"\",\"type\":\"uint24\"}],\"name\":\"getPool\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"owner\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"parameters\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"factory\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"token0\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"token1\",\"type\":\"address\"},{\"internalType\":\"uint24\",\"name\":\"fee\",\"type\":\"uint24\"},{\"internalType\":\"int24\",\"name\":\"tickSpacing\",\"type\":\"int24\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_owner\",\"type\":\"address\"}],\"name\":\"setOwner\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"}]' var contractV3FactoryAddress = "0x1F98431c8aD98523631AE4a59f267346ea31F984" var contractV3SwapRouterV2Address = "0x68b3465833fb72A70ecDF485E0e4C7bD8665Fc45" function toAmount(s, decimals) { return Number((BigDecimal(BigInt(s)) / BigDecimal(Math.pow(10, decimals))).toString()) } function main() { // 注册Uniswap工厂合约的ABI exchange.IO("abi", contractV3FactoryAddress, abiFactory) // 注册Uniswap路由合约的ABI exchange.IO("abi", contractV3SwapRouterV2Address, abiRoute) // 获取交易对的池地址 var tokenIn = {name : "1INCH", address: "0x111111111117dC0aa78b770fA6A738034120C302", decimals: exchange.IO("api", "0x111111111117dC0aa78b770fA6A738034120C302", "decimals")} var tokenOut = {name : "WETH", address: "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2", decimals: exchange.IO("api", "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2", "decimals")} var poolAddress = exchange.IO("api", contractV3FactoryAddress, "getPool", tokenIn.address, tokenOut.address, 10000) // 注册池合约ABI exchange.IO("abi", poolAddress, abiPool) // 获取当前配置的钱包地址 var walletAddress = exchange.IO("address") // 1INCH -> ETH, tokenIn: 1INCH var allowanceAmount = exchange.IO("api", tokenIn.address, "allowance", walletAddress, contractV3SwapRouterV2Address) Log("allowanceAmount:", allowanceAmount, ", 使用toAmount()函数转换为可读数值:", toAmount(allowanceAmount, tokenIn.decimals)) }

对于代码中的toAmount()函数我们应该比较熟悉,前几章课程中我们多次使用了这个自定义函数代码,用于把数据处理为方便人类阅读的数值。

javascript
function toAmount(s, decimals) { return Number((BigDecimal(BigInt(s)) / BigDecimal(Math.pow(10, decimals))).toString()) }

运行代码,发现当前授权数量为0,那么要进行兑换就必须授权足够的数量给路由合约才行。

img

授权Uniswap V3路由合约

如果在使用allowance查询后发现授权数量不足,就需要使用approve授权Uniswap V3路由合约,允许其操作一定数量的ERC20代币。

还是以1INCH -> ETH兑换为例,使用ERC20approve方法给Uniswap的路由合约授权操作当前配置的钱包中的1INCH代币。

javascript
var abiRoute = '[{"inputs":[{"internalType":"address","name":"_factoryV2","type":"address"},{"internalType":"address","name":"factoryV3","type":"address"},{"internalType":"address","name":"_positionManager","type":"address"},{"internalType":"address","name":"_WETH9","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"WETH9","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"}],"name":"approveMax","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"}],"name":"approveMaxMinusOne","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"}],"name":"approveZeroThenMax","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"}],"name":"approveZeroThenMaxMinusOne","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"bytes","name":"data","type":"bytes"}],"name":"callPositionManager","outputs":[{"internalType":"bytes","name":"result","type":"bytes"}],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"bytes[]","name":"paths","type":"bytes[]"},{"internalType":"uint128[]","name":"amounts","type":"uint128[]"},{"internalType":"uint24","name":"maximumTickDivergence","type":"uint24"},{"internalType":"uint32","name":"secondsAgo","type":"uint32"}],"name":"checkOracleSlippage","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes","name":"path","type":"bytes"},{"internalType":"uint24","name":"maximumTickDivergence","type":"uint24"},{"internalType":"uint32","name":"secondsAgo","type":"uint32"}],"name":"checkOracleSlippage","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"components":[{"internalType":"bytes","name":"path","type":"bytes"},{"internalType":"address","name":"recipient","type":"address"},{"internalType":"uint256","name":"amountIn","type":"uint256"},{"internalType":"uint256","name":"amountOutMinimum","type":"uint256"}],"internalType":"struct IV3SwapRouter.ExactInputParams","name":"params","type":"tuple"}],"name":"exactInput","outputs":[{"internalType":"uint256","name":"amountOut","type":"uint256"}],"stateMutability":"payable","type":"function"},{"inputs":[{"components":[{"internalType":"address","name":"tokenIn","type":"address"},{"internalType":"address","name":"tokenOut","type":"address"},{"internalType":"uint24","name":"fee","type":"uint24"},{"internalType":"address","name":"recipient","type":"address"},{"internalType":"uint256","name":"amountIn","type":"uint256"},{"internalType":"uint256","name":"amountOutMinimum","type":"uint256"},{"internalType":"uint160","name":"sqrtPriceLimitX96","type":"uint160"}],"internalType":"struct IV3SwapRouter.ExactInputSingleParams","name":"params","type":"tuple"}],"name":"exactInputSingle","outputs":[{"internalType":"uint256","name":"amountOut","type":"uint256"}],"stateMutability":"payable","type":"function"},{"inputs":[{"components":[{"internalType":"bytes","name":"path","type":"bytes"},{"internalType":"address","name":"recipient","type":"address"},{"internalType":"uint256","name":"amountOut","type":"uint256"},{"internalType":"uint256","name":"amountInMaximum","type":"uint256"}],"internalType":"struct IV3SwapRouter.ExactOutputParams","name":"params","type":"tuple"}],"name":"exactOutput","outputs":[{"internalType":"uint256","name":"amountIn","type":"uint256"}],"stateMutability":"payable","type":"function"},{"inputs":[{"components":[{"internalType":"address","name":"tokenIn","type":"address"},{"internalType":"address","name":"tokenOut","type":"address"},{"internalType":"uint24","name":"fee","type":"uint24"},{"internalType":"address","name":"recipient","type":"address"},{"internalType":"uint256","name":"amountOut","type":"uint256"},{"internalType":"uint256","name":"amountInMaximum","type":"uint256"},{"internalType":"uint160","name":"sqrtPriceLimitX96","type":"uint160"}],"internalType":"struct IV3SwapRouter.ExactOutputSingleParams","name":"params","type":"tuple"}],"name":"exactOutputSingle","outputs":[{"internalType":"uint256","name":"amountIn","type":"uint256"}],"stateMutability":"payable","type":"function"},{"inputs":[],"name":"factory","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"factoryV2","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"getApprovalType","outputs":[{"internalType":"enum IApproveAndCall.ApprovalType","name":"","type":"uint8"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"components":[{"internalType":"address","name":"token0","type":"address"},{"internalType":"address","name":"token1","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"uint256","name":"amount0Min","type":"uint256"},{"internalType":"uint256","name":"amount1Min","type":"uint256"}],"internalType":"struct IApproveAndCall.IncreaseLiquidityParams","name":"params","type":"tuple"}],"name":"increaseLiquidity","outputs":[{"internalType":"bytes","name":"result","type":"bytes"}],"stateMutability":"payable","type":"function"},{"inputs":[{"components":[{"internalType":"address","name":"token0","type":"address"},{"internalType":"address","name":"token1","type":"address"},{"internalType":"uint24","name":"fee","type":"uint24"},{"internalType":"int24","name":"tickLower","type":"int24"},{"internalType":"int24","name":"tickUpper","type":"int24"},{"internalType":"uint256","name":"amount0Min","type":"uint256"},{"internalType":"uint256","name":"amount1Min","type":"uint256"},{"internalType":"address","name":"recipient","type":"address"}],"internalType":"struct IApproveAndCall.MintParams","name":"params","type":"tuple"}],"name":"mint","outputs":[{"internalType":"bytes","name":"result","type":"bytes"}],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"previousBlockhash","type":"bytes32"},{"internalType":"bytes[]","name":"data","type":"bytes[]"}],"name":"multicall","outputs":[{"internalType":"bytes[]","name":"","type":"bytes[]"}],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint256","name":"deadline","type":"uint256"},{"internalType":"bytes[]","name":"data","type":"bytes[]"}],"name":"multicall","outputs":[{"internalType":"bytes[]","name":"","type":"bytes[]"}],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"bytes[]","name":"data","type":"bytes[]"}],"name":"multicall","outputs":[{"internalType":"bytes[]","name":"results","type":"bytes[]"}],"stateMutability":"payable","type":"function"},{"inputs":[],"name":"positionManager","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"value","type":"uint256"}],"name":"pull","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[],"name":"refundETH","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"value","type":"uint256"},{"internalType":"uint256","name":"deadline","type":"uint256"},{"internalType":"uint8","name":"v","type":"uint8"},{"internalType":"bytes32","name":"r","type":"bytes32"},{"internalType":"bytes32","name":"s","type":"bytes32"}],"name":"selfPermit","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"nonce","type":"uint256"},{"internalType":"uint256","name":"expiry","type":"uint256"},{"internalType":"uint8","name":"v","type":"uint8"},{"internalType":"bytes32","name":"r","type":"bytes32"},{"internalType":"bytes32","name":"s","type":"bytes32"}],"name":"selfPermitAllowed","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"nonce","type":"uint256"},{"internalType":"uint256","name":"expiry","type":"uint256"},{"internalType":"uint8","name":"v","type":"uint8"},{"internalType":"bytes32","name":"r","type":"bytes32"},{"internalType":"bytes32","name":"s","type":"bytes32"}],"name":"selfPermitAllowedIfNecessary","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"value","type":"uint256"},{"internalType":"uint256","name":"deadline","type":"uint256"},{"internalType":"uint8","name":"v","type":"uint8"},{"internalType":"bytes32","name":"r","type":"bytes32"},{"internalType":"bytes32","name":"s","type":"bytes32"}],"name":"selfPermitIfNecessary","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint256","name":"amountIn","type":"uint256"},{"internalType":"uint256","name":"amountOutMin","type":"uint256"},{"internalType":"address[]","name":"path","type":"address[]"},{"internalType":"address","name":"to","type":"address"}],"name":"swapExactTokensForTokens","outputs":[{"internalType":"uint256","name":"amountOut","type":"uint256"}],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint256","name":"amountOut","type":"uint256"},{"internalType":"uint256","name":"amountInMax","type":"uint256"},{"internalType":"address[]","name":"path","type":"address[]"},{"internalType":"address","name":"to","type":"address"}],"name":"swapTokensForExactTokens","outputs":[{"internalType":"uint256","name":"amountIn","type":"uint256"}],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"amountMinimum","type":"uint256"},{"internalType":"address","name":"recipient","type":"address"}],"name":"sweepToken","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"amountMinimum","type":"uint256"}],"name":"sweepToken","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"amountMinimum","type":"uint256"},{"internalType":"uint256","name":"feeBips","type":"uint256"},{"internalType":"address","name":"feeRecipient","type":"address"}],"name":"sweepTokenWithFee","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"amountMinimum","type":"uint256"},{"internalType":"address","name":"recipient","type":"address"},{"internalType":"uint256","name":"feeBips","type":"uint256"},{"internalType":"address","name":"feeRecipient","type":"address"}],"name":"sweepTokenWithFee","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"int256","name":"amount0Delta","type":"int256"},{"internalType":"int256","name":"amount1Delta","type":"int256"},{"internalType":"bytes","name":"_data","type":"bytes"}],"name":"uniswapV3SwapCallback","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"amountMinimum","type":"uint256"},{"internalType":"address","name":"recipient","type":"address"}],"name":"unwrapWETH9","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint256","name":"amountMinimum","type":"uint256"}],"name":"unwrapWETH9","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint256","name":"amountMinimum","type":"uint256"},{"internalType":"address","name":"recipient","type":"address"},{"internalType":"uint256","name":"feeBips","type":"uint256"},{"internalType":"address","name":"feeRecipient","type":"address"}],"name":"unwrapWETH9WithFee","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint256","name":"amountMinimum","type":"uint256"},{"internalType":"uint256","name":"feeBips","type":"uint256"},{"internalType":"address","name":"feeRecipient","type":"address"}],"name":"unwrapWETH9WithFee","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint256","name":"value","type":"uint256"}],"name":"wrapETH","outputs":[],"stateMutability":"payable","type":"function"},{"stateMutability":"payable","type":"receive"}]'; var abiPool = '[{\"inputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"owner\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"int24\",\"name\":\"tickLower\",\"type\":\"int24\"},{\"indexed\":true,\"internalType\":\"int24\",\"name\":\"tickUpper\",\"type\":\"int24\"},{\"indexed\":false,\"internalType\":\"uint128\",\"name\":\"amount\",\"type\":\"uint128\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"amount0\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"amount1\",\"type\":\"uint256\"}],\"name\":\"Burn\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"owner\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"address\",\"name\":\"recipient\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"int24\",\"name\":\"tickLower\",\"type\":\"int24\"},{\"indexed\":true,\"internalType\":\"int24\",\"name\":\"tickUpper\",\"type\":\"int24\"},{\"indexed\":false,\"internalType\":\"uint128\",\"name\":\"amount0\",\"type\":\"uint128\"},{\"indexed\":false,\"internalType\":\"uint128\",\"name\":\"amount1\",\"type\":\"uint128\"}],\"name\":\"Collect\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"sender\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"recipient\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint128\",\"name\":\"amount0\",\"type\":\"uint128\"},{\"indexed\":false,\"internalType\":\"uint128\",\"name\":\"amount1\",\"type\":\"uint128\"}],\"name\":\"CollectProtocol\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"sender\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"recipient\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"amount0\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"amount1\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"paid0\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"paid1\",\"type\":\"uint256\"}],\"name\":\"Flash\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"uint16\",\"name\":\"observationCardinalityNextOld\",\"type\":\"uint16\"},{\"indexed\":false,\"internalType\":\"uint16\",\"name\":\"observationCardinalityNextNew\",\"type\":\"uint16\"}],\"name\":\"IncreaseObservationCardinalityNext\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"uint160\",\"name\":\"sqrtPriceX96\",\"type\":\"uint160\"},{\"indexed\":false,\"internalType\":\"int24\",\"name\":\"tick\",\"type\":\"int24\"}],\"name\":\"Initialize\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"address\",\"name\":\"sender\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"owner\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"int24\",\"name\":\"tickLower\",\"type\":\"int24\"},{\"indexed\":true,\"internalType\":\"int24\",\"name\":\"tickUpper\",\"type\":\"int24\"},{\"indexed\":false,\"internalType\":\"uint128\",\"name\":\"amount\",\"type\":\"uint128\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"amount0\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"amount1\",\"type\":\"uint256\"}],\"name\":\"Mint\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"uint8\",\"name\":\"feeProtocol0Old\",\"type\":\"uint8\"},{\"indexed\":false,\"internalType\":\"uint8\",\"name\":\"feeProtocol1Old\",\"type\":\"uint8\"},{\"indexed\":false,\"internalType\":\"uint8\",\"name\":\"feeProtocol0New\",\"type\":\"uint8\"},{\"indexed\":false,\"internalType\":\"uint8\",\"name\":\"feeProtocol1New\",\"type\":\"uint8\"}],\"name\":\"SetFeeProtocol\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"sender\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"recipient\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"int256\",\"name\":\"amount0\",\"type\":\"int256\"},{\"indexed\":false,\"internalType\":\"int256\",\"name\":\"amount1\",\"type\":\"int256\"},{\"indexed\":false,\"internalType\":\"uint160\",\"name\":\"sqrtPriceX96\",\"type\":\"uint160\"},{\"indexed\":false,\"internalType\":\"uint128\",\"name\":\"liquidity\",\"type\":\"uint128\"},{\"indexed\":false,\"internalType\":\"int24\",\"name\":\"tick\",\"type\":\"int24\"}],\"name\":\"Swap\",\"type\":\"event\"},{\"inputs\":[{\"internalType\":\"int24\",\"name\":\"tickLower\",\"type\":\"int24\"},{\"internalType\":\"int24\",\"name\":\"tickUpper\",\"type\":\"int24\"},{\"internalType\":\"uint128\",\"name\":\"amount\",\"type\":\"uint128\"}],\"name\":\"burn\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"amount0\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"amount1\",\"type\":\"uint256\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"recipient\",\"type\":\"address\"},{\"internalType\":\"int24\",\"name\":\"tickLower\",\"type\":\"int24\"},{\"internalType\":\"int24\",\"name\":\"tickUpper\",\"type\":\"int24\"},{\"internalType\":\"uint128\",\"name\":\"amount0Requested\",\"type\":\"uint128\"},{\"internalType\":\"uint128\",\"name\":\"amount1Requested\",\"type\":\"uint128\"}],\"name\":\"collect\",\"outputs\":[{\"internalType\":\"uint128\",\"name\":\"amount0\",\"type\":\"uint128\"},{\"internalType\":\"uint128\",\"name\":\"amount1\",\"type\":\"uint128\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"recipient\",\"type\":\"address\"},{\"internalType\":\"uint128\",\"name\":\"amount0Requested\",\"type\":\"uint128\"},{\"internalType\":\"uint128\",\"name\":\"amount1Requested\",\"type\":\"uint128\"}],\"name\":\"collectProtocol\",\"outputs\":[{\"internalType\":\"uint128\",\"name\":\"amount0\",\"type\":\"uint128\"},{\"internalType\":\"uint128\",\"name\":\"amount1\",\"type\":\"uint128\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"factory\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"fee\",\"outputs\":[{\"internalType\":\"uint24\",\"name\":\"\",\"type\":\"uint24\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"feeGrowthGlobal0X128\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"feeGrowthGlobal1X128\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"recipient\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amount0\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"amount1\",\"type\":\"uint256\"},{\"internalType\":\"bytes\",\"name\":\"data\",\"type\":\"bytes\"}],\"name\":\"flash\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint16\",\"name\":\"observationCardinalityNext\",\"type\":\"uint16\"}],\"name\":\"increaseObservationCardinalityNext\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint160\",\"name\":\"sqrtPriceX96\",\"type\":\"uint160\"}],\"name\":\"initialize\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"liquidity\",\"outputs\":[{\"internalType\":\"uint128\",\"name\":\"\",\"type\":\"uint128\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"maxLiquidityPerTick\",\"outputs\":[{\"internalType\":\"uint128\",\"name\":\"\",\"type\":\"uint128\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"recipient\",\"type\":\"address\"},{\"internalType\":\"int24\",\"name\":\"tickLower\",\"type\":\"int24\"},{\"internalType\":\"int24\",\"name\":\"tickUpper\",\"type\":\"int24\"},{\"internalType\":\"uint128\",\"name\":\"amount\",\"type\":\"uint128\"},{\"internalType\":\"bytes\",\"name\":\"data\",\"type\":\"bytes\"}],\"name\":\"mint\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"amount0\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"amount1\",\"type\":\"uint256\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"name\":\"observations\",\"outputs\":[{\"internalType\":\"uint32\",\"name\":\"blockTimestamp\",\"type\":\"uint32\"},{\"internalType\":\"int56\",\"name\":\"tickCumulative\",\"type\":\"int56\"},{\"internalType\":\"uint160\",\"name\":\"secondsPerLiquidityCumulativeX128\",\"type\":\"uint160\"},{\"internalType\":\"bool\",\"name\":\"initialized\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint32[]\",\"name\":\"secondsAgos\",\"type\":\"uint32[]\"}],\"name\":\"observe\",\"outputs\":[{\"internalType\":\"int56[]\",\"name\":\"tickCumulatives\",\"type\":\"int56[]\"},{\"internalType\":\"uint160[]\",\"name\":\"secondsPerLiquidityCumulativeX128s\",\"type\":\"uint160[]\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"name\":\"positions\",\"outputs\":[{\"internalType\":\"uint128\",\"name\":\"liquidity\",\"type\":\"uint128\"},{\"internalType\":\"uint256\",\"name\":\"feeGrowthInside0LastX128\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"feeGrowthInside1LastX128\",\"type\":\"uint256\"},{\"internalType\":\"uint128\",\"name\":\"tokensOwed0\",\"type\":\"uint128\"},{\"internalType\":\"uint128\",\"name\":\"tokensOwed1\",\"type\":\"uint128\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"protocolFees\",\"outputs\":[{\"internalType\":\"uint128\",\"name\":\"token0\",\"type\":\"uint128\"},{\"internalType\":\"uint128\",\"name\":\"token1\",\"type\":\"uint128\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint8\",\"name\":\"feeProtocol0\",\"type\":\"uint8\"},{\"internalType\":\"uint8\",\"name\":\"feeProtocol1\",\"type\":\"uint8\"}],\"name\":\"setFeeProtocol\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"slot0\",\"outputs\":[{\"internalType\":\"uint160\",\"name\":\"sqrtPriceX96\",\"type\":\"uint160\"},{\"internalType\":\"int24\",\"name\":\"tick\",\"type\":\"int24\"},{\"internalType\":\"uint16\",\"name\":\"observationIndex\",\"type\":\"uint16\"},{\"internalType\":\"uint16\",\"name\":\"observationCardinality\",\"type\":\"uint16\"},{\"internalType\":\"uint16\",\"name\":\"observationCardinalityNext\",\"type\":\"uint16\"},{\"internalType\":\"uint8\",\"name\":\"feeProtocol\",\"type\":\"uint8\"},{\"internalType\":\"bool\",\"name\":\"unlocked\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"int24\",\"name\":\"tickLower\",\"type\":\"int24\"},{\"internalType\":\"int24\",\"name\":\"tickUpper\",\"type\":\"int24\"}],\"name\":\"snapshotCumulativesInside\",\"outputs\":[{\"internalType\":\"int56\",\"name\":\"tickCumulativeInside\",\"type\":\"int56\"},{\"internalType\":\"uint160\",\"name\":\"secondsPerLiquidityInsideX128\",\"type\":\"uint160\"},{\"internalType\":\"uint32\",\"name\":\"secondsInside\",\"type\":\"uint32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"recipient\",\"type\":\"address\"},{\"internalType\":\"bool\",\"name\":\"zeroForOne\",\"type\":\"bool\"},{\"internalType\":\"int256\",\"name\":\"amountSpecified\",\"type\":\"int256\"},{\"internalType\":\"uint160\",\"name\":\"sqrtPriceLimitX96\",\"type\":\"uint160\"},{\"internalType\":\"bytes\",\"name\":\"data\",\"type\":\"bytes\"}],\"name\":\"swap\",\"outputs\":[{\"internalType\":\"int256\",\"name\":\"amount0\",\"type\":\"int256\"},{\"internalType\":\"int256\",\"name\":\"amount1\",\"type\":\"int256\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"int16\",\"name\":\"\",\"type\":\"int16\"}],\"name\":\"tickBitmap\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"tickSpacing\",\"outputs\":[{\"internalType\":\"int24\",\"name\":\"\",\"type\":\"int24\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"int24\",\"name\":\"\",\"type\":\"int24\"}],\"name\":\"ticks\",\"outputs\":[{\"internalType\":\"uint128\",\"name\":\"liquidityGross\",\"type\":\"uint128\"},{\"internalType\":\"int128\",\"name\":\"liquidityNet\",\"type\":\"int128\"},{\"internalType\":\"uint256\",\"name\":\"feeGrowthOutside0X128\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"feeGrowthOutside1X128\",\"type\":\"uint256\"},{\"internalType\":\"int56\",\"name\":\"tickCumulativeOutside\",\"type\":\"int56\"},{\"internalType\":\"uint160\",\"name\":\"secondsPerLiquidityOutsideX128\",\"type\":\"uint160\"},{\"internalType\":\"uint32\",\"name\":\"secondsOutside\",\"type\":\"uint32\"},{\"internalType\":\"bool\",\"name\":\"initialized\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"token0\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"token1\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"}]' var abiFactory = '[{\"inputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"uint24\",\"name\":\"fee\",\"type\":\"uint24\"},{\"indexed\":true,\"internalType\":\"int24\",\"name\":\"tickSpacing\",\"type\":\"int24\"}],\"name\":\"FeeAmountEnabled\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"oldOwner\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"newOwner\",\"type\":\"address\"}],\"name\":\"OwnerChanged\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"token0\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"token1\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"uint24\",\"name\":\"fee\",\"type\":\"uint24\"},{\"indexed\":false,\"internalType\":\"int24\",\"name\":\"tickSpacing\",\"type\":\"int24\"},{\"indexed\":false,\"internalType\":\"address\",\"name\":\"pool\",\"type\":\"address\"}],\"name\":\"PoolCreated\",\"type\":\"event\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"tokenA\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"tokenB\",\"type\":\"address\"},{\"internalType\":\"uint24\",\"name\":\"fee\",\"type\":\"uint24\"}],\"name\":\"createPool\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"pool\",\"type\":\"address\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint24\",\"name\":\"fee\",\"type\":\"uint24\"},{\"internalType\":\"int24\",\"name\":\"tickSpacing\",\"type\":\"int24\"}],\"name\":\"enableFeeAmount\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint24\",\"name\":\"\",\"type\":\"uint24\"}],\"name\":\"feeAmountTickSpacing\",\"outputs\":[{\"internalType\":\"int24\",\"name\":\"\",\"type\":\"int24\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"},{\"internalType\":\"uint24\",\"name\":\"\",\"type\":\"uint24\"}],\"name\":\"getPool\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"owner\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"parameters\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"factory\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"token0\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"token1\",\"type\":\"address\"},{\"internalType\":\"uint24\",\"name\":\"fee\",\"type\":\"uint24\"},{\"internalType\":\"int24\",\"name\":\"tickSpacing\",\"type\":\"int24\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_owner\",\"type\":\"address\"}],\"name\":\"setOwner\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"}]' var contractV3FactoryAddress = "0x1F98431c8aD98523631AE4a59f267346ea31F984" var contractV3SwapRouterV2Address = "0x68b3465833fb72A70ecDF485E0e4C7bD8665Fc45" function toAmount(s, decimals) { return Number((BigDecimal(BigInt(s)) / BigDecimal(Math.pow(10, decimals))).toString()) } function toInnerAmount(n, decimals) { return (BigDecimal(n) * BigDecimal(Math.pow(10, decimals))).toFixed(0) } function main() { // 注册Uniswap工厂合约的ABI exchange.IO("abi", contractV3FactoryAddress, abiFactory) // 注册Uniswap路由合约的ABI exchange.IO("abi", contractV3SwapRouterV2Address, abiRoute) // 获取交易对的池地址 var tokenIn = {name : "1INCH", address: "0x111111111117dC0aa78b770fA6A738034120C302", decimals: exchange.IO("api", "0x111111111117dC0aa78b770fA6A738034120C302", "decimals")} var tokenOut = {name : "WETH", address: "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2", decimals: exchange.IO("api", "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2", "decimals")} var poolAddress = exchange.IO("api", contractV3FactoryAddress, "getPool", tokenIn.address, tokenOut.address, 10000) // 注册池合约ABI exchange.IO("abi", poolAddress, abiPool) // 获取当前配置的钱包地址 var walletAddress = exchange.IO("address") // 1INCH -> WETH, tokenIn: 1INCH var allowanceAmount = exchange.IO("api", tokenIn.address, "allowance", walletAddress, contractV3SwapRouterV2Address) Log("allowanceAmount:", allowanceAmount, ", 使用toAmount()函数转换为可读数值:", toAmount(allowanceAmount, tokenIn.decimals)) var balance = exchange.IO("api", "0x111111111117dC0aa78b770fA6A738034120C302", "balanceOf", walletAddress) var balanceOf1INCH = toAmount(balance, tokenIn.decimals) Log("balanceOf1INCH:", balanceOf1INCH) var swapAmount = 38 if (balanceOf1INCH < swapAmount) { Log("钱包中", tokenIn.name, "不足,数量只有:", balanceOf1INCH) } if (toAmount(allowanceAmount, tokenIn.decimals) < swapAmount) { Log("授权数量不足,进行授权") // 也可以指定授权数量为无限,即把代码中toInnerAmount(swapAmount, tokenIn.decimals) 替换为 '0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff' var txApprove = exchange.IO("api", tokenIn.address, "approve", contractV3SwapRouterV2Address, toInnerAmount(swapAmount, tokenIn.decimals)) if (!txApprove) { Log("授权失败") } else { for (var i = 0; i < 10; i++) { Sleep(5000) var info = exchange.IO("api", "eth", "eth_getTransactionReceipt", txApprove) if (info && info.gasUsed) { Log("info:", info) break } Log('Transaction not yet mined', txApprove) } // 再次查询授权数量 allowanceAmount = exchange.IO("api", tokenIn.address, "allowance", walletAddress, contractV3SwapRouterV2Address) Log("再次查询授权数量,", "allowanceAmount:", allowanceAmount, ", 使用toAmount()函数转换为可读数值:", toAmount(allowanceAmount, tokenIn.decimals)) } } }

img

代码中增加了一个toInnerAmount()自定义函数的实现,这个函数我们之前的课程也经常用到,这里不再赘述。
可以看到授权记录,再次调用代码检查授权情况:

javascript
allowanceAmount = exchange.IO("api", tokenIn.address, "allowance", walletAddress, contractV3SwapRouterV2Address) Log("再次查询授权数量,", "allowanceAmount:", allowanceAmount, ", 使用toAmount()函数转换为可读数值:", toAmount(allowanceAmount, tokenIn.decimals))

img

使用Uniswap进行代币兑换

当钱包中有足够的需要支付的代币(即:tokenIn),并且授权Uniswap路由合约足够的操作数量,接下来就可以执行兑换了。
Uniswap路由合约有多种兑换方法,这里演示使用exactInput方法,实际的兑换池为1INCH/WETH,兑换之后再使用unwrapWETH9方法把WETH解包为ETH。这个概念我们在之前的ERC20章节withdraw说明中讲解过。

兑换操作主要使用了3个方法即exactInputunwrapWETH9multicall,首先把Uniswap路由合约的exactInput方法的调用、unwrapWETH9方法的调用编码,然后再使用路由合约的multicall方法批量执行。

javascript
// 兑换 var recipientAddress = "0x0000000000000000000000000000000000000002" // 如果最终兑换到手的是ETH或者WETH,需要把exactInput方法的recipient参数指定为"0x0000000000000000000000000000000000000002" var fee = exchange.IO("encodePacked", "uint24", 10000) var path = tokenIn.address.slice(2).toLowerCase() + fee + tokenOut.address.slice(2).toLowerCase() var minOut = 1 var amountIn = toInnerAmount(swapAmount, tokenIn.decimals) var swapToken = exchange.IO("encode", contractV3SwapRouterV2Address, "exactInput", { path: path, recipient: recipientAddress, amountIn: amountIn, amountOutMinimum: minOut }) var data = [swapToken] data.push(exchange.IO("encode", contractV3SwapRouterV2Address, "unwrapWETH9(uint256,address)", 1, walletAddress)) var tx = exchange.IO("api", contractV3SwapRouterV2Address, "multicall(uint256,bytes[])", 0, (new Date().getTime() / 1000) + 3600, data) Log("tx:", tx)
  • 使用exchange.IO("encodePacked", ...)函数把费率参数编码压缩。
  • 根据exactInput方法的参数要求,构造路径参数path
  • 使用exchange.IO("encode", ...)函数编码exactInput方法的调用。
    需要注意:如果最终兑换到手的是ETH或者WETH,需要把exactInput方法的recipient参数指定为"0x0000000000000000000000000000000000000002"
  • 使用exchange.IO("encode", ...)函数编码unwrapWETH9(uint256,address)方法的调用。
  • 使用multicall(uint256,bytes[])方法执行以上编码的函数调用。

查询到兑换结果:

img

完整的代码:

javascript
var abiRoute = '[{"inputs":[{"internalType":"address","name":"_factoryV2","type":"address"},{"internalType":"address","name":"factoryV3","type":"address"},{"internalType":"address","name":"_positionManager","type":"address"},{"internalType":"address","name":"_WETH9","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"WETH9","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"}],"name":"approveMax","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"}],"name":"approveMaxMinusOne","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"}],"name":"approveZeroThenMax","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"}],"name":"approveZeroThenMaxMinusOne","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"bytes","name":"data","type":"bytes"}],"name":"callPositionManager","outputs":[{"internalType":"bytes","name":"result","type":"bytes"}],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"bytes[]","name":"paths","type":"bytes[]"},{"internalType":"uint128[]","name":"amounts","type":"uint128[]"},{"internalType":"uint24","name":"maximumTickDivergence","type":"uint24"},{"internalType":"uint32","name":"secondsAgo","type":"uint32"}],"name":"checkOracleSlippage","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes","name":"path","type":"bytes"},{"internalType":"uint24","name":"maximumTickDivergence","type":"uint24"},{"internalType":"uint32","name":"secondsAgo","type":"uint32"}],"name":"checkOracleSlippage","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"components":[{"internalType":"bytes","name":"path","type":"bytes"},{"internalType":"address","name":"recipient","type":"address"},{"internalType":"uint256","name":"amountIn","type":"uint256"},{"internalType":"uint256","name":"amountOutMinimum","type":"uint256"}],"internalType":"struct IV3SwapRouter.ExactInputParams","name":"params","type":"tuple"}],"name":"exactInput","outputs":[{"internalType":"uint256","name":"amountOut","type":"uint256"}],"stateMutability":"payable","type":"function"},{"inputs":[{"components":[{"internalType":"address","name":"tokenIn","type":"address"},{"internalType":"address","name":"tokenOut","type":"address"},{"internalType":"uint24","name":"fee","type":"uint24"},{"internalType":"address","name":"recipient","type":"address"},{"internalType":"uint256","name":"amountIn","type":"uint256"},{"internalType":"uint256","name":"amountOutMinimum","type":"uint256"},{"internalType":"uint160","name":"sqrtPriceLimitX96","type":"uint160"}],"internalType":"struct IV3SwapRouter.ExactInputSingleParams","name":"params","type":"tuple"}],"name":"exactInputSingle","outputs":[{"internalType":"uint256","name":"amountOut","type":"uint256"}],"stateMutability":"payable","type":"function"},{"inputs":[{"components":[{"internalType":"bytes","name":"path","type":"bytes"},{"internalType":"address","name":"recipient","type":"address"},{"internalType":"uint256","name":"amountOut","type":"uint256"},{"internalType":"uint256","name":"amountInMaximum","type":"uint256"}],"internalType":"struct IV3SwapRouter.ExactOutputParams","name":"params","type":"tuple"}],"name":"exactOutput","outputs":[{"internalType":"uint256","name":"amountIn","type":"uint256"}],"stateMutability":"payable","type":"function"},{"inputs":[{"components":[{"internalType":"address","name":"tokenIn","type":"address"},{"internalType":"address","name":"tokenOut","type":"address"},{"internalType":"uint24","name":"fee","type":"uint24"},{"internalType":"address","name":"recipient","type":"address"},{"internalType":"uint256","name":"amountOut","type":"uint256"},{"internalType":"uint256","name":"amountInMaximum","type":"uint256"},{"internalType":"uint160","name":"sqrtPriceLimitX96","type":"uint160"}],"internalType":"struct IV3SwapRouter.ExactOutputSingleParams","name":"params","type":"tuple"}],"name":"exactOutputSingle","outputs":[{"internalType":"uint256","name":"amountIn","type":"uint256"}],"stateMutability":"payable","type":"function"},{"inputs":[],"name":"factory","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"factoryV2","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"getApprovalType","outputs":[{"internalType":"enum IApproveAndCall.ApprovalType","name":"","type":"uint8"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"components":[{"internalType":"address","name":"token0","type":"address"},{"internalType":"address","name":"token1","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"uint256","name":"amount0Min","type":"uint256"},{"internalType":"uint256","name":"amount1Min","type":"uint256"}],"internalType":"struct IApproveAndCall.IncreaseLiquidityParams","name":"params","type":"tuple"}],"name":"increaseLiquidity","outputs":[{"internalType":"bytes","name":"result","type":"bytes"}],"stateMutability":"payable","type":"function"},{"inputs":[{"components":[{"internalType":"address","name":"token0","type":"address"},{"internalType":"address","name":"token1","type":"address"},{"internalType":"uint24","name":"fee","type":"uint24"},{"internalType":"int24","name":"tickLower","type":"int24"},{"internalType":"int24","name":"tickUpper","type":"int24"},{"internalType":"uint256","name":"amount0Min","type":"uint256"},{"internalType":"uint256","name":"amount1Min","type":"uint256"},{"internalType":"address","name":"recipient","type":"address"}],"internalType":"struct IApproveAndCall.MintParams","name":"params","type":"tuple"}],"name":"mint","outputs":[{"internalType":"bytes","name":"result","type":"bytes"}],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"previousBlockhash","type":"bytes32"},{"internalType":"bytes[]","name":"data","type":"bytes[]"}],"name":"multicall","outputs":[{"internalType":"bytes[]","name":"","type":"bytes[]"}],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint256","name":"deadline","type":"uint256"},{"internalType":"bytes[]","name":"data","type":"bytes[]"}],"name":"multicall","outputs":[{"internalType":"bytes[]","name":"","type":"bytes[]"}],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"bytes[]","name":"data","type":"bytes[]"}],"name":"multicall","outputs":[{"internalType":"bytes[]","name":"results","type":"bytes[]"}],"stateMutability":"payable","type":"function"},{"inputs":[],"name":"positionManager","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"value","type":"uint256"}],"name":"pull","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[],"name":"refundETH","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"value","type":"uint256"},{"internalType":"uint256","name":"deadline","type":"uint256"},{"internalType":"uint8","name":"v","type":"uint8"},{"internalType":"bytes32","name":"r","type":"bytes32"},{"internalType":"bytes32","name":"s","type":"bytes32"}],"name":"selfPermit","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"nonce","type":"uint256"},{"internalType":"uint256","name":"expiry","type":"uint256"},{"internalType":"uint8","name":"v","type":"uint8"},{"internalType":"bytes32","name":"r","type":"bytes32"},{"internalType":"bytes32","name":"s","type":"bytes32"}],"name":"selfPermitAllowed","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"nonce","type":"uint256"},{"internalType":"uint256","name":"expiry","type":"uint256"},{"internalType":"uint8","name":"v","type":"uint8"},{"internalType":"bytes32","name":"r","type":"bytes32"},{"internalType":"bytes32","name":"s","type":"bytes32"}],"name":"selfPermitAllowedIfNecessary","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"value","type":"uint256"},{"internalType":"uint256","name":"deadline","type":"uint256"},{"internalType":"uint8","name":"v","type":"uint8"},{"internalType":"bytes32","name":"r","type":"bytes32"},{"internalType":"bytes32","name":"s","type":"bytes32"}],"name":"selfPermitIfNecessary","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint256","name":"amountIn","type":"uint256"},{"internalType":"uint256","name":"amountOutMin","type":"uint256"},{"internalType":"address[]","name":"path","type":"address[]"},{"internalType":"address","name":"to","type":"address"}],"name":"swapExactTokensForTokens","outputs":[{"internalType":"uint256","name":"amountOut","type":"uint256"}],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint256","name":"amountOut","type":"uint256"},{"internalType":"uint256","name":"amountInMax","type":"uint256"},{"internalType":"address[]","name":"path","type":"address[]"},{"internalType":"address","name":"to","type":"address"}],"name":"swapTokensForExactTokens","outputs":[{"internalType":"uint256","name":"amountIn","type":"uint256"}],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"amountMinimum","type":"uint256"},{"internalType":"address","name":"recipient","type":"address"}],"name":"sweepToken","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"amountMinimum","type":"uint256"}],"name":"sweepToken","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"amountMinimum","type":"uint256"},{"internalType":"uint256","name":"feeBips","type":"uint256"},{"internalType":"address","name":"feeRecipient","type":"address"}],"name":"sweepTokenWithFee","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"amountMinimum","type":"uint256"},{"internalType":"address","name":"recipient","type":"address"},{"internalType":"uint256","name":"feeBips","type":"uint256"},{"internalType":"address","name":"feeRecipient","type":"address"}],"name":"sweepTokenWithFee","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"int256","name":"amount0Delta","type":"int256"},{"internalType":"int256","name":"amount1Delta","type":"int256"},{"internalType":"bytes","name":"_data","type":"bytes"}],"name":"uniswapV3SwapCallback","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"amountMinimum","type":"uint256"},{"internalType":"address","name":"recipient","type":"address"}],"name":"unwrapWETH9","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint256","name":"amountMinimum","type":"uint256"}],"name":"unwrapWETH9","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint256","name":"amountMinimum","type":"uint256"},{"internalType":"address","name":"recipient","type":"address"},{"internalType":"uint256","name":"feeBips","type":"uint256"},{"internalType":"address","name":"feeRecipient","type":"address"}],"name":"unwrapWETH9WithFee","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint256","name":"amountMinimum","type":"uint256"},{"internalType":"uint256","name":"feeBips","type":"uint256"},{"internalType":"address","name":"feeRecipient","type":"address"}],"name":"unwrapWETH9WithFee","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint256","name":"value","type":"uint256"}],"name":"wrapETH","outputs":[],"stateMutability":"payable","type":"function"},{"stateMutability":"payable","type":"receive"}]'; var abiPool = '[{\"inputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"owner\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"int24\",\"name\":\"tickLower\",\"type\":\"int24\"},{\"indexed\":true,\"internalType\":\"int24\",\"name\":\"tickUpper\",\"type\":\"int24\"},{\"indexed\":false,\"internalType\":\"uint128\",\"name\":\"amount\",\"type\":\"uint128\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"amount0\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"amount1\",\"type\":\"uint256\"}],\"name\":\"Burn\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"owner\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"address\",\"name\":\"recipient\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"int24\",\"name\":\"tickLower\",\"type\":\"int24\"},{\"indexed\":true,\"internalType\":\"int24\",\"name\":\"tickUpper\",\"type\":\"int24\"},{\"indexed\":false,\"internalType\":\"uint128\",\"name\":\"amount0\",\"type\":\"uint128\"},{\"indexed\":false,\"internalType\":\"uint128\",\"name\":\"amount1\",\"type\":\"uint128\"}],\"name\":\"Collect\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"sender\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"recipient\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint128\",\"name\":\"amount0\",\"type\":\"uint128\"},{\"indexed\":false,\"internalType\":\"uint128\",\"name\":\"amount1\",\"type\":\"uint128\"}],\"name\":\"CollectProtocol\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"sender\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"recipient\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"amount0\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"amount1\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"paid0\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"paid1\",\"type\":\"uint256\"}],\"name\":\"Flash\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"uint16\",\"name\":\"observationCardinalityNextOld\",\"type\":\"uint16\"},{\"indexed\":false,\"internalType\":\"uint16\",\"name\":\"observationCardinalityNextNew\",\"type\":\"uint16\"}],\"name\":\"IncreaseObservationCardinalityNext\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"uint160\",\"name\":\"sqrtPriceX96\",\"type\":\"uint160\"},{\"indexed\":false,\"internalType\":\"int24\",\"name\":\"tick\",\"type\":\"int24\"}],\"name\":\"Initialize\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"address\",\"name\":\"sender\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"owner\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"int24\",\"name\":\"tickLower\",\"type\":\"int24\"},{\"indexed\":true,\"internalType\":\"int24\",\"name\":\"tickUpper\",\"type\":\"int24\"},{\"indexed\":false,\"internalType\":\"uint128\",\"name\":\"amount\",\"type\":\"uint128\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"amount0\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"amount1\",\"type\":\"uint256\"}],\"name\":\"Mint\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"uint8\",\"name\":\"feeProtocol0Old\",\"type\":\"uint8\"},{\"indexed\":false,\"internalType\":\"uint8\",\"name\":\"feeProtocol1Old\",\"type\":\"uint8\"},{\"indexed\":false,\"internalType\":\"uint8\",\"name\":\"feeProtocol0New\",\"type\":\"uint8\"},{\"indexed\":false,\"internalType\":\"uint8\",\"name\":\"feeProtocol1New\",\"type\":\"uint8\"}],\"name\":\"SetFeeProtocol\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"sender\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"recipient\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"int256\",\"name\":\"amount0\",\"type\":\"int256\"},{\"indexed\":false,\"internalType\":\"int256\",\"name\":\"amount1\",\"type\":\"int256\"},{\"indexed\":false,\"internalType\":\"uint160\",\"name\":\"sqrtPriceX96\",\"type\":\"uint160\"},{\"indexed\":false,\"internalType\":\"uint128\",\"name\":\"liquidity\",\"type\":\"uint128\"},{\"indexed\":false,\"internalType\":\"int24\",\"name\":\"tick\",\"type\":\"int24\"}],\"name\":\"Swap\",\"type\":\"event\"},{\"inputs\":[{\"internalType\":\"int24\",\"name\":\"tickLower\",\"type\":\"int24\"},{\"internalType\":\"int24\",\"name\":\"tickUpper\",\"type\":\"int24\"},{\"internalType\":\"uint128\",\"name\":\"amount\",\"type\":\"uint128\"}],\"name\":\"burn\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"amount0\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"amount1\",\"type\":\"uint256\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"recipient\",\"type\":\"address\"},{\"internalType\":\"int24\",\"name\":\"tickLower\",\"type\":\"int24\"},{\"internalType\":\"int24\",\"name\":\"tickUpper\",\"type\":\"int24\"},{\"internalType\":\"uint128\",\"name\":\"amount0Requested\",\"type\":\"uint128\"},{\"internalType\":\"uint128\",\"name\":\"amount1Requested\",\"type\":\"uint128\"}],\"name\":\"collect\",\"outputs\":[{\"internalType\":\"uint128\",\"name\":\"amount0\",\"type\":\"uint128\"},{\"internalType\":\"uint128\",\"name\":\"amount1\",\"type\":\"uint128\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"recipient\",\"type\":\"address\"},{\"internalType\":\"uint128\",\"name\":\"amount0Requested\",\"type\":\"uint128\"},{\"internalType\":\"uint128\",\"name\":\"amount1Requested\",\"type\":\"uint128\"}],\"name\":\"collectProtocol\",\"outputs\":[{\"internalType\":\"uint128\",\"name\":\"amount0\",\"type\":\"uint128\"},{\"internalType\":\"uint128\",\"name\":\"amount1\",\"type\":\"uint128\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"factory\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"fee\",\"outputs\":[{\"internalType\":\"uint24\",\"name\":\"\",\"type\":\"uint24\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"feeGrowthGlobal0X128\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"feeGrowthGlobal1X128\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"recipient\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amount0\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"amount1\",\"type\":\"uint256\"},{\"internalType\":\"bytes\",\"name\":\"data\",\"type\":\"bytes\"}],\"name\":\"flash\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint16\",\"name\":\"observationCardinalityNext\",\"type\":\"uint16\"}],\"name\":\"increaseObservationCardinalityNext\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint160\",\"name\":\"sqrtPriceX96\",\"type\":\"uint160\"}],\"name\":\"initialize\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"liquidity\",\"outputs\":[{\"internalType\":\"uint128\",\"name\":\"\",\"type\":\"uint128\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"maxLiquidityPerTick\",\"outputs\":[{\"internalType\":\"uint128\",\"name\":\"\",\"type\":\"uint128\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"recipient\",\"type\":\"address\"},{\"internalType\":\"int24\",\"name\":\"tickLower\",\"type\":\"int24\"},{\"internalType\":\"int24\",\"name\":\"tickUpper\",\"type\":\"int24\"},{\"internalType\":\"uint128\",\"name\":\"amount\",\"type\":\"uint128\"},{\"internalType\":\"bytes\",\"name\":\"data\",\"type\":\"bytes\"}],\"name\":\"mint\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"amount0\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"amount1\",\"type\":\"uint256\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"name\":\"observations\",\"outputs\":[{\"internalType\":\"uint32\",\"name\":\"blockTimestamp\",\"type\":\"uint32\"},{\"internalType\":\"int56\",\"name\":\"tickCumulative\",\"type\":\"int56\"},{\"internalType\":\"uint160\",\"name\":\"secondsPerLiquidityCumulativeX128\",\"type\":\"uint160\"},{\"internalType\":\"bool\",\"name\":\"initialized\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint32[]\",\"name\":\"secondsAgos\",\"type\":\"uint32[]\"}],\"name\":\"observe\",\"outputs\":[{\"internalType\":\"int56[]\",\"name\":\"tickCumulatives\",\"type\":\"int56[]\"},{\"internalType\":\"uint160[]\",\"name\":\"secondsPerLiquidityCumulativeX128s\",\"type\":\"uint160[]\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"name\":\"positions\",\"outputs\":[{\"internalType\":\"uint128\",\"name\":\"liquidity\",\"type\":\"uint128\"},{\"internalType\":\"uint256\",\"name\":\"feeGrowthInside0LastX128\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"feeGrowthInside1LastX128\",\"type\":\"uint256\"},{\"internalType\":\"uint128\",\"name\":\"tokensOwed0\",\"type\":\"uint128\"},{\"internalType\":\"uint128\",\"name\":\"tokensOwed1\",\"type\":\"uint128\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"protocolFees\",\"outputs\":[{\"internalType\":\"uint128\",\"name\":\"token0\",\"type\":\"uint128\"},{\"internalType\":\"uint128\",\"name\":\"token1\",\"type\":\"uint128\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint8\",\"name\":\"feeProtocol0\",\"type\":\"uint8\"},{\"internalType\":\"uint8\",\"name\":\"feeProtocol1\",\"type\":\"uint8\"}],\"name\":\"setFeeProtocol\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"slot0\",\"outputs\":[{\"internalType\":\"uint160\",\"name\":\"sqrtPriceX96\",\"type\":\"uint160\"},{\"internalType\":\"int24\",\"name\":\"tick\",\"type\":\"int24\"},{\"internalType\":\"uint16\",\"name\":\"observationIndex\",\"type\":\"uint16\"},{\"internalType\":\"uint16\",\"name\":\"observationCardinality\",\"type\":\"uint16\"},{\"internalType\":\"uint16\",\"name\":\"observationCardinalityNext\",\"type\":\"uint16\"},{\"internalType\":\"uint8\",\"name\":\"feeProtocol\",\"type\":\"uint8\"},{\"internalType\":\"bool\",\"name\":\"unlocked\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"int24\",\"name\":\"tickLower\",\"type\":\"int24\"},{\"internalType\":\"int24\",\"name\":\"tickUpper\",\"type\":\"int24\"}],\"name\":\"snapshotCumulativesInside\",\"outputs\":[{\"internalType\":\"int56\",\"name\":\"tickCumulativeInside\",\"type\":\"int56\"},{\"internalType\":\"uint160\",\"name\":\"secondsPerLiquidityInsideX128\",\"type\":\"uint160\"},{\"internalType\":\"uint32\",\"name\":\"secondsInside\",\"type\":\"uint32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"recipient\",\"type\":\"address\"},{\"internalType\":\"bool\",\"name\":\"zeroForOne\",\"type\":\"bool\"},{\"internalType\":\"int256\",\"name\":\"amountSpecified\",\"type\":\"int256\"},{\"internalType\":\"uint160\",\"name\":\"sqrtPriceLimitX96\",\"type\":\"uint160\"},{\"internalType\":\"bytes\",\"name\":\"data\",\"type\":\"bytes\"}],\"name\":\"swap\",\"outputs\":[{\"internalType\":\"int256\",\"name\":\"amount0\",\"type\":\"int256\"},{\"internalType\":\"int256\",\"name\":\"amount1\",\"type\":\"int256\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"int16\",\"name\":\"\",\"type\":\"int16\"}],\"name\":\"tickBitmap\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"tickSpacing\",\"outputs\":[{\"internalType\":\"int24\",\"name\":\"\",\"type\":\"int24\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"int24\",\"name\":\"\",\"type\":\"int24\"}],\"name\":\"ticks\",\"outputs\":[{\"internalType\":\"uint128\",\"name\":\"liquidityGross\",\"type\":\"uint128\"},{\"internalType\":\"int128\",\"name\":\"liquidityNet\",\"type\":\"int128\"},{\"internalType\":\"uint256\",\"name\":\"feeGrowthOutside0X128\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"feeGrowthOutside1X128\",\"type\":\"uint256\"},{\"internalType\":\"int56\",\"name\":\"tickCumulativeOutside\",\"type\":\"int56\"},{\"internalType\":\"uint160\",\"name\":\"secondsPerLiquidityOutsideX128\",\"type\":\"uint160\"},{\"internalType\":\"uint32\",\"name\":\"secondsOutside\",\"type\":\"uint32\"},{\"internalType\":\"bool\",\"name\":\"initialized\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"token0\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"token1\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"}]' var abiFactory = '[{\"inputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"uint24\",\"name\":\"fee\",\"type\":\"uint24\"},{\"indexed\":true,\"internalType\":\"int24\",\"name\":\"tickSpacing\",\"type\":\"int24\"}],\"name\":\"FeeAmountEnabled\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"oldOwner\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"newOwner\",\"type\":\"address\"}],\"name\":\"OwnerChanged\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"token0\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"token1\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"uint24\",\"name\":\"fee\",\"type\":\"uint24\"},{\"indexed\":false,\"internalType\":\"int24\",\"name\":\"tickSpacing\",\"type\":\"int24\"},{\"indexed\":false,\"internalType\":\"address\",\"name\":\"pool\",\"type\":\"address\"}],\"name\":\"PoolCreated\",\"type\":\"event\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"tokenA\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"tokenB\",\"type\":\"address\"},{\"internalType\":\"uint24\",\"name\":\"fee\",\"type\":\"uint24\"}],\"name\":\"createPool\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"pool\",\"type\":\"address\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint24\",\"name\":\"fee\",\"type\":\"uint24\"},{\"internalType\":\"int24\",\"name\":\"tickSpacing\",\"type\":\"int24\"}],\"name\":\"enableFeeAmount\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint24\",\"name\":\"\",\"type\":\"uint24\"}],\"name\":\"feeAmountTickSpacing\",\"outputs\":[{\"internalType\":\"int24\",\"name\":\"\",\"type\":\"int24\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"},{\"internalType\":\"uint24\",\"name\":\"\",\"type\":\"uint24\"}],\"name\":\"getPool\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"owner\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"parameters\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"factory\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"token0\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"token1\",\"type\":\"address\"},{\"internalType\":\"uint24\",\"name\":\"fee\",\"type\":\"uint24\"},{\"internalType\":\"int24\",\"name\":\"tickSpacing\",\"type\":\"int24\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_owner\",\"type\":\"address\"}],\"name\":\"setOwner\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"}]' var contractV3FactoryAddress = "0x1F98431c8aD98523631AE4a59f267346ea31F984" var contractV3SwapRouterV2Address = "0x68b3465833fb72A70ecDF485E0e4C7bD8665Fc45" function toAmount(s, decimals) { return Number((BigDecimal(BigInt(s)) / BigDecimal(Math.pow(10, decimals))).toString()) } function toInnerAmount(n, decimals) { return (BigDecimal(n) * BigDecimal(Math.pow(10, decimals))).toFixed(0) } function main() { // 注册Uniswap工厂合约的ABI exchange.IO("abi", contractV3FactoryAddress, abiFactory) // 注册Uniswap路由合约的ABI exchange.IO("abi", contractV3SwapRouterV2Address, abiRoute) // 获取交易对的池地址 var tokenIn = {name : "1INCH", address: "0x111111111117dC0aa78b770fA6A738034120C302", decimals: exchange.IO("api", "0x111111111117dC0aa78b770fA6A738034120C302", "decimals")} var tokenOut = {name : "WETH", address: "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2", decimals: exchange.IO("api", "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2", "decimals")} var poolAddress = exchange.IO("api", contractV3FactoryAddress, "getPool", tokenIn.address, tokenOut.address, 10000) // 注册池合约ABI exchange.IO("abi", poolAddress, abiPool) // 获取当前配置的钱包地址 var walletAddress = exchange.IO("address") // 1INCH -> WETH, tokenIn: 1INCH var allowanceAmount = exchange.IO("api", tokenIn.address, "allowance", walletAddress, contractV3SwapRouterV2Address) Log("allowanceAmount:", allowanceAmount, ", 使用toAmount()函数转换为可读数值:", toAmount(allowanceAmount, tokenIn.decimals)) var balance = exchange.IO("api", "0x111111111117dC0aa78b770fA6A738034120C302", "balanceOf", walletAddress) var balanceOf1INCH = toAmount(balance, tokenIn.decimals) Log("balanceOf1INCH:", balanceOf1INCH) var swapAmount = 38 if (balanceOf1INCH < swapAmount) { Log("钱包中", tokenIn.name, "不足,数量只有:", balanceOf1INCH) } if (toAmount(allowanceAmount, tokenIn.decimals) < swapAmount) { Log("授权数量不足,进行授权") // 也可以指定授权数量为无限,即把代码中toInnerAmount(swapAmount, tokenIn.decimals) 替换为 '0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff' var txApprove = exchange.IO("api", tokenIn.address, "approve", contractV3SwapRouterV2Address, toInnerAmount(swapAmount, tokenIn.decimals)) if (!txApprove) { Log("授权失败") } else { for (var i = 0; i < 10; i++) { Sleep(5000) var info = exchange.IO("api", "eth", "eth_getTransactionReceipt", txApprove) if (info && info.gasUsed) { Log("info:", info) break } Log('Transaction not yet mined', txApprove) } // 再次查询授权数量 allowanceAmount = exchange.IO("api", tokenIn.address, "allowance", walletAddress, contractV3SwapRouterV2Address) Log("再次查询授权数量,", "allowanceAmount:", allowanceAmount, ", 使用toAmount()函数转换为可读数值:", toAmount(allowanceAmount, tokenIn.decimals)) } } // 兑换 var recipientAddress = "0x0000000000000000000000000000000000000002" var fee = exchange.IO("encodePacked", "uint24", 10000) var path = tokenIn.address.slice(2).toLowerCase() + fee + tokenOut.address.slice(2).toLowerCase() var minOut = 1 var amountIn = toInnerAmount(swapAmount, tokenIn.decimals) var swapToken = exchange.IO("encode", contractV3SwapRouterV2Address, "exactInput", { path: path, recipient: recipientAddress, amountIn: amountIn, amountOutMinimum: minOut }) var data = [swapToken] data.push(exchange.IO("encode", contractV3SwapRouterV2Address, "unwrapWETH9(uint256,address)", 1, walletAddress)) var tx = exchange.IO("api", contractV3SwapRouterV2Address, "multicall(uint256,bytes[])", 0, (new Date().getTime() / 1000) + 3600, data) Log("tx:", tx) }

获取池中的兑换价格

之前我们讲解过,当我们使用代币地址(有两个,tokenIn、tokenOut)和费率设置,就可以从UniswapFactory合约的getPool方法获取这个兑换组合的兑换池(智能合约)地址。
获得兑换池地址后,注册池合约的ABI。然后就可以调用池合约的slot0方法,获取当前兑换价格相关的数据,进一步解析出兑换价格。

javascript
var abiRoute = '[{"inputs":[{"internalType":"address","name":"_factoryV2","type":"address"},{"internalType":"address","name":"factoryV3","type":"address"},{"internalType":"address","name":"_positionManager","type":"address"},{"internalType":"address","name":"_WETH9","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"WETH9","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"}],"name":"approveMax","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"}],"name":"approveMaxMinusOne","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"}],"name":"approveZeroThenMax","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"}],"name":"approveZeroThenMaxMinusOne","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"bytes","name":"data","type":"bytes"}],"name":"callPositionManager","outputs":[{"internalType":"bytes","name":"result","type":"bytes"}],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"bytes[]","name":"paths","type":"bytes[]"},{"internalType":"uint128[]","name":"amounts","type":"uint128[]"},{"internalType":"uint24","name":"maximumTickDivergence","type":"uint24"},{"internalType":"uint32","name":"secondsAgo","type":"uint32"}],"name":"checkOracleSlippage","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes","name":"path","type":"bytes"},{"internalType":"uint24","name":"maximumTickDivergence","type":"uint24"},{"internalType":"uint32","name":"secondsAgo","type":"uint32"}],"name":"checkOracleSlippage","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"components":[{"internalType":"bytes","name":"path","type":"bytes"},{"internalType":"address","name":"recipient","type":"address"},{"internalType":"uint256","name":"amountIn","type":"uint256"},{"internalType":"uint256","name":"amountOutMinimum","type":"uint256"}],"internalType":"struct IV3SwapRouter.ExactInputParams","name":"params","type":"tuple"}],"name":"exactInput","outputs":[{"internalType":"uint256","name":"amountOut","type":"uint256"}],"stateMutability":"payable","type":"function"},{"inputs":[{"components":[{"internalType":"address","name":"tokenIn","type":"address"},{"internalType":"address","name":"tokenOut","type":"address"},{"internalType":"uint24","name":"fee","type":"uint24"},{"internalType":"address","name":"recipient","type":"address"},{"internalType":"uint256","name":"amountIn","type":"uint256"},{"internalType":"uint256","name":"amountOutMinimum","type":"uint256"},{"internalType":"uint160","name":"sqrtPriceLimitX96","type":"uint160"}],"internalType":"struct IV3SwapRouter.ExactInputSingleParams","name":"params","type":"tuple"}],"name":"exactInputSingle","outputs":[{"internalType":"uint256","name":"amountOut","type":"uint256"}],"stateMutability":"payable","type":"function"},{"inputs":[{"components":[{"internalType":"bytes","name":"path","type":"bytes"},{"internalType":"address","name":"recipient","type":"address"},{"internalType":"uint256","name":"amountOut","type":"uint256"},{"internalType":"uint256","name":"amountInMaximum","type":"uint256"}],"internalType":"struct IV3SwapRouter.ExactOutputParams","name":"params","type":"tuple"}],"name":"exactOutput","outputs":[{"internalType":"uint256","name":"amountIn","type":"uint256"}],"stateMutability":"payable","type":"function"},{"inputs":[{"components":[{"internalType":"address","name":"tokenIn","type":"address"},{"internalType":"address","name":"tokenOut","type":"address"},{"internalType":"uint24","name":"fee","type":"uint24"},{"internalType":"address","name":"recipient","type":"address"},{"internalType":"uint256","name":"amountOut","type":"uint256"},{"internalType":"uint256","name":"amountInMaximum","type":"uint256"},{"internalType":"uint160","name":"sqrtPriceLimitX96","type":"uint160"}],"internalType":"struct IV3SwapRouter.ExactOutputSingleParams","name":"params","type":"tuple"}],"name":"exactOutputSingle","outputs":[{"internalType":"uint256","name":"amountIn","type":"uint256"}],"stateMutability":"payable","type":"function"},{"inputs":[],"name":"factory","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"factoryV2","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"getApprovalType","outputs":[{"internalType":"enum IApproveAndCall.ApprovalType","name":"","type":"uint8"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"components":[{"internalType":"address","name":"token0","type":"address"},{"internalType":"address","name":"token1","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"uint256","name":"amount0Min","type":"uint256"},{"internalType":"uint256","name":"amount1Min","type":"uint256"}],"internalType":"struct IApproveAndCall.IncreaseLiquidityParams","name":"params","type":"tuple"}],"name":"increaseLiquidity","outputs":[{"internalType":"bytes","name":"result","type":"bytes"}],"stateMutability":"payable","type":"function"},{"inputs":[{"components":[{"internalType":"address","name":"token0","type":"address"},{"internalType":"address","name":"token1","type":"address"},{"internalType":"uint24","name":"fee","type":"uint24"},{"internalType":"int24","name":"tickLower","type":"int24"},{"internalType":"int24","name":"tickUpper","type":"int24"},{"internalType":"uint256","name":"amount0Min","type":"uint256"},{"internalType":"uint256","name":"amount1Min","type":"uint256"},{"internalType":"address","name":"recipient","type":"address"}],"internalType":"struct IApproveAndCall.MintParams","name":"params","type":"tuple"}],"name":"mint","outputs":[{"internalType":"bytes","name":"result","type":"bytes"}],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"previousBlockhash","type":"bytes32"},{"internalType":"bytes[]","name":"data","type":"bytes[]"}],"name":"multicall","outputs":[{"internalType":"bytes[]","name":"","type":"bytes[]"}],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint256","name":"deadline","type":"uint256"},{"internalType":"bytes[]","name":"data","type":"bytes[]"}],"name":"multicall","outputs":[{"internalType":"bytes[]","name":"","type":"bytes[]"}],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"bytes[]","name":"data","type":"bytes[]"}],"name":"multicall","outputs":[{"internalType":"bytes[]","name":"results","type":"bytes[]"}],"stateMutability":"payable","type":"function"},{"inputs":[],"name":"positionManager","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"value","type":"uint256"}],"name":"pull","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[],"name":"refundETH","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"value","type":"uint256"},{"internalType":"uint256","name":"deadline","type":"uint256"},{"internalType":"uint8","name":"v","type":"uint8"},{"internalType":"bytes32","name":"r","type":"bytes32"},{"internalType":"bytes32","name":"s","type":"bytes32"}],"name":"selfPermit","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"nonce","type":"uint256"},{"internalType":"uint256","name":"expiry","type":"uint256"},{"internalType":"uint8","name":"v","type":"uint8"},{"internalType":"bytes32","name":"r","type":"bytes32"},{"internalType":"bytes32","name":"s","type":"bytes32"}],"name":"selfPermitAllowed","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"nonce","type":"uint256"},{"internalType":"uint256","name":"expiry","type":"uint256"},{"internalType":"uint8","name":"v","type":"uint8"},{"internalType":"bytes32","name":"r","type":"bytes32"},{"internalType":"bytes32","name":"s","type":"bytes32"}],"name":"selfPermitAllowedIfNecessary","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"value","type":"uint256"},{"internalType":"uint256","name":"deadline","type":"uint256"},{"internalType":"uint8","name":"v","type":"uint8"},{"internalType":"bytes32","name":"r","type":"bytes32"},{"internalType":"bytes32","name":"s","type":"bytes32"}],"name":"selfPermitIfNecessary","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint256","name":"amountIn","type":"uint256"},{"internalType":"uint256","name":"amountOutMin","type":"uint256"},{"internalType":"address[]","name":"path","type":"address[]"},{"internalType":"address","name":"to","type":"address"}],"name":"swapExactTokensForTokens","outputs":[{"internalType":"uint256","name":"amountOut","type":"uint256"}],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint256","name":"amountOut","type":"uint256"},{"internalType":"uint256","name":"amountInMax","type":"uint256"},{"internalType":"address[]","name":"path","type":"address[]"},{"internalType":"address","name":"to","type":"address"}],"name":"swapTokensForExactTokens","outputs":[{"internalType":"uint256","name":"amountIn","type":"uint256"}],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"amountMinimum","type":"uint256"},{"internalType":"address","name":"recipient","type":"address"}],"name":"sweepToken","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"amountMinimum","type":"uint256"}],"name":"sweepToken","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"amountMinimum","type":"uint256"},{"internalType":"uint256","name":"feeBips","type":"uint256"},{"internalType":"address","name":"feeRecipient","type":"address"}],"name":"sweepTokenWithFee","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"amountMinimum","type":"uint256"},{"internalType":"address","name":"recipient","type":"address"},{"internalType":"uint256","name":"feeBips","type":"uint256"},{"internalType":"address","name":"feeRecipient","type":"address"}],"name":"sweepTokenWithFee","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"int256","name":"amount0Delta","type":"int256"},{"internalType":"int256","name":"amount1Delta","type":"int256"},{"internalType":"bytes","name":"_data","type":"bytes"}],"name":"uniswapV3SwapCallback","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"amountMinimum","type":"uint256"},{"internalType":"address","name":"recipient","type":"address"}],"name":"unwrapWETH9","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint256","name":"amountMinimum","type":"uint256"}],"name":"unwrapWETH9","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint256","name":"amountMinimum","type":"uint256"},{"internalType":"address","name":"recipient","type":"address"},{"internalType":"uint256","name":"feeBips","type":"uint256"},{"internalType":"address","name":"feeRecipient","type":"address"}],"name":"unwrapWETH9WithFee","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint256","name":"amountMinimum","type":"uint256"},{"internalType":"uint256","name":"feeBips","type":"uint256"},{"internalType":"address","name":"feeRecipient","type":"address"}],"name":"unwrapWETH9WithFee","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint256","name":"value","type":"uint256"}],"name":"wrapETH","outputs":[],"stateMutability":"payable","type":"function"},{"stateMutability":"payable","type":"receive"}]'; var abiPool = '[{\"inputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"owner\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"int24\",\"name\":\"tickLower\",\"type\":\"int24\"},{\"indexed\":true,\"internalType\":\"int24\",\"name\":\"tickUpper\",\"type\":\"int24\"},{\"indexed\":false,\"internalType\":\"uint128\",\"name\":\"amount\",\"type\":\"uint128\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"amount0\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"amount1\",\"type\":\"uint256\"}],\"name\":\"Burn\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"owner\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"address\",\"name\":\"recipient\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"int24\",\"name\":\"tickLower\",\"type\":\"int24\"},{\"indexed\":true,\"internalType\":\"int24\",\"name\":\"tickUpper\",\"type\":\"int24\"},{\"indexed\":false,\"internalType\":\"uint128\",\"name\":\"amount0\",\"type\":\"uint128\"},{\"indexed\":false,\"internalType\":\"uint128\",\"name\":\"amount1\",\"type\":\"uint128\"}],\"name\":\"Collect\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"sender\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"recipient\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint128\",\"name\":\"amount0\",\"type\":\"uint128\"},{\"indexed\":false,\"internalType\":\"uint128\",\"name\":\"amount1\",\"type\":\"uint128\"}],\"name\":\"CollectProtocol\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"sender\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"recipient\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"amount0\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"amount1\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"paid0\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"paid1\",\"type\":\"uint256\"}],\"name\":\"Flash\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"uint16\",\"name\":\"observationCardinalityNextOld\",\"type\":\"uint16\"},{\"indexed\":false,\"internalType\":\"uint16\",\"name\":\"observationCardinalityNextNew\",\"type\":\"uint16\"}],\"name\":\"IncreaseObservationCardinalityNext\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"uint160\",\"name\":\"sqrtPriceX96\",\"type\":\"uint160\"},{\"indexed\":false,\"internalType\":\"int24\",\"name\":\"tick\",\"type\":\"int24\"}],\"name\":\"Initialize\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"address\",\"name\":\"sender\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"owner\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"int24\",\"name\":\"tickLower\",\"type\":\"int24\"},{\"indexed\":true,\"internalType\":\"int24\",\"name\":\"tickUpper\",\"type\":\"int24\"},{\"indexed\":false,\"internalType\":\"uint128\",\"name\":\"amount\",\"type\":\"uint128\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"amount0\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"amount1\",\"type\":\"uint256\"}],\"name\":\"Mint\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"uint8\",\"name\":\"feeProtocol0Old\",\"type\":\"uint8\"},{\"indexed\":false,\"internalType\":\"uint8\",\"name\":\"feeProtocol1Old\",\"type\":\"uint8\"},{\"indexed\":false,\"internalType\":\"uint8\",\"name\":\"feeProtocol0New\",\"type\":\"uint8\"},{\"indexed\":false,\"internalType\":\"uint8\",\"name\":\"feeProtocol1New\",\"type\":\"uint8\"}],\"name\":\"SetFeeProtocol\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"sender\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"recipient\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"int256\",\"name\":\"amount0\",\"type\":\"int256\"},{\"indexed\":false,\"internalType\":\"int256\",\"name\":\"amount1\",\"type\":\"int256\"},{\"indexed\":false,\"internalType\":\"uint160\",\"name\":\"sqrtPriceX96\",\"type\":\"uint160\"},{\"indexed\":false,\"internalType\":\"uint128\",\"name\":\"liquidity\",\"type\":\"uint128\"},{\"indexed\":false,\"internalType\":\"int24\",\"name\":\"tick\",\"type\":\"int24\"}],\"name\":\"Swap\",\"type\":\"event\"},{\"inputs\":[{\"internalType\":\"int24\",\"name\":\"tickLower\",\"type\":\"int24\"},{\"internalType\":\"int24\",\"name\":\"tickUpper\",\"type\":\"int24\"},{\"internalType\":\"uint128\",\"name\":\"amount\",\"type\":\"uint128\"}],\"name\":\"burn\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"amount0\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"amount1\",\"type\":\"uint256\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"recipient\",\"type\":\"address\"},{\"internalType\":\"int24\",\"name\":\"tickLower\",\"type\":\"int24\"},{\"internalType\":\"int24\",\"name\":\"tickUpper\",\"type\":\"int24\"},{\"internalType\":\"uint128\",\"name\":\"amount0Requested\",\"type\":\"uint128\"},{\"internalType\":\"uint128\",\"name\":\"amount1Requested\",\"type\":\"uint128\"}],\"name\":\"collect\",\"outputs\":[{\"internalType\":\"uint128\",\"name\":\"amount0\",\"type\":\"uint128\"},{\"internalType\":\"uint128\",\"name\":\"amount1\",\"type\":\"uint128\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"recipient\",\"type\":\"address\"},{\"internalType\":\"uint128\",\"name\":\"amount0Requested\",\"type\":\"uint128\"},{\"internalType\":\"uint128\",\"name\":\"amount1Requested\",\"type\":\"uint128\"}],\"name\":\"collectProtocol\",\"outputs\":[{\"internalType\":\"uint128\",\"name\":\"amount0\",\"type\":\"uint128\"},{\"internalType\":\"uint128\",\"name\":\"amount1\",\"type\":\"uint128\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"factory\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"fee\",\"outputs\":[{\"internalType\":\"uint24\",\"name\":\"\",\"type\":\"uint24\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"feeGrowthGlobal0X128\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"feeGrowthGlobal1X128\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"recipient\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amount0\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"amount1\",\"type\":\"uint256\"},{\"internalType\":\"bytes\",\"name\":\"data\",\"type\":\"bytes\"}],\"name\":\"flash\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint16\",\"name\":\"observationCardinalityNext\",\"type\":\"uint16\"}],\"name\":\"increaseObservationCardinalityNext\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint160\",\"name\":\"sqrtPriceX96\",\"type\":\"uint160\"}],\"name\":\"initialize\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"liquidity\",\"outputs\":[{\"internalType\":\"uint128\",\"name\":\"\",\"type\":\"uint128\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"maxLiquidityPerTick\",\"outputs\":[{\"internalType\":\"uint128\",\"name\":\"\",\"type\":\"uint128\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"recipient\",\"type\":\"address\"},{\"internalType\":\"int24\",\"name\":\"tickLower\",\"type\":\"int24\"},{\"internalType\":\"int24\",\"name\":\"tickUpper\",\"type\":\"int24\"},{\"internalType\":\"uint128\",\"name\":\"amount\",\"type\":\"uint128\"},{\"internalType\":\"bytes\",\"name\":\"data\",\"type\":\"bytes\"}],\"name\":\"mint\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"amount0\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"amount1\",\"type\":\"uint256\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"name\":\"observations\",\"outputs\":[{\"internalType\":\"uint32\",\"name\":\"blockTimestamp\",\"type\":\"uint32\"},{\"internalType\":\"int56\",\"name\":\"tickCumulative\",\"type\":\"int56\"},{\"internalType\":\"uint160\",\"name\":\"secondsPerLiquidityCumulativeX128\",\"type\":\"uint160\"},{\"internalType\":\"bool\",\"name\":\"initialized\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint32[]\",\"name\":\"secondsAgos\",\"type\":\"uint32[]\"}],\"name\":\"observe\",\"outputs\":[{\"internalType\":\"int56[]\",\"name\":\"tickCumulatives\",\"type\":\"int56[]\"},{\"internalType\":\"uint160[]\",\"name\":\"secondsPerLiquidityCumulativeX128s\",\"type\":\"uint160[]\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"name\":\"positions\",\"outputs\":[{\"internalType\":\"uint128\",\"name\":\"liquidity\",\"type\":\"uint128\"},{\"internalType\":\"uint256\",\"name\":\"feeGrowthInside0LastX128\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"feeGrowthInside1LastX128\",\"type\":\"uint256\"},{\"internalType\":\"uint128\",\"name\":\"tokensOwed0\",\"type\":\"uint128\"},{\"internalType\":\"uint128\",\"name\":\"tokensOwed1\",\"type\":\"uint128\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"protocolFees\",\"outputs\":[{\"internalType\":\"uint128\",\"name\":\"token0\",\"type\":\"uint128\"},{\"internalType\":\"uint128\",\"name\":\"token1\",\"type\":\"uint128\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint8\",\"name\":\"feeProtocol0\",\"type\":\"uint8\"},{\"internalType\":\"uint8\",\"name\":\"feeProtocol1\",\"type\":\"uint8\"}],\"name\":\"setFeeProtocol\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"slot0\",\"outputs\":[{\"internalType\":\"uint160\",\"name\":\"sqrtPriceX96\",\"type\":\"uint160\"},{\"internalType\":\"int24\",\"name\":\"tick\",\"type\":\"int24\"},{\"internalType\":\"uint16\",\"name\":\"observationIndex\",\"type\":\"uint16\"},{\"internalType\":\"uint16\",\"name\":\"observationCardinality\",\"type\":\"uint16\"},{\"internalType\":\"uint16\",\"name\":\"observationCardinalityNext\",\"type\":\"uint16\"},{\"internalType\":\"uint8\",\"name\":\"feeProtocol\",\"type\":\"uint8\"},{\"internalType\":\"bool\",\"name\":\"unlocked\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"int24\",\"name\":\"tickLower\",\"type\":\"int24\"},{\"internalType\":\"int24\",\"name\":\"tickUpper\",\"type\":\"int24\"}],\"name\":\"snapshotCumulativesInside\",\"outputs\":[{\"internalType\":\"int56\",\"name\":\"tickCumulativeInside\",\"type\":\"int56\"},{\"internalType\":\"uint160\",\"name\":\"secondsPerLiquidityInsideX128\",\"type\":\"uint160\"},{\"internalType\":\"uint32\",\"name\":\"secondsInside\",\"type\":\"uint32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"recipient\",\"type\":\"address\"},{\"internalType\":\"bool\",\"name\":\"zeroForOne\",\"type\":\"bool\"},{\"internalType\":\"int256\",\"name\":\"amountSpecified\",\"type\":\"int256\"},{\"internalType\":\"uint160\",\"name\":\"sqrtPriceLimitX96\",\"type\":\"uint160\"},{\"internalType\":\"bytes\",\"name\":\"data\",\"type\":\"bytes\"}],\"name\":\"swap\",\"outputs\":[{\"internalType\":\"int256\",\"name\":\"amount0\",\"type\":\"int256\"},{\"internalType\":\"int256\",\"name\":\"amount1\",\"type\":\"int256\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"int16\",\"name\":\"\",\"type\":\"int16\"}],\"name\":\"tickBitmap\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"tickSpacing\",\"outputs\":[{\"internalType\":\"int24\",\"name\":\"\",\"type\":\"int24\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"int24\",\"name\":\"\",\"type\":\"int24\"}],\"name\":\"ticks\",\"outputs\":[{\"internalType\":\"uint128\",\"name\":\"liquidityGross\",\"type\":\"uint128\"},{\"internalType\":\"int128\",\"name\":\"liquidityNet\",\"type\":\"int128\"},{\"internalType\":\"uint256\",\"name\":\"feeGrowthOutside0X128\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"feeGrowthOutside1X128\",\"type\":\"uint256\"},{\"internalType\":\"int56\",\"name\":\"tickCumulativeOutside\",\"type\":\"int56\"},{\"internalType\":\"uint160\",\"name\":\"secondsPerLiquidityOutsideX128\",\"type\":\"uint160\"},{\"internalType\":\"uint32\",\"name\":\"secondsOutside\",\"type\":\"uint32\"},{\"internalType\":\"bool\",\"name\":\"initialized\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"token0\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"token1\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"}]' var abiFactory = '[{\"inputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"uint24\",\"name\":\"fee\",\"type\":\"uint24\"},{\"indexed\":true,\"internalType\":\"int24\",\"name\":\"tickSpacing\",\"type\":\"int24\"}],\"name\":\"FeeAmountEnabled\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"oldOwner\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"newOwner\",\"type\":\"address\"}],\"name\":\"OwnerChanged\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"token0\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"token1\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"uint24\",\"name\":\"fee\",\"type\":\"uint24\"},{\"indexed\":false,\"internalType\":\"int24\",\"name\":\"tickSpacing\",\"type\":\"int24\"},{\"indexed\":false,\"internalType\":\"address\",\"name\":\"pool\",\"type\":\"address\"}],\"name\":\"PoolCreated\",\"type\":\"event\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"tokenA\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"tokenB\",\"type\":\"address\"},{\"internalType\":\"uint24\",\"name\":\"fee\",\"type\":\"uint24\"}],\"name\":\"createPool\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"pool\",\"type\":\"address\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint24\",\"name\":\"fee\",\"type\":\"uint24\"},{\"internalType\":\"int24\",\"name\":\"tickSpacing\",\"type\":\"int24\"}],\"name\":\"enableFeeAmount\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint24\",\"name\":\"\",\"type\":\"uint24\"}],\"name\":\"feeAmountTickSpacing\",\"outputs\":[{\"internalType\":\"int24\",\"name\":\"\",\"type\":\"int24\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"},{\"internalType\":\"uint24\",\"name\":\"\",\"type\":\"uint24\"}],\"name\":\"getPool\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"owner\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"parameters\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"factory\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"token0\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"token1\",\"type\":\"address\"},{\"internalType\":\"uint24\",\"name\":\"fee\",\"type\":\"uint24\"},{\"internalType\":\"int24\",\"name\":\"tickSpacing\",\"type\":\"int24\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_owner\",\"type\":\"address\"}],\"name\":\"setOwner\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"}]' var contractV3FactoryAddress = "0x1F98431c8aD98523631AE4a59f267346ea31F984" var contractV3SwapRouterV2Address = "0x68b3465833fb72A70ecDF485E0e4C7bD8665Fc45" function toAmount(s, decimals) { return Number((BigDecimal(BigInt(s)) / BigDecimal(Math.pow(10, decimals))).toString()) } function toInnerAmount(n, decimals) { return (BigDecimal(n) * BigDecimal(Math.pow(10, decimals))).toFixed(0) } function main() { // 注册Uniswap工厂合约的ABI exchange.IO("abi", contractV3FactoryAddress, abiFactory) // 注册Uniswap路由合约的ABI exchange.IO("abi", contractV3SwapRouterV2Address, abiRoute) // 获取交易对的池地址 var tokenIn = {name : "1INCH", address: "0x111111111117dC0aa78b770fA6A738034120C302", decimals: exchange.IO("api", "0x111111111117dC0aa78b770fA6A738034120C302", "decimals")} var tokenOut = {name : "WETH", address: "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2", decimals: exchange.IO("api", "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2", "decimals")} var poolAddress = exchange.IO("api", contractV3FactoryAddress, "getPool", tokenIn.address, tokenOut.address, 10000) // 注册池合约ABI exchange.IO("abi", poolAddress, abiPool) var slot0 = exchange.IO("api", poolAddress, "slot0") Log("slot0:", slot0) }

获取到兑换池的价格信息,打印出代码中slot0变量:

javascript
{ "feeProtocol":0, "unlocked":true, "sqrtPriceX96":"1128983883551457130720648561", "tick":"-85025", "observationIndex":5, "observationCardinality":6, "observationCardinalityNext":6 }

其中主要的价格信息数据就记录在sqrtPriceX96字段,需要根据兑换组合的token精度数据一起计算出兑换池当前价格,根据Uniswap文档中的描述我们实现一个函数来计算:

javascript
function computePoolPrice(decimals0, decimals1, sqrtPriceX96) { // sqrtPriceX96 = sqrt(price) * 2^96 [decimals0, decimals1, sqrtPriceX96] = [decimals0, decimals1, sqrtPriceX96].map(BigInt); const TWO = BigInt(2); const TEN = BigInt(10); const SIX_TENTH = BigInt(1000000); const Q192 = (TWO ** BigInt(96)) ** TWO; return ( Number((sqrtPriceX96 ** TWO * TEN ** decimals0 * SIX_TENTH) / (Q192 * TEN ** decimals1)) / Number(SIX_TENTH) ); }

使用这个函数计算token组合为1INCH/WETH,费率为10000的兑换池当前的价格。

javascript
function computePoolPrice(decimals0, decimals1, sqrtPriceX96) { // sqrtPriceX96 = sqrt(price) * 2^96 [decimals0, decimals1, sqrtPriceX96] = [decimals0, decimals1, sqrtPriceX96].map(BigInt); const TWO = BigInt(2); const TEN = BigInt(10); const SIX_TENTH = BigInt(1000000); const Q192 = (TWO ** BigInt(96)) ** TWO; return ( Number((sqrtPriceX96 ** TWO * TEN ** decimals0 * SIX_TENTH) / (Q192 * TEN ** decimals1)) / Number(SIX_TENTH) ); } function main() { var tokenIn = {name : "1INCH", address: "0x111111111117dC0aa78b770fA6A738034120C302", decimals: exchange.IO("api", "0x111111111117dC0aa78b770fA6A738034120C302", "decimals")} var tokenOut = {name : "WETH", address: "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2", decimals: exchange.IO("api", "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2", "decimals")} // 获取的slot0变量中"sqrtPriceX96":"1128983883551457130720648561", var price = computePoolPrice(tokenIn.decimals, tokenOut.decimals, "1128983883551457130720648561") Log("price:", price) }

打印变量price显示:price: 0.000203(1个1INCH兑换0.000203个WETH)。

Uniswap V3 交易类库

发明者量化交易平台公开了一个封装好的Uniswap模板,已经实现了兑换、获取价格、查询钱包余额等功能,不用再根据以上讲解中的内容重复编写代码,可以阅读这个模板的源码更深入地学习、开发Web3方向的应用。
该模板类库中有很多细节设计值得学习:

  • 自动获取token信息
    摘取其中一段代码讲解,当该模板的参数AutoFetchTokens设置为true时,模板程序会自动访问https://tokens.coingecko.com/uniswap/all.json链接,获取并自动处理所有token的信息。这样就可以不用在策略代码中手动的添加某个token(否则就需要使用addToken(name, address)添加token)。

    javascript
    if (AutoFetchTokens) { let res = JSON.parse(HttpQuery("https://tokens.coingecko.com/uniswap/all.json")) Log("fetch", res.tokens.length, "tokens from", res.name) res.tokens.forEach(function(token) { if (token.chainId == chainId && token.symbol != "WETH") { self.tokenInfo[token.symbol] = { name: token.symbol, decimals: token.decimals, address: token.address } } }) }
  • 根据链设置适配不同的合约地址
    该模板设置了ChainType参数,支持切换多种链:

    desc
    'https://rpc.ankr.com/eth', 'https://arb1.arbitrum.io/rpc', 'https://mainnet.optimism.io/', 'https://rpc.ankr.com/avalanche', 'https://polygon-rpc.com', 'https://rpc.ankr.com/celo',

    节选代码:

    javascript
    if (typeof(ChainType) === 'number') { let chainRpc = [ '', 'https://rpc.ankr.com/eth', 'https://arb1.arbitrum.io/rpc', 'https://mainnet.optimism.io/', 'https://rpc.ankr.com/avalanche', 'https://polygon-rpc.com', 'https://rpc.ankr.com/celo', //'https://mainnet.aurora.dev', //'https://bsc-dataseed.binance.org', //'https://exchainrpc.okex.org' ][ChainType] if (chainRpc && chainRpc.length > 0) { e.IO("base", chainRpc) Log("change base rpc to", chainRpc) } }

    调用以太坊的RPC方法eth_chainId查询当前chainId,再根据chainId去适配WETH地址、Uniswap一系列合约地址、USDT的合约地址等(某种智能合约在不同的链上可能合约地址不同)。

    节选代码:

    javascript
    // https://docs.uniswap.org/contracts/v3/reference/deployments let WETHAddress = { 1: "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2", // Ethereum 3: "0xc778417E063141139Fce010982780140Aa0cD5Ab", // Ropsten 4: "0xc778417E063141139Fce010982780140Aa0cD5Ab", // Rinkeby 5: "0xB4FBF271143F4FBf7B91A5ded31805e42b2208d6", // Goerli 42: "0xd0A1E359811322d97991E03f863a0C30C2cF029C", // Kovan 10: "0x4200000000000000000000000000000000000006", // Optimism 69: "0x4200000000000000000000000000000000000006", // Optimistic Kovan 42161: "0x82aF49447D8a07e3bd95BD0d56f35241523fBab1", // Arbitrum One 421611: "0xB47e6A5f8b33b3F17603C83a0535A9dcD7E32681", // Arbitrum Rinkeby 137: "0x0d500B1d8E8eF31E21C99d1Db9A6444d3ADf1270", // Polygon 80001: "0x9c3C9283D3e44854697Cd22D3Faa240Cfb032889", // Polygon Mumbai } let chainId = e.IO("api", "ETH", "eth_chainId") if (chainId) { chainId = Number(chainId) Log("chainId: ", chainId) let addr = WETHAddress[chainId] if (addr) { Log("Register WETH address", addr) self.addToken("ETH", addr) } if (chainId == 42220) { // Celo Address ContractV3Factory = '0xAfE208a311B21f13EF87E33A90049fC17A7acDEc' ContractV3SwapRouterV2 = '0x5615CDAb10dc425a742d643d949a7F474C01abc4' self.addToken('CELO', '0x471ece3750da237f93b8e339c536989b8978a438') } else if (chainId == 42161) { self.addToken('USDT', '0xfd086bc7cd5c481dcc9c85ebe478a1c0b69fcbb9') } } else { panic("get chain Id error") }
  • 使用「Uniswap V3 交易类库」

    在该模板中$.testUniswap()函数是一个测试模板功能的函数,其代码给出了如何使用模板的调用例子:

    javascript
    $.testUniswap = function() { let ex = $.NewUniswapV3() Log("walletAddress: ", ex.walletAddress) let tokenAddressMap = { "USDT": "0xdac17f958d2ee523a2206206994597c13d831ec7", "1INCH": "0x111111111117dC0aa78b770fA6A738034120C302", "USDC": "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", "DAI": "0x6b175474e89094c44da98b954eedeac495271d0f", } for (let name in tokenAddressMap) { ex.addToken(name, tokenAddressMap[name]) } Log(ex.getPrice('ETH_USDT')) Log(ex.getPrice('1INCH_USDT')) // swap 0.01 ETH to USDT Log(ex.swapToken('ETH', 0.01, 'USDT')) let usdtBalance = ex.balanceOf('USDT') Log("balance of USDT", usdtBalance) // swap USDT to DAI then DAI to ETH Log(ex.swapToken('USDT', usdtBalance, 'DAI,ETH')) Log("balance of ETH", ex.getETHBalance()) // Log(ex.sendETH('0x11111', 0.02)) // ... }

    当某个策略引用了「Uniswap V3 交易类库」时(如何引用模板类库可以参考FMZ平台文档),就可以调用这个模板类库中封装好的函数。

    创建一个名为ex的变量,调用「Uniswap V3 交易类库」模板封装好的接口函数$.NewUniswapV3()创建一个对象赋值给ex

    javascript
    let ex = $.NewUniswapV3()

    使用ex对象的成员函数addToken()添加(注册)token信息。

    javascript
    let tokenAddressMap = { "USDT": "0xdac17f958d2ee523a2206206994597c13d831ec7", "1INCH": "0x111111111117dC0aa78b770fA6A738034120C302", "USDC": "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", "DAI": "0x6b175474e89094c44da98b954eedeac495271d0f", } for (let name in tokenAddressMap) { ex.addToken(name, tokenAddressMap[name]) }

    如果要获取并打印某个交易对的兑换池价格,可以使用ex对象的成员函数getPrice(),可以这样写:

    javascript
    Log(ex.getPrice('ETH_USDT')) Log(ex.getPrice('1INCH_USDT'))

    如果要执行兑换操作,可以使用ex对象的成员函数swapToken(),执行兑换:

    javascript
    // swap 0.01 ETH to USDT Log(ex.swapToken('ETH', 0.01, 'USDT')) let usdtBalance = ex.balanceOf('USDT') Log("balance of USDT", usdtBalance) // swap USDT to DAI then DAI to ETH Log(ex.swapToken('USDT', usdtBalance, 'DAI,ETH'))

检索事件

本章节我们将学习使用发明者量化交易平台读取智能合约释放的事件,智能合约释放出的事件存储于以太坊虚拟机的日志中。

eth_getLogs

查询智能合约释放的事件,需要使用以太坊的RPC方法eth_getLogs,获取链上日志数据,对于如何调用以太坊RPC节点我们之前的课程中已经讲解。
例如我们获取WETH合约的事件可以编写代码,使用FMZ的调试工具测试,交易所对象配置的RPC节点为以太坊主网节点,在调用eth_getLogs方法时我们指定了三个参数fromBlocktoBlockaddress,我们使用fromBlock和toBlock参数限定查询一个区块内的数据:

javascript
function toAmount(s, decimals) { return Number((BigDecimal(BigInt(s)) / BigDecimal(Math.pow(10, decimals))).toString()) } function toInnerAmount(n, decimals) { return (BigDecimal(n) * BigDecimal(Math.pow(10, decimals))).toFixed(0) } function main() { // getBlockNumber var blockNumber = exchange.IO("api", "eth", "eth_blockNumber") Log("blockNumber:", blockNumber) // get logs var fromBlock = "0x" + (toAmount(blockNumber, 0) - 1).toString(16) var toBlock = "0x" + toAmount(blockNumber, 0).toString(16) var params = { "fromBlock" : fromBlock, "toBlock" : toBlock, "address" : "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2" // WETH合约的地址 } var logs = exchange.IO("api", "eth", "eth_getLogs", params) // 由于数据量比较大,如果使用Log函数打印,数据会被截断。使用return将完整数据返回在页面「函数结果」编辑框中 return logs }

获取到logs数据,由于数据内容较多,我们省略其中一部分内容:

javascript
[{ "data": "0x00000000000000000000000000000000000000000000000001c1a55000000000", "topics": ["0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", "0x0000000000000000000000006b75d8af000000e20b7a7ddf000ba900b4009a80", "0x000000000000000000000000bcb095c1f9c3dc02e834976706c87dee5d0f1fb6"], "transactionHash": "0x27f9bf5abe3148169b4b85a83e1de32bd50eb81ecc52e5af006157d93353e4c4", "transactionIndex": "0x0", "removed": false, "address": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", "blockHash": "0x847be24a7b159c292bda030a011dfec89487b70e71eed486969b032d6ef04bad", "blockNumber": "0x109b1cc", "logIndex": "0x0" }, { "data": "0x00000000000000000000000000000000000000000000000008ea20cdea027c00", "logIndex": "0x5", "topics": ["0xe1fffcc4923d04b559f4d29a8bfc6cda04eb5b0d3c460751c2402c5c5cc9109c", "0x0000000000000000000000007a250d5630b4cf539739df2c5dacb4c659f2488d"], "address": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", "blockHash": "0x847be24a7b159c292bda030a011dfec89487b70e71eed486969b032d6ef04bad", "blockNumber": "0x109b1cc", "removed": false, "transactionHash": "0xace3afa02e8af5d1ef6fc1635fbdf7bee37624547937ea5272c23968dd034c09", "transactionIndex": "0x1" }, ... { "blockNumber": "0x109b1cd", "address": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", "data": "0x00000000000000000000000000000000000000000000000002c053531ab8a000", "logIndex": "0xd3", "removed": false, "topics": ["0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", "0x0000000000000000000000001111111254eeb25477b68fb85ed929f73a960582", "0x000000000000000000000000252ba9b5916171dbdadd2cec7f91875a006955d0"], "transactionHash": "0x3012b82891f85b077cfe1c12cb9722b93c696ef2c37d67981ccddcc9c3396aca", "transactionIndex": "0x8d", "blockHash": "0xcd3d567c9bd02a4549b1de0dc638ab5523e847c3c156b096424f56c633000fd9" }, { "topics": ["0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", "0x00000000000000000000000012b791bb27b3a4ee958b5a435fea7d49ec076e9c", "0x000000000000000000000000ef1c6e67703c7bd7107eed8303fbe6ec2554bf6b"], "transactionIndex": "0x91", "logIndex": "0xdb", "removed": false, "blockNumber": "0x109b1cd", "data": "0x0000000000000000000000000000000000000000000000000164f2434262e1cc", "transactionHash": "0x6aa8d80daf42f442591e7530e31323d05e1d6dd9f9f9b9c102e157d89810c048", "address": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", "blockHash": "0xcd3d567c9bd02a4549b1de0dc638ab5523e847c3c156b096424f56c633000fd9" }, { "address": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", "blockHash": "0xcd3d567c9bd02a4549b1de0dc638ab5523e847c3c156b096424f56c633000fd9", "blockNumber": "0x109b1cd", "logIndex": "0xde", "removed": false, "topics": ["0x7fcf532c15f0a6db0bd6d0e038bea71d30d808c7d98cb3bf7268a95bf5081b65", "0x000000000000000000000000ef1c6e67703c7bd7107eed8303fbe6ec2554bf6b"], "data": "0x0000000000000000000000000000000000000000000000000164f2434262e1cc", "transactionHash": "0x6aa8d80daf42f442591e7530e31323d05e1d6dd9f9f9b9c102e157d89810c048", "transactionIndex": "0x91" }]

可以看到logs数据中有各种各样的事件,如果我们只关心Transfer事件就需要把这些数据中的Transfer事件过滤出来。

检索日志

以太坊日志分为两个部分,1、主题topics;2、数据data

  • 主题topics
    eth_getLogs章节测试的代码运行结果为例,topics字段的数据为:

    desc
    "topics": ["0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", "0x00000000000000000000000012b791bb27b3a4ee958b5a435fea7d49ec076e9c", "0x000000000000000000000000ef1c6e67703c7bd7107eed8303fbe6ec2554bf6b"],

    这个topics(主题)字段的值是一个数组结构,用于描述事件。规定了其(数组)长度不能超过4,第一个元素为事件的签名哈希值。
    在发明者量化交易平台,我们使用Encode函数就可以计算出这个签名哈希值,使用以下代码计算:

    javascript
    function main() { var eventFunction = "Transfer(address,address,uint256)" var eventHash = Encode("keccak256", "string", "hex", eventFunction) Log("eventHash:", "0x" + eventHash) // eventHash: 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef }

    计算得出Transfer(address,address,uint256)keccak256哈希值(hex编码)为0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef

    topics字段的值是一个数组结构,第二个元素、第三个元素分别为:

    • 发出地址from
    • 接收地址to
  • 数据data

    data字段的数据为:

    desc
    "data": "0x0000000000000000000000000000000000000000000000000164f2434262e1cc",

    事件中某些参数(智能合约的Solidity代码中参数不带indexed声明的)会被存储在data部分中。

    解析这个数据0x0000000000000000000000000000000000000000000000000164f2434262e1cc

    javascript
    function toAmount(s, decimals) { return Number((BigDecimal(BigInt(s)) / BigDecimal(Math.pow(10, decimals))).toString()) } function main() { var value = "0x0000000000000000000000000000000000000000000000000164f2434262e1cc" Log(toAmount(value, 0) / 1e18) // 0.10047146239950075 }

    得出这个数据为:0.10047146239950075,data数据是对应转账数额。


讲解、练习了以上内容,准备就绪。我们可以开始检索日志了:

javascript
function toAmount(s, decimals) { return Number((BigDecimal(BigInt(s)) / BigDecimal(Math.pow(10, decimals))).toString()) } function toInnerAmount(n, decimals) { return (BigDecimal(n) * BigDecimal(Math.pow(10, decimals))).toFixed(0) } function main() { // getBlockNumber var blockNumber = exchange.IO("api", "eth", "eth_blockNumber") Log("blockNumber:", blockNumber) // get logs var fromBlock = "0x" + (toAmount(blockNumber, 0) - 1).toString(16) var toBlock = "0x" + toAmount(blockNumber, 0).toString(16) var params = { "fromBlock" : fromBlock, "toBlock" : toBlock, "address" : "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2" } var logs = exchange.IO("api", "eth", "eth_getLogs", params) // 遍历logs var eventFunction = "Transfer(address,address,uint256)" var eventHash = "0x" + Encode("keccak256", "string", "hex", eventFunction) Log("eventHash:", eventHash) var counter = 0 for (var i = logs.length - 1; i >= 0 && counter < 10; i--) { if (logs[i].topics[0] == eventHash) { Log("Event Transfer, data:", toAmount(logs[i].data, 0) / 1e18, ", blockNumber:", toAmount(logs[i].blockNumber, 0), ", transactionHash:", logs[i].transactionHash, ", log:", logs[i]) counter++ } } }

https://etherscan.io/上查询:

img

测试代码在FMZ调试工具运行的结果:

img

根据检索时的需求,也可以解析fromto字段的数据,例如:

javascript
function main() { var from = "0x00000000000000000000000012b791bb27b3a4ee958b5a435fea7d49ec076e9c" var address = "0x" + exchange.IO("encodePacked", "address", from) Log("address:", address) }

运行结果:

address: 0x12b791bb27b3a4ee958b5a435fea7d49ec076e9c

监听合约事件

由于调试工具只能短时间测试代码,并且代码执行完成后才输出内容,无法实时显示、输出日志。本节内容我们使用发明者量化交易平台创建实盘来测试。

这里我们使用以太坊主网,我们来监听USDT这个币种合约的Transfer(address,address,uint256)事件,根据我们上节课学习的内容,我们设计编写了一个持续监听某个智能合约事件的例子:

javascript
function toAmount(s, decimals) { return Number((BigDecimal(BigInt(s)) / BigDecimal(Math.pow(10, decimals))).toString()) } function toInnerAmount(n, decimals) { return (BigDecimal(n) * BigDecimal(Math.pow(10, decimals))).toFixed(0) } function addEventListener(contractAddress, event, callBack) { var self = {} self.eventHash = "0x" + Encode("keccak256", "string", "hex", event) self.contractAddress = contractAddress self.latestBlockNumber = 0 self.fromBlockNumber = 0 self.firstBlockNumber = 0 /* TODO: test self.isFirst = true */ self.getBlockNumber = function() { var maxTry = 10 for (var i = 0; i < maxTry; i++) { var ret = exchange.IO("api", "eth", "eth_blockNumber") if (ret) { return toAmount(ret, 0) } Sleep(5000) } throw "getBlockNumber failed" } self.run = function() { var currBlockNumber = self.getBlockNumber() var fromBlock = "0x" + self.fromBlockNumber.toString(16) var toBlock = "0x" + currBlockNumber.toString(16) var params = { "fromBlock" : fromBlock, "toBlock" : toBlock, "address" : self.contractAddress, "topics" : [self.eventHash] } // Log("fromBlockNumber:", self.fromBlockNumber, ", currBlockNumber:", currBlockNumber, "#FF0000") var logs = exchange.IO("api", "eth", "eth_getLogs", params) if (!logs) { return } for (var i = 0; i < logs.length; i++) { if (toAmount(logs[i].blockNumber, 0) > self.latestBlockNumber) { /* TODO: test if (self.isFirst) { self.firstBlockNumber = toAmount(logs[i].blockNumber, 0) Log("firstBlockNumber:", self.firstBlockNumber) self.isFirst = false } */ callBack(logs[i]) } } self.latestBlockNumber = currBlockNumber self.fromBlockNumber = self.latestBlockNumber - 1 } self.latestBlockNumber = self.getBlockNumber() self.fromBlockNumber = self.latestBlockNumber - 1 return self } var listener = null function main() { var event = "Transfer(address,address,uint256)" var contractAddress = "0xdac17f958d2ee523a2206206994597c13d831ec7" var decimals = exchange.IO("api", contractAddress, "decimals") Log(exchange.IO("api", contractAddress, "name"), " decimals:", decimals) listener = addEventListener(contractAddress, event, function(log) { var fromAddress = "0x" + exchange.IO("encodePacked", "address", log.topics[1]) var toAddress = "0x" + exchange.IO("encodePacked", "address", log.topics[2]) Log("Transfer:", fromAddress, "->", toAddress, ", value:", toAmount(log.data, decimals), ", blockNumber:", toAmount(log.blockNumber, 0)) /* TODO: test arrLog.push(log) */ }) while (true) { listener.run() Sleep(5000) } } /* TODO: test var arrLog = [] function onexit() { Log("结束运行,验证记录") var firstBlockNumber = listener.firstBlockNumber var endBlockNumber = listener.latestBlockNumber Log("getLogs, from:", firstBlockNumber, " -> to:", endBlockNumber) var fromBlock = "0x" + (firstBlockNumber).toString(16) var toBlock = "0x" + (endBlockNumber).toString(16) var params = { "fromBlock" : fromBlock, "toBlock" : toBlock, "topics" : ["0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"], "address" : "0xdac17f958d2ee523a2206206994597c13d831ec7" } var logs = exchange.IO("api", "eth", "eth_getLogs", params) Log("arrLog:", arrLog.length) Log("logs:", logs.length) if (arrLog.length != logs.length) { Log("长度不等!") return } for (var i = 0; i < arrLog.length; i++) { Log("判断blockNumber:", logs[i].blockNumber == arrLog[i].blockNumber, ",判断from:", logs[i].topics[1] == arrLog[i].topics[1], "判断to:", logs[i].topics[2] == arrLog[i].topics[2]) } } */

实盘运行:

img

对于执行结果,代码中也编写了验证部分(TODO: test)。经过简单验证可以看到持续监控USDT合约的Transfer事件并且记录数据,用这个数据和一次性获取的事件数据对比可以观察出数据一致:

img

事件过滤

在上一节课程「监听合约事件」的基础上,我们拓展一下,在监听的过程中增加过滤器,监听指定地址的转入转出。当智能合约创建日志时(即释放事件),日志数据中topics最多包含4条信息。所以我们设计一个过滤规则,以[[A1, A2, ...An], null, [C1], D]为例子。

1、[A1, A2, ...An]对应topics[0]位置的数据。
2、null对应topics[1]位置的数据。
3、[C1]对应topics[2]位置的数据。
4、D对应topics[3]位置的数据。

  • 如果条件结构中的元素设置null表示不被过滤,例如null对应topics[1],任何值都匹配。
  • 如果条件结构中的元素设置单个值表示该位置必须匹配,例如[C1]对应topics[2]或者D对应topics[3],不匹配的日志被过滤。
  • 如果条件结构中的元素是一个数组,表示数组中的元素至少有一个要匹配,例如[A1, A2, ...An]对应topics[0][A1, A2, ...An]中有任意一个和topics[0]匹配,则日志不会被过滤。

监听交易所的USDT转账

监控从币安交易所转出、转入币安交易所USDT的交易:

javascript
function toAmount(s, decimals) { return Number((BigDecimal(BigInt(s)) / BigDecimal(Math.pow(10, decimals))).toString()) } function toInnerAmount(n, decimals) { return (BigDecimal(n) * BigDecimal(Math.pow(10, decimals))).toFixed(0) } function addEventListener(contractAddress, event, callBack) { var self = {} self.eventHash = "0x" + Encode("keccak256", "string", "hex", event) self.contractAddress = contractAddress self.latestBlockNumber = 0 self.fromBlockNumber = 0 self.firstBlockNumber = 0 self.filters = [] self.setFilter = function(filterCondition) { if (filterCondition.length > 4) { throw "filterCondition error" } self.filters.push(filterCondition) Log("设置过滤条件:", filterCondition) } self.getTokenBalanceOfWallet = function(walletAddress, tokenAddress, tokenDecimals) { var balance = exchange.IO("api", tokenAddress, "balanceOf", walletAddress) if (balance) { return toAmount(balance, tokenDecimals) } return null } self.getBlockNumber = function() { var maxTry = 10 for (var i = 0; i < maxTry; i++) { var ret = exchange.IO("api", "eth", "eth_blockNumber") if (ret) { return toAmount(ret, 0) } Sleep(5000) } throw "getBlockNumber failed" } self.run = function() { var currBlockNumber = self.getBlockNumber() var fromBlock = "0x" + self.fromBlockNumber.toString(16) var toBlock = "0x" + currBlockNumber.toString(16) var params = { "fromBlock" : fromBlock, "toBlock" : toBlock, "address" : self.contractAddress, "topics" : [self.eventHash] } var logs = exchange.IO("api", "eth", "eth_getLogs", params) if (!logs) { return } for (var i = 0; i < logs.length; i++) { if (toAmount(logs[i].blockNumber, 0) > self.latestBlockNumber) { // 检查过滤条件,设置了过滤条件则执行判断 if (self.filters.length != 0) { // 初始过滤标记 var isFilter = true // 遍历过滤条件设置 for (var j = 0; j < self.filters.length; j++) { // 取一个过滤设置,例如:[[A1, A2, ...An], null, [C1], D] var cond = self.filters[j] // 遍历这个过滤设置 var final = true for (var topicsIndex = 0; topicsIndex < cond.length; topicsIndex++) { // 拿到这个过滤设置中的某一个条件,如果是第一个条件:即要和topics[0]对比的数据 var condValue = cond[topicsIndex] // 日志中的数据 if (topicsIndex > logs[i].topics.length - 1) { continue } var topicsEleValue = logs[i].topics[topicsIndex] // 如果是Transfer事件,需要处理from和to if (logs[i].topics[0] == "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef") { if (topicsIndex == 1 || topicsIndex == 2) { topicsEleValue = "0x" + exchange.IO("encodePacked", "address", topicsEleValue) } } // 如果condValue类型是数组,表示该位置的对比条件有多个,多个条件对比是逻辑或关系 if (Array.isArray(condValue) && condValue.length > 1) { // 判断 condValue[0] == topicsEleValue || condValue[1] == topicsEleValue final = final && condValue.some(element => element === topicsEleValue) }else if (condValue === null) { final = final && true } else { final = final && (condValue === topicsEleValue) } } if (final) { isFilter = false } } if (isFilter) { continue } } callBack(logs[i]) } } self.latestBlockNumber = currBlockNumber self.fromBlockNumber = self.latestBlockNumber - 1 } self.latestBlockNumber = self.getBlockNumber() self.fromBlockNumber = self.latestBlockNumber - 1 return self } var listener = null function main() { // 初始清理日志 LogReset(1) LogProfitReset() var event = "Transfer(address,address,uint256)" // 监听事件 var contractAddress = "0xdac17f958d2ee523a2206206994597c13d831ec7" // USDT合约地址 var decimals = exchange.IO("api", contractAddress, "decimals") // 获取USDT token的精度信息 var accountBinanceAddress = "0x28C6c06298d514Db089934071355E5743bf21d60" // Binance 热钱包地址 accountBinanceAddress = accountBinanceAddress.toLowerCase() // 地址处理为小写 Log(exchange.IO("api", contractAddress, "name"), " decimals:", decimals) // 创建监听对象 listener = addEventListener(contractAddress, event, function(log) { var fromAddress = "0x" + exchange.IO("encodePacked", "address", log.topics[1]) var toAddress = "0x" + exchange.IO("encodePacked", "address", log.topics[2]) if (fromAddress == accountBinanceAddress) { Log("币安转出 - ", " Transfer:", fromAddress, "->", toAddress, ", value:", toAmount(log.data, decimals), ", blockNumber:", toAmount(log.blockNumber, 0), "#CD32CD") } else if (toAddress == accountBinanceAddress) { Log("转入币安 - ", " Transfer:", fromAddress, "->", toAddress, ", value:", toAmount(log.data, decimals), ", blockNumber:", toAmount(log.blockNumber, 0), "#FF0000") } }) // 设置事件过滤 listener.setFilter([null, accountBinanceAddress, null]) // Binance -> USDT listener.setFilter([null, null, accountBinanceAddress]) // USDT -> Binance var preBalance = 0 while (true) { listener.run() var balance = listener.getTokenBalanceOfWallet(accountBinanceAddress, contractAddress, decimals) if (balance) { var direction = "" if (preBalance != 0 && preBalance > balance) { direction = " ↓ " + (preBalance - balance) + "#CD32CD" } else if (preBalance != 0 && preBalance < balance) { direction = " ↑ " + (balance - preBalance) + "#FF0000" } Log("币安钱包地址:", accountBinanceAddress, " 余额:", balance, direction) LogProfit(balance, "&") // 只画图,不打印日志 preBalance = balance } LogStatus(_D(), "币安钱包地址:", accountBinanceAddress, ", 余额:", balance) Sleep(5000 * 3) } }

以上代码在实盘运行:

img

img

这一节课程,我们介绍了如何设计事件过滤器。并且用它监听了与币安交易所热钱包相关的USDT交易。你可以修改、扩展这个范例程序,用来监听任何你感兴趣的事件,看看smart money做了哪些新交易,NFT大佬冲了哪些新项目等等。

单位转换

以太坊相关的许多计算中,数值都超出了JavaScript语言的最大安全整数。因此在发明者量化交易平台上需要一些方法来处理大数值,这些方法在之前的课程中我们也具体用到过,没有详细讲解。那么本章节就详细讨论一下这方面的内容。

打印JavaScript语言中定义的最大安全整数:

javascript
function main() { Log("Number.MAX_SAFE_INTEGER:", Number.MAX_SAFE_INTEGER) }

运行结果:

Number.MAX_SAFE_INTEGER: 9007199254740991

BigInt

在以太坊中定义的最小单位是1wei,定义1Gwei等于1000000000 wei1Gwei在以太坊相关计算中其实并不算一个很大的数,有些数据比它大得多。那么这些值超大的数据就很容易超过Number.MAX_SAFE_INTEGER: 9007199254740991

在发明者量化交易平台,我们使用平台的BigInt对象来表示这些超大的整数数据。使用构造函数BigInt()来构造BigInt对象。可以使用数值、十六进制数值字符串作为参数构造BigInt对象。使用BigInt对象的toString()方法将对象表示的数据输出为字符串形式。

BigInt对象支持的运算:

  • 加法运算:+
  • 减法运算:-
  • 乘法运算:*
  • 除法运算:/
  • 求模运算:%
  • 幂运算:**

参考以下代码例子:

javascript
function main() { // 1Gwei的十进制表示 var oneGwei = 1000000000 // 1Gwei的十进制转换为十六进制表示 var oneGweiForHex = "0x" + oneGwei.toString(16) Log("oneGwei : ", oneGwei) Log("oneGweiForHex : ", oneGweiForHex) // 构造BigInt对象 Log("1Gwei / 1Gwei : ", (BigInt(oneGwei) / BigInt(oneGweiForHex)).toString(10)) Log("1Gwei * 1Gwei : ", (BigInt(oneGwei) * BigInt(oneGweiForHex)).toString(10)) Log("1Gwei - 1Gwei : ", (BigInt(oneGwei) - BigInt(oneGweiForHex)).toString(10)) Log("1Gwei + 1Gwei : ", (BigInt(oneGwei) + BigInt(oneGweiForHex)).toString(10)) Log("(1Gwei + 1) % 1Gwei : ", (BigInt(oneGwei + 1) % BigInt(oneGweiForHex)).toString(10)) Log("1Gwei ** 2 : ", (BigInt(oneGwei) ** BigInt(2)).toString(10)) Log("100 的平方根 : ", (BigInt(100) ** BigFloat(0.5)).toString(10)) Log("Number.MAX_SAFE_INTEGER : ", BigInt(Number.MAX_SAFE_INTEGER).toString(10)) Log("Number.MAX_SAFE_INTEGER * 2 : ", (BigInt(Number.MAX_SAFE_INTEGER) * BigInt("2")).toString(10)) }

调试工具测试:

run
2023-06-08 11:39:50 信息 Number.MAX_SAFE_INTEGER * 2 : 18014398509481982 2023-06-08 11:39:50 信息 Number.MAX_SAFE_INTEGER : 9007199254740991 2023-06-08 11:39:50 信息 100 的平方根 : 10 2023-06-08 11:39:50 信息 1Gwei ** 2 : 1000000000000000000 2023-06-08 11:39:50 信息 (1Gwei + 1) % 1Gwei : 1 2023-06-08 11:39:50 信息 1Gwei + 1Gwei : 2000000000 2023-06-08 11:39:50 信息 1Gwei - 1Gwei : 0 2023-06-08 11:39:50 信息 1Gwei * 1Gwei : 1000000000000000000 2023-06-08 11:39:50 信息 1Gwei / 1Gwei : 1 2023-06-08 11:39:50 信息 oneGweiForHex : 0x3b9aca00 2023-06-08 11:39:50 信息 oneGwei : 1000000000

BigFloat

BigFloat对象与BigInt对象使用类似,用于表示数值较大的浮点数,也支持加减乘除运算。
BigFloat对象支持toFixed()方法。

参考以下代码例子:

javascript
function main() { var pi = 3.14 var oneGwei = "1000000000" var oneGweiForHex = "0x3b9aca00" Log("pi + oneGwei : ", (BigFloat(pi) + BigFloat(oneGwei)).toFixed(2)) Log("pi - oneGweiForHex : ", (BigFloat(pi) - BigFloat(oneGweiForHex)).toFixed(2)) Log("pi * 2.0 : ", (BigFloat(pi) * BigFloat(2.0)).toFixed(2)) Log("pi / 2.0 : ", (BigFloat(pi) / BigFloat(2.0)).toFixed(2)) }

调试工具测试:

javascript
2023-06-08 13:56:44 信息 pi / 2.0 : 1.57 2023-06-08 13:56:44 信息 pi * 2.0 : 6.28 2023-06-08 13:56:44 信息 pi - oneGweiForHex : -999999996.86 2023-06-08 13:56:44 信息 pi + oneGwei : 1000000003.14

BigDecimal

BigDecimal对象兼容了整型数值、浮点型数值,支持使用BigInt对象、BigFloat对象初始化,也支持加减乘除运算。

参考以下代码例子:

javascript
function main() { var pi = 3.1415 var oneGwei = 1000000000 var oneGweiForHex = "0x3b9aca00" Log("pi : ", BigDecimal(pi).toFixed(2)) Log("oneGwei : ", BigDecimal(oneGwei).toString()) Log("oneGweiForHex : ", BigDecimal(BigInt(oneGweiForHex)).toString()) Log("BigInt(oneGwei) : ", BigDecimal(BigInt(oneGwei)).toString()) Log("BigFloat(pi) : ", BigDecimal(BigFloat(pi)).toFixed(4)) Log("oneGwei + pi : ", (BigDecimal(oneGwei) + BigDecimal(pi)).toString()) Log("oneGwei - pi : ", (BigDecimal(oneGwei) - BigDecimal(pi)).toString()) Log("2.0 * pi : ", (BigDecimal(2.0) * BigDecimal(pi)).toString()) Log("pi / pi : ", (BigDecimal(pi) / BigDecimal(pi)).toString()) }

调试工具中运行:

run
2023-06-08 14:52:53 信息 pi / pi : 1 2023-06-08 14:52:53 信息 2.0 * pi : 6.283 2023-06-08 14:52:53 信息 oneGwei - pi : 999999996.8585 2023-06-08 14:52:53 信息 oneGwei + pi : 1000000003.1415 2023-06-08 14:52:53 信息 BigFloat(pi) : 3.1415 2023-06-08 14:52:53 信息 BigInt(oneGwei) : 1e+9 2023-06-08 14:52:53 信息 oneGweiForHex : 1e+9 2023-06-08 14:52:53 信息 oneGwei : 1e+9 2023-06-08 14:52:53 信息 pi : 3.14

单位转换

以下这两个函数:toAmount()toInnerAmount()我们在之前的课程中多次使用过,这两个函数主要用于数据精度转换。

javascript
function toAmount(s, decimals) { return Number((BigDecimal(BigInt(s)) / BigDecimal(Math.pow(10, decimals))).toString()) } function toInnerAmount(n, decimals) { return (BigDecimal(n) * BigDecimal(Math.pow(10, decimals))).toFixed(0) }

toAmount()函数将一个变量s,按照精度参数decimals进行(缩小)转换。在web3实际开发中,经常需要处理一些链上的十六进制数据。
在我们之前的课程中就经常遇到,例如智能合约的Transfer(address,address,uint256)事件中的data字段数据:

desc
{ "data": "0x00000000000000000000000000000000000000000000000001c1a55000000000", "topics": ["0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", "0x0000000000000000000000006b75d8af000000e20b7a7ddf000ba900b4009a80", "0x000000000000000000000000bcb095c1f9c3dc02e834976706c87dee5d0f1fb6"], "transactionHash": "0x27f9bf5abe3148169b4b85a83e1de32bd50eb81ecc52e5af006157d93353e4c4", "transactionIndex": "0x0", "removed": false, "address": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", "blockHash": "0x847be24a7b159c292bda030a011dfec89487b70e71eed486969b032d6ef04bad", "blockNumber": "0x109b1cc", "logIndex": "0x0" }

处理数据"data": "0x00000000000000000000000000000000000000000000000001c1a55000000000",这个时候使用toAmount()函数。这样的处理设计就可以很好的把data字段数据转换为可读数值。

javascript
function toAmount(s, decimals) { return Number((BigDecimal(BigInt(s)) / BigDecimal(Math.pow(10, decimals))).toString()) } function main() { var data = "0x00000000000000000000000000000000000000000000000001c1a55000000000" Log(toAmount(data, 18)) // 打印出 0.12656402755905127 }

1个ETH代币我们知道是1e18 wei,如果我们拿到了一个以wei为单位的数据126564027559051260如何换算成ETH代币个数?
使用toAmount(, 18)函数就可以很简单的转换了。toInnerAmount()函数则是toAmount()函数的反向操作(根据精度,放大),使用这两个函数就很方便的可以转换数据了。

需要注意的是JavaScript语言中的整型数值安全范围,即Number.MAX_SAFE_INTEGER,以下这个例子说明一个数据转换时比较隐蔽的问题:

javascript
function toAmount(s, decimals) { return Number((BigDecimal(BigInt(s)) / BigDecimal(Math.pow(10, decimals))).toString()) } function toInnerAmount(n, decimals) { return (BigDecimal(n) * BigDecimal(Math.pow(10, decimals))).toFixed(0) } function main() { var amount = 0.01 var innerAmount = Number(toInnerAmount(amount, 18)) Log("Number.MAX_SAFE_INTEGER:", Number.MAX_SAFE_INTEGER) // 9007199254740991 Log("innerAmount:", innerAmount) // 10000000000000000 Log("typeof(innerAmount):", typeof(innerAmount), ", innerAmount:", innerAmount) // 十进制数值 10000000000000000 -> 十六进制数值 0x2386f26fc10000 Log("转换", innerAmount, "为十六进制:", innerAmount.toString(16)) Log("转换", BigInt(10000000000000000).toString(10), "为十六进制:", BigInt(10000000000000000).toString(16)) Log("0x" + BigInt(10000000000000000).toString(16), "转换为10进制:", toAmount("0x" + BigInt(10000000000000000).toString(16), 0)) }

可以在调试工具中运行:

run
2023-06-15 16:21:40 信息 0x2386f26fc10000 转换为10进制: 10000000000000000 2023-06-15 16:21:40 信息 转换 10000000000000000 为十六进制: 2386f26fc10000 2023-06-15 16:21:40 信息 转换 10000000000000000 为十六进制: 10000000000000000 2023-06-15 16:21:40 信息 typeof(innerAmount): number , innerAmount: 10000000000000000 2023-06-15 16:21:40 信息 innerAmount: 10000000000000000 2023-06-15 16:21:40 信息 Number.MAX_SAFE_INTEGER: 9007199254740991

通过观察我们发现:

javascript
Log("转换", innerAmount, "为十六进制:", innerAmount.toString(16))

这一行代码对应的日志输出:转换 10000000000000000 为十六进制: 10000000000000000,竟然没有正确转换。原因自然是因为10000000000000000超出了Number.MAX_SAFE_INTEGER

但是当十进制数值在安全范围内,即小于Number.MAX_SAFE_INTEGER时,toString(16)函数转换又是正常的,例如:

javascript
function main() { var value = 1000 Log("把value转换为十六进制:", "0x" + value.toString(16)) // 0x3e8 Log("把0x3e8转换为十进制:", Number("0x3e8")) // 1000 }

在区块链领域即使0.01个ETH换算成以wei为单位的数值10000000000000000也会超出Number.MAX_SAFE_INTEGER,所以对于此类情况比较安全的转换是:BigInt(10000000000000000).toString(16)

模拟调用

在以太坊上执行交易、调用智能合约的Write方法需要消耗一定gas费用,并且有时候有失败的风险。在发送交易、调用之前知道哪些交易可能会失败非常重要。在以太坊上有模拟调用的方法用来测试。

eth_call

以太坊的RPC方法eth_call:可以模拟一笔交易,并返回可能的交易结果,但是并不会真正的在区块链上执行这次交易。

eth_call方法的参数有2个,第一个参数为一个字典结构,transactionObject

javascript
// transactionObject { "from" : ..., // The address from which the transaction is sent "to" : ..., // The address to which the transaction is addressed "gas" : ..., // The integer of gas provided for the transaction execution "gasPrice" : ..., // The integer of gasPrice used for each paid gas encoded as hexadecimal "value" : ..., // The integer of value sent with this transaction encoded as hexadecimal "data" : ..., // The hash of the method signature and encoded parameters. For more information, see the Contract ABI description in the Solidity documentation }

第二个参数为blockNumber:可以传标签latest/pending/earliest等:

javascript
/* blockNumber The block number in hexadecimal format or the string latest, earliest, pending, safe or finalized (safe and finalized tags are only supported on Ethereum, Gnosis, Arbitrum, Arbitrum Nova and Avalanche C-chain), see the default block parameter description in the official Ethereum documentation */

接下来我们以代币DAI的智能合约方法approvetransfer调用为例子进行模拟调用,以下测试环境为以太坊主网。

模拟调用approve

对于ERC20合约的approve方法我们都已经很熟悉了,之前的课程中我们也实践过。由于ERC20合约在FMZ平台ABI已经内置,所以不用注册所要模拟调用的智能合约的ABI。

javascript
function main() { var contractAddressUniswapV3SwapRouterV2 = "0x68b3465833fb72A70ecDF485E0e4C7bD8665Fc45" var contractAddress_DAI = "0x6b175474e89094c44da98b954eedeac495271d0f" var wallet = exchange.IO("address") // encode approve var data = exchange.IO("encode", contractAddress_DAI, "approve(address,uint256)", contractAddressUniswapV3SwapRouterV2, "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff") Log("ERC20 token DAI approve encode, data:", data) var transactionObject = { "from" : wallet, "to" : contractAddress_DAI, // "gasPrice" : "0x" + parseInt("21270894680").toString(16), // "gas" : "0x" + parseInt("21000").toString(16), "data" : "0x" + data, } var blockNumber = "latest" var ret = exchange.IO("api", "eth", "eth_call", transactionObject, blockNumber) Log("ret:", ret) }

例子中的代码首先将approve(address,uint256)方法、参数进行编码,approve方法的参数值0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff表示授权最大数量。授权给智能合约,地址0x68b3465833fb72A70ecDF485E0e4C7bD8665Fc45Uniswap V3的路由合约。最后调用以太坊RPC方法eth_call进行模拟。可以看到transactionObject参数中的gasPricegas字段是可以省略的。

调试工具运行,模拟调用approve方法授权成功(并不会真正授权):

run
2023-06-09 11:58:39 信息 ret: 0x0000000000000000000000000000000000000000000000000000000000000001 2023-06-09 11:58:39 信息 ERC20 token DAI approve encode, data: 095ea7b300000000000000000000000068b3465833fb72a70ecdf485e0e4c7bd8665fc45ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff

也可以模拟一些失败的场景,当我们调整gasPricegas参数时,如果钱包中的ETH不足以支付gas费用会报错:

insufficient funds

当设置了过低的gas费用,会报错:

intrinsic gas too low: have 21000, want 21944 (supplied gas 21000)

模拟调用transfer

对于ERC20的transfer方法我们也并不陌生,这个方法可以给某个钱包地址转账ERC20代币,我们就尝试模拟给V神转账1000个DAI。

javascript
function toInnerAmount(n, decimals) { return (BigDecimal(n) * BigDecimal(Math.pow(10, decimals))).toFixed(0) } function main() { var walletVitalik = "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045" var contractAddress_DAI = "0x6b175474e89094c44da98b954eedeac495271d0f" var wallet = exchange.IO("address") // 转账给V神 var decimals_DAI = exchange.IO("api", contractAddress_DAI, "decimals") var transferAmount = toInnerAmount(1000, decimals_DAI) Log("转账金额:", 1000, "DAI, 使用 toInnerAmount 转换为:", transferAmount) // encode transfer var data = exchange.IO("encode", contractAddress_DAI, "transfer(address,uint256)", walletVitalik, transferAmount) var transactionObject = { "from" : wallet, "to" : contractAddress_DAI, "data" : "0x" + data, } var blockNumber = "latest" var ret = exchange.IO("api", "eth", "eth_call", transactionObject, blockNumber) return ret }

由于我这个测试钱包没有DAI代币,所以在调试工具中运行的结果意料之中的报错了:

execution reverted: Dai/insufficient-balance

查看V神的这个钱包地址:0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045可知,这个钱包是有DAI代币的。那么我们来对调一下模拟调用的转账方向,模拟一下V神给我们转账1000DAI。

修改一下代码,在修改的地方我作了注释:

javascript
function toInnerAmount(n, decimals) { return (BigDecimal(n) * BigDecimal(Math.pow(10, decimals))).toFixed(0) } function main() { var walletVitalik = "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045" var contractAddress_DAI = "0x6b175474e89094c44da98b954eedeac495271d0f" var wallet = exchange.IO("address") var decimals_DAI = exchange.IO("api", contractAddress_DAI, "decimals") var transferAmount = toInnerAmount(1000, decimals_DAI) Log("转账金额:", 1000, "DAI, 使用 toInnerAmount 转换为:", transferAmount) // encode transfer var data = exchange.IO("encode", contractAddress_DAI, "transfer(address,uint256)", wallet, transferAmount) // 使用wallet变量作为参数,转账接收方地址改为我自己 var transactionObject = { "from" : walletVitalik, // 使用walletVitalik变量作为from字段的值,模拟这个调用是由V神钱包地址发出 "to" : contractAddress_DAI, "data" : "0x" + data, } var blockNumber = "latest" var ret = exchange.IO("api", "eth", "eth_call", transactionObject, blockNumber) Log(ret) }

调试工具测试:

javascript
2023-06-09 13:34:31 信息 0x0000000000000000000000000000000000000000000000000000000000000001 2023-06-09 13:34:31 信息 转账金额: 1000 DAI, 使用 toInnerAmount 转换为: 1000000000000000000000

使用发明者量化交易平台,可以很方便地模拟交易的结果,并避免发送可能失败的交易造成不必要的gas费用损失。我们使用本章节课程中的例子代码模拟了向V神钱包转账、V神钱包向我们转账的调用。当然,这个eth_call方法还有更多用处。发挥你的想象力,你会将eth_call方法用在什么地方呢?

识别ERC721合约

我们知道ETH、BTC之类的token都属于同质化token,你手中的token和我手中的token并没有不同。但是世界上有很多东西是不同质的,例如:房产、古董、虚拟艺术品等,这些无法用同质化代币来抽象代表。因此,就有了ERC721标准来抽象非同质化的对象,就有了NFT以及相关概念。
那么在以太坊上部署的众多智能合约中,我们如何识别哪些智能合约是ERC721标准的智能合约呢?

要识别ERC721,那么首先要了解ERC165标准。

ERC165

通过ERC165标准,智能合约可以声明它支持的接口供其它合约检查。ERC165接口合约只有一个函数:supportsInterface(bytes4 interfaceId),参数interfaceId即要查询的接口Id。如果合约实现了该接口Id返回布尔值真值,否则返回假值。

下面我们要讲一下这个interfaceId具体是如何计算、编码的。

ERC165标准给出了一个例子:

solidity
pragma solidity ^0.4.20; interface Solidity101 { function hello() external pure; function world(int) external pure; } contract Selector { function calculateSelector() public pure returns (bytes4) { Solidity101 i; return i.hello.selector ^ i.world.selector; } }

对于接口的函数签名(由函数名和参数类型列表组成)进行异或运算,对于合约只有一个函数的ERC165接口合约来说:

solidity
pragma solidity ^0.4.20; interface ERC165 { /// @notice Query if a contract implements an interface /// @param interfaceID The interface identifier, as specified in ERC-165 /// @dev Interface identification is specified in ERC-165. This function /// uses less than 30,000 gas. /// @return `true` if the contract implements `interfaceID` and /// `interfaceID` is not 0xffffffff, `false` otherwise function supportsInterface(bytes4 interfaceID) external view returns (bool); }

The interface identifier for this interface is 0x01ffc9a7. You can calculate this by running bytes4(keccak256('supportsInterface(bytes4)')); or using the Selector contract above.

直接计算函数签名,取其前4个byte得出的就是interfaceId

javascript
function main() { var ret = Encode("keccak256", "string", "hex", "supportsInterface(bytes4)") Log("supportsInterface(bytes4) interfaceId:", "0x" + ret.slice(0, 8)) }

在调试工具中可以运行测试:

run
2023-06-13 14:53:35 信息 supportsInterface(bytes4) interfaceId: 0x01ffc9a7

可以看到计算出的结果和ERC165标准文档中描述一致。

ERC721

接下来我们看ERC721合约标准的接口定义:

solidity
interface ERC721 /* is ERC165 */ { event Transfer(address indexed _from, address indexed _to, uint256 indexed _tokenId); event Approval(address indexed _owner, address indexed _approved, uint256 indexed _tokenId); event ApprovalForAll(address indexed _owner, address indexed _operator, bool _approved); function balanceOf(address _owner) external view returns (uint256); function ownerOf(uint256 _tokenId) external view returns (address); function safeTransferFrom(address _from, address _to, uint256 _tokenId, bytes data) external payable; function safeTransferFrom(address _from, address _to, uint256 _tokenId) external payable; function transferFrom(address _from, address _to, uint256 _tokenId) external payable; function approve(address _approved, uint256 _tokenId) external payable; function setApprovalForAll(address _operator, bool _approved) external; function getApproved(uint256 _tokenId) external view returns (address); function isApprovedForAll(address _owner, address _operator) external view returns (bool); }

如果要判断某个智能合约是否是ERC721合约,我们首先要知道ERC721合约的interfaceId,然后才能尝试使用supportsInterface(bytes4 interfaceId)方法判断,之前的课程我们已经熟悉了ERC165标准的一些概念和计算interfaceId的算法,我们直接写代码计算:

javascript
function calcSelector(arrSelector) { var ret = null if (Array.isArray(arrSelector)) { if (arrSelector.length == 1) { ret = Encode("keccak256", "string", "hex", arrSelector[0]) } else if (arrSelector.length == 0) { throw "错误:数组中元素个数为0" } else { var viewEncodeData = null for (var i = 0; i < arrSelector.length; i++) { if (i == 0) { ret = new Uint8Array(Encode("keccak256", "string", "raw", arrSelector[i])) } else { viewData = new Uint8Array(Encode("keccak256", "string", "raw", arrSelector[i])) if (viewData.length != ret.length) { throw "错误:TypeArray view长度不同" } for (var index = 0; index < ret.length; index++) { ret[index] ^= viewData[index] } } } ret = Encode("raw", "raw", "hex", ret.buffer) } } else { throw "错误:参数需要数组类型。" } return "0x" + ret.slice(0, 8) } function main() { // supportsInterface(bytes4): 0x01ffc9a7 // var ret = calcSelector(["supportsInterface(bytes4)"]) // ERC721Metadata: 0x5b5e139f /* var arrSelector = [ "name()", "symbol()", "tokenURI(uint256)" ] var ret = calcSelector(arrSelector) */ // ERC721: 0x80ac58cd // /* var arrSelector = [ "balanceOf(address)", "ownerOf(uint256)", "safeTransferFrom(address,address,uint256,bytes)", "safeTransferFrom(address,address,uint256)", "transferFrom(address,address,uint256)", "approve(address,uint256)", "setApprovalForAll(address,bool)", "getApproved(uint256)", "isApprovedForAll(address,address)", ] var ret = calcSelector(arrSelector) // */ Log(ret) }

代码中使用了Encode()函数进行函数签名计算(keccak256算法),对于以上代码例子中的计算来说,指定Encode()函数的输出参数为"raw",该函数则返回JavaScript语言的ArrayBuffer类型。
如果要对两个ArrayBuffer对象进行^(异或)运算,需要基于ArrayBuffer对象创建TypedArray视图,然后遍历其中的数据,逐个进行异或运算。

调试工具中运行:

run
2023-06-13 15:04:09 信息 0x80ac58cd

可以看到算出的结果和eip-721中描述的一致。

solidity
pragma solidity ^0.4.20; /// @title ERC-721 Non-Fungible Token Standard /// @dev See https://eips.ethereum.org/EIPS/eip-721 /// Note: the ERC-165 identifier for this interface is 0x80ac58cd. interface ERC721 /* is ERC165 */ { /// @dev This emits when ownership of any NFT changes by any mechanism. /// This event emits when NFTs are created (`from` == 0) and destroyed /// (`to` == 0). Exception: during contract creation, any number of NFTs /// may be created and assigned without emitting Transfer. At the time of /// any transfer, the approved address for that NFT (if any) is reset to none. event Transfer(address indexed _from, address indexed _to, uint256 indexed _tokenId); ...

有了ERC721的接口Id,我们就可以判断某个合约是不是ERC721标准的合约了。我们使用BAYC做测试,这是一个遵循ERC721的合约,首先我们需要注册ABI,由于我们只调用以下三个方法,可以只注册这三个方法:

  • supportsInterface(interfaceId)
  • symbol()
  • name()

具体代码如下:

javascript
function main() { // ERC721的合约地址,这里用的BAYC var testContractAddress = "0xbc4ca0eda7647a8ab7c2061c2e118a18a936f13d" var testABI = `[{ "inputs": [{ "internalType": "bytes4", "name": "interfaceId", "type": "bytes4" }], "name": "supportsInterface", "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], "stateMutability": "view", "type": "function" }, { "inputs": [], "name": "symbol", "outputs": [{ "internalType": "string", "name": "", "type": "string" }], "stateMutability": "view", "type": "function" }, { "inputs": [], "name": "name", "outputs": [{ "internalType": "string", "name": "", "type": "string" }], "stateMutability": "view", "type": "function" }]` // ERC721接口Id,在之前的课程中计算得出 var interfaceId = "0x80ac58cd" // 注册ABI exchange.IO("abi", testContractAddress, testABI) // 调用supportsInterface方法 var isErc721 = exchange.IO("api", testContractAddress, "supportsInterface", interfaceId) // 输出信息 Log("合约地址:", testContractAddress) Log("合约名称:", exchange.IO("api", testContractAddress, "name")) Log("合约代号:", exchange.IO("api", testContractAddress, "symbol")) Log("合约是否为ERC721标准:", isErc721) }

在调试工具中可以运行测试:

run
2023-06-13 16:32:57 信息 合约是否为ERC721标准: true 2023-06-13 16:32:57 信息 合约代号: BAYC 2023-06-13 16:32:57 信息 合约名称: BoredApeYachtClub 2023-06-13 16:32:57 信息 合约地址: 0xbc4ca0eda7647a8ab7c2061c2e118a18a936f13d

判断出地址0xbc4ca0eda7647a8ab7c2061c2e118a18a936f13d的合约为ERC721标准。

这一讲,我们介绍了如何判断ERC721合约,那么ERC20这种不支持ERC165的标准的合约,就要用别的方法识别了。你知道怎么检查一个合约是否为ERC20标准吗?

编码calldata

什么是calldata?通过作者的理解,这里简单通俗的描述就是:

"calldata"是以太坊中对某个函数调用、参数的编码,"calldata"是根据合约的ABI(Application Binary Interface)规范进行编码的。

例如我们可以把之前课程中学习过的ERC20合约的balanceOftransfer方法调用、连同调用时的参数一起编码为一个calldata。在一些应用场景例如:合约间交互,这种场景下就会用到calldata,当然还有很多其它应用场景这里就不一一列举了。

如何编码一个智能合约函数调用,得到calldata

在发明者量化交易平台可以使用exchange.IO("encode", ...)对智能合约函数调用进行编码,使用也非常简单。exchange.IO("encode", ...)函数的第一个参数为固定字符串"encode";第二个参数为智能合约的地址;第三个参数为所要编码的智能合约方法名;其余参数传入这个所要编码的智能合约方法的具体参数值。

eth_sendRawTransaction

当我们编码了一个智能合约方法的调用并生成了对应的calldata数据时,如果这个智能合约方法是一个Write方法(即:写操作),我们需要将生成的calldata数据作为交易的数据字段,然后使用以太坊的RPC方法eth_sendRawTransaction发送一个包含该交易的原始数据的请求到以太坊网络。

eth_sendRawTransaction方法的参数只有一个data

data: The signed transaction (typically signed with a library, using your private key)

这个data参数是一个交易数据经过签名计算后的数据,以太坊的交易数据结构主要有以下字段:

javascript
{ "nonce": "0x1", // 交易发送方的账户交易次数 "gasPrice": "0x12a05f200", // 交易的Gas价格 "gasLimit": "0x5208", // 交易的Gas限制 "to": "0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2", // 目标合约地址或接收方地址 "value": "0x4563918244F40000", // 转账的以太币数量 "data": "0x0123456789ABCDEF", // 要发送给合约的数据 }

如何对一个以太坊交易进行签名呢?

在发明者量化交易平台我们使用Encode()函数来进行签名计算,具体例子我们写在后续课程「执行Write方法calldata」。

执行Read方法calldata

对于Read方法的calldata执行,我们使用之前学习过的RPC方法:eth_call来执行,之前我们讲解eth_call这个以太坊的RPC方法只做了智能合约Write方法的演示,借此章节使用calldata的方式演示一下执行智能合约Read方法的调用。我们来使用WETH合约的balanceOf方法读取当前钱包的WETH代币余额。

我们使用调试工具,在以太坊主网测试:

javascript
function toAmount(s, decimals) { return Number((BigDecimal(BigInt(s)) / BigDecimal(Math.pow(10, decimals))).toString()) } function main() { // WETH合约的ABI var abiWETH = `[{"constant":true,"inputs":[],"name":"name","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"guy","type":"address"},{"name":"wad","type":"uint256"}],"name":"approve","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"totalSupply","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"src","type":"address"},{"name":"dst","type":"address"},{"name":"wad","type":"uint256"}],"name":"transferFrom","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"wad","type":"uint256"}],"name":"withdraw","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"decimals","outputs":[{"name":"","type":"uint8"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"","type":"address"}],"name":"balanceOf","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"symbol","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"dst","type":"address"},{"name":"wad","type":"uint256"}],"name":"transfer","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[],"name":"deposit","outputs":[],"payable":true,"stateMutability":"payable","type":"function"},{"constant":true,"inputs":[{"name":"","type":"address"},{"name":"","type":"address"}],"name":"allowance","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"payable":true,"stateMutability":"payable","type":"fallback"},{"anonymous":false,"inputs":[{"indexed":true,"name":"src","type":"address"},{"indexed":true,"name":"guy","type":"address"},{"indexed":false,"name":"wad","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"src","type":"address"},{"indexed":true,"name":"dst","type":"address"},{"indexed":false,"name":"wad","type":"uint256"}],"name":"Transfer","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"dst","type":"address"},{"indexed":false,"name":"wad","type":"uint256"}],"name":"Deposit","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"src","type":"address"},{"indexed":false,"name":"wad","type":"uint256"}],"name":"Withdrawal","type":"event"}]` // WETH合约地址 var wethAddress = "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2" // 注册WETH合约的ABI exchange.IO("abi", wethAddress, abiWETH) // 当前配置的交易所对象的钱包地址 var walletAddress = exchange.IO("address") // 编码WETH合约的deposit方法调用 var calldataForDeposit = exchange.IO("encode", wethAddress, "balanceOf(address)", walletAddress) Log("calldataForDeposit:", "0x" + calldataForDeposit) // 构造transaction,作为eth_call的第一个参数 var transaction = { "from" : walletAddress, "to" : wethAddress, "data" : "0x" + calldataForDeposit, } // eth_call的第二个参数 var blockNumber = "latest" // 使用eth_call调用 var ret = exchange.IO("api", "eth", "eth_call", transaction, blockNumber) var wethBalance = exchange.IO("decode", "uint256", ret) // 可以使用exchange.IO("decode", ...) 函数解码 Log("wethBalance:", toAmount(wethBalance, 18)) // 从以wei为单位,换算成WETH个数为单位 }

调试工具中运行:

run
2023-06-15 11:51:31 信息 wethBalance: 0.015 2023-06-15 11:51:31 信息 calldataForDeposit: 0x70a082310000000000000000000000006b3f11d807809b0b1e5e3243df04a280d9f94bf4

如果智能合约的方法有返回值,可以使用exchange.IO("decode", ...)函数解码。可以看到通过传递calldata的方式和直接调用智能合约的balanceOf方法是一样的,拿到了我这个测试钱包的WETH余额为0.015个WETH。

执行Write方法calldata

对于Write方法的calldata执行,就需要使用RPC方法:eth_sendRawTransaction了。

我们使用调试工具,在以太坊主网测试:

javascript
function toAmount(s, decimals) { return Number((BigDecimal(BigInt(s)) / BigDecimal(Math.pow(10, decimals))).toString()) } function toInnerAmount(s, decimals) { return (BigDecimal(s)*BigDecimal(Math.pow(10, decimals))).toFixed(0) } function main() { // WETH合约的ABI var abiWETH = `[{"constant":true,"inputs":[],"name":"name","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"guy","type":"address"},{"name":"wad","type":"uint256"}],"name":"approve","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"totalSupply","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"src","type":"address"},{"name":"dst","type":"address"},{"name":"wad","type":"uint256"}],"name":"transferFrom","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"wad","type":"uint256"}],"name":"withdraw","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"decimals","outputs":[{"name":"","type":"uint8"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"","type":"address"}],"name":"balanceOf","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"symbol","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"dst","type":"address"},{"name":"wad","type":"uint256"}],"name":"transfer","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[],"name":"deposit","outputs":[],"payable":true,"stateMutability":"payable","type":"function"},{"constant":true,"inputs":[{"name":"","type":"address"},{"name":"","type":"address"}],"name":"allowance","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"payable":true,"stateMutability":"payable","type":"fallback"},{"anonymous":false,"inputs":[{"indexed":true,"name":"src","type":"address"},{"indexed":true,"name":"guy","type":"address"},{"indexed":false,"name":"wad","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"src","type":"address"},{"indexed":true,"name":"dst","type":"address"},{"indexed":false,"name":"wad","type":"uint256"}],"name":"Transfer","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"dst","type":"address"},{"indexed":false,"name":"wad","type":"uint256"}],"name":"Deposit","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"src","type":"address"},{"indexed":false,"name":"wad","type":"uint256"}],"name":"Withdrawal","type":"event"}]` // WETH合约地址 var wethAddress = "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2" // 注册WETH合约的ABI exchange.IO("abi", wethAddress, abiWETH) // 当前配置的交易所对象的钱包地址 var walletAddress = exchange.IO("address") // 编码WETH合约的deposit方法调用 var calldataForDeposit = exchange.IO("encode", wethAddress, "deposit") Log("calldataForDeposit:", "0x" + calldataForDeposit) // 获取nonce var nonce = exchange.IO("api", "eth", "eth_getTransactionCount", walletAddress, "pending") // 获取gasPrice var gasPrice = exchange.IO("api", "eth", "eth_gasPrice") // 调用deposit方法把ETH换为WETH,需要转账ETH,这里把0.01ETH转换为以wei为单位的十六进制数值 var innerAmount = BigInt(Number(toInnerAmount(0.005, 18))).toString(16) // The transaction call object: var obj = { "from" : walletAddress, "to" : wethAddress, "gasPrice" : gasPrice, "value" : "0x" + innerAmount, "data" : "0x" + calldataForDeposit, } // 计算gasLimit var gasLimit = exchange.IO("api", "eth", "eth_estimateGas", obj) // 构造交易 var transaction = { "to": wethAddress, "value": toAmount("0x" + innerAmount, 0), // 转换为10进制 "data": "0x" + calldataForDeposit, "gasLimit": toAmount(gasLimit, 0), // 转换为10进制 "gasPrice": toAmount(gasPrice, 0), // 转换为10进制 "nonce": toAmount(nonce, 0), // 转换为10进制 "chainId": 1, // 以太坊主网Id } Log("transaction:", transaction) // 签名,your key 替换为你的私钥 var signedTx = Encode("signTx", "string", "hex", JSON.stringify(transaction), "hex", "0x" + "your key") Log("signedTx:", "0x" + signedTx) // 调用eth_sendRawTransaction发送交易 var ret = exchange.IO("api", "eth", "eth_sendRawTransaction", "0x" + signedTx) return ret }

调试工具中运行:

run
2023-06-15 09:58:50 信息 signedTx: 0xf86f4f8504202067888... 2023-06-15 09:58:50 信息 transaction: {"to":"0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2","value":5000000000000000,"data":"0xd0e30db0","gasLimit":27938,"gasPrice":17718863752,"nonce":79,"chainId":1} 2023-06-15 09:58:50 信息 calldataForDeposit: 0xd0e30db0

执行var ret = exchange.IO("api", "eth", "eth_sendRawTransaction", "0x" + signedTx)函数,返回的Transaction Hash为:0x2ff585504b0fe59b0122f696e8808abfe2f3ce263448066533f3bb8a4f55e8e6。这个eth_sendRawTransaction调用执行了其中的calldata,调用WETH合约的deposit方法将发送的0.005个ETH换为WETH。

监听mempool

在以太坊上用户的交易被矿工打包进以太坊区块链之前,所有交易会汇集在Mempool(交易内存池)中,“矿工”也是在这里寻找费用高的交易优先打包,以实现挖矿利益最大化。所以通常交易的gasPrice设置越高越容易被打包。

一些交易脚本也会对Mempool进行嗅探,希望找到一些有利可图的交易。例如一笔交易如果设置过高的兑换滑点,这笔交易就可能被这些交易脚本实施“三明治攻击”。那么这些脚本是如何监听Mempool中的pending(未决,待打包)交易呢?

使用REST协议监听

使用我们之前学习过的RPC方法:eth_getBlockByNumber,只不过我们这次不传具体的blockNumber,我们使用"pending"标签。

javascript
function main() { var data = exchange.IO("api", "eth", "eth_getBlockByNumber", "pending", true) if (Array.isArray(data.transactions)) { for (var i = 0; i < data.transactions.length; i++) { Log(data.transactions[i]) } } }

调试工具中运行:

run
2023-06-18 19:23:05 信息 {"blockNumber":"0x10b2027","type":"0x2","accessList":[],"blockHash":"0xf833ed36435c53d63bd7109bb1e85383075534410c14573881bf26d912f46a89","from":"0xd50521974d62f1fa34b8e81cb742ccf6147d05ff","gasPrice":"0x32ea2db37","hash":"0xf8f10f8f473c340b021298feb48d0affe529e8737a309c4cc1902e8989ef0914","input":"0xa22cb4650000000000000000000000001e0049783f008a0085193e00003d00cd54003c710000000000000000000000000000000000000000000000000000000000000001","v":"0x0","value":"0x0","maxFeePerGas":"0x48a413364","maxPriorityFeePerGas":"0x5f5e100","nonce":"0x8","r":"0x8c1cc36f43b02c9e9e454153588cc9d38757f1da69ec49d3cfdda74ab69e06a8","s":"0x2f3dd3e5ddf9e5d42c128a8e900026aca7568fa83c68cf332e1328066ee8d03a","transactionIndex":"0x3a","chainId":"0x1","gas":"0x1142d","to":"0x8c3c0274c33f263f0a55d129cfc8eaa3667a9e8b"} 2023-06-18 19:23:05 信息 {"input":"0x646174613a2c7b2270223a226572632d3230222c226f70223a226d696e74222c227469636b223a2265746873222c226964223a223139323732222c22616d74223a2231303030227d","nonce":"0x1d","blockHash":"0xf833ed36435c53d63bd7109bb1e85383075534410c14573881bf26d912f46a89","from":"0xe7fa86855af674837cea1b58f88b5352543ca27b","gas":"0x81cc","gasPrice":"0x32ea2db37","to":"0xe7fa86855af674837cea1b58f88b5352543ca27b","chainId":"0x1","transactionIndex":"0x39","type":"0x2","value":"0x0","accessList":[],"blockNumber":"0x10b2027","hash":"0x55702f5d14736fc9d0c58fdac2d2052a602db171c46b5e1fa9ff6af5c277f9a2","maxFeePerGas":"0x48a413364","maxPriorityFeePerGas":"0x5f5e100","r":"0x5a703d389d23b51adf8ef0f55db8876e7392636797b68a4be6afe73e76d7e1f2","s":"0x4b4bb11257c4434a0acc2672357f8793476e4bfdf98bc30d2389ce335e7de64e","v":"0x1"} 2023-06-18 19:23:05 信息 {"gas":"0x186a0","nonce":"0x46533","r":"0xfeea052a4ac2283ca058a657a806ba0916d8e7d52d2a577f150c40eb1dfbec65","s":"0x5bf0089a3c060ba787b67a205b44e1065a0d11d132b41737ab9adf0f55066811","transactionIndex":"0x38","value":"0x78f0975742c400","blockHash":"0xf833ed36435c53d63bd7109bb1e85383075534410c14573881bf26d912f46a89","chainId":"0x1","hash":"0x56bdf1b38e23db66e8d1c4014d1e9f690a9217d8a0232489210325fc69e25cf9","v":"0x25","input":"0x","type":"0x0","blockNumber":"0x10b2027","gasPrice":"0x4a817c800","from":"0x97b9d2102a9a65a26e1ee82d59e42d1b73b68689","to":"0xcb513e99c020e9d15a6eafef873fef5d9f078221"} ...

摘出其中一条数据:

javascript
{ "blockNumber": "0x10b2027", "type": "0x2", "accessList": [], "blockHash": "0xf833ed36435c53d63bd7109bb1e85383075534410c14573881bf26d912f46a89", "from": "0xd50521974d62f1fa34b8e81cb742ccf6147d05ff", "gasPrice": "0x32ea2db37", "hash": "0xf8f10f8f473c340b021298feb48d0affe529e8737a309c4cc1902e8989ef0914", "input": "0xa22cb4650000000000000000000000001e0049783f008a0085193e00003d00cd54003c710000000000000000000000000000000000000000000000000000000000000001", "v": "0x0", "value": "0x0", "maxFeePerGas": "0x48a413364", "maxPriorityFeePerGas": "0x5f5e100", "nonce": "0x8", "r": "0x8c1cc36f43b02c9e9e454153588cc9d38757f1da69ec49d3cfdda74ab69e06a8", "s": "0x2f3dd3e5ddf9e5d42c128a8e900026aca7568fa83c68cf332e1328066ee8d03a", "transactionIndex": "0x3a", "chainId": "0x1", "gas": "0x1142d", "to": "0x8c3c0274c33f263f0a55d129cfc8eaa3667a9e8b" }

使用WebSocket协议监听

在发明者量化交易平台,我们使用Dial函数来创建WebSocket连接,可以查看FMZ API文档了解Dial函数。

本章节测试代码在以太坊主网环境运行,由于使用WebSocket协议通信,使用发明者量化实盘测试比较方便。Websocket协议订阅消息为:

javascript
{"jsonrpc": "2.0", "id": 1, "method": "eth_subscribe", "params": ["newPendingTransactions"]}

除了newPendingTransactions,还可以订阅newHeadslogs

收到WebSocket连接推送的数据:

javascript
{ "jsonrpc": "2.0", "method": "eth_subscription", "params": { "subscription": "0x2c5c087b4aa188e008f4747828ef4e61", "result": "0x69c4251cecb814e17cfe7a5ee41742a616f9a4d1bbf245c49b186b1006fd14d3" } }

然后根据其中的:"result": "0x69c4251cecb814e17cfe7a5ee41742a616f9a4d1bbf245c49b186b1006fd14d3",进一步查询transaction。对于一个具体的transaction我们使用以太坊RPC方法eth_getTransactionByHash来查询。

javascript
var ws = null function main () { // {"jsonrpc": "2.0", "id": 1, "method": "eth_subscribe", "params": ["xxxxx"]} , "xxxxx" 是订阅的具体消息 var payload = {"jsonrpc": "2.0", "id": 1, "method": "eth_subscribe", "params": ["newPendingTransactions"]} // wss://mainnet.infura.io/ws/v3/xxxxx , "xxxxx" 是你的infura key var infuraKey = "your key" ws = Dial("wss://mainnet.infura.io/ws/v3/" + infuraKey + "|reconnect=true&payload=" + JSON.stringify(payload)) if (!ws) { throw "websocket链接infura失败!" } // eth_getTransactionByHash 调用计数 var getTransactionCounter = 0 var beginTS = new Date().getTime() // 循环获取消息 while (true) { // 接收推送的消息 var data = ws.read() if (data) { var ts = new Date().getTime() if (ts - beginTS >= 1000) { getTransactionCounter = 0 beginTS = ts } // 根据txHash查询交易详情 if (ts - beginTS < 1000 && getTransactionCounter >= 100) { Sleep(1000) getTransactionCounter = 0 beginTS = ts } var obj = JSON.parse(data) if (obj["params"] && obj["params"]["result"]) { var transcationInfo = exchange.IO("api", "eth", "eth_getTransactionByHash", obj["params"]["result"]) Log(obj["params"]["result"], "transcationInfo:", transcationInfo) } getTransactionCounter++ } LogStatus(_D()) } } function onexit() { Log("断开WS连接") ws.close() }

创建实盘运行以上代码,可以收到WebSocket连接推送的数据,数据在不停的推送,我们摘出其中一条transaction

javascript
{ "maxPriorityFeePerGas": "0x5f5e100", "nonce": "0x1a9", "accessList": [], "blockNumber": "0x10b1c9f", "from": "0x5888700be02f52c8adf85890886ef84a6b8a7829", "blockHash": "0x92c3d77ea218cdc0967ab74b6005bb393b92355047f206c7e2d59d41828e7fa9", "chainId": "0x1", "gasPrice": "0x34fdbf43d", "s": "0x7d86ae29a786a61b9e74a7a9e2cc4b39b7913aa3d4c3816ccb07528fed82048a", "to": "0xfc2068c3d47b575a60f6a4a7bf60dea0ac368e01", "type": "0x2", "v": "0x1", "value": "0x0", "gas": "0x1aad3", "hash": "0x2c77c0704aefbb26db460cbb71efdb488df968ad53d2c2b3f1e1172056b40b22", "input": "0x42842e0e0000000000000000000000005888700be02f52c8adf85890886ef84a6b8a7829000000000000000000000000d2d07e4d1bb0f40ac3e4aa7cc3ad05d348bfd2c3000000000000000000000000000000000000000000000000000000000000180b", "maxFeePerGas": "0x4712d1273", "r": "0x8ec58f95f6d9729a6eee075e6976658b6c5346cbc90eb68ac361a40af073b10e", "transactionIndex": "0xc1" }

节选(省略一部分)的日志数据:

run
2023-06-18 16:20:07 信息 断开WS连接 2023-06-18 16:20:07 信息 0xba07ca903f9eafbfa7d494bb26197713034b9ca2dd3c19bc0898af3f35b59343 transcationInfo: {"accessList":[],"from":"0xe2977d60182da068dfd78693f96362ee7a2e9644","nonce":"0xf","value":"0x0","blockHash":"0x92c3d77ea218cdc0967ab74b6005bb393b92355047f206c7e2d59d41828e7fa9","blockNumber":"0x10b1c9f","chainId":"0x1","hash":"0xba07ca903f9eafbfa7d494bb26197713034b9ca2dd3c19bc0898af3f35b59343","maxFeePerGas":"0x530c30b70","r":"0xf28bfdf372a5401a2e00675c6ebe8d5e73f2c955db44b1aa56240b9197d6cbc7","type":"0x2","v":"0x0","gas":"0x21079","gasPrice":"0x367b3783d","input":"0x657bb1130000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000012000000000000000000000000000000000000000000000000000000000000001e000000000000000000000000000000000000000000000000000000000000002200000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000001e0300000000000000000000000033c6eec1723b12c46732f7ab41398de45641fa42000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000041976bd7d021a5b94cbba72b291093b50a0ecf21d1c6cd8193fbfcd685c4723ce068feb249bdcace58c28eb3b6cc647e8c839b0826c84f8dfe4c31d57d1ac1f0111b00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000648ebef50000000000000000000000000000000000000000000000000000000000000000","maxPriorityFeePerGas":"0x1dcd6500","s":"0x71d51246bb60e792f963a3c75c46fd8f557921ce6face7224c944e1768a76ca","to":"0x0b51eb9d0e54c562fedc07ceba453f05b70c4b79","transactionIndex":"0x40"} 2023-06-18 16:20:07 信息 0x2c77c0704aefbb26db460cbb71efdb488df968ad53d2c2b3f1e1172056b40b22 transcationInfo: {"maxPriorityFeePerGas":"0x5f5e100","nonce":"0x1a9","accessList":[],"blockNumber":"0x10b1c9f","from":"0x5888700be02f52c8adf85890886ef84a6b8a7829","blockHash":"0x92c3d77ea218cdc0967ab74b6005bb393b92355047f206c7e2d59d41828e7fa9","chainId":"0x1","gasPrice":"0x34fdbf43d","s":"0x7d86ae29a786a61b9e74a7a9e2cc4b39b7913aa3d4c3816ccb07528fed82048a","to":"0xfc2068c3d47b575a60f6a4a7bf60dea0ac368e01","type":"0x2","v":"0x1","value":"0x0","gas":"0x1aad3","hash":"0x2c77c0704aefbb26db460cbb71efdb488df968ad53d2c2b3f1e1172056b40b22","input":"0x42842e0e0000000000000000000000005888700be02f52c8adf85890886ef84a6b8a7829000000000000000000000000d2d07e4d1bb0f40ac3e4aa7cc3ad05d348bfd2c3000000000000000000000000000000000000000000000000000000000000180b","maxFeePerGas":"0x4712d1273","r":"0x8ec58f95f6d9729a6eee075e6976658b6c5346cbc90eb68ac361a40af073b10e","transactionIndex":"0xc1"} 2023-06-18 16:20:07 信息 0xbc42d5db10e5cb2e888c76005c522cb2474a0c0a7325feb867b618f69ff26f2a transcationInfo: {"accessList":[],"blockNumber":"0x10b1c9f","gas":"0x1cc12b","hash":"0xbc42d5db10e5cb2e888c76005c522cb2474a0c0a7325feb867b618f69ff26f2a","maxFeePerGas":"0x6ab262e5c","value":"0x0","v":"0x1","chainId":"0x1","from":"0xc1b634853cb333d3ad8663715b08f41a3aec47cc","input":"0x8f111f3c000000000000000000000000000000000000000000000000000000000003b83700000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000e0fa2000000000000000000000000e64a54e2533fd126c2e452c5fab544d80e2e4eb50000000000000000000000000000000000000000000000000000000004c6ff1c0000000000000000000000000000000000000000000000000000000004c70029000000000000000000000000000000000000000000000000000000000001822d005b1979341221e80ed20b20d832de88a8a4b535fe9990a90c165f3c95ad085ab9445c0a998c70edff76f1c2de3f4263d7e4fe3c3fb73fe7dcfbdede92371842fb883267f5408c8aaf08ba2f6c22463f19da98183d2302735615460d7380d6f9ff5e764e75bcaca9a93946cf644cd4d4448f314c4cf60cd0353f085aa0562d70e16a510b8bc4c2a09b5e7fafcd43f07dc1b5dd1782962af8f6fff7a6965bfc127e11501a72c64913d58e624333f9ec51687c7cb1bb4a9850541f1e03b2790ed4ee508052910dfe22542d900548d5243ca238811427491d49e98cf269ccab5b1724f0f9698120e406c00910c4090c0e84e0400e2706822d2a001a3964a0ca8101700a547342c2c1fff8934a988416f020a0c98f0909c7f529875f8443914e10b58145c79d38914d1fafbc9ee57ebcb377e4ac1cd252bdebe3c59e8e917fea7dbc7bf66dfc1846482a858645b95555b3ecc9ab4f9e2b0e3e78d68379b009e606a1cefe675670a5eabd5f5a2efa5d77a1084288480c98d01c70a3d8c6b854496e2a966dc9051b13b872b7c6c2c5d82676fd8e82c680514333db21db2006d23f42074021de7e61c54d88b01824d40f03d1505eb6ec6d0cb7ccd38deb821517a5e63d0e89f6bf0385f109c81ea36dd00e7a903a100290f5b47a940ed146ae9338ff8bc17a2b5bc457614d0831e743e485c0de84636b034400bf6bd192ff723045cc170e109aabf273dc9de19c9987038515b6613249f471f9ddeb31331cc1643902212d20241c417532ad7e4a9ac742b4b5f68e1019795cf9386dcf36037502c13ff51f50a2202b2c1cac1c0b38a21ec798deff778c9a6b679d16d0521d2df89c439f4f8f9425ed378f4194d03d00 2023-06-18 16:20:06 信息 0xff0945c3d682a37e18ee433d56c8bedbb93d9ac368af968ed8d53b655575e8e5 transcationInfo: {"gas":"0x5208","s":"0x63572e1fa060841b939cea0849154e55781fe0efcbdfe5ce6979b44ce0980e4a","transactionIndex":"0xa7","value":"0x113e9d515e400","blockHash":"0x92c3d77ea218cdc0967ab74b6005bb393b92355047f206c7e2d59d41828e7fa9","hash":"0xff0945c3d682a37e18ee433d56c8bedbb93d9ac368af968ed8d53b655575e8e5","nonce":"0x2","r":"0x698fe26331ad39ba89c4d30985b707792ea4ab09b25205727f8fac2a6120b54a","gasPrice":"0x35458af00","from":"0x228d93af92d03184c07aa9e39b3d2d61b666686d","input":"0x","to":"0x0246177b98a5e42835cdcfaac1c274d3e6c39486","v":"0x26","blockNumber":"0x10b1c9f","type":"0x0","chainId":"0x1"} ...

解码交易详情

上一讲课程我们编写了一个监控以太坊上未决交易的监控程序,通过WebSocket协议获取推送的交易哈希,然后根据交易哈希查询具体交易详情。

接下来我们要对交易详情数据中的input字段数据做进一步解析。input字段数据看似是杂乱无章的十六进制数据,实际上编码了这笔交易的内容:包括调用的函数,以及输入的参数等。

经过反复、大量测试发现WebSocket连接推送过来的数据时效性、数量与当前使用的RPC节点有很大关系,两个不同的RPC节点服务(例如infura、ALCHEMY)同时创建WebSocket连接,收到的推送数据也不是完全一样的,由于当前这个场景会产生大量请求,所以还是需要使用比较稳定、速度较快的RPC服务。WebSocket连接还会推送过来很多已经Pending很长时间的交易哈希,当使用eth_getTransactionByHash去查询时往往得到一个空值(在FMZ上测试、node.js测试)。

我们这次使用alchemy的RPC节点:wss://eth-mainnet.g.alchemy.com/v2/oKmOQKbneVkxgHZfibs-iFhIlIAl6HDN。这个节点即支持WebSocket协议,也支持REST协议。

我们监控Uniswap去中心化交易所的路由智能合约的multicall(uint256,bytes[])方法,所以首先我们需要计算这个方法的函数签名哈希。

// 取完整哈希值的前8个字符 // multicall: 0x5ae401dc var sigHash = "0x" + Encode("keccak256", "string", "hex", "multicall(uint256,bytes[])").slice(0, 8)

基于上节课程的例子,我们做了一些修改。在接收WebSocket连接推送的消息时,使用var data = ws.read(-2)方式接收最新数据,read()函数参数设置为-2表示立即返回最新数据。我们只关心包含multicall调用的Transaction ,使用if (tx && tx.input.indexOf(sigHash) !== -1)判断过滤。

需要设计2个自定义函数:

  • calcAllFuncSigHash(): 基于ABI计算所有方法的签名哈希。
  • decodeCall(): 解码函数。

接下来检测到multicall调用时,就可以开始解码操作了,第一次解码出multicall方法的参数:deadlinedatadeadline就是一个时间戳比较好理解,data又是一个编码过的calldata,所以还需要继续使用decodeCall()函数解码。

完整的实现例子:

javascript
var ws = null var arrLog = [] const ABI_Route = '[{"inputs":[{"internalType":"address","name":"_factoryV2","type":"address"},{"internalType":"address","name":"factoryV3","type":"address"},{"internalType":"address","name":"_positionManager","type":"address"},{"internalType":"address","name":"_WETH9","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"WETH9","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"}],"name":"approveMax","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"}],"name":"approveMaxMinusOne","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"}],"name":"approveZeroThenMax","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"}],"name":"approveZeroThenMaxMinusOne","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"bytes","name":"data","type":"bytes"}],"name":"callPositionManager","outputs":[{"internalType":"bytes","name":"result","type":"bytes"}],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"bytes[]","name":"paths","type":"bytes[]"},{"internalType":"uint128[]","name":"amounts","type":"uint128[]"},{"internalType":"uint24","name":"maximumTickDivergence","type":"uint24"},{"internalType":"uint32","name":"secondsAgo","type":"uint32"}],"name":"checkOracleSlippage","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes","name":"path","type":"bytes"},{"internalType":"uint24","name":"maximumTickDivergence","type":"uint24"},{"internalType":"uint32","name":"secondsAgo","type":"uint32"}],"name":"checkOracleSlippage","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"components":[{"internalType":"bytes","name":"path","type":"bytes"},{"internalType":"address","name":"recipient","type":"address"},{"internalType":"uint256","name":"amountIn","type":"uint256"},{"internalType":"uint256","name":"amountOutMinimum","type":"uint256"}],"internalType":"struct IV3SwapRouter.ExactInputParams","name":"params","type":"tuple"}],"name":"exactInput","outputs":[{"internalType":"uint256","name":"amountOut","type":"uint256"}],"stateMutability":"payable","type":"function"},{"inputs":[{"components":[{"internalType":"address","name":"tokenIn","type":"address"},{"internalType":"address","name":"tokenOut","type":"address"},{"internalType":"uint24","name":"fee","type":"uint24"},{"internalType":"address","name":"recipient","type":"address"},{"internalType":"uint256","name":"amountIn","type":"uint256"},{"internalType":"uint256","name":"amountOutMinimum","type":"uint256"},{"internalType":"uint160","name":"sqrtPriceLimitX96","type":"uint160"}],"internalType":"struct IV3SwapRouter.ExactInputSingleParams","name":"params","type":"tuple"}],"name":"exactInputSingle","outputs":[{"internalType":"uint256","name":"amountOut","type":"uint256"}],"stateMutability":"payable","type":"function"},{"inputs":[{"components":[{"internalType":"bytes","name":"path","type":"bytes"},{"internalType":"address","name":"recipient","type":"address"},{"internalType":"uint256","name":"amountOut","type":"uint256"},{"internalType":"uint256","name":"amountInMaximum","type":"uint256"}],"internalType":"struct IV3SwapRouter.ExactOutputParams","name":"params","type":"tuple"}],"name":"exactOutput","outputs":[{"internalType":"uint256","name":"amountIn","type":"uint256"}],"stateMutability":"payable","type":"function"},{"inputs":[{"components":[{"internalType":"address","name":"tokenIn","type":"address"},{"internalType":"address","name":"tokenOut","type":"address"},{"internalType":"uint24","name":"fee","type":"uint24"},{"internalType":"address","name":"recipient","type":"address"},{"internalType":"uint256","name":"amountOut","type":"uint256"},{"internalType":"uint256","name":"amountInMaximum","type":"uint256"},{"internalType":"uint160","name":"sqrtPriceLimitX96","type":"uint160"}],"internalType":"struct IV3SwapRouter.ExactOutputSingleParams","name":"params","type":"tuple"}],"name":"exactOutputSingle","outputs":[{"internalType":"uint256","name":"amountIn","type":"uint256"}],"stateMutability":"payable","type":"function"},{"inputs":[],"name":"factory","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"factoryV2","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"getApprovalType","outputs":[{"internalType":"enum IApproveAndCall.ApprovalType","name":"","type":"uint8"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"components":[{"internalType":"address","name":"token0","type":"address"},{"internalType":"address","name":"token1","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"uint256","name":"amount0Min","type":"uint256"},{"internalType":"uint256","name":"amount1Min","type":"uint256"}],"internalType":"struct IApproveAndCall.IncreaseLiquidityParams","name":"params","type":"tuple"}],"name":"increaseLiquidity","outputs":[{"internalType":"bytes","name":"result","type":"bytes"}],"stateMutability":"payable","type":"function"},{"inputs":[{"components":[{"internalType":"address","name":"token0","type":"address"},{"internalType":"address","name":"token1","type":"address"},{"internalType":"uint24","name":"fee","type":"uint24"},{"internalType":"int24","name":"tickLower","type":"int24"},{"internalType":"int24","name":"tickUpper","type":"int24"},{"internalType":"uint256","name":"amount0Min","type":"uint256"},{"internalType":"uint256","name":"amount1Min","type":"uint256"},{"internalType":"address","name":"recipient","type":"address"}],"internalType":"struct IApproveAndCall.MintParams","name":"params","type":"tuple"}],"name":"mint","outputs":[{"internalType":"bytes","name":"result","type":"bytes"}],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"previousBlockhash","type":"bytes32"},{"internalType":"bytes[]","name":"data","type":"bytes[]"}],"name":"multicall","outputs":[{"internalType":"bytes[]","name":"","type":"bytes[]"}],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint256","name":"deadline","type":"uint256"},{"internalType":"bytes[]","name":"data","type":"bytes[]"}],"name":"multicall","outputs":[{"internalType":"bytes[]","name":"","type":"bytes[]"}],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"bytes[]","name":"data","type":"bytes[]"}],"name":"multicall","outputs":[{"internalType":"bytes[]","name":"results","type":"bytes[]"}],"stateMutability":"payable","type":"function"},{"inputs":[],"name":"positionManager","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"value","type":"uint256"}],"name":"pull","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[],"name":"refundETH","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"value","type":"uint256"},{"internalType":"uint256","name":"deadline","type":"uint256"},{"internalType":"uint8","name":"v","type":"uint8"},{"internalType":"bytes32","name":"r","type":"bytes32"},{"internalType":"bytes32","name":"s","type":"bytes32"}],"name":"selfPermit","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"nonce","type":"uint256"},{"internalType":"uint256","name":"expiry","type":"uint256"},{"internalType":"uint8","name":"v","type":"uint8"},{"internalType":"bytes32","name":"r","type":"bytes32"},{"internalType":"bytes32","name":"s","type":"bytes32"}],"name":"selfPermitAllowed","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"nonce","type":"uint256"},{"internalType":"uint256","name":"expiry","type":"uint256"},{"internalType":"uint8","name":"v","type":"uint8"},{"internalType":"bytes32","name":"r","type":"bytes32"},{"internalType":"bytes32","name":"s","type":"bytes32"}],"name":"selfPermitAllowedIfNecessary","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"value","type":"uint256"},{"internalType":"uint256","name":"deadline","type":"uint256"},{"internalType":"uint8","name":"v","type":"uint8"},{"internalType":"bytes32","name":"r","type":"bytes32"},{"internalType":"bytes32","name":"s","type":"bytes32"}],"name":"selfPermitIfNecessary","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint256","name":"amountIn","type":"uint256"},{"internalType":"uint256","name":"amountOutMin","type":"uint256"},{"internalType":"address[]","name":"path","type":"address[]"},{"internalType":"address","name":"to","type":"address"}],"name":"swapExactTokensForTokens","outputs":[{"internalType":"uint256","name":"amountOut","type":"uint256"}],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint256","name":"amountOut","type":"uint256"},{"internalType":"uint256","name":"amountInMax","type":"uint256"},{"internalType":"address[]","name":"path","type":"address[]"},{"internalType":"address","name":"to","type":"address"}],"name":"swapTokensForExactTokens","outputs":[{"internalType":"uint256","name":"amountIn","type":"uint256"}],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"amountMinimum","type":"uint256"},{"internalType":"address","name":"recipient","type":"address"}],"name":"sweepToken","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"amountMinimum","type":"uint256"}],"name":"sweepToken","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"amountMinimum","type":"uint256"},{"internalType":"uint256","name":"feeBips","type":"uint256"},{"internalType":"address","name":"feeRecipient","type":"address"}],"name":"sweepTokenWithFee","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"amountMinimum","type":"uint256"},{"internalType":"address","name":"recipient","type":"address"},{"internalType":"uint256","name":"feeBips","type":"uint256"},{"internalType":"address","name":"feeRecipient","type":"address"}],"name":"sweepTokenWithFee","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"int256","name":"amount0Delta","type":"int256"},{"internalType":"int256","name":"amount1Delta","type":"int256"},{"internalType":"bytes","name":"_data","type":"bytes"}],"name":"uniswapV3SwapCallback","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"amountMinimum","type":"uint256"},{"internalType":"address","name":"recipient","type":"address"}],"name":"unwrapWETH9","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint256","name":"amountMinimum","type":"uint256"}],"name":"unwrapWETH9","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint256","name":"amountMinimum","type":"uint256"},{"internalType":"address","name":"recipient","type":"address"},{"internalType":"uint256","name":"feeBips","type":"uint256"},{"internalType":"address","name":"feeRecipient","type":"address"}],"name":"unwrapWETH9WithFee","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint256","name":"amountMinimum","type":"uint256"},{"internalType":"uint256","name":"feeBips","type":"uint256"},{"internalType":"address","name":"feeRecipient","type":"address"}],"name":"unwrapWETH9WithFee","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint256","name":"value","type":"uint256"}],"name":"wrapETH","outputs":[],"stateMutability":"payable","type":"function"},{"stateMutability":"payable","type":"receive"}]' function calcAllFuncSigHash(jsonABI) { var mapSigHash = {} for (var i in jsonABI) { var ele = jsonABI[i] if (typeof(ele["name"]) != "undefined") { if (ele["inputs"]) { var funcName = ele["name"] if (ele["inputs"].length == 0) { var methodId = "0x" + Encode("keccak256", "string", "hex", funcName + "()").slice(0, 8) mapSigHash[methodId] = {"argsTypeList": [], "argsNameList": [], "funcName": funcName} } else { var arr = [] var arrName = [] var argPrototype = [] for (var j in ele["inputs"]) { var inputType = ele["inputs"][j]["type"] if (inputType == "tuple") { var components = ele["inputs"][j]["components"] var tupleType = [] var protoType = [] for (var componentsIdx = 0; componentsIdx < components.length; componentsIdx++) { tupleType.push(components[componentsIdx]["type"]) protoType.push(components[componentsIdx]["name"] + " " + components[componentsIdx]["type"]) } arr.push("(" + tupleType.join() + ")") arrName.push(ele["inputs"][j]["name"]) // 原型 argPrototype.push("tuple" + "(" + protoType.join() + ")") } else { arr.push(inputType) arrName.push(ele["inputs"][j]["name"]) // 原型 argPrototype.push(inputType) } } var functionSignature = funcName + "(" + arr.join() + ")" var methodId = "0x" + Encode("keccak256", "string", "hex", functionSignature).slice(0, 8) mapSigHash[methodId] = {"argsTypeList": arr, "argsNameList": arrName, "funcName": funcName, "argPrototype": argPrototype} } } } } return mapSigHash } function decodeCall(input, abi) { var mapSigHash = calcAllFuncSigHash(JSON.parse(abi)) var methodId = input.slice(0, 10) var data = input.slice(10) var decodedArgs = {} var infoMethod = mapSigHash[methodId] if (typeof(infoMethod) == "undefined") { return [methodId, mapSigHash] } var arr = [] for (var i = 0; i < infoMethod["argsTypeList"].length; i++) { if (infoMethod["argsTypeList"][i].startsWith("(")) { arr.push(infoMethod["argPrototype"][i]) } else { arr.push(infoMethod["argsTypeList"][i]) } } if (arr.length == 0) { return {"funcName": infoMethod["funcName"], "args": decodedArgs} } var args = exchange.IO("decode", arr.join(), data) if (!Array.isArray(args)) { args = [args] } if (args.length != infoMethod["argsNameList"].length) { Log("args:", args) Log("infoMethod:", infoMethod) throw "解码后的args与argsNameList不等" } for (var i = 0; i < infoMethod["argsNameList"].length; i++) { var key = infoMethod["argsNameList"][i] var value = args[i] decodedArgs[key] = value } return {"funcName": infoMethod["funcName"], "args": decodedArgs} } function main () { // {"jsonrpc": "2.0", "id": 1, "method": "eth_subscribe", "params": ["xxxxx"]} , "xxxxx" 是订阅的具体消息 var payload = {"jsonrpc": "2.0", "id": 1, "method": "eth_subscribe", "params": ["newPendingTransactions"]} // 使用alchemy服务 ws = Dial("wss://eth-mainnet.g.alchemy.com/v2/oKmOQKbneVkxgHZfibs-iFhIlIAl6HDN" + "|reconnect=true&payload=" + JSON.stringify(payload)) if (!ws) { throw "websocket链接alchemy失败!" } // eth_getTransactionByHash 调用计数 var getTransactionCounter = 0 // 起始时间戳 var beginTS = new Date().getTime() // 计算函数签名哈希 var sigHash = "0x" + Encode("keccak256", "string", "hex", "multicall(uint256,bytes[])").slice(0, 8) Log("sigHash:", sigHash) // 循环获取消息 while (true) { var msg = "" var recv = null // 接收推送的消息,使用read参数-2,立即返回最新数据 var data = ws.read(-2) if (data && data != "") { var ts = new Date().getTime() if (ts - beginTS >= 1000) { getTransactionCounter = 0 beginTS = ts } // 根据txHash查询交易详情 if (ts - beginTS < 1000 && getTransactionCounter >= 100) { Sleep(1000) getTransactionCounter = 0 beginTS = ts } var obj = JSON.parse(data) if (obj["params"] && obj["params"]["result"]) { var txHash = obj["params"]["result"] var tx = exchange.IO("api", "eth", "eth_getTransactionByHash", txHash) if (tx && tx.input.indexOf(sigHash) !== -1) { // 解码交易详情 arrLog = [] var decodedInput = decodeCall(tx.input, ABI_Route) // Log("----------------", txHash, "/", decodedInput["funcName"], "----------------", "#FF0000") arrLog.push("----------------" + txHash + "/" + decodedInput["funcName"] + "----------------" + "#FF0000") arrLog.push(tx.from + " -> " + tx.to) for (var i = 0; i < decodedInput["args"]["data"].length; i++) { var calldata = "0x" + decodedInput["args"]["data"][i] var decodedCalldata = decodeCall(calldata, ABI_Route) // Log("----------------", decodedCalldata["funcName"], "----------------", "#FF0000") arrLog.push("----------------" + decodedCalldata["funcName"] + "----------------" + "#FF0000") for (var key in decodedCalldata["args"]) { // Log(key, decodedCalldata["args"][key]) arrLog.push(key + ": " + JSON.stringify(decodedCalldata["args"][key])) } } // 输出日志 for (var logIdx = arrLog.length - 1; logIdx >= 0; logIdx--) { Log(arrLog[logIdx]) } } getTransactionCounter++ } recv = obj } else if (data == null) { msg = "缓冲区队列空了,时间:" + _D() } LogStatus(_D(), ", msg:", msg, ", recv:", recv) } } function onexit() { Log("断开WS连接") ws.close() } function onerror() { Log("断开WS连接") ws.close() for (var logIdx = arrLog.length - 1; logIdx >= 0; logIdx--) { Log(arrLog[logIdx]) } }

创建实盘运行测试:

run
2023-06-20 17:01:00 信息 ----------------0x5288a7bd6e0f57162ca763df722de73793e542734d7d2b7af5755664e2e67910/multicall---------------- 2023-06-20 17:01:00 信息 0x851b594033d57c98af753bcb3a7d0237a615de32 -> 0x68b3465833fb72a70ecdf485e0e4c7bd8665fc45 2023-06-20 17:01:00 信息 ----------------exactInputSingle---------------- 2023-06-20 17:01:00 信息 params: {"tokenOut":"0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2","fee":"10000","recipient":"0x0000000000000000000000000000000000000002","amountIn":"8952087000296027130940868","amountOutMinimum":"41638694112306829","sqrtPriceLimitX96":"0","tokenIn":"0xe1283567345349942acdfad3692924a1b16cf3cc"} 2023-06-20 17:01:00 信息 ----------------unwrapWETH9---------------- 2023-06-20 17:01:00 信息 amountMinimum: "41638694112306829" 2023-06-20 17:01:00 信息 recipient: "0x851b594033d57c98af753bcb3a7d0237a615de32" 2023-06-20 16:59:03 信息 ----------------0x55e0c4a38a17d3aa6e8f558a66c77e9defa9f8f6e347536363ac1b921de9aaf3/multicall---------------- 2023-06-20 16:59:03 信息 0x27457ada2dd725c7d0f28e1737bdd0bf583c0f0b -> 0x68b3465833fb72a70ecdf485e0e4c7bd8665fc45 2023-06-20 16:59:03 信息 ----------------swapExactTokensForTokens---------------- 2023-06-20 16:59:03 信息 amountIn: "816769666850161" 2023-06-20 16:59:03 信息 amountOutMin: "40404501509302321" 2023-06-20 16:59:03 信息 path: ["0x7863e06bca47ded821fcb53ab788eeb371243eda","0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2"] 2023-06-20 16:59:03 信息 to: "0x27457ada2dd725c7d0f28e1737bdd0bf583c0f0b" 2023-06-20 16:58:25 信息 sigHash: 0x5ae401dc

截图:

img

可以看到Transaction Hash为0x5288a7bd6e0f57162ca763df722de73793e542734d7d2b7af5755664e2e67910的这笔交易,input data数据包含的调用是一个multicall方法的调用。这笔交易发送方向是:0x851b594033d57c98af753bcb3a7d0237a615de32 -> 0x68b3465833fb72a70ecdf485e0e4c7bd8665fc45。0x68b3465833fb72a70ecdf485e0e4c7bd8665fc45即Uniswap的路由合约地址。

img

解析出了multicall打包调用了合约的exactInputSingleunwrapWETH9方法、以及这些方法的具体参数。

----------------exactInputSingle---------------- params: { "tokenOut":"0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", "fee":"10000", "recipient":"0x0000000000000000000000000000000000000002", "amountIn":"8952087000296027130940868", "amountOutMinimum":"41638694112306829", "sqrtPriceLimitX96":"0", "tokenIn":"0xe1283567345349942acdfad3692924a1b16cf3cc" } ----------------unwrapWETH9---------------- amountMinimum: "41638694112306829" recipient: "0x851b594033d57c98af753bcb3a7d0237a615de32"

有兴趣的同学可以基于此例子修改、扩展,监控更多的交易、解析这些链上操作。

Comment
All comments (0)
No data
No data
  • 1
iPhone Download
Forums
PINE Language
© 2015 - ∞ INVENTOR PTE LTD (SG)