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.
1
//USDC-DAI liquidity pool on UniswapV2
2
var myLiquidityPool = "0xAE461cA67B15dc8dc81CE7615e0320dA1A9aB8D5";
3
​
4
//The mainnet address of the AMM Aggregator
5
var ammAggregatorAddress = "0x81391d117a03A6368005e447197739D06550D4CD";
6
​
7
var ammAggreggator = new web3.eth.Contract(abis.AMMAggregatorABI, ammAggregatorAddress);
8
​
9
var liquidityPoolData = await ammAggregator.methods.findByLiquidityPool(myLiquidityPool).call();
10
​
11
//findByLiquidityPool returns several interesting data:
12
​
13
//Position 0: The total supply of liquidity pool token
14
var liquidityPoolTotalSupplyByAMMAggregator = liquidityPoolData[0];
15
​
16
//Position 1: An array containing the reserves of each token in the pool
17
var tokenReservesByAMMAggregator = liquidityPoolData[1];
18
​
19
//Position 2: An array containing the token addresses that make up the pool
20
var tokenAddressesByAMMAggregator = liquidityPoolData[2];
21
​
22
//Position 3: The address of the correct AMM plugin which served the previous data
23
var ammPluginAddress = liquidityPoolData[3];
Copied!

Let's do some cool reverse engineering!

1
//We'll use the previously retrieved ammPluginAddressByAMMAggregator to grab some data
2
var ammPlugin = new web3.eth.Contract(abis.AMMABI, ammPluginAddress);
3
​
4
//First of all, let's introduce some entropy: revert the order of the tokens
5
var tokens = tokenAddressesByAMMAggregator.reverse();
6
​
7
//Let's use the liquidity pool tokens to retrieve liquidity pool info
8
var data = await ammPlugin.methods.byTokens(tokens).call();
9
​
10
//Position 0: The total supply of liquidity pool token
11
var liquidityPoolTotalSupplyByAMMPlugin = data[0];
12
​
13
//Same value as before!
14
assert(liquidityPoolTotalSupplyByAMMPlugin === liquidityPoolTotalSupplyByAMMAggregator);
15
​
16
//Position 1: An array containing the reserves of each token in the pool
17
var tokenReservesByAMMPlugin = data[1];
18
​
19
//Same value as before!
20
assert(JSON.stringify(tokenReservesByAMMPlugin) === JSON.stringify(tokenReservesByAMMAggregator));
21
​
22
//Position 2: The liquidity pool address that contains those tokens in the AMM
23
var liquidityPoolAddressByAMMPlugin = data[2];
24
​
25
//Same value as before!
26
assert(liquidityPoolAddressByAMMPlugin === myLiquidityPool);
27
​
28
//Position 3: An array containing the tokens addresses that make up the pool
29
var tokenAddressesByAMMPlugin = data[3];
30
​
31
//Same value as before!
32
assert(JSON.stringify(tokenAddressesByAMMPlugin) === JSON.stringify(tokenAddressesByAMMAggregator));
33
​
34
//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
Copied!

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.
1
//Retrieve all AMM plugins
2
var ammPluginAddresses = await ammAggregator.methods.amms().call();
3
​
4
var ammPlugins = {};
5
​
6
await Promise.all(ammPluginAddresses.map(async ammPluginAddress => {
7
var contract = new web3.eth.Contract(abis.AMMABI, ammPluginAddress);
8
var info = await contract.methods.info().call();
9
ammPlugins[info[0]] = contract;
10
}));
11
​
12
var uniswapV2AMMPlugin = Object.entries(ammPlugins).filter(entry => entry[0].toLowerCase().indexOf("uniswapv2") !== -1)[0][1];
Copied!

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.
1
//The data method returns all those operative info needed to operate in a correct way with the plugin
2
var ammData = await uniswapV2AMMPlugin.methods.data().call();
3
​
4
//It returns the address this AMM implementation uses to represent Ethereum.
5
var ethereumAddress = ammData[0];
6
​
7
var DAIAddress = "0x6B175474E89094C44Da98b954EedeAC495271d0F";
8
var DAIToken = new web3.eth.Contract(abis.IERC20ABI, DAIAddress);
9
​
10
var liquidityPoolAddress = await uniswapV2AMMPlugin.methods.byTokens([ethereumAddress, DAIAddress]).call();
11
​
12
//Liquidity can be added/removed in several ways.
13
​
14
// === Case 1: I want to add a more 3% of the entire Liquidity Pool amount
15
var data = await uniswapV2AMMPlugin.methods.byPercentage(liquidityPoolAddress, 3, 100).call();
16
​
17
var liquidityPoolAmount = data[0];
18
​
19
var tokensAmounts = data[1];
20
​
21
var tokensAddresses = data[2];
22
​
23
​
24
// === Case 2: I only know that I want to add 300 DAI and I need to calculate how much ETH I need
25
var DAIAmount = 300 * 1e18;
26
data = await uniswapV2AMMPlugin.methods.byTokenAmount(liquidityPoolAddress, DAIAddress, DAIAmount).call();
27
​
28
liquidityPoolAmount = data[0];
29
​
30
var tokensAmounts = data[1];
31
​
32
var tokensAddresses = data[2];
33
​
34
​
35
// === 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)
36
liquidityPoolAmount = 0.05 * 1e18;
37
data = await uniswapV2AMMPlugin.methods.byLiquidityPoolAmount(liquidityPoolAddress, liquidityPoolAmount).call();
38
​
39
tokensAmounts = data[0];
40
​
41
tokensAddresses = data[1];
42
​
43
​
44
//Grab the correct DAI amount for approve.
45
//tokensAmounts array holds the values in the same order of the tokensAddresses
46
var daiIndexInArray = tokensAddresses.indexOf(DAIAddress);
47
DAIAmount = tokensAmounts[daiIndexArray];
48
await DAIToken.methods.approve(uniswapV2AMMPlugin.options.address, DAIAmount).send({from: myAddress});
49
​
50
//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).
51
//In both cases, the AMM plugin will internally calculate all the necessary amounts and to the correct transfers.
52
​
53
// === Case 1: Add liquidity using DAI amount for calculations
54
var amount = DAIAmount;
55
var amountIsLiquidityPool = false;
56
var tokenAddress = DAIAddress;
57
​
58
// === Case 2: Add liquidity using LP amount for calculations
59
amount = liquidityPoolAmount;
60
amountIsLiquidityPool = true;
61
//If amountIsLiquidityPool is true then the tokenAddress value is ignored
62
tokenAddress = "0x0000000000000000000000000000000000000000";
63
​
64
//In the specific case of UniswapV2 AMM, involvingETH == true means the liquidity is DAI-ETH.
65
//involvingETH == false means the liquidity is DAI-WETH
66
var involvingETH = true;
67
​
68
var ethIndexInArray = tokensAddresses.indexOf(ethereumAddress);
69
var ethAmount = tokensAmounts[ethIndexInArray];
70
​
71
var liquidityPoolData = {
72
liquidityPoolAddress,
73
amount,
74
tokenAddress,
75
amountIsLiquidityPool,
76
involvingETH,
77
receiver : myAddress
78
}
79
​
80
await uniswapV2AMMPlugin.methods.addLiquidity(liquidityPoolData).send({from: myAddress, value : ethAmount});
Copied!

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.
1
var retrieveSwapData = async function retrieveSwapData(tokens, amm) {
2
var swapPools = [];
3
var swapTokens = [];
4
​
5
for(i = 1; i < tokens.length; i++) {
6
swapTokens.push(tokens[i]);
7
var couple = [
8
tokens[i - 1];
9
tokens[i];
10
]
11
var data = await amm.methods.byTokens(couple).call();
12
var liquidityPoolToken = data[0];
13
swapPools.push(liquidityPoolToken);
14
}
15
16
//Keep in mind that swapPools and swapTokens must have the same length (greater than or equal to 1) to represent all the swapping hops
17
return {
18
tokenAddress : tokens[0],
19
swapPools,
20
swapTokens
21
}
22
}
23
​
24
var tokens = [
25
DAITokenAddress,
26
buidlTokenAddress,
27
ethereumAddress
28
];
29
​
30
var preliminarySwapData = await retrieveSwapData(tokens, uniswapV2AMMPlugin);
31
​
32
var amount = 300 * 1e18;
33
​
34
var swapData = {
35
enterInETH : false,
36
exitInETH : true, //In the specific case of UniswapV2, exitInETH == true means DAI -> buidl -> ETH, , exitInETH == false means DAI -> buidl -> WETH
37
liquidityPoolAddresses : preliminarySwapData.swapPools,
38
path : preliminarySwapData.swapTokens,
39
inputToken : preliminarySwapData.tokenAddress,
40
amount,
41
receiver : myAddress
42
};
43
​
44
await uniswapV2AMMPlugin.methods.swapLiquidity(swapData).send({from : myAddress});Find the Correct AMM Plugin for a Given Liquidity Pool
45
In these links, you can find the AMM Aggregator smart contract for both
46
mainnet
47
and
48
Ropsten
49
.
50
And you can find all of the needed ABIs
51
here
52
.
53
//USDC-DAI liquidity pool on UniswapV2
54
var myLiquidityPool = "0xAE461cA67B15dc8dc81CE7615e0320dA1A9aB8D5";
55
​
56
//The mainnet address of the AMM Aggregator
57
var ammAggregatorAddress = "0x81391d117a03A6368005e447197739D06550D4CD";
58
​
59
var ammAggreggator = new web3.eth.Contract(abis.AMMAggregatorABI, ammAggregatorAddress);
60
​
61
var liquidityPoolData = await ammAggregator.methods.findByLiquidityPool(myLiquidityPool).call();
62
​
63
//findByLiquidityPool returns several interesting data:
64
​
65
//Position 0: The total supply of liquidity pool token
66
var liquidityPoolTotalSupplyByAMMAggregator = liquidityPoolData[0];
67
​
68
//Position 1: An array containing the reserves of each token in the pool
69
var tokenReservesByAMMAggregator = liquidityPoolData[1];
70
​
71
//Position 2: An array containing the token addresses that make up the pool
72
var tokenAddressesByAMMAggregator = liquidityPoolData[2];
73
​
74
//Position 3: The address of the correct AMM plugin which served the previous data
75
var ammPluginAddress = liquidityPoolData[3];
76
Let's do some cool reverse engineering!
77
//We'll use the previously retrieved ammPluginAddressByAMMAggregator to grab some data
78
var ammPlugin = new web3.eth.Contract(abis.AMMABI, ammPluginAddress);
79
​
80
//First of all, let's introduce some entropy: revert the order of the tokens
81
var tokens = tokenAddressesByAMMAggregator.reverse();
82
​
83
//Let's use the liquidity pool tokens to retrieve liquidity pool info
84
var data = await ammPlugin.methods.byTokens(tokens).call();
85
​
86
//Position 0: The total supply of liquidity pool token
87
var liquidityPoolTotalSupplyByAMMPlugin = data[0];
88
​
89
//Same value as before!
90
assert(liquidityPoolTotalSupplyByAMMPlugin === liquidityPoolTotalSupplyByAMMAggregator);
91
​
92
//Position 1: An array containing the reserves of each token in the pool
93
var tokenReservesByAMMPlugin = data[1];
94
​
95
//Same value as before!
96
assert(JSON.stringify(tokenReservesByAMMPlugin) === JSON.stringify(tokenReservesByAMMAggregator));
97
​
98
//Position 2: The liquidity pool address that contains those tokens in the AMM
99
var liquidityPoolAddressByAMMPlugin = data[2];
100
​
101
//Same value as before!
102
assert(liquidityPoolAddressByAMMPlugin === myLiquidityPool);
103
​
104
//Position 3: An array containing the tokens addresses that make up the pool
105
var tokenAddressesByAMMPlugin = data[3];
106
​
107
//Same value as before!
108
assert(JSON.stringify(tokenAddressesByAMMPlugin) === JSON.stringify(tokenAddressesByAMMAggregator));
109
​
110
//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
111
Find the UniswapV2 AMM Plugin
112
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.
113
//Retrieve all AMM plugins
114
var ammPluginAddresses = await ammAggregator.methods.amms().call();
115
​
116
var ammPlugins = {};
117
​
118
await Promise.all(ammPluginAddresses.map(async ammPluginAddress => {
119
var contract = new web3.eth.Contract(abis.AMMABI, ammPluginAddress);
120
var info = await contract.methods.info().call();
121
ammPlugins[info[0]] = contract;
122
}));
123
​
124
var uniswapV2AMMPlugin = Object.entries(ammPlugins).filter(entry => entry[0].toLowerCase().indexOf("uniswapv2") !== -1)[0][1];
125
Add Liquidity In a Pool Involving ETH
126
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.
127
//The data method returns all those operative info needed to operate in a correct way with the plugin
128
var ammData = await uniswapV2AMMPlugin.methods.data().call();
129
​
130
//It returns the address this AMM implementation uses to represent Ethereum.
131
var ethereumAddress = ammData[0];
132
​
133
var DAIAddress = "0x6B175474E89094C44Da98b954EedeAC495271d0F";
134
var DAIToken = new web3.eth.Contract(abis.IERC20ABI, DAIAddress);
135
​
136
var liquidityPoolAddress = await uniswapV2AMMPlugin.methods.byTokens([ethereumAddress, DAIAddress]).call();
137
​
138
//Liquidity can be added/removed in several ways.
139
​
140
// === Case 1: I want to add a more 3% of the entire Liquidity Pool amount
141
var data = await uniswapV2AMMPlugin.methods.byPercentage(liquidityPoolAddress, 3, 100).call();
142
​
143
var liquidityPoolAmount = data[0];
144
​
145
var tokensAmounts = data[1];
146
​
147
var tokensAddresses = data[2];
148
​
149
​
150
// === Case 2: I only know that I want to add 300 DAI and I need to calculate how much ETH I need
151
var DAIAmount = 300 * 1e18;
152
data = await uniswapV2AMMPlugin.methods.byTokenAmount(liquidityPoolAddress, DAIAddress, DAIAmount).call();
153
​
154
liquidityPoolAmount = data[0];
155
​
156
var tokensAmounts = data[1];
157
​
158
var tokensAddresses = data[2];
159
​
160
​
161
// === 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)
162
liquidityPoolAmount = 0.05 * 1e18;
163
data = await uniswapV2AMMPlugin.methods.byLiquidityPoolAmount(liquidityPoolAddress, liquidityPoolAmount).call();
164
​
165
tokensAmounts = data[0];
166
​
167
tokensAddresses = data[1];
168
​
169
​
170
//Grab the correct DAI amount for approve.
171
//tokensAmounts array holds the values in the same order of the tokensAddresses
172
var daiIndexInArray = tokensAddresses.indexOf(DAIAddress);
173
DAIAmount = tokensAmounts[daiIndexArray];
174
await DAIToken.methods.approve(uniswapV2AMMPlugin.options.address, DAIAmount).send({from: myAddress});
175
​
176
//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).
177
//In both cases, the AMM plugin will internally calculate all the necessary amounts and to the correct transfers.
178
​
179
// === Case 1: Add liquidity using DAI amount for calculations
180
var amount = DAIAmount;
181
var amountIsLiquidityPool = false;
182
var tokenAddress = DAIAddress;
183
​
184
// === Case 2: Add liquidity using LP amount for calculations
185
amount = liquidityPoolAmount;
186
amountIsLiquidityPool = true;
187
//If amountIsLiquidityPool is true then the tokenAddress value is ignored
188
tokenAddress = "0x0000000000000000000000000000000000000000";
189
​
190
//In the specific case of UniswapV2 AMM, involvingETH == true means the liquidity is DAI-ETH.
191
//involvingETH == false means the liquidity is DAI-WETH
192
var involvingETH = true;
193
​
194
var ethIndexInArray = tokensAddresses.indexOf(ethereumAddress);
195
var ethAmount = tokensAmounts[ethIndexInArray];
196
​
197
var liquidityPoolData = {
198
liquidityPoolAddress,
199
amount,
200
tokenAddress,
201
amountIsLiquidityPool,
202
involvingETH,
203
receiver : myAddress
204
}
205
​
206
await uniswapV2AMMPlugin.methods.addLiquidity(liquidityPoolData).send({from: myAddress, value : ethAmount});
207
Swap Liquidity
208
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.
209
var retrieveSwapData = async function retrieveSwapData(tokens, amm) {
210
var swapPools = [];
211
var swapTokens = [];
212
​
213
for(i = 1; i < tokens.length; i++) {
214
swapTokens.push(tokens[i]);
215
var couple = [
216
tokens[i - 1];
217
tokens[i];
218
]
219
var data = await amm.methods.byTokens(couple).call();
220
var liquidityPoolToken = data[0];
221
swapPools.push(liquidityPoolToken);
222
}
223
224
//Keep in mind that swapPools and swapTokens must have the same length (greater than or equal to 1) to represent all the swapping hops
225
return {
226
tokenAddress : tokens[0],
227
swapPools,
228
swapTokens
229
}
230
}
231
​
232
var tokens = [
233
DAITokenAddress,
234
buidlTokenAddress,
235
ethereumAddress
236
];
237
​
238
var preliminarySwapData = await retrieveSwapData(tokens, uniswapV2AMMPlugin);
239
​
240
var amount = 300 * 1e18;
241
​
242
var swapData = {
243
enterInETH : false,
244
exitInETH : true, //In the specific case of UniswapV2, exitInETH == true means DAI -> buidl -> ETH, , exitInETH == false means DAI -> buidl -> WETH
245
liquidityPoolAddresses : preliminarySwapData.swapPools,
246
path : preliminarySwapData.swapTokens,
247
inputToken : preliminarySwapData.tokenAddress,
248
amount,
249
receiver : myAddress
250
};
251
​
252
await uniswapV2AMMPlugin.methods.swapLiquidity(swapData).send({from : myAddress});
Copied!