π οΈHODLMM API Documentation
This page provides endpoints and sample code for integrating with HODLMM.
API Usage and Documents
const BFF_API_URL = 'https://bff.bitflowapis.finance/api';
const BFF_API_KEY = '<bitflow-assigned-api-key>';
fetch(url, {
method: 'GET', // or 'POST'
headers: {
'Content-Type': 'application/json',
'X-API-Key': BFF_API_KEY
},
})Adding Liquidity
1
Getting Pool Bins
const BFF_API_URL = 'https://<api-endpoint>/quotes/v1/bins';
const BFF_API_KEY = '<bitflow-assigned-api-key>';
const get_pool_bins = async (pool_id) => {
const response = await fetch(BFF_API_URL + `/${pool_id}`, {
method: 'GET',
headers: { 'Content-Type': 'application/json', 'X-API-Key': BFF_API_KEY }
});
const data = await response.json();
return data;
};2
Getting User Position Bins
const BFF_API_URL = 'https://<api-endpoint>/app/v1/users';
const BFF_API_KEY = '<bitflow-assigned-api-key>';
const get_user_position_bins = async (user_address, pool_id) => {
const response = await fetch(BFF_API_URL + `/${user_address}/positions/${pool_id}/bins`, {
method: 'GET',
headers: { 'Content-Type': 'application/json', 'X-API-Key': BFF_API_KEY }
});
const data = await response.json();
return data;
};3
Preparing Bins for Add Liquidity
const bins_to_add = [
{
bin_id: 500,
x_amount: 10000000000,
y_amount: 1750000000
}
];
const prepare_bins_for_add = (pool_bins, user_positions, active_bin_id, bins_to_add) => {
const user_position_bins_map = new Map(
(Array.isArray(user_positions?.bins)
? user_positions.bins
: []).map(bin => [bin.bin_id, bin]));
return bins_to_add.map(add_bin => {
const pool_bin = pool_bins.bins.find(b => b.bin_id === add_bin.bin_id);
if (!pool_bin) throw new Error(`Bin ${add_bin.bin_id} not found in pool`);
if (add_bin.bin_id < active_bin_id && add_bin.x_amount > 0) {
throw new Error('Only y_token can be added to bins below the active bin');
};
if (add_bin.bin_id > active_bin_id && add_bin.y_amount > 0) {
throw new Error('Only x_token can be added to bins above the active bin');
};
if (add_bin.bin_id === active_bin_id && add_bin.x_amount === 0 && add_bin.y_amount === 0) {
throw new Error('Active bin requires at least one token amount to be greater than 0');
};
return {
is_active_bin: add_bin.bin_id === active_bin_id,
bin_id: add_bin.bin_id,
x_amount: add_bin.x_amount,
y_amount: add_bin.y_amount,
bin_price: Number(pool_bin.price),
reserve_x: Number(pool_bin.reserve_x),
reserve_y: Number(pool_bin.reserve_y),
bin_shares: Number(pool_bin.liquidity ?? 0),
user_liquidity: user_position_bins_map.get(add_bin.bin_id)?.user_liquidity || 0,
has_ever_added_to_bin: user_position_bins_map.has(add_bin.bin_id)
};
});
};4
Executing Add Liquidity
const {
intCV,
uintCV,
listCV,
tupleCV,
principalCV,
contractPrincipalCV,
createAssetInfo,
makeStandardFungiblePostCondition,
makeStandardNonFungiblePostCondition,
FungibleConditionCode,
NonFungibleConditionCode,
PostConditionMode,
AnchorMode,
makeContractCall,
broadcastTransaction
} = require('@stacks/transactions');
const { StacksMainnet } = require('@stacks/network');
const stacks_network = new StacksMainnet();
const STACKS_PRIVATE_KEY = '<your_stacks_private_key>';
const STACKS_TRANSACTION_FEE = 10000; // Transaction fee in uSTX (1 STX = 1000000)
const LIQUIDITY_ROUTER_CONTRACT = 'SP3ESW1QCNQPVXJDGQWT7E45RDCH38QBK9HEJSX4X.dlmm-liquidity-router-v-0-1';
const PRICE_SCALE_BPS = 1e8;
const FEE_SCALE_BPS = 1e4;
const get_signed_bin_id = (unsigned_bin_id) => {
return unsigned_bin_id - 500;
};
const get_token_asset_name = async (token_contract) => {
const BFF_API_URL = 'https://<api-endpoint>/quotes/v1/tokens';
const response = await fetch(BFF_API_URL, {
method: 'GET',
headers: { 'Content-Type': 'application/json' }
});
const data = await response.json();
const token = data.tokens.find(t => t.contract_address === token_contract);
if (!token) throw new Error(`Token not found: ${token_contract}`);
return token.asset_name;
};
const calculate_min_dlp_for_bin = (bin, pool_fees, slippage_tolerance = 1) => {
const {
is_active_bin,
bin_price,
reserve_x,
reserve_y,
bin_shares,
x_amount,
y_amount
} = bin;
const {
x_protocol_fee,
x_provider_fee,
x_variable_fee,
y_protocol_fee,
y_provider_fee,
y_variable_fee
} = pool_fees;
const minimum_bin_shares = 10000;
const minimum_burnt_shares = 1000;
const y_amount_scaled = y_amount * PRICE_SCALE_BPS;
const reserve_y_scaled = reserve_y * PRICE_SCALE_BPS;
const add_liquidity_value = bin_price * x_amount + y_amount_scaled;
const bin_liquidity_value = bin_price * reserve_x + reserve_y_scaled;
const dlp = bin_shares === 0 || bin_liquidity_value === 0
? Math.sqrt(add_liquidity_value)
: (add_liquidity_value * bin_shares) / bin_liquidity_value;
let x_amount_fees_liquidity = 0;
let y_amount_fees_liquidity = 0;
if (is_active_bin && dlp > 0) {
const x_liquidity_fee = x_protocol_fee + x_provider_fee + x_variable_fee;
const y_liquidity_fee = y_protocol_fee + y_provider_fee + y_variable_fee;
const x_amount_withdrawable = (dlp * (reserve_x + x_amount)) / (bin_shares + dlp);
const y_amount_withdrawable = (dlp * (reserve_y + y_amount)) / (bin_shares + dlp);
if (y_amount_withdrawable > y_amount && x_amount > x_amount_withdrawable) {
const max_x_amount_fees_liquidity = ((x_amount - x_amount_withdrawable) * x_liquidity_fee) / FEE_SCALE_BPS;
x_amount_fees_liquidity = x_amount > max_x_amount_fees_liquidity ? max_x_amount_fees_liquidity : x_amount;
};
if (x_amount_withdrawable > x_amount && y_amount > y_amount_withdrawable) {
const max_y_amount_fees_liquidity = ((y_amount - y_amount_withdrawable) * y_liquidity_fee) / FEE_SCALE_BPS;
y_amount_fees_liquidity = y_amount > max_y_amount_fees_liquidity ? max_y_amount_fees_liquidity : y_amount;
};
};
const x_amount_post_fees = x_amount - x_amount_fees_liquidity;
const y_amount_post_fees = y_amount - y_amount_fees_liquidity;
const y_amount_post_fees_scaled = y_amount_post_fees * PRICE_SCALE_BPS;
const reserve_x_post_fees = reserve_x + x_amount_fees_liquidity;
const reserve_y_post_fees_scaled = (reserve_y + y_amount_fees_liquidity) * PRICE_SCALE_BPS;
const add_liquidity_value_post_fees = bin_price * x_amount_post_fees + y_amount_post_fees_scaled;
const bin_liquidity_value_post_fees = bin_price * reserve_x_post_fees + reserve_y_post_fees_scaled;
let dlp_post_fees;
if (bin_shares === 0) {
const intended_dlp = Math.sqrt(add_liquidity_value_post_fees);
dlp_post_fees = intended_dlp >= minimum_bin_shares ? intended_dlp - minimum_burnt_shares : 0;
} else if (bin_liquidity_value_post_fees === 0) {
dlp_post_fees = Math.sqrt(add_liquidity_value_post_fees);
} else {
dlp_post_fees = (add_liquidity_value_post_fees * bin_shares) / bin_liquidity_value_post_fees;
};
const min_dlp = Math.floor(dlp_post_fees * (1 - slippage_tolerance / 1e2));
return {
min_dlp,
x_amount_fees_liquidity: Math.ceil(x_amount_fees_liquidity),
y_amount_fees_liquidity: Math.ceil(y_amount_fees_liquidity)
};
};
const add_liquidity = async (
pool_contract,
pool_data,
x_token_contract,
y_token_contract,
bins_to_add,
sender_address,
slippage_tolerance = 1
) => {
const router_contract_address = LIQUIDITY_ROUTER_CONTRACT.split('.')[0];
const router_contract_name = LIQUIDITY_ROUTER_CONTRACT.split('.')[1];
const pool_contract_address = pool_contract.split('.')[0];
const pool_contract_name = pool_contract.split('.')[1];
const x_token_contract_address = x_token_contract.split('.')[0];
const x_token_contract_name = x_token_contract.split('.')[1];
const token_x_asset_name = await get_token_asset_name(x_token_contract);
const token_x_asset_info = createAssetInfo(x_token_contract_address, x_token_contract_name, token_x_asset_name);
const y_token_contract_address = y_token_contract.split('.')[0];
const y_token_contract_name = y_token_contract.split('.')[1];
const token_y_asset_name = await get_token_asset_name(y_token_contract);
const token_y_asset_info = createAssetInfo(y_token_contract_address, y_token_contract_name, token_y_asset_name);
const pool_fees = {
x_protocol_fee: pool_data.x_protocol_fee || 0,
x_provider_fee: pool_data.x_provider_fee || 0,
x_variable_fee: pool_data.x_variable_fee || 0,
y_protocol_fee: pool_data.y_protocol_fee || 0,
y_provider_fee: pool_data.y_provider_fee || 0,
y_variable_fee: pool_data.y_variable_fee || 0
};
const bin_add_positions = bins_to_add.map(bin => {
const { min_dlp, x_amount_fees_liquidity, y_amount_fees_liquidity } = calculate_min_dlp_for_bin(bin, pool_fees, slippage_tolerance);
const max_x_liquidity_fee = Math.ceil(x_amount_fees_liquidity * (1 + slippage_tolerance / 1e2));
const max_y_liquidity_fee = Math.ceil(y_amount_fees_liquidity * (1 + slippage_tolerance / 1e2));
return tupleCV({
'pool-trait': contractPrincipalCV(pool_contract_address, pool_contract_name),
'x-token-trait': contractPrincipalCV(x_token_contract_address, x_token_contract_name),
'y-token-trait': contractPrincipalCV(y_token_contract_address, y_token_contract_name),
'bin-id': intCV(get_signed_bin_id(bin.bin_id)),
'x-amount': uintCV(bin.x_amount),
'y-amount': uintCV(bin.y_amount),
'min-dlp': uintCV(min_dlp),
'max-x-liquidity-fee': uintCV(max_x_liquidity_fee),
'max-y-liquidity-fee': uintCV(max_y_liquidity_fee)
});
});
const total_x_amount = bins_to_add.reduce((sum, bin) => sum + bin.x_amount, 0);
const total_y_amount = bins_to_add.reduce((sum, bin) => sum + bin.y_amount, 0);
const post_conditions = [];
post_conditions.push(
makeStandardFungiblePostCondition(
sender_address,
FungibleConditionCode.LessEqual,
total_x_amount.toString(),
token_x_asset_info
)
);
post_conditions.push(
makeStandardFungiblePostCondition(
sender_address,
FungibleConditionCode.LessEqual,
total_y_amount.toString(),
token_y_asset_info
)
);
bins_to_add.forEach(bin => {
if (bin.has_ever_added_to_bin) {
post_conditions.push(
makeStandardNonFungiblePostCondition(
sender_address,
NonFungibleConditionCode.Sends,
createAssetInfo(pool_contract_address, pool_contract_name, 'pool-token-id'),
tupleCV({
'token-id': uintCV(bin.bin_id),
'owner': principalCV(sender_address)
})
)
);
};
});
const tx_options = {
contractAddress: router_contract_address,
contractName: router_contract_name,
functionName: 'add-liquidity-multi',
functionArgs: [listCV(bin_add_positions)],
senderKey: STACKS_PRIVATE_KEY,
network: stacks_network,
fee: STACKS_TRANSACTION_FEE,
postConditions: post_conditions,
postConditionMode: PostConditionMode.Deny,
anchorMode: AnchorMode.Any
};
const transaction = await makeContractCall(tx_options);
const response = await broadcastTransaction(transaction, stacks_network);
return response;
};Withdrawing Liquidity
Moving Liquidity
Swapping Tokens
Getting Data
Getting Available Tokens
Getting Available Pools
Getting Available Pairs
Getting Pool Data
Getting Pool Bins
Getting Pool Bin Price History
Getting User Position for a Pool
Getting User Position Bins for a Pool
Last updated