avatar of 发明者量化-小小梦 发明者量化-小小梦
フォロー ダイレクトメッセージ
4
フォロー
1271
フォロワー

FMZプラットフォームに基づくWeb3 TronパッケージングとSunSwap DEXへのアクセスの実践

作成日:: 2025-03-21 15:17:05, 更新日:: 2025-03-28 09:52:32
comments   0
hits   715

[TOC]

序文

ブロックチェーン取引の分野では、分散型取引所(DEX)が信頼のない資産交換方法を提供しており、Tronエコシステムの代表的なDEXであるSunSwapは、Web3に基づくスマートコントラクトのやり取りをサポートしています。 FMZ プラットフォーム (FMZ Quantitative) は、開発者が複数の取引戦略を統合できるようにする強力な定量取引フレームワークを提供します。

FMZ プラットフォームはすでに Web3 Ethereum (ETH) 交換オブジェクトのカプセル化をサポートしており、最近では Web3 TRON のサポートが追加され、TRON チェーンの交換オブジェクトがカプセル化されました。 UniSwap DEX へのアクセスと同様に、SunSwap DEX へのアクセスも可能です。

この記事では、FMZ 定量プラットフォームに基づいて Web3 インターフェイスをカプセル化し、トランザクションのために SunSwap DEX に接続する方法について説明します。 Tron ネットワークの Web3 対話方式、SunSwap スマート コントラクト呼び出しを紹介し、コード例を通じて流動性を照会し、トランザクションを実行し、トランザクション結果を取得する方法を示して、効率的な定量的取引戦略を構築します。

声明

この記事で共有されているすべてのコードとデザインはテスト済みであり、学習とコミュニケーションを目的としています。ただし、テスト後でも、実際のニーズに基づいて調整する必要がある場合があります。実際の取引に適用する必要がある場合は、必ずご自身で評価、最適化、修正し、ご自身のニーズを満たしていることを確認してください。

準備を整える

  • FMZプラットフォームのWeb3 TRON交換オブジェクトを構成する

前の記事「FMZ Quantitative Web3 拡張: Tron サポートを追加し、オンチェーン トランザクション機能を拡張」この記事では、Tron gRPC ノードを設定する一般的な方法と、FMZ 上の Web3 TRON 交換オブジェクトを構成する方法を学びました。この記事ではこれらの内容を繰り返すつもりはありません。ノードアドレスについてはTRONが公式に提供しているノードをそのまま使用します。

  • ホストを展開する

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

CEX 取引所とは異なり、心配するような壁はありません。ホストをローカルにデプロイすることで、公式ノードにアクセスできます。

  • SunSwap DEX

https://sun.io/

ブラウザで SunSwap のフロントエンドページを開くと、ウォレットに接続してデータを簡単に確認できます。

  • tronscan

https://tronscan.org/

データを表示および分析するための必須ツール。

  • base58

TRON 上のアドレスは base58 でエンコードされており、Ethereum とは異なります。データを表示および比較するには、場合によっては変換が必要になります。 FMZ では、base58 でエンコードされたアドレスを直接使用できます。一部のスマート コントラクトは HEX エンコードでデータを返すため、何らかの変換が必要になります。アドレス形式を変換するために、いくつかの関数が簡単に実装されています。

主な変換機能は以下のとおりです。スペースが限られているため、すべてのコードを掲載することはできません。完全なコードは、記事の最後に公開されている「Tron SunSwap V3 Trading Library」テンプレート ライブラリにあります。

  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)
  }
  • パッケージングコール

一度に複数のメソッドからデータを読み取る必要がある場合があるため、メソッドを 1 つずつ呼び出すのは時間がかかり、面倒です。したがって、パッケージ化された呼び出し関数はカプセル化されます。

  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交易对象。

このテンプレートには、次のインターフェース関数が 1 つだけあります。$.CreateSunSwapEx()SunSwap トランザクション オブジェクトを作成するために使用されます。オブジェクトが作成された後、このオブジェクトのメソッドを呼び出して、償還トランザクション、プール価格の照会、すべての V3 プール情報の取得、誓約、解凍などの機能呼び出しを行うことができます。

GetMarkets

「Tron SunSwap V3 Trading Library」のテストコードはテンプレートにあります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 プールのすべての交換プールの関連情報を取得するために使用され、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メソッド呼び出しはエンコードされ、データは 1 回要求されます。

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 を指定すると、可能な交換パス、出力トークンの数、その他の情報を返すことができます。

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 トークンをトラバースするための適切なメカニズムがないため、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 契約方式のみで実行されます。

テンプレートコードに追加できる他の関数がいくつかあります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","internalTy