Skip to main content
This recipe walks the full agent payment lifecycle end-to-end: define a policy envelope that caps spend per transaction and allowlists a vendor address, get a scoped grant from the OAuth authorization server, call payments.initiate, then query audit.stream to confirm the event landed. This is the canonical path every production agent should follow. Audience: agent authors who want to go beyond quickstart toy calls.

Prerequisites

  • The axtior-neobank repo cloned locally with pnpm install run at the root.
  • apps/mcp running on localhost:3001 (default port from apps/mcp/src/server.ts).
  • Node 22+ and pnpm.
  • A Glide OAuth client with payments:initiate and audit:stream scopes.

Steps

1. Clone the example

2. Set environment variables

3. Define the policy envelope (src/0-policy.ts)

AgentPolicyEnvelope is the 14-axis object evaluated by @glideco/policy-engine on every agent tool call. All amount caps are integer cents (not dollar strings).
Key fields:
  • policy_id — stable UUID across version bumps; (vault_id, policy_id) is unique.
  • policy_version — monotonic counter; cache invalidation key for the policy engine.
  • amount_cap_cents_* — absent means no cap on that axis; the engine still evaluates other gates.
  • counterparty_allowlist — non-empty array closes the gate (only listed counterparties allowed); empty array (or omitted) means no counterparty restriction. Match the source semantics in policy-engine/src/evaluate.ts.

4. Issue a scoped grant (src/1-grant.ts)

In production, your agent runtime calls the OAuth AS with client_credentials to get a JWT. The mock below shows the shape the AS issues.
Grant fields:
  • sub + act.sub — human principal and the agent acting on their behalf (RFC 8693 actor claim).
  • aud.vault_id + aud.entity_id — both required; the gateway checks tenant isolation on every call.
  • policy_version — pinned at issuance; the verifier re-checks against the live policy on each call.
  • Max TTL is 3600 seconds (enforced by grantClaimsValidatedSchema).

5. Call payments.initiate and inspect the Receipt (src/2-pay.ts)

The MCP gateway runs on three category-scoped endpoints. Write tools live on /mcp/write.
Output fields (from paymentsInitiateOutput):
  • kindaccepted (enqueued) or pending_step_up (caller must complete biometric step-up first).
  • pending_payment_id — UUID of the row in agent_pending_payments. Correlate with audit.stream events as they arrive.
  • policy_version — pinned at evaluate time; the live policy may have advanced by settle time.
  • risk_verdictallow or allow_with_step_up. Deny verdicts come back as JSON-RPC errors, not as outputs.
  • enqueued_at — when the gateway accepted the call.
Once the connector settles, a Receipt row lands in activity_log and an AgentActivityEvent is emitted to the SSE audit stream. The fields you’ll read off the receipt:
  • receipt_id — primary key; use this to correlate against audit.stream events.
  • on_chain_tx — server-fetched from chain RPC, never from a facilitator response body (F1).
  • risk_verdictallow | allow_with_step_up. Denied calls do not produce receipts.
  • vendor_used — connector slug that settled the tx (matches ConnectorManifest.slug).
If payments.initiate returns JSON-RPC error code -32003 with step_up_url, open that URL in the user’s browser, wait for Privy WebAuthn to complete, then retry the call with the same idempotency_key.

6. Subscribe to the audit stream (src/3-audit.ts)

Read tools live on /mcp/read. audit.stream returns an sse_url and cursor — connect to the URL with EventSource (or any SSE client) to receive AgentActivityEvent rows as they’re written.

Run it

Expected output:

Extend it

  • Set amount_cents above step_up_amount_cents to trigger the step-up path — payments.initiate returns kind: 'pending_step_up' with a step_up_url to open in the browser.
  • Add a second counterparty address NOT in counterparty_allowlist and confirm the policy engine rejects it.
  • Reduce velocity_max_txs_per_hour to 1 and submit two calls in the same minute to hit the velocity gate.
  • Bump policy_version without updating amount_cap_cents_per_tx — the grant’s pinned policy_version will no longer match and calls will fail with PolicyStaleError.

Source

github.com/darshanbathija/axtior-neobank/tree/main/examples/agent-pays-vendor

Reading list