t2000_withdraw
Withdraw assets from NAVI lending directly to your wallet. Supports any deposited asset; specify amount or use 'all' for full withdrawal. Preview with dry run mode.
Instructions
Withdraw from NAVI lending back to wallet. Supports any deposited asset. Amount is in token units. Use "all" to withdraw everything. Set dryRun: true to preview.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| amount | Yes | Amount to withdraw, or "all" | |
| asset | No | Asset to withdraw (default: auto-selects largest position) | |
| dryRun | No | Preview without signing (default: false) |
Implementation Reference
- packages/mcp/src/tools/write.ts:118-156 (registration)Registers the 't2000_withdraw' tool on the MCP server using server.tool(), with Zod schema for amount (number or 'all'), asset (optional string), and dryRun (optional boolean). The handler calls agent.withdraw() via a mutex.
server.tool( 't2000_withdraw', 'Withdraw from NAVI lending back to wallet. Supports any deposited asset. Amount is in token units. Use "all" to withdraw everything. Set dryRun: true to preview.', { amount: z.union([z.number(), z.literal('all')]).describe('Amount to withdraw, or "all"'), asset: z.string().optional().describe('Asset to withdraw (default: auto-selects largest position)'), dryRun: z.boolean().optional().describe('Preview without signing (default: false)'), }, async ({ amount, asset, dryRun }) => { try { if (dryRun) { agent.enforcer.assertNotLocked(); const positions = await agent.positions(); const health = await agent.healthFactor(); const savings = positions.positions .filter(p => p.type === 'save' && (!asset || p.asset === asset)) .reduce((sum, p) => sum + p.amount, 0); return { content: [{ type: 'text', text: JSON.stringify({ preview: true, amount: amount === 'all' ? savings : amount, asset: asset ?? 'auto', currentSavings: savings, currentHealthFactor: health.healthFactor, }), }], }; } const result = await mutex.run(() => agent.withdraw({ amount, asset })); return { content: [{ type: 'text', text: JSON.stringify(result) }] }; } catch (err) { return errorResult(err); } }, ); - packages/mcp/src/tools/write.ts:126-155 (handler)The async handler for t2000_withdraw. If dryRun is true, fetches positions and health factor to preview. Otherwise, calls agent.withdraw({ amount, asset }) via mutex.run() and returns the result as JSON text.
async ({ amount, asset, dryRun }) => { try { if (dryRun) { agent.enforcer.assertNotLocked(); const positions = await agent.positions(); const health = await agent.healthFactor(); const savings = positions.positions .filter(p => p.type === 'save' && (!asset || p.asset === asset)) .reduce((sum, p) => sum + p.amount, 0); return { content: [{ type: 'text', text: JSON.stringify({ preview: true, amount: amount === 'all' ? savings : amount, asset: asset ?? 'auto', currentSavings: savings, currentHealthFactor: health.healthFactor, }), }], }; } const result = await mutex.run(() => agent.withdraw({ amount, asset })); return { content: [{ type: 'text', text: JSON.stringify(result) }] }; } catch (err) { return errorResult(err); } }, - Zod input schema for t2000_withdraw: amount (z.number() | z.literal('all')), asset (z.string().optional()), dryRun (z.boolean().optional()).
{ amount: z.union([z.number(), z.literal('all')]).describe('Amount to withdraw, or "all"'), asset: z.string().optional().describe('Asset to withdraw (default: auto-selects largest position)'), dryRun: z.boolean().optional().describe('Preview without signing (default: false)'), }, - packages/mcp/src/index.ts:37-37 (registration)Calls registerWriteTools(server, agent) which registers t2000_withdraw among other write tools.
registerWriteTools(server, agent); - Engine-level withdraw tool definition used by the CLI ('serve' command) — separate from the MCP tool but shows the underlying agent.withdraw() call pattern.
export const withdrawTool = buildTool({ name: 'withdraw', description: 'Withdraw USDC or USDsui from NAVI lending back to wallet. Defaults to USDC. ' + 'Audric supports ONLY USDC and USDsui — these are the same two stables save_deposit accepts. ' + 'NAVI may also surface legacy positions (USDe, SUI, etc.) in savings_info / balance_check; those are READ-ONLY through Audric. ' + 'For non-canonical positions, direct the user to NAVI\'s app (https://app.naviprotocol.io) — Audric will not withdraw them. ' + 'Payment Intent: composable — when paired with another composable write in the same request (e.g. "withdraw and send to Mom"), emit all calls in the same assistant turn so the engine compiles them into one atomic Payment Intent the user signs once.', inputSchema: z.object({ amount: z.number().positive(), asset: z.string().optional().describe('Asset to withdraw — must be USDC (default) or USDsui. Other assets surfaced in savings_info are read-only via Audric.'), }), jsonSchema: { type: 'object', properties: { amount: { description: 'Exact amount to withdraw in token units', }, asset: { type: 'string', description: 'Asset to withdraw — USDC (default) or USDsui only. Other assets surfaced in savings_info are read-only via Audric; direct the user to https://app.naviprotocol.io for those.', }, }, required: ['amount'], }, isReadOnly: false, permissionLevel: 'confirm', flags: { mutating: true, affectsHealth: true }, async call(input, context) { const agent = requireAgent(context); const result = await agent.withdraw({ amount: input.amount, asset: input.asset, }); const withdrawnAsset = (result as { asset?: string }).asset ?? input.asset ?? 'USDC'; return { data: { success: result.success, tx: result.tx, amount: result.amount, asset: withdrawnAsset, gasCost: result.gasCost, }, displayText: `Withdrew ${result.amount.toFixed(result.amount < 1 ? 6 : 2)} ${withdrawnAsset} (tx: ${result.tx.slice(0, 8)}…)`, }; }, });