Type/to search
8
Follow
1364
Followers
Elegan dan sederhana! Terhubung ke Uniswap V3 di FMZ dengan 200 baris kode
Discussions
Created 2023-01-30 15:38:02  Updated 2023-09-18 19:39:40
 5
 3104

img

Elegan dan sederhana! Terhubung ke Uniswap V3 di FMZ dengan 200 baris kode

Dengan populernya konsep Defi dalam beberapa tahun terakhir, Uniswap V3 telah menjadi salah satu topik paling populer di bidang keuangan terdesentralisasi (DeFi). Sebagai protokol pertukaran terdesentralisasi terkemuka, Uniswap V3 memberikan pengalaman pengguna yang lebih efisien, lebih aman, dan lebih baik. Sekarang, hanya dengan 200 baris kode, pedagang dan pengembang dapat dengan mudah mengakses Uniswap V3 di platform FMZ.

FMZ adalah platform perdagangan kuantitatif yang mendukung pengembangan, pengujian ulang, dan penerapan strategi perdagangan kuantitatif secara real-time. Dengan antarmuka yang mudah digunakan dan fitur-fitur canggih, mudah untuk memahami mengapa FMZ menjadi pilihan utama bagi para pedagang dan pengembang DeFi.

Proses integrasi Uniswap V3 ke FMZ sederhana dan mudah dipahami, dan hanya membutuhkan 200 baris kode untuk menyelesaikannya. Artinya, meskipun Anda baru dalam coding, Anda dapat dengan mudah terhubung ke Uniswap V3 di FMZ dan segera memulai perdagangan.

FMZ telah merangkum serangkaian fungsi web3 dasar. Selain Uniswap, bursa DEX lainnya juga dapat dirangkum dengan kode yang sangat sedikit. Selanjutnya, izinkan saya mengajak Anda mempelajari konsep dan teknologi dalam aplikasi DeFi dari awal. Karena keterbatasan ruang, uraian berikut ini mencoba menggunakan cara yang paling sederhana dan mudah dipahami. Mungkin tidak terlalu ketat, tetapi mudah dipahami.

Platform FMZ terbuka untuk umum「Perpustakaan Transaksi Uniswap V3」

Kodenya adalah sebagai berikut:

/* jshint esversion: 7 */ const ABI_Route = '[{"inputs":[{"internalType":"address","name":"_factoryV2","type":"address"},{"internalType":"address","name":"factoryV3","type":"address"},{"internalType":"address","name":"_positionManager","type":"address"},{"internalType":"address","name":"_WETH9","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"WETH9","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"}],"name":"approveMax","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"}],"name":"approveMaxMinusOne","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"}],"name":"approveZeroThenMax","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"}],"name":"approveZeroThenMaxMinusOne","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"bytes","name":"data","type":"bytes"}],"name":"callPositionManager","outputs":[{"internalType":"bytes","name":"result","type":"bytes"}],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"bytes[]","name":"paths","type":"bytes[]"},{"internalType":"uint128[]","name":"amounts","type":"uint128[]"},{"internalType":"uint24","name":"maximumTickDivergence","type":"uint24"},{"internalType":"uint32","name":"secondsAgo","type":"uint32"}],"name":"checkOracleSlippage","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes","name":"path","type":"bytes"},{"internalType":"uint24","name":"maximumTickDivergence","type":"uint24"},{"internalType":"uint32","name":"secondsAgo","type":"uint32"}],"name":"checkOracleSlippage","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"components":[{"internalType":"bytes","name":"path","type":"bytes"},{"internalType":"address","name":"recipient","type":"address"},{"internalType":"uint256","name":"amountIn","type":"uint256"},{"internalType":"uint256","name":"amountOutMinimum","type":"uint256"}],"internalType":"struct IV3SwapRouter.ExactInputParams","name":"params","type":"tuple"}],"name":"exactInput","outputs":[{"internalType":"uint256","name":"amountOut","type":"uint256"}],"stateMutability":"payable","type":"function"},{"inputs":[{"components":[{"internalType":"address","name":"tokenIn","type":"address"},{"internalType":"address","name":"tokenOut","type":"address"},{"internalType":"uint24","name":"fee","type":"uint24"},{"internalType":"address","name":"recipient","type":"address"},{"internalType":"uint256","name":"amountIn","type":"uint256"},{"internalType":"uint256","name":"amountOutMinimum","type":"uint256"},{"internalType":"uint160","name":"sqrtPriceLimitX96","type":"uint160"}],"internalType":"struct IV3SwapRouter.ExactInputSingleParams","name":"params","type":"tuple"}],"name":"exactInputSingle","outputs":[{"internalType":"uint256","name":"amountOut","type":"uint256"}],"stateMutability":"payable","type":"function"},{"inputs":[{"components":[{"internalType":"bytes","name":"path","type":"bytes"},{"internalType":"address","name":"recipient","type":"address"},{"internalType":"uint256","name":"amountOut","type":"uint256"},{"internalType":"uint256","name":"amountInMaximum","type":"uint256"}],"internalType":"struct IV3SwapRouter.ExactOutputParams","name":"params","type":"tuple"}],"name":"exactOutput","outputs":[{"internalType":"uint256","name":"amountIn","type":"uint256"}],"stateMutability":"payable","type":"function"},{"inputs":[{"components":[{"internalType":"address","name":"tokenIn","type":"address"},{"internalType":"address","name":"tokenOut","type":"address"},{"internalType":"uint24","name":"fee","type":"uint24"},{"internalType":"address","name":"recipient","type":"address"},{"internalType":"uint256","name":"amountOut","type":"uint256"},{"internalType":"uint256","name":"amountInMaximum","type":"uint256"},{"internalType":"uint160","name":"sqrtPriceLimitX96","type":"uint160"}],"internalType":"struct IV3SwapRouter.ExactOutputSingleParams","name":"params","type":"tuple"}],"name":"exactOutputSingle","outputs":[{"internalType":"uint256","name":"amountIn","type":"uint256"}],"stateMutability":"payable","type":"function"},{"inputs":[],"name":"factory","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"factoryV2","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"getApprovalType","outputs":[{"internalType":"enum IApproveAndCall.ApprovalType","name":"","type":"uint8"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"components":[{"internalType":"address","name":"token0","type":"address"},{"internalType":"address","name":"token1","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"uint256","name":"amount0Min","type":"uint256"},{"internalType":"uint256","name":"amount1Min","type":"uint256"}],"internalType":"struct IApproveAndCall.IncreaseLiquidityParams","name":"params","type":"tuple"}],"name":"increaseLiquidity","outputs":[{"internalType":"bytes","name":"result","type":"bytes"}],"stateMutability":"payable","type":"function"},{"inputs":[{"components":[{"internalType":"address","name":"token0","type":"address"},{"internalType":"address","name":"token1","type":"address"},{"internalType":"uint24","name":"fee","type":"uint24"},{"internalType":"int24","name":"tickLower","type":"int24"},{"internalType":"int24","name":"tickUpper","type":"int24"},{"internalType":"uint256","name":"amount0Min","type":"uint256"},{"internalType":"uint256","name":"amount1Min","type":"uint256"},{"internalType":"address","name":"recipient","type":"address"}],"internalType":"struct IApproveAndCall.MintParams","name":"params","type":"tuple"}],"name":"mint","outputs":[{"internalType":"bytes","name":"result","type":"bytes"}],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"previousBlockhash","type":"bytes32"},{"internalType":"bytes[]","name":"data","type":"bytes[]"}],"name":"multicall","outputs":[{"internalType":"bytes[]","name":"","type":"bytes[]"}],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint256","name":"deadline","type":"uint256"},{"internalType":"bytes[]","name":"data","type":"bytes[]"}],"name":"multicall","outputs":[{"internalType":"bytes[]","name":"","type":"bytes[]"}],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"bytes[]","name":"data","type":"bytes[]"}],"name":"multicall","outputs":[{"internalType":"bytes[]","name":"results","type":"bytes[]"}],"stateMutability":"payable","type":"function"},{"inputs":[],"name":"positionManager","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"value","type":"uint256"}],"name":"pull","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[],"name":"refundETH","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"value","type":"uint256"},{"internalType":"uint256","name":"deadline","type":"uint256"},{"internalType":"uint8","name":"v","type":"uint8"},{"internalType":"bytes32","name":"r","type":"bytes32"},{"internalType":"bytes32","name":"s","type":"bytes32"}],"name":"selfPermit","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"nonce","type":"uint256"},{"internalType":"uint256","name":"expiry","type":"uint256"},{"internalType":"uint8","name":"v","type":"uint8"},{"internalType":"bytes32","name":"r","type":"bytes32"},{"internalType":"bytes32","name":"s","type":"bytes32"}],"name":"selfPermitAllowed","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"nonce","type":"uint256"},{"internalType":"uint256","name":"expiry","type":"uint256"},{"internalType":"uint8","name":"v","type":"uint8"},{"internalType":"bytes32","name":"r","type":"bytes32"},{"internalType":"bytes32","name":"s","type":"bytes32"}],"name":"selfPermitAllowedIfNecessary","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"value","type":"uint256"},{"internalType":"uint256","name":"deadline","type":"uint256"},{"internalType":"uint8","name":"v","type":"uint8"},{"internalType":"bytes32","name":"r","type":"bytes32"},{"internalType":"bytes32","name":"s","type":"bytes32"}],"name":"selfPermitIfNecessary","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint256","name":"amountIn","type":"uint256"},{"internalType":"uint256","name":"amountOutMin","type":"uint256"},{"internalType":"address[]","name":"path","type":"address[]"},{"internalType":"address","name":"to","type":"address"}],"name":"swapExactTokensForTokens","outputs":[{"internalType":"uint256","name":"amountOut","type":"uint256"}],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint256","name":"amountOut","type":"uint256"},{"internalType":"uint256","name":"amountInMax","type":"uint256"},{"internalType":"address[]","name":"path","type":"address[]"},{"internalType":"address","name":"to","type":"address"}],"name":"swapTokensForExactTokens","outputs":[{"internalType":"uint256","name":"amountIn","type":"uint256"}],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"amountMinimum","type":"uint256"},{"internalType":"address","name":"recipient","type":"address"}],"name":"sweepToken","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"amountMinimum","type":"uint256"}],"name":"sweepToken","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"amountMinimum","type":"uint256"},{"internalType":"uint256","name":"feeBips","type":"uint256"},{"internalType":"address","name":"feeRecipient","type":"address"}],"name":"sweepTokenWithFee","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"amountMinimum","type":"uint256"},{"internalType":"address","name":"recipient","type":"address"},{"internalType":"uint256","name":"feeBips","type":"uint256"},{"internalType":"address","name":"feeRecipient","type":"address"}],"name":"sweepTokenWithFee","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"int256","name":"amount0Delta","type":"int256"},{"internalType":"int256","name":"amount1Delta","type":"int256"},{"internalType":"bytes","name":"_data","type":"bytes"}],"name":"uniswapV3SwapCallback","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"amountMinimum","type":"uint256"},{"internalType":"address","name":"recipient","type":"address"}],"name":"unwrapWETH9","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint256","name":"amountMinimum","type":"uint256"}],"name":"unwrapWETH9","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint256","name":"amountMinimum","type":"uint256"},{"internalType":"address","name":"recipient","type":"address"},{"internalType":"uint256","name":"feeBips","type":"uint256"},{"internalType":"address","name":"feeRecipient","type":"address"}],"name":"unwrapWETH9WithFee","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint256","name":"amountMinimum","type":"uint256"},{"internalType":"uint256","name":"feeBips","type":"uint256"},{"internalType":"address","name":"feeRecipient","type":"address"}],"name":"unwrapWETH9WithFee","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint256","name":"value","type":"uint256"}],"name":"wrapETH","outputs":[],"stateMutability":"payable","type":"function"},{"stateMutability":"payable","type":"receive"}]'; const ABI_Pool = '[{\"inputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"owner\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"int24\",\"name\":\"tickLower\",\"type\":\"int24\"},{\"indexed\":true,\"internalType\":\"int24\",\"name\":\"tickUpper\",\"type\":\"int24\"},{\"indexed\":false,\"internalType\":\"uint128\",\"name\":\"amount\",\"type\":\"uint128\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"amount0\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"amount1\",\"type\":\"uint256\"}],\"name\":\"Burn\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"owner\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"address\",\"name\":\"recipient\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"int24\",\"name\":\"tickLower\",\"type\":\"int24\"},{\"indexed\":true,\"internalType\":\"int24\",\"name\":\"tickUpper\",\"type\":\"int24\"},{\"indexed\":false,\"internalType\":\"uint128\",\"name\":\"amount0\",\"type\":\"uint128\"},{\"indexed\":false,\"internalType\":\"uint128\",\"name\":\"amount1\",\"type\":\"uint128\"}],\"name\":\"Collect\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"sender\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"recipient\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint128\",\"name\":\"amount0\",\"type\":\"uint128\"},{\"indexed\":false,\"internalType\":\"uint128\",\"name\":\"amount1\",\"type\":\"uint128\"}],\"name\":\"CollectProtocol\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"sender\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"recipient\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"amount0\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"amount1\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"paid0\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"paid1\",\"type\":\"uint256\"}],\"name\":\"Flash\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"uint16\",\"name\":\"observationCardinalityNextOld\",\"type\":\"uint16\"},{\"indexed\":false,\"internalType\":\"uint16\",\"name\":\"observationCardinalityNextNew\",\"type\":\"uint16\"}],\"name\":\"IncreaseObservationCardinalityNext\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"uint160\",\"name\":\"sqrtPriceX96\",\"type\":\"uint160\"},{\"indexed\":false,\"internalType\":\"int24\",\"name\":\"tick\",\"type\":\"int24\"}],\"name\":\"Initialize\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"address\",\"name\":\"sender\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"owner\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"int24\",\"name\":\"tickLower\",\"type\":\"int24\"},{\"indexed\":true,\"internalType\":\"int24\",\"name\":\"tickUpper\",\"type\":\"int24\"},{\"indexed\":false,\"internalType\":\"uint128\",\"name\":\"amount\",\"type\":\"uint128\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"amount0\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"amount1\",\"type\":\"uint256\"}],\"name\":\"Mint\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"uint8\",\"name\":\"feeProtocol0Old\",\"type\":\"uint8\"},{\"indexed\":false,\"internalType\":\"uint8\",\"name\":\"feeProtocol1Old\",\"type\":\"uint8\"},{\"indexed\":false,\"internalType\":\"uint8\",\"name\":\"feeProtocol0New\",\"type\":\"uint8\"},{\"indexed\":false,\"internalType\":\"uint8\",\"name\":\"feeProtocol1New\",\"type\":\"uint8\"}],\"name\":\"SetFeeProtocol\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"sender\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"recipient\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"int256\",\"name\":\"amount0\",\"type\":\"int256\"},{\"indexed\":false,\"internalType\":\"int256\",\"name\":\"amount1\",\"type\":\"int256\"},{\"indexed\":false,\"internalType\":\"uint160\",\"name\":\"sqrtPriceX96\",\"type\":\"uint160\"},{\"indexed\":false,\"internalType\":\"uint128\",\"name\":\"liquidity\",\"type\":\"uint128\"},{\"indexed\":false,\"internalType\":\"int24\",\"name\":\"tick\",\"type\":\"int24\"}],\"name\":\"Swap\",\"type\":\"event\"},{\"inputs\":[{\"internalType\":\"int24\",\"name\":\"tickLower\",\"type\":\"int24\"},{\"internalType\":\"int24\",\"name\":\"tickUpper\",\"type\":\"int24\"},{\"internalType\":\"uint128\",\"name\":\"amount\",\"type\":\"uint128\"}],\"name\":\"burn\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"amount0\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"amount1\",\"type\":\"uint256\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"recipient\",\"type\":\"address\"},{\"internalType\":\"int24\",\"name\":\"tickLower\",\"type\":\"int24\"},{\"internalType\":\"int24\",\"name\":\"tickUpper\",\"type\":\"int24\"},{\"internalType\":\"uint128\",\"name\":\"amount0Requested\",\"type\":\"uint128\"},{\"internalType\":\"uint128\",\"name\":\"amount1Requested\",\"type\":\"uint128\"}],\"name\":\"collect\",\"outputs\":[{\"internalType\":\"uint128\",\"name\":\"amount0\",\"type\":\"uint128\"},{\"internalType\":\"uint128\",\"name\":\"amount1\",\"type\":\"uint128\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"recipient\",\"type\":\"address\"},{\"internalType\":\"uint128\",\"name\":\"amount0Requested\",\"type\":\"uint128\"},{\"internalType\":\"uint128\",\"name\":\"amount1Requested\",\"type\":\"uint128\"}],\"name\":\"collectProtocol\",\"outputs\":[{\"internalType\":\"uint128\",\"name\":\"amount0\",\"type\":\"uint128\"},{\"internalType\":\"uint128\",\"name\":\"amount1\",\"type\":\"uint128\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"factory\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"fee\",\"outputs\":[{\"internalType\":\"uint24\",\"name\":\"\",\"type\":\"uint24\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"feeGrowthGlobal0X128\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"feeGrowthGlobal1X128\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"recipient\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amount0\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"amount1\",\"type\":\"uint256\"},{\"internalType\":\"bytes\",\"name\":\"data\",\"type\":\"bytes\"}],\"name\":\"flash\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint16\",\"name\":\"observationCardinalityNext\",\"type\":\"uint16\"}],\"name\":\"increaseObservationCardinalityNext\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint160\",\"name\":\"sqrtPriceX96\",\"type\":\"uint160\"}],\"name\":\"initialize\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"liquidity\",\"outputs\":[{\"internalType\":\"uint128\",\"name\":\"\",\"type\":\"uint128\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"maxLiquidityPerTick\",\"outputs\":[{\"internalType\":\"uint128\",\"name\":\"\",\"type\":\"uint128\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"recipient\",\"type\":\"address\"},{\"internalType\":\"int24\",\"name\":\"tickLower\",\"type\":\"int24\"},{\"internalType\":\"int24\",\"name\":\"tickUpper\",\"type\":\"int24\"},{\"internalType\":\"uint128\",\"name\":\"amount\",\"type\":\"uint128\"},{\"internalType\":\"bytes\",\"name\":\"data\",\"type\":\"bytes\"}],\"name\":\"mint\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"amount0\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"amount1\",\"type\":\"uint256\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"name\":\"observations\",\"outputs\":[{\"internalType\":\"uint32\",\"name\":\"blockTimestamp\",\"type\":\"uint32\"},{\"internalType\":\"int56\",\"name\":\"tickCumulative\",\"type\":\"int56\"},{\"internalType\":\"uint160\",\"name\":\"secondsPerLiquidityCumulativeX128\",\"type\":\"uint160\"},{\"internalType\":\"bool\",\"name\":\"initialized\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint32[]\",\"name\":\"secondsAgos\",\"type\":\"uint32[]\"}],\"name\":\"observe\",\"outputs\":[{\"internalType\":\"int56[]\",\"name\":\"tickCumulatives\",\"type\":\"int56[]\"},{\"internalType\":\"uint160[]\",\"name\":\"secondsPerLiquidityCumulativeX128s\",\"type\":\"uint160[]\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"name\":\"positions\",\"outputs\":[{\"internalType\":\"uint128\",\"name\":\"liquidity\",\"type\":\"uint128\"},{\"internalType\":\"uint256\",\"name\":\"feeGrowthInside0LastX128\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"feeGrowthInside1LastX128\",\"type\":\"uint256\"},{\"internalType\":\"uint128\",\"name\":\"tokensOwed0\",\"type\":\"uint128\"},{\"internalType\":\"uint128\",\"name\":\"tokensOwed1\",\"type\":\"uint128\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"protocolFees\",\"outputs\":[{\"internalType\":\"uint128\",\"name\":\"token0\",\"type\":\"uint128\"},{\"internalType\":\"uint128\",\"name\":\"token1\",\"type\":\"uint128\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint8\",\"name\":\"feeProtocol0\",\"type\":\"uint8\"},{\"internalType\":\"uint8\",\"name\":\"feeProtocol1\",\"type\":\"uint8\"}],\"name\":\"setFeeProtocol\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"slot0\",\"outputs\":[{\"internalType\":\"uint160\",\"name\":\"sqrtPriceX96\",\"type\":\"uint160\"},{\"internalType\":\"int24\",\"name\":\"tick\",\"type\":\"int24\"},{\"internalType\":\"uint16\",\"name\":\"observationIndex\",\"type\":\"uint16\"},{\"internalType\":\"uint16\",\"name\":\"observationCardinality\",\"type\":\"uint16\"},{\"internalType\":\"uint16\",\"name\":\"observationCardinalityNext\",\"type\":\"uint16\"},{\"internalType\":\"uint8\",\"name\":\"feeProtocol\",\"type\":\"uint8\"},{\"internalType\":\"bool\",\"name\":\"unlocked\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"int24\",\"name\":\"tickLower\",\"type\":\"int24\"},{\"internalType\":\"int24\",\"name\":\"tickUpper\",\"type\":\"int24\"}],\"name\":\"snapshotCumulativesInside\",\"outputs\":[{\"internalType\":\"int56\",\"name\":\"tickCumulativeInside\",\"type\":\"int56\"},{\"internalType\":\"uint160\",\"name\":\"secondsPerLiquidityInsideX128\",\"type\":\"uint160\"},{\"internalType\":\"uint32\",\"name\":\"secondsInside\",\"type\":\"uint32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"recipient\",\"type\":\"address\"},{\"internalType\":\"bool\",\"name\":\"zeroForOne\",\"type\":\"bool\"},{\"internalType\":\"int256\",\"name\":\"amountSpecified\",\"type\":\"int256\"},{\"internalType\":\"uint160\",\"name\":\"sqrtPriceLimitX96\",\"type\":\"uint160\"},{\"internalType\":\"bytes\",\"name\":\"data\",\"type\":\"bytes\"}],\"name\":\"swap\",\"outputs\":[{\"internalType\":\"int256\",\"name\":\"amount0\",\"type\":\"int256\"},{\"internalType\":\"int256\",\"name\":\"amount1\",\"type\":\"int256\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"int16\",\"name\":\"\",\"type\":\"int16\"}],\"name\":\"tickBitmap\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"tickSpacing\",\"outputs\":[{\"internalType\":\"int24\",\"name\":\"\",\"type\":\"int24\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"int24\",\"name\":\"\",\"type\":\"int24\"}],\"name\":\"ticks\",\"outputs\":[{\"internalType\":\"uint128\",\"name\":\"liquidityGross\",\"type\":\"uint128\"},{\"internalType\":\"int128\",\"name\":\"liquidityNet\",\"type\":\"int128\"},{\"internalType\":\"uint256\",\"name\":\"feeGrowthOutside0X128\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"feeGrowthOutside1X128\",\"type\":\"uint256\"},{\"internalType\":\"int56\",\"name\":\"tickCumulativeOutside\",\"type\":\"int56\"},{\"internalType\":\"uint160\",\"name\":\"secondsPerLiquidityOutsideX128\",\"type\":\"uint160\"},{\"internalType\":\"uint32\",\"name\":\"secondsOutside\",\"type\":\"uint32\"},{\"internalType\":\"bool\",\"name\":\"initialized\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"token0\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"token1\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"}]' const ABI_Factory = '[{\"inputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"uint24\",\"name\":\"fee\",\"type\":\"uint24\"},{\"indexed\":true,\"internalType\":\"int24\",\"name\":\"tickSpacing\",\"type\":\"int24\"}],\"name\":\"FeeAmountEnabled\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"oldOwner\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"newOwner\",\"type\":\"address\"}],\"name\":\"OwnerChanged\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"token0\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"token1\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"uint24\",\"name\":\"fee\",\"type\":\"uint24\"},{\"indexed\":false,\"internalType\":\"int24\",\"name\":\"tickSpacing\",\"type\":\"int24\"},{\"indexed\":false,\"internalType\":\"address\",\"name\":\"pool\",\"type\":\"address\"}],\"name\":\"PoolCreated\",\"type\":\"event\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"tokenA\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"tokenB\",\"type\":\"address\"},{\"internalType\":\"uint24\",\"name\":\"fee\",\"type\":\"uint24\"}],\"name\":\"createPool\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"pool\",\"type\":\"address\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint24\",\"name\":\"fee\",\"type\":\"uint24\"},{\"internalType\":\"int24\",\"name\":\"tickSpacing\",\"type\":\"int24\"}],\"name\":\"enableFeeAmount\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint24\",\"name\":\"\",\"type\":\"uint24\"}],\"name\":\"feeAmountTickSpacing\",\"outputs\":[{\"internalType\":\"int24\",\"name\":\"\",\"type\":\"int24\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"},{\"internalType\":\"uint24\",\"name\":\"\",\"type\":\"uint24\"}],\"name\":\"getPool\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"owner\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"parameters\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"factory\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"token0\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"token1\",\"type\":\"address\"},{\"internalType\":\"uint24\",\"name\":\"fee\",\"type\":\"uint24\"},{\"internalType\":\"int24\",\"name\":\"tickSpacing\",\"type\":\"int24\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_owner\",\"type\":\"address\"}],\"name\":\"setOwner\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"}]' let ContractV3Factory = "0x1F98431c8aD98523631AE4a59f267346ea31F984" let ContractV3SwapRouterV2 = "0x68b3465833fb72A70ecDF485E0e4C7bD8665Fc45" 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 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) } $.NewUniswapV3 = function(e) { e = e || exchange if (e.GetName() !== 'Web3') { panic("only support Web3 exchange") } let self = { tokenInfo: {}, walletAddress: e.IO("address"), pool: {} } // register e.IO("abi", ContractV3Factory, ABI_Factory) e.IO("abi", ContractV3SwapRouterV2, ABI_Route) self.addToken = function(name, address) { let ret = e.IO("api", address, "decimals") if (!ret) { throw "get token decimals failed" } let decimals = Number(ret) self.tokenInfo[name] = { name: name, decimals: decimals, address: address } } self.waitMined = function(tx) { while (true) { Sleep(1000) let info = e.IO("api", "eth", "eth_getTransactionReceipt", tx) if (info && info.gasUsed) { return true } Log('Transaction not yet mined', tx) } } self.swapToken = function(tokenIn, amountInDecimal, tokenOut, options) { // options like {gasPrice: 11, gasLimit: 111, nonce: 111} let tokenInInfo = self.tokenInfo[tokenIn] let tokenOutInfo = self.tokenInfo[tokenOut] if (!tokenInInfo) { throw "not found token info " + tokenIn } if (!tokenOutInfo) { throw "not found token info " + tokenOut } let amountIn = toInnerAmount(amountInDecimal, tokenInInfo.decimals) let recipientAddress = self.walletAddress if (tokenInInfo.name != 'ETH') { let allowanceAmount = e.IO("api", tokenInInfo.address, "allowance", self.walletAddress, ContractV3SwapRouterV2); let realAmount = toAmount(allowanceAmount, tokenInInfo.decimals) if (realAmount < toAmount(amountIn, tokenInInfo.decimals)) { Log("realAmount is", realAmount, "too small, try to approve large amount") if (tokenInInfo.name == 'USDT') { // As described in Tether code: To change the approve amount you first have to reduce the addresses allowance to 0 calling approve(spender, 0) let txApprove = e.IO("api", tokenInInfo.address, "approve", ContractV3SwapRouterV2, 0) if (!txApprove) { throw "approve error" } Log("wait reduce approve", txApprove) self.waitMined(txApprove) } let txApprove = e.IO("api", tokenInInfo.address, "approve", ContractV3SwapRouterV2, '0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff'); if (!txApprove) { throw "approve error" } Log("wait approve", txApprove) self.waitMined(txApprove) Log("approve success amountIn", amountIn) } else { Log("allowance", realAmount, "no need to approve") } } if (tokenOutInfo.name == 'ETH' || tokenOutInfo.address.toLowerCase() == '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2') { /* ADDRESS_THIS https://degencode.substack.com/p/uniswapv3-multicall https://etherscan.io/address/0x68b3465833fb72a70ecdf485e0e4c7bd8665fc45#code */ recipientAddress = '0x0000000000000000000000000000000000000002' } let swapToken = e.IO("encode", ContractV3SwapRouterV2, "swapExactTokensForTokens", amountIn, 1, [tokenInInfo.address, tokenOutInfo.address], recipientAddress) let data = [swapToken] if (tokenOutInfo.name == 'ETH') { data.push(e.IO("encode", ContractV3SwapRouterV2, "unwrapWETH9(uint256,address)", 1, self.walletAddress)) } let tx = e.IO("api", ContractV3SwapRouterV2, "multicall(uint256,bytes[])", (tokenInInfo.name == 'ETH' ? amountIn : 0), (new Date().getTime() / 1000) + 3600, data, options || {}) if (tx) { Log("tx: ", tx) self.waitMined(tx) Log("swap", tokenInInfo.name, "to", tokenOutInfo.name, "success") return true } else { Log("trans error") return false } } self.getETHBalance = function(address) { return toAmount(e.IO("api", "eth", "eth_getBalance", address || self.walletAddress, "latest"), 18) } self.balanceOf = function(token, address) { let tokenInfo = self.tokenInfo[token] if (!tokenInfo) { throw "not found token info " + token } return toAmount(e.IO("api", tokenInfo.address, "balanceOf", address || self.walletAddress), tokenInfo.decimals) } self.sendETH = function(to, amount, options) { return e.IO("api", "eth", "send", to, toInnerAmount(amount, 18), options || {}) } self.getPrice = function(pair) { let arr = pair.split('_') let token0 = self.tokenInfo[arr[0]] if (!token0) { throw "token " + arr[0] + "not found" } let token1 = self.tokenInfo[arr[1]] if (!token1) { throw "token " + arr[1] + "not found" } let reverse = false if (BigInt(token0.address) > BigInt(token1.address)) { let tmp = token0 token0 = token1 token1 = tmp reverse = true } let key = token0.address + '/' + token1.address if (typeof(self.pool[key]) == 'undefined') { let pool = e.IO("api", ContractV3Factory, "getPool", token0.address, token1.address, 3000) if (pool) { self.pool[key] = pool // register pool address e.IO("abi", pool, ABI_Pool) } } if (typeof(self.pool[key]) == 'undefined') { throw "pool " + pair + " not found" } let slot0 = e.IO("api", self.pool[key], "slot0") if (!slot0) { return null } let price = computePoolPrice(token0.decimals, token1.decimals, slot0.sqrtPriceX96) if (reverse) { price = 1 / price } return price } return self } $.testUniswap = function() { let ex = $.NewUniswapV3() Log("walletAddress: ", ex.walletAddress) let tokenAddressMap = { "ETH": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", // WETH "USDT": "0xdac17f958d2ee523a2206206994597c13d831ec7", "1INCH": "0x111111111117dC0aa78b770fA6A738034120C302", } for (let name in tokenAddressMap) { ex.addToken(name, tokenAddressMap[name]) } Log(ex.getPrice('ETH_USDT')) Log(ex.getPrice('1INCH_USDT')) // swap 0.01 ETH to USDT Log(ex.swapToken('ETH', 0.01, 'USDT')) let usdtBalance = ex.balanceOf('USDT') Log("balance of USDT", usdtBalance) // swap reverse Log(ex.swapToken('USDT', usdtBalance, 'ETH')) Log("balance of ETH", ex.getETHBalance()) // Log(ex.sendETH('0x11111', 0.02)) }

Jaringan Ethereum

Jaringan Ethereum dapat dipahami sebagai infrastruktur perangkat lunak tempat berbagai kontrak pintar dapat digunakan dan dijalankan. Kontrak pintar memiliki berbagai fungsi dan skenario aplikasi. Perangkat yang menjalankan klien Ethereum merupakan node dalam jaringan Ethereum.

Beberapa konsep di Uniswap V3

Tidak dikenalUniswap V3Bagi mereka yang baru mengenal protokol, Anda perlu memahami beberapa konsep terlebih dahulu.Uniswap V3Ini juga merupakan kontrak pintar yang diterapkan dan dijalankan di Ethereum.

  1. Rute: Rute juga merupakan kontrak pintar yang digunakan untuk mengelolatokenmenukarkan.
  2. Pool: Pool juga merupakan kontrak pintar yang digunakan untuk menyimpan dua token Ethereum dan melakukan pertukaran di antara keduanya.
  3. Kontrak Pabrik: Kontrak pabrik adalah kontrak pintar yang digunakan untuk membuat kumpulan.
  4. ABI: (Application Binary Interface) adalah spesifikasi yang menjelaskan bagaimana kontrak pintar berkomunikasi dengan dunia luar. Ini menentukan nama fungsi, jenis parameter, dan jenis nilai pengembalian kontrak pintar, serta cara mengodekan dan mendekode data, dan menentukan antarmuka eksternal kontrak pintar. Dapat dipahami bahwa untuk memanggil suatu antarmuka tertentu, antarmuka tersebut harus dipanggil sesuai dengan standar yang disetujui oleh antarmuka tersebut, dan ABI mencatat serangkaian standar yang disepakati.

Setelah kontrak pintar diterapkan di Ethereum, kontrak tersebut memiliki alamat.

Membedah kode pustaka perdagangan Uniswap V3

Kode pustaka perdagangan Uniswap V3 sebagian besar dibagi menjadi 4 bagian. Mari kita jelaskan satu per satu.

Bagian 1: Konstanta yang digunakan saat berinteraksi dengan Uniswap V3

const ABI_Route = '[{"inputs":[{"internalType":"address... const ABI_Pool = '[{\"inputs\":[],\"stateMutability\":\"nonpayable... const ABI_Factory = '[{\"inputs\":[],\"stateMutability\":\"... let ContractV3Factory = "0x1F98431c8aD98523631AE4a59f267346ea31F984" let ContractV3SwapRouterV2 = "0x68b3465833fb72A70ecDF485E0e4C7bD8665Fc45"

Dengan konsep dasar di atas yang sudah dipahami, mudah untuk memahaminya.

ABI_RouteString yang disimpan dalam konstanta ini adalah ABI dari kontrak pintar router.
ABI_PoolABI dari kontrak kumpulan penyimpanan.
ABI_FactoryABI untuk kontrak pabrik.

Karena untaian ini sangat panjang, untaian ini hanyalah kutipan. Konten ini menyediakan program dengan standar untuk memanggil metode kontrak pintar (misalnya, apa saja parameter antarmuka kontrak pintar ini, berapa banyak parameter yang ada, apa jenisnya, apa jenis data yang dikembalikan, dll.).

Kami sebutkan sebelumnya bahwa setelah kontrak pintar diterapkan di Ethereum, kontrak tersebut akan memiliki alamat.

ContractV3Factory: Mencatat alamat kontrak pabrik.
ContractV3SwapRouterV2:Alamat router V2 Uniswap V3. Harap dicatat bahwa Uniswap memiliki V1 dan V2, dan router Uniswap V3 juga memiliki V1 dan V2. Alamat kontrak yang berbeda berbeda.

Bagian 2: Fungsi Alat

1、computePoolPriceFungsi ini digunakan untuk menghitung harga token dalam pool.

function computePoolPrice(decimals0, decimals1, sqrtPriceX96) { [decimals0, decimals1, sqrtPriceX96] = [decimals0, decimals1, sqrtPriceX96].map(BigInt); // 使用BigInt函数处理,因为JavaScript语言数值精度的原因,需要使用FMZ的一个底层处理函数BigInt来处理 const TWO = BigInt(2); // 定义常量2用于计算 const TEN = BigInt(10); // 定义常量10用于计算 const SIX_TENTH = BigInt(1000000); // 定义常量10的6次方,即1e6 const Q192 = (TWO ** BigInt(96)) ** TWO; // 2^192 return ( Number((sqrtPriceX96 ** TWO * TEN ** decimals0 * SIX_TENTH) / (Q192 * TEN ** decimals1)) / Number(SIX_TENTH) ); }

Jika pasangan perdagangan adalahETH_USDT,Jaditoken0YaETHtoken1YaUSDTdecimals0yaitutoken0Data akurasi,decimals1yaitutoken1Data akurasi.sqrtPriceX96Ini adalah data terkait harga (bukan nilai harga langsung), yang dapat diperoleh dari kontrak poolslot0Akuisisi metode.

sqrtPriceX96 : The current price of the pool as a sqrt(token1/token0) Q64.96 value
Q64.96 adalah standar penyimpanan pemrosesan data.

decimals0decimals1sqrtPriceX96Ketiga data ini dilewatkan sebagai parametercomputePoolPriceFungsi ini dapat menghitung pasangan transaksiETH_USDTharga. Fungsi akhirnyareturnAlgoritma dalam pernyataan tersebut adalah untuk mengonversisqrtPriceX96Memulihkantoken1/token0proses. Misalnya, saat ini, jumlah token0 (ETH) di pool adalah 1, dan jumlah token1 (USDT) adalah 1100. Jadi1100/1=1100, pasangan perdagangan saat iniETH_USDTHarga di kolam renang adalah 1100.

2、toAmountFungsi ini digunakan untuk mengubah data numerik on-chain menjadi data yang dapat dibaca.

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

Sederhananya, misalnya, jumlah token ETH yang terwakili pada rantai adalah 1e18, yaitu 10 pangkat 18, karena data presisi ETH adalah 18. Tidak semua token memiliki presisi 18. Presisi USDT berbeda dengan ETH.toAmountFungsi ini mengonversi 1e18 menjadi 1.

3、toInnerAmountFungsi dantoAmountSebaliknya, ia mengubah data yang dapat dibaca menjadi nilai numerik yang digunakan secara on-chain.

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

Selanjutnya, mari kita analisis kode “Uniswap V3 Trading Library” bersama-sama.

Bagian 3: Konstruktor objek operasi Uniswap V3

Inti dari pustaka kelas templat ini adalah objek operasi Uniswap V3, yang mengimplementasikan operasi dasar pada Uniswap V3. Fungsi lainnya mungkin dapat ditingkatkan nanti. Dengan menganalisis contoh kode ini, bahkan jika Anda tidak menggunakan platform FMZ, Anda akan meningkatkan pemahaman Anda tentangUniswapiniDEXUntuk memahami proses dan detail setiap tautan, mari kita pelajari bagaimana fungsi dasar ini dirancang dan diimplementasikan di FMZ.

Kode konstruktor objek operasi Uniswap V3:

javascript
$.NewUniswapV3 = function(e) { e = e || exchange // 如果没有传参数e,就使用交易所对象exchange,即策略上第一个添加的交易所 if (e.GetName() !== 'Web3') { // 判断交易所对象是否是Web3,因为这个模板只支持Web3交易所对象 panic("only support Web3 exchange") } let self = { // 当前函数是一个构造函数,构造的对象就是self这个对象 tokenInfo: {}, // self对象的成员变量,用于记录token的注册信息 walletAddress: e.IO("address"), // 记录当前交易所对象绑定的钱包地址 pool: {} // 用于记录注册的池信息 } // register e.IO("abi", ContractV3Factory, ABI_Factory) // 注册工厂合约的ABI e.IO("abi", ContractV3SwapRouterV2, ABI_Route) // 注册路由合约的ABI self.addToken = function(name, address) { // 用于注册token let ret = e.IO("api", address, "decimals") // 调用decimals方法,获取token精度信息 if (!ret) { throw "get token decimals failed" } let decimals = Number(ret) self.tokenInfo[name] = { name: name, decimals: decimals, address: address } } self.waitMined = function(tx) { // 用于等待以太坊上某个操作的结果,哈希为tx参数 while (true) { Sleep(1000) let info = e.IO("api", "eth", "eth_getTransactionReceipt", tx) // 查询结果使用eth_getTransactionReceipt方法,没有查询到,循环继续查询 if (info && info.gasUsed) { return true } Log('Transaction not yet mined', tx) } } self.swapToken = function(tokenIn, amountInDecimal, tokenOut, options) { // 用于token兑换 // options like {gasPrice: 11, gasLimit: 111, nonce: 111} let tokenInInfo = self.tokenInfo[tokenIn] // 拿到兑换出去的token的信息 let tokenOutInfo = self.tokenInfo[tokenOut] // 拿到兑换回来的token的信息 if (!tokenInInfo) { throw "not found token info " + tokenIn } if (!tokenOutInfo) { throw "not found token info " + tokenOut } let amountIn = toInnerAmount(amountInDecimal, tokenInInfo.decimals) // 转换为智能合约上使用的数据 let recipientAddress = self.walletAddress if (tokenInInfo.name != 'ETH') { let allowanceAmount = e.IO("api", tokenInInfo.address, "allowance", self.walletAddress, ContractV3SwapRouterV2); // 查询授权的数量 let realAmount = toAmount(allowanceAmount, tokenInInfo.decimals) if (realAmount < toAmount(amountIn, tokenInInfo.decimals)) { // 如果授权数量不足 Log("realAmount is", realAmount, "too small, try to approve large amount") if (tokenInInfo.name == 'USDT') { // As described in Tether code: To change the approve amount you first have to reduce the addresses allowance to 0 calling approve(spender, 0) let txApprove = e.IO("api", tokenInInfo.address, "approve", ContractV3SwapRouterV2, 0) // 如果授权的token是USDT,需要先授权为0 if (!txApprove) { throw "approve error" } Log("wait reduce approve", txApprove) self.waitMined(txApprove) } let txApprove = e.IO("api", tokenInInfo.address, "approve", ContractV3SwapRouterV2, '0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff'); // 授权Router合约操作钱包的代币 if (!txApprove) { throw "approve error" } Log("wait approve", txApprove) self.waitMined(txApprove) Log("approve success amountIn", amountIn) } else { Log("allowance", realAmount, "no need to approve") } } if (tokenOutInfo.name == 'ETH' || tokenOutInfo.address.toLowerCase() == '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2') { /* ADDRESS_THIS https://degencode.substack.com/p/uniswapv3-multicall https://etherscan.io/address/0x68b3465833fb72a70ecdf485e0e4c7bd8665fc45#code */ recipientAddress = '0x0000000000000000000000000000000000000002' // 其它币换成 WETH的时候,要让合约HOLD住WETH才可以赎回 } let swapToken = e.IO("encode", ContractV3SwapRouterV2, "swapExactTokensForTokens", amountIn, 1, [tokenInInfo.address, tokenOutInfo.address], recipientAddress) // 打包swapExactTokensForTokens调用 let data = [swapToken] if (tokenOutInfo.name == 'ETH') { // 如果兑换时,兑换回来的token是ETH,这里实际是WETH,则需要解包 data.push(e.IO("encode", ContractV3SwapRouterV2, "unwrapWETH9(uint256,address)", 1, self.walletAddress)) // 所以这里再打包一个unwrapWETH9解包调用 } let tx = e.IO("api", ContractV3SwapRouterV2, "multicall(uint256,bytes[])", (tokenInInfo.name == 'ETH' ? amountIn : 0), (new Date().getTime() / 1000) + 3600, data, options || {}) // 使用multicall执行这些打包的操作(swapExactTokensForTokens、unwrapWETH9) if (tx) { Log("tx: ", tx) self.waitMined(tx) Log("swap", tokenInInfo.name, "to", tokenOutInfo.name, "success") return true } else { Log("trans error") return false } } self.getETHBalance = function(address) { // 查询钱包的ETH余额 return toAmount(e.IO("api", "eth", "eth_getBalance", address || self.walletAddress, "latest"), 18) } self.balanceOf = function(token, address) { // 查询钱包的某个token余额(根据参数确定) let tokenInfo = self.tokenInfo[token] if (!tokenInfo) { throw "not found token info " + token } return toAmount(e.IO("api", tokenInfo.address, "balanceOf", address || self.walletAddress), tokenInfo.decimals) } self.sendETH = function(to, amount, options) { // 向某个地址发送ETH代币,即转账 return e.IO("api", "eth", "send", to, toInnerAmount(amount, 18), options || {}) } self.getPrice = function(pair, fee) { // 获取交易对价格 let arr = pair.split('_') let token0 = self.tokenInfo[arr[0]] if (!token0) { throw "token " + arr[0] + "not found" } let token1 = self.tokenInfo[arr[1]] // 首先拿到构成交易对的两个token信息 if (!token1) { throw "token " + arr[1] + "not found" } let reverse = false if (BigInt(token0.address) > BigInt(token1.address)) { let tmp = token0 token0 = token1 token1 = tmp reverse = true } let key = token0.address + '/' + token1.address if (typeof(self.pool[key]) == 'undefined') { let pool = e.IO("api", ContractV3Factory, "getPool", token0.address, token1.address, typeof(fee) === 'number' ? fee : 3000) // 调用工厂合约的getPool方法,获取兑换池的地址 if (pool) { self.pool[key] = pool // 注册池地址,并注册池合约的ABI // register pool address e.IO("abi", pool, ABI_Pool) } } if (typeof(self.pool[key]) == 'undefined') { throw "pool " + pair + " not found" } let slot0 = e.IO("api", self.pool[key], "slot0") // 调用池合约的slot0方法,拿到价格相关信息 if (!slot0) { return null } let price = computePoolPrice(token0.decimals, token1.decimals, slot0.sqrtPriceX96) // 计算出可读的价格 if (reverse) { price = 1 / price } return price } return self }

Siswa yang mungkin tidak terbiasa dengan FMZ mungkin melihat fungsi ini$.NewUniswapV3Penamaannya agak aneh, dengan$.Fungsi di awal menunjukkan bahwa fungsi ini adalah fungsi antarmuka pustaka template di FMZ (apa itu pustaka template bisaMelihat), secara sederhana$.NewUniswapV3Fungsi memungkinkan referensi lain ke iniPerpustakaan TemplateStrateginya disebut langsung. Strategi ini dimiliki secara langsungUniswap V3fungsi.

ini$.NewUniswapV3Fungsi ini secara langsung membangun dan membuat sebuah objek, dan objek ini dapat digunakan untuk melakukan beberapa operasi:

  • Pertukaran token: berdasarkan objekswapTokenImplementasi metode.
  • Kueri saldo ETH: berdasarkan objekgetETHBalanceImplementasi metode.
  • Kueri saldo token: berdasarkan objekbalanceOfImplementasi metode.
  • Kueri harga pasangan perdagangan: berdasarkan objekgetPriceImplementasi metode.
  • Kirim ETH untuk transfer:sendETHImplementasi metode.

Pustaka ini mungkin tidak terbatas pada fungsi-fungsi ini di masa mendatang, dan bahkan dapat ditingkatkan untuk menambahkan fungsi-fungsi seperti "menambah likuiditas". Mari kita lanjutkan membedah kodenya:

e = e || exchange if (e.GetName() !== 'Web3') { panic("only support Web3 exchange") } let self = { tokenInfo: {}, walletAddress: e.IO("address"), pool: {} } // register e.IO("abi", ContractV3Factory, ABI_Factory) e.IO("abi", ContractV3SwapRouterV2, ABI_Route)

Konstruktor$.NewUniswapV3Hanya satu parametere, e ini mewakili objek pertukaran (konfigurasi pertukaran pada FMZ). Karena strategi pada FMZ dapat dirancang untuk multi-bursa, jika bursa tertentu dilewatkan di sini, berarti bursa tersebut telah dibuat.Uniswap V3Objek tersebut digunakan untuk mengoperasikan objek pertukaran. Jika tidak ada parameter yang dilewatkane, operasi default adalah objek pertukaran yang ditambahkan pertama.

Konfigurasikan alamat layanan node dan kunci privat (kunci privat dapat diterapkan secara lokal, dan penerapan lokal hanya memerlukan konfigurasi jalur) untuk membuat objek pertukaran. Ini dapat ditambahkan ke strategi selama perdagangan nyata. Objek ini tercermin dalam kode strategi sebagaiexchangeYaituexchanges[0]Jika Anda menambahkan yang kedua,exchanges[1], tambahkan sepertigaexchanges[2],...

img

Alamat node yang saya konfigurasikan dalam tangkapan layar: https://mainnet.infura.io/v3/xxx adalah node Infura. Ini dapat diterapkan oleh individu. Setiap akun memiliki alamat spesifiknya sendiri. xxx adalah topengnya. xxx bagiannya berbeda untuk setiap akun.

Melanjutkan kode, konstruktor mulai menentukan apakah objek pertukaran adalah Web3, dan melaporkan kesalahan jika bukan Web3. Kemudian buatlah sebuah variabelself, self ini adalah objek yang akhirnya dikembalikan oleh konstruktor. Konstruktor berikutnya menambahkan berbagai fungsi ke objek ini dan mengimplementasikan fungsi tertentu. Variabel self memiliki 3 atribut:

  • tokenInfo: mencatat informasi token yang terdaftar dalam objek ini. Informasi token meliputi alamat token, presisi token, dan nama token.
  • walletAddress: Alamat dompet objek pertukaran saat ini.
  • pool: Informasi pool pertukaran yang terdaftar dalam objek ini, terutama nama pool pertukaran dan alamat pool pertukaran.

Kemudian kami menggunakan konsep yang kami pelajari di artikel sebelumnya:

e.IO("abi", ContractV3Factory, ABI_Factory) // 注册Uniswap V3 工厂合约的ABI e.IO("abi", ContractV3SwapRouterV2, ABI_Route) // 注册Uniswap Router V2 路由的ABI

Mengapa kita perlu mendaftarkan informasi antarmuka ini?

Sebab beberapa fungsi yang akan diimplementasikan nanti memerlukan pemanggilan antarmuka kontrak pintar ini. Langkah selanjutnya adalah menambahkan berbagai metode ke objek self. Selain metode yang disebutkan di atas: menebus token, memeriksa saldo, dll., ada juga beberapa fungsi alat yang termasuk dalam objek self. Mari kita analisis fungsi alat ini terlebih dahulu.

Fungsi utilitas untuk objek diri

1、self.addToken = function(name, address)

Dengan mengamati kode spesifik fungsi ini, kita dapat melihat bahwa fungsi ini adalah untuk memberikan objek saat iniselfCatatantokenAnggota informasitokenInfoTambahkan (dengan kata lain: daftarkan) informasi token. KarenatokenData presisi (token) sering digunakan dalam perhitungan selanjutnya, jadi ketika fungsi ini menambahkan informasi token (terdaftar), ia memanggillet ret = e.IO("api", address, "decimals")Fungsi, melalui fungsi exchange.IO yang dienkapsulasi oleh FMZ (seperti yang kami sebutkan sebelumnya, e adalah objek pertukaran yang masuk), memanggil kontrak token token"decimals"Metode untuk memperoleh presisi token.

Jadiself.tokenInfoIni adalah struktur kamus. Setiap nama kunci adalah nama token, dan nilai kunci adalah informasi token ini, termasuk: alamat, nama, dan presisi. Mungkin terlihat seperti ini:

{ "ETH": {name: "ETH", decimals: 18, address: "0x..."}, "USDT": {name: "USDT", decimals: 6, address: "0x..."}, ... }

2、self.waitMined = function(tx)

Fungsi ini digunakan untuk menunggu hasil eksekusi kontrak pintar di Ethereum. Dari kode implementasi fungsi ini, kita dapat melihat bahwa fungsi ini telah dipanggil dalam satu putaran.let info = e.IO("api", "eth", "eth_getTransactionReceipt", tx), dengan memanggil metode RPC Ethereumeth_getTransactionReceipt, untuk menanyakanHash transaksi mengembalikan tanda terima untuk transaksi tersebut,parametertxYaituHash Transaksi

eth_getTransactionReceiptInformasi terkait lainnya dapat dilihat di: https://ethereum.org/zh/developers/docs/apis/json-rpc/#eth_gettransactionreceipt

Beberapa siswa mungkin bertanya: Mengapa menggunakan fungsi ini?

A: Saat melakukan beberapa operasi, seperti pertukaran token, Anda perlu menunggu hasilnya.

Selanjutnya, mari kita lihat$.NewUniswapV3Mari kita mulai dengan implementasi paling sederhana dari fungsi utama lain dari objek yang dibuat oleh fungsi tersebut.

Fungsi utama

1、self.getETHBalance = function(address)

Ada dua cara untuk memeriksa saldo token: memeriksa saldo ETH (Ethereum) dan memeriksa saldo token ERC20 lainnya. Fungsi getETHBalance dari objek self digunakan untuk menanyakan saldo ETH. Ketika parameter alamat dompet tertentu dimasukkan, saldo ETH dari alamat ini akan ditanyakan. Jika tidak ada parameter alamat yang dilewatkan, lakukan kueriself.walletAddressSaldo ETH dari alamat (yaitu dompet yang dikonfigurasi pada bursa saat ini).

Ini dilakukan dengan memanggil metode RPC Ethereumeth_getBalancemenyelesaikan.

2、self.balanceOf = function(token, address)

Untuk menanyakan saldo token selain ETH, Anda perlu memasukkan parameter token, yang merupakan nama token, seperti USDT. Masukkan alamat dompet yang akan ditanyakan. Jika tidak ada alamat yang dimasukkan, lakukan kueriself.walletAddressSaldo alamat. Dengan mengamati kode yang diterapkan oleh fungsi ini, kita dapat melihat bahwa kita perlu meneruskanself.addTokenHanya token yang terdaftar oleh fungsi yang dapat ditanyakan, karena kontrak yang memanggil tokenbalanceOfmetode, informasi presisi dan alamat token diperlukan.

3、self.sendETH = function(to, amount, options)

Fungsinya adalah untuk mentransfer ETH ke alamat dompet (menggunakantoPengaturan parameter) mentransfer sejumlah ETH (menggunakanamountPengaturan parameter), Anda dapat mengatur yang lainoptionsParameter (struktur data:{gasPrice: 111, gasLimit: 111, nonce: 111}) digunakan untuk menentukangasLimit/gasPrice/nonceJika Anda tidak memasukkan parameter opsi, pengaturan default sistem akan digunakan.

gasLimit/gasPriceMempengaruhi ETH yang dikonsumsi saat melakukan operasi pada Ethereum (beberapa operasi pada Ethereum mengonsumsi gas, yaitu mengonsumsi sejumlah token ETH).

4、self.getPrice = function(pair, fee)

Fungsi ini digunakan untuk memperoleh harga pasangan mata uang di Uniswap. Dari kode implementasi fungsi, kita dapat melihat bahwa saat fungsi mulai dijalankan, pasangan mata uang akan diurai terlebih dahulu untuk memperoleh baseCurrency dan quoteCurrency. Misalnya, jika pasangan perdagangannya adalah ETH_USDT, maka akan dibagi menjadi ETH dan USDT. Kemudian tanyakanself.tokenInfoPeriksa apakah ada informasi tentang kedua token ini dalam data. Jika tidak, kesalahan akan dilaporkan.

Alamat kumpulan pertukaran di Uniswap terdiri dari dua alamat token yang berpartisipasi dan Biaya (standar tarif), jadi saat menanyakanself.poolKetika alamat pool dicatat dalam (self.pool seperti yang kami sebutkan sebelumnya, Anda dapat memeriksanya), jika tidak ditemukan, alamat kedua token dan Biaya digunakan untuk menghitung alamat pool. Jadi satu pasangan perdagangan mungkin memiliki beberapa kumpulan karena Biayanya mungkin berbeda.

Menanyakan dan menghitung alamat kumpulan pertukaran dengan memanggil kontrak pabrik Uniswap V3getPoolMetode yang diperoleh (jadi ABI kontrak pabrik harus didaftarkan di awal).
Setelah Anda memperoleh alamat kumpulan pasangan perdagangan ini, Anda dapat mendaftarkan ABI kontrak kumpulan tersebut. Dengan cara ini, pool (kontrak pintar) dapat disebutslot0metode untuk memperoleh data harga. Tentu saja, data yang dikembalikan oleh metode ini bukanlah harga yang dapat dibaca manusia, tetapi struktur data yang terkait dengan harga. Pemrosesan lebih lanjut diperlukan untuk mendapatkan harga yang dapat dibaca. Ini adalah saat kita menggunakancomputePoolPricefungsi.

5、self.swapToken = function(tokenIn, amountInDecimal, tokenOut, options)

Fungsinya adalah untuk menukar token. Parameter tokenIn adalah nama token yang dibayarkan selama pertukaran, parameter tokenOut adalah nama token yang diperoleh selama pertukaran, parameter amountInDecimal adalah jumlah pertukaran (jumlah yang dapat dibaca manusia), dan Opsi parameternya sama seperti yang kami sebutkan sebelumnya. Sama seperti , Anda dapat mengatur konsumsi gas, nonce, dll. saat melakukan pertukaran.

Ketika fungsi dijalankan, fungsi tersebut akan dilewatkan terlebih dahuluself.tokenInfoInformasi token diperoleh dari variabel. Pertukaran juga memiliki banyak detail. Pertama-tama, jika token yang terlibat dalam pertukaran bukan ETH, Anda perlu memberikanrute(Kontrak pintar yang bertanggung jawab untuk pertukaran) otorisasi. Sebelum otorisasi, periksa apakah jumlah otorisasi mencukupi.

let allowanceAmount = e.IO("api", tokenInInfo.address, "allowance", self.walletAddress, ContractV3SwapRouterV2);

Gunakan metode kelonggaran kontrak token untuk menanyakan jumlah yang diotorisasi. Dengan membandingkan jumlah yang diotorisasi dengan jumlah penebusan saat ini, jika jumlah yang diotorisasi mencukupi untuk penebusan, maka tidak diperlukan otorisasi lebih lanjut. Jika jumlahnya tidak mencukupi, pemrosesan otorisasi dilakukan.

Ada juga detail dalam otorisasi di sini. Jika token yang diotorisasi adalah USDT, Anda perlu mengatur ulang jumlah otorisasi ke 0 sebelum mengotorisasi. Otorisasi penggunaan metode persetujuan kontrak token. Perhatikan bahwa metode otorisasi persetujuan merupakan metode yang mengonsumsi gas dan akan mengonsumsi sejumlah ETH. Jadi Anda perlu menggunakan fungsi self.waitMined untuk menunggu hasil pemrosesan.

Untuk menghindari otorisasi yang sering dan pembayaran ETH yang tidak perlu, operasi otorisasi ini mengotorisasi nilai maksimum pada satu waktu.

let txApprove = e.IO("api", tokenInInfo.address, "approve", ContractV3SwapRouterV2, '0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff');

Jika Anda memiliki kuota pertukaran yang cukup, Anda dapat melakukan pertukaran. Namun ada juga rinciannya di sini. Jika token yang terlibat dalam pertukaran adalah ETH, alamat penerima perlu dimodifikasi:

recipientAddress = '0x0000000000000000000000000000000000000002'

Alasan spesifiknya cukup rumit dan tidak akan diuraikan di sini. Silakan merujuk ke:

ADDRESS_THIS https://degencode.substack.com/p/uniswapv3-multicall
https://etherscan.io/address/0x68b3465833fb72a70ecdf485e0e4c7bd8665fc45#code

Kemudian gunakan fungsi pengemasan yang dienkapsulasi oleh platform FMZe.IO("encode", ..., paketkan panggilan metode swapExactTokensForTokens untuk router (kontrak pintar). Jika token yang diperoleh setelah pertukaran adalah ETH, Anda perlu menambahkan langkah operasi pembongkaran WETH9:

data.push(e.IO("encode", ContractV3SwapRouterV2, "unwrapWETH9(uint256,address)", 1, self.walletAddress))

Karena token yang terlibat dalam pertukaran adalah WETH yang merupakan token paket ETH. Untuk mengonversi ke ETH asli, diperlukan operasi pembongkaran. Setelah mengemas operasi pembongkaran, metode multicall dari router (kontrak pintar) dapat dipanggil untuk melakukan serangkaian operasi ini. Ada satu detail lagi yang perlu Anda perhatikan lebih lanjut. Jika pasangan transaksi yang terlibat dalam pertukaran adalah ETH, Anda perlu mengatur jumlah ETH yang akan ditransfer dalam langkah-langkah berikut. Jika bukan ETH, aturlah ke 0.

let tx = e.IO("api", ContractV3SwapRouterV2, "multicall(uint256,bytes[])", (tokenInInfo.name == 'ETH' ? amountIn : 0), (new Date().getTime() / 1000) + 3600, data, options || {})

Pengaturan ini tercermin di sini:(tokenInInfo.name == 'ETH' ? amountIn : 0). Editor tidak mengetahuinya sebelumnya dan tidak menetapkan 0 ketika tokenIn tidak sama dengan token ETH, yang menyebabkan kesalahan transfer ETH. Jadi berhati-hatilah saat menulis kode transfer.

Bagian 4: Cara menggunakan objek operasi Uniswap V3

Kode dalam templat ini sebenarnya memiliki kurang dari 200 baris fungsionalitas. Bagian berikut sebenarnya adalah demonstrasi penggunaan.

$.testUniswap = function() { let ex = $.NewUniswapV3() Log("walletAddress: ", ex.walletAddress) let tokenAddressMap = { "ETH": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", // WETH "USDT": "0xdac17f958d2ee523a2206206994597c13d831ec7", "1INCH": "0x111111111117dC0aa78b770fA6A738034120C302", } for (let name in tokenAddressMap) { ex.addToken(name, tokenAddressMap[name]) } Log(ex.getPrice('ETH_USDT')) Log(ex.getPrice('1INCH_USDT')) // swap 0.01 ETH to USDT Log(ex.swapToken('ETH', 0.01, 'USDT')) let usdtBalance = ex.balanceOf('USDT') Log("balance of USDT", usdtBalance) // swap reverse Log(ex.swapToken('USDT', usdtBalance, 'ETH')) Log("balance of ETH", ex.getETHBalance()) // Log(ex.sendETH('0x11111', 0.02)) }

$.testUniswap = function()Fungsi ini hanya demonstrasi, mohon jangan memanggilnya jika tidak ada manfaat praktisnya. Kita akan menggunakan fungsi ini untuk melihat cara menggunakan pustaka templat ini untuk mengoperasikan fungsi Uniswap V3.

Kode pertama kali dieksekusilet ex = $.NewUniswapV3()Membangun objek operasi Uniswap V3. Jika Anda ingin mendapatkan alamat dompet yang terikat ke bursa saat ini, Anda dapat menggunakanex.walletAddressMendapatkan. Kemudian gunakan dalam kodeex.addTokenTiga token didaftarkan, yaitu ETH, USDT, dan 1INCH.

Cetak harga pasangan perdagangan (token harus didaftarkan terlebih dahulu):

Log(ex.getPrice('ETH_USDT')) Log(ex.getPrice('1INCH_USDT'))

Jika fungsi getPrice tidak menetapkan Biaya, tarif default 3000 digunakan, yang diubah menjadi nilai yang dapat dibaca sebesar 0,3%.

Jika Anda ingin mengonversi 0,01 ETH ke USDT, memeriksa saldo, lalu mengonversinya kembali, gunakan kode:

Log(ex.swapToken('ETH', 0.01, 'USDT')) let usdtBalance = ex.balanceOf('USDT') // 查询兑换后的USDT余额 Log("balance of USDT", usdtBalance) Log(ex.swapToken('USDT', usdtBalance, 'ETH')) // 把USDT兑换为ETH Log("balance of ETH", ex.getETHBalance()) // 查询ETH余额 // Log(ex.sendETH('0x11111', 0.02)) // ETH转账操作

Uji coba menggunakan testnet Goerli

  1. Konfigurasikan objek pertukaran testnet

Harap perhatikan bahwa saat mengatur node, Anda perlu mengaturnya ke node jaringan uji Goerli.

img

  1. Tulis strategi dan uji pada jaringan uji Goerli.
function main() { let ex = $.NewUniswapV3() Log("walletAddress: ", ex.walletAddress) let tokenAddressMap = { "ETH" : "0xB4FBF271143F4FBf7B91A5ded31805e42b2208d6", // WETH "LINK" : "0x326C977E6efc84E512bB9C30f76E30c160eD06FB", "UNI" : "0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984", } for (let name in tokenAddressMap) { ex.addToken(name, tokenAddressMap[name]) } // ETH_UNI 、 UNI_ETH Log("ETH_UNI:", ex.getPrice('ETH_UNI')) Log("UNI_ETH:", ex.getPrice('UNI_ETH')) // ETH Log("balance of ETH", ex.getETHBalance()) // UNI let uniBalance = ex.balanceOf('UNI') Log("balance of UNI", uniBalance) // LINK let linkBalance = ex.balanceOf('LINK') Log("balance of LINK", linkBalance) // swap 0.001 ETH to UNI Log(ex.swapToken('ETH', 0.001, 'UNI')) // swap UNI to LINK Log(ex.swapToken('UNI', ex.balanceOf('UNI') - uniBalance, 'LINK')) }

Dalam kode pengujian, kami menguji pencetakan alamat dompet, mendaftarkan informasi token, mencetak saldo aset, dan melakukan pertukaran berkelanjutan.ETH -> UNI -> LINK. Perlu dicatat bahwa alamat token yang terdaftar di sini berada di jaringan uji Ethereum Goerli, jadi alamat token dengan nama yang sama berbeda. Untuk koin uji, Anda dapat menggunakan faucet jaringan uji ini untuk mengajukan token uji. Anda dapat memeriksa Google untuk detailnya.

img

Perlu diketahui bahwa Anda perlu memeriksa template "Uniswap V3 Trading Library" untuk menggunakannya$.NewUniswapV3()Fungsi, jika akun FMZ Anda tidak memiliki template ini, Anda dapat mengklikDapatkan di sini

Log operasi strategi:

img

img

Nilai aset yang ditampilkan di halaman Uniswap

https://app.uniswap.org/

img

Operasi ini juga dapat ditanyakan pada rantai:

https://goerli.etherscan.io/

img

Pertukaran ETH ke UNI dilakukan satu kali, otorisasi UNI dilakukan satu kali, dan pertukaran UNI ke LINK dilakukan satu kali.

END

Perpustakaan ini memiliki banyak fungsi yang dapat diperluas, dan bahkan dapat diperluas untuk mengemas beberapa penukarantokenA -> tokenB -> tokenCPertukaran jalur. Kode perpustakaan ini dapat dioptimalkan dan diperluas sesuai dengan kebutuhan spesifik. Jenis kode perpustakaan ini terutama disediakan untuk pengajaran.

memperbarui

DitingkatkanswapTokenFungsi, mendukungtokenA -> tokenB -> tokenC ... -> tokenDFungsi penebusan berkelanjutan. Anda dapat melihat kode terbaru dari templat ini yang diterbitkan di Strategy Square di FMZ.

Comment
All comments (3)

    梦总,有python版本的吗

    3 years ago

    可以回头移植一个,调用都一样。

    3 years ago

    学习中,mark

    3 years ago
  • 1
iPhone Download
Forums
PINE Language
© 2015 - ∞ INVENTOR PTE LTD (SG)