Biatec CLAMM Basic Use Cases
This guide walks through the day-to-day flows supported by the Biatec concentrated liquidity AMM (CLAMM). It focuses on instantiating clients, funding liquidity inside a price band or at a constant price, removing liquidity, executing swaps, and consuming the pool-provider oracle feed. Advanced topics such as rounding behaviour and staking-specific flows are covered in the existing documents under docs/.
Prerequisites
- Access to an Algorand network (Sandbox LocalNet, TestNet, or MainNet) and an
Algodv2RPC endpoint. - Deployed instances of the Biatec configuration provider, identity provider, and pool provider contracts. The examples below assume their app IDs are already known.
- Always confirm that the pool-provider global state (
key = 'B') matches the configuration app ID you reference in any deployment group before signing. If the IDs diverge, abort the transaction to avoid interacting with a forged config. - A funded account represented as an
AlgokitTransactionSignerAccountto sign transactions. - Familiarity with the 1e9 base scale (
SCALE = 1_000_000_000n) used by the contracts. Seedocs/liquidity-rounding.mdfor precision details. - Optional but recommended: use
getConfig(genesisId)to pull the latest production app IDs for supported networks instead of hard-coding them.
Shared Setup
import algosdk from 'algosdk';
import { AlgorandClient } from '@algorandfoundation/algokit-utils';
import { mnemonicToSecretKey } from 'algosdk';
import { clientBiatecClammPool, BiatecPoolProviderClient, getConfig } from 'biatec-concentrated-liquidity-amm';
const ALGOD_URL = 'http://localhost:4001';
const ALGOD_TOKEN = 'a'.repeat(64);
const algod = new algosdk.Algodv2(ALGOD_TOKEN, ALGOD_URL, '');
const algorand = AlgorandClient.fromConfig({ algod });
const { addr, sk } = mnemonicToSecretKey(process.env.MNEMONIC!);
const signerAccount = {
addr,
signer: async (txs: algosdk.Transaction[], indexes: number[]) => {
const signed = txs.map((tx) => tx.signTxn(sk));
return indexes.map((i) => signed[i]);
},
};
const { configAppId: configProviderAppId, identityAppId: identityProviderAppId, poolProviderAppId } = getConfig('testnet-v1.0');
const poolProviderClient = new BiatecPoolProviderClient({
algorand,
appId: poolProviderAppId,
});
// Point the CLAMM client at an existing pool once you know its app ID.
const poolAppId = 45678n;
const poolClient = clientBiatecClammPool({
algorand,
appId: poolAppId,
});
Tip: Use the helper in
src/biatecClamm/getPools.tsto discover pools managed by the pool provider when you only know an asset ID or verification class.
Creating a Pool (Price Range vs Constant Price)
Pool creation is handled by clammCreateSender. Pass priceMin, priceMax, and currentPrice in base scale (1e9). Setting priceMin === priceMax pins the pool to a constant price, which is how staking pools are created.
import { clammCreateSender } from 'biatec-concentrated-liquidity-amm';
const SCALE = 1_000_000_000n;
await poolProviderClient.send.setNativeTokenName({
args: {
appBiatecConfigProvider: configProviderAppId,
nativeTokenName: 'Algo',
},
appReferences: [configProviderAppId],
});
const poolClient = await clammCreateSender({
transactionSigner: signerAccount,
clientBiatecPoolProvider: poolProviderClient,
appBiatecConfigProvider: configProviderAppId,
assetA: 9581n,
assetB: 0n, // 0n indicates the native token
fee: 10_000_000n, // 1% fee expressed in base scale
verificationClass: 0,
priceMin: SCALE / 2n,
priceMax: SCALE * 2n,
currentPrice: SCALE,
});
// clammCreateSender automatically calls bootstrapStep2, so the pool is ready for deposits.
For constant-price staking pools, reuse the snippet above and set priceMin, priceMax, and currentPrice to the same base-scale value (usually SCALE). See docs/staking-pools.md for additional staking-specific guidance.
Supplying Liquidity
clammAddLiquiditySender wraps the grouped transaction set required to deposit both assets and reference the pool-provider metadata.
import { clammAddLiquiditySender } from 'biatec-concentrated-liquidity-amm';
await clammAddLiquiditySender({
clientBiatecClammPool: poolClient,
clientBiatecPoolProvider: poolProviderClient,
account: signerAccount,
algod,
appBiatecConfigProvider: configProviderAppId,
appBiatecIdentityProvider: identityProviderAppId,
assetA: 9581n,
assetB: 0n,
assetLp: 9619n,
assetADeposit: 2_500_000n, // amount in the asset's native decimals
assetBDeposit: 2_500_000n,
});
- The sender automatically opts into the LP token if needed.
- Deposits must respect the pool’s price range. When adding liquidity to a wide range, size both deposits according to the ratios exposed by the off-chain calculators under
contracts/clients/BiatecPoolProviderClient.ts(calculateAsset*queries). - For detailed rounding expectations, read
docs/liquidity-rounding.mdanddocs/liquidity-fee-protection.md.
Withdrawing Liquidity
clammRemoveLiquiditySender burns LP tokens in exchange for the underlying assets plus accrued fees.
import { clammRemoveLiquiditySender } from 'biatec-concentrated-liquidity-amm';
await clammRemoveLiquiditySender({
clientBiatecClammPool: poolClient,
account: signerAccount,
algod,
appBiatecConfigProvider: configProviderAppId,
appBiatecIdentityProvider: identityProviderAppId,
assetA: 9581n,
assetB: 0n,
assetLp: 9619n,
lpToSend: 3_000_000n,
});
Use the clammRemoveLiquidityAdminSender helper if you need an administrative withdrawal that bypasses the identity checks (reserved for governance flows).
Swapping Assets
clammSwapSender executes a swap in either direction. Provide the asset you are sending, the amount, and the minimum amount you are willing to receive (after fees) in the counter asset.
import { clammSwapSender } from 'biatec-concentrated-liquidity-amm';
await clammSwapSender({
clientBiatecClammPool: poolClient,
account: signerAccount,
algod,
appBiatecConfigProvider: configProviderAppId,
appBiatecIdentityProvider: identityProviderAppId,
appBiatecPoolProvider: poolProviderAppId,
assetA: 9581n,
assetB: 0n,
fromAsset: 9581n,
fromAmount: 10_000_000n,
minimumToReceive: 9_800_000n,
});
The swap helper attaches pool-provider and identity box references automatically. To estimate output amounts before submitting a swap, call the read-only calculator methods exposed on BiatecPoolProviderClient (for example, clientBiatecPoolProvider.getPrice or the calculateAsset* family of methods).
Consuming the Pool-Provider Oracle Feed
The Biatec pool provider maintains on-chain oracle data for each asset pair. Call the generated getPrice method with appPoolId = 0n to retrieve the aggregated metrics. The return value is already decoded into camelCase fields.
const priceInfo = await poolProviderClient.appClient.getPrice({
args: {
assetA: 0n,
assetB: 9581n,
appPoolId: 0n, // zero => aggregated across all pools for the pair
},
});
console.log('latest price (base scale):', priceInfo.latestPrice);
console.log('period1 VWAP (base scale):', priceInfo.period1NowVwap);
console.log('most recent trade fee (asset A):', priceInfo.period1NowFeeA);
When you need pool-specific oracle data, pass the CLAMM app ID as appPoolId instead of zero. The method returns the same AppPoolInfo structure in both cases.
Additional Operations
- Distribute rewards:
clammDistributeExcessAssetsSendercredits staking or fee rewards to LP holders. Amounts must be expressed in the 1e9 base scale. - Withdraw protocol fees:
clammWithdrawExcessAssetsSenderlets the fee executor retrieve accumulated protocol revenue. - Toggle validator status:
clammSendOnlineKeyRegistrationSenderandclammSendOfflineKeyRegistrationSenderwrap the AVM key registration flows when the pool account stakes on consensus chains. - Pool discovery and quoting:
getPools(fromsrc/biatecClamm/getPools.ts) enumerates registry entries; the generated pool-provider client exposes pure functions for sizing deposits and withdrawals (e.g.,calculateAssetADepositOnAssetBDeposit).
Recommended Reading
docs/liquidity-rounding.mdfor rounding and precision rules.docs/liquidity-fee-protection.mdfor fee accounting guarantees.docs/staking-pools.mdfor constant-price (same-asset) staking scenarios.
These building blocks cover the standard user journey: create or discover a pool, provide liquidity with the correct price bounds, earn fees, execute swaps, and rely on the pool provider for oracle-grade price data.