Frontend Integration

Find the Correct AMM Plugin for a Given Liquidity Pool

In these links, you can find the AMM Aggregator smart contract for both mainnet and Ropsten.

And you can find all of the needed ABIs here.

//USDC-DAI liquidity pool on UniswapV2
var myLiquidityPool = "0xAE461cA67B15dc8dc81CE7615e0320dA1A9aB8D5";

//The mainnet address of the AMM Aggregator
var ammAggregatorAddress = "0x81391d117a03A6368005e447197739D06550D4CD";

var ammAggreggator = new web3.eth.Contract(abis.AMMAggregatorABI, ammAggregatorAddress);

var liquidityPoolData = await ammAggregator.methods.findByLiquidityPool(myLiquidityPool).call();

//findByLiquidityPool returns several interesting data:

//Position 0: The total supply of liquidity pool token
var liquidityPoolTotalSupplyByAMMAggregator = liquidityPoolData[0];

//Position 1: An array containing the reserves of each token in the pool
var tokenReservesByAMMAggregator = liquidityPoolData[1];

//Position 2: An array containing the token addresses that make up the pool
var tokenAddressesByAMMAggregator = liquidityPoolData[2];

//Position 3: The address of the correct AMM plugin which served the previous data
var ammPluginAddress = liquidityPoolData[3];

Let's do some cool reverse engineering!

//We'll use the previously retrieved ammPluginAddressByAMMAggregator to grab some data
var ammPlugin = new web3.eth.Contract(abis.AMMABI, ammPluginAddress);

//First of all, let's introduce some entropy: revert the order of the tokens
var tokens = tokenAddressesByAMMAggregator.reverse();

//Let's use the liquidity pool tokens to retrieve liquidity pool info
var data = await ammPlugin.methods.byTokens(tokens).call();

//Position 0: The total supply of liquidity pool token
var liquidityPoolTotalSupplyByAMMPlugin = data[0];

//Same value as before!
assert(liquidityPoolTotalSupplyByAMMPlugin === liquidityPoolTotalSupplyByAMMAggregator);

//Position 1: An array containing the reserves of each token in the pool
var 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 AMM
var liquidityPoolAddressByAMMPlugin = data[2];

//Same value as before!
assert(liquidityPoolAddressByAMMPlugin === myLiquidityPool);

//Position 3: An array containing the tokens addresses that make up the pool
var 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 AMM Aggregator for frontend purposes only, and the AMM Plugin directly for smart contracts.

//Retrieve all AMM plugins
var ammPluginAddresses = await ammAggregator.methods.amms().call();

var ammPlugins = {};

await Promise.all(ammPluginAddresses.map(async ammPluginAddress => {
    var contract = new web3.eth.Contract(abis.AMMABI, ammPluginAddress);
    var info = await contract.methods.info().call();
    ammPlugins[info[0]] = contract;
}));

var uniswapV2AMMPlugin = Object.entries(ammPlugins).filter(entry => entry[0].toLowerCase().indexOf("uniswapv2") !== -1)[0][1];

Add Liquidity In a Pool Involving ETH

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 plugin
var ammData = await uniswapV2AMMPlugin.methods.data().call();

//It returns the address this AMM implementation uses to represent Ethereum.
var ethereumAddress = ammData[0];

var DAIAddress = "0x6B175474E89094C44Da98b954EedeAC495271d0F";
var DAIToken = new web3.eth.Contract(abis.IERC20ABI, DAIAddress);

var liquidityPoolAddress = await uniswapV2AMMPlugin.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 amount
var data = await uniswapV2AMMPlugin.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 need
var DAIAmount = 300 * 1e18;
data = await uniswapV2AMMPlugin.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 = await uniswapV2AMMPlugin.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 tokensAddresses
var daiIndexInArray = tokensAddresses.indexOf(DAIAddress);
DAIAmount = tokensAmounts[daiIndexArray];
await DAIToken.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 calculations
var amount = DAIAmount;
var amountIsLiquidityPool = false;
var tokenAddress = DAIAddress;

// === Case 2: Add liquidity using LP amount for calculations
amount = liquidityPoolAmount;
amountIsLiquidityPool = true;
//If amountIsLiquidityPool is true then the tokenAddress value is ignored
tokenAddress = "0x0000000000000000000000000000000000000000";

//In the specific case of UniswapV2 AMM, involvingETH == true means the liquidity is DAI-ETH.
//involvingETH == false means the liquidity is DAI-WETH
var involvingETH = true;

var ethIndexInArray = tokensAddresses.indexOf(ethereumAddress);
var ethAmount = tokensAmounts[ethIndexInArray];

var liquidityPoolData = {
    liquidityPoolAddress,
    amount,
    tokenAddress,
    amountIsLiquidityPool,
    involvingETH,
    receiver : myAddress
}

await uniswapV2AMMPlugin.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.

var retrieveSwapData = async function retrieveSwapData(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 = await amm.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 hops
    return {
        tokenAddress : tokens[0],
        swapPools,
        swapTokens
    }
}

var tokens = [
    DAITokenAddress,
    buidlTokenAddress,
    ethereumAddress
];

var preliminarySwapData = await retrieveSwapData(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
};

await uniswapV2AMMPlugin.methods.swapLiquidity(swapData).send({from : myAddress});Find the Correct AMM Plugin for a Given Liquidity Pool
In these links, you can find the AMM Aggregator smart contract for both 
mainnet
 and 
Ropsten
.
And you can find all of the needed ABIs 
here
.
//USDC-DAI liquidity pool on UniswapV2
var myLiquidityPool = "0xAE461cA67B15dc8dc81CE7615e0320dA1A9aB8D5";

//The mainnet address of the AMM Aggregator
var ammAggregatorAddress = "0x81391d117a03A6368005e447197739D06550D4CD";

var ammAggreggator = new web3.eth.Contract(abis.AMMAggregatorABI, ammAggregatorAddress);

var liquidityPoolData = await ammAggregator.methods.findByLiquidityPool(myLiquidityPool).call();

//findByLiquidityPool returns several interesting data:

//Position 0: The total supply of liquidity pool token
var liquidityPoolTotalSupplyByAMMAggregator = liquidityPoolData[0];

//Position 1: An array containing the reserves of each token in the pool
var tokenReservesByAMMAggregator = liquidityPoolData[1];

//Position 2: An array containing the token addresses that make up the pool
var tokenAddressesByAMMAggregator = liquidityPoolData[2];

//Position 3: The address of the correct AMM plugin which served the previous data
var ammPluginAddress = liquidityPoolData[3];
Let's do some cool reverse engineering!
//We'll use the previously retrieved ammPluginAddressByAMMAggregator to grab some data
var ammPlugin = new web3.eth.Contract(abis.AMMABI, ammPluginAddress);

//First of all, let's introduce some entropy: revert the order of the tokens
var tokens = tokenAddressesByAMMAggregator.reverse();

//Let's use the liquidity pool tokens to retrieve liquidity pool info
var data = await ammPlugin.methods.byTokens(tokens).call();

//Position 0: The total supply of liquidity pool token
var liquidityPoolTotalSupplyByAMMPlugin = data[0];

//Same value as before!
assert(liquidityPoolTotalSupplyByAMMPlugin === liquidityPoolTotalSupplyByAMMAggregator);

//Position 1: An array containing the reserves of each token in the pool
var 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 AMM
var liquidityPoolAddressByAMMPlugin = data[2];

//Same value as before!
assert(liquidityPoolAddressByAMMPlugin === myLiquidityPool);

//Position 3: An array containing the tokens addresses that make up the pool
var 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 AMM Aggregator for frontend purposes only, and the AMM Plugin directly for smart contracts.
//Retrieve all AMM plugins
var ammPluginAddresses = await ammAggregator.methods.amms().call();

var ammPlugins = {};

await Promise.all(ammPluginAddresses.map(async ammPluginAddress => {
    var contract = new web3.eth.Contract(abis.AMMABI, ammPluginAddress);
    var info = await contract.methods.info().call();
    ammPlugins[info[0]] = contract;
}));

var uniswapV2AMMPlugin = Object.entries(ammPlugins).filter(entry => entry[0].toLowerCase().indexOf("uniswapv2") !== -1)[0][1];
Add Liquidity In a Pool Involving ETH
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 plugin
var ammData = await uniswapV2AMMPlugin.methods.data().call();

//It returns the address this AMM implementation uses to represent Ethereum.
var ethereumAddress = ammData[0];

var DAIAddress = "0x6B175474E89094C44Da98b954EedeAC495271d0F";
var DAIToken = new web3.eth.Contract(abis.IERC20ABI, DAIAddress);

var liquidityPoolAddress = await uniswapV2AMMPlugin.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 amount
var data = await uniswapV2AMMPlugin.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 need
var DAIAmount = 300 * 1e18;
data = await uniswapV2AMMPlugin.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 = await uniswapV2AMMPlugin.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 tokensAddresses
var daiIndexInArray = tokensAddresses.indexOf(DAIAddress);
DAIAmount = tokensAmounts[daiIndexArray];
await DAIToken.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 calculations
var amount = DAIAmount;
var amountIsLiquidityPool = false;
var tokenAddress = DAIAddress;

// === Case 2: Add liquidity using LP amount for calculations
amount = liquidityPoolAmount;
amountIsLiquidityPool = true;
//If amountIsLiquidityPool is true then the tokenAddress value is ignored
tokenAddress = "0x0000000000000000000000000000000000000000";

//In the specific case of UniswapV2 AMM, involvingETH == true means the liquidity is DAI-ETH.
//involvingETH == false means the liquidity is DAI-WETH
var involvingETH = true;

var ethIndexInArray = tokensAddresses.indexOf(ethereumAddress);
var ethAmount = tokensAmounts[ethIndexInArray];

var liquidityPoolData = {
    liquidityPoolAddress,
    amount,
    tokenAddress,
    amountIsLiquidityPool,
    involvingETH,
    receiver : myAddress
}

await uniswapV2AMMPlugin.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.
var retrieveSwapData = async function retrieveSwapData(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 = await amm.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 hops
    return {
        tokenAddress : tokens[0],
        swapPools,
        swapTokens
    }
}

var tokens = [
    DAITokenAddress,
    buidlTokenAddress,
    ethereumAddress
];

var preliminarySwapData = await retrieveSwapData(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
};

await uniswapV2AMMPlugin.methods.swapLiquidity(swapData).send({from : myAddress});

Last updated