You can create both single-sided and double-sided pools with the Flashnet SDK. The SDK handles all the complexity of intent generation, signing, and multi-step processes automatically.
Pool Types
Single-Sided Pools (Bonding Curve)
- Goal: Gradually discover price as tokens sell, then “graduate” into a normal constant-product pool.
- Mechanism: Single-sided pools are modeled using virtual reserves that move on a constant‑product curve. The creator deposits an initial supply of Asset A; price evolves as virtual reserves update with sales.
- Graduation: Once a specified percentage of Asset A is sold, the accumulated Asset B and remaining Asset A are used to seed a new constant product pool.
- Target-Based Creation: The SDK provides a helper to compute virtual reserves from your initial supply, graduation threshold, and target Asset B raised at graduation.
Double-Sided Pools (Constant Product)
- Require initial deposits of both assets
- Use actual reserves for pricing
- Follow the constant product (
x*y=k
) formula
- Better for liquid markets
In the Flashnet Stack, Bitcoin is always denominated as satoshis and bears the public key 020202020202020202020202020202020202020202020202020202020202020202
.Currently, Asset B must be Bitcoin, otherwise pool creation will fail.
Prerequisites
Before creating pools, make sure you have set up the Flashnet SDK client. This guide assumes you have already initialized the client
instance, and that you have a wallet with either some BTC and LRC20 tokens, or two different LRC20 tokens.
In our below examples we will use BTC and USDB, where BTC is denominated in sats (8 decimals) and USDB has 6 decimals.
Creating a Constant Product Pool
// Create a constant product pool with initial liquidity
async function createConstantProductPool() {
try {
const pool = await client.createConstantProductPool({
assetAAddress: "bitcoin-pubkey",
assetBAddress: "usdb-pubkey",
lpFeeRateBps: 30, // 0.3% LP fee
totalHostFeeRateBps: 20, // 0.2% host fee
hostNamespace: "my-namespace", // Optional: for host attribution
initialLiquidity: { // Optional: add initial liquidity
assetAAmount: 100000000n, // 1 BTC
assetBAmount: 100000000000n, // 100,000 USDB
assetAMinAmountIn: 100000000n, // Min Asset A to deposit
assetBMinAmountIn: 100000000000n, // Min Asset B to deposit
}
});
console.log('Pool created:', pool);
console.log('Pool ID:', pool.poolId);
} catch (error) {
console.error('Failed to create pool:', error);
}
}
// Create a pool without initial liquidity
async function createEmptyPool() {
const pool = await client.createConstantProductPool({
assetAAddress: "bitcoin-pubkey",
assetBAddress: "usdb-pubkey",
lpFeeRateBps: 30,
totalHostFeeRateBps: 20,
});
console.log('Empty pool created. Add liquidity separately.');
}
Creating a Single-Sided Pool
The SDK automatically handles the deposit and confirmation process:
async function createSingleSidedPool() {
try {
// The SDK will automatically:
// 1. Create the pool with a virtual-reserve price mechanism.
// 2. Transfer the initial reserve of Asset A.
// 3. Confirm the deposit.
// 4. Graduate to a constant product pool when the threshold is met.
// Compute virtual reserves from goals
const { virtualReserveA, virtualReserveB, threshold } = FlashnetClient.calculateVirtualReserves({
initialTokenSupply: "1000000000000", // 1,000,000 USDB initial supply
graduationThresholdPct: 80, // Graduate when 80% is sold (<= 95)
targetRaise: "100000000", // Target to raise 1 BTC (in sats)
});
const pool = await client.createSingleSidedPool({
assetAAddress: "usdb-pubkey", // The token being sold
assetBAddress: "bitcoin-pubkey", // The token being raised
assetAInitialReserve: "1000000000000",
virtualReserveA: virtualReserveA.toString(),
virtualReserveB: virtualReserveB.toString(),
threshold: threshold.toString(), // Graduation threshold percentage
lpFeeRateBps: 30, // 0.3% LP fee
totalHostFeeRateBps: 50, // 0.5% host fee
hostNamespace: "my-exchange" // Optional: for host attribution
});
console.log('Single-sided pool created:', pool);
console.log('Pool ID:', pool.poolId);
} catch (error) {
console.error('Failed to create pool:', error);
}
}
Fee Configuration
Fee Constraints
- LP Fee: Trading fee that goes to liquidity providers (typically 0.1-1%)
- Host Fee: Additional fee for the host/integrator (0-1%)
- Total Fee: Sum of LP and host fees charged to traders
Important: If creating a pool with a hostNamespace
, the totalHostFeeRateBps
must be greater than or equal to the host’s registered minFeeBps
.
- If you do NOT provide a
hostNamespace
, the minimum totalHostFeeRateBps
is 10 BPS.
For single-sided pools, the graduation threshold
(percentage sold) is capped at 95% by the underlying AMM math.
// Check host requirements before creating pool
async function createPoolWithHost(hostNamespace: string) {
// Get host info to check minimum fee
const hostInfo = await client.getHost(hostNamespace);
console.log(`Host ${hostNamespace} requires minimum ${hostInfo.minFeeBps} BPS`);
// Create pool meeting host requirements
const pool = await client.createConstantProductPool({
assetAAddress: "bitcoin-pubkey",
assetBAddress: "usdb-pubkey",
lpFeeRateBps: 30,
totalHostFeeRateBps: Math.max(50, hostInfo.minFeeBps), // Meet minimum
hostNamespace: hostNamespace,
});
console.log('Pool created with host:', pool);
}
Complete Example: Pool Creation and Management
async function completePoolFlow() {
// 1. Create a pool
const pool = await client.createConstantProductPool({
assetAAddress: "bitcoin-pubkey",
assetBAddress: "usdb-pubkey",
lpFeeRateBps: 30,
totalHostFeeRateBps: 20,
initialLiquidity: {
assetAAmount: 100000000n, // 1 BTC
assetBAmount: 100000000000n, // 100,000 USDB
assetAMinAmountIn: 100000000n, // Min Asset A to deposit
assetBMinAmountIn: 100000000000n, // Min Asset B to deposit
}
});
const poolId = pool.poolId;
console.log('Pool created:', poolId);
// 2. Get pool details
const poolInfo = await client.getPool(poolId);
console.log('Pool info:', poolInfo);
// 3. Check your LP position
const position = await client.getLpPosition(poolId);
console.log('Your LP tokens:', position.lpTokensOwned);
console.log('Your share:', position.sharePercentage, '%');
// 4. Add more liquidity
await client.addLiquidity({
poolId: poolId,
assetAAmount: "50000000", // 0.5 BTC
assetBAmount: "50000000000", // 50,000 USDB
assetAMinAmountIn: "50000000", // set your acceptable minimums
assetBMinAmountIn: "50000000000",
});
console.log('Additional liquidity added');
// 5. List all pools (with filters)
const pools = await client.listPools({
assetAAddress: "bitcoin-pubkey",
assetBAddress: "usdb-pubkey",
curveTypes: ["CONSTANT_PRODUCT"],
});
console.log('Active BTC pools:', pools.pools.length);
}
Error Handling
async function createPoolWithErrorHandling() {
try {
// Compute virtual reserves from goals
const { virtualReserveA, virtualReserveB, threshold } = FlashnetClient.calculateVirtualReserves({
initialTokenSupply: "1000000000000", // 1,000,000 USDB initial supply
graduationThresholdPct: 80, // Graduate when 80% is sold
targetRaise: "100000000", // Target to raise 1 BTC (in sats)
});
const pool = await client.createSingleSidedPool({
assetAAddress: "usdb-pubkey",
assetBAddress: "bitcoin-pubkey",
assetAInitialReserve: "1000000000000",
virtualReserveA: virtualReserveA.toString(),
virtualReserveB: virtualReserveB.toString(),
threshold: threshold.toString(),
lpFeeRateBps: 30,
totalHostFeeRateBps: 50,
});
console.log('Success:', pool);
} catch (error) {
if (error.message.includes('Insufficient balance')) {
console.error('Not enough funds to create pool');
} else if (error.message.includes('initial deposit failed')) {
console.error('Pool created but deposit failed:', error.message);
// The pool ID is included in the error message
} else {
console.error('Pool creation failed:', error);
}
}
}
Next Steps
After creating a pool, you can:
- Execute swaps against your pool
- Add more liquidity to earn LP fees
- Remove liquidity when needed