優(yōu)雅簡(jiǎn)潔!在FMZ上用200行代碼接入了Uniswap V3(下篇)
內(nèi)容接(上篇),接下來(lái)我們來(lái)一起繼續(xù)剖析「Uniswap V3 交易類庫(kù)」的代碼。
Part3:Uniswap V3操作對(duì)象的構(gòu)造函數(shù)
這個(gè)模板類庫(kù)的核心就是Uniswap V3操作對(duì)象,這個(gè)對(duì)象實(shí)現(xiàn)了在Uniswap V3上的基本操作。后續(xù)可能還會(huì)升級(jí)更多的功能。通過(guò)剖析這個(gè)代碼例子,即使不使用FMZ平臺(tái),也會(huì)增加對(duì)Uniswap
這個(gè)DEX
的各個(gè)環(huán)節(jié)流程、細(xì)節(jié)的認(rèn)識(shí)和理解,目前我們就來(lái)學(xué)習(xí)下這些基本的功能是如何在FMZ上設(shè)計(jì)實(shí)現(xiàn)的。
Uniswap V3操作對(duì)象的構(gòu)造函數(shù)代碼:
可能不熟悉FMZ的同學(xué)看到這個(gè)函數(shù)$.NewUniswapV3
命名有些奇怪,帶有$.
開(kāi)頭的函數(shù),表示這個(gè)函數(shù)是FMZ上模板類庫(kù)的接口函數(shù)(何為模板類庫(kù)可以查閱),簡(jiǎn)單說(shuō)就是$.NewUniswapV3
函數(shù)可以讓其它引用了該模板類庫(kù)的策略直接調(diào)用。策略就直接擁有了Uniswap V3
的功能。
這個(gè)$.NewUniswapV3
函數(shù)直接構(gòu)造、創(chuàng)建一個(gè)對(duì)象,使用這個(gè)對(duì)象就可以進(jìn)行一些操作:
token兌換:由該對(duì)象的
swapToken
方法實(shí)現(xiàn)。ETH余額查詢:由該對(duì)象的
getETHBalance
方法實(shí)現(xiàn)。token余額查詢:由該對(duì)象的
balanceOf
方法實(shí)現(xiàn)。交易對(duì)價(jià)格查詢:由該對(duì)象的
getPrice
方法實(shí)現(xiàn)。發(fā)送ETH進(jìn)行轉(zhuǎn)賬:由該對(duì)象的
sendETH
方法實(shí)現(xiàn)。
這個(gè)類庫(kù)可能后續(xù)不局限于這些功能,甚至可以升級(jí)增加「添加流動(dòng)性」等功能。我們來(lái)繼續(xù)剖析代碼:
構(gòu)造函數(shù)$.NewUniswapV3
只有一個(gè)參數(shù)e
,這個(gè)e表示交易所對(duì)象(在FMZ上的交易所配置)。因?yàn)樵贔MZ上策略可以設(shè)計(jì)成多exchange的,所以這里如果傳入某個(gè)具體的exchange就表示創(chuàng)建出來(lái)的Uniswap V3
對(duì)象是操作該交易所對(duì)象的。如果不傳參數(shù)e
,默認(rèn)操作第一個(gè)添加的交易所對(duì)象。
配置節(jié)點(diǎn)服務(wù)地址、私鑰(可以本地部署私鑰,本地部署只用配置路徑),就創(chuàng)建了一個(gè)交易所對(duì)象。在實(shí)盤的時(shí)候就可以添加在策略上,這個(gè)對(duì)象體現(xiàn)在策略代碼中就是exchange
也即exchanges[0]
,如果添加第二個(gè)就是exchanges[1]
,添加第三個(gè)為exchanges[2]
,...

截圖中我配置的節(jié)點(diǎn)地址:https://mainnet.infura.io/v3/xxx?是用的infura的節(jié)點(diǎn),這個(gè)可以個(gè)人申請(qǐng),每個(gè)賬號(hào)都有各自的具體地址,xxx這里是掩碼,每個(gè)賬戶的xxx部分各不相同。
繼續(xù)說(shuō)代碼,該構(gòu)造函數(shù)開(kāi)始判斷交易所對(duì)象是不是Web3的,不是Web3就報(bào)錯(cuò)。然后創(chuàng)建了一個(gè)變量self
,這個(gè)self就是構(gòu)造函數(shù)最終返回的對(duì)象,后續(xù)構(gòu)造函數(shù)給這個(gè)對(duì)象增加了各種函數(shù),并且實(shí)現(xiàn)具體功能。self變量有3個(gè)屬性:
tokenInfo :記錄注冊(cè)在該對(duì)象的token代幣信息,代幣信息包括代幣地址、代幣精度、代幣名稱。
walletAddress:當(dāng)前交易所對(duì)象的錢包地址。
pool:注冊(cè)在該對(duì)象的兌換池信息,主要是兌換池名稱和兌換池地址。
緊接著用到了我們上篇學(xué)習(xí)到的概念:
為什么要注冊(cè)這些接口信息呢?
因?yàn)楹罄m(xù)要實(shí)現(xiàn)的一些功能需要調(diào)用這些智能合約的接口。接下來(lái)就是該構(gòu)造函數(shù)給self對(duì)象增加各種方法了,self對(duì)象的方法除了上述提到的:兌換token、查詢余額等,還有一些屬于這個(gè)self對(duì)象的工具函數(shù),我們這里先剖析這些工具函數(shù)。
self對(duì)象的工具函數(shù)
1、self.addToken = function(name, address)
觀察這個(gè)函數(shù)的具體代碼可知,這個(gè)函數(shù)功能是給當(dāng)前對(duì)象self
中記錄token
信息的成員tokenInfo
增加(換種說(shuō)法就是:注冊(cè))一個(gè)token(代幣)信息。因?yàn)?code>token(代幣)的精度數(shù)據(jù)在后續(xù)計(jì)算時(shí)要經(jīng)常用到,所以在這個(gè)函數(shù)增加(注冊(cè))token信息的時(shí)候,調(diào)用了let ret = e.IO("api", address, "decimals")
函數(shù),通過(guò)FMZ封裝的exchange.IO函數(shù)(前邊我們提過(guò)了e就是傳入的exchange對(duì)象),調(diào)用token代幣合約的"decimals"
方法,從而獲取token的精度。
所以self.tokenInfo
是一個(gè)字典結(jié)構(gòu),每個(gè)鍵名是token名字,鍵值是這個(gè)token的信息,包括:地址、名稱、精度。大概是這個(gè)樣子:
2、self.waitMined = function(tx)
該函數(shù)用于等待以太坊上智能合約的執(zhí)行結(jié)果,從這個(gè)函數(shù)的實(shí)現(xiàn)代碼上可以看到,這個(gè)函數(shù)一直在循環(huán)調(diào)用let info = e.IO("api", "eth", "eth_getTransactionReceipt", tx)
,通過(guò)調(diào)用以太坊的RPC方法eth_getTransactionReceipt
,來(lái)查詢交易哈希返回交易的收據(jù),參數(shù)tx
即為交易哈希。
eth_getTransactionReceipt
等相關(guān)資料可以查看:https://ethereum.org/zh/developers/docs/apis/json-rpc/#eth_gettransactionreceipt
可能有同學(xué)會(huì)問(wèn):為什么要用這個(gè)函數(shù)?
答:在執(zhí)行一些操作時(shí),例如token兌換,是需要等待結(jié)果的。
接下來(lái)我們?cè)賮?lái)看$.NewUniswapV3
函數(shù)創(chuàng)建的對(duì)象self的其它主要功能實(shí)現(xiàn),我們從最簡(jiǎn)單的講起。
主要功能函數(shù)
1、self.getETHBalance = function(address)
查詢token(代幣)余額是有區(qū)分的,分為查詢ETH(以太坊)余額,查詢其它ERC20的token余額。self對(duì)象的getETHBalance函數(shù)是用來(lái)查詢ETH余額的,當(dāng)傳入了具體錢包地址參數(shù)address時(shí),查詢這個(gè)地址的ETH余額。如果沒(méi)有傳address參數(shù)則查詢self.walletAddress
地址的ETH余額(即當(dāng)前exchange上配置的錢包)。
這些通過(guò)調(diào)用以太坊的RPC方法eth_getBalance
實(shí)現(xiàn)。
2、self.balanceOf = function(token, address)
查詢除了ETH以外的token余額,需要傳入?yún)?shù)token即代幣名稱,例如USDT。傳入所要查詢的錢包地址address,沒(méi)有傳入address則查詢self.walletAddress
地址的余額。觀察這個(gè)函數(shù)實(shí)現(xiàn)的代碼可知,需要事先通過(guò)self.addToken
函數(shù)注冊(cè)過(guò)的token才可以查詢,因?yàn)檎{(diào)用token的合約的balanceOf
方法時(shí),需要用到token(代幣)的精度信息和地址。
3、self.sendETH = function(to, amount, options)
該函數(shù)的功能為ETH轉(zhuǎn)賬,向某個(gè)錢包地址(使用to
參數(shù)設(shè)置)轉(zhuǎn)賬一定數(shù)量的ETH(使用amount
參數(shù)設(shè)置),可以再設(shè)置一個(gè)options
參數(shù)(數(shù)據(jù)結(jié)構(gòu):{gasPrice: 111, gasLimit: 111, nonce: 111}
)用來(lái)指定gasLimit/gasPrice/nonce
,不傳入options參數(shù)即使用系統(tǒng)默認(rèn)的設(shè)置。
gasLimit/gasPrice
影響在以太坊上執(zhí)行操作時(shí)消耗的ETH(以太坊上的一些操作是消耗gas的,即消耗一定ETH代幣)。
4、self.getPrice = function(pair, fee)
該函數(shù)用來(lái)獲取在Uniswap上某個(gè)交易對(duì)的價(jià)格,通過(guò)函數(shù)實(shí)現(xiàn)代碼可以看到,在函數(shù)開(kāi)始執(zhí)行時(shí)會(huì)首先將交易對(duì)pair解析,得到baseCurrency和quoteCurrency。例如交易對(duì)是ETH_USDT,則會(huì)拆分為ETH和USDT。然后查詢self.tokenInfo
中是否有這兩種token(代幣)的信息,沒(méi)有則報(bào)錯(cuò)。
在Uniswap上的兌換池地址是由參與的兩種token(代幣)地址、Fee(費(fèi)率標(biāo)準(zhǔn))計(jì)算構(gòu)成的,所以在查詢self.pool
(self.pool之前我們提過(guò),可以看下)中記錄的池地址時(shí),如果沒(méi)有查詢到就使用兩種token的地址、Fee去計(jì)算池地址。所以一個(gè)交易對(duì)可能有多個(gè)池,因?yàn)镕ee可能不同。
查詢、計(jì)算兌換池的地址通過(guò)調(diào)用Uniswap V3的工廠合約的getPool
方法獲得(所以要在開(kāi)始注冊(cè)工廠合約的ABI)。
拿到這個(gè)交易對(duì)的池地址,就可以注冊(cè)池合約的ABI。這樣才能調(diào)用這個(gè)池(智能合約)的slot0
方法,從而拿到價(jià)格數(shù)據(jù)。當(dāng)然這個(gè)方法返回的數(shù)據(jù)并不是人類可讀的價(jià)格,而是一個(gè)和價(jià)格相關(guān)的數(shù)據(jù)結(jié)構(gòu),需要進(jìn)一步處理獲取可讀的價(jià)格,這個(gè)時(shí)候就使用到我們上篇中提到的computePoolPrice
函數(shù)。
5、self.swapToken = function(tokenIn, amountInDecimal, tokenOut, options)
該函數(shù)的功能是token兌換,參數(shù)tokenIn是兌換時(shí)支付的代幣名稱,參數(shù)tokenOut是兌換時(shí)獲得的代幣名稱,參數(shù)amountInDecimal是兌換數(shù)量(人類可讀的數(shù)量),參數(shù)options和我們之前提到的一樣,可以設(shè)置兌換時(shí)的gas消耗、nonce等。
函數(shù)執(zhí)行時(shí)首先還是先通過(guò)self.tokenInfo
變量中拿到token(代幣)的信息,兌換也是很多細(xì)節(jié)的,首先如果參與兌換的token中,支付的token不是ETH則需要先給路由(負(fù)責(zé)兌換的智能合約)授權(quán)。授權(quán)之前要先查詢是否已經(jīng)有足夠的授權(quán)額度。
使用token合約allowance方法查詢已經(jīng)授權(quán)的額度。通過(guò)比較已經(jīng)授權(quán)的額度和當(dāng)前兌換的數(shù)量,如果授權(quán)的額度足夠兌換,則不用再授權(quán)。如果額度不夠則執(zhí)行授權(quán)處理。
這里授權(quán)也有一個(gè)細(xì)節(jié),如果授權(quán)的token是USDT,則需要先重置授權(quán)數(shù)量為0,再進(jìn)行授權(quán)。授權(quán)使用token合約的approve方法。注意approve授權(quán)方法是一個(gè)消耗gas的方法,會(huì)消耗一定量的ETH。所以需要使用self.waitMined函數(shù)等待處理結(jié)果。
為了避免頻繁授權(quán),支付不必要的ETH,這個(gè)授權(quán)操作一次性授權(quán)最大值。
有足夠的兌換額度,就可以進(jìn)行兌換了。但是這里也有細(xì)節(jié),如果參與兌換的token中,兌換后獲取的token是ETH則需要修改接收地址:
具體原因比較復(fù)雜,這里不在贅述,可以參看:
ADDRESS_THIS?https://degencode.substack.com/p/uniswapv3-multicall
https://etherscan.io/address/0x68b3465833fb72a70ecdf485e0e4c7bd8665fc45#code
接著使用FMZ平臺(tái)封裝的打包函數(shù)e.IO("pack", ...
,打包對(duì)于路由(智能合約)的swapExactTokensForTokens方法調(diào)用,如果兌換后獲取的token是ETH則還需要增加一步WETH9的解包操作:
因?yàn)閰⑴c兌換的是WETH,這個(gè)是ETH的一個(gè)包裝后的代幣。換成真正的ETH需要解包操作,把這個(gè)解包操作也打包之后就可以調(diào)用路由(智能合約)的multicall方法執(zhí)行這一系列操作了。這里還有一個(gè)細(xì)節(jié)要額外注意,如果參與兌換的交易對(duì),支付的token是ETH時(shí)是需要在如下步驟設(shè)置轉(zhuǎn)賬的ETH數(shù)量,如果不是ETH則設(shè)置0。
這個(gè)設(shè)定體現(xiàn)在這里:(tokenInInfo.name == 'ETH' ? amountIn : 0)
。小編就因?yàn)橹皼](méi)弄清楚,沒(méi)有在tokenIn不等于ETH代幣時(shí)設(shè)置0,導(dǎo)致誤轉(zhuǎn)了ETH。所以編寫(xiě)轉(zhuǎn)賬代碼時(shí)要格外小心。
Part4:Uniswap V3操作對(duì)象如何使用
這個(gè)模板中的代碼在功能實(shí)現(xiàn)上實(shí)際不到200行,以下這一段實(shí)際是使用演示。
$.testUniswap = function()
這個(gè)函數(shù)僅僅只是一個(gè)演示,沒(méi)有實(shí)際用途請(qǐng)勿調(diào)用。我們通過(guò)這個(gè)函數(shù)來(lái)看如何使用這個(gè)模板類庫(kù)操作Uniswap V3的功能。
代碼中首先執(zhí)行let ex = $.NewUniswapV3()
構(gòu)造了一個(gè)Uniswap V3操作對(duì)象,如果想拿到當(dāng)前exchange綁定的錢包地址,可以使用ex.walletAddress
獲取。接著代碼中使用ex.addToken
注冊(cè)了三種token,分別是ETH、USDT、1INCH。
打印某個(gè)交易對(duì)的價(jià)格(token需要先注冊(cè)):
getPrice函數(shù)如果沒(méi)有設(shè)置Fee,則使用的是默認(rèn)3000這個(gè)費(fèi)率,轉(zhuǎn)換為可讀數(shù)值是0.3%。
如果要把0.01個(gè)ETH兌換成USDT,然后查詢余額,接著再兌換回來(lái),則使用代碼:
使用測(cè)試網(wǎng) Goerli 測(cè)試
1、配置測(cè)試網(wǎng)交易所對(duì)象
注意設(shè)置節(jié)點(diǎn)就需要設(shè)置為測(cè)試網(wǎng)Goerli的節(jié)點(diǎn)。

2、編寫(xiě)一個(gè)策略,在測(cè)試網(wǎng)Goerli上測(cè)試。
測(cè)試代碼中我們測(cè)試了打印錢包地址、注冊(cè)token信息、打印資產(chǎn)余額、進(jìn)行了一次連續(xù)兌換ETH -> UNI -> LINK
。需要注意這里注冊(cè)的代幣地址是以太坊測(cè)試網(wǎng)Goerli上的,所以同樣名稱的代幣地址是不同的,至于測(cè)試幣可以用這個(gè)測(cè)試網(wǎng)的水龍頭申請(qǐng)測(cè)試代幣,具體可以谷歌查詢。

注意要勾選「Uniswap V3 交易類庫(kù)」模板才能使用$.NewUniswapV3()
函數(shù),如果你的FMZ賬號(hào)還沒(méi)有這個(gè)模板,可以點(diǎn)擊這里獲取。
策略運(yùn)行日志:


Uniswap頁(yè)面上顯示的資產(chǎn)數(shù)值
https://app.uniswap.org/

在鏈上對(duì)應(yīng)也能查詢到這些操作:
https://goerli.etherscan.io/

ETH兌換為UNI執(zhí)行了一次,對(duì)UNI授權(quán)執(zhí)行一次,把UNI兌換為L(zhǎng)INK執(zhí)行了一次。
END
這個(gè)類庫(kù)還有很多功能可以擴(kuò)展,甚至可以擴(kuò)展打包多次兌換實(shí)現(xiàn)tokenA -> tokenB -> tokenC
路徑兌換。具體可以根據(jù)需求優(yōu)化、擴(kuò)展,此類庫(kù)代碼主要提供教學(xué)為主。