资源加载中... loading...

以太坊

在发明者量化交易平台,编写策略代码通过exchange.IO()函数实现以太坊链上RPC方法、智能合约的调用。

Web3交易所对象配置

需要在发明者量化交易平台上配置接入节点。接入节点可以是自建节点或者使用第三方的服务,例如:infura。 在发明者量化交易平台的「交易所」页面。选择协议:加密货币,然后交易所选择为Web3。 配置Rpc Address(接入节点的服务地址)和Private Key(私钥)。支持将私钥本地化部署,可以参看「密钥安全性」

注册ABI

调用合约如果是标准ERC20方法,则不需要注册可以直接调用。调用标准合约以外的方法需要注册ABI内容:exchange.IO("abi", tokenAddress, abiContent)。 获取合约的ABI内容可以通过下面URL获取, 只取result字段。

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为数量
    
  • 查询gasPrice
    
    exchange.IO("api", "eth", "eth_gasPrice")
    
  • 查询eth_estimateGas
    
    exchange.IO("api", "eth", "eth_estimateGas", data)
    

支持encode

具体使用可以参考平台公开的[「Uniswap V3 交易类库」模板](https://www.fmz.com/strategy/397260)。
这里使用编码```unwrapWETH9```方法的调用为例子:

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```,参考以下例子。

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)或者包含元组的类型顺序编码:

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类型的变量:

{ a: 30, b: 20, c: “0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2” }

  传入的参数也必须和```tuple```的结构、类型保持一致,如同```types```参数中定义的形式:```tuple(a uint256,b uint8,c address)```。
- 对应bytes类型的变量:

“0011”


支持对数组或者包含数组的类型顺序编码:

function main() { var path = [“0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2”, “0xdac17f958d2ee523a2206206994597c13d831ec7”] // ETH address, USDT address var ret = exchange.IO(“encode”, “address[]”, path) Log(“encode: “, ret) }



### 支持encodePacked

例如在```Uniswap V3```这个DEX的方法调用时,需要传入兑换路径之类的参数,就需要使用```encodePacked```操作:

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)```函数进行```decode```操作。

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```作为参数。
然后```encode```路由合约的```exactOutput```方法,该方法只有一个参数,参数类型是```tuple```类型。
```exactOutput```这个方法名编码后即:```0x09b81346```,使用```exchange.IO("decode", ...)```方法解码得出```decodeRaw```,与变量```dataTuple```一致。


### 支持切换私钥

支持切换私钥,可以操作多个钱包地址,例如:

function main() { exchange.IO(“key”, “Private Key”) // “Private Key”代表私钥字符串,需要具体填写 }



### 调用智能合约方法

以下内容是一些智能合约方法的调用例子。
- decimals
  ```decimals```方法是```ERC20```的一个```constant```的方法(在FMZ量化策略代码中调用标准ERC20方法时不用注册ABI),不会产生```gas```消耗,可以查询某个```token```的精度数据。
  ```decimals```方法没有参数,返回值为```token```的精度数据。  
  
  ```javascript
  function main(){
      var tokenAddress = "0x111111111117dC0aa78b770fA6A738034120C302"    // 代币的合约地址,例子中的代币为1INCH
      Log(exchange.IO("api", tokenAddress, "decimals"))                  // 查询,打印1INCH代币的精度指数为18
  }
  • allowance allowance方法是ERC20的一个constant的方法,不会产生gas消耗,可以查询某个token对于某个合约地址的授权额度。 allowance方法需要传2个参数,第一个参数为钱包地址,第二个参数为被授权的地址。返回值为token的授权额度。
  function main(){
      // 代币的合约地址,例子中的代币为1INCH
      var tokenAddress = "0x111111111117dC0aa78b770fA6A738034120C302"            
      var owner = ""
      var spender = ""
      
      // 例如查询得出1000000000000000000,除以该token的精度单位1e18,得出当前交易所对象绑定的钱包给spender地址授权了1个1INCH数量
      Log(exchange.IO("api", tokenAddress, "allowance", owner, spender))
  }
  ```spender```:被授权的合约地址,实际使用需要具体填写地址,例如可以是```Uniswap V3 router v1```地址。
- approve
  ```approve```方法是```ERC20```的一个非```constant```的方法,会产生```gas```消耗,用来给某个合约地址授权```token```的操作额度。
  ```approve```方法需要传2个参数,第一个参数为被授权的地址,第二个参数为授权的额度。返回值为```txid```。
  
  ```javascript
  function main(){
      // 代币的合约地址,例子中的代币为1INCH
      var tokenAddress = "0x111111111117dC0aa78b770fA6A738034120C302"
      var spender = ""
      var amount = "0xde0b6b3a7640000"
      
      // 授权量的十六进制字符串: 0xde0b6b3a7640000 , 对应的十进制字符串: 1e18 , 1e18除以该token的精度单位,即1个代币数量 , 所以这里指授权一个代币
      Log(exchange.IO("api", tokenAddress, "approve", spender, amount))
  }
  ```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 交易类库」模板](https://www.fmz.com/strategy/397260)
  
  ```javascript
  function 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)
  }
  ```contractV3SwapRouterV2```:Uniswap V3的router v2地址,实际使用需要具体填写地址。
  ```value```:转账的ETH数量,如果兑换操作的```tokenIn```代币不是ETH则设置为0,需要根据实际情况填写。
  ```deadline```:可以设置为```(new Date().getTime() / 1000) + 3600```,表示一小时内有效。
  ```data```:需要执行的打包操作数据,需要根据实际情况填写。

  也可以指定方法调用的```gasLimit/gasPrice/nonce```设置:
  
  ```javascript
  exchange.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```属性是```nonpayable```则不需要传```value```参数。


### 其它功能调用

- 获取交易所对象配置的钱包的地址
  ```javascript
  function main() {
      Log(exchange.IO("address"))         // 打印exchange交易所对象上配置的私钥的钱包地址
  }
  • 切换区块链RPC节点

    function main() {
      var chainRpc = "https://bsc-dataseed.binance.org"
    
    
      // 切换到BSC链
      e.IO("base", chainRpc)
    }
    
JavaScript策略编写说明 内置库