Skip to main content
Glama

Settle (or reject) a pending payment

grip_settle_payment
DestructiveIdempotent

Settles a staged payment by either executing an on-chain USDC transfer on Base mainnet or marking it as rejected, after human confirmation.

Instructions

Executes the on-chain transfer for a previously-staged payment, or marks it as rejected. ONLY call this after the human has explicitly confirmed (or declined) the payment in plain language. If the human has not confirmed, do not call this tool. On approve, this performs a real USDC transfer on Base mainnet via the Pimlico paymaster — it is irreversible.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
approval_tokenYes
decisionYes

Implementation Reference

  • Registration of the 'grip_settle_payment' tool with MCP server, including its input schema (approval_token and decision) and annotations (destructiveHint).
    server.registerTool(
      "grip_settle_payment",
      {
        title: "Settle (or reject) a pending payment",
        description:
          "Executes the on-chain transfer for a previously-staged payment, or marks it as rejected. ONLY call this after the human has explicitly confirmed (or declined) the payment in plain language. If the human has not confirmed, do not call this tool. On approve, this performs a real USDC transfer on Base mainnet via the Pimlico paymaster — it is irreversible.",
        inputSchema: {
          approval_token: z.string().min(1),
          decision: z.enum(["approve", "reject"]),
        },
        annotations: {
          readOnlyHint: false,
          destructiveHint: true,
          idempotentHint: true,
          openWorldHint: true,
        },
      },
      async ({ approval_token, decision }) => {
        const p = pendingPayments.get(approval_token);
        if (!p) {
          return {
            content: [{ type: "text", text: `Unknown approval token: ${approval_token}` }],
            isError: true,
          };
        }
        if (p.status !== "pending") {
          return {
            content: [{ type: "text", text: `Payment ${approval_token} already ${p.status}.` }],
            isError: true,
          };
        }
    
        if (decision === "reject") {
          p.status = "rejected";
          pendingPayments.set(p.token, p);
          return {
            content: [{ type: "text", text: `🔴 Payment ${approval_token} rejected by human. No on-chain action taken.` }],
            structuredContent: { payment: p },
          };
        }
    
        // Approve → settle on-chain
        try {
          const wad = await client.openWad({
            agentId: "grip-mcp",
            dailyCap: DAILY_CAP,
            perTxCap: PER_TX_CAP,
          });
          const result = await wad.pay({
            to: p.recipient as `0x${string}`,
            amount: p.amountUsdc,
          });
          p.status = "settled";
          p.txHash = result.hash;
          p.basescanUrl = result.basescanUrl;
          pendingPayments.set(p.token, p);
          recordSpend(p.amountUsdc);
    
          const text = [
            `🟢 Payment SETTLED on Base mainnet.`,
            ``,
            `   Amount:    ${p.amountUsdc.toFixed(2)} USDC`,
            `   To:        ${p.recipient}`,
            p.memo ? `   Memo:      "${p.memo}"` : null,
            `   Tx:        ${result.hash}`,
            `   Basescan:  ${result.basescanUrl}`,
          ]
            .filter(Boolean)
            .join("\n");
          return {
            content: [{ type: "text", text }],
            structuredContent: { payment: p, result },
          };
        } catch (e) {
          const msg = (e as Error).message || "unknown error";
          p.status = "failed";
          p.errorMessage = msg;
          pendingPayments.set(p.token, p);
          return {
            content: [{ type: "text", text: `⚠️ Payment ${approval_token} failed during settlement: ${msg}` }],
            isError: true,
          };
        }
      },
    );
  • Handler function for grip_settle_payment. Looks up the pending payment by approval_token, checks status is 'pending', handles 'reject' by marking rejected, or 'approve' by executing on-chain payment via client.openWad()/wad.pay(), then records the result.
      async ({ approval_token, decision }) => {
        const p = pendingPayments.get(approval_token);
        if (!p) {
          return {
            content: [{ type: "text", text: `Unknown approval token: ${approval_token}` }],
            isError: true,
          };
        }
        if (p.status !== "pending") {
          return {
            content: [{ type: "text", text: `Payment ${approval_token} already ${p.status}.` }],
            isError: true,
          };
        }
    
        if (decision === "reject") {
          p.status = "rejected";
          pendingPayments.set(p.token, p);
          return {
            content: [{ type: "text", text: `🔴 Payment ${approval_token} rejected by human. No on-chain action taken.` }],
            structuredContent: { payment: p },
          };
        }
    
        // Approve → settle on-chain
        try {
          const wad = await client.openWad({
            agentId: "grip-mcp",
            dailyCap: DAILY_CAP,
            perTxCap: PER_TX_CAP,
          });
          const result = await wad.pay({
            to: p.recipient as `0x${string}`,
            amount: p.amountUsdc,
          });
          p.status = "settled";
          p.txHash = result.hash;
          p.basescanUrl = result.basescanUrl;
          pendingPayments.set(p.token, p);
          recordSpend(p.amountUsdc);
    
          const text = [
            `🟢 Payment SETTLED on Base mainnet.`,
            ``,
            `   Amount:    ${p.amountUsdc.toFixed(2)} USDC`,
            `   To:        ${p.recipient}`,
            p.memo ? `   Memo:      "${p.memo}"` : null,
            `   Tx:        ${result.hash}`,
            `   Basescan:  ${result.basescanUrl}`,
          ]
            .filter(Boolean)
            .join("\n");
          return {
            content: [{ type: "text", text }],
            structuredContent: { payment: p, result },
          };
        } catch (e) {
          const msg = (e as Error).message || "unknown error";
          p.status = "failed";
          p.errorMessage = msg;
          pendingPayments.set(p.token, p);
          return {
            content: [{ type: "text", text: `⚠️ Payment ${approval_token} failed during settlement: ${msg}` }],
            isError: true,
          };
        }
      },
    );
  • Input schema for grip_settle_payment: approval_token (string min 1) and decision (enum 'approve' | 'reject').
    inputSchema: {
      approval_token: z.string().min(1),
      decision: z.enum(["approve", "reject"]),
    },
  • PendingPayment type definition used by the handler to store payment state including token, recipient, amount, memo, status, and settlement details.
    type PendingPayment = {
      token: string;
      recipient: string;
      amountUsdc: number;
      memo: string;
      createdAt: string;
      status: "pending" | "settled" | "rejected" | "failed";
      txHash?: string;
      basescanUrl?: string;
      errorMessage?: string;
    };
  • pendingPayments Map used to store and look up staged payments by token, accessed by the grip_settle_payment handler.
    const pendingPayments = new Map<string, PendingPayment>();
    const dailySpent: { date: string; total: number } = { date: "", total: 0 };
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Beyond annotations (destructiveHint=true, idempotentHint=true), the description adds critical context: 'performs a real USDC transfer on Base mainnet via Pimlico paymaster — it is irreversible'. This fully discloses the irreversible and destructive nature.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Three sentences with no wasted words. The first sentence covers the primary action, and subsequent sentences add critical usage guidance and behavioral context. Highly efficient.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the tool's destructive and irreversible nature, the description provides essential context (on-chain transfer, paymaster). However, the lack of parameter detail for 'approval_token' means the agent may lack complete information to use the tool correctly. Minor gap prevents a 5.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters2/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

With 0% schema description coverage, the description does not adequately explain the 'approval_token' parameter (e.g., format, source). It only mentions 'decision' with enum values, leaving a significant gap for the agent to understand how to obtain or use the approval_token.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states it executes an on-chain transfer or marks a payment as rejected, with specific verb 'executes' and resource 'previously-staged payment'. It distinguishes from siblings like grip_create_payment and grip_list_payments by focusing on settlement.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Explicitly states to call only after human confirmation, with a clear prohibition ('do not call this tool') if not confirmed. This provides excellent guidance on when to use vs. not use.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Install Server

Other Tools

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/grip-foundation/grip-mcp'

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