//USDC-DAI liquidity pool on UniswapV2var myLiquidityPool ="0xAE461cA67B15dc8dc81CE7615e0320dA1A9aB8D5";//The mainnet address of the AMM Aggregatorvar ammAggregatorAddress ="0x81391d117a03A6368005e447197739D06550D4CD";var ammAggreggator =newweb3.eth.Contract(abis.AMMAggregatorABI, ammAggregatorAddress);var liquidityPoolData =awaitammAggregator.methods.findByLiquidityPool(myLiquidityPool).call();//findByLiquidityPool returns several interesting data://Position 0: The total supply of liquidity pool tokenvar liquidityPoolTotalSupplyByAMMAggregator = liquidityPoolData[0];//Position 1: An array containing the reserves of each token in the poolvar tokenReservesByAMMAggregator = liquidityPoolData[1];//Position 2: An array containing the token addresses that make up the poolvar tokenAddressesByAMMAggregator = liquidityPoolData[2];//Position 3: The address of the correct AMM plugin which served the previous datavar ammPluginAddress = liquidityPoolData[3];
Let's do some cool reverse engineering!
//We'll use the previously retrieved ammPluginAddressByAMMAggregator to grab some datavar ammPlugin =newweb3.eth.Contract(abis.AMMABI, ammPluginAddress);//First of all, let's introduce some entropy: revert the order of the tokensvar tokens =tokenAddressesByAMMAggregator.reverse();//Let's use the liquidity pool tokens to retrieve liquidity pool infovar data =awaitammPlugin.methods.byTokens(tokens).call();//Position 0: The total supply of liquidity pool tokenvar liquidityPoolTotalSupplyByAMMPlugin = data[0];//Same value as before!assert(liquidityPoolTotalSupplyByAMMPlugin === liquidityPoolTotalSupplyByAMMAggregator);//Position 1: An array containing the reserves of each token in the poolvar tokenReservesByAMMPlugin = data[1];//Same value as before!assert(JSON.stringify(tokenReservesByAMMPlugin) ===JSON.stringify(tokenReservesByAMMAggregator));//Position 2: The liquidity pool address that contains those tokens in the AMMvar liquidityPoolAddressByAMMPlugin = data[2];//Same value as before!assert(liquidityPoolAddressByAMMPlugin === myLiquidityPool);//Position 3: An array containing the tokens addresses that make up the poolvar tokenAddressesByAMMPlugin = data[3];//Same value as before!assert(JSON.stringify(tokenAddressesByAMMPlugin) ===JSON.stringify(tokenAddressesByAMMAggregator));//No matter the input order, the AMM Plugin will always return you back the tokenAddresses array in the same exact order the original AMM would give you
Find the UniswapV2 AMM Plugin
There's no particularly efficient way to do this. You need to use a polling approach, and so it is better to use the AMMAggregator for frontend purposes only, and the AMM Plugin directly for smart contracts.
Every AMM has its own method to manage liquidity pools that are paired with ETH. Uniswap V2, for example, makes use of the WETH token. The AMM Aggregator plugins use very simple and general-purpose APIs that can communicate with the peculiarities of all AMMs.
//The data method returns all those operative info needed to operate in a correct way with the pluginvar ammData =awaituniswapV2AMMPlugin.methods.data().call();//It returns the address this AMM implementation uses to represent Ethereum.var ethereumAddress = ammData[0];var DAIAddress ="0x6B175474E89094C44Da98b954EedeAC495271d0F";var DAIToken =newweb3.eth.Contract(abis.IERC20ABI, DAIAddress);var liquidityPoolAddress =awaituniswapV2AMMPlugin.methods.byTokens([ethereumAddress, DAIAddress]).call();//Liquidity can be added/removed in several ways.// === Case 1: I want to add a more 3% of the entire Liquidity Pool amountvar data =awaituniswapV2AMMPlugin.methods.byPercentage(liquidityPoolAddress,3,100).call();var liquidityPoolAmount = data[0];var tokensAmounts = data[1];var tokensAddresses = data[2];// === Case 2: I only know that I want to add 300 DAI and I need to calculate how much ETH I needvar DAIAmount =300*1e18;data =awaituniswapV2AMMPlugin.methods.byTokenAmount(liquidityPoolAddress, DAIAddress, DAIAmount).call();liquidityPoolAmount = data[0];var tokensAmounts = data[1];var tokensAddresses = data[2];// === Case 3: I want to add a more 0.05 expressed in LP tokens (the example is assuming that the LP token has 18 decimals)liquidityPoolAmount =0.05*1e18;data =awaituniswapV2AMMPlugin.methods.byLiquidityPoolAmount(liquidityPoolAddress, liquidityPoolAmount).call();tokensAmounts = data[0];tokensAddresses = data[1];//Grab the correct DAI amount for approve.//tokensAmounts array holds the values in the same order of the tokensAddressesvar daiIndexInArray =tokensAddresses.indexOf(DAIAddress);DAIAmount = tokensAmounts[daiIndexArray];awaitDAIToken.methods.approve(uniswapV2AMMPlugin.options.address, DAIAmount).send({from: myAddress});//Liquidity pool data needs the amount value of the LP token (amountIsLiquidityPool = true) or the address and value of one of the tokens within the pool (amountIsLiquidityPool = false).//In both cases, the AMM plugin will internally calculate all the necessary amounts and to the correct transfers.// === Case 1: Add liquidity using DAI amount for calculationsvar amount = DAIAmount;var amountIsLiquidityPool =false;var tokenAddress = DAIAddress;// === Case 2: Add liquidity using LP amount for calculationsamount = liquidityPoolAmount;amountIsLiquidityPool =true;//If amountIsLiquidityPool is true then the tokenAddress value is ignoredtokenAddress ="0x0000000000000000000000000000000000000000";//In the specific case of UniswapV2 AMM, involvingETH == true means the liquidity is DAI-ETH.//involvingETH == false means the liquidity is DAI-WETHvar involvingETH =true;var ethIndexInArray =tokensAddresses.indexOf(ethereumAddress);var ethAmount = tokensAmounts[ethIndexInArray];var liquidityPoolData = { liquidityPoolAddress, amount, tokenAddress, amountIsLiquidityPool, involvingETH, receiver : myAddress}awaituniswapV2AMMPlugin.methods.addLiquidity(liquidityPoolData).send({from: myAddress, value : ethAmount});
Swap Liquidity
To demonstrate, let's swap 300 DAI for ETH, and to make things a little more complex, let's articulate the swap path to pass through BUIDL. To swap liquidity, the AMM plugin needs the liquidity pool addresses linked to the swapping pairs. For UniswapV2, since it has unique liquidity pools, the utility method retrieveSwapData easily recovers all of the LP tokens and the swap path. For AMMs like Balancer, you need to manually provide the LP token addresses.
varretrieveSwapData=asyncfunctionretrieveSwapData(tokens, amm) {var swapPools = [];var swapTokens = [];for(i =1; i <tokens.length; i++) {swapTokens.push(tokens[i]);var couple = [ tokens[i -1]; tokens[i]; ]var data =awaitamm.methods.byTokens(couple).call();var liquidityPoolToken = data[0];swapPools.push(liquidityPoolToken); }//Keep in mind that swapPools and swapTokens must have the same length (greater than or equal to 1) to represent all the swapping hopsreturn { tokenAddress : tokens[0], swapPools, swapTokens }}var tokens = [ DAITokenAddress, buidlTokenAddress, ethereumAddress];var preliminarySwapData =awaitretrieveSwapData(tokens, uniswapV2AMMPlugin);var amount =300*1e18;var swapData = { enterInETH :false, exitInETH :true,//In the specific case of UniswapV2, exitInETH == true means DAI -> buidl -> ETH, , exitInETH == false means DAI -> buidl -> WETH liquidityPoolAddresses :preliminarySwapData.swapPools, path :preliminarySwapData.swapTokens, inputToken :preliminarySwapData.tokenAddress, amount, receiver : myAddress};awaituniswapV2AMMPlugin.methods.swapLiquidity(swapData).send({from : myAddress});