Ethereum
On the FMZ Quant Trading Platform, use the exchange.IO() function to write strategy code to implement Ethereum blockchain RPC method calls and smart contract interactions.
Web3 Exchange Object Configuration
You need to configure access nodes on the FMZ Quant Trading Platform. Access nodes can be self-hosted nodes or third-party services, such as: infura. On the FMZ Quant Trading Platform's "Exchange" page, select protocol: Cryptocurrency, then select exchange as Web3.
Configure Rpc Address (service address of the access node) and Private Key (private key). Supports local deployment of private keys. For details, please refer to "Key Security".
Register ABI
When calling contracts, if using standard ERC20 methods, you can call directly without registration. Calling methods outside of standard contracts requires registering the ABI content first: exchange.IO("abi", tokenAddress, abiContent).
To obtain the contract's ABI content, you can use the following URL and extract only the result field.
url
https://api.etherscan.io/api?module=contract&action=getabi&address=0x68b3465833fb72A70ecDF485E0e4C7bD8665Fc45
Calling Ethereum RPC Methods
Use the exchange.IO() function to call Ethereum RPC methods.
- Query ETH balance in walletjavascriptexchange.IO("api", "eth", "eth_getBalance", owner, "latest") // owner is the specific wallet address
- ETH transferjavascriptexchange.IO("api", "eth", "send", toAddress, toAmount) // toAddress is the wallet address receiving ETH, toAmount is the transfer amount
- Query Gas pricejavascriptexchange.IO("api", "eth", "eth_gasPrice")
- Query estimated Gas feejavascriptexchange.IO("api", "eth", "eth_estimateGas", data)
Support for encode
The exchange.IO() function encapsulates the encode method, which can encode function calls and return them as hex string format. For specific usage, please refer to the platform's public "Uniswap V3 Trading Library" template.
The following example shows how to encode the unwrapWETH9 method call:
javascript
function main() {
// ContractV3SwapRouterV2 mainnet address: 0x68b3465833fb72A70ecDF485E0e4C7bD8665Fc45
// Calling the unwrapWETH9 method requires registering the ABI first, registration steps are omitted here
// "owner" represents the wallet address, you need to fill in the actual address, 1 represents the unwrap amount, unwrapping one WETH to ETH
var data = exchange.IO("encode", "0x68b3465833fb72A70ecDF485E0e4C7bD8665Fc45", "unwrapWETH9(uint256,address)", 1, "owner")
Log(data)
}
When calling the exchange.IO("encode", ...) function, if the second parameter (string type) starts with 0x, it indicates encoding a method call on a smart contract.
If it doesn't start with 0x, it is used to encode the specified type sequence, functionally equivalent to abi.encode in solidity. Please refer to the following example.
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 means uint256, type length must be specified on FMZ
Log("ret:", ret)
/*
000000000000000000000000000000000000000000000000000000000000000a // x
00000000000000000000000002a5fbb259d20a3ad2fdf9ccadef86f6c1c1ccc9 // address
0000000000000000000000000000000000000000000000000000000000000080 // offset of str
00000000000000000000000000000000000000000000000000000000000000c0 // offset of array
000000000000000000000000000000000000000000000000000000000000000b // length of str
48656c6c6f20576f726c64000000000000000000000000000000000000000000 // str data
0000000000000000000000000000000000000000000000000000000000000003 // length of array
0000000000000000000000000000000000000000000000000000000000000001 // first element of array
0000000000000000000000000000000000000000000000000000000000000002 // second element of array
0000000000000000000000000000000000000000000000000000000000000003 // third element of array
*/
}
Supports encoding of tuples or type sequences containing tuples:
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)
}
This type sequence consists of tuple and bytes, so when calling the exchange.IO() function for encode, two parameters need to be passed:
- Variable corresponding to tuple type:
The passed parameters must match the structure and types of thejson{ a: 30, b: 20, c: "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2" }tuple, as defined in thetypesparameter:tuple(a uint256,b uint8,c address). - Variable corresponding to bytes type:string"0011"
Supports encoding of arrays or type sequences containing arrays:
javascript
function main() {
var path = ["0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", "0xdac17f958d2ee523a2206206994597c13d831ec7"] // ETH address, USDT address
var ret = exchange.IO("encode", "address[]", path)
Log("encode: ", ret)
}
Support for encodePacked
For example, when calling methods on Uniswap V3 decentralized exchange, you need to pass parameters such as swap path, which requires using the encodePacked operation:
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)
}
Support for decode
Data processing supports not only encoding (encode) but also decoding (decode). Use the exchange.IO("decode", types, rawData) function for decoding operations.
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) // The ABI here only contains the content of the exactOutput method, the complete ABI can be found online
// 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)
}
This example first performs an encodePacked operation when processing the path parameter, as the subsequent exactOutput method call that needs to be encoded requires path as a parameter. Then it encodes the exactOutput method of the router contract, which has only one parameter of tuple type.
The encoded exactOutput method name is: 0x09b81346. The decodeRaw obtained by decoding with the exchange.IO("decode", ...) method is consistent with the variable dataTuple.
Support Private Key Switching
Support private key switching to operate multiple wallet addresses, for example:
javascript
function main() {
exchange.IO("key", "Private Key") // "Private Key" represents the private key string, you need to fill in the actual private key value
}
Call Smart Contract Method
The following are examples of smart contract method calls.
-
decimals
decimalsmethod is aconstantmethod ofERC20(no need to register ABI when calling standard ERC20 methods in FMZ quantitative strategy code), which does not consumegasand can query the precision data of atoken.
Thedecimalsmethod has no parameters and returns the precision data of thetoken.javascriptfunction main(){ var tokenAddress = "0x111111111117dC0aa78b770fA6A738034120C302" // Token contract address, the token in this example is 1INCH Log(exchange.IO("api", tokenAddress, "decimals")) // Query and print that the precision exponent of 1INCH token is 18 } -
allowance
allowancemethod is aconstantmethod ofERC20, which does not consumegasand can query the authorization amount of atokenfor a certain contract address.
Theallowancemethod requires 2 parameters, the first parameter is the wallet address, and the second parameter is the authorized address. The return value is the authorization amount of thetoken.javascriptfunction main(){ // Token contract address, the token in this example is 1INCH var tokenAddress = "0x111111111117dC0aa78b770fA6A738034120C302" var owner = "" var spender = "" // For example, if the query returns 1000000000000000000, divide by the token's precision unit 1e18 to get that the wallet bound to the current exchange object has authorized 1 1INCH to the spender address Log(exchange.IO("api", tokenAddress, "allowance", owner, spender)) }owner: Wallet address, needs to be filled with the specific address in actual use.
spender: Authorized contract address, needs to be filled with the specific address in actual use, for example, it can be theUniswap V3 router v1address. -
approve
approvemethod is a non-constantmethod ofERC20, which consumesgasand is used to authorize a certain contract address with the operation amount oftoken.
Theapprovemethod requires 2 parameters, the first parameter is the authorized address, and the second parameter is the authorization amount. The return value istxid.javascriptfunction main(){ // Token contract address, the token in this example is 1INCH var tokenAddress = "0x111111111117dC0aa78b770fA6A738034120C302" var spender = "" var amount = "0xde0b6b3a7640000" // Hexadecimal string of authorization amount: 0xde0b6b3a7640000, corresponding decimal string: 1e18, 1e18 divided by the token's precision unit equals 1 token amount, so this authorizes one token Log(exchange.IO("api", tokenAddress, "approve", spender, amount)) }spender: Authorized contract address, needs to be filled with the specific address in actual use, for example, it can be theUniswap V3 router v1address.
amount: Authorization amount, represented here as a hexadecimal string. The corresponding decimal value is1e18, divided by thetokenprecision unit in the example (i.e., 1e18), resulting in authorization of 1token.The third parameter of the
exchange.IO()function passes the method nameapprove, which can also be written in the form ofmethodId, for example: "0x571ac8b0". It can also be written as the complete standard method name, for example: "approve(address,uint256)". -
multicall
multicallmethod is a non-constant method ofUniswap V3, which consumesgasand is used for batch token swaps.
Themulticallmethod may have multiple parameter passing methods, you can check the ABI containing this method for details. The ABI needs to be registered before calling this method. The return value istxid.For specific
multicallmethod call examples, you can refer to the platform's public "Uniswap V3 Trading Library" templatejavascriptfunction 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: ABI of Uniswap V3's router v2 contract, needs to be filled according to actual situation.
contractV3SwapRouterV2: Uniswap V3's router v2 address, needs to be filled with the specific address in actual use.
value: Amount of ETH to transfer, set to 0 if thetokenIntoken for the swap operation is not ETH, needs to be filled according to actual situation.
deadline: Can be set to(new Date().getTime() / 1000) + 3600, indicating validity within one hour.
data: Packed operation data to be executed, needs to be filled according to actual situation.You can also specify
gasLimit/gasPrice/noncesettings for method calls:javascriptexchange.IO("api", contractV3SwapRouterV2, "multicall(uint256,bytes[])", value, deadline, data, {gasPrice: 5000000000, gasLimit: 21000})You can set
{gasPrice: 5000000000, gasLimit: 21000, nonce: 100}parameters according to specific needs, this parameter is set as the last parameter of theexchange.IO()function.
You can omitnonceto use the system default value, or not setgasLimit/gasPrice/nonceto use all system default values.Note that the
stateMutabilityattribute of themulticall(uint256,bytes[])method in the example ispayable, which requires passing thevalueparameter.
ThestateMutability":"payable"attribute can be viewed from theABI, theexchange.IO()function will determine the required parameters based on thestateMutabilityattribute in the registeredABI.
If thestateMutabilityattribute isnonpayable, there is no need to pass thevalueparameter.
Other Function Calls
- Get wallet address configured for exchange objectjavascriptfunction main() { Log(exchange.IO("address")) // Print the wallet address corresponding to the private key configured for the exchange object }
- Switch blockchain RPC nodejavascriptfunction main() { var chainRpc = "https://bsc-dataseed.binance.org" // Switch to BSC chain, can also use SetBase function to switch e.IO("base", chainRpc) }