以太坊
在发明者量化交易平台,通过exchange.IO()函数编写策略代码,实现以太坊链上RPC方法调用和智能合约交互。
Web3交易所对象配置
需要在发明者量化交易平台上配置接入节点。接入节点可以是自建节点或使用第三方服务,例如:infura。在发明者量化交易平台的「交易所」页面,选择协议:加密货币,然后选择交易所为Web3。
配置Rpc Address(接入节点的服务地址)和Private Key(私钥)。支持私钥本地化部署,详情请参考「密钥安全性」。
注册ABI
调用合约时,如果使用标准的ERC20方法,则无需注册即可直接调用。调用标准合约以外的方法需要先注册ABI内容:exchange.IO("abi", tokenAddress, abiContent)。
获取合约的ABI内容可以通过以下URL获取,仅需提取result字段。
url
https://api.etherscan.io/api?module=contract&action=getabi&address=0x68b3465833fb72A70ecDF485E0e4C7bD8665Fc45
调用以太坊RPC方法
使用exchange.IO()函数调用以太坊RPC方法。
- 查询钱包中ETH余额exchange.IO("api", "eth", "eth_getBalance", owner, "latest") // owner为具体的钱包地址
- ETH转账exchange.IO("api", "eth", "send", toAddress, toAmount) // toAddress为接收ETH的钱包地址,toAmount为转账数量
- 查询Gas价格exchange.IO("api", "eth", "eth_gasPrice")
- 查询预估Gas费用exchange.IO("api", "eth", "eth_estimateGas", data)
支持encode
exchange.IO()函数封装了encode方法,可以将函数调用编码并返回为hex字符串格式。具体使用方法可参考平台公开的「Uniswap V3 交易类库」模板。
以下示例展示了如何编码unwrapWETH9方法的调用:
javascript
function main() {
// ContractV3SwapRouterV2 主网地址:0x68b3465833fb72A70ecDF485E0e4C7bD8665Fc45
// 调用unwrapWETH9方法需要先注册ABI,此处省略注册步骤
// "owner"代表钱包地址,需要填写实际地址,1代表解包装数量,将一个WETH解包装为ETH
var data = exchange.IO("encode", "0x68b3465833fb72A70ecDF485E0e4C7bD8665Fc45", "unwrapWETH9(uint256,address)", 1, "owner")
Log(data)
}
在调用exchange.IO("encode", ...)函数时,如果第二个参数(字符串类型)以0x开头,表示编码(encode)智能合约上的方法调用。
如果不以0x开头,则用于编码指定的类型顺序,功能等同于solidity中的abi.encode,请参考以下示例。
javascript
function main() {
var x = 10
var address = "0x02a5fBb259d20A3Ad2Fdf9CCADeF86F6C1c1Ccc9"
var str = "Hello World"
var array = [1, 2, 3]
var ret = exchange.IO("encode", "uint256,address,string,uint256[]", x, address, str, array) // uint 即 uint256,FMZ上需要指定类型长度
Log("ret:", ret)
/*
000000000000000000000000000000000000000000000000000000000000000a // x
00000000000000000000000002a5fbb259d20a3ad2fdf9ccadef86f6c1c1ccc9 // address
0000000000000000000000000000000000000000000000000000000000000080 // str 的偏移
00000000000000000000000000000000000000000000000000000000000000c0 // array 的偏移
000000000000000000000000000000000000000000000000000000000000000b // str 的长度
48656c6c6f20576f726c64000000000000000000000000000000000000000000 // str 数据
0000000000000000000000000000000000000000000000000000000000000003 // array 的长度
0000000000000000000000000000000000000000000000000000000000000001 // array 第一个数据
0000000000000000000000000000000000000000000000000000000000000002 // array 第二个数据
0000000000000000000000000000000000000000000000000000000000000003 // array 第三个数据
*/
}
支持对元组(tuple)或包含元组的类型顺序进行编码:
javascript
function main() {
var types = "tuple(a uint256,b uint8,c address),bytes"
var ret = exchange.IO("encode", types, {
a: 30,
b: 20,
c: "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2"
}, "0011")
Log("encode: ", ret)
}
该类型顺序由tuple、bytes组成,因此在调用exchange.IO()函数进行encode时需要传入两个参数:
- 对应tuple类型的变量:
传入的参数必须与json{ a: 30, b: 20, c: "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2" }tuple的结构、类型保持一致,如types参数中定义的形式:tuple(a uint256,b uint8,c address)。 - 对应bytes类型的变量:string"0011"
支持对数组或包含数组的类型顺序进行编码:
javascript
function main() {
var path = ["0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", "0xdac17f958d2ee523a2206206994597c13d831ec7"] // ETH address, USDT address
var ret = exchange.IO("encode", "address[]", path)
Log("encode: ", ret)
}
支持encodePacked
例如在Uniswap V3这个去中心化交易所的方法调用时,需要传入兑换路径等参数,就需要使用encodePacked操作:
javascript
function main() {
var fee = exchange.IO("encodePacked", "uint24", 3000)
var tokenInAddress = "0x111111111117dC0aa78b770fA6A738034120C302"
var tokenOutAddress = "0x6b175474e89094c44da98b954eedeac495271d0f"
var path = tokenInAddress.slice(2).toLowerCase()
path += fee + tokenOutAddress.slice(2).toLowerCase()
Log("path:", path)
}
支持decode
对于数据的处理不仅支持编码(encode),还支持解码(decode)。使用exchange.IO("decode", types, rawData)函数进行解码操作。
javascript
function main() {
// register SwapRouter02 abi
var walletAddress = "0x398a93ca23CBdd2642a07445bCD2b8435e0a373f"
var routerAddress = "0x68b3465833fb72A70ecDF485E0e4C7bD8665Fc45"
var abi = `[{"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"}]`
exchange.IO("abi", routerAddress, abi) // 此处ABI仅包含exactOutput方法的内容,完整的ABI可以在网上搜索获取
// encode path
var fee = exchange.IO("encodePacked", "uint24", 3000)
var tokenInAddress = "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2"
var tokenOutAddress = "0xdac17f958d2ee523a2206206994597c13d831ec7"
var path = tokenInAddress.slice(2).toLowerCase()
path += fee + tokenOutAddress.slice(2).toLowerCase()
Log("path:", path)
var dataTuple = {
"path" : path,
"recipient" : walletAddress,
"amountOut" : 1000,
"amountInMaximum" : 1,
}
// encode SwapRouter02 exactOutput
var rawData = exchange.IO("encode", routerAddress, "exactOutput", dataTuple)
Log("method hash:", rawData.slice(0, 8)) // 09b81346
Log("params hash:", rawData.slice(8))
// decode exactOutput params
var decodeRaw = exchange.IO("decode", "tuple(path bytes,recipient address,amountOut uint256,amountInMaximum uint256)", rawData.slice(8))
Log("decodeRaw:", decodeRaw)
}
该示例首先在处理path参数时执行了encodePacked操作,因为后续需要编码的exactOutput方法调用需要将path作为参数。然后对路由合约的exactOutput方法进行编码,该方法仅有一个参数,参数类型为tuple类型。
exactOutput方法名编码后为:0x09b81346,使用exchange.IO("decode", ...)方法解码得到的decodeRaw与变量dataTuple一致。
支持切换私钥
支持切换私钥,可以操作多个钱包地址,例如:
javascript
function main() {
exchange.IO("key", "Private Key") // "Private Key"代表私钥字符串,需要填写实际的私钥值
}
调用智能合约方法
以下内容是一些智能合约方法的调用示例。
-
decimals
decimals方法是ERC20的一个constant方法(在FMZ量化策略代码中调用标准ERC20方法时无需注册ABI),不会产生gas消耗,可以查询某个token的精度数据。
decimals方法没有参数,返回值为token的精度数据。javascriptfunction main(){ var tokenAddress = "0x111111111117dC0aa78b770fA6A738034120C302" // 代币的合约地址,例子中的代币为1INCH Log(exchange.IO("api", tokenAddress, "decimals")) // 查询,打印1INCH代币的精度指数为18 } -
allowance
allowance方法是ERC20的一个constant方法,不会产生gas消耗,可以查询某个token对某个合约地址的授权额度。
allowance方法需要传入2个参数,第一个参数为钱包地址,第二个参数为被授权的地址。返回值为token的授权额度。javascriptfunction main(){ // 代币的合约地址,例子中的代币为1INCH var tokenAddress = "0x111111111117dC0aa78b770fA6A738034120C302" var owner = "" var spender = "" // 例如查询得出1000000000000000000,除以该token的精度单位1e18,得出当前交易所对象绑定的钱包给spender地址授权了1个1INCH数量 Log(exchange.IO("api", tokenAddress, "allowance", owner, spender)) }owner:钱包地址,实际使用时需要填写具体地址。
spender:被授权的合约地址,实际使用时需要填写具体地址,例如可以是Uniswap V3 router v1地址。 -
approve
approve方法是ERC20的一个非constant方法,会产生gas消耗,用于给某个合约地址授权token的操作额度。
approve方法需要传入2个参数,第一个参数为被授权的地址,第二个参数为授权的额度。返回值为txid。javascriptfunction main(){ // 代币的合约地址,例子中的代币为1INCH var tokenAddress = "0x111111111117dC0aa78b770fA6A738034120C302" var spender = "" var amount = "0xde0b6b3a7640000" // 授权量的十六进制字符串: 0xde0b6b3a7640000 , 对应的十进制字符串: 1e18 , 1e18除以该token的精度单位,即1个代币数量 , 所以这里指授权一个代币 Log(exchange.IO("api", tokenAddress, "approve", spender, amount)) }spender:被授权的合约地址,实际使用时需要填写具体地址,例如可以是Uniswap V3 router v1地址。
amount:授权数量,这里使用的是十六进制字符串表示。对应的十进制数值为1e18,除以示例中的token精度单位(即1e18),得出授权了1个token。exchange.IO()函数的第三个参数传入方法名approve,也可以写成methodId的形式,例如:"0x571ac8b0"。也可以写成完整的标准方法名,例如:"approve(address,uint256)"。 -
multicall
multicall方法是Uniswap V3的一个非constant方法,会产生gas消耗,用于批量兑换代币。
multicall方法可能有多种传参方式,具体可以查询包含该方法的ABI,调用该方法之前需要先注册ABI。返回值为txid。具体的
multicall方法调用示例,可以参考平台公开的「Uniswap V3 交易类库」模板javascriptfunction main() { var ABI_Route = "" var contractV3SwapRouterV2 = "" var value = 0 var deadline = (new Date().getTime() / 1000) + 3600 var data = "" exchange.IO("abi", contractV3SwapRouterV2, ABI_Route) exchange.IO("api", contractV3SwapRouterV2, "multicall(uint256,bytes[])", value, deadline, data) }ABI_Route:Uniswap V3的router v2合约的ABI,需要根据实际情况填写。
contractV3SwapRouterV2:Uniswap V3的router v2地址,实际使用时需要填写具体地址。
value:转账的ETH数量,如果兑换操作的tokenIn代币不是ETH则设置为0,需要根据实际情况填写。
deadline:可以设置为(new Date().getTime() / 1000) + 3600,表示一小时内有效。
data:需要执行的打包操作数据,需要根据实际情况填写。也可以指定方法调用的
gasLimit/gasPrice/nonce设置:javascriptexchange.IO("api", contractV3SwapRouterV2, "multicall(uint256,bytes[])", value, deadline, data, {gasPrice: 5000000000, gasLimit: 21000})可以根据具体需求设置
{gasPrice: 5000000000, gasLimit: 21000, nonce: 100}参数,该参数设置在exchange.IO()函数的最后一个参数上。
可以省略其中的nonce使用系统默认值,或者不设置gasLimit/gasPrice/nonce,全部使用系统默认值。需要注意示例中的
multicall(uint256,bytes[])方法的stateMutability属性是payable,需要传入value参数。
stateMutability":"payable"属性可以从ABI中查看,exchange.IO()函数会根据已注册的ABI中的stateMutability属性判断所需的参数,
如果stateMutability属性是nonpayable则不需要传入value参数。