Thanh lịch và đơn giản! Đã kết nối với Uniswap V3 trên FMZ với 200 dòng mã
Với sự phổ biến của khái niệm DeFi trong những năm gần đây, Uniswap V3 đã trở thành một trong những chủ đề phổ biến nhất trong lĩnh vực tài chính phi tập trung (DeFi). Là giao thức trao đổi phi tập trung hàng đầu, Uniswap V3 mang lại trải nghiệm người dùng hiệu quả hơn, an toàn hơn và tốt hơn. Hiện nay, chỉ với 200 dòng mã, các nhà giao dịch và nhà phát triển có thể dễ dàng truy cập Uniswap V3 trên nền tảng FMZ.
FMZ là một nền tảng giao dịch định lượng hỗ trợ phát triển, kiểm tra ngược và triển khai các chiến lược giao dịch định lượng theo thời gian thực. Với giao diện dễ sử dụng và các tính năng mạnh mẽ, không khó hiểu tại sao FMZ lại trở thành sự lựa chọn hàng đầu cho các nhà phát triển và giao dịch DeFi.
Quá trình tích hợp Uniswap V3 vào FMZ rất đơn giản và dễ hiểu, chỉ cần 200 dòng mã để hoàn tất. Điều này có nghĩa là ngay cả khi bạn mới học lập trình, bạn vẫn có thể dễ dàng kết nối với Uniswap V3 trên FMZ và bắt đầu giao dịch ngay lập tức.
FMZ đã đóng gói một loạt các chức năng web3 cơ bản. Ngoài Uniswap, các sàn giao dịch DEX khác cũng có thể được đóng gói với rất ít mã. Tiếp theo, tôi sẽ hướng dẫn bạn tìm hiểu các khái niệm và công nghệ trong ứng dụng DeFi từ đầu. Do hạn chế về không gian, mô tả sau đây cố gắng sử dụng cách đơn giản và dễ hiểu nhất. Có thể không quá nghiêm ngặt, nhưng rất dễ hiểu.
Nền tảng FMZ mở cửa cho công chúng「Thư viện giao dịch Uniswap V3」
Mã như sau:
/* 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))
}
Mạng lưới Ethereum
Mạng lưới Ethereum có thể được hiểu là một cơ sở hạ tầng phần mềm mà trên đó có thể triển khai và chạy nhiều hợp đồng thông minh khác nhau. Hợp đồng thông minh có nhiều chức năng và kịch bản ứng dụng khác nhau. Các thiết bị chạy ứng dụng Ethereum tạo thành các nút trong mạng Ethereum.
Một số khái niệm trong Uniswap V3
Không quenUniswap V3Đối với những người mới làm quen với giao thức này, trước tiên bạn cần hiểu một số khái niệm.Uniswap V3Đây cũng là một hợp đồng thông minh được triển khai và chạy trên Ethereum.
- Route: Route cũng là một hợp đồng thông minh được sử dụng để quản lý
tokentrao đổi. - Pool: Pool cũng là một hợp đồng thông minh được sử dụng để lưu trữ hai token Ethereum và trao đổi giữa chúng.
- Hợp đồng nhà máy: Hợp đồng nhà máy là hợp đồng thông minh được sử dụng để tạo ra một nhóm.
- ABI: (Giao diện nhị phân ứng dụng) là một thông số kỹ thuật mô tả cách hợp đồng thông minh giao tiếp với thế giới bên ngoài. Nó chỉ định tên hàm, kiểu tham số và kiểu giá trị trả về của hợp đồng thông minh, cũng như cách mã hóa và giải mã dữ liệu và xác định giao diện bên ngoài của hợp đồng thông minh. Có thể hiểu rằng để gọi một giao diện nào đó thì phải gọi theo các tiêu chuẩn mà giao diện đó đã thỏa thuận và ABI sẽ ghi lại một loạt các tiêu chuẩn đã thỏa thuận.
Khi một hợp đồng thông minh được triển khai trên Ethereum, nó sẽ có địa chỉ.
Phân tích mã thư viện giao dịch Uniswap V3
Mã thư viện giao dịch Uniswap V3 chủ yếu được chia thành 4 phần. Chúng ta hãy giải thích từng phần một.
Phần 1: Các hằng số được sử dụng khi tương tác với 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"
Với những khái niệm cơ bản nêu trên, bạn sẽ dễ dàng hiểu được.
ABI_RouteChuỗi được lưu trữ trong hằng số này là ABI của hợp đồng thông minh của bộ định tuyến.
ABI_PoolABI của hợp đồng nhóm lưu trữ.
ABI_FactoryABI cho hợp đồng nhà máy.
Vì những chuỗi này rất dài nên chúng chỉ là những đoạn trích. Những nội dung này cung cấp cho chương trình các tiêu chuẩn để gọi các phương thức hợp đồng thông minh (ví dụ, các tham số của giao diện hợp đồng thông minh này là gì, có bao nhiêu tham số, chúng thuộc loại nào, loại dữ liệu trả về là gì, v.v.).
Chúng tôi đã đề cập trước đó rằng khi một hợp đồng thông minh được triển khai trên Ethereum, nó sẽ có địa chỉ.
ContractV3Factory: Ghi lại địa chỉ của hợp đồng nhà máy.
ContractV3SwapRouterV2:Địa chỉ bộ định tuyến V2 của Uniswap V3. Xin lưu ý rằng Uniswap có V1 và V2, và bộ định tuyến của Uniswap V3 cũng có V1 và V2. Địa chỉ của các hợp đồng khác nhau là khác nhau.
Phần 2: Chức năng công cụ
1、computePoolPriceHàm này được sử dụng để tính giá của các token trong nhóm.
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)
);
}
Nếu cặp giao dịch làETH_USDT,Vì thếtoken0ĐúngETH,token1ĐúngUSDT。decimals0đó làtoken0Dữ liệu độ chính xác,decimals1đó làtoken1Dữ liệu độ chính xác.sqrtPriceX96Đây là dữ liệu liên quan đến giá (không phải giá trị giá trực tiếp), có thể lấy được từ hợp đồng nhómslot0Phương pháp tiếp thu.
sqrtPriceX96 : The current price of the pool as a sqrt(token1/token0) Q64.96 value
Q64.96 là tiêu chuẩn lưu trữ xử lý dữ liệu.
decimals0,decimals1, sqrtPriceX96Ba dữ liệu này được truyền vào dưới dạng tham sốcomputePoolPriceChức năng này có thể tính toán cặp giao dịchETH_USDTgiá. Chức năng cuối cùngreturnThuật toán trong câu lệnh là chuyển đổisqrtPriceX96Khôi phụctoken1/token0quá trình. Ví dụ, tại thời điểm này, số token0 (ETH) trong nhóm là 1 và số token1 (USDT) là 1100. Vì thế1100/1=1100, cặp giao dịch hiện tạiETH_USDTGiá trong hồ bơi là 1100.
2、toAmountHàm này được sử dụng để chuyển đổi dữ liệu số trên chuỗi thành dữ liệu có thể đọc được.
function toAmount(s, decimals) {
return Number((BigDecimal(BigInt(s))/BigDecimal(Math.pow(10, decimals))).toString())
}
Nói một cách đơn giản, ví dụ, số lượng token ETH được biểu thị trên chuỗi là 1e18, tức là 10 mũ 18, vì dữ liệu chính xác của ETH là 18. Không phải tất cả các token đều có độ chính xác là 18. Độ chính xác của USDT khác với độ chính xác của ETH.toAmountHàm này chuyển đổi 1e18 thành 1.
3、toInnerAmountChức năng vàtoAmountThay vào đó, nó chuyển đổi dữ liệu có thể đọc được thành các giá trị số được sử dụng trên chuỗi.
function toInnerAmount(n, decimals) {
return (BigDecimal(n)*BigDecimal(Math.pow(10,decimals))).toFixed(0)
}
Tiếp theo, chúng ta hãy cùng nhau phân tích mã của “Thư viện giao dịch Uniswap V3”.
Phần 3: Constructor của đối tượng hoạt động Uniswap V3
Cốt lõi của thư viện lớp mẫu này là đối tượng hoạt động Uniswap V3, thực hiện các hoạt động cơ bản trên Uniswap V3. Nhiều chức năng khác có thể được nâng cấp trong tương lai. Bằng cách phân tích ví dụ mã này, ngay cả khi bạn không sử dụng nền tảng FMZ, bạn sẽ tăng thêm sự hiểu biết của mình vềUniswapcái nàyDEXĐể hiểu được các quy trình và chi tiết của từng liên kết, chúng ta hãy tìm hiểu cách các chức năng cơ bản này được thiết kế và triển khai trên FMZ.
Mã xây dựng của đối tượng hoạt động 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
}
Học sinh có thể không quen thuộc với FMZ có thể thấy chức năng này$.NewUniswapV3Việc đặt tên có vẻ hơi lạ, với$.Hàm ở đầu cho biết hàm này là hàm giao diện của thư viện mẫu trên FMZ (thư viện mẫu có thể là gì?Xem), nói một cách đơn giản$.NewUniswapV3Chức năng cho phép các tham chiếu khác đến điều nàyThư viện mẫuChiến lược được gọi trực tiếp. Chiến lược này được sở hữu trực tiếpUniswap V3chức năng.
cái này$.NewUniswapV3Hàm này trực tiếp xây dựng và tạo ra một đối tượng và đối tượng này có thể được sử dụng để thực hiện một số thao tác:
- Trao đổi mã thông báo: theo đối tượng
swapTokenPhương pháp thực hiện. - Truy vấn số dư ETH: theo đối tượng
getETHBalancePhương pháp thực hiện. - Truy vấn số dư mã thông báo: theo đối tượng
balanceOfPhương pháp thực hiện. - Truy vấn giá cặp giao dịch: theo đối tượng
getPricePhương pháp thực hiện. - Gửi ETH để chuyển:
sendETHPhương pháp thực hiện.
Trong tương lai, thư viện này có thể không giới hạn ở những chức năng này và thậm chí có thể được nâng cấp để thêm các chức năng như "thêm tính thanh khoản". Chúng ta hãy tiếp tục phân tích mã:
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)
Người xây dựng$.NewUniswapV3Chỉ có một tham sốe, e này biểu diễn đối tượng trao đổi (cấu hình trao đổi trên FMZ). Vì chiến lược trên FMZ có thể được thiết kế để trao đổi đa kênh, nên nếu một trao đổi cụ thể được truyền vào đây, điều đó có nghĩa là trao đổi đó đã được tạo.Uniswap V3Đối tượng được sử dụng để vận hành đối tượng trao đổi. Nếu không có tham số nào được truyềne, thao tác mặc định là đối tượng trao đổi được thêm đầu tiên.
Cấu hình địa chỉ dịch vụ nút và khóa riêng (khóa riêng có thể được triển khai cục bộ và việc triển khai cục bộ chỉ yêu cầu cấu hình đường dẫn) để tạo đối tượng trao đổi. Nó có thể được thêm vào chiến lược trong quá trình giao dịch thực tế. Đối tượng này được phản ánh trong mã chiến lược nhưexchangeĐó làexchanges[0]Nếu bạn thêm cái thứ hai,exchanges[1], thêm một phần baexchanges[2],...
Địa chỉ nút tôi đã cấu hình trong ảnh chụp màn hình: https://mainnet.infura.io/v3/xxx là nút của Infura. Điều này có thể được áp dụng bởi các cá nhân. Mỗi tài khoản có địa chỉ cụ thể riêng. xxx là mặt nạ. xxx phần này khác nhau đối với mỗi tài khoản.
Tiếp tục với mã, trình xây dựng bắt đầu xác định xem đối tượng trao đổi có phải là Web3 hay không và báo lỗi nếu không phải là Web3. Sau đó tạo ra một biếnself, self này là đối tượng cuối cùng được trả về bởi constructor. Các constructor tiếp theo thêm nhiều hàm khác nhau vào đối tượng này và triển khai các hàm cụ thể. Biến self có 3 thuộc tính:
- tokenInfo: ghi lại thông tin token được đăng ký trong đối tượng. Thông tin token bao gồm địa chỉ token, độ chính xác của token và tên token.
- walletAddress: Địa chỉ ví của đối tượng trao đổi hiện tại.
- pool: Thông tin về nhóm trao đổi được đăng ký trong đối tượng này, chủ yếu là tên nhóm trao đổi và địa chỉ nhóm trao đổi.
Sau đó chúng tôi sử dụng các khái niệm đã học ở bài viết trước:
e.IO("abi", ContractV3Factory, ABI_Factory) // 注册Uniswap V3 工厂合约的ABI
e.IO("abi", ContractV3SwapRouterV2, ABI_Route) // 注册Uniswap Router V2 路由的ABI
Tại sao chúng ta cần phải đăng ký những thông tin giao diện này?
Bởi vì một số chức năng được triển khai sau này đòi hỏi phải gọi giao diện của các hợp đồng thông minh này. Bước tiếp theo là thêm nhiều phương thức khác nhau vào đối tượng self. Ngoài các phương thức được đề cập ở trên: đổi token, kiểm tra số dư, v.v., còn có một số hàm công cụ thuộc về đối tượng self. Trước tiên, chúng ta hãy phân tích các hàm công cụ này.
Các hàm tiện ích cho đối tượng self
1、self.addToken = function(name, address)
Quan sát mã cụ thể của hàm này, chúng ta có thể thấy rằng hàm này dùng để cung cấp đối tượng hiện tạiselfGhitokenCác thành viên của thông tintokenInfoThêm (nói cách khác: đăng ký) thông tin mã thông báo. bởi vìtokenDữ liệu độ chính xác của (mã thông báo) thường được sử dụng trong các phép tính tiếp theo, vì vậy khi hàm này thêm thông tin mã thông báo (đã đăng ký), nó sẽ gọilet ret = e.IO("api", address, "decimals")Chức năng, thông qua chức năng exchange.IO được đóng gói bởi FMZ (như chúng tôi đã đề cập trước đó, e là đối tượng trao đổi đến), gọi hợp đồng mã thông báo"decimals"Phương pháp để có được độ chính xác của mã thông báo.
Vì thếself.tokenInfoĐây là cấu trúc từ điển. Mỗi tên khóa là tên mã thông báo và giá trị khóa là thông tin của mã thông báo này, bao gồm: địa chỉ, tên và độ chính xác. Nó có thể trông như thế này:
{
"ETH": {name: "ETH", decimals: 18, address: "0x..."},
"USDT": {name: "USDT", decimals: 6, address: "0x..."},
...
}
2、self.waitMined = function(tx)
Hàm này được sử dụng để chờ kết quả thực hiện của hợp đồng thông minh trên Ethereum. Từ mã triển khai của hàm này, chúng ta có thể thấy rằng hàm này đã được gọi trong một vòng lặp.let info = e.IO("api", "eth", "eth_getTransactionReceipt", tx), bằng cách gọi phương thức RPC của Ethereumeth_getTransactionReceipt, để truy vấnBăm giao dịch trả về biên lai cho giao dịch,tham sốtxĐó làGiao dịch băm。
eth_getTransactionReceiptĐể biết thông tin liên quan, vui lòng tham khảo: https://ethereum.org/zh/developers/docs/apis/json-rpc/#eth_gettransactionreceipt
Một số học sinh có thể hỏi: Tại sao lại sử dụng chức năng này?
A: Khi thực hiện một số thao tác, chẳng hạn như trao đổi mã thông báo, bạn cần phải đợi kết quả.
Tiếp theo, chúng ta hãy xem$.NewUniswapV3Chúng ta hãy bắt đầu với cách triển khai đơn giản nhất các hàm chính khác của đối tượng được tạo ra bởi hàm.
Chức năng chính
1、self.getETHBalance = function(address)
Có hai cách để kiểm tra số dư token: kiểm tra số dư ETH (Ethereum) và kiểm tra số dư của các token ERC20 khác. Hàm getETHBalance của đối tượng self được sử dụng để truy vấn số dư ETH. Khi tham số địa chỉ ví cụ thể address được truyền vào, số dư ETH của địa chỉ này sẽ được truy vấn. Nếu không có tham số địa chỉ nào được truyền, truy vấnself.walletAddressSố dư ETH của địa chỉ (tức là ví được cấu hình trên sàn giao dịch hiện tại).
Những điều này được thực hiện bằng cách gọi các phương thức RPC của Ethereumeth_getBalancehoàn thành.
2、self.balanceOf = function(token, address)
Để truy vấn số dư của các token khác ngoài ETH, bạn cần truyền vào tham số token, đây là tên token, chẳng hạn như USDT. Truyền vào địa chỉ ví để được truy vấn. Nếu không có địa chỉ nào được truyền vào, hãy truy vấnself.walletAddressSố dư của địa chỉ. Khi quan sát mã được thực hiện bởi hàm này, chúng ta có thể thấy rằng chúng ta cần phải truyềnself.addTokenChỉ có thể truy vấn các mã thông báo được đăng ký bởi hàm, vì hợp đồng gọi mã thông báobalanceOfphương pháp này yêu cầu thông tin chính xác và địa chỉ của mã thông báo.
3、self.sendETH = function(to, amount, options)
Chức năng này là chuyển ETH đến một địa chỉ ví (sử dụngtoCài đặt tham số) chuyển một số lượng ETH nhất định (sử dụngamountCài đặt tham số), bạn có thể thiết lập mộtoptionsTham số (cấu trúc dữ liệu:{gasPrice: 111, gasLimit: 111, nonce: 111}) được sử dụng để chỉ địnhgasLimit/gasPrice/nonceNếu bạn không truyền tham số tùy chọn, cài đặt mặc định của hệ thống sẽ được sử dụng.
gasLimit/gasPriceẢnh hưởng đến lượng ETH tiêu thụ khi thực hiện các hoạt động trên Ethereum (một số hoạt động trên Ethereum tiêu thụ gas, tức là tiêu thụ một lượng token ETH nhất định).
4、self.getPrice = function(pair, fee)
Hàm này được sử dụng để lấy giá của một cặp giao dịch trên Uniswap. Từ mã triển khai hàm, chúng ta có thể thấy rằng khi hàm bắt đầu thực thi, cặp giao dịch sẽ được phân tích cú pháp trước để lấy baseCurrency và quoteCurrency. Ví dụ, nếu cặp giao dịch là ETH_USDT, nó sẽ được tách thành ETH và USDT. Sau đó truy vấnself.tokenInfoKiểm tra xem có thông tin về hai mã thông báo này trong dữ liệu không. Nếu không, lỗi sẽ được báo cáo.
Địa chỉ nhóm trao đổi trên Uniswap bao gồm hai địa chỉ mã thông báo tham gia và Phí (tiêu chuẩn tỷ giá), vì vậy khi truy vấnself.poolKhi địa chỉ pool được ghi vào (self.pool như chúng tôi đã đề cập trước đó, bạn có thể kiểm tra), nếu không tìm thấy, địa chỉ của hai mã thông báo và Phí sẽ được sử dụng để tính toán địa chỉ pool. Vì vậy, một cặp giao dịch có thể có nhiều nhóm vì Phí có thể khác nhau.
Truy vấn và tính toán địa chỉ của nhóm trao đổi bằng cách gọi hợp đồng nhà máy Uniswap V3getPoolPhương pháp thu được (do đó, ABI của hợp đồng nhà máy phải được đăng ký ngay từ đầu).
Khi bạn có được địa chỉ nhóm của cặp giao dịch này, bạn có thể đăng ký ABI của hợp đồng nhóm. Theo cách này, nhóm (hợp đồng thông minh) có thể được gọislot0phương pháp để lấy dữ liệu giá. Tất nhiên, dữ liệu trả về theo phương pháp này không phải là giá mà con người có thể đọc được, mà là cấu trúc dữ liệu liên quan đến giá. Cần phải xử lý thêm để có được giá có thể đọc được. Đây là lúc chúng ta sử dụngcomputePoolPricechức năng.
5、self.swapToken = function(tokenIn, amountInDecimal, tokenOut, options)
Chức năng này là trao đổi token. Tham số tokenIn là tên của token được thanh toán trong quá trình trao đổi, tham số tokenOut là tên của token thu được trong quá trình trao đổi, tham số amountInDecimal là số tiền trao đổi (số tiền có thể đọc được bằng con người) và tùy chọn tham số giống như chúng tôi đã đề cập trước đó. Giống như , bạn có thể thiết lập mức tiêu thụ gas, nonce, v.v. khi trao đổi.
Khi hàm được thực thi, đầu tiên nó được truyềnself.tokenInfoThông tin token được lấy từ biến. Sàn giao dịch cũng có nhiều chi tiết. Trước hết, nếu token liên quan đến sàn giao dịch không phải là ETH, bạn cần cung cấplộ trình(Hợp đồng thông minh chịu trách nhiệm trao đổi) ủy quyền. Trước khi ủy quyền, hãy kiểm tra xem số tiền ủy quyền có đủ không.
let allowanceAmount = e.IO("api", tokenInInfo.address, "allowance", self.walletAddress, ContractV3SwapRouterV2);
Sử dụng phương pháp cho phép hợp đồng mã thông báo để truy vấn số tiền được ủy quyền. Bằng cách so sánh số tiền được ủy quyền với số tiền quy đổi hiện tại, nếu số tiền được ủy quyền đủ để quy đổi thì không cần ủy quyền thêm nữa. Nếu số tiền không đủ, quá trình ủy quyền sẽ được thực hiện.
Ngoài ra còn có một chi tiết trong ủy quyền ở đây. Nếu mã thông báo được ủy quyền là USDT, bạn cần đặt lại số lượng ủy quyền về 0 trước khi ủy quyền. Cho phép sử dụng phương thức phê duyệt của hợp đồng mã thông báo. Lưu ý rằng phương pháp phê duyệt ủy quyền là phương pháp tiêu tốn gas và sẽ tiêu tốn một lượng ETH nhất định. Vì vậy, bạn cần sử dụng hàm self.waitMined để chờ kết quả xử lý.
Để tránh việc ủy quyền thường xuyên và thanh toán ETH không cần thiết, hoạt động ủy quyền này sẽ ủy quyền giá trị tối đa tại một thời điểm.
let txApprove = e.IO("api", tokenInInfo.address, "approve", ContractV3SwapRouterV2, '0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff');
Nếu bạn có đủ hạn ngạch trao đổi, bạn có thể thực hiện trao đổi. Nhưng cũng có chi tiết ở đây. Nếu token liên quan đến việc trao đổi là ETH, địa chỉ nhận cần phải được sửa đổi:
recipientAddress = '0x0000000000000000000000000000000000000002'
Lý do cụ thể khá phức tạp và sẽ không được trình bày ở đây. Vui lòng tham khảo:
ADDRESS_THIS https://degencode.substack.com/p/uniswapv3-multicall
https://etherscan.io/address/0x68b3465833fb72a70ecdf485e0e4c7bd8665fc45#code
Sau đó sử dụng chức năng đóng gói được đóng gói bởi nền tảng FMZe.IO("encode", ..., gói lệnh gọi phương thức swapExactTokensForTokens cho bộ định tuyến (hợp đồng thông minh). Nếu token thu được sau khi trao đổi là ETH, bạn cần thêm một bước của hoạt động giải nén WETH9:
data.push(e.IO("encode", ContractV3SwapRouterV2, "unwrapWETH9(uint256,address)", 1, self.walletAddress))
Bởi vì token liên quan đến giao dịch là WETH, đây là token được đóng gói của ETH. Để chuyển đổi sang ETH thực, cần phải có thao tác giải nén. Sau khi đóng gói thao tác giải nén, có thể gọi phương thức multicall của bộ định tuyến (hợp đồng thông minh) để thực hiện chuỗi thao tác này. Có một chi tiết nữa mà bạn cần chú ý thêm. Nếu cặp giao dịch liên quan đến việc trao đổi là ETH, bạn cần đặt số lượng ETH cần chuyển theo các bước sau. Nếu không phải là ETH, hãy đặt thành 0.
let tx = e.IO("api", ContractV3SwapRouterV2, "multicall(uint256,bytes[])", (tokenInInfo.name == 'ETH' ? amountIn : 0), (new Date().getTime() / 1000) + 3600, data, options || {})
Thiết lập này được phản ánh ở đây:(tokenInInfo.name == 'ETH' ? amountIn : 0). Biên tập viên đã không tìm ra điều này trước đó và không đặt 0 khi tokenIn không bằng token ETH, dẫn đến việc chuyển nhầm ETH. Vì vậy, hãy hết sức cẩn thận khi viết mã chuyển.
Phần 4: Cách sử dụng các đối tượng hoạt động Uniswap V3
Mã trong mẫu này thực sự có ít hơn 200 dòng chức năng. Phần sau đây thực sự là một bản trình diễn cách sử dụng.
$.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()Chức năng này chỉ mang tính minh họa, vui lòng không gọi nó nếu nó không có tác dụng thực tế. Chúng ta sẽ sử dụng chức năng này để xem cách sử dụng thư viện mẫu này để vận hành các chức năng của Uniswap V3.
Mã được thực thi đầu tiênlet ex = $.NewUniswapV3()Xây dựng một đối tượng hoạt động Uniswap V3. Nếu bạn muốn lấy địa chỉ ví được liên kết với sàn giao dịch hiện tại, bạn có thể sử dụngex.walletAddressLấy. Sau đó sử dụng nó trong mãex.addTokenCó ba token đã được đăng ký, cụ thể là ETH, USDT và 1INCH.
In giá của cặp giao dịch (mã thông báo cần được đăng ký trước):
Log(ex.getPrice('ETH_USDT'))
Log(ex.getPrice('1INCH_USDT'))
Nếu hàm getPrice không thiết lập Phí, tỷ lệ mặc định là 3000 sẽ được sử dụng, được chuyển đổi thành giá trị có thể đọc được là 0,3%.
Nếu bạn muốn chuyển đổi 0,01 ETH sang USDT, hãy kiểm tra số dư rồi chuyển đổi lại, hãy sử dụng mã:
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转账操作
Kiểm tra bằng cách sử dụng mạng thử nghiệm Goerli
- Cấu hình đối tượng trao đổi testnet
Xin lưu ý rằng khi thiết lập nút, bạn cần thiết lập nó thành nút của mạng thử nghiệm Goerli.
- Viết chiến lược và thử nghiệm trên mạng thử nghiệm 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'))
}
Trong mã thử nghiệm, chúng tôi đã thử nghiệm việc in địa chỉ ví, đăng ký thông tin mã thông báo, in số dư tài sản và thực hiện trao đổi liên tục.ETH -> UNI -> LINK. Cần lưu ý rằng địa chỉ token được đăng ký ở đây nằm trên mạng thử nghiệm Ethereum Goerli, vì vậy các địa chỉ token có cùng tên là khác nhau. Đối với các đồng tiền thử nghiệm, bạn có thể sử dụng faucet của mạng thử nghiệm này để đăng ký token thử nghiệm. Bạn có thể kiểm tra Google để biết thông tin chi tiết.
Lưu ý rằng bạn cần kiểm tra mẫu "Uniswap V3 Trading Library" để sử dụng nó$.NewUniswapV3()Chức năng, nếu tài khoản FMZ của bạn không có mẫu này, bạn có thể nhấp vàoNhận nó ở đây。
Nhật ký hoạt động chiến lược:
Giá trị tài sản được hiển thị trên trang Uniswap
Các hoạt động này cũng có thể được truy vấn trên chuỗi:
Việc trao đổi ETH sang UNI được thực hiện một lần, việc ủy quyền UNI được thực hiện một lần và việc trao đổi UNI sang LINK được thực hiện một lần.
END
Thư viện này có nhiều chức năng có thể mở rộng và thậm chí có thể mở rộng để đóng gói nhiều lần đổi thưởngtokenA -> tokenB -> tokenCTrao đổi đường dẫn. Có thể được tối ưu hóa và mở rộng theo nhu cầu cụ thể. Loại mã thư viện này chủ yếu được cung cấp cho mục đích giảng dạy.
làm mới
Đã nâng cấpswapTokenChức năng, hỗ trợtokenA -> tokenB -> tokenC ... -> tokenDChức năng đổi thưởng liên tục. Bạn có thể xem mã mới nhất của mẫu này được công bố trên Strategy Square trên FMZ.
- 1








