Elegant and simple! Accessed Uniswap V3 on FMZ with 200 lines of code

Author: Lydia, Created: 2023-02-09 14:34:40, Updated: 2023-09-18 19:38:35

the contract hold WETH before you can redeem it. }

    let swapToken = e.IO("encode", ContractV3SwapRouterV2, "swapExactTokensForTokens", amountIn, 1, [tokenInInfo.address, tokenOutInfo.address], recipientAddress)   // Packaged swapExactTokensForTokens to call.
    let data = [swapToken]
    if (tokenOutInfo.name == 'ETH') {    // If the token exchanged back is ETH, which in this case is actually WETH, then it needs to unpack.
        data.push(e.IO("encode", ContractV3SwapRouterV2, "unwrapWETH9(uint256,address)", 1, self.walletAddress))   // So here is another unwrapWETH9 unpacking to call
    }
    let tx = e.IO("api", ContractV3SwapRouterV2, "multicall(uint256,bytes[])", (tokenInInfo.name == 'ETH' ? amountIn : 0), (new Date().getTime() / 1000) + 3600, data, options || {})   // Use multicall to perform these packaged operations (swapExactTokensForTokens, unwrapWETH9)
    if (tx) {
        Log("tx: ", tx)
        self.waitMined(tx)
        Log("swap", tokenInInfo.name, "to", tokenOutInfo.name, "success")
        return true
    } else {
        Log("trans error")
        return false
    }
}

self.getETHBalance = function(address) {   // Check your wallet's ETH balance
    return toAmount(e.IO("api", "eth", "eth_getBalance", address || self.walletAddress, "latest"), 18)
}

self.balanceOf = function(token, address) {  // Query the balance of a token in a wallet (determined by parameters)
    let tokenInfo = self.tokenInfo[token]
    if (!tokenInfo) {
        throw "not found token info " + token
    }

    return toAmount(e.IO("api", tokenInfo.address, "balanceOf", address || self.walletAddress), tokenInfo.decimals)
}

self.sendETH = function(to, amount, options) {   // Sending ETH tokens to an address, i.e. transfer
    return e.IO("api", "eth", "send", to, toInnerAmount(amount, 18), options || {})
}

self.getPrice = function(pair, fee) {     // Get price of trading pair
    let arr = pair.split('_')
    let token0 = self.tokenInfo[arr[0]]
    if (!token0) {
        throw "token " + arr[0] + "not found"
    }
    let token1 = self.tokenInfo[arr[1]]    // First, get the two token information that constitute the trading pair
    if (!token1) {
        throw "token " + arr[1] + "not found"
    }
    let reverse = false
    if (BigInt(token0.address) > BigInt(token1.address)) {
        let tmp = token0
        token0 = token1
        token1 = tmp
        reverse = true
    }
    let key = token0.address + '/' + token1.address
    if (typeof(self.pool[key]) == 'undefined') {
        let pool = e.IO("api", ContractV3Factory, "getPool", token0.address, token1.address, typeof(fee) === 'number' ? fee : 3000)   // Call the getPool method of the factory contract to obtain the address of the exchange pool
        if (pool) {
            self.pool[key] = pool    // Register the pool address and register the ABI of the pool contract
            // register pool address
            e.IO("abi", pool, ABI_Pool)
        }
    }
    if (typeof(self.pool[key]) == 'undefined') {
        throw "pool " + pair + " not found"
    }

    let slot0 = e.IO("api", self.pool[key], "slot0")  // Call the slot0 method of the pool contract to get price related information

    if (!slot0) {
        return null
    }

    let price = computePoolPrice(token0.decimals, token1.decimals, slot0.sqrtPriceX96)  // Calculate the readable price
    if (reverse) {
        price = 1 / price
    }
    return price
}

return self

}

Students who may be not familiar with FMZ see the function ```$.NewUniswapV3``` has a strange name. Functions with ```$.``` at the beginning indicates that this function is the interface function of the template class library on FMZ (what is the template class library can be [viewed](https://www.fmz.com/api#template - library), simply put, the ```$.NewUniswapV3``` function can be called by other strategies that reference the **template class library** directly. The strategy has the function of ```Uniswap V3``` directly.

The function ```$.NewUniswapV3``` directly constructs and creates an object, which can be used to perform some operations:

- Token exchange: implemented by the ```swapToken``` method of the object.
- ETH balance query: implemented by the ```getETHBalance``` method of the object.
- Token balance query: implemented by the ```balanceOf``` method of the object.
- Transaction pair price query: implemented by the ```getPrice``` method of the object.
- Send ETH for transfer: implemented by the ```sendETH``` method of the object.

This class library may not be limited to these functions in the future, and may even upgrade and add "add liquidity" and other functions. Let's continue to analyze the code:
e = e || exchange
if (e.GetName() !== 'Web3') {
    panic("only support Web3 exchange")
}


let self = {
    tokenInfo: {},
    walletAddress: e.IO("address"),
    pool: {}
}

// register
e.IO("abi", ContractV3Factory, ABI_Factory)
e.IO("abi", ContractV3SwapRouterV2, ABI_Route)
The constructor ```$.NewUniswapV3``` has only one parameter ```e```, which means the exchange object (the exchange configuration on FMZ). Because the strategy on FMZ can be designed to be multi-exchange, so if a specific exchange is passed here, it means that the ```Uniswap V3``` object created is the one that operates the exchange object. If the parameter ```e``` is not passed, the first added exchange object is operated by default.

Configure the node service address, private key (you can deploy the private key locally, local deployment only uses the configuration path), and an exchange object is created. It can be added to the strategy at the time of the real market, this object is reflected in the strategy code is ```exchange``` also known as ```exchanges[0]```, if you add the second, it's ```exchanges[1]```, add the third for ```exchanges[2]```, ...

![img](/upload/asset/28ced0144dc6659506d18.png)

The node address I configured in the screenshot: https://mainnet.infura.io/v3/xxx, which is an infura node, which can be applied by individuals. Each account has its own specific address. xxx here is the mask, and the xxx part of each account is different.

Continue with the code. The constructor starts to determine whether the exchange object is Web3, if not, an error will be reported. Then a variable ```self``` is created. The self is the object finally returned by the constructor. Subsequent constructors add various functions to this object and implement specific functions. The variable self has three attributes:

- tokenInfo: records the token information registered in the object, including token address, token precision, and token name.
- walletAddress: the wallet address of the current exchange object.
- pool: the exchange pool information registered in the object, mainly including the name and address of the exchange pool.

Then we use the concept we learned in the previous chapter:

e.IO("abi", ContractV3Factory, ABI_Factory) // Register ABI for Uniswap V3 factory contract e.IO("abi", ContractV3SwapRouterV2, ABI_Route) // Register ABI for Uniswap Router V2 routing

Why register this interface information?

Because some functions to be implemented in the future need to call the interfaces of these smart contracts. Next, the constructor adds various methods to the self object. In addition to the above mentioned self object methods: exchange token, query balance, etc., there are also some tool functions belonging to the self object. Let's analyze these tool functions first.

#### Tool functions for self objects
1. ```self.addToken = function(name, address)```

By observing the specific code of this function, we can see that this function is used to add (in other words, register) a token information to the current object ```self``` in the member ```tokenInfo``` that records ```token``` information. Because the precision data of ```token``` is often used in subsequent calculations, when this function adds (registers) token information, it calls the ```let ret=e.IO ("api", address, "decimals")``` function, and calls the ```"decimals"``` method of token contract through the FMZ encapsulated exchange.IO function (we mentioned that e is the exchange object passed in) to obtain the precision of token.

So ```self.tokenInfo``` is a dictionary structure. Each key name is the token name, and the key value is the token information, including address, name, and precision. It looks like this:

{ “ETH”: {name: “ETH”, decimals: 18, address: “0x…”}, “USDT”: {name: “USDT”, decimals: 6, address: “0x…”}, … }

2. ```self.waitMined = function(tx)```

This function is used to wait for the execution result of the smart contract on Ethereum. From the implementation code of this function, we can see that this function has been calling ```let info=e.IO ("api", "eth", "eth_getTransactionReceipt", tx)``` in a loop. By calling the RPC method ```eth_GetTransactionReceipt``` of Ethereum to query the transaction hash and return the transaction receipt. The parameter ```tx``` is the transaction hash.

```eth_GetTransactionReceipt``` and other relevant data can be viewed at: https://ethereum.org/zh/developers/docs/apis/json-rpc/#eth_gettransactionreceipt

Some students may ask: Why do we need this function?

Answer: When performing some operations, such as token exchange, it is necessary to wait for the result.

Next we will look at the other main functions of the object self created by the ```$.NewUniswapV3``` function, we start with the simplest one.

### Main function functions
1. ```self.getETHBalance = function(address)```

The query of token balance can be divided into the query of ETH balance and the query of other ERC20 token balance. The getETHBalance function of the self object is used to query the ETH balance. When the specific wallet address parameter address is passed in, the ETH balance of this address is queried. If the address parameter is not passed, then the ETH balance of the ```self.walletAddress``` address (i.e. the wallet configured on the current exchange) is queried.

If the address parameter is not passed, then the ETH balance of the ```self.walletAddress``` address (i.e. the wallet configured on the current exchange) is queried.

These are achieved by calling the RPC method ```eth_getBalance``` of Ethereum.

2. ```self.balanceOf = function(token, address)```

To query the token balance other than ETH, you need to pass in the parameter token, that is, the token name, such as USDT. Pass in the address of the wallet to be queried, and if no address is passed in, query the balance of the address of ```self.walletAddress```. By observing the code implemented by this function, we can see that only the token registered through the ```self.addToken``` function can be queried, because the precision information and address of the token (token) are required when calling the ```balanceOf``` method of the token contract.

3. ```self.sendETH = function(to, amount, options)```

The function of this function is ETH transfer. To transfer a certain amount of ETH to a wallet address (using the parameter ```to``` to set), you can set another ```options``` parameter (data structure: ```{gasPrice: 111, gasLimit: 111, nonce: 111}```) to specify the ```gasLimit/gasPrice/once``` parameter. The system default setting is used without passing in the options parameter.

```GasLimit/gasPrice``` affects the ETH consumed when performing operations on Ethereum (some operations on Ethereum consume gas, that is, certain ETH tokens).

4. ```self.getPrice = function(pair, fee)```

This function is used to obtain the price of a trading pair on Uniswap. You can see from the function implementation code that the trading pair will be parsed at the beginning of the function execution to obtain the baseCurrency and quoteCurrency. For example, the trading pair is ETH_USDT, it will be split into ETH and USDT. Then query whether there are two kinds of token information in ```self.tokenInfo```. If there is no information, an error will be reported.

The exchange pool address on Uniswap is composed of two token addresses and Fee (rate standard) calculations. So when querying the pool address recorded in ```self.pool``` (we have mentioned self.pool before, you can check it), if not found, use the two token addresses and Fee to calculate the pool address. So a trading pair may have multiple pools, because the Fee may be different.

The address of the query and calculation exchange pool is obtained by calling the ```getPool``` method of the factory contract in Uniswap V3 (so you need to register the ABI of the factory contract at the beginning).

Get the pool address of the trading pair, and you can register the ABI of the pool contract. In this way, the ```slot0``` method of this pool (smart contract) can be called to get the price data. Of course, the data returned by this method is not a human-readable price, but a price-related data structure. Further processing is needed to obtain a readable price. At this time, we use the ```computePoolPrice``` function mentioned in the previous section.

5. ```self.swapToken = function(tokenIn, amountInDecimal, tokenOut, options)```

The function of this function is token exchange. The parameter tokenIn is the name of the token paid during exchange, the parameter tokenOut is the name of the token obtained during exchange, and the parameter amountInDecimal is the exchange quantity (human-readable quantity). The parameter options, as we mentioned earlier, can be set for gas consumption, nonce, etc. during exchange.

When the function is executed, the information of token is first obtained from the ```self.tokenInfo``` variable. There are many details of the exchange. First, if the token involved in the exchange is not ETH, then the **routing** (smart contract responsible for exchange) needs to be authorized first. Before authorization, check whether there is enough authorization.

let allowanceAmount = e.IO("api", tokenInInfo.address, “allowance”, self.walletAddress, ContractV3SwapRouterV2);

Use the token contract allowance method to query the authorized amount. By comparing the authorized amount with the current amount of exchange, if the authorized amount is enough to exchange, no authorization is needed. If the amount is insufficient, the authorization processing will be executed.

There is also a detail of authorization here. If the authorized token is a USDT, you need to reset the number of authorization to 0 before authorization. The approve method of the token contract is authorized. Note that the approval authorization method is a gas consumption method, which will consume a certain amount of ETH. So you need to use the self.waitMined function to wait for the processing result.

In order to avoid frequent authorization and pay unnecessary ETH, this authorization operation is the maximum one-time authorization.

let txApprove = e.IO("api", tokenInInfo.address, “approve”, ContractV3SwapRouterV2, ‘0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff’);

With enough exchange limits, you can exchange. However, there are also details here. If the token involved in the exchange, the token obtained after the exchange is ETH, then you need to change the receiving address:

recipientAddress = ‘0x0000000000000000000000000000000000000002’

The specific reasons are more complex and are not described here, please refer to:

ADDRESS_THIS https://degencode.substack.com/p/uniswapv3-multicall
https://etherscan.io/address/0x68b3465833fb72a70ecdf485e0e4c7bd8665fc45#code

Then use the packaging function ```e.IO("encode", ...```) encapsulated by the FMZ platform to package the swapExactTokensForTokens method call for routing (smart contract). If the token obtained after exchange is ETH, you need to add a step of WETH9 unpacking operation:

data.push(e.IO("encode", ContractV3SwapRouterV2, “unwrapWETH9(uint256,address)”, 1, self.walletAddress))

Because WETH is involved in the exchange. This is a packaged token of ETH. For real ETH, unpacking operation is required. After the unpacking operation is also packaged, the multicall method of routing (smart contract) can be called to perform this series of operations. Here is another detail to pay extra attention to: if the trading pair participating in the exchange, the payment token is ETH, the number of ETH transferred needs to be set in the following steps. If it is not ETH, set 0.

let tx = e.IO("api", ContractV3SwapRouterV2, “multicall(uint256,bytes[])”, (tokenInInfo.name == ‘ETH’ ? amountIn : 0), (new Date().getTime() / 1000) + 3600, data, options || {})

The setting is reflected here: ```(tokenInInfo.name=='ETH '? amountIn : 0)```. I did not make it clear before that I did not set 0 when tokenIn was not equal to ETH token, resulting in the wrong transfer of ETH. So be careful when writing the transfer code.

### Part4: How to use Uniswap V3 operation objects
The code in this template is actually less than 200 lines in terms of function implementation. The following paragraph is a demonstration of its use.

$.testUniswap = function() { let ex = $.NewUniswapV3() Log("walletAddress: ", ex.walletAddress) let tokenAddressMap = { “ETH”: “0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2”, // WETH “USDT”: “0xdac17f958d2ee523a2206206994597c13d831ec7”, “1INCH”: “0x111111111117dC0aa78b770fA6A738034120C302”, } 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 reverse
Log(ex.swapToken('USDT', usdtBalance, 'ETH'))

Log("balance of ETH", ex.getETHBalance())

// Log(ex.sendETH('0x11111', 0.02))

}

The function ```$.testUniswap=function()``` is only a demonstration. Do not call it without practical use. Let's use this function to see how to use this template class library to operate the function of Uniswap V3.

In the code, execute ```let ex=$.NewUniswapV3()``` to construct a Uniswap V3 operation object first. If you want to get the wallet address bound by the current exchange, you can use ```ex.walletAddress``` to get it. Then, the code uses the ```ex.addToken``` to register three kinds of tokens, namely ETH, USDT and 1INCH.

Print the price of a trading pair (token needs to be registered first):

Log(ex.getPrice(‘ETH_USDT’)) Log(ex.getPrice(‘1INCH_USDT’))

The getPrice function uses the default rate of 3,000 if no Fee is set, which is converted to a readable value of 0.3%.

If you want to convert 0.01 ETH to USDT, then check the balance and then convert back, use the code:

Log(ex.swapToken(‘ETH’, 0.01, ‘USDT’))

let usdtBalance = ex.balanceOf(‘USDT’) // Check the balance of USDT after exchange Log(“balance of USDT”, usdtBalance)

Log(ex.swapToken(‘USDT’, usdtBalance, ‘ETH’)) // Exchange USDT to ETH Log(“balance of ETH”, ex.getETHBalance()) // Check ETH balance

// Log(ex.sendETH(‘0x11111’, 0.02)) // ETH transfer operations

### Testing with the test network Goerli
1. Configure the test network exchange object

Note that setting up the node requires setting up the node as a test network Goerli.

![img](/upload/asset/28e80bb756b7abaef8df1.png)

2. Write a strategy and test it on the test network Goerli.

function main() { let ex = $.NewUniswapV3() Log("walletAddress: ", ex.walletAddress) let tokenAddressMap = { “ETH” : “0xB4FBF271143F4FBf7B91A5ded31805e42b2208d6”, // WETH “LINK” : “0x326C977E6efc84E512bB9C30f76E30c160eD06FB”, “UNI” : “0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984”, } for (let name in tokenAddressMap) { ex.addToken(name, tokenAddressMap[name]) }

// ETH_UNI 、 UNI_ETH
Log("ETH_UNI:", ex.getPrice('ETH_UNI'))
Log("UNI_ETH:", ex.getPrice('UNI_ETH'))

// ETH 
Log("balance of ETH", ex.getETHBalance())

// UNI
let uniBalance = ex.balanceOf('UNI')
Log("balance of UNI", uniBalance)

// LINK
let linkBalance = ex.balanceOf('LINK')
Log("balance of LINK", linkBalance)

// swap 0.001 ETH to UNI
Log(ex.swapToken('ETH', 0.001, 'UNI'))

// swap UNI to LINK
Log(ex.swapToken('UNI', ex.balanceOf('UNI') - uniBalance, 'LINK'))

}

In the test code, we tested the printing of wallet address, registration of token information, printing of asset balance, and a continuous exchange of ```ETH ->UNI ->LINK```. It should be noted that the token address registered here is on the Goorli test network of Ethereum, so the token address with the same name is different. As for the test token, you can use the faucet of this test network to apply for the test token, and the details can be queried by Google.

![img](/upload/asset/28d416d268d528b1fe100.png)

Note that you must check the "Uniswap V3 Trade Template" to use the ```$.NewUniswapV3()``` function. If your FMZ account does not have this template, you can click [Get here](https://www.fmz.com/strategy/397260).

Strategy backtest logs:

![img](/upload/asset/28d7c9de8453c8451b10b.png)
![img](/upload/asset/28d794ada72b662b7455b.png)

Asset values displayed on the Uniswap page

https://app.uniswap.org/

![img](/upload/asset/28d6b36e05eb02d172761.png)

These operations can also be queried in the chain:

https://goerli.etherscan.io/

![img](/upload/asset/28e57b1de79ab33294cd2.png)

ETH was converted to UNI once, UNI authorization was executed once, and UNI was exchanged to LINK once.

### END
There are many functions of this class library that can be extended, and can even be extended to package multiple exchanges to realize ```tokenA ->tokenB ->tokenC``` path exchange. It can be optimized and expanded according to the needs. This kind of library code is mainly for teaching.

### Update
The ```swapToken``` function has been upgraded to support ```tokenA ->tokenB ->tokenC... -> TokenD``` continuous exchange function. You can check the latest code of the template published by Strategy Square on FMZ platform.

Related

More