Skip to main content
Glama
botwallet-co

BotWallet MCP Server

by botwallet-co

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

TableJSON Schema
NameRequiredDescriptionDefault
toNoRecipient username (required unless using payment_request_id)
amountNoAmount in USD (required unless using payment_request_id)
payment_request_idNoPay a specific paylink by ID (alternative to to+amount)
noteNoNote visible to recipient
referenceNoYour internal reference for tracking
idempotency_keyNoUnique key to prevent duplicate payments on retry. Auto-generated if omitted.

Implementation Reference

  • 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);
        }
      },
    };

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/botwallet-co/mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server