The Problem: Agents Need Wallets, But You Need Guarantees

We're at an inflection point in AI systems design. Autonomous agents are no longer theoretical—they're operational, making decisions that affect real resources. But we've built them with a dangerous assumption: agents can request payments, but verifying whether they should happens somewhere else, asynchronously, after the fact.

This is backwards.

Modern agentic systems require a different model: agents should declare their intent to pay, receive a policy-checked authorization plan, and execute within explicit bounds—all verified before settlement occurs.

ChainPay's universal MCP solves this by making agent payment capability a first-class protocol primitive.

This isn't a payment processor. It's an authorization layer that lets any MCP-capable agent (Claude with MCP, open-source agents, specialized ML systems) interact with a unified payment interface while enforcing cryptographic guarantees about what they can and cannot do.


What "Universal" Means in Agent Systems

In the agentic AI landscape, we're building systems where:

  • Multiple agent runtimes coexist: Claude via MCP, Anthropic's agent framework, open-source alternatives (CrewAI, AutoGen), fine-tuned domain-specific models
  • Each agent needs payment capability: Booking services, purchasing data, paying for compute, settling transactions with other agents
  • No single agent framework owns the namespace: Agents should be interchangeable; the payment system shouldn't care if you're using Claude or a local model

Universal MCP means:

  1. One discovery endpoint for all agents to find payment tools
  2. Standardized tool signatures so any agent implementation can call the same functions
  3. Policy-first authorization that works across different runtimes
  4. Portable credentials that don't require learning framework-specific auth

An agent shouldn't need to know about Solana, Token-2022, or receipt PDAs. It should declare its intent, get back a policy-checked plan, and either approve or decline. The system handles the complexity.


The MCP Tool Architecture: Specific Capabilities for Every Agent

ChainPay exposes a curated set of tools. These aren't REST endpoints—they're MCP tool definitions that agents can discover, understand, and invoke.

Tool Categories

Discovery & Context
├─ discover_supported_assets()
│  Returns: mint, token program (SPL or Token-2022), decimals, supported paths
│
├─ get_mandate_info(mandate_address)
│  Returns: approved_agent, limits, cooldown, expiry, pause/revoke state
│
└─ check_agent_eligibility(agent_pubkey)
   Returns: list of available mandates, constraints

Authorization & Planning
├─ create_mandate(owner, agent, token_mint, limit, expiry)
│  Returns: mandate_address, signature_plan
│
├─ get_quote_and_preflight(mandate, amount, recipient)
│  Returns: feasibility check, estimated fees, routing decision
│
└─ authorize_payment(owner_pubkey, mandate, amount, challenge)
   Returns: signed authorization proof

Execution & Settlement
├─ prepare_payment_transaction(mandate, agent, amount, recipient)
│  Returns: unsigned wire transaction
│
├─ submit_signed_transaction(signed_tx, signature)
│  Returns: transaction ID
│
└─ get_payment_receipt(tx_signature)
   Returns: receipt PDA data, settlement status, finality proof

Each tool has:

  • Clear input schema: What data the agent must provide
  • Bounded output: Only the information the agent needs to know
  • Error codes: Explicit failure modes (limit exceeded, mandate expired, etc.)
  • Idempotency markers: For safely retrying failed operations

Example Tool: prepare_payment_transaction

An agent discovers this tool and sees:

{
  "name": "prepare_payment_transaction",
  "description": "Prepare an unsigned payment within mandate constraints",
  "inputSchema": {
    "type": "object",
    "properties": {
      "mandate_address": {
        "type": "string",
        "description": "Base58-encoded mandate account address"
      },
      "agent_pubkey": {
        "type": "string",
        "description": "The agent's public key (must match mandate.approved_agent)"
      },
      "amount": {
        "type": "string",
        "description": "Amount in token smallest units (lamports for SOL, etc.)"
      },
      "recipient_token_account": {
        "type": "string",
        "description": "Destination token account (must be for same mint as mandate)"
      }
    },
    "required": ["mandate_address", "agent_pubkey", "amount", "recipient_token_account"]
  },
  "returns": {
    "type": "object",
    "properties": {
      "unsigned_transaction": {
        "type": "string",
        "description": "Base64-encoded unsigned transaction"
      },
      "fee_estimate": {
        "type": "string",
        "description": "Transaction fee in lamports"
      },
      "routing_path": {
        "type": "string",
        "description": "Token program used (SPL or Token-2022)"
      },
      "receipt_pda": {
        "type": "string",
        "description": "Where receipt will be stored on-chain"
      }
    }
  }
}

The agent calls this tool. It gets back an unsigned transaction—a wire that the human or delegated signer approves, not the agent.

This is the core design principle: agents propose, humans dispose.


Backend Verification: Joining Proofs

Backend (MCP Server)
    ↓
    Submits signed transaction to Solana
    ↓ Receives: tx_signature

    Waits for finality
    ├─ Polls: connection.confirmTransaction(sig, 'finalized')
    └─ Timeout if not finalized within N slots

    Fetches receipt PDA
    ├─ Calls: connection.getAccountInfo(receipt_pda)
    ├─ Decodes receipt data
    └─ Verifies fields match request

    Database Write
    ├─ Insert payment record:
    │  {
    │    tx_signature,
    │    mandate_address,
    │    agent_pubkey,
    │    amount,
    │    recipient,
    │    receipt_pda,
    │    slot,
    │    status: 'finalized'
    │  }
    └─ Index by mandate, agent, timestamp

    Agent Notification
    └─ Calls: agent.notify_payment_complete(receipt_pda, status)

The backend never signs. It never holds keys. It submits transactions that already have cryptographic proof of authorization.


Policy Enforcement: Mandates as Capability Objects

A mandate is the on-chain representation of a payment policy. It's not a row in a database—it's an account that the Solana runtime enforces.

Mandate Lifecycle

1. Creation (Owner Action)

Owner calls create_mandate with:

  • Agent pubkey (who can spend)
  • Token mint (what they can spend)
  • Per-transaction limit (how much per payment)
  • Total limit (cumulative spending cap)
  • Expiry time (when authority ends)
  • Cooldown period (minimum time between payments)

The MCP returns a signature plan—the owner signs, authorization is immutable.

2. Monitoring (Agent & Owner)

Agent can call get_mandate_info anytime:

  • Current spent amount
  • Remaining budget
  • Whether mandate is paused or revoked
  • Time until next allowed payment

This is observability without trust. The agent doesn't have to ask the backend; it reads from the source of truth.

3. Modification (Owner Action)

Owner can:

  • Pause mandate (agent can't spend)
  • Revoke mandate (agent can't spend, can't be reactivated)
  • Update limits (creates new mandate)

These are on-chain state changes, cryptographically bound to owner's key.

Why Mandates Work Better Than Backend Validation

Traditional Backend Validation:
┌─────────────────────────────┐
│ Agent requests payment      │
└──────────────┬──────────────┘
               ↓
┌─────────────────────────────┐
│ Backend checks:              │
│ - Agent exists?              │
│ - Limit not exceeded?        │
│ - Rate limit OK?             │
│ (All in memory/cache)        │
└──────────────┬──────────────┘
               ↓
        (Races, stale cache,
         concurrent payments...)


Mandate-Based (ChainPay):
┌─────────────────────────────┐
│ Agent requests payment      │
└──────────────┬──────────────┘
               ↓
┌─────────────────────────────┐
│ Solana program checks:       │
│ - Agent in mandate?          │
│ - Limit not exceeded?        │
│ - Cooldown respected?        │
│ - Expiry not reached?        │
│ - Replay protection?         │
│ (All atomic on-chain)        │
└──────────────┬──────────────┘
               ↓
        (Guaranteed, auditable,
         cryptographically sound)

Mandates move policy enforcement from the backend (where it's soft, configurable, but fragile) to the protocol layer (where it's hard, immutable, and verifiable).


Asset Routing: USDC vs PYUSD and Why It Matters

Both USDC and PYUSD are stablecoins on Solana. Both can move tokens. But they're governed by different token programs, and that difference is critical for agent safety.

The Asset Registry

Registry Entry:
{
  mint: "EPjFWaJHqSgkG44qRaRiChkSLrD8bPYRHSvV4FL7SReg",
  symbol: "USDC",
  token_program: "SPL",
  decimals: 6,
  routing_paths: ["SPL_Direct"],
  status: "active"
}

Registry Entry:
{
  mint: "2b1kV6DkPAnxd5ixfnxCpjxmKwWybVrKusYv7q5pxvxn",
  symbol: "PYUSD",
  token_program: "Token-2022",
  decimals: 6,
  routing_paths: ["Token-2022_Direct"],
  extensions: {
    transfer_fee: false,
    hooks: false,
    required_memo: false,
    cpi_guard: false,
    confidential: false
  },
  status: "active"
}

Why this matters for agents:

An agent asks: "Can I pay Bob 100 USDC?"

Without a registry, it might construct a payment using Token-2022 logic when USDC is SPL, or vice versa. The transaction fails mysteriously.

With the registry, the agent discovers:

  1. The correct token program
  2. The exact mint address
  3. Whether any risky extensions are active
  4. How to derive the correct token accounts

Capability-checked routing means:

  • Agent calls get_quote_and_preflight(mint, amount, recipient)
  • ChainPay scans the mint for extensions (transfer fees, hooks, required memos)
  • If any unsafe extension is detected → fail closed, return error
  • Agent adjusts strategy or routes differently

This prevents silent failures where an agent thinks it's paying 100 USDC but actually pays 99 due to transfer fees, or pays to the wrong account due to memo requirements.


The x402 Bridge: HTTP Meets Solana

x402 is an HTTP standard for payment-required responses. ChainPay integrates with x402 but keeps boundaries clean.

x402 Flow (Human-Signed)

Client → Merchant: GET /resource

Merchant → Client: 402 Payment Required
         Header: x402-payment-required: true
         Body: {
           amount: "100000000",  // lamports
           recipient: "merchant_wallet_address",
           token_mint: "EPj...",
           expiry: 1704067200
         }

Client (Human) → ChainPay MCP:
         Calls: get_quote_and_preflight(
           mint=recipient_mint,
           amount=100000000,
           recipient=merchant_wallet
         )
         ↓ Gets: routing decision, fee estimate

         Calls: prepare_payment_transaction(...)
         ↓ Gets: unsigned_tx

Human signs in wallet → Client submits signed_tx

Client → Merchant: GET /resource
         Header: x402-payment-signature: <tx_signature>

Merchant → Corbits (Verification Service):
         "Verify this payment for merchant X"
         ← Returns: receipt PDA, amount, status

Merchant → Client: 200 OK + Resource

Key design choice: x402 does NOT invoke the ChainPay program directly. It's a human-signed, direct SPL transfer that happens to follow x402 semantics.

This keeps two concerns separate:

  • x402: HTTP-level payment negotiation
  • ChainPay mandates: Agent-level delegated spending

MCP Discovery: How Agents Learn About Payment

Agent Runtime Initialization
    ↓
MCP Server List:
[
  {
    name: "chainpay",
    url: "https://chainpay-mcp.onrender.com",
    protocols: ["stdio", "sse"]
  }
]
    ↓
Agent: "I have an MCP server available"
    ↓
Agent calls: list_tools()
    ↓
Returns: [
  discover_supported_assets,
  get_mandate_info,
  check_agent_eligibility,
  create_mandate,
  get_quote_and_preflight,
  authorize_payment,
  prepare_payment_transaction,
  submit_signed_transaction,
  get_payment_receipt,
  pause_mandate,
  revoke_mandate
]
    ↓
Agent: "I understand how to pay"
    ↓
Agent calls: get_mandate_info(mandate_x)
    ↓
Agent discovers:
-- Can spend up to 1000 USDC per transaction
-- Has 500 USDC remaining this period
-- Cooldown: 60 seconds between payments
-- Expires: March 15, 2027
    ↓
Agent makes informed decisions about what to do

This is capability discovery. The agent learns what it can do from the protocol, not from hardcoded configuration.


Why This Matters for ML Deployments

The Trust Problem in Agentic Systems

Modern ML agents are powerful but unpredictable. Fine-tuning a model can change its behavior in subtle ways. A new prompt injection technique could be discovered tomorrow. We can't rely on agent behavior alone.

ChainPay solves this at the architecture level:

  1. Agent declares intent (I want to pay Alice $100)
  2. Policy layer validates (Is this within mandate limits?)
  3. Cryptographic proof (Can you show me the receipt?)
  4. Owner approval (Does this match what I authorized?)

The agent cannot:

  • Exceed its limit
  • Spend after expiry
  • Access unauthorized tokens
  • Cause double-spends (replay protection)
  • Transfer to unapproved recipients (those are chosen by the agent, but the mandate controls which mints)

Composable Agent Systems

If you're building a multi-agent system where:

  • Agent A queries data
  • Agent B processes results
  • Agent C pays for Agent A's API calls
  • Agent D audits spending

Each agent uses the same MCP interface. No integration per agent type. No framework-specific wallet handling.

Agent C doesn't need to know if Agent B is Claude, or a local model, or a fine-tuned specialist. It just calls prepare_payment_transaction and trusts the policy layer.


Diagrams: System Architecture

High-Level: Who Talks to Whom

┌──────────────┐
│ AI Agent     │
│ (MCP Client) │
└──────┬───────┘
       │ MCP Tool Calls
       ↓
┌──────────────────────────────────┐
│ ChainPay MCP Server              │
│ ┌────────────────────────────────┐│
│ │ Tool Layer                     ││
│ │ - Discovery                    ││
│ │ - Authorization                ││
│ │ - Execution                    ││
│ │ - Receipts                     ││
│ └────────────────────────────────┘│
└──────┬───────────────────────────┘
       │ Solana RPC Calls
       ↓
┌──────────────────────────────────┐
│ Solana Blockchain                │
│ ┌────────────────────────────────┐│
│ │ ChainPay Program               ││
│ │ ┌──────────────────────────────┐││
│ │ │ Mandate Accounts             │││
│ │ │ Receipt PDAs                 │││
│ │ │ Policy Enforcement           │││
│ │ └──────────────────────────────┘││
│ │ ┌──────────────────────────────┐││
│ │ │ SPL Token Transfer           │││
│ │ │ Token-2022 Transfer          │││
│ │ └──────────────────────────────┘││
│ └────────────────────────────────┘│
└──────────────────────────────────┘

┌──────────────────────────────────┐
│ Backend (PostgreSQL)             │
│ - Payment Records (indexed)      │
│ - Agent Registry                 │
│ - Audit Logs                     │
└──────────────────────────────────┘

Detailed: Payment Flow

DISCOVERY PHASE
===============
Agent → MCP: discover_supported_assets()
    ↓ Returns: [USDC, PYUSD, ...]
    
Agent → MCP: get_mandate_info(mandate_addr)
    ↓ Returns: { limit, spent, expiry, cooldown, ... }

PLANNING PHASE
==============
Agent → MCP: get_quote_and_preflight(mint, amount, recipient)
    ↓ Checks: limit, expiry, token extensions
    ↓ Returns: { feasible: true, routing_path: "Token-2022" }

PREPARATION PHASE
=================
Agent → MCP: prepare_payment_transaction(...)
    ↓ Builds: unsigned tx
    ↓ Returns: { unsigned_tx, fee_estimate, receipt_pda }

AUTHORIZATION PHASE
===================
Agent → Owner: "I want to execute this payment"
Owner → Wallet: Sign this transaction
    ↓ Wallet: Cryptographically signs

SETTLEMENT PHASE
================
Owner/Agent → MCP: submit_signed_transaction(signed_tx)
    ↓ MCP → Solana: sendTransaction(signed_tx)
    ↓ Solana: Executes ChainPay program
    ↓ Creates: Receipt PDA
    
VERIFICATION PHASE
==================
MCP: Waits for finality (typically ~32 seconds on Solana)
MCP → Solana: Fetch receipt PDA
    ↓ Verifies: receipt.mandate == mandate_addr
    ↓ Verifies: receipt.amount == requested_amount
    ↓ Verifies: receipt.agent == agent_pubkey
    
MCP → PostgreSQL: Insert payment record
    ↓ INSERT into payments (
      tx_signature, mandate_addr, agent_pubkey,
      amount, recipient, receipt_pda, slot, status
    )
    
Agent → MCP: get_payment_receipt(tx_signature)
    ↓ Returns: { status: 'finalized', receipt_pda, ... }

Token Routing Decision Tree

Agent requests payment with mint M

├─ Is M in supported_asset_registry?
│  ├─ NO → Error: unsupported_mint
│  └─ YES ↓
│
├─ Get token_program for M
│  ├─ Program == "SPL"
│  │  └─ Use: SPL Token program derivation
│  │
│  └─ Program == "Token-2022"
│     └─ Scan for extensions ↓
│        │
│        ├─ transfer_fee present? → Error: transfer_fees_unsupported
│        ├─ hooks present? → Error: hooks_unsupported
│        ├─ required_memo present? → Error: memo_unsupported
│        ├─ cpi_guard active? → Error: cpi_guard_conflict
│        ├─ confidential_transfer? → Error: confidential_unsupported
│        │
│        └─ Safe to proceed
│           └─ Use: Token-2022 derivation with detected extensions
│
├─ Derive payer token account (ATA or custom)
├─ Derive recipient token account
│
└─ Return: { routing_path: "SPL|Token-2022", accounts: [...] }

Why This Architecture Scales

For Agent Deployments

Each agent type (Claude MCP, CrewAI, AutoGen, custom) uses the same tools. No vendor lock-in. No integration per agent. Deploy a new agent, point it at ChainPay MCP, and it can spend money within predefined bounds.

For Financial Operators

If you're running multiple agents that need spending authority:

  • Create mandates for each agent
  • Set per-transaction limits, total budgets, expiry times
  • Observe spending in real-time via get_mandate_info
  • Revoke mandates instantly if needed
  • Audit all payments with on-chain receipts

For Settlement Layer Operators

ChainPay's design is settlement-agnostic. Today it's Solana. Tomorrow it could be Polygon, Arbitrum, or a Layer 2. The MCP interface stays the same; the backend swaps settlement layers.

For Security Practitioners

Policy is immutable (on-chain mandates). Signing is explicit (humans approve before settlement). Authority is bounded (agents can't exceed limits). Receipts are verifiable (on-chain PDAs). Nothing is hidden or soft.


Conclusion: Universal Means Auditable

A universal MCP for agent payments is not about removing complexity—it's about making complexity explicit and verifiable.

Every agent learns the same tools. Every payment follows the same policy checks. Every settlement produces a cryptographic receipt. No agent can secretly widen its authority. No backend can silently approve a risky payment.

This is how you scale agentic systems without sacrificing control.

Explore the ChainPay API and MCP documentation at https://chainpay-mcp.onrender.com/docs or test the tools directly at https://chainpay-mcp.onrender.com/tools.


Tags: #AgenticAI #MCP #BlockchainPayments #Solana #PolicyEnforcement #BoundedAuthority #MLOps #Protocol