Type/to search
8
Follow
1364
Followers
우아하고 단순해요! FMZ의 Uniswap V3에 200줄의 코드로 연결됨
Discussions
Created 2023-01-30 15:38:02  Updated 2023-09-18 19:39:40
 5
 3104

img

우아하고 단순해요! FMZ의 Uniswap V3에 200줄의 코드로 연결됨

최근 몇 년 동안 Defi 개념이 인기를 끌면서 Uniswap V3는 분산형 금융(DeFi) 분야에서 가장 인기 있는 주제 중 하나가 되었습니다. 선도적인 분산형 거래 프로토콜인 Uniswap V3는 보다 효율적이고 안전하며 더 나은 사용자 경험을 제공합니다. 이제 200줄의 코드만으로 트레이더와 개발자는 FMZ 플랫폼에서 Uniswap V3에 쉽게 액세스할 수 있습니다.

FMZ는 양적 거래 전략의 개발, 백테스팅 및 실시간 배포를 지원하는 양적 거래 플랫폼입니다. 사용하기 쉬운 인터페이스와 강력한 기능을 갖추고 있어 FMZ가 DeFi 트레이더와 개발자에게 첫 번째 선택이 되는 이유를 쉽게 이해할 수 있습니다.

Uniswap V3를 FMZ에 통합하는 과정은 간단하고 이해하기 쉬우며, 완료하는 데 필요한 코드는 200줄에 불과합니다. 즉, 코딩을 처음 접하는 사람이라도 FMZ에서 Uniswap V3에 쉽게 연결하여 바로 거래를 시작할 수 있습니다.

FMZ는 일련의 기본 웹3 기능을 캡슐화했습니다. Uniswap 외에도 다른 DEX 거래소도 매우 적은 코드로 캡슐화할 수 있습니다. 다음으로, DeFi 애플리케이션의 개념과 기술을 처음부터 배우도록 하겠습니다. 공간 제약으로 인해 다음 설명은 가장 간단하고 이해하기 쉬운 방법을 사용하려고 합니다. 그다지 엄격하지는 않지만 이해하기 쉽습니다.

FMZ 플랫폼은 대중에게 공개되었습니다.「Uniswap V3 트랜잭션 라이브러리」

코드는 다음과 같습니다.

/* 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)) }

이더리움 네트워크

이더리움 네트워크는 다양한 스마트 계약을 배포하고 실행할 수 있는 소프트웨어 인프라로 이해될 수 있습니다. 스마트 계약에는 다양한 기능과 적용 시나리오가 있습니다. 이더리움 클라이언트를 실행하는 장치는 이더리움 네트워크의 노드를 구성합니다.

Uniswap V3의 일부 개념

생소한Uniswap V3이 프로토콜을 처음 사용하는 분들은 먼저 몇 가지 개념을 이해해야 합니다.Uniswap V3이는 또한 이더리움에 배포되고 실행되는 스마트 계약입니다.

  1. Route : Route는 또한 관리하는 데 사용되는 스마트 계약입니다.token교환.
  2. 풀: 풀은 두 개의 이더리움 토큰을 저장하고 두 토큰을 교환하는 데 사용되는 스마트 계약입니다.
  3. 팩토리 계약: 팩토리 계약은 풀을 생성하는 데 사용되는 스마트 계약입니다.
  4. ABI: (Application Binary Interface)는 스마트 계약이 외부 세계와 통신하는 방법을 설명하는 사양입니다. 스마트 계약의 함수 이름, 매개변수 유형, 반환 값 유형을 지정하고, 데이터를 인코딩 및 디코딩하는 방법을 지정하며, 스마트 계약의 외부 인터페이스를 결정합니다. 특정 인터페이스를 호출하려면 인터페이스에서 합의한 표준에 따라 호출해야 하며, ABI는 일련의 합의된 표준을 기록한다는 것을 알 수 있습니다.

스마트 계약이 이더리움에 배포되면 주소가 부여됩니다.

Uniswap V3 거래 라이브러리 코드 분석

Uniswap V3 트레이딩 라이브러리 코드는 주로 4개 부분으로 나뉩니다. 하나씩 설명하겠습니다.

1부: 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"

위의 기본 개념을 이해하면 여기서는 쉽게 이해할 수 있습니다.

ABI_Route이 상수에 저장된 문자열은 라우터 스마트 계약의 ABI입니다.
ABI_Pool스토리지 풀 계약의 ABI.
ABI_Factory공장 계약에 대한 ABI.

이 문자열은 매우 길기 때문에 발췌본일 뿐입니다. 이러한 콘텐츠는 프로그램에 스마트 계약 메서드를 호출하기 위한 표준을 제공합니다(예: 스마트 계약 인터페이스의 매개변수는 무엇이며, 매개변수의 수와 유형은 무엇이며, 반환되는 데이터의 유형은 무엇인가 등).

앞서 언급했듯이 스마트 계약이 이더리움에 배포되면 주소가 부여됩니다.

ContractV3Factory: 공장 계약의 주소를 기록합니다.
ContractV3SwapRouterV2:Uniswap V3의 라우터 V2 주소입니다. Uniswap에는 V1과 V2가 있고 Uniswap V3의 라우터에도 V1과 V2가 있습니다. 다른 계약의 주소는 다릅니다.

2부: 도구 기능

1、computePoolPrice이 함수는 풀에 있는 토큰의 가격을 계산하는 데 사용됩니다.

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) ); }

거래 쌍이ETH_USDT,그래서token0ETHtoken1USDTdecimals0그것은token0정확도 데이터,decimals1그것은token1정확도 데이터.sqrtPriceX96이는 풀 계약에서 얻을 수 있는 가격 관련 데이터(직접적인 가격 값이 아님)입니다.slot0방법 습득.

sqrtPriceX96 : The current price of the pool as a sqrt(token1/token0) Q64.96 value
Q64.96은 데이터 처리 저장 표준이다.

decimals0decimals1sqrtPriceX96이 세 가지 데이터는 매개변수로 전달됩니다.computePoolPrice이 기능은 거래 쌍을 계산할 수 있습니다.ETH_USDT가격. 함수는 마침내return해당 문장의 알고리즘은 변환하는 것입니다sqrtPriceX96복원하다token1/token0프로세스. 예를 들어, 이때 풀에 있는 토큰0(ETH)의 개수는 1개이고, 토큰1(USDT)의 개수는 1100개입니다. 그래서1100/1=1100, 현재 거래 쌍ETH_USDT수영장의 가격은 1100입니다.

2、toAmount해당 함수는 체인상의 숫자 데이터를 읽을 수 있는 데이터로 변환하는 데 사용됩니다.

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

간단히 말해서, 예를 들어 체인에 표현된 ETH 토큰의 수량은 1e18인데, 이는 10의 18제곱입니다. ETH의 정밀도 데이터는 18이기 때문입니다. 모든 토큰의 정밀도가 18인 것은 아닙니다. USDT의 정밀도는 ETH의 정밀도와 다릅니다.toAmount이 함수는 1e18을 1로 변환합니다.

3、toInnerAmount기능과toAmount대신 읽을 수 있는 데이터를 체인상에서 사용되는 숫자 값으로 변환합니다.

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

다음으로, “Uniswap V3 Trading Library”의 코드를 함께 분석해 보겠습니다.

3부: Uniswap V3 작업 객체 생성자

이 템플릿 클래스 라이브러리의 핵심은 Uniswap V3 작업 객체로, Uniswap V3에서 기본적인 작업을 구현합니다. 나중에 더 많은 기능이 업그레이드될 수도 있습니다. 이 코드 예제를 분석하면 FMZ 플랫폼을 사용하지 않더라도 이해도가 높아질 것입니다.Uniswap이것DEX각 링크의 프로세스와 세부 사항을 이해하기 위해 이제 FMZ에서 이러한 기본 기능이 어떻게 설계되고 구현되는지 알아보겠습니다.

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 }

FMZ에 익숙하지 않은 학생은 이 기능을 볼 수 있습니다.$.NewUniswapV3이름이 조금 이상하네요.$.시작 부분의 함수는 이 함수가 FMZ의 템플릿 라이브러리의 인터페이스 함수임을 나타냅니다(템플릿 라이브러리가 무엇인지 알 수 있음)보다), 간단히 말해서$.NewUniswapV3이 기능은 이것에 대한 다른 참조를 허용합니다.템플릿 라이브러리전략은 직접적으로 호출됩니다. 전략은 직접 소유됩니다Uniswap V3기능.

이것$.NewUniswapV3이 함수는 객체를 직접 구성하고 생성하며, 이 객체를 사용하여 일부 작업을 수행할 수 있습니다.

  • 토큰 교환: 객체별swapToken메서드 구현.
  • ETH 잔액 조회: 객체별getETHBalance메서드 구현.
  • 토큰 잔액 조회: 객체별balanceOf메서드 구현.
  • 거래 쌍 가격 쿼리: 객체별getPrice메서드 구현.
  • 이체를 위해 ETH를 보내세요:sendETH메서드 구현.

이 라이브러리는 앞으로 이러한 기능에만 국한되지 않을 수도 있으며, '유동성 추가' 등의 기능을 추가하도록 업그레이드될 수도 있습니다. 계속해서 코드를 분석해 보겠습니다.

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)

건설자$.NewUniswapV3매개변수는 단 하나e, 이 e는 교환 객체(FMZ의 교환 구성)를 나타냅니다. FMZ의 전략은 다중 거래소로 설계될 수 있기 때문에 특정 거래소가 여기에 전달되면 해당 거래소가 생성되었음을 의미합니다.Uniswap V3객체는 교환 객체를 작동시키는 데 사용됩니다. 매개변수가 전달되지 않으면e기본 작업은 첫 번째로 추가된 교환 개체입니다.

교환 객체를 생성하려면 노드 서비스 주소와 개인 키를 구성합니다(개인 키는 로컬에 배포될 수 있으며, 로컬 배포에는 경로만 구성하면 됩니다). 실제 거래 중에 전략에 추가할 수 있습니다. 이 객체는 전략 코드에 다음과 같이 반영됩니다.exchange그것은이다exchanges[0]두번째 것을 추가하면,exchanges[1], 세 번째를 추가하세요exchanges[2],...

img

스크린샷에서 내가 구성한 노드 주소: https://mainnet.infura.io/v3/xxx는 Infura의 노드입니다. 이는 개인이 적용할 수 있습니다. 각 계정에는 고유한 주소가 있습니다. xxx는 마스크입니다. xxx 각 계정마다 부분이 다릅니다.

코드를 계속 진행하면, 생성자는 교환 객체가 Web3인지 여부를 판별하고, Web3가 아니면 오류를 보고합니다. 그런 다음 변수를 생성했습니다.self, 이 self는 생성자에 의해 최종적으로 반환되는 객체입니다. 이후의 생성자는 이 객체에 다양한 함수를 추가하고 특정 함수를 구현합니다. self 변수에는 3가지 속성이 있습니다.

  • tokenInfo: 객체에 등록된 토큰 정보를 기록합니다. 토큰 정보에는 토큰 주소, 토큰 정밀도, 토큰 이름이 포함됩니다.
  • walletAddress: 현재 거래소 객체의 지갑 주소입니다.
  • pool: 이 객체에 등록된 거래소 풀 정보로, 주로 거래소 풀 이름과 거래소 풀 주소입니다.

그런 다음 우리는 이전 기사에서 배운 개념을 사용했습니다.

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

왜 이러한 인터페이스 정보를 등록해야 하나요?

나중에 구현될 일부 기능은 스마트 계약의 인터페이스를 호출해야 하기 때문입니다. 다음 단계는 self 객체에 다양한 메서드를 추가하는 것입니다. 토큰 상환 및 잔액 쿼리와 같은 위에서 언급한 메서드 외에도 self 객체에 속하는 몇 가지 도구 함수가 있습니다. 먼저 이러한 도구 함수를 분석해 보겠습니다.

self 객체에 대한 유틸리티 함수

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

이 함수의 구체적인 코드를 살펴보면 이 함수는 현재 객체를 제공하는 것임을 알 수 있습니다.self기록token정보의 회원tokenInfo토큰 정보를 추가(다시 말해 등록)합니다. 왜냐하면token(토큰)의 정밀도 데이터는 종종 후속 계산에 사용되므로 이 함수가 (등록된) 토큰 정보를 추가할 때 호출됩니다.let ret = e.IO("api", address, "decimals")FMZ에 의해 캡슐화된 exchange.IO 함수를 통한 함수(앞서 언급했듯이 e는 들어오는 교환 객체)는 토큰 토큰 계약을 호출합니다."decimals"토큰의 정확도를 얻는 방법.

그래서self.tokenInfo사전 구조입니다. 각 키 이름은 토큰 이름이고, 키 값은 주소, 이름, 정밀도를 포함한 이 토큰의 정보입니다. 이는 다음과 같이 보일 수 있습니다.

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

2、self.waitMined = function(tx)

이 함수는 Ethereum에서 스마트 계약의 실행 결과를 기다리는 데 사용됩니다. 이 함수의 구현 코드에서 이 함수가 루프에서 호출되었음을 알 수 있습니다.let info = e.IO("api", "eth", "eth_getTransactionReceipt", tx), Ethereum의 RPC 메서드를 호출하여eth_getTransactionReceipt, 질의하다거래 해시는 거래에 대한 영수증을 반환합니다., 매개변수tx그것은이다거래 해시

eth_getTransactionReceipt관련 정보는 https://ethereum.org/zh/developers/docs/apis/json-rpc/#eth_gettransactionreceipt를 참조하세요.

일부 학생들은 "왜 이 기능을 사용하나요?"라고 질문할 수 있습니다.

답변: 토큰 교환 등 일부 작업을 수행할 때는 결과를 기다려야 합니다.

다음으로 살펴보자$.NewUniswapV3함수에 의해 생성된 객체 self의 다른 주요 함수의 가장 간단한 구현부터 시작해 보겠습니다.

주요 기능

1、self.getETHBalance = function(address)

토큰 잔액을 확인하는 방법은 두 가지가 있습니다. ETH(이더리움) 잔액을 확인하는 것과 다른 ERC20 토큰의 잔액을 확인하는 것입니다. self 객체의 getETHBalance 함수는 ETH 잔액을 쿼리하는 데 사용됩니다. 특정 지갑 주소 매개변수 address가 전달되면 이 주소의 ETH 잔액이 쿼리됩니다. 주소 매개변수가 전달되지 않으면 쿼리self.walletAddress해당 주소의 ETH 잔액(즉, 현재 거래소에 구성된 지갑)입니다.

이는 Ethereum의 RPC 메서드를 호출하여 수행됩니다.eth_getBalance성취하다.

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

ETH가 아닌 다른 토큰의 잔액을 조회하려면 USDT와 같은 토큰 이름인 token 매개변수를 전달해야 합니다. 쿼리할 지갑 주소를 전달합니다. 주소가 전달되지 않으면 쿼리합니다.self.walletAddress주소의 잔액. 이 함수가 구현한 코드를 살펴보면 다음을 전달해야 한다는 것을 알 수 있습니다.self.addToken토큰을 호출하는 계약이기 때문에 함수에 의해 등록된 토큰만 쿼리될 수 있습니다.balanceOf이 방법을 사용하려면 토큰의 정밀도 정보와 주소가 필요합니다.

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

이 기능은 ETH를 지갑 주소로 전송하는 것입니다(to매개변수 설정) 특정 양의 ETH를 전송합니다(사용amount매개변수 설정)을 통해 다른 것을 설정할 수 있습니다.options매개변수(데이터 구조:{gasPrice: 111, gasLimit: 111, nonce: 111})는 지정하는 데 사용됩니다gasLimit/gasPrice/nonce옵션 매개변수를 전달하지 않으면 시스템 기본 설정이 사용됩니다.

gasLimit/gasPrice이더리움에서 작업을 수행할 때 소비되는 ETH에 영향을 미칩니다(이더리움에서 수행되는 일부 작업은 가스를 소비합니다. 즉, 특정 양의 ETH 토큰을 소비합니다).

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

이 함수는 Uniswap에서 거래 쌍의 가격을 얻는 데 사용됩니다. 함수 구현 코드에서 함수가 실행되기 시작하면 거래 쌍 쌍이 먼저 구문 분석되어 baseCurrency와 quoteCurrency를 얻습니다. 예를 들어, 거래 쌍이 ETH_USDT인 경우 ETH와 USDT로 분할됩니다. 그런 다음 쿼리self.tokenInfo데이터에 이 두 토큰에 대한 정보가 있는지 확인하세요. 없으면 오류가 보고됩니다.

유니스왑의 거래소 주소는 참여 토큰 주소 2개와 수수료(요금 기준)로 구성되어 있으므로,self.pool(앞서 언급한 대로 self.pool, 직접 확인해 보세요) 풀 주소가 기록되어 있을 때, 해당 주소가 발견되지 않으면 두 토큰의 주소와 Fee를 사용하여 풀 주소를 계산합니다. 따라서 수수료가 다를 수 있으므로 하나의 거래 쌍에 여러 개의 풀이 있을 수 있습니다.

Uniswap V3 팩토리 계약을 호출하여 교환 풀의 주소를 쿼리하고 계산합니다.getPool획득된 방법(따라서 공장 계약의 ABI는 처음에 등록되어야 함).
해당 거래 쌍의 풀 주소를 얻으면 풀 계약의 ABI를 등록할 수 있습니다. 이런 방식으로 풀(스마트 계약)을 호출할 수 있습니다.slot0가격 데이터를 얻는 방법. 물론, 이 메서드에서 반환된 데이터는 사람이 읽을 수 있는 가격이 아니라 가격과 관련된 데이터 구조입니다. 읽을 수 있는 가격을 얻으려면 추가 처리가 필요합니다. 이때 우리는 다음을 사용합니다.computePoolPrice기능.

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

이 기능은 토큰을 교환하는 것입니다. 매개변수 tokenIn은 교환 중에 지불한 토큰의 이름이고, 매개변수 tokenOut은 교환 중에 얻은 토큰의 이름이고, 매개변수 amountInDecimal은 교환 금액(인간이 읽을 수 있는 금액)이며, 매개변수 옵션은 이전에 언급한 것과 동일합니다. 와 동일하게 교환 시 가스 소비량, nonce 등을 설정할 수 있습니다.

함수가 실행되면 먼저 전달됩니다.self.tokenInfo토큰 정보는 변수에서 얻습니다. 거래소에도 많은 세부 정보가 있습니다. 우선, 거래소에 관련된 토큰이 ETH가 아닌 경우 다음을 제공해야 합니다.라우팅(교환을 담당하는 스마트 계약) 승인. 승인 전에 승인금액이 충분한지 확인하세요.

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

토큰 계약 허용 방법을 사용하여 승인된 금액을 조회합니다. 승인된 금액을 현재 환매 금액과 비교한 결과, 승인된 금액이 환매에 충분하면 추가 승인이 필요하지 않습니다. 금액이 부족한 경우, 승인 처리가 진행됩니다.

여기 권한 부여에 대한 세부 사항도 있습니다. 권한 부여된 토큰이 USDT인 경우 권한 부여하기 전에 권한 부여 수량을 0으로 재설정해야 합니다. 토큰 계약의 승인 메서드 사용을 승인합니다. 승인 권한 부여 방법은 가스를 소모하는 방법이므로 일정량의 ETH를 소모합니다. 따라서 self.waitMined 함수를 사용하여 처리 결과를 기다려야 합니다.

빈번한 승인 및 불필요한 ETH 지불을 피하기 위해 이 승인 작업은 한 번에 최대 값을 승인합니다.

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

교환할 수 있는 양이 충분하다면 교환이 가능합니다. 하지만 여기에는 세부 사항도 있습니다. 교환에 관련된 토큰이 ETH인 경우 수신 주소를 수정해야 합니다.

recipientAddress = '0x0000000000000000000000000000000000000002'

구체적인 이유는 매우 복잡하므로 여기서 자세히 설명하지 않겠습니다. 다음을 참조하세요.

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

그런 다음 FMZ 플랫폼에 의해 캡슐화된 패키징 기능을 사용합니다.e.IO("encode", ..., 라우터(스마트 계약)에 대한 swapExactTokensForTokens 메서드 호출을 패키징합니다. 교환 후 얻은 토큰이 ETH인 경우 WETH9 압축 해제 작업 단계를 추가해야 합니다.

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

거래에 관련된 토큰은 WETH인데, 이는 ETH를 패키지로 만든 토큰입니다. 실제 ETH로 변환하려면 언패킹 작업이 필요합니다. 언패킹 작업을 패키징한 후 라우터(스마트 계약)의 멀티콜 메서드를 호출하여 이 일련의 작업을 실행할 수 있습니다. 추가로 주의해야 할 세부 사항이 하나 더 있습니다. 거래소에 관련된 거래 쌍이 ETH인 경우 다음 단계에서 전송할 ETH 금액을 설정해야 합니다. ETH가 아닌 경우 다음과 같이 설정합니다. 0.

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

이 설정은 여기에 반영됩니다.(tokenInInfo.name == 'ETH' ? amountIn : 0). 편집자는 이전에 이 문제를 알아내지 못했고 tokenIn이 ETH 토큰과 같지 않을 때 0을 설정하지 않아 실수로 ETH가 전송되었습니다. 따라서 전송 코드를 작성할 때는 각별히 주의하세요.

4부: Uniswap V3 작업 객체를 사용하는 방법

이 템플릿의 코드는 실제로 200줄 미만의 기능을 가지고 있습니다. 다음 섹션은 실제로 사용 데모입니다.

$.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()이 기능은 단지 데모일 뿐이므로, 실제 용도가 없다면 호출하지 마세요. 이 함수를 사용하여 이 템플릿 라이브러리를 사용하여 Uniswap V3의 기능을 작동하는 방법을 살펴보겠습니다.

코드가 먼저 실행됩니다let ex = $.NewUniswapV3()Uniswap V3 작업 객체를 구성합니다. 현재 거래소에 바인딩된 지갑 주소를 가져오려면 다음을 사용할 수 있습니다.ex.walletAddress얻다. 그런 다음 코드에서 사용하세요ex.addTokenETH, USDT, 1INCH의 3개 토큰이 등록되었습니다.

거래 쌍의 가격을 인쇄합니다(먼저 토큰을 등록해야 함):

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

getPrice 함수가 Fee를 설정하지 않으면 기본 요율 3000이 사용되고, 이는 0.3%의 읽을 수 있는 값으로 변환됩니다.

0.01 ETH를 USDT로 변환하려면 잔액을 확인한 후 다시 변환하려면 다음 코드를 사용하세요.

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转账操作

Goerli 테스트넷을 사용하여 테스트

  1. 테스트넷 거래소 객체 구성

노드를 설정할 때는 테스트 네트워크 Goerli의 노드로 설정해야 합니다.

img

  1. 전략을 작성하고 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')) }

테스트 코드에서는 지갑 주소 인쇄, 토큰 정보 등록, 자산 잔액 인쇄, 연속 교환 수행을 테스트했습니다.ETH -> UNI -> LINK. 여기에 등록된 토큰 주소는 Ethereum 테스트 네트워크 Goerli에 있으므로 동일한 이름의 토큰 주소는 다릅니다. 테스트 코인의 경우 이 테스트 네트워크의 faucet을 사용하여 테스트 토큰을 신청할 수 있습니다. 자세한 내용은 Google에서 확인하세요.

img

이를 사용하려면 "Uniswap V3 Trading Library" 템플릿을 확인해야 합니다.$.NewUniswapV3()기능, FMZ 계정에 이 템플릿이 없으면 클릭할 수 있습니다.여기서 받으세요

전략 작업 로그:

img

img

Uniswap 페이지에 표시된 자산 가치

https://app.uniswap.org/

img

이러한 작업은 체인에서도 쿼리할 수 있습니다.

https://goerli.etherscan.io/

img

ETH를 UNI로 교환하는 작업이 한 번 실행되었고, UNI의 승인이 한 번 실행되었으며, UNI를 LINK로 교환하는 작업이 한 번 실행되었습니다.

END

이 라이브러리에는 확장 가능한 많은 기능이 있으며, 여러 개의 상환을 패키지로 확장할 수도 있습니다.tokenA -> tokenB -> tokenC경로 교환. 특정 요구에 따라 최적화하고 확장할 수 있습니다. 이 유형의 라이브러리 코드는 주로 교육을 위해 제공됩니다.

고쳐 쓰다

업그레이드됨swapToken기능, 지원tokenA -> tokenB -> tokenC ... -> tokenD연속적 상환 기능. FMZ의 Strategy Square에 게시된 이 템플릿의 최신 코드를 볼 수 있습니다.

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)