botwallet_pay
Send payments to merchants or bots using USDC on Solana. Specify recipient and amount for direct payments, or use a payment request ID for paylinks. Payments complete immediately within spending limits or require owner approval for larger amounts.
Instructions
Make a payment to a merchant or bot. Specify to + amount for direct payments, or payment_request_id to pay a specific paylink. If the payment is within your guard rails, it completes immediately via FROST threshold signing and returns your new balance. If it requires owner approval, returns needs_approval: true with a transaction_id — check botwallet_events for the approval result, then call botwallet_confirm_payment. Always call botwallet_can_i_afford first if unsure about your balance.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| to | No | Recipient username (required unless using payment_request_id) | |
| amount | No | Amount in USD (required unless using payment_request_id) | |
| payment_request_id | No | Pay a specific paylink by ID (alternative to to+amount) | |
| note | No | Note visible to recipient | |
| reference | No | Your internal reference for tracking | |
| idempotency_key | No | Unique key to prevent duplicate payments on retry. Auto-generated if omitted. |
Implementation Reference
- src/tools/spending.ts:50-136 (handler)The definition and handler implementation for the 'botwallet_pay' tool. It handles payment creation, potential approval flows, and optional FROST threshold signing for transaction completion.
const pay: ToolDefinition = { name: 'botwallet_pay', description: 'Make a payment to a merchant or bot. Specify `to` + `amount` for direct payments, ' + 'or `payment_request_id` to pay a specific paylink. ' + 'If the payment is within your guard rails, it completes immediately via FROST threshold signing ' + 'and returns your new balance. If it requires owner approval, returns `needs_approval: true` ' + 'with a `transaction_id` — check botwallet_events for the approval result, then call ' + 'botwallet_confirm_payment. Always call botwallet_can_i_afford first if unsure about your balance.', inputSchema: z.object({ to: UsernameSchema.optional().describe('Recipient username (required unless using payment_request_id)'), amount: AmountSchema.optional().describe('Amount in USD (required unless using payment_request_id)'), payment_request_id: z.string().optional() .describe('Pay a specific paylink by ID (alternative to to+amount)'), note: z.string().max(500).optional() .describe('Note visible to recipient'), reference: z.string().max(100).optional() .describe('Your internal reference for tracking'), idempotency_key: z.string().optional() .describe('Unique key to prevent duplicate payments on retry. Auto-generated if omitted.'), }), async handler(args, ctx) { try { const { to, amount, payment_request_id, note, reference, idempotency_key } = args as { to?: string; amount?: number; payment_request_id?: string; note?: string; reference?: string; idempotency_key?: string; }; const idemKey = idempotency_key || randomUUID(); const payResult = await ctx.sdk.pay({ to, amount, payment_request_id, note, reference }, idemKey); // If needs approval, return immediately — no signing needed if ('needs_approval' in payResult && payResult.needs_approval) { return formatResult({ needs_approval: true, approval_id: payResult.approval_id, reason: payResult.reason, message: payResult.message, approval_url: payResult.approval_url, next_steps: 'Check botwallet_events for approval result, then call botwallet_confirm_payment.', }); } // Already completed (custodial or server-signed) if ('paid' in payResult && payResult.paid) { return formatResult(payResult); } // Pre-approved with transaction_id: confirm → get Solana tx → FROST sign const result = payResult as Record<string, unknown>; if (result.transaction_id) { if (!ctx.config.hasSeed || !ctx.config.walletName) { return noSeedError('complete payment (FROST signing)'); } const txId = result.transaction_id as string; const confirmResult = await ctx.sdk.confirmPayment({ transaction_id: txId, }); if (!confirmResult.message) { return formatToolError(new Error( 'Transaction may have expired or already been completed. Check status with botwallet_list_payments.' )); } const mnemonic = loadSeed(ctx.config.walletName); const signResult = await frostSignAndSubmit( ctx.sdk, mnemonic, txId, confirmResult.message, ); return formatResult({ paid: true, ...signResult, }); } return formatToolError(new Error( 'Unexpected payment response from server. Check status with botwallet_list_payments or botwallet_events.' )); } catch (e) { return formatToolError(e); } }, };