4
ध्यान केंद्रित करना
1271
समर्थक

FMZ प्लेटफॉर्म पर आधारित वेब3 ट्रॉन पैकेजिंग और सनस्वैप DEX तक पहुंच का अभ्यास

में बनाया: 2025-03-21 15:17:05, को अपडेट: 2025-03-28 09:52:32
comments   0
hits   715

[TOC]

प्रस्तावना

ब्लॉकचेन लेनदेन के क्षेत्र में, विकेन्द्रीकृत एक्सचेंज (DEX) परिसंपत्तियों के आदान-प्रदान के लिए एक भरोसेमंद तरीका प्रदान करते हैं, और ट्रॉन पारिस्थितिकी तंत्र में एक प्रतिनिधि DEX के रूप में सनस्वैप, वेब 3 पर आधारित स्मार्ट अनुबंध इंटरैक्शन का समर्थन करता है। एफएमजेड प्लेटफॉर्म (एफएमजेड क्वांटिटेटिव) एक शक्तिशाली मात्रात्मक ट्रेडिंग ढांचा प्रदान करता है जो डेवलपर्स को कई ट्रेडिंग रणनीतियों को एकीकृत करने में सक्षम बनाता है।

FMZ प्लेटफॉर्म पहले से ही Web3 Ethereum (ETH) एक्सचेंज ऑब्जेक्ट्स के एनकैप्सुलेशन का समर्थन करता है, और हाल ही में TRON श्रृंखला के एक्सचेंज ऑब्जेक्ट्स को एनकैप्सुलेट करते हुए Web3 TRON के लिए समर्थन जोड़ा गया है। UniSwap DEX तक पहुंचने की तरह, SunSwap DEX तक भी पहुंच प्राप्त की जा सकती है।

यह आलेख यह पता लगाएगा कि FMZ मात्रात्मक प्लेटफॉर्म पर आधारित Web3 इंटरफ़ेस को कैसे समाहित किया जाए और लेनदेन के लिए SunSwap DEX से कैसे जोड़ा जाए। हम ट्रॉन नेटवर्क की वेब3 इंटरैक्शन विधि, सनस्वैप स्मार्ट कॉन्ट्रैक्ट कॉल का परिचय देंगे, और कोड उदाहरणों के माध्यम से दिखाएंगे कि तरलता की जांच कैसे करें, लेनदेन निष्पादित करें और लेनदेन परिणाम प्राप्त करें, ताकि एक कुशल मात्रात्मक व्यापार रणनीति बनाई जा सके।

कथन

इस आलेख में साझा किए गए सभी कोड और डिज़ाइन का परीक्षण किया गया है और वे सीखने और संचार के लिए हैं। हालाँकि, परीक्षण के बाद भी वास्तविक आवश्यकताओं के आधार पर इन्हें समायोजित करने की आवश्यकता हो सकती है। यदि आपको इसे वास्तविक ट्रेडिंग में लागू करने की आवश्यकता है, तो कृपया स्वयं इसका मूल्यांकन, अनुकूलन और संशोधन करना सुनिश्चित करें ताकि यह आपकी आवश्यकताओं को पूरा कर सके।

तैयारी

  • FMZ प्लेटफ़ॉर्म के Web3 TRON एक्सचेंज ऑब्जेक्ट को कॉन्फ़िगर करें

पिछले लेख में「FMZ क्वांटिटेटिव वेब3 विस्तार: ट्रॉन समर्थन जोड़ें और ऑन-चेन लेनदेन क्षमताओं का विस्तार करें」इस लेख में हमने प्रस्तुत किया हैTron gRPC हमने नोड्स स्थापित करने के सामान्य तरीकों और FMZ पर Web3 TRON एक्सचेंज ऑब्जेक्ट को कॉन्फ़िगर करने के तरीके को सीखा है। इस लेख में इन सामग्रियों को दोहराया नहीं जाएगा। जहां तक ​​नोड पते की बात है, हम सीधे TRON द्वारा आधिकारिक तौर पर उपलब्ध कराए गए नोड का उपयोग करेंगे।

  • होस्ट तैनात करें

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

सीईएक्स एक्सचेंजों के विपरीत, यहां चिंता करने वाली कोई दीवार नहीं है। आप स्थानीय स्तर पर होस्ट तैनात करके आधिकारिक नोड तक पहुंच सकते हैं।

  • SunSwap DEX

https://sun.io/

ब्राउज़र में SunSwap का फ्रंट-एंड पेज खोलें, आप वॉलेट से कनेक्ट कर सकते हैं और डेटा को सुविधाजनक रूप से देख सकते हैं।

  • tronscan

https://tronscan.org/

डेटा देखने और विश्लेषण के लिए आवश्यक उपकरण।

  • base58

ट्रॉन पर पते बेस58 एनकोडेड हैं, जो एथेरियम से अलग है। कभी-कभी डेटा को देखने और तुलना करने के लिए कुछ रूपांतरण की आवश्यकता होती है। FMZ पर, आप सीधे बेस58-एन्कोडेड पते का उपयोग कर सकते हैं। कुछ स्मार्ट अनुबंध HEX एन्कोडिंग में डेटा लौटाते हैं, जिसके लिए कुछ रूपांतरण की आवश्यकता होती है। पता प्रारूपों को परिवर्तित करने के लिए कई कार्यों को सरलता से क्रियान्वित किया जाता है:

मुख्य रूपांतरण कार्य निम्नलिखित हैं। सीमित स्थान के कारण, मैं सभी कोड पोस्ट नहीं कर पाऊंगा। पूरा कोड “ट्रॉन सनस्वैप 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
  }

ट्रॉन सनस्वैप V3 ट्रेडिंग लाइब्रेरी

सनस्वैप V3 ट्रेडिंग लाइब्रेरी का डिज़ाइन FMZ प्लेटफॉर्म के यूनीस्वैप ट्रेडिंग लाइब्रेरी टेम्पलेट के आधार पर तैयार किया गया है। चूंकि सनस्वैप टेम्पलेट कोड अपेक्षाकृत लंबा है, इसलिए मैं इसे यहां पोस्ट नहीं करूंगा, लेकिन मुख्य रूप से कार्यान्वित कार्यों को समझाऊंगा।

SunSwap लेनदेन ऑब्जेक्ट बनाएँ

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

इस टेम्पलेट में केवल एक इंटरफ़ेस फ़ंक्शन है, अर्थात्:$.CreateSunSwapEx()सनस्वैप लेनदेन ऑब्जेक्ट बनाने के लिए उपयोग किया जाता है। ऑब्जेक्ट बनने के बाद, आप कुछ कार्यात्मक कॉल करने के लिए इस ऑब्जेक्ट की विधि को कॉल कर सकते हैं, जैसे: मोचन लेनदेन, क्वेरी पूल की कीमतें, सभी V3 पूल जानकारी प्राप्त करना, प्रतिज्ञा, विघटन, आदि।

GetMarkets

“ट्रॉन सनस्वैप 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()इसका उपयोग सनस्वैप V3 पूल के सभी एक्सचेंज पूल की प्रासंगिक जानकारी प्राप्त करने के लिए किया जाता है, जो सनस्वैप लेनदेन ऑब्जेक्ट के चरों में कैश किया जाता है, तथा अन्य कार्यों के दौरान सटीकता, पता और अन्य जानकारी की क्वेरी करने के लिए उपयोग किया जाता है।

यह परीक्षण कोड चलाने के बाद सभी V3 पूल के बारे में जानकारी प्रदर्शित करेगा:

FMZ प्लेटफॉर्म पर आधारित वेब3 ट्रॉन पैकेजिंग और सनस्वैप 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))

यह एक अपेक्षाकृत महत्वपूर्ण कार्य है, जो सनस्वैप की बुद्धिमान रूटिंग सेवा को समाहित करता है। tokenInSymbol और tokenOutSymbol दिए जाने पर, यह संभावित विनिमय पथ, आउटपुट टोकन की संख्या और अन्य जानकारी लौटा सकता है।

FMZ प्लेटफॉर्म पर आधारित वेब3 ट्रॉन पैकेजिंग और सनस्वैप 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 टोकन को पार करने के लिए कोई अच्छा तंत्र नहीं है, इसलिए ट्रॉनस्कैन के ट्रैवर्सल की मदद से, इस तृतीय-पक्ष इंटरफ़ेस का उपयोग खाता परिसंपत्ति की जानकारी क्वेरी करने के लिए किया जाता है।

FMZ प्लेटफॉर्म पर आधारित वेब3 ट्रॉन पैकेजिंग और सनस्वैप DEX तक पहुंच का अभ्यास

FMZ प्लेटफॉर्म पर आधारित वेब3 ट्रॉन पैकेजिंग और सनस्वैप 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","stateMutabil