build_add_liquidity
Create an unsigned transaction to add liquidity to a token pair on the Casper Network DEX, specifying token amounts and slippage parameters.
Instructions
Build an unsigned add-liquidity transaction for a token pair
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| token_a | Yes | First token: symbol, name, or hash | |
| token_b | Yes | Second token: symbol, name, or hash | |
| amount_a | Yes | Human-readable amount of first token | |
| amount_b | Yes | Human-readable amount of second token | |
| slippage_bps | No | Slippage in basis points (default 300) | |
| deadline_minutes | No | Deadline in minutes (default 20) | |
| sender_public_key | Yes | Sender hex public key | |
| token_a_balance | No | Raw token A balance for one-time approval | |
| token_b_balance | No | Raw token B balance for one-time approval |
Implementation Reference
- packages/mcp/src/tools/liquidity.ts:7-57 (registration)The 'build_add_liquidity' tool is registered in this file, which calls the client's buildAddLiquidity method.
server.tool( 'build_add_liquidity', 'Build an unsigned add-liquidity transaction for a token pair', { token_a: z.string().describe('First token: symbol, name, or hash'), token_b: z.string().describe('Second token: symbol, name, or hash'), amount_a: z.string().describe('Human-readable amount of first token'), amount_b: z.string().describe('Human-readable amount of second token'), slippage_bps: z.number().optional().describe('Slippage in basis points (default 300)'), deadline_minutes: z.number().optional().describe('Deadline in minutes (default 20)'), sender_public_key: z.string().describe('Sender hex public key'), token_a_balance: z.string().optional().describe('Raw token A balance for one-time approval'), token_b_balance: z.string().optional().describe('Raw token B balance for one-time approval'), }, async (args) => { const bundle = await client.buildAddLiquidity({ tokenA: args.token_a, tokenB: args.token_b, amountA: args.amount_a, amountB: args.amount_b, slippageBps: args.slippage_bps, deadlineMinutes: args.deadline_minutes, senderPublicKey: args.sender_public_key, tokenABalance: args.token_a_balance, tokenBBalance: args.token_b_balance, }); const parts = [bundle.summary]; if (bundle.approvalsRequired?.length) { parts.push('\n--- APPROVALS REQUIRED ---'); for (let i = 0; i < bundle.approvalsRequired.length; i++) { const approval = bundle.approvalsRequired[i]; const approvalPath = await writeDeployFile(approval.transactionJson); parts.push(`\nStep ${i + 1}: ${approval.summary}`); parts.push(`Approval transaction saved to: ${approvalPath}`); parts.push(`Gas: ${approval.estimatedGasCost}`); } parts.push('\n--- ADD LIQUIDITY TRANSACTION ---'); } const deployPath = await writeDeployFile(bundle.transactionJson); parts.push(`\nTransaction saved to: ${deployPath}`); if (bundle.approvalsRequired?.length) { parts.push('\nWorkflow: Sign and submit each approval with submit_transaction, then sign and submit the add-liquidity transaction with submit_transaction.'); } return { content: [{ type: 'text' as const, text: parts.join('\n') }] }; }, ); - packages/sdk/src/client.ts:311-419 (handler)The actual implementation of 'buildAddLiquidity' which calculates transaction arguments and builds the unsigned transaction.
async buildAddLiquidity(params: AddLiquidityParams): Promise<TransactionBundle> { const tokenA = await this.tokenResolver.resolve(params.tokenA); const tokenB = await this.tokenResolver.resolve(params.tokenB); const slippageBps = params.slippageBps ?? DEFAULT_SLIPPAGE_BPS; const deadlineMinutes = params.deadlineMinutes ?? DEFAULT_DEADLINE_MINUTES; const deadline = Date.now() + deadlineMinutes * 60 * 1000; const isCSPRPair = tokenA.id === CSPR_TOKEN_ID || tokenB.id === CSPR_TOKEN_ID; const rawAmountA = toRawAmount(params.amountA, tokenA.decimals); const rawAmountB = toRawAmount(params.amountB, tokenB.decimals); const accountHash = PublicKey.fromHex(params.senderPublicKey).accountHash().toPrefixedString(); let innerArgs; let entryPoint: string; let attachedValue = '0'; if (isCSPRPair) { const csprToken = tokenA.id === CSPR_TOKEN_ID ? tokenA : tokenB; const otherToken = tokenA.id === CSPR_TOKEN_ID ? tokenB : tokenA; const motesAmount = tokenA.id === CSPR_TOKEN_ID ? rawAmountA : rawAmountB; const tokenAmount = tokenA.id === CSPR_TOKEN_ID ? rawAmountB : rawAmountA; innerArgs = buildAddLiquidityInnerArgs({ isCSPRPair: true, tokenHash: otherToken.packageHash, amountTokenDesired: tokenAmount, amountTokenMin: calculateMinWithSlippage(tokenAmount, slippageBps), amountCSPRMin: calculateMinWithSlippage(motesAmount, slippageBps), accountHash, deadline, }); entryPoint = 'add_liquidity_cspr'; attachedValue = motesAmount; } else { // Sort tokens by package hash for consistency const [sortedA, sortedB, sortedAmountA, sortedAmountB] = tokenA.packageHash < tokenB.packageHash ? [tokenA, tokenB, rawAmountA, rawAmountB] : [tokenB, tokenA, rawAmountB, rawAmountA]; innerArgs = buildAddLiquidityInnerArgs({ isCSPRPair: false, tokenAHash: sortedA.packageHash, tokenBHash: sortedB.packageHash, amountADesired: sortedAmountA, amountBDesired: sortedAmountB, amountAMin: calculateMinWithSlippage(sortedAmountA, slippageBps), amountBMin: calculateMinWithSlippage(sortedAmountB, slippageBps), accountHash, deadline, }); entryPoint = 'add_liquidity'; } // Use higher gas for potentially new pools const gasCost = GAS_COSTS.addLiquidity; // caller can override if new pool const proxyArgs = buildProxyWasmArgs({ routerPackageHash: this.networkConfig.routerPackageHash.replace('hash-', ''), entryPoint, innerArgs, attachedValue, }); const wasmBinary = await getProxyCallerWasm(); const transaction = buildWasmTransaction({ publicKey: params.senderPublicKey, paymentAmount: gasCost.toString(), wasmBinary, runtimeArgs: proxyArgs, networkConfig: this.networkConfig, }); // Build approval transactions for non-CSPR tokens. // When CSPR is one side, the router handles wrapping internally — no approval needed for that side. const tokenBalances = [params.tokenABalance, params.tokenBBalance]; const rawAmounts = [rawAmountA, rawAmountB]; const approvals: TransactionBundle[] = []; for (let i = 0; i < 2; i++) { const token = [tokenA, tokenB][i]; if (token.id === CSPR_TOKEN_ID) continue; // Router handles CSPR wrapping const approvalAmount = tokenBalances[i] ?? rawAmounts[i]; const approval = await this.buildApproval({ tokenContractPackageHash: token.packageHash, spenderPackageHash: this.networkConfig.routerPackageHash, amount: approvalAmount, senderPublicKey: params.senderPublicKey, }); approval.summary = tokenBalances[i] ? `Approve ${token.symbol} for router (full balance — one-time)` : `Approve ${token.symbol} for router (liquidity amount only)`; approvals.push(approval); } const summary = [ `Add liquidity: ${params.amountA} ${tokenA.symbol} + ${params.amountB} ${tokenB.symbol}`, `Slippage tolerance: ${(slippageBps / 100).toFixed(2)}%`, `Deadline: ${deadlineMinutes} minutes`, `Estimated gas: ${Number(gasCost) / 1_000_000_000} CSPR`, ].join('\n'); return { transactionJson: JSON.stringify(transaction.toJSON()), summary, estimatedGasCost: `${Number(gasCost) / 1_000_000_000} CSPR`, approvalsRequired: approvals.length > 0 ? approvals : undefined, warnings: [], }; }