Settle (or reject) a pending payment
grip_settle_paymentSettles 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
| Name | Required | Description | Default |
|---|---|---|---|
| approval_token | Yes | ||
| decision | Yes |
Implementation Reference
- server/src/index.ts:227-311 (registration)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, }; } }, ); - server/src/index.ts:244-311 (handler)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, }; } }, ); - server/src/index.ts:233-236 (schema)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"]), }, - server/src/index.ts:74-84 (helper)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; }; - server/src/index.ts:86-87 (helper)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 };