[TOC]
ব্লকচেইন লেনদেনের ক্ষেত্রে, বিকেন্দ্রীভূত এক্সচেঞ্জ (DEX) সম্পদ বিনিময়ের একটি বিশ্বাসহীন উপায় প্রদান করে এবং ট্রন ইকোসিস্টেমে DEX-এর প্রতিনিধি হিসেবে SunSwap, Web3-এর উপর ভিত্তি করে স্মার্ট চুক্তি মিথস্ক্রিয়া সমর্থন করে। FMZ প্ল্যাটফর্ম (FMZ কোয়ান্টিটেটিভ) একটি শক্তিশালী পরিমাণগত ট্রেডিং কাঠামো প্রদান করে যা ডেভেলপারদের একাধিক ট্রেডিং কৌশল একীভূত করতে সক্ষম করে।
FMZ প্ল্যাটফর্মটি ইতিমধ্যেই Web3 Ethereum (ETH) এক্সচেঞ্জ অবজেক্টের এনক্যাপসুলেশন সমর্থন করে এবং সম্প্রতি Web3 TRON-এর জন্য সমর্থন যোগ করেছে, যা TRON চেইনের এক্সচেঞ্জ অবজেক্টগুলিকে এনক্যাপসুলেট করে। UniSwap DEX অ্যাক্সেস করার মতো, SunSwap DEX অ্যাক্সেসও অর্জন করা যেতে পারে।
এই প্রবন্ধে FMZ পরিমাণগত প্ল্যাটফর্মের উপর ভিত্তি করে Web3 ইন্টারফেসকে কীভাবে এনক্যাপসুলেট করা যায় এবং লেনদেনের জন্য SunSwap DEX-এর সাথে কীভাবে সংযোগ স্থাপন করা যায় তা অন্বেষণ করা হবে। আমরা ট্রন নেটওয়ার্কের Web3 ইন্টারঅ্যাকশন পদ্ধতি, সানসোয়াপ স্মার্ট কন্ট্রাক্ট কল, এবং কোড উদাহরণের মাধ্যমে দেখাবো কিভাবে তরলতা অনুসন্ধান করতে হয়, লেনদেন সম্পাদন করতে হয় এবং লেনদেনের ফলাফল পেতে হয়, যাতে একটি দক্ষ পরিমাণগত ট্রেডিং কৌশল তৈরি করা যায়।
এই প্রবন্ধে ভাগ করা সমস্ত কোড এবং ডিজাইন পরীক্ষা করা হয়েছে এবং শেখার এবং যোগাযোগের জন্য তৈরি করা হয়েছে। তবে, পরীক্ষার পরেও, প্রকৃত চাহিদার উপর ভিত্তি করে এগুলি সামঞ্জস্য করার প্রয়োজন হতে পারে। যদি আপনার এটি বাস্তব ট্রেডিংয়ে প্রয়োগ করার প্রয়োজন হয়, তাহলে অনুগ্রহ করে নিজেই এটি মূল্যায়ন, অপ্টিমাইজ এবং পরিবর্তন করুন যাতে এটি আপনার নিজস্ব চাহিদা পূরণ করে।
আগের প্রবন্ধে「FMZ কোয়ান্টিটেটিভ ওয়েব3 সম্প্রসারণ: ট্রন সাপোর্ট যোগ করুন এবং অন-চেইন লেনদেনের ক্ষমতা প্রসারিত করুন」এই প্রবন্ধে আমরা পরিচয় করিয়ে দিয়েছিTron gRPC আমরা নোড সেট আপ করার সাধারণ পদ্ধতি এবং FMZ-এ Web3 TRON এক্সচেঞ্জ অবজেক্ট কীভাবে কনফিগার করতে হয় তা শিখেছি। এই প্রবন্ধে এই বিষয়বস্তুর পুনরাবৃত্তি হবে না। নোড ঠিকানার ক্ষেত্রে, আমরা সরাসরি TRON দ্বারা প্রদত্ত নোডটি আনুষ্ঠানিকভাবে ব্যবহার করব।
CEX এক্সচেঞ্জের বিপরীতে, চিন্তা করার মতো কোনও দেয়াল নেই। আপনি স্থানীয়ভাবে একটি হোস্ট স্থাপন করে অফিসিয়াল নোড অ্যাক্সেস করতে পারেন।
ব্রাউজারে SunSwap-এর ফ্রন্ট-এন্ড পেজটি খুলুন, আপনি ওয়ালেটের সাথে সংযোগ করতে পারবেন এবং সুবিধাজনকভাবে ডেটা পর্যবেক্ষণ করতে পারবেন।
তথ্য দেখা এবং বিশ্লেষণের জন্য প্রয়োজনীয় সরঞ্জাম।
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)
}
যেহেতু কখনও কখনও আপনাকে একসাথে একাধিক পদ্ধতি থেকে ডেটা পড়তে হয়, তাই একে একে কল করা সময়সাপেক্ষ এবং শ্রমসাধ্য। সুতরাং একটি প্যাকেজড কলিং ফাংশন এনক্যাপসুলেটেড:
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
}
SunSwap V3 ট্রেডিং লাইব্রেরির নকশা FMZ প্ল্যাটফর্মের UniSwap ট্রেডিং লাইব্রেরি টেমপ্লেটের অনুকরণে তৈরি। যেহেতু সানসোয়াপ টেমপ্লেট কোডটি তুলনামূলকভাবে দীর্ঘ, আমি এখানে এটি পোস্ট করব না, তবে মূলত বাস্তবায়িত ফাংশনগুলি ব্যাখ্যা করব।
let e = $.CreateSunSwapEx() // 默认使用exchange即exchanges[0]初始化SunSwap交易对象。
// let e = $.CreateSunSwapEx(exchanges[1]) // 使用次交易所对象初始化SunSwap交易对象。
এই টেমপ্লেটটিতে শুধুমাত্র একটি ইন্টারফেস ফাংশন রয়েছে, যথা:$.CreateSunSwapEx()সানসোয়াপ লেনদেনের বস্তু তৈরি করতে ব্যবহৃত হয়। অবজেক্ট তৈরি হওয়ার পর, আপনি কিছু কার্যকরী কল করার জন্য এই অবজেক্টের পদ্ধতিতে কল করতে পারেন, যেমন: রিডেম্পশন লেনদেন, পুলের দাম জিজ্ঞাসা করা, সমস্ত V3 পুলের তথ্য প্রাপ্ত করা, অঙ্গীকার, ডিকম্প্রেশন ইত্যাদি।
“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 পুল সম্পর্কে তথ্য প্রদর্শন করবে:

“ফিশিং চুক্তি” এবং “অবৈধ চুক্তি” এর মতো কিছু চুক্তির ঠিকানা যা এখন পর্যন্ত দেখা গেছে, কোড থেকে সরিয়ে ফেলা হয়েছে।
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()ফাংশনটি একটি নির্দিষ্ট এক্সচেঞ্জ পুলের দাম জিজ্ঞাসা করে এবং একটি নির্দিষ্ট পুলের ঠিকানা নির্দিষ্ট করতে পারে। মনে রাখবেন যে কিছু পুলের তরলতা খুবই কম এবং দামগুলি শুধুমাত্র রেফারেন্সের জন্য।
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মেথড কলটি এনকোড করা আছে এবং ডেটা একবার অনুরোধ করা হয়েছে।
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 দেওয়া হলে, এটি সম্ভাব্য বিনিময় পথ, আউটপুট টোকেনের সংখ্যা এবং অন্যান্য তথ্য ফেরত দিতে পারে।

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 টোকেনগুলি অতিক্রম করার জন্য কোনও ভাল ব্যবস্থা নেই, তাই ট্রনস্ক্যানের ট্র্যাভার্সালের সাহায্যে, এই তৃতীয়-পক্ষের ইন্টারফেসটি অ্যাকাউন্ট সম্পদের তথ্য অনুসন্ধানের জন্য ব্যবহার করা হয়।


// 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":"