Elegante e simples! Conectado ao Uniswap V3 na FMZ com 200 linhas de código
Com a popularidade do conceito Defi nos últimos anos, o Uniswap V3 se tornou um dos tópicos mais populares no campo das finanças descentralizadas (DeFi). Como o principal protocolo de troca descentralizada, o Uniswap V3 oferece uma experiência de usuário mais eficiente, mais segura e melhor. Agora, com apenas 200 linhas de código, traders e desenvolvedores podem acessar facilmente o Uniswap V3 na plataforma FMZ.
FMZ é uma plataforma de negociação quantitativa que oferece suporte ao desenvolvimento, backtesting e implantação em tempo real de estratégias de negociação quantitativa. Com sua interface fácil de usar e recursos poderosos, é fácil entender por que a FMZ está se tornando a primeira escolha para traders e desenvolvedores DeFi.
O processo de integração do Uniswap V3 no FMZ é simples e fácil de entender, e requer apenas 200 linhas de código para ser concluído. Isso significa que, mesmo que você seja novo em codificação, você pode se conectar facilmente ao Uniswap V3 na FMZ e começar a negociar imediatamente.
A FMZ encapsulou uma série de funções básicas da web3. Além da Uniswap, outras exchanges DEX também podem ser encapsuladas com muito pouco código. Em seguida, deixe-me levá-lo para aprender os conceitos e tecnologias em aplicativos DeFi do zero. Devido a restrições de espaço, a descrição a seguir tenta usar a maneira mais simples e compreensível. Pode não ser muito rigoroso, mas é fácil de entender.
A plataforma FMZ está aberta ao público「Biblioteca de transações Uniswap V3」
O código é o seguinte:
/* 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))
}
Rede Ethereum
A rede Ethereum pode ser entendida como uma infraestrutura de software na qual vários contratos inteligentes podem ser implantados e executados. Os contratos inteligentes têm várias funções e cenários de aplicação. Dispositivos que executam o cliente Ethereum constituem nós na rede Ethereum.
Alguns conceitos no Uniswap V3
DesconhecidoUniswap V3Para aqueles que são novos no protocolo, é preciso primeiro entender alguns conceitos.Uniswap V3É também um contrato inteligente implantado e executado no Ethereum.
- Rota: A rota também é um contrato inteligente usado para gerenciar
tokenintercâmbio. - Pool: Um pool também é um contrato inteligente usado para armazenar dois tokens Ethereum e trocá-los.
- Contrato de fábrica: O contrato de fábrica é um contrato inteligente usado para criar um pool.
- ABI: (Application Binary Interface) é uma especificação que descreve como os contratos inteligentes se comunicam com o mundo exterior. Ele especifica o nome da função, os tipos de parâmetros e os tipos de valor de retorno do contrato inteligente, bem como como codificar e decodificar dados, e determina a interface externa do contrato inteligente. Pode-se entender que para chamar uma determinada interface, ela deve ser chamada de acordo com os padrões acordados pela interface, e o ABI registra uma série de padrões acordados.
Quando um contrato inteligente é implantado no Ethereum, ele tem um endereço.
Dissecando o código da biblioteca de negociação Uniswap V3
O código da biblioteca de negociação Uniswap V3 é dividido principalmente em 4 partes. Vamos explicá-las uma por uma.
Parte 1: Constantes usadas ao interagir com 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"
Com os conceitos básicos acima aquecidos, fica fácil entender aqui.
ABI_RouteA string armazenada nesta constante é o ABI do contrato inteligente do roteador.
ABI_PoolABI do contrato do pool de armazenamento.
ABI_FactoryO ABI para o contrato de fábrica.
Como essas sequências são muito longas, elas são apenas trechos. Esses conteúdos fornecem ao programa padrões para chamar métodos de contrato inteligente (por exemplo, quais são os parâmetros dessa interface de contrato inteligente, quantos parâmetros existem, quais são os tipos, que tipo de dados retornados são, etc.).
Mencionamos anteriormente que, quando um contrato inteligente é implantado no Ethereum, ele tem um endereço.
ContractV3Factory: Registra o endereço do contrato de fábrica.
ContractV3SwapRouterV2:O endereço do roteador V2 do Uniswap V3. Observe que o Uniswap tem V1 e V2, e o roteador do Uniswap V3 também tem V1 e V2. Os endereços de diferentes contratos são diferentes.
Parte 2: Funções da ferramenta
1、computePoolPriceA função é usada para calcular o preço dos tokens no pool.
function computePoolPrice(decimals0, decimals1, sqrtPriceX96) {
[decimals0, decimals1, sqrtPriceX96] = [decimals0, decimals1, sqrtPriceX96].map(BigInt); // 使用BigInt函数处理,因为JavaScript语言数值精度的原因,需要使用FMZ的一个底层处理函数BigInt来处理
const TWO = BigInt(2); // 定义常量2用于计算
const TEN = BigInt(10); // 定义常量10用于计算
const SIX_TENTH = BigInt(1000000); // 定义常量10的6次方,即1e6
const Q192 = (TWO ** BigInt(96)) ** TWO; // 2^192
return (
Number((sqrtPriceX96 ** TWO * TEN ** decimals0 * SIX_TENTH) / (Q192 * TEN ** decimals1)) /
Number(SIX_TENTH)
);
}
Se o par de negociação forETH_USDT,Entãotoken0simETH,token1simUSDT。decimals0aquilo étoken0Os dados de precisão,decimals1aquilo étoken1Os dados de precisão.sqrtPriceX96Estes são dados relacionados com o preço (não valores de preços diretos), que podem ser obtidos no contrato do poolslot0Aquisição de método.
sqrtPriceX96 : The current price of the pool as a sqrt(token1/token0) Q64.96 value
Q64.96 é um padrão de armazenamento de processamento de dados.
decimals0,decimals1, sqrtPriceX96Esses três dados são passados como parâmetroscomputePoolPriceA função pode calcular o par de transaçõesETH_USDTpreço. A função finalmentereturnO algoritmo na declaração é convertersqrtPriceX96Restaurartoken1/token0processo. Por exemplo, neste momento, o número de token0 (ETH) no pool é 1, e o número de token1 (USDT) é 1100. Então1100/1=1100, o par de negociação atualETH_USDTO preço na piscina é 1100.
2、toAmountA função é usada para converter os dados numéricos na cadeia em dados legíveis.
function toAmount(s, decimals) {
return Number((BigDecimal(BigInt(s))/BigDecimal(Math.pow(10, decimals))).toString())
}
Para simplificar, por exemplo, a quantidade de um token ETH representado na cadeia é 1e18, que é 10 elevado à 18ª potência, porque os dados de precisão do ETH são 18. Nem todos os tokens têm uma precisão de 18. A precisão do USDT é diferente daquela do ETH.toAmountA função converte 1e18 em 1.
3、toInnerAmountFunção etoAmountEm vez disso, ele converte dados legíveis em valores numéricos usados na cadeia.
function toInnerAmount(n, decimals) {
return (BigDecimal(n)*BigDecimal(Math.pow(10,decimals))).toFixed(0)
}
A seguir, vamos analisar juntos o código da “Uniswap V3 Trading Library”.
Parte 3: Construtor do objeto de operação Uniswap V3
O núcleo desta biblioteca de classes de modelo é o objeto de operação Uniswap V3, que implementa operações básicas no Uniswap V3. Mais funções podem ser atualizadas posteriormente. Ao analisar este exemplo de código, mesmo que você não use a plataforma FMZ, você aumentará sua compreensão deUniswapesseDEXPara entender os processos e detalhes de cada link, vamos agora aprender como essas funções básicas são projetadas e implementadas no FMZ.
Código do construtor do objeto de operação 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
}
Os alunos que não estejam familiarizados com o FMZ podem ver esta função$.NewUniswapV3A nomenclatura é um pouco estranha, com$.A função no início indica que esta função é a função de interface da biblioteca de modelos no FMZ (o que é a biblioteca de modelos pode serVisualizar), em termos simples$.NewUniswapV3A função permite outras referências a estaBiblioteca de modelosA estratégia é chamada diretamente. A estratégia é de propriedade diretaUniswap V3função.
esse$.NewUniswapV3A função constrói e cria diretamente um objeto, e este objeto pode ser usado para executar algumas operações:
- Troca de token: pelo objeto
swapTokenImplementação do método. - Consulta de saldo ETH: pelo objeto
getETHBalanceImplementação do método. - Consulta de saldo de token: pelo objeto
balanceOfImplementação do método. - Consulta de preço do par de negociação: pelo objeto
getPriceImplementação do método. - Enviar ETH para transferência:
sendETHImplementação do método.
Esta biblioteca pode não se limitar a essas funções no futuro e pode até ser atualizada para adicionar funções como "adicionar liquidez". Vamos continuar dissecando o código:
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)
Construtor$.NewUniswapV3Apenas um parâmetroe, este e representa o objeto de troca (configuração de troca no FMZ). Como a estratégia no FMZ pode ser projetada para ser multi-bolsa, se uma bolsa específica for passada aqui, significa que ela foi criada.Uniswap V3O objeto é usado para operar o objeto de troca. Se nenhum parâmetro for passadoe, a operação padrão é o primeiro objeto de troca adicionado.
Configure o endereço do serviço do nó e a chave privada (a chave privada pode ser implantada localmente, e a implantação local requer apenas a configuração do caminho) para criar um objeto de troca. Pode ser adicionado à estratégia durante a negociação real. Este objeto é refletido no código da estratégia comoexchangeAquilo éexchanges[0]Se você adicionar um segundo,exchanges[1], adicione um terceiroexchanges[2],...
O endereço do nó que configurei na captura de tela: https://mainnet.infura.io/v3/xxx é o nó do Infura. Isso pode ser aplicado por indivíduos. Cada conta tem seu próprio endereço específico. xxx é a máscara. O xxx parte é diferente para cada conta.
Continuando com o código, o construtor começa a determinar se o objeto de troca é Web3 e relata um erro se não for Web3. Então criei uma variávelself, esse self é o objeto finalmente retornado pelo construtor. Construtores subsequentes adicionam várias funções a esse objeto e implementam funções específicas. A variável self possui 3 atributos:
- tokenInfo: registra as informações do token registradas no objeto. As informações do token incluem o endereço do token, a precisão do token e o nome do token.
- walletAddress: O endereço da carteira do objeto de troca atual.
- pool: As informações do pool de troca registradas neste objeto, principalmente o nome do pool de troca e o endereço do pool de troca.
Então usamos os conceitos que aprendemos no artigo anterior:
e.IO("abi", ContractV3Factory, ABI_Factory) // 注册Uniswap V3 工厂合约的ABI
e.IO("abi", ContractV3SwapRouterV2, ABI_Route) // 注册Uniswap Router V2 路由的ABI
Por que precisamos registrar essas informações de interface?
Porque algumas funções a serem implementadas posteriormente exigem a chamada das interfaces desses contratos inteligentes. O próximo passo é adicionar vários métodos ao objeto self. Além dos métodos mencionados acima, como resgate de token e consulta de saldo, também há algumas funções de ferramenta pertencentes ao objeto self. Vamos analisar essas funções de ferramenta primeiro. .
Funções de utilidade para o objeto self
1、self.addToken = function(name, address)
Observando o código específico desta função, podemos ver que esta função é para dar ao objeto atualselfRegistrotokenMembros da informaçãotokenInfoAdicione (em outras palavras: registre) uma informação de token. porquetokenOs dados de precisão do (token) são frequentemente usados em cálculos subsequentes, portanto, quando esta função adiciona informações de token (registradas), ela chamalet ret = e.IO("api", address, "decimals")A função, por meio da função exchange.IO encapsulada pelo FMZ (como mencionamos anteriormente, e é o objeto de troca de entrada), chama o contrato de token do token"decimals"Método para obter a precisão do token.
entãoself.tokenInfoÉ uma estrutura de dicionário. Cada nome de chave é o nome do token, e o valor da chave é a informação desse token, incluindo: endereço, nome e precisão. Poderia parecer assim:
{
"ETH": {name: "ETH", decimals: 18, address: "0x..."},
"USDT": {name: "USDT", decimals: 6, address: "0x..."},
...
}
2、self.waitMined = function(tx)
Esta função é usada para esperar o resultado da execução do contrato inteligente no Ethereum. A partir do código de implementação desta função, podemos ver que esta função foi chamada em um loop.let info = e.IO("api", "eth", "eth_getTransactionReceipt", tx), chamando o método RPC do Ethereumeth_getTransactionReceipt, para consultarO hash da transação retorna o recibo da transação,parâmetrotxAquilo éHash de transação。
eth_getTransactionReceiptPara obter informações relacionadas, consulte: https://ethereum.org/zh/developers/docs/apis/json-rpc/#eth_gettransactionreceipt
Alguns alunos podem perguntar: Por que usar esta função?
R: Ao realizar algumas operações, como troca de tokens, você precisa aguardar os resultados.
A seguir, vamos dar uma olhada em$.NewUniswapV3Vamos começar com a implementação mais simples de outras funções principais do objeto self criado pela função.
Principais funções
1、self.getETHBalance = function(address)
Há duas maneiras de verificar o saldo do token: verificar o saldo do ETH (Ethereum) e verificar o saldo de outros tokens ERC20. A função getETHBalance do objeto self é usada para consultar o saldo ETH. Quando o parâmetro de endereço de carteira específico address é passado, o saldo ETH desse endereço é consultado. Se nenhum parâmetro de endereço for passado, consulteself.walletAddressO saldo de ETH do endereço (ou seja, a carteira configurada na exchange atual).
Isso é feito chamando os métodos RPC do Ethereumeth_getBalanceconcluir.
2、self.balanceOf = function(token, address)
Para consultar o saldo de tokens diferentes de ETH, você precisa passar o parâmetro token, que é o nome do token, como USDT. Passe o endereço da carteira a ser consultado. Se nenhum endereço for passado, consulteself.walletAddressO saldo do endereço. Observando o código implementado por esta função, podemos ver que precisamos passarself.addTokenSomente os tokens registrados pela função podem ser consultados, pois o contrato que chama o tokenbalanceOfmétodo, as informações de precisão e endereço do token são necessárias.
3、self.sendETH = function(to, amount, options)
A função é transferir ETH para um endereço de carteira (usandotoAs configurações de parâmetros) transferem uma certa quantidade de ETH (usandoamountConfigurações de parâmetros), você pode definir outrooptionsParâmetros (estrutura de dados:{gasPrice: 111, gasLimit: 111, nonce: 111}) é usado para especificargasLimit/gasPrice/nonceSe você não passar o parâmetro options, as configurações padrão do sistema serão usadas.
gasLimit/gasPriceAfeta o ETH consumido ao realizar operações no Ethereum (algumas operações no Ethereum consomem gás, ou seja, consomem uma certa quantidade de tokens ETH).
4、self.getPrice = function(pair, fee)
Esta função é usada para obter o preço de um par de negociação no Uniswap. A partir do código de implementação da função, podemos ver que quando a função começa a ser executada, o par de pares de negociação será analisado primeiro para obter a baseCurrency e a quoteCurrency. Por exemplo, se o par de negociação for ETH_USDT, ele será dividido em ETH e USDT. Então pergunteself.tokenInfoVerifique se há informações sobre esses dois tokens nos dados. Se não, um erro será reportado.
O endereço do pool de câmbio no Uniswap é composto pelos dois endereços de token participantes e Fee (taxa padrão), portanto, ao consultarself.poolQuando o endereço do pool é registrado em (self.pool como mencionamos antes, você pode conferir), se ele não for encontrado, os endereços dos dois tokens e a taxa são usados para calcular o endereço do pool. Portanto, um par de negociação pode ter vários pools porque a taxa pode ser diferente.
Consultar e calcular o endereço do pool de troca chamando o contrato de fábrica Uniswap V3getPoolMétodo obtido (portanto o ABI do contrato de fábrica deve ser registrado no início).
Depois de obter o endereço do pool deste par de negociação, você pode registrar o ABI do contrato do pool. Dessa forma, o pool (contrato inteligente) pode ser chamado deslot0método para obter dados de preços. Claro, os dados retornados por esse método não são um preço legível por humanos, mas uma estrutura de dados relacionada ao preço. Processamento adicional é necessário para obter um preço legível. É quando usamos ocomputePoolPricefunção.
5、self.swapToken = function(tokenIn, amountInDecimal, tokenOut, options)
A função é trocar tokens. O parâmetro tokenIn é o nome do token pago na troca, o parâmetro tokenOut é o nome do token obtido na troca, o parâmetro amountInDecimal é o valor da troca (valor legível por humanos) e o parâmetro options é o mesmo que mencionamos antes. Igual a , você pode definir o consumo de gás, nonce, etc. ao trocar.
Quando a função é executada, ela é primeiro passadaself.tokenInfoAs informações do token são obtidas da variável. A troca também tem muitos detalhes. Primeiro de tudo, se o token envolvido na troca não for ETH, você precisa darroteamento(Contrato inteligente responsável pela autorização de troca). Antes da autorização, verifique se há valor de autorização suficiente.
let allowanceAmount = e.IO("api", tokenInInfo.address, "allowance", self.walletAddress, ContractV3SwapRouterV2);
Utilize o método de subsídio de contrato de token para consultar o valor autorizado. Ao comparar o valor autorizado com o valor de resgate atual, se o valor autorizado for suficiente para o resgate, nenhuma autorização adicional será necessária. Caso o valor seja insuficiente, será realizado o processamento da autorização.
Também há um detalhe na autorização aqui. Se o token autorizado for USDT, você precisa redefinir a quantidade de autorização para 0 antes de autorizar. Autorize o uso do método approve do contrato de token. Observe que o método de autorização de aprovação é um método que consome gás e consumirá uma certa quantidade de ETH. Então você precisa usar a função self.waitMined para aguardar o resultado do processamento.
Para evitar autorizações frequentes e pagamentos desnecessários de ETH, esta operação de autorização autoriza o valor máximo de uma só vez.
let txApprove = e.IO("api", tokenInInfo.address, "approve", ContractV3SwapRouterV2, '0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff');
Se você tiver cota de troca suficiente, você pode fazer a troca. Mas também há detalhes aqui. Se o token envolvido na troca for ETH, o endereço de recebimento precisa ser modificado:
recipientAddress = '0x0000000000000000000000000000000000000002'
As razões específicas são bem complicadas e não serão elaboradas aqui. Por favor, consulte:
ADDRESS_THIS https://degencode.substack.com/p/uniswapv3-multicall
https://etherscan.io/address/0x68b3465833fb72a70ecdf485e0e4c7bd8665fc45#code
Em seguida, use a função de empacotamento encapsulada pela plataforma FMZe.IO("encode", ..., empacote a chamada do método swapExactTokensForTokens para o roteador (contrato inteligente). Se o token obtido após a troca for ETH, você precisa adicionar uma etapa da operação de desempacotamento WETH9:
data.push(e.IO("encode", ContractV3SwapRouterV2, "unwrapWETH9(uint256,address)", 1, self.walletAddress))
Porque o token envolvido na troca é WETH, que é um token empacotado de ETH. Para converter para ETH real, uma operação de desempacotamento é necessária. Após o empacotamento da operação de desempacotamento, o método multicall do roteador (contrato inteligente) pode ser chamado para executar esta série de operações. Há mais um detalhe ao qual você precisa prestar atenção extra. Se o par de transações envolvido na troca for ETH, você precisa definir a quantidade de ETH a ser transferida nas etapas a seguir. Se não for ETH, defina-o como 0.
let tx = e.IO("api", ContractV3SwapRouterV2, "multicall(uint256,bytes[])", (tokenInInfo.name == 'ETH' ? amountIn : 0), (new Date().getTime() / 1000) + 3600, data, options || {})
Esta configuração é refletida aqui:(tokenInInfo.name == 'ETH' ? amountIn : 0). O editor não percebeu isso antes e não definiu 0 quando tokenIn não era igual ao token ETH, o que levou à transferência equivocada de ETH. Portanto, tenha muito cuidado ao escrever o código de transferência.
Parte 4: Como usar objetos de operação Uniswap V3
O código neste modelo na verdade tem menos de 200 linhas de funcionalidade. A seção a seguir é, na verdade, uma demonstração de uso.
$.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()Esta função é apenas uma demonstração, por favor, não a chame se não tiver utilidade prática. Usaremos esta função para ver como usar esta biblioteca de modelos para operar as funções do Uniswap V3.
O código é executado primeirolet ex = $.NewUniswapV3()Constrói um objeto de operação Uniswap V3. Se você quiser obter o endereço da carteira vinculado à troca atual, você pode usarex.walletAddressPegar. Então use-o no códigoex.addTokenTrês tokens foram registrados: ETH, USDT e 1INCH.
Imprima o preço de um par de negociação (o token precisa ser registrado primeiro):
Log(ex.getPrice('ETH_USDT'))
Log(ex.getPrice('1INCH_USDT'))
Se a função getPrice não definir Fee, a taxa padrão de 3000 será usada, que será convertida em um valor legível de 0,3%.
Se você quiser converter 0,01 ETH para USDT, verificar o saldo e depois convertê-lo novamente, use o código:
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转账操作
Teste usando a rede de teste Goerli
- Configurar o objeto de troca testnet
Observe que ao definir o nó, você precisa defini-lo como o nó da rede de teste Goerli.
- Escreva uma estratégia e teste-a na rede de testes 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'))
}
No código de teste, testamos a impressão de endereços de carteira, o registro de informações de token, a impressão de saldos de ativos e a realização de uma troca contínua.ETH -> UNI -> LINK. Deve-se notar que o endereço de token registrado aqui está na rede de teste Ethereum Goerli, então os endereços de token com o mesmo nome são diferentes. Quanto às moedas de teste, você pode usar o faucet desta rede de teste para solicitar tokens de teste. Você pode verificar o Google para mais detalhes.
Observe que você precisa verificar o modelo "Uniswap V3 Trading Library" para usá-lo$.NewUniswapV3()Função, caso sua conta FMZ não tenha esse modelo, você pode clicarPegue aqui。
Registro de operação de estratégia:
Valores de ativos exibidos na página Uniswap
Essas operações também podem ser consultadas na cadeia:
A troca de ETH para UNI foi executada uma vez, a autorização de UNI foi executada uma vez e a troca de UNI para LINK foi executada uma vez.
END
Esta biblioteca tem muitas funções que podem ser expandidas e até mesmo expandidas para empacotar vários resgatestokenA -> tokenB -> tokenCTroca de caminho. Ele pode ser otimizado e expandido de acordo com necessidades específicas. Este tipo de código de biblioteca é fornecido principalmente para ensino.
renovar
AtualizadoswapTokenFunção, suportatokenA -> tokenB -> tokenC ... -> tokenDFunção de resgate contínuo. Você pode ver o código mais recente deste modelo publicado no Strategy Square no FMZ.
- 1








