avatar of 发明者量化-小小梦 发明者量化-小小梦
focar em Mensagem privada
4
focar em
1271
Seguidores

Prática de empacotamento Web3 Tron e acesso ao SunSwap DEX com base na plataforma FMZ

Criado em: 2025-03-21 15:17:05, atualizado em: 2025-03-28 09:52:32
comments   0
hits   715

[TOC]

Prefácio

No campo das transações de blockchain, as exchanges descentralizadas (DEX) fornecem uma maneira confiável de trocar ativos, e a SunSwap, como uma DEX representativa no ecossistema Tron, oferece suporte a interações de contratos inteligentes baseadas na Web3. A plataforma FMZ (FMZ Quantitative) fornece uma poderosa estrutura de negociação quantitativa que permite aos desenvolvedores integrar diversas estratégias de negociação.

A plataforma FMZ já suporta o encapsulamento de objetos de troca Web3 Ethereum (ETH) e recentemente adicionou suporte para Web3 TRON, encapsulando os objetos de troca da cadeia TRON. Assim como acessar o UniSwap DEX, o acesso ao SunSwap DEX também pode ser obtido.

Este artigo explorará como encapsular a interface Web3 com base na plataforma quantitativa FMZ e se conectar ao SunSwap DEX para transações. Apresentaremos o método de interação Web3 da rede Tron, a chamada de contrato inteligente SunSwap e mostraremos por meio de exemplos de código como consultar liquidez, executar transações e obter resultados de transações, para construir uma estratégia de negociação quantitativa eficiente.

declaração

Todos os códigos e designs compartilhados neste artigo foram testados e são destinados ao aprendizado e à comunicação. Entretanto, mesmo após os testes, talvez seja necessário ajustá-los com base nas necessidades reais. Se você precisar aplicá-lo em negociações reais, certifique-se de avaliá-lo, otimizá-lo e modificá-lo você mesmo para garantir que ele atenda às suas próprias necessidades.

Preparação

  • Configurar o objeto de troca Web3 TRON da plataforma FMZ

No artigo anterior「Expansão FMZ Quantitative Web3: Adicione suporte Tron e expanda as capacidades de transação on-chain」Neste artigo apresentamosTron gRPC Aprendemos os métodos comuns de configuração de nós e como configurar o objeto de troca Web3 TRON no FMZ. Este artigo não repetirá esses conteúdos. Quanto ao endereço do nó, usaremos diretamente o nó fornecido oficialmente pelo TRON.

  • Implantar o host

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

Ao contrário das bolsas CEX, não há barreiras com as quais se preocupar. Você pode acessar o nó oficial implantando um host localmente.

  • SunSwap DEX

https://sun.io/

Abra a página inicial do SunSwap no navegador, você pode se conectar à carteira e observar os dados convenientemente.

  • tronscan

https://tronscan.org/

Ferramentas essenciais para visualizar e analisar dados.

  • base58

Os endereços no TRON são codificados em base58, o que é diferente do Ethereum. Às vezes, alguma conversão é necessária para visualizar e comparar dados. Na FMZ, você pode usar diretamente endereços codificados em base58. Alguns contratos inteligentes retornam dados em codificação HEX, o que requer alguma conversão. Várias funções são implementadas de forma simples para converter formatos de endereço:

A seguir estão as principais funções de conversão. Devido ao espaço limitado, não postarei todos os códigos. O código completo está na biblioteca de modelos “Tron SunSwap V3 Trading Library” divulgada no final do artigo.

  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)
  }
  • Chamada de pacote

Como às vezes você precisa ler dados de vários métodos ao mesmo tempo, chamá-los um por um é demorado e trabalhoso. Então uma função de chamada empacotada é encapsulada:

  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
  }

Biblioteca de negociação Tron SunSwap V3

O design da biblioteca de negociação SunSwap V3 é modelado com base no modelo de biblioteca de negociação UniSwap da plataforma FMZ. Como o código do modelo SunSwap é relativamente longo, não o publicarei aqui, mas explicarei principalmente as funções implementadas.

Criar objeto de transação SunSwap

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

Este modelo tem apenas uma função de interface, a saber:$.CreateSunSwapEx()Usado para criar objetos de transação SunSwap. Após a criação do objeto, você pode chamar o método deste objeto para fazer algumas chamadas funcionais, como: transações de resgate, consultar preços do pool, obter todas as informações do pool V3, penhor, descompressão, etc.

GetMarkets

O código de teste da “Tron SunSwap V3 Trading Library” está no modelomain()A função foi gravada e não será repetida aqui.

    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) + "`")

funçãoGetMarkets()Usado para obter informações relevantes de todos os pools de troca do pool SunSwap V3, armazenados em cache nas variáveis ​​dos objetos de transação do SunSwap e usados ​​para consultar precisão, endereço e outras informações durante outras operações.

Este código de teste exibirá informações sobre todos os pools V3 após a execução:

Prática de empacotamento Web3 Tron e acesso ao SunSwap DEX com base na plataforma FMZ

Alguns dos endereços de contrato, como “contratos de phishing” e “contratos inválidos”, que foram vistos até agora foram removidos do código.

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)

DisponívelGetTicker()A função consulta o preço de um determinado pool de câmbio e pode especificar o endereço de um pool específico. Observe que alguns pools têm liquidez muito baixa e os preços são apenas para referência.

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))

Esta função pode ser usada para encapsulamento de funções subsequente. Por exemplo, no exemplo, você pode colocarbalanceOfA chamada do método é codificada e os dados são solicitados uma vez.

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))

Esta é uma função relativamente importante, que encapsula o serviço de roteamento inteligente do SunSwap. Dados tokenInSymbol e tokenOutSymbol, ele pode retornar possíveis caminhos de troca, o número de tokens de saída e outras informações.

Prática de empacotamento Web3 Tron e acesso ao SunSwap DEX com base na plataforma FMZ

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) + "`")

Como não há um bom mecanismo para percorrer tokens TRC20, com a ajuda da travessia do tronscan, essa interface de terceiros é usada para consultar informações de ativos da conta.

Prática de empacotamento Web3 Tron e acesso ao SunSwap DEX com base na plataforma FMZ

Prática de empacotamento Web3 Tron e acesso ao SunSwap DEX com base na plataforma FMZ

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)

A função mais importante é essa troca. Ao executar a troca, o caminho de troca será formulado de acordo com o roteamento inteligente e, então, o método de troca será chamado para trocar os tokens. Observe que, se for uma troca de embalagem/desembalagem TRX, ela só será executada usando o método de contrato WTRX.

Existem algumas outras funções que podem ser adicionadas ao código do modelomain()Função, não vou entrar em detalhes.

Código do modelo

Este modelo não possui nenhumDesign de interaçãoeParâmetros de interface, ele realiza apenas a função básica de acessar o SunSwap DEX, e mais funções podem ser otimizadas e adicionadas no futuro.

”`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","nam