avatar of 发明者量化-小小梦 发明者量化-小小梦
关注 私信
4
关注
1128
关注者

基于FMZ平台Web3 Tron封装接入SunSwap DEX的实践

创建于: 2025-03-21 15:17:05, 更新于: 2025-03-28 09:52:32
comments   0
hits   174

[TOC]

基于FMZ平台Web3 Tron封装接入SunSwap DEX的实践

前言

在区块链交易领域,去中心化交易所(DEX) 提供了无需信任的资产交换方式,而 SunSwap 作为 Tron 生态中的代表性 DEX,支持基于 Web3 的智能合约交互。FMZ 平台(发明者量化) 提供了强大的量化交易框架,使开发者能够集成多种交易策略。

FMZ平台已经支持了Web3 以太坊(ETH)交易所对象的封装,近期新增了对于Web3 TRON的支持,封装了波场链的交易所对象。与接入UniSwap DEX一样,使得SunSwap这个DEX的接入也可以实现。

本文将探讨如何 基于 FMZ 量化平台,封装 Web3 接口,并接入 SunSwap DEX 进行交易。我们将介绍 Tron 网络的 Web3 交互方式、SunSwap 智能合约调用,并通过 代码示例 展示如何 查询流动性、执行交易、获取交易结果,从而构建高效的量化交易策略。

声明

本文分享的所有代码和设计均经过测试,旨在用于学习和交流。然而,即便经过测试,这些内容仍可能需要根据实际需求进行调整。如需应用于实盘交易,请务必自行评估、优化并修改,以确保符合自身需求。

准备工作

  • 配置FMZ平台的Web3 TRON 交易所对象

在上一篇「FMZ量化Web3拓展:新增Tron支持,扩展链上交易能力」文章中我们已经介绍了 Tron gRPC 节点的常用方法,并学习了如何在 FMZ 上配置 Web3 TRON 交易所对象,本篇将不再重复这些内容。至于节点地址,我们将直接使用波场官方提供的节点。

  • 部署托管者

https://www.bilibili.com/video/BV1PC4y1F7TN/

与CEX交易所不同,没有墙的烦恼。本地部署一个托管者就可以访问到官方节点。

  • SunSwap DEX

https://sun.io/

浏览器打开SunSwap的前端页面,可以连接钱包,也方便观察数据。

  • tronscan

https://tronscan.org/

必备的工具,查看、分析数据。

  • base58

TRON上的地址为base58编码,这一点和以太坊不太一样,有时候为了查看数据,对比数据则需要做一些转换。在FMZ上可以直接使用base58编码的地址,有些智能合约返回的数据是HEX编码则需要做一些转换。简单实现了几个函数用于转换地址格式:

以下是主要的转换函数,由于篇幅有限,就不贴上全部的代码了,完整的代码在文章末尾公开的「Tron SunSwap V3 交易类库」模板类库中。

  function ethAddressToTron(ethAddress) {
      if (!/^0x[0-9a-fA-F]{40}$/.test(ethAddress)) {
          throw "Invalid Ethereum address"
      }
  
      const ethAddressBytes = new Uint8Array(20)
      for (let i = 0; i < 20; i++) {
          ethAddressBytes[i] = parseInt(ethAddress.substr(2 + i * 2, 2), 16)
      }
  
      const tronAddressBytes = new Uint8Array(21)
      tronAddressBytes[0] = 0x41
      tronAddressBytes.set(ethAddressBytes, 1)
  
      const hash1 = Encode("sha256", "hex", "hex", uint8ArrayToHex(tronAddressBytes))
      const hash2 = Encode("sha256", "hex", "hex", hash1)
      const checksum = hash2.slice(0, 8)
  
      const fullAddress = new Uint8Array(25)
      fullAddress.set(tronAddressBytes, 0)
      fullAddress.set(hexToUint8Array(checksum), 21)
  
      return base58Encode(fullAddress)
  }
  • 打包调用

由于有时候需要一次性读取多个方法的数据,逐个调用也费事费力。所以封装了一个打包调用函数:

  function multicall(e, multicallContractAddress, data, outTypes) {
      let ret = e.IO("api", multicallContractAddress, "aggregate", data)
      if (!ret || !ret["returnData"] || !Array.isArray(ret["returnData"])) {
          Log("invalid ret:", ret)
          return null
      }
  
      let arrRet = ret["returnData"]
      if (outTypes.length != arrRet.length) {
          Log("Arrays have unequal lengths:", arrRet, outTypes)
          return null
      }
  
      let outData = []
      for (let i in arrRet) {
          outData.push(e.IO("decode", outTypes[i], arrRet[i]))
      }
  
      return outData
  }

Tron SunSwap V3 交易类库

SunSwap V3 交易类库设计模仿自FMZ平台的UniSwap交易类库模板。由于SunSwap模板代码比较长,所以就不占用篇幅贴出来了,主要说明实现的功能。

创建SunSwap交易对象

let e = $.CreateSunSwapEx()                 // 默认使用exchange即exchanges[0]初始化SunSwap交易对象。
// let e = $.CreateSunSwapEx(exchanges[1])  // 使用次交易所对象初始化SunSwap交易对象。

该模板只有一个接口函数,即:$.CreateSunSwapEx()用于创建SunSwap交易对象。创建对象完成后就可以调用这个对象的方法,进行一些功能调用,例如:兑换交易、查询池价格、获取全部V3池信息、质押、解压等。

GetMarkets

「Tron SunSwap V3 交易类库」的测试代码都在模板的main()函数中有记录,不在赘述。

    let markets = e.GetMarkets()
    let idx = 0
    let tbl = {"type": "table", "title": "test GetMarkets", "cols": ["Index", "PoolAddress", "Symbol", "BaseAsset", "QuoteAsset", "BaseName", "QuoteName", "BaseDecimals", "QuoteDecimals", "BaseAddress", "QuoteAddress"], "rows": []}
    for (let currency in markets) {
        let arrMarket = markets[currency]
        for (let market of arrMarket) {
            tbl["rows"].push([idx, market["PoolAddress"], market["Symbol"], market["BaseAsset"], market["QuoteAsset"], market["BaseName"], market["QuoteName"], market["BaseDecimals"], market["QuoteDecimals"], market["BaseAddress"], market["QuoteAddress"]])    
            idx++
        }
    }
    LogStatus("`" + JSON.stringify(tbl) + "`")

函数GetMarkets()用于获取SunSwap V3 pool的所有兑换池的相关信息,缓存在SunSwap交易对象的变量中,用于其它操作时查询精度、地址等信息。

这段测试代码运行后会显示所有V3池的信息:

基于FMZ平台Web3 Tron封装接入SunSwap DEX的实践

代码中已经剔除了目前看到的一些「钓鱼合约」、「无效合约」等合约地址。

GetTicker

    let ticker1 = e.GetTicker("WTRX_USDT")
    Log("symbol:", "WTRX_USDT", ", ticker:", ticker1)
    let ticker2 = e.GetTicker("iCMX_USDT", "TLVDozYEBgeaJXH7oKBsougzEJKNrogFun")  // iCMX_USDT
    Log("symbol:", "iCMX_USDT", ", ticker:", ticker2)

可以使用GetTicker()函数查询某个兑换池的价格,可以指定具体池的地址。注意有些池的流动性很差,价格仅用于参考。

multicall

    let data = []
    let walletAddress = exchange.IO("address")
    data.push(["TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t", "0x" + exchange.IO("encode", "TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t", "balanceOf", walletAddress)])    
    data.push(["TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t", "0x" + exchange.IO("encode", "TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t", "balanceOf", walletAddress)])    
    Log(data)
    let ret = multicall(exchange, "TGXuuKAb4bnrn137u39EKbYzKNXvdCes98", data, ["uint256", "uint256"])
    Log(ret, toAmount(ret[0], 6))

这个函数可以用于后续功能封装,例如例子中可以把两次balanceOf方法调用进行编码,一次请求到数据。

GetSwapOutAmount

    let retBuy = e.GetSwapOutAmount("WTRX_USDT", 1, "buy")     // 1 quote -> base
    Log("WTRX_USDT", "buy outAmount:", retBuy["outAmount"], ", ask:", (1 / retBuy["outAmount"]))
    let retSell = e.GetSwapOutAmount("WTRX_USDT", 1, "sell")   // 1 base -> quote
    Log("WTRX_USDT", "sell outAmount:", retSell["outAmount"], ", bid:", (retSell["outAmount"] / 1))

这是一个比较重要的功能,这里封装了SunSwap的智能路由服务,给出tokenInSymbol和tokenOutSymbol就可以返回可能的兑换路径、输出的token数量等信息。

基于FMZ平台Web3 Tron封装接入SunSwap DEX的实践

GetAssets

    let assets = e.GetAssets()
    let tbl = {"type": "table", "title": "test GetAssets", "cols": ["Name", "Address", "Decimals", "Balance"], "rows": []}   
    for (let asset of assets) {
        tbl["rows"].push([asset.Currency, asset.Address, asset.Decimals, asset.Amount])
    }
    LogStatus("`" + JSON.stringify(tbl) + "`")

由于没有想到好的遍历TRC20 token的机制,借助tronscan的功能,使用这个第三方接口查询账户资产信息。

基于FMZ平台Web3 Tron封装接入SunSwap DEX的实践

基于FMZ平台Web3 Tron封装接入SunSwap DEX的实践

Swap

    // 6 USDT -> ? WTRX
    let ret = e.Swap("WTRX_USDT", 6, "buy")
    Log(ret)

    // 10 TRX -> ? WTRX
    // let ret = e.Swap("TRX_WTRX", 10, "sell")
    // Log(ret)

最主要的功能就是这个兑换了,执行兑换的时候会首先根据智能路由制定兑换路径,然后调用兑换方法兑换代币。 需要注意如果是TRX的封包/解包兑换,仅仅是使用WTRX合约的方法执行。

我们执行以下测试:

let ret = e.Swap("TRX_USDT", 5, "sell")
Log(ret)

基于FMZ平台Web3 Tron封装接入SunSwap DEX的实践

基于FMZ平台Web3 Tron封装接入SunSwap DEX的实践

其它封装

还有一些其它功能例如「质押」、「解押」等。可以到模板代码中的main()函数中查看,就不再赘述。 也可以扩展增加添加流动性,移除流动性等功能。

模板代码

该模板没有任何交互设计界面参数,仅仅实现了接入 SunSwap DEX 的基本功能,后续还可以优化、添加更多功能。

”`js function isEmptyObject(obj) { return Object.keys(obj).length === 0 }

function computePoolPrice(decimals0, decimals1, sqrtPriceX96) { [decimals0, decimals1, sqrtPriceX96] = [decimals0, decimals1, sqrtPriceX96].map(BigInt) const TWO = BigInt(2) const TEN = BigInt(10) const SIX_TENTH = BigInt(1000000) const Q192 = (TWO ** BigInt(96)) ** TWO return (Number((sqrtPriceX96 ** TWO * TEN ** decimals0 * SIX_TENTH) / (Q192 * TEN ** decimals1)) / Number(SIX_TENTH)) }

function multicall(e, multicallContractAddress, data, outTypes) { let ret = e.IO(“api”, multicallContractAddress, “aggregate”, data) if (!ret || !ret[“returnData”] || !Array.isArray(ret[“returnData”])) { Log(“invalid ret:”, ret) return null }

let arrRet = ret["returnData"]
if (outTypes.length != arrRet.length) {
    Log("Arrays have unequal lengths:", arrRet, outTypes)
    return null
}

let outData = []
for (let i in arrRet) {
    outData.push(e.IO("decode", outTypes[i], arrRet[i]))
}

return outData

}

function toAmount(s, decimals) { return Number((BigDecimal(BigInt(s)) / BigDecimal(Math.pow(10, decimals))).toString()) }

function toInnerAmount(n, decimals) { return (BigDecimal(n) * BigDecimal(Math.pow(10, decimals))).toFixed(0) }

function hexToUint8Array(hex) { if (hex.length % 2 !== 0) { throw new Error(“Invalid hex string length”) } return Uint8Array.from( hex.match(/.{1,2}/g).map(byte => parseInt(byte, 16)) ) }

function uint8ArrayToHex(array) { return Array.from(array).map(byte => byte.toString(16).padStart(2, “0”)).join(“”) }

function ethAddressToTron(ethAddress) { if (!/^0x[0-9a-fA-F]{40}$/.test(ethAddress)) { throw “Invalid Ethereum address” }

const ethAddressBytes = new Uint8Array(20)
for (let i = 0; i < 20; i++) {
    ethAddressBytes[i] = parseInt(ethAddress.substr(2 + i * 2, 2), 16)
}

const tronAddressBytes = new Uint8Array(21)
tronAddressBytes[0] = 0x41
tronAddressBytes.set(ethAddressBytes, 1)

const hash1 = Encode("sha256", "hex", "hex", uint8ArrayToHex(tronAddressBytes))
const hash2 = Encode("sha256", "hex", "hex", hash1)
const checksum = hash2.slice(0, 8)

const fullAddress = new Uint8Array(25)
fullAddress.set(tronAddressBytes, 0)
fullAddress.set(hexToUint8Array(checksum), 21)

return base58Encode(fullAddress)

}

function base58Encode(buffer) { const base58Alphabet = “123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz” let num = BigInt(“0x” + Array.from(buffer).map(b => b.toString(16).padStart(2, “0”)).join(“”)) let encoded = “” while (num > 0) { let remainder = num % 58n num = num / 58n encoded = base58Alphabet[Number(remainder)] + encoded } for (let byte of buffer) { if (byte === 0) { encoded = base58Alphabet[0] + encoded } else { break } }

return encoded

}

function base58ToHex(base58Str) { const ALPHABET = “123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz”

var num = BigInt(0)
for (var char of base58Str) {
    var digit = BigInt(ALPHABET.indexOf(char))
    if (digit === BigInt(-1)) throw new Error("Invalid Base58 character: " + char)
    num = num * BigInt(58) + digit
}

var hex = num.toString(16)    
if (hex.length % 2 !== 0) {
    hex = "0" + hex
}

return "0x" + hex

}

$.CreateSunSwapEx = function(e) { let sunSwapEx = {}

sunSwapEx.registerABI = function(address, codeABI) {
    let e = sunSwapEx.e

    if (typeof(address) == "undefined" || typeof(codeABI) == "undefined") {
        throw "need address, codeABI"
    }

    return e.IO("abi", address, codeABI)
}

sunSwapEx.getCurrencyInfo = function(token0Address, token1Address) {
    let e = sunSwapEx.e

    let arrTokenAddress = [token0Address, token1Address]
    let tokenInfoCallData = []
    let tokenInfoRetType = []
    for (let tokenAddress of arrTokenAddress) {
        tokenInfoCallData.push([tokenAddress, "0x" + e.IO("encode", tokenAddress, "symbol")])
        tokenInfoRetType.push("string")
        tokenInfoCallData.push([tokenAddress, "0x" + e.IO("encode", tokenAddress, "name")])
        tokenInfoRetType.push("string")
        tokenInfoCallData.push([tokenAddress, "0x" + e.IO("encode", tokenAddress, "decimals")])
        tokenInfoRetType.push("uint8")
    }
    let currencyInfo = _C(multicall, e, multicallContractAddress, tokenInfoCallData, tokenInfoRetType)
    if (currencyInfo.length != 6) {
        Log("invalid currency Info:", currencyInfo)
        return null
    }
    let ret = {
        "token0Symbol": currencyInfo[0],
        "token0Name": currencyInfo[1],
        "token0Decimals": currencyInfo[2],
        "token1Symbol": currencyInfo[3],
        "token1Name": currencyInfo[4],
        "token1Decimals": currencyInfo[5]
    }

    return ret
}

sunSwapEx.waitMined = function(tx) {
    let e = sunSwapEx.e        
    let i = 0
    let maxLoop = 10

    while (true) {
        Sleep(3000)

        let info = e.IO("api", "tron", "GetTransactionInfoByID", tx)
        if (info && info.receipt && typeof(info.receipt.result) == "number") {
            Log("GetTransactionInfoByID:", info)
            if (info.receipt.result == 1) {
                return true
            } else {                
                return false 
            }            
        }

        Log("Transaction not yet mined", tx)
        if (i > maxLoop) {
            break
        }
        i++
    }

    Log(`Transaction: ${tx} not found`)
    return false 
}

sunSwapEx.getTokenInfo = function(tokenSymbol) {
    let markets = sunSwapEx.markets
    if (!markets || isEmptyObject(markets)) {
        markets = sunSwapEx.GetMarkets()
    }

    if (tokenSymbol == "TRX") {
        return {"tokenSymbol": tokenSymbol, "tokenName": tokenSymbol, "tokenDecimals": 6, "tokenAddress": "--"}
    }

    for (let currency in markets) {
        for (let market of markets[currency]) {
            if (market["BaseAsset"] == tokenSymbol) {
                return {"tokenSymbol": market["BaseAsset"], "tokenName": market["BaseName"], "tokenDecimals": market["BaseDecimals"], "tokenAddress": market["BaseAddress"]}
            } else if (market["QuoteAsset"] == tokenSymbol) {
                return {"tokenSymbol": market["QuoteAsset"], "tokenName": market["QuoteName"], "tokenDecimals": market["QuoteDecimals"], "tokenAddress": market["QuoteAddress"]}
            }
        }
    }

    Log("not found token symbol:", tokenSymbol)
    return null
}

sunSwapEx.dealStakeTRX = function(methodName, resourceName, amount) {
    let e = sunSwapEx.e

    let walletAddress = e.IO("address")
    let balance = toInnerAmount(amount, 6)
    let resourceCode = -1

    if (resourceName == "BANDWIDTH") {
        resourceCode = 0
    } else if (resourceName == "ENERGY") {
        resourceCode = 1
    } else if (resourceName == "TRON_POWER") {
        resourceCode = 2
    } else {
        Log("not support resourceName:", resourceName)
        return null 
    }

    return e.IO("api", "tron", methodName, walletAddress, resourceCode, balance)
}

sunSwapEx.GetMarkets = function() {
    // sunswap v3 pool

    let e = sunSwapEx.e

    if (!sunSwapEx.markets) {
        sunSwapEx.markets = {}
    }

    let markets = sunSwapEx.markets
    if (!isEmptyObject(markets)) {
        return markets
    }

    let factoryV3Address = sunSwapEx.factoryV3Address

    // allPoolsLength
    let poolIdx = e.IO("api", factoryV3Address, "allPoolsLength")
    if (!poolIdx) {
        Log("invalid poolIdx:", poolIdx)
        return null 
    }
    Log("allPoolsLength:", poolIdx)

    // multicallContractAddress
    let multicallContractAddress = sunSwapEx.multicallContractAddress

    // get All pool address
    let retPools = []
    let getPoolsData = []
    let retPoolsType = []
    for (let i = 0; i < poolIdx; i++) {
        getPoolsData.push([factoryV3Address, "0x" + e.IO("encode", factoryV3Address, "allPools", String(i))])
        retPoolsType.push("address")

        if (getPoolsData.length > 30 || i == poolIdx - 1) {
            let arr = _C(multicall, e, multicallContractAddress, getPoolsData, retPoolsType)

            retPools.push(...arr)
            getPoolsData = []
            retPoolsType = []
        } 
    }

    // allPools
    let poolABI = sunSwapEx.poolABI
    for (let i in retPools) {
        // poolAddress
        let poolAddress = ethAddressToTron(retPools[i])

        // register pool ABI
        sunSwapEx.registerABI(poolAddress, poolABI)

        // get token address of the pool
        let tokenCallData = [[poolAddress, "0x" + e.IO("encode", poolAddress, "token0")], [poolAddress, "0x" + e.IO("encode", poolAddress, "token1")]]
        let tokenRetType = ["address", "address"]
        let arrTokenAddress = _C(multicall, e, multicallContractAddress, tokenCallData, tokenRetType)

        let token0Address = ethAddressToTron(arrTokenAddress[0])
        let token1Address = ethAddressToTron(arrTokenAddress[1])

        // symbol , name , decimals
        let currencyInfo = sunSwapEx.getCurrencyInfo(token0Address, token1Address)
        if (!currencyInfo) {
            return null
        }

        let token0Symbol = currencyInfo["token0Symbol"]
        let token0Name = currencyInfo["token0Name"]
        let token0Decimals = currencyInfo["token0Decimals"]
        let token1Symbol = currencyInfo["token1Symbol"]
        let token1Name = currencyInfo["token1Name"]
        let token1Decimals = currencyInfo["token1Decimals"]

        // Alias
        let mapAlias = {
            "TJWm3jWaJeCdyRckEXfopsJBvZi6wXVK2p": "PAPA",
            "TPYmHEhy5n8TCEfYGqW2rPxsghSfzghPDn": "USDDOLD",
            "TTiwtPv4MFHMoCpGDBEF85qQjD2v4e99ck": "HOUSDOLD",
            "TRFe3hT5oYhjSZ6f3ji5FJ7YCfrkWnHRvh": "ETHB"
        }
        if (typeof(mapAlias[token0Address]) != "undefined") {
            token0Symbol = mapAlias[token0Address]
        }
        if (typeof(mapAlias[token1Address]) != "undefined") {
            token1Symbol = mapAlias[token1Address]
        }

        // bad
        let mapBad = {
            "TMEmRKPob42vhkqEGopCnfBPixN5XTkdUc": "U S D T",      // 注意:钓鱼合约
            "TXioUZK8ju3R54KvzSqPc6Ufq5wkxFqpq9": "U S D T",      // 注意:钓鱼合约
            "TU41FWQQT8hZ6t72gVEjtSGn1Dshbevi7g": "CNY Coin",     // 注意:无效
            "TYB7oXNq4VyuiH5BHb4os8rTh7Ca8pxqSx": "BULL",         // 注意:钓鱼合约
            "TYJxozCVMUiHg3TbQp9PKeuXdTsX9ugJz9": "SHISHA",       // 注意:不流通
            "TF9io9LGyjuK3uTpr73pAaQ5m9scxd9xvr": "TestToken18",  // 注意:测试交易对
            "TK8E3sFhBt3EB6gTT6d6co8RMB6DFUnNwE": "TestToken6"    // 注意:测试交易对
        }
        if (typeof(mapBad[token0Address]) != "undefined" || typeof(mapBad[token1Address]) != "undefined") {
            continue
        }

        // market
        let currency = token0Symbol + "_" + token1Symbol
        let market = {
            "Symbol": currency,
            "BaseAsset": token0Symbol,
            "QuoteAsset": token1Symbol,
            "BaseName": token0Name,
            "QuoteName": token1Name,
            "BaseDecimals": token0Decimals,
            "QuoteDecimals": token1Decimals,
            "BaseAddress": token0Address,
            "QuoteAddress": token1Address,
            "PoolAddress": poolAddress,
            "PoolIndex": i
        }

        if (!Array.isArray(markets[currency])) {
            markets[currency] = []
        }
        markets[currency].push(market)
        LogStatus(_D(), "get markets, length:", Object.keys(markets).length)

        Sleep(200)
    }

    _G("sunswap_markets", markets)
    sunSwapEx.markets = markets

    return markets
}

sunSwapEx.GetTicker = function(symbol, targetPoolAddress) {
    let e = sunSwapEx.e

    let markets = sunSwapEx.markets
    if (!markets || isEmptyObject(markets)) {
        markets = sunSwapEx.GetMarkets()
    }

    let arrPoolAddress = []
    let arrToken0Decimals = []
    let arrToken1Decimals = []
    for (let currency in markets) {        
        if (!Array.isArray(markets[currency]) || markets[currency].length == 0) {
            continue 
        }

        if (currency == symbol) {
            for (let ele of markets[currency]) {
                if (typeof(targetPoolAddress) != "undefined" && ele["PoolAddress"] != targetPoolAddress) {
                    continue 
                }
                arrPoolAddress.push(ele["PoolAddress"])
                arrToken0Decimals.push(ele["BaseDecimals"])
                arrToken1Decimals.push(ele["QuoteDecimals"])
            }
        }
    }

    if (arrPoolAddress.length == 0) {
        Log(`${symbol} and ${targetPoolAddress} not found`)
        sunSwapEx.markets = {}
        return null
    }

    let arrPrice = []
    let slot0CallData = []
    let slot0RetType = []
    for (let i in arrPoolAddress) {
        let poolAddress = arrPoolAddress[i]
        let poolABI = sunSwapEx.poolABI
        e.IO("abi", poolAddress, poolABI)
        slot0CallData.push([poolAddress, "0x" + e.IO("encode", poolAddress, "slot0")])
        slot0RetType.push("(uint160,int24,uint16,uint16,uint16,uint8,bool)")
    }
    let multicallContractAddress = sunSwapEx.multicallContractAddress
    let arrSlot0 = _C(multicall, e, multicallContractAddress, slot0CallData, slot0RetType)

    for (let i in arrSlot0) {
        let slot0 = arrSlot0[i]
        let token0Decimals = arrToken0Decimals[i]
        let token1Decimals = arrToken1Decimals[i]
        let sqrtPriceX96 = slot0["Field1"]
        let poolAddress = arrPoolAddress[i]
        let price = computePoolPrice(token0Decimals, token1Decimals, sqrtPriceX96)

        arrPrice.push({"price": price, "poolAddress": poolAddress})
    }

    return arrPrice
}

sunSwapEx.GetSwapOutAmount = function(symbol, amountIn, type) {
    let arrCurrency = symbol.split("_")
    if (arrCurrency.length != 2) {
        Log("invalid symbol:", symbol)
        return null
    }

    let baseCurrencyInfo = sunSwapEx.getTokenInfo(arrCurrency[0])
    let quoteCurrencyInfo = sunSwapEx.getTokenInfo(arrCurrency[1])
    if (!baseCurrencyInfo || !quoteCurrencyInfo) {
        return null 
    }

    let baseSymbol = baseCurrencyInfo["tokenSymbol"]
    let baseAddress = baseCurrencyInfo["tokenAddress"]
    let baseDecimals = baseCurrencyInfo["tokenDecimals"]
    let quoteSymbol = quoteCurrencyInfo["tokenSymbol"]
    let quoteAddress = quoteCurrencyInfo["tokenAddress"]
    let quoteDecimals = quoteCurrencyInfo["tokenDecimals"]

    // black hole address
    if (baseSymbol == "TRX") {
        baseAddress = "T9yD14Nj9j7xAB4dbGeiX9h8unkKHxuWwb"
    }
    if (quoteSymbol == "TRX") {
        quoteAddress = "T9yD14Nj9j7xAB4dbGeiX9h8unkKHxuWwb"
    }

    let query = null
    if (type == "buy") {
        let amount = toInnerAmount(amountIn, quoteDecimals)
        query = `?fromToken=${quoteAddress}&toToken=${baseAddress}&amountIn=${amount}&typeList=WTRX,SUNSWAP_V1,SUNSWAP_V2,SUNSWAP_V3`
    } else if (type == "sell") {
        let amount = toInnerAmount(amountIn, baseDecimals)
        query = `?fromToken=${baseAddress}&toToken=${quoteAddress}&amountIn=${amount}&typeList=WTRX,SUNSWAP_V1,SUNSWAP_V2,SUNSWAP_V3`
    } else {
        Log("not support type:", type)
        return null 
    }

    try {
        let ret = JSON.parse(HttpQuery("https://rot.endjgfsv.link/swap/router" + query))
        Log("https://rot.endjgfsv.link/swap/router" + query, "GetSwapOutAmount ret:", ret)

        if (!ret || ret["message"] != "SUCCESS") {
            Log("invalid data:", ret)
            return null
        }

        let outAmount = null
        let best = null 
        let info = ret["data"]
        for (let ele of info) {
            if (!outAmount) {
                outAmount = parseFloat(ele["amountOut"])
                best = ele
            } else {
                let amount = parseFloat(ele["amountOut"])
                if (amount > outAmount) {
                    outAmount = amount
                    best = ele
                }
            }
        }

        if (!outAmount || !best) {
            Log("info:", info)
            return null 
        }

        return {"info": info, "outAmount": outAmount, "best": best, "baseCurrencyInfo": baseCurrencyInfo, "quoteCurrencyInfo": quoteCurrencyInfo}
    } catch(e) {
        Log("e.name:", e.name, "e.stack:", e.stack, "e.message:", e.message)
        return null
    }
}

sunSwapEx.GetAccount = function() {
    let e = sunSwapEx.e

    let walletAddress = e.IO("address")
    let account = e.IO("api", "tron", "GetAccount", walletAddress)
    if (!account) {
        Log("get account failed, account:", account)
        return null 
    }

    let trxBalance = toAmount(account["balance"], 6)

    let resource = e.IO("api", "tron", "GetAccountResource", walletAddress)
    if (!resource) {
        Log("get resource failed, resource:", resource)
        return null 
    }

    let energyLimit = resource["EnergyLimit"] ? resource["EnergyLimit"] : 0
    let freeNetLimit = resource["freeNetLimit"] ? resource["freeNetLimit"] : 0

    return {"Info": {"GetAccount": account, "GetAccountResource": resource}, "TRX": trxBalance, "Bandwidth": freeNetLimit, "Energy": energyLimit}
}

sunSwapEx.GetAssets = function() {
    let e = sunSwapEx.e
    let walletAddress = e.IO("address")

    try {
        let ret = JSON.parse(HttpQuery("https://apilist.tronscanapi.com/api/account/token_asset_overview?address=" + walletAddress))

        let assets = []
        for (let info of ret["data"]) {
            let decimals = parseInt(info["tokenDecimal"])
            let asset = {
                "Currency": info["tokenAbbr"] == "trx" ? "TRX" : info["tokenAbbr"],
                "Decimals": decimals,
                "Amount": toAmount(info["balance"], decimals),
                "Address": info["tokenId"],
            }

            assets.push(asset)
        }

        return assets
    } catch(e) {
        Log("e.name:", e.name, "e.stack:", e.stack, "e.message:", e.message)
        return null
    }
}

sunSwapEx.StakeTRX = function(resourceName, frozenBalance) {
    return sunSwapEx.dealStakeTRX("FreezeBalanceV2", resourceName, frozenBalance)
}

sunSwapEx.UnStakeTRX = function(resourceName, unfreezeBalance) {
    return sunSwapEx.dealStakeTRX("UnfreezeBalanceV2", resourceName, unfreezeBalance)
}

sunSwapEx.WrappedTRX = function(actionType, amountIn) {
    let e = sunSwapEx.e

    let tx = null
    if (actionType == "deposit") {
        let amount = toInnerAmount(amountIn, 6)
        tx = e.IO("api", "TNUC9Qb1rRpS5CbWLmNMxXBjyFoydXjWFR", "deposit", amount, {gasLimit: toInnerAmount(sunSwapEx.feeLimit, 6)})
    } else if (actionType == "withdraw") {
        tx = e.IO("api", "TNUC9Qb1rRpS5CbWLmNMxXBjyFoydXjWFR", "withdraw(uint256)", toInnerAmount(amountIn, 6), {gasLimit: toInnerAmount(sunSwapEx.feeLimit, 6)})
    } else {
        Log("not support actionType:", actionType)
        return false 
    }

    if (tx) {
        Log("tx: ", tx)
        let txRet = sunSwapEx.waitMined(tx)
        if (!txRet) {
            Log(actionType, "failed")
            return false
        } else {
            Log(actionType, "success")
            return true
        }
    } else {
        Log("trans error")
        return false
    }
}

sunSwapEx.Swap = function(symbol, amountIn, type) {
    let e = sunSwapEx.e
    let smartRouterAddress = sunSwapEx.smartRouterAddress

    if (symbol == "TRX_WTRX" || symbol == "WTRX_TRX") {
        let actionType = null 
        if (type == "buy") {
            actionType = symbol == "TRX_WTRX" ? "withdraw" : "deposit"
        } else if (type == "sell") {
            actionType = symbol == "TRX_WTRX" ? "deposit" : "withdraw"
        } else {
            Log("invalid type:", type)
            return false 
        }

        return sunSwapEx.WrappedTRX(actionType, amountIn)
    }

    let swapInfo = sunSwapEx.GetSwapOutAmount(symbol, amountIn, type)
    if (!swapInfo || !swapInfo["best"]) {
        Log("invalid swapInfo:", swapInfo)
        return false
    }

    let outAmount = swapInfo["outAmount"]
    let best = swapInfo["best"]

    // path
    let path = best["tokens"]
    if (!path || path.length < 2) {
        Log("invalid path:", path)
        return false 
    }

    // poolVersions and versionsLen
    let poolVersions = [] 
    let versionsLen = []
    for (var v of best["poolVersions"]) {
        if (poolVersions.length == 0) {
            poolVersions.push(v)
            versionsLen.push(2)
        } else {
            if (poolVersions[poolVersions.length - 1] == v) {
                versionsLen[versionsLen.length - 1] += 1
            } else {
                poolVersions.push(v)
                versionsLen.push(1)
            }
        }
    }

    // fees 
    let poolFees = best["poolFees"]

    // get decimals , token name 
    let token0Decimals = swapInfo["baseCurrencyInfo"]["tokenDecimals"]
    let token1Decimals = swapInfo["quoteCurrencyInfo"]["tokenDecimals"]

    let tokenInName = type == "buy" ? swapInfo["quoteCurrencyInfo"]["tokenSymbol"] : swapInfo["baseCurrencyInfo"]["tokenSymbol"]
    let tokenOutName = type == "buy" ? swapInfo["baseCurrencyInfo"]["tokenSymbol"] : swapInfo["quoteCurrencyInfo"]["tokenSymbol"]
    let tokenInAddress = type == "buy" ? swapInfo["quoteCurrencyInfo"]["tokenAddress"] : swapInfo["baseCurrencyInfo"]["tokenAddress"]
    let tokenOutAddress = type == "buy" ? swapInfo["baseCurrencyInfo"]["tokenAddress"] : swapInfo["quoteCurrencyInfo"]["tokenAddress"]
    let tokenInDecimals = type == "buy" ? token1Decimals : token0Decimals

    // calc amount
    let amount = null 
    let minAmount = null 
    if (type == "buy") {
        amount = toInnerAmount(amountIn, token1Decimals)
        minAmount = toInnerAmount(outAmount * 0.99, token0Decimals)
    } else if (type == "sell") {
        amount = toInnerAmount(amountIn, token0Decimals)
        minAmount = toInnerAmount(outAmount * 0.99, token1Decimals)
    } else {
        Log("invalid type:", type)
        return false 
    }

    // wallet address 
    let walletAddress = e.IO("address")

    // expired timestamp
    let expiredTS = parseInt((new Date().getTime() + 1000 * 60 * 5) / 1000)

    // [amount of the token to be swapped, minimum acceptable amount of the token obtained from the swap, address to receive the token obtained from the swap, deadline]
    let data = [String(amount), String(minAmount), walletAddress, expiredTS]

    // allowance
    if (tokenInName != "TRX" && !(tokenInName == "WTRX" && tokenOutName == "TRX")) {
        let allowanceAmount = e.IO("api", tokenInAddress, "allowance", walletAddress, smartRouterAddress)
        let realAmount = toAmount(allowanceAmount, tokenInDecimals)
        if (realAmount < amountIn) {
            // approve
            Log("realAmount is", realAmount, "too small, try to approve large amount")
            let txApprove = e.IO("api", tokenInAddress, "approve(address,uint256)", smartRouterAddress, "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", {gasLimit: toInnerAmount(sunSwapEx.feeLimit, 6)})
            if (!txApprove) {
                throw "approve error"
            }

            let txRet = sunSwapEx.waitMined(txApprove)
            if (!txRet) {
                Log("approve failed")
                return false 
            } else {
                Log("approve success")
            }
        } else {
            Log("allowance", realAmount, "no need to approve")
        }
    }

    // swap
    Log(`path:${path}, poolVersions:${poolVersions}, versionsLen:${versionsLen}, poolFees:${poolFees}, data:${data}`)
    Log("best swap:", best)
    let tx = e.IO("api", smartRouterAddress, "swapExactInput(address[],string[],uint256[],uint24[],(uint256,uint256,address,uint256))", tokenInName == "TRX" ? toInnerAmount(amountIn, 6) : 0, path, poolVersions, versionsLen, poolFees, data, {gasLimit: toInnerAmount(sunSwapEx.feeLimit, 6)})
    if (tx) {
        Log("tx: ", tx)
        let txRet = sunSwapEx.waitMined(tx)
        if (!txRet) {
            Log("swap", tokenInName, "to", tokenOutName, "failed")
            return false
        } else {
            Log("swap", tokenInName, "to", tokenOutName, "success")
            return true
        }            
    } else {
        Log("trans error")
        return false
    }
}

sunSwapEx.SendTRX = function(to, amount) {
    let e = sunSwapEx.e
    return e.IO("api", "tron", "send", to, toInnerAmount(amount, 6))
}

sunSwapEx.SetFeeLimit = function(feeLimit) {
    sunSwapEx.feeLimit = feeLimit
    Log("SetFeeLimit, feeLimit:", sunSwapEx.feeLimit, ", toInnerAmount:", toInnerAmount(sunSwapEx.feeLimit, 6))        
}

// init
if (typeof(e) == "undefined") {
    e = exchange
    Log("默认使用exchange配置")
}
sunSwapEx.e = e

// tron
let ret = e.IO("api", "tron", "GetNodeInfo")
if (!ret) {
    throw "当前Web3 tron交易所对象可能配置有误"
} else {
    Log("node节点信息:", ret)
}

// feeLimit
sunSwapEx.feeLimit = 50

// register abi
let factoryV3Address = "TThJt8zaJzJMhCEScH7zWKnp5buVZqys9x"
sunSwapEx.factoryV3Address = factoryV3Address

let poolABI = `[{"inputs":[],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[{"indexed":true,"name":"owner","internalType":"address","type":"address"},{"indexed":true,"name":"tickLower","internalType":"int24","type":"int24"},{"indexed":true,"name":"tickUpper","internalType":"int24","type":"int24"},{"indexed":false,"name":"amount","internalType":"uint128","type":"uint128"},{"indexed":false,"name":"amount0","internalType":"uint256","type":"uint256"},{"indexed":false,"name":"amount1","internalType":"uint256","type":"uint256"}],"name":"Burn","anonymous":false,"type":"event"},{"inputs":[{"indexed":true,"name":"owner","internalType":"address","type":"address"},{"indexed":false,"name":"recipient","internalType":"address","type":"address"},{"indexed":true,"name":"tickLower","internalType":"int24","type":"int24"},{"indexed":true,"name":"tickUpper","internalType":"int24","type":"int24"},{"indexed":false,"name":"amount0","internalType":"uint128","type":"uint128"},{"indexed":false,"name":"amount1","internalType":"uint128","type":"uint128"}],"name":"Collect","anonymous":false,"type":"event"},{"inputs":[{"indexed":true,"name":"sender","internalType":"address","type":"address"},{"indexed":true,"name":"recipient","internalType":"address","type":"address"},{"indexed":false,"name":"amount0","internalType":"uint128","type":"uint128"},{"indexed":false,"name":"amount1","internalType":"uint128","type":"uint128"}],"name":"CollectProtocol","anonymous":false,"type":"event"},{"inputs":[{"indexed":true,"name":"sender","internalType":"address","type":"address"},{"indexed":true,"name":"recipient","internalType":"address","type":"address"},{"indexed":false,"name":"amount0","internalType":"uint256","type":"uint256"},{"indexed":false,"name":"amount1","internalType":"uint256","type":"uint256"},{"indexed":false,"name":"paid0","internalType":"uint256","type":"uint256"},{"indexed":false,"name":"paid1","internalType":"uint256","type":"uint256"}],"name":"Flash","anonymous":false,"type":"event"},{"inputs":[{"indexed":false,"name":"observationCardinalityNextOld","internalType":"uint16","type":"uint16"},{"indexed":false,"name":"observationCardinalityNextNew","internalType":"uint16","type":"uint16"}],"name":"IncreaseObservationCardinalityNext","anonymous":false,"type":"event"},{"inputs":[{"indexed":false,"name":"sqrtPriceX96","internalType":"uint160","type":"uint160"},{"indexed":false,"name":"tick","internalType":"int24","type":"int24"}],"name":"Initialize","anonymous":false,"type":"event"},{"inputs":[{"indexed":false,"name":"sender","internalType":"address","type":"address"},{"indexed":true,"name":"owner","internalType":"address","type":"address"},{"indexed":true,"name":"tickLower","internalType":"int24","type":"int24"},{"indexed":true,"name":"tickUpper","internalType":"int24","type":"int24"},{"indexed":false,"name":"amount","internalType":"uint128","type":"uint128"},{"indexed":false,"name":"amount0","internalType":"uint256","type":"uint256"},{"indexed":false,"name":"amount1","internalType":"uint256","type":"uint256"}],"name":"Mint","anonymous":false,"type":"event"},{"inputs":[{"indexed":false,"name":"feeProtocol0Old","internalType":"uint8","type":"uint8"},{"indexed":false,"name":"feeProtocol1Old","internalType":"uint8","type":"uint8"},{"indexed":false,"name":"feeProtocol0New","internalType":"uint8","type":"uint8"},{"indexed":false,"name":"feeProtocol1New","internalType":"uint8","type":"uint8"}],"name":"SetFeeProtocol","anonymous":false,"type":"event"},{"inputs":[{"indexed":true,"name":"sender","internalType":"address","type":"address"},{"indexed":true,"name":"recipient","internalType":"address","type":"address"},{"indexed":false,"name":"amount0","internalType":"int256","type":"int256"},{"indexed":false,"name":"amount1","internalType":"int256","type":"int256"},{"indexed":false,"name":"sqrtPriceX96","internalType":"uint160","type":"uint160"},{"indexed":false,"name":"liquidity","internalType":"uint128","type":"uint128"},{"indexed":false,"name":"tick","internalType":"int24","type":"int24"}],"name":"Swap","anonymous":false,"type":"event"},{"outputs":[{"name":"","internalType":"address","type":"address"}],"inputs":[],"name":"factory","stateMutability":"view","type":"function"},{"outputs":[{"name":"","internalType":"uint24","type":"uint24"}],"inputs":[],"name":"fee","stateMutability":"view","type":"function"},{"outputs":[{"name":"","internalType":"uint256","type":"uint256"}],"inputs":[],"name":"feeGrowthGlobal0X128","stateMutability":"view","type":"function"},{"outputs":[{"name":"","internalType":"uint256","type":"uint256"}],"inputs":[],"name":"feeGrowthGlobal1X128","stateMutability":"view","type":"function"},{"outputs":[{"name":"","internalType":"uint128","type":"uint128"}],"inputs":[],"name":"liquidity","stateMutability":"view","type":"function"},{"outputs":[{"name":"","internalType":"uint128","type":"uint128"}],"inputs":[],"name":"maxLiquidityPerTick","stateMutability":"view","type":"function"},{"outputs":[{"name":"blockTimestamp","internalType":"uint32","type":"uint32"},{"name":"tickCumulative","internalType":"int56","type":"int56"},{"name":"secondsPerLiquidityCumulativeX128","internalType":"uint160","type":"uint160"},{"name":"initialized","internalType":"bool","type":"bool"}],"inputs":[{"name":"","internalType":"uint256","type":"uint256"}],"name":"observations","stateMutability":"view","type":"function"},{"outputs":[{"name":"liquidity","internalType":"uint128","type":"uint128"},{"name":"feeGrowthInside0LastX128","internalType":"uint256","type":"uint256"},{"name":"feeGrowthInside1LastX128","internalType":"uint256","type":"uint256"},{"name":"tokensOwed0","internalType":"uint128","type":"uint128"},{"name":"tokensOwed1","internalType":"uint128","type":"uint128"}],"inputs":[{"name":"","internalType":"bytes32","type":"bytes32"}],"name":"positions","stateMutability":"view","type":"function"},{"outputs":[{"name":"token0","internalType":"uint128","type":"uint128"},{"name":"token1","internalType":"uint128","type":"uint128"}],"inputs":[],"name":"protocolFees","stateMutability":"view","type":"function"},{"outputs":[{"name":"sqrtPriceX96","internalType":"uint160","type":"uint160"},{"name":"tick","internalType":"int24","type":"int24"},{"name":"observationIndex","internalType":"uint16","type":"uint16"},{"name":"observationCardinality","internalType":"uint16","type":"uint16"},{"name":"observationCardinalityNext","internalType":"uint16","type":"uint16"},{"name":"feeProtocol","internalType":"uint8","type":"uint8"},{"name":"unlocked","internalType":"bool","type":"bool"}],"inputs":[],"name":"slot0","stateMutability":"view","type":"function"},{"outputs":[{"name":"","internalType":"uint256","type":"uint256"}],"inputs":[{"name":"","internalType":"int16","type":"int16"}],"name":"tickBitmap","stateMutability":"view","type":"function"},{"outputs":[{"name":"","internalType":"int24","type":"int24"}],"inputs":[],"name":"tickSpacing","stateMutability":"view","type":"function"},{"outputs":[{"name":"liquidityGross","internalType":"uint128","type":"uint128"},{"name":"liquidityNet","internalType":"int128","type":"int128"},{"name":"feeGrowthOutside0X128","internalType":"uint256","type":"uint256"},{"name":"feeGrowthOutside1X128","internalType":"uint256","type":"uint256"},{"name":"tickCumulativeOutside","internalType":"int56","type":"int56"},{"name":"secondsPerLiquidityOutsideX128","internalType":"uint160","type":"uint160"},{"name":"secondsOutside","internalType":"uint32","type":"uint32"},{"name":"initialized","internalType":"bool","type":"bool"}],"inputs":[{"name":"","internalType":"int24","type":"int24"}],"name":"ticks","stateMutability":"view","type":"function"},{"outputs":[{"name":"","internalType":"address","type":"address"}],"inputs":[],"name":"token0","stateMutability":"view","type":"function"},{"outputs":[{"name":"","internalType":"address","type":"address"}],"inputs":[],"name":"token1","stateMutability":"view","type":"function"},{"outputs":[{"name":"tickCumulativeInside","internalType":"int56","type":"int56"},{"name":"secondsPerLiquidityInsideX128","internalType":"uint160","type":"uint160"},{"name":"secondsInside","internalType":"uint32","type":"uint32"}],"inputs":[{"name":"tickLower","internalType":"int24","type":"int24"},{"name":"tickUpper","internalType":"int24","type":"int24"}],"name":"snapshotCumulativesInside","stateMutability":"view","type":"function"},{"outputs":[{"name":"tickCumulatives","internalType":"int56[]","type":"int56[]"},{"name":"secondsPerLiquidityCumulativeX128s","internalType":"uint160[]","type":"uint160[]"}],"inputs":[{"name":"secondsAgos","internalType":"uint32[]","type":"uint32[]"}],"name":"observe","stateMutability":"view","type":"function"},{"outputs":[],"inputs":[{"name":"observationCardinalityNext","internalType":"uint16","type":"uint16"}],"name":"increaseObservationCardinalityNext","stateMutability":"nonpayable","type":"function"},{"outputs":[],"inputs":[{"name":"sqrtPriceX96","internalType":"uint160","type":"uint160"}],"name":"initialize","stateMutability":"nonpayable","type":"function"},{"outputs":[{"name":"amount0","internalType":"uint256","type":"uint256"},{"name":"amount1","internalType":"uint256","type":"uint256"}],"inputs":[{"name":"recipient","internalType":"address","type":"address"},{"name":"tickLower","internalType":"int24","type":"int24"},{"name":"tickUpper","internalType":"int24","type":"int24"},{"name":"amount","internalType":"uint128","type":"uint128"},{"name":"data","internalType":"bytes","type":"bytes"}],"name":"mint","stateMutability":"nonpayable","type":"function"},{"outputs":[{"name":"amount0","internalType":"uint128","type":"uint128"},{"name":"amount1","internalType":"uint128","type":"uint128"}],"inputs":[{"name":"recipient","internalType":"address","type":"address"},{"name":"tickLower","internalType":"int24","type":"int24"},{"name":"tickUpper","internalType":"int24","type":"int24"},{"name":"amount0Requested","internalType":"uint128","type":"uint128"},{"name":"amount1Requested","internalType":"uint128","type":"uint128"}],"name":"collect","stateMutability":"nonpayable","type":"function"},{"outputs":[{"name":"amount0","internalType":"uint256","type":"uint256"},{"name":"amount1","internalType":"uint256","type":"uint256"}],"inputs":[{"name":"tickLower","internalType":"int24","type":"int24"},{"name":"tickUpper","internalType":"int24","type":"int24"},{"name":"amount","internalType":"uint128","type":"uint128"}],"name":"burn","stateMutability":"nonpayable","type":"function"},{"outputs":[{"name":"amount0","internalType":"int256","type":"int256"},{"name":"amount1","internalType":"int256","type":"int256"}],"inputs":[{"name":"recipient","internalType":"address","type":"address"},{"name":"zeroForOne","internalType":"bool","type":"bool"},{"name":"amountSpecified","internalType":"int256","type":"int256"},{"name":"sqrtPriceLimitX96","internalType":"uint160","type":"uint160"},{"name":"data","internalType":"bytes","type":"bytes"}],"name":"swap","stateMutability":"nonpayable","type":"function"},{"outputs":[],"inputs":[{"name":"recipient","internalType":"address","type":"address"},{"name":"amount0","internalType":"uint256","type":"uint256"},{"name":"amount1","internalType":"uint256","type":"uint256"},{"name":"data","internalType":"bytes","type":"bytes"}],"name":"flash","stateMutability":"nonpayable","type":"function"},{"outputs":[],"inputs":[{"name":"feeProtocol0","internalType":"uint8","type":"uint8"},{"name":"feeProtocol1","internalType":"uint8","type":"uint8"}],"name":"setFeeProtocol","stateMutability":"nonpayable","type":"function"},{"outputs":[{"name":"amount0","internalType":"uint128","type":"uint128"},{"name":"amount1","internalType":"uint128","type":"uint128"}],"inputs":[{"name":"recipient","internalType":"address","type":"address"},{"name":"amount0Requested","internalType":"uint128","type":"uint128"},{"name":"amount1Requested","internalType":"uint128","type":"uint128"}],"name":"collectProtocol","stateMutability":"nonpayable","type":"function"}]`
sunSwapEx.poolABI = poolABI

let multicallContractAddress = "TGXuuKAb4bnrn137u39EKbYzKNXvdCes98"
sunSwapEx.multicallContractAddress = multicallContractAddress
let multicallContractABI = `[{"inputs":[{"components":[{"internalType":"address","name":"target","type":"address"},{"internalType":"bytes","name":"callData","type":"bytes"}],"internalType":"struct TronMulticall.Call[]","name":"calls","type":"tuple[]"}],"name":"aggregate","outputs":[{"internalType":"uint256","name":"blockNumber","type":"uint256"},{"internalType":"bytes[]","name":"returnData","type":"bytes[]"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getBasefee","outputs":[{"internalType":"uint256","name":"basefee","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"blockNumber","type":"uint256"}],"name":"getBlockHash","outputs":[{"internalType":"bytes32","name":"blockHash","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getBlockNumber","outputs":[{"internalType":"uint256","name":"blockNumber","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getChainId","outputs":[{"internalType":"uint256","name":"chainid","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getCurrentBlockCoinbase","outputs":[{"internalType":"address","name":"coinbase","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getCurrentBlockDifficulty","outputs":[{"internalType":"uint256","name":"difficulty","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getCurrentBlockTimestamp","outputs":[{"internalType":"uint256","name":"timestamp","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"addr","type":"address"}],"name":"getEthBalance","outputs":[{"internalType":"uint256","name":"balance","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getLastBlockHash","outputs":[{"internalType":"bytes32","nam
相关推荐