Pool Types
Users can only add liquidity to constant product pools. Single-sided pools only receive an initial deposit of asset A, at which point they trade against a bonding curve until they bond. Constant product pools require an initial deposit of both assets.Simulate Before Adding
Adding liquidity has to be done in the right ratio to maintain the pool’s price. Always simulate to check expected LP tokens and potential refunds:Copy
Ask AI
async function simulateAddLiquidity() {
try {
const simulation = await client.simulateAddLiquidity({
poolId: "pool-public-key",
assetAAmount: "100000000", // 1 BTC
assetBAmount: "100000000000", // 100,000 USDB
});
console.log('LP tokens to mint:', simulation.lpTokensToMint);
console.log('Pool share %:', simulation.poolSharePercentage);
console.log('Asset A refund:', simulation.assetARefundAmount);
console.log('Asset B refund:', simulation.assetBRefundAmount);
return simulation;
} catch (error) {
console.error('Simulation failed:', error);
}
}
Execute Liquidity Addition
Copy
Ask AI
async function addLiquidity() {
try {
// First, check the pool to understand the current ratio
const pool = await client.getPool("pool-id");
console.log('Current pool ratio:', pool.assetAReserve, ':', pool.assetBReserve);
// Add liquidity maintaining the pool ratio
const response = await client.addLiquidity({
poolId: "pool-id",
assetAAmount: "100000000", // 1 BTC
assetBAmount: "100000000000", // 100,000 USDB
assetAMinAmountIn: "100000000", // set minimums to protect from price moves
assetBMinAmountIn: "100000000000",
});
console.log('Liquidity added successfully!');
console.log('LP tokens received:', response.lpTokensMinted);
// Check if any assets were refunded
if (response.refund) {
console.log('Asset A refunded:', response.refund.assetAAmount);
console.log('Asset B refunded:', response.refund.assetBAmount);
}
return response;
} catch (error) {
console.error('Failed to add liquidity:', error);
}
}
Optimal Amount Calculation
Avoid refunds by calculating optimal amounts:Copy
Ask AI
async function addLiquidityOptimal(poolId: string, assetADesired: bigint) {
// Get current pool state
const pool = await client.getPool(poolId);
// Calculate optimal asset B amount based on current ratio
const optimalAssetB = (assetADesired * BigInt(pool.assetBReserve)) / BigInt(pool.assetAReserve);
console.log('Optimal amounts:', {
assetA: assetADesired,
assetB: optimalAssetB
});
// Add liquidity with optimal amounts
const response = await client.addLiquidity({
poolId: poolId,
assetAAmount: assetADesired.toString(),
assetBAmount: optimalAssetB.toString(),
assetAMinAmountIn: assetADesired.toString(),
assetBMinAmountIn: optimalAssetB.toString(),
});
console.log('Added liquidity with no refunds');
return response;
}
Tracking Your Position
Monitor LP Holdings
Copy
Ask AI
async function trackPosition(poolId: string) {
// Get your current position
const position = await client.getLpPosition(poolId);
console.log('LP tokens owned:', position.lpTokensOwned);
console.log('Pool share:', position.sharePercentage, '%');
console.log('Current value - Asset A:', position.valueAssetA);
console.log('Current value - Asset B:', position.valueAssetB);
// Check principal amounts if available
if (position.principalAssetA !== undefined && position.principalAssetB !== undefined) {
console.log('Initial deposit - Asset A:', position.principalAssetA);
console.log('Initial deposit - Asset B:', position.principalAssetB);
}
// Check unrealized P&L if available
if (position.unrealizedProfitLossAssetA && position.unrealizedProfitLossAssetB) {
console.log('Unrealized P&L - Asset A:', position.unrealizedProfitLossAssetA);
console.log('Unrealized P&L - Asset B:', position.unrealizedProfitLossAssetB);
}
return position;
}
Calculate Returns
Track your investment performance:Copy
Ask AI
async function calculateReturns(poolId: string) {
const position = await client.getLpPosition(poolId);
// If principal amounts are available, calculate returns
if (position.principalAssetA !== undefined && position.principalAssetB !== undefined) {
const returnsA = position.valueAssetA - position.principalAssetA;
const returnsB = position.valueAssetB - position.principalAssetB;
// Calculate percentage returns
const percentReturnA = position.principalAssetA > 0
? (returnsA / position.principalAssetA) * 100
: 0;
const percentReturnB = position.principalAssetB > 0
? (returnsB / position.principalAssetB) * 100
: 0;
console.log('Returns:');
console.log(`Asset A: ${returnsA} (${percentReturnA.toFixed(2)}%)`);
console.log(`Asset B: ${returnsB} (${percentReturnB.toFixed(2)}%)`);
}
// The API provides unrealized P&L directly
if (position.unrealizedProfitLossAssetA && position.unrealizedProfitLossAssetB) {
console.log('Unrealized P&L from API:');
console.log(`Asset A: ${position.unrealizedProfitLossAssetA}`);
console.log(`Asset B: ${position.unrealizedProfitLossAssetB}`);
}
// Check for impermanent loss
const pool = await client.getPool(poolId);
const currentPrice = Number(pool.reserveB) / Number(pool.reserveA);
if (position.principalAssetA && position.principalAssetB) {
const initialPrice = position.principalAssetB / position.principalAssetA;
const priceChange = ((currentPrice - initialPrice) / initialPrice) * 100;
console.log(`Price change since deposit: ${priceChange.toFixed(2)}%`);
}
}
Removing Liquidity
Simulate Removal
Check what you’ll receive before removing:Copy
Ask AI
async function simulateRemoveLiquidity(poolId: string, lpTokensToRemove: string) {
try {
const simulation = await client.simulateRemoveLiquidity({
poolId: poolId,
providerPublicKey: (await client.wallet.getIdentityPublicKey()),
lpTokensToRemove: lpTokensToRemove,
});
console.log('Expected asset A:', simulation.assetAAmount);
console.log('Expected asset B:', simulation.assetBAmount);
return simulation;
} catch (error) {
console.error('Simulation failed:', error);
}
}
Execute Removal
Remove liquidity and claim accumulated fees:Copy
Ask AI
async function removeLiquidity(poolId: string, lpTokensToRemove: string) {
try {
// Check your LP balance first
const position = await client.getLpPosition(poolId);
const lpTokensOwned = BigInt(position.lpTokensOwned);
const tokensToRemove = BigInt(lpTokensToRemove);
if (lpTokensOwned < tokensToRemove) {
throw new Error(`Insufficient LP tokens. Owned: ${lpTokensOwned}, Requested: ${tokensToRemove}`);
}
// Remove liquidity
const response = await client.removeLiquidity({
poolId: poolId,
lpTokensToRemove: lpTokensToRemove,
});
console.log('Liquidity removed successfully!');
console.log('Asset A received:', response.assetAWithdrawn);
console.log('Asset B received:', response.assetBWithdrawn);
console.log('Transfers:', response.assetATransferId, response.assetBTransferId);
return response;
} catch (error) {
console.error('Failed to remove liquidity:', error);
}
}
Partial vs Full Removal
Copy
Ask AI
// Remove all liquidity
async function removeAllLiquidity(poolId: string) {
const position = await client.getLpPosition(poolId);
return await removeLiquidity(poolId, position.lpTokensOwned);
}
// Remove partial liquidity (e.g., 50%)
async function removePartialLiquidity(poolId: string, percentage: number) {
const position = await client.getLpPosition(poolId);
const lpTokensToRemove = (BigInt(position.lpTokensOwned) * BigInt(percentage)) / 100n;
return await removeLiquidity(poolId, lpTokensToRemove.toString());
}
Complete Liquidity Lifecycle
Copy
Ask AI
async function completeLiquidityLifecycle() {
const poolId = "pool-id";
// 1. Add liquidity
console.log('=== Adding Liquidity ===');
const addResponse = await client.addLiquidity({
poolId: poolId,
assetAAmount: 100000000n, // 1 BTC
assetBAmount: 100000000000n, // 100,000 USDB
});
const initialInvestment = {
assetA: 100000000n,
assetB: 100000000000n,
};
// 2. Monitor position over time
console.log('\n=== Monitoring Position ===');
await new Promise(resolve => setTimeout(resolve, 60000)); // Wait 1 minute
await trackPosition(poolId);
// 3. Calculate returns
console.log('\n=== Calculating Returns ===');
await calculateReturns(poolId);
// 4. Remove 50% of liquidity
console.log('\n=== Removing 50% Liquidity ===');
await removePartialLiquidity(poolId, 50);
// 5. Check remaining position
console.log('\n=== Remaining Position ===');
await trackPosition(poolId);
}
Error Handling
Copy
Ask AI
async function safeAddLiquidity(poolId: string, assetAAmount: bigint, assetBAmount: bigint) {
try {
// Check balances first
const balance = await client.getBalance();
// Validate sufficient balance
const pool = await client.getPool(poolId);
const BTC_PUBKEY = "020202020202020202020202020202020202020202020202020202020202020202";
const btcRequired = pool.assetAAddress === BTC_PUBKEY ? assetAAmount :
pool.assetBAddress === BTC_PUBKEY ? assetBAmount : 0n;
if (balance.balance < btcRequired) {
throw new Error('Insufficient BTC balance');
}
// Add liquidity
return await client.addLiquidity({
poolId: poolId,
assetAAmount: assetAAmount,
assetBAmount: assetBAmount,
});
} catch (error) {
if (error.message.includes('Insufficient balance')) {
console.error('Not enough funds');
} else if (error.message.includes('Pool inactive')) {
console.error('Pool is currently inactive');
} else if (error.message.includes('Below minimum')) {
console.error('Amount below minimum requirement');
} else {
console.error('Operation failed:', error);
}
throw error;
}
}
Best Practices
- Always simulate first: Check expected outcomes before executing
- Track impermanent loss: Understand the risks of providing liquidity
- Regular monitoring: Check your position and fees periodically
Next Steps
- Create new pools to earn from the start
- Execute swaps to generate fees
- Become a host to earn additional fees