# AMP > Persistent payment channels for AI agents on Solana ## Brand AMP brand assets and guidelines. *** ### Logo The AMP logo is the text "AMP" set in bold system font. It uses the current text color to adapt to light and dark backgrounds.
AMP
### Colors | Color | Hex | Usage | | --------- | --------- | --------------------------------- | | Black | `#000000` | Primary accent, links, highlights | | Navy | `#0a1628` | Dark background | | Deep Navy | `#060f1d` | Secondary dark background | | White | `#FFFFFF` | Text on dark backgrounds | ### Typography AMP uses the system font stack for maximum performance and consistency across platforms: ``` font-family: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif ``` ### Usage guidelines * Always use the logo on dark backgrounds for best contrast. * Do not modify the logo proportions. * When referencing the protocol, write "AMP" in all caps. * The full name is "Autonomous Machine Payments" — use it on first reference. ### Built by AMP is built by [Valeo](https://github.com/valeo-cash). The protocol is open source under the MIT license. ## Comparison: x402 vs MPP vs AMP An honest comparison of the three machine payment protocols. *** ### Protocol overview **x402** (Coinbase) — HTTP 402 with per-request payments on Base (EVM). Simple, stateless, one on-chain transaction per call. **MPP** (Tempo/Stripe) — extensible HTTP 402 framework with multiple payment methods (Tempo, Stripe, cards, Lightning, Solana). Supports sessions with off-chain vouchers. IETF specification. **AMP** (Valeo) — Solana-native persistent channels. Off-chain metering, on-chain net settlement. Supports delegation, chaining, and multiple transports. ### Feature matrix | Feature | x402 | MPP | AMP | | ------------------------ | ------------- | ------------------------------- | ---------------------- | | Payment model | Per-request | Per-request + sessions | Persistent channels | | Runtime latency | Per-call | Per-call / near-zero (session) | Zero after open | | Transport | HTTP only | HTTP + MCP | HTTP + MCP + WS + gRPC | | Chain | Base (EVM) | Tempo, Solana, Lightning, cards | Solana native | | On-chain txns / 1K calls | 1,000 | 2 (session) | \~3 | | Streaming | No | SSE with vouchers | Native (any transport) | | Delegation | No | No | Native | | Credit support | No | No | Native (ACE-ready) | | On-chain composability | Limited (EVM) | Varies | Full Solana DeFi | | IETF standardization | No | Yes | No (open spec) | | Fiat support | No | Yes (Stripe, cards) | No (crypto-native) | | Service discovery | No | No | On-chain registry | | Multi-channel netting | No | No | Stratum | | Reputation | No | No | On-chain scoring | ### Cost analysis (1,000 calls at $0.001/call) | Protocol | On-chain txns | Gas cost | | ------------------- | ------------- | ------------------ | | x402 (Base) | 1,000 | \~$1.00 | | MPP session (Tempo) | 2 | \~2x Tempo tx cost | | AMP (Solana) | \~3 | \~$0.00075 | ### When to use what #### x402 wins when: * You want the simplest possible integration * You're already on Base/EVM * You only need one-off per-request payments #### MPP wins when: * You need multi-method support (crypto + cards + fiat) * You want IETF-grade standardization * You need Python or Rust SDKs today #### AMP wins when: * You're building on Solana and want native composability * You need high-volume usage with minimal on-chain cost * You need delegation (agent-to-agent budget forwarding) * You need transport bindings beyond HTTP (MCP, gRPC, WebSocket) * You want on-chain channel state that other programs can read and compose with ### The real differentiator MPP is a **payment framework** — flexible, multi-method, payment-rail agnostic. AMP is a **financial state protocol** — Solana-native, composable, persistent. They serve different architectural needs. [Full comparison on GitHub](https://github.com/valeo-cash/amp/blob/main/COMPARISON.md) ## FAQ Frequently asked questions about the AMP protocol. *** #### How is AMP different from x402? x402 requires one on-chain transaction per API call. The client sends a request, gets a 402 rejection, signs a payment on Base (EVM), and retries with a receipt. Every call costs gas. AMP opens a channel once and makes unlimited calls with zero per-request transactions. Settlement happens periodically, netting all usage into a single transaction. For 1,000 calls, x402 costs \~$1.00 in gas; AMP costs \~$0.00075. [Full comparison →](/comparison) #### How is AMP different from MPP? MPP is a payment-method-agnostic framework that works across multiple rails (Tempo, Stripe, cards, Lightning). AMP is Solana-native — channels are on-chain PDAs, composable with DeFi. MPP sessions use off-chain vouchers on Tempo; AMP uses off-chain metering with on-chain net settlement on Solana. AMP additionally supports delegation (budget forwarding to sub-agents), channel chaining (agent supply chains), and on-chain reputation scoring. They serve different needs: MPP for multi-method flexibility, AMP for Solana-native composability. [Full comparison →](/comparison) #### Is AMP compatible with MPP or x402? AMP uses HTTP 402 for initial discovery (same status code), but the channel lifecycle is different. AMP is a separate protocol optimized for Solana-native persistent channels. You could run AMP and x402 on different routes of the same server. #### What payment methods does AMP support? USDC on Solana (SPL token). AMP is intentionally Solana-only for maximum on-chain composability. Any SPL token can be used — the server specifies the accepted mint in its pricing manifest. #### Do I need a Solana wallet? Yes. Both the agent (client) and service (server) need Solana keypairs. The agent's keypair signs channel operations and request credentials. The server's keypair signs metering proofs for settlement. #### How much does it cost? The protocol is free and open source. Transaction costs are Solana fees: \~$0.00025 per transaction. A typical channel lifecycle (open + settle + close) costs \~$0.00075 in gas, regardless of how many API calls were made. #### What are channels? A channel is a persistent financial state object between two parties, stored as a PDA on Solana. The agent deposits funds, consumes services freely, and the service settles periodically. Unlike per-request payments, the channel maintains state across interactions. [Learn about channels →](/protocol/channels) #### What is delegation? An agent can assign a portion of its channel budget to a sub-agent. The sub-agent consumes services against the original channel up to a specified limit. This enables multi-agent workflows without each agent needing its own capital. [Learn about delegation →](/protocol/delegation) #### What is channel chaining? When Service B needs to call Service C to fulfill a request, Service B can create a downstream channel funded from the upstream channel. Value flows through the chain automatically. No separate capital required. [Learn about chaining →](/guides/chaining) #### Is it safe? Channel funds are held in a PDA-owned vault on Solana. Only the AMP program can move funds — via settlement (to recipient) or close (refund to funder). Replay attacks are prevented by monotonic sequence numbers. The client can close the channel at any time to recover unspent funds. [Security model →](/protocol/channels#security) #### Is AMP deployed? Yes, to Solana devnet. Program ID: [`2KQCaQ9j8YtewZ4QjmDfnsVANZXLBcPSYFhAj2eUNaPP`](https://explorer.solana.com/address/2KQCaQ9j8YtewZ4QjmDfnsVANZXLBcPSYFhAj2eUNaPP?cluster=devnet). Mainnet deployment is pending security review. #### Who is building AMP? AMP is built by [Valeo](https://github.com/valeo-cash). The protocol is open source under the MIT license. ## Autonomous Machine Payments Protocol The open protocol for persistent machine-to-machine payment channels. *** The Autonomous Machine Payments Protocol (AMP) lets any client — agents, apps, or humans — pay for any service through persistent payment channels on Solana. Developers use AMP to let their agents pay for services. Service operators use AMP to accept payments for their APIs. Unlike per-request protocols (x402, MPP charge), AMP opens a channel once and consumes freely — zero payment friction per call. ### Who is AMP for? **Developers** build apps and agents that consume paid services. You integrate an AMP client so your agent can discover, pay for, and use third-party APIs without per-request payment overhead. **Agents** are the entities that take action — calling APIs, generating images, querying data. They pay for services autonomously through persistent channels with deposited budgets. **Services** operate APIs that charge for access. You integrate AMP middleware to accept payments with zero per-request friction. Settlement happens automatically in the background. ### The problem with per-request payments Current protocols (x402, MPP charge) require a payment transaction for every API call. This creates three systemic problems: 1. **Per-call latency.** Every request requires payment verification, adding round-trips. 2. **Per-call on-chain costs.** Each payment produces an on-chain transaction — O(N) cost for N calls. 3. **Stateless interactions.** Each request is financially independent. No concept of a budget, an ongoing relationship, or accumulated trust. Agents don't work this way. They maintain ongoing relationships with services, execute against budgets, open streaming connections, and delegate sub-tasks. A payment protocol for agents must model payments as continuous state, not discrete events. ### Payment flow 1. **Client discovers pricing** — `GET /.well-known/amp.json` returns the server's rates, accepted tokens, and minimum deposit. 2. **Client opens a channel** — one Solana transaction deposits SPL tokens into a PDA-owned vault. 3. **Client makes requests** — every request includes `AMP-Channel`, `AMP-Seq`, and `AMP-Sig` headers. Zero on-chain transactions. 4. **Server meters usage** — the server tracks consumption off-chain (per-call, per-second, per-byte). 5. **Server settles periodically** — one Solana transaction nets all usage since the last settlement. 6. **Client closes channel** — remaining balance is refunded. Channel PDA is closed, rent reclaimed. ### Use cases * **LLM inference** — pay for completions through a persistent channel instead of signing every request. * **Streaming data** — continuous metering for real-time feeds, priced per-second. * **Multi-agent workflows** — delegate budget to sub-agents for autonomous task execution. * **Agent supply chains** — chain channels so Service A can pay Service B from the same upstream budget. ### Official SDKs * **TypeScript** — [`@valeo/amp-server`](/sdk/typescript) (Express middleware, metering, auto-settlement) and [`@valeo/amp-client`](/sdk/typescript) (agent SDK, auto-discovery, `amp.fetch()`) ### Next steps * [Quick Start — Add payments to your API](/quickstart/server) * [Protocol concepts — Channels, metering, settlement](/protocol) * [Full Specification on GitHub](https://github.com/valeo-cash/amp/blob/main/SPEC.md) ## Services AMP-enabled services on Solana. *** AMP is deployed to Solana devnet. Production services are coming soon. The AMP protocol includes an [on-chain Service Registry](/ecosystem/registry) that will let agents discover services by category, price, and reputation. Once live, services will be listed here automatically. ### Be the first to integrate Adding AMP payments to your API takes a few lines of code: ```typescript import { AMP } from "@valeo/amp-server" app.use(AMP.middleware({ rate: "0.001/call" })) ``` [Add payments to your API →](/quickstart/server) ### Service categories The registry supports these categories: | Category | Description | | ------------- | --------------------------------- | | Inference | LLM, image generation, embeddings | | Data | Search, scraping, enrichment | | Compute | GPU, serverless, batch processing | | Storage | IPFS, Arweave, S3-compatible | | Communication | Email, SMS, notifications | | Financial | Pricing, market data, trading | | Identity | KYC, verification, attestation | ## SDKs Official AMP SDKs for integrating with the protocol. *** ### TypeScript The primary SDK, with three packages: | Package | Description | | ---------------------------------------------------------------------------------- | ----------------------------------------------------------------- | | [`@valeo/amp-core`](https://github.com/valeo-cash/amp/tree/main/packages/core) | Shared types, PDA derivation, constants, metering proof utilities | | [`@valeo/amp-server`](https://github.com/valeo-cash/amp/tree/main/packages/server) | Express middleware, MCP server, metering engine, auto-settlement | | [`@valeo/amp-client`](https://github.com/valeo-cash/amp/tree/main/packages/client) | Agent SDK, `amp.fetch()`, auto-discovery, channel lifecycle | [TypeScript SDK reference →](/sdk/typescript) ### Install ```bash # Server npm install @valeo/amp-server # Client npm install @valeo/amp-client # Core (included as dependency of both) npm install @valeo/amp-core ``` ### Quick examples #### Server ```typescript import { AMP } from "@valeo/amp-server" app.use(AMP.middleware({ rate: "0.001/call" })) ``` #### Client ```typescript import { AMPClient } from "@valeo/amp-client" const amp = new AMPClient({ wallet, connection, budget: 10.00, token: "USDC" }) const res = await amp.fetch("https://api.example.com/v1/data") ``` ### Other languages Python and Rust SDKs are planned. In the meantime, any language that can interact with Solana and sign Ed25519 can implement the AMP protocol using the [specification](https://github.com/valeo-cash/amp/blob/main/SPEC.md). ## TypeScript SDK API reference for the AMP TypeScript packages. *** ### @valeo/amp-core Shared types and utilities used by both server and client. #### PDA derivation ```typescript import { deriveChannelPDA, deriveVaultPDA } from "@valeo/amp-core" const [channelPDA, bump] = deriveChannelPDA(funderPubkey, recipientPubkey, nonce) const [vaultPDA] = deriveVaultPDA(channelPDA) ``` #### Channel queries ```typescript import { fetchChannel, findChannelsByFunder, isChannelHealthy } from "@valeo/amp-core" const channel = await fetchChannel(channelPDA, program) const channels = await findChannelsByFunder(program, funderPubkey) const healthy = isChannelHealthy(channel) ``` #### Metering proofs ```typescript import { signMeteringProof, verifyMeteringProof } from "@valeo/amp-core" const proof = signMeteringProof({ channel, amount, callCount, periodStart, periodEnd, seqStart, seqEnd }, serverKeypair) const valid = verifyMeteringProof(proof, serverPubkey) ``` #### Constants ```typescript import { AMP_PROGRAM_ID, USDC_MINT, USDC_DECIMALS, AMP_VERSION } from "@valeo/amp-core" ``` *** ### @valeo/amp-server #### AMP class The main server integration. Two patterns: ```typescript import { AMP } from "@valeo/amp-server" // One-liner app.use(AMP.middleware({ rate: "0.001/call" })) // Full config const amp = new AMP({ wallet: Keypair, connection: Connection, pricing: PricingConfig, routes?: Record>, settlement?: { auto?: boolean, interval?: number }, network?: string, programId?: PublicKey, }) app.use(amp.middleware()) ``` #### AMPContext Attached to `req.amp` after successful validation: ```typescript interface AMPContext { channel: PublicKey // channel PDA channelState: ChannelState seq: number // request sequence number balance: bigint // current channel balance isDelegate: boolean // whether request is from a delegate } ``` #### AMPMcpServer ```typescript import { AMPMcpServer } from "@valeo/amp-server" const server = new AMPMcpServer({ wallet, connection, tools: { tool_name: { description: string, pricing: PricingConfig, inputSchema: object, handler: async (args, ampContext) => result, } } }) ``` #### Standalone utilities ```typescript import { parseRateString, buildPricingManifest, validateRequest, verifyAmpSig } from "@valeo/amp-server" const pricing = parseRateString("0.001/call") // → PricingConfig const manifest = buildPricingManifest(config) // → AmpPricingManifest const result = await validateRequest(channelPDA, seq, sig, program, cache) const valid = verifyAmpSig(seq, sig, pubkey) // → boolean ``` *** ### @valeo/amp-client #### AMPClient ```typescript import { AMPClient } from "@valeo/amp-client" const amp = new AMPClient({ wallet: Keypair, connection: Connection, budget: number, // human units (e.g. 10.00) token?: string | PublicKey, // default: "USDC" settleInterval?: number, // default: 3600 programId?: PublicKey, }) ``` **Methods:** | Method | Returns | Description | | ------------------------ | ---------------------------- | ------------------------------------------ | | `fetch(url, options?)` | `AMPResponse` | Drop-in fetch with auto channel management | | `openChannel(opts)` | `PublicKey` | Explicitly open a channel | | `topUp(channel, amount)` | `string` | Add funds (returns tx sig) | | `close(channel)` | `string` | Close one channel | | `closeAll()` | `Map` | Close all channels | | `getChannels()` | `Map` | Get all active channels | | `getRemainingBudget()` | `number` | Total remaining across channels | #### AMPMcpClient ```typescript import { AMPMcpClient } from "@valeo/amp-client" const client = new AMPMcpClient({ wallet, connection, budget: 5.0 }) await client.connect("http://localhost:3000/mcp") const tools = client.listTools() const result = await client.callTool("tool_name", { arg: "value" }) await client.close() ``` #### Discovery ```typescript import { discoverPricing } from "@valeo/amp-client" const pricing = await discoverPricing("https://api.example.com/v1/data") // Tries /.well-known/amp.json, falls back to 402 header parsing ``` ## Use with Agents Let your agent pay for services autonomously through persistent channels. *** ### Install ```bash npm install @valeo/amp-client @solana/web3.js ``` ### Basic usage `AMPClient.fetch()` is a drop-in replacement for native `fetch()`. It handles discovery, channel opening, and credential signing automatically. ```typescript import { AMPClient } from "@valeo/amp-client" import { Keypair, Connection } from "@solana/web3.js" const amp = new AMPClient({ wallet: agentKeypair, connection: new Connection("https://api.mainnet-beta.solana.com"), budget: 10.00, // $10 USDC total budget token: "USDC", }) // First call: discovers pricing, opens channel, then fetches const res = await amp.fetch("https://api.example.com/v1/data", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ query: "What is AMP?" }), }) console.log(await res.json()) console.log("Balance:", res.ampBalance) // remaining channel balance // Subsequent calls reuse the open channel — zero on-chain cost for (let i = 0; i < 100; i++) { await amp.fetch("https://api.example.com/v1/data") } // Close all channels when done — remaining budget refunded await amp.closeAll() ``` ### What happens under the hood 1. **Discovery** — on first call to a new host, the client fetches `/.well-known/amp.json` to learn pricing and the server's pubkey. 2. **Channel open** — the client submits one Solana transaction to open a channel with a deposit. 3. **Request signing** — every request includes three headers: * `AMP-Channel` — the channel PDA address * `AMP-Seq` — a monotonically increasing sequence number * `AMP-Sig` — Ed25519 signature of the sequence number 4. **Retry** — if a 402 is returned (pricing changed), the client re-discovers and opens a new channel automatically. 5. **Close** — `closeAll()` closes every open channel and refunds remaining balances. ### Explicit channel management If you want more control, open and close channels directly: ```typescript const channel = await amp.openChannel({ recipient: serverPubkey, deposit: 5.00, settleInterval: 3600, }) await amp.topUp(channel, 2.00) // add more funds await amp.close(channel) // close and refund ``` ### Check remaining budget ```typescript const remaining = amp.getRemainingBudget() // in human units console.log(`$${remaining} USDC remaining`) ``` ### Next steps * [Accept delegated payments →](/guides/delegation) * [Channel chaining →](/guides/chaining) * [Protocol concepts →](/protocol) ## Quick Start Get up and running with AMP in minutes. *** Choose your path: ### I operate an API Add AMP payments to your Express server. Clients pay through persistent channels — you get zero-friction, metered revenue. [Add payments to your API →](/quickstart/server) ### I'm building an agent Let your agent pay for services autonomously. One line opens a channel, then `amp.fetch()` handles everything. [Use with agents →](/quickstart/agent) ## Add Payments to Your API Accept AMP payments for your API endpoints in under 5 minutes. *** ### Using an LLM? Paste this into your coding agent: ``` Reference https://amp.valeoprotocol.io/quickstart/server Add AMP payments to my Express server with a /v1/data route that charges $0.01 per request using USDC on Solana. ``` ### Install ```bash npm install @valeo/amp-server @solana/web3.js ``` ### One-liner setup The fastest way to add AMP payments: ```typescript import express from "express" import { AMP } from "@valeo/amp-server" const app = express() // Protect all routes — $0.001 per call app.use(AMP.middleware({ rate: "0.001/call" })) app.get("/v1/data", (req, res) => { res.json({ message: "Hello from a paid API!", channel: req.amp?.channel?.toBase58(), }) }) app.listen(3000) ``` This does everything automatically: * Returns `402` with pricing headers when no AMP credentials are present * Validates the channel on-chain * Verifies Ed25519 signatures * Meters usage * Auto-settles periodically ### Full configuration For production, provide your own keypair and connection: ```typescript import { AMP } from "@valeo/amp-server" import { Keypair, Connection } from "@solana/web3.js" const serverKeypair = Keypair.fromSecretKey(/* your key */) const connection = new Connection("https://api.mainnet-beta.solana.com") const amp = new AMP({ wallet: serverKeypair, connection, pricing: { mode: "per-call", rate: "10000", // $0.01 in USDC (6 decimals) token: "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", minDeposit: "1000000", // $1.00 minimum channel deposit settleInterval: 3600, // settle every hour }, routes: { "/v1/inference": { mode: "per-call", rate: "100000" }, // $0.10/call "/v1/stream": { mode: "per-second", rate: "1000" }, // $0.001/sec }, settlement: { auto: true, interval: 3600 }, }) // Serve pricing manifest app.get("/.well-known/amp.json", amp.wellKnownHandler()) // Protected endpoints app.use("/v1", amp.middleware()) ``` ### Pricing manifest AMP clients discover your pricing via `/.well-known/amp.json`: ```json { "amp_version": "1.0", "recipient": "", "program_id": "2KQCaQ9j8YtewZ4QjmDfnsVANZXLBcPSYFhAj2eUNaPP", "network": "solana:mainnet-beta", "pricing": { "default": { "mode": "per-call", "rate": "10000", "token": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", "minDeposit": "1000000", "settleInterval": 3600 } } } ``` ### What happens when a client connects 1. Client sends `GET /v1/data` with no AMP headers 2. Your middleware returns `402` with `AMP-Pricing` headers 3. Client opens a channel on-chain (deposits USDC) 4. Client retries with `AMP-Channel`, `AMP-Seq`, `AMP-Sig` headers 5. Middleware validates, meters, and calls `next()` 6. Your handler runs normally — `req.amp` has channel context ### Next steps * [Accept streaming payments →](/guides/streaming) * [Protocol details →](/protocol) * [TypeScript SDK reference →](/sdk/typescript) ## Channels A persistent financial state object between two parties on Solana. *** A channel is the core primitive of AMP. It replaces per-request payments with a persistent financial relationship. One funder deposits tokens, consumes services freely, and the recipient settles periodically. ### Lifecycle ``` Open → Active → Settled (repeating) → Closed ``` 1. **Open** — the funder submits `open_channel` on-chain. Tokens transfer from the funder's wallet to a PDA-owned vault. 2. **Active** — the funder makes requests with channel credentials. The server meters usage off-chain. 3. **Settled** — the recipient submits `settle` on-chain. Tokens transfer from the vault to the recipient. Channel returns to Active. 4. **Closed** — either party submits `close_channel`. Final settlement to the recipient, remaining balance refunded to funder. ### Channel identity A channel is a PDA derived from: ``` Seeds: [b"amp-channel", funder_pubkey, recipient_pubkey, nonce_le_bytes] ``` The nonce allows multiple channels between the same funder-recipient pair. ### Opening a channel Before opening, the client discovers the server's pricing via `/.well-known/amp.json` or by parsing a `402` response. The client then derives the PDA, creates the vault, and deposits tokens: ```typescript const [channelPDA] = PublicKey.findProgramAddressSync( [Buffer.from("amp-channel"), funder.toBuffer(), recipient.toBuffer(), nonceBuf], programId ) await program.methods .openChannel(deposit, rateLimit, settleInterval, nonce) .accounts({ funder, recipient, mint, channelState: channelPDA, vault: vaultPDA, funderTokenAccount, systemProgram, tokenProgram }) .rpc() ``` ### Request credentials After opening, every request includes three headers: | Header | Value | Purpose | | ------------- | --------------------------------- | ---------------------- | | `AMP-Channel` | Channel PDA in base58 | Identifies the channel | | `AMP-Seq` | Monotonically increasing integer | Replay prevention | | `AMP-Sig` | Ed25519 signature of seq (LE u64) | Authentication | The signature proves the request comes from the channel's funder (or delegate). ### Security * **PDA ownership** — the vault is owned by the channel PDA. Only the AMP program can move funds. * **Replay prevention** — the server rejects any sequence number less than or equal to the last seen. * **Unilateral close** — the funder can close the channel at any time, recovering unspent funds. Maximum loss is one unsettled period. * **Sybil resistance** — opening a channel requires a deposit and rent, making spam costly. ### Next steps * [Metering →](/protocol/metering) * [Settlement →](/protocol/settlement) * [On-chain channel state →](/on-chain/channel-state) ## Delegation Assign consumption rights from a funder to a sub-agent. *** Delegation lets a funder assign a delegate pubkey to their channel. The delegate can consume services against the channel up to a specified limit. This enables agent-to-agent budget forwarding without the delegate needing its own capital. ### How it works 1. Agent A opens a channel with $10 2. Agent A calls `set_delegate` with Agent B's pubkey and a $3 limit 3. Agent B signs requests with its own keypair, referencing Agent A's channel 4. The server verifies Agent B's signature against the `delegate` field on-chain 5. Usage is metered normally; the delegate limit is tracked separately ### Setting a delegate ```typescript await program.methods .setDelegate(agentBPubkey, new BN(3_000_000)) // $3 limit .accounts({ funder: agentA.publicKey, channelState: channelPDA, }) .signers([agentA]) .rpc() ``` ### On-chain state | Field | Type | Description | | ------------------- | ---------------- | ----------------------------------- | | `delegate` | `Option` | Delegate's public key | | `delegate_limit` | `u64` | Maximum amount delegate can consume | | `delegate_consumed` | `u64` | Amount consumed so far | ### Constraints * Only the funder can set or change the delegate * The delegate limit cannot exceed the current channel balance * Setting a new delegate resets `delegate_consumed` to zero * The server SDK validates both funder and delegate signatures automatically ### Use cases * **Multi-agent workflows** — a planning agent delegates budget to task-specific sub-agents * **Team budgets** — one wallet funds a channel, team members consume independently * **Tiered access** — delegate different limits to different agents based on their role ### Next steps * [Accept delegated payments guide →](/guides/delegation) * [Channel chaining →](/guides/chaining) ## Protocol Overview How AMP works under the hood. *** AMP is a financial state protocol. Instead of processing a payment per request, it maintains a persistent financial relationship between two parties through an on-chain channel. ### Payment flow 1. **Client requests a resource** — `GET /v1/data` with no payment headers 2. **Server returns pricing** — `402 Payment Required` with `AMP-Pricing`, `AMP-Recipient`, `AMP-Program` headers 3. **Client opens a channel** — one Solana transaction deposits funds into a PDA-owned vault 4. **Client retries with credentials** — `AMP-Channel`, `AMP-Seq`, `AMP-Sig` headers 5. **Server validates and serves** — verifies channel state, signature, and sequence number. Returns `200 OK` 6. **Repeat** — steps 4-5 for all subsequent requests. Zero on-chain transactions. 7. **Server settles periodically** — one Solana transaction covers all accumulated usage 8. **Client closes channel** — remaining balance is refunded ### Core concepts #### [Channels](/protocol/channels) A persistent financial state object between a funder and recipient, stored as a PDA on Solana. Holds deposited tokens in a vault. Lifecycle: Open, Active, Settled (repeating), Closed. #### [Metering](/protocol/metering) Off-chain usage tracking by the server. Modes: per-call, per-second, per-byte, per-compute. No on-chain transactions during consumption. #### [Settlement](/protocol/settlement) On-chain transfer of owed funds from the vault to the recipient. Always net — one transaction covers all usage since the last settlement. #### [Delegation](/protocol/delegation) A funder assigns consumption rights to a delegate pubkey. The delegate can consume services against the channel up to a specified limit. ### Error codes | Code | Description | HTTP Status | | ------------------- | -------------------------------------------- | ----------- | | `AMP_NO_CHANNEL` | No valid channel for this request | 402 | | `AMP_UNDERFUNDED` | Channel balance below minimum | 402 | | `AMP_CLOSED` | Channel has been closed | 410 | | `AMP_RATE_EXCEEDED` | Usage exceeds channel rate limit | 429 | | `AMP_INVALID_SEQ` | Sequence number not monotonically increasing | 400 | | `AMP_INVALID_SIG` | Signature verification failed | 401 | | `AMP_SETTLE_EARLY` | Settlement attempted before interval elapsed | 425 | | `AMP_DEPOSIT_LOW` | Initial deposit below server minimum | 400 | | `AMP_INVALID_PROOF` | Metering proof signature invalid | 400 | ### Transports AMP is transport-agnostic. Defined bindings for: * [HTTP](/protocol/transports/http) — headers on every request * [MCP / JSON-RPC](/protocol/transports/mcp) — `_amp` credentials in tool calls * [WebSocket](/protocol/transports/websocket) — JSON or binary frame prefix * [gRPC](/protocol/transports/grpc) — metadata keys ## Metering Off-chain usage tracking by the server. *** Metering is how the server tracks what the client has consumed. All metering happens off-chain — no transactions during consumption. The accumulated usage is then settled periodically in a single on-chain transaction. ### Metering modes | Mode | Unit | Description | | ------------- | ------------------------ | --------------------------------------------------- | | `per-call` | Fixed per invocation | Each API call costs a fixed amount | | `per-second` | Per second of connection | For streaming or long-lived connections | | `per-byte` | Per byte transferred | For data-heavy endpoints | | `per-compute` | Per compute unit | For variable-cost operations (inference, rendering) | | `custom` | Server-defined | Server publishes a custom metering function | ### How it works 1. Client sends a request with `AMP-Channel`, `AMP-Seq`, `AMP-Sig` headers. 2. Server validates credentials (channel exists, signature valid, seq monotonic). 3. Server serves the request. 4. Server increments the local metering counter. 5. No on-chain transaction occurs. The server maintains a `ChannelUsage` record per channel: ```typescript interface ChannelUsage { callCount: number bytesConsumed: number secondsConsumed: number totalAmount: number // rate * usage seqStart: number seqEnd: number periodStart: number // unix timestamp } ``` ### Metering proofs When the server settles, it produces a `MeteringProof`: ```json { "channel": "", "amount": 4500000, "callCount": 4500, "periodStart": 1711234567, "periodEnd": 1711238167, "seqStart": 1001, "seqEnd": 5500, "serverSignature": "" } ``` The signature covers: `SHA256(channel || amount || callCount || periodStart || periodEnd || seqStart || seqEnd)`. ### Pricing configuration Servers declare pricing in their manifest: ```json { "pricing": { "default": { "mode": "per-call", "rate": "1000", "token": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", "minDeposit": "1000000", "settleInterval": 3600 }, "routes": { "/v1/inference": { "mode": "per-call", "rate": "10000" }, "/v1/stream": { "mode": "per-second", "rate": "1000" } } } } ``` All monetary values are in the token's smallest unit. USDC has 6 decimals: `"1000"` = $0.001. ### Next steps * [Settlement →](/protocol/settlement) * [Accept per-call payments →](/guides/per-call) ## Settlement On-chain transfer of owed funds from the vault to the recipient. *** Settlement is the process of moving tokens from the channel vault to the recipient, based on accumulated usage. Settlement is always net — one on-chain transaction covers all usage since the last settlement, regardless of how many API calls occurred. ### When settlement happens Settlement is triggered when any of these conditions are met: * **Time interval** — `settle_interval` seconds have elapsed since the last settlement * **Usage threshold** — accumulated usage exceeds a server-defined amount * **Explicit request** — either party requests settlement * **Channel close** — final settlement occurs automatically on close ### Settlement process 1. Server constructs a metering proof (see [Metering](/protocol/metering)) 2. Server submits the `settle` instruction to the AMP program 3. Program verifies: amount is within balance and rate limit, settle interval has elapsed 4. Program transfers tokens from vault to recipient's token account 5. Program updates: `balance -= amount`, `total_consumed += amount`, `last_settle_ts = now` ```typescript await program.methods .settle(new BN(settleAmount)) .accounts({ authority: recipientKeypair.publicKey, channelState: channelPDA, vault: vaultPDA, recipientTokenAccount: recipientAta, tokenProgram: TOKEN_PROGRAM_ID, }) .signers([recipientKeypair]) .rpc() ``` ### Rate limiting The `rate_limit` field on the channel constrains the maximum amount that can be settled per interval. This protects the funder from excessive charges: * `settle` instruction verifies `amount <= rate_limit` * `settle` instruction verifies `now >= last_settle_ts + settle_interval` ### Auto-settlement The server SDK includes an auto-settlement engine that runs a background loop: ```typescript const amp = new AMP({ // ... settlement: { auto: true, // start auto-settlement (default: true) interval: 3600, // settle every hour }, }) ``` ### Net clearing The key advantage over per-request protocols: | Protocol | 1,000 calls | On-chain transactions | | -------- | -------------- | --------------------- | | x402 | 1,000 payments | 1,000 | | AMP | 1 settlement | 1 | The server accumulates all usage off-chain and settles the total in a single transaction. ### Next steps * [Channels →](/protocol/channels) * [On-chain instructions →](/on-chain/instructions) ## gRPC Transport AMP credentials as gRPC metadata. *** AMP credentials are carried as gRPC metadata keys, supporting both unary and streaming RPCs. ### Discovery The server exposes an AMP discovery RPC method, or the client calls any method and receives a gRPC status `FAILED_PRECONDITION` with AMP pricing in the error details. ### Request credentials AMP metadata keys on every RPC call: ``` amp-channel: amp-seq: amp-sig: ``` ### Streaming RPCs For server-streaming or bidirectional-streaming RPCs, the client sends AMP metadata in the initial request metadata. The server meters each response message sent. ### Example (Node.js) ```typescript const metadata = new grpc.Metadata() metadata.set("amp-channel", channelPDA.toBase58()) metadata.set("amp-seq", seq.toString()) metadata.set("amp-sig", sig) const response = await client.getData({ query: "test" }, metadata) ``` ### Next steps * [HTTP transport →](/protocol/transports/http) * [MCP transport →](/protocol/transports/mcp) ## HTTP Transport How AMP credentials are carried over HTTP. *** HTTP is the primary transport for AMP. Credentials are sent as request headers, pricing is discovered via a well-known endpoint or 402 response headers. ### Discovery #### Well-known endpoint Servers expose pricing at `GET /.well-known/amp.json`: ```json { "amp_version": "1.0", "recipient": "", "program_id": "2KQCaQ9j8YtewZ4QjmDfnsVANZXLBcPSYFhAj2eUNaPP", "network": "solana:mainnet-beta", "pricing": { "default": { "mode": "per-call", "rate": "1000", "token": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", "minDeposit": "1000000", "settleInterval": 3600 }, "routes": { "/v1/inference": { "mode": "per-call", "rate": "10000" } } } } ``` #### 402 response headers When a request arrives without AMP credentials, the server responds with `402 Payment Required` and these headers: ``` HTTP/1.1 402 Payment Required AMP-Version: 1.0 AMP-Pricing: {"mode":"per-call","rate":"1000","token":"EPjFWdd5...","minDeposit":"1000000","settleInterval":3600} AMP-Recipient: AMP-Program: 2KQCaQ9j8YtewZ4QjmDfnsVANZXLBcPSYFhAj2eUNaPP AMP-Network: solana:mainnet-beta ``` ### Request credentials Include three headers with every request: | Header | Value | Example | | ------------- | --------------------------------- | ----------------- | | `AMP-Channel` | Channel PDA address (base58) | `9fhH5Jbg7VWx...` | | `AMP-Seq` | Monotonic sequence number | `42` | | `AMP-Sig` | Ed25519 signature of seq (base58) | `3xKn9P2b...` | The signature is computed over the sequence number encoded as a little-endian u64 (8 bytes), signed with the funder's (or delegate's) Ed25519 keypair. ### Response headers The server includes the remaining balance in the response: ``` AMP-Balance: 8500000 ``` ### Error responses ```json { "error": "AMP_UNDERFUNDED", "message": "Channel balance is 500 but minimum required is 1000.", "channel": "", "balance": 500 } ``` ## MCP and JSON-RPC Transport AMP credentials in MCP tool calls via JSON-RPC 2.0. *** MCP (Model Context Protocol) is the primary transport for AI agent tool calls. AMP supports MCP as a first-class transport with per-tool pricing. ### Discovery An AMP-enabled MCP server advertises pricing via a dedicated `amp/pricing` method: ```json // Request { "jsonrpc": "2.0", "method": "amp/pricing", "id": 1 } // Response { "jsonrpc": "2.0", "id": 1, "result": { "amp_version": "1.0", "recipient": "", "program_id": "2KQCaQ9j8YtewZ4QjmDfnsVANZXLBcPSYFhAj2eUNaPP", "network": "solana:mainnet-beta", "pricing": { "tools": { "generate_image": { "mode": "per-call", "rate": "50000" }, "search_web": { "mode": "per-call", "rate": "5000" } } } } } ``` Each MCP tool can have its own rate and metering mode. ### Channel credentials in tool calls Include AMP credentials in the `_amp` field of `params`: ```json { "jsonrpc": "2.0", "method": "tools/call", "params": { "name": "generate_image", "arguments": { "prompt": "a cat in space" }, "_amp": { "channel": "", "seq": 42, "sig": "" } }, "id": 2 } ``` The `_amp` field is a reserved namespace. MCP servers that don't support AMP ignore fields prefixed with `_`. ### Payment required response If a tool call lacks credentials: ```json { "jsonrpc": "2.0", "id": 2, "error": { "code": -32602, "message": "AMP_NO_CHANNEL", "data": { "amp_pricing": { "mode": "per-call", "rate": "50000", "recipient": "" } } } } ``` ### Server SDK ```typescript import { AMPMcpServer } from "@valeo/amp-server" const server = new AMPMcpServer({ wallet: serverKeypair, connection, tools: { generate_image: { description: "Generate an image", pricing: { mode: "per-call", rate: "50000", token: "...", minDeposit: "1000000", settleInterval: 3600 }, inputSchema: { type: "object", properties: { prompt: { type: "string" } } }, handler: async (args, amp) => ({ url: "..." }), }, }, }) ``` ### Client SDK ```typescript import { AMPMcpClient } from "@valeo/amp-client" const client = new AMPMcpClient({ wallet, connection, budget: 5.0 }) await client.connect("http://localhost:3000/mcp") const result = await client.callTool("generate_image", { prompt: "cat" }) await client.close() ``` ## WebSocket Transport AMP credentials over WebSocket connections. *** For streaming, real-time data, and long-lived connections, AMP defines a WebSocket binding. ### Discovery The server sends pricing as the first text frame after the WebSocket handshake, using the same JSON schema as `/.well-known/amp.json`. ### Channel binding The client sends a channel binding message as the first text frame: ```json { "amp_channel": "", "amp_seq": 0, "amp_sig": "" } ``` The server acknowledges: ```json { "amp_status": "active", "amp_balance": "5000000" } ``` ### Subsequent messages #### Text mode Wrap each message with AMP fields: ```json { "amp_seq": 42, "amp_sig": "", "payload": { "query": "stream data" } } ``` #### Binary mode For high-frequency streams, use a binary prefix: ``` [2 bytes: amp_seq as u16 LE] [64 bytes: amp_sig as raw Ed25519 signature] [remaining bytes: application payload] ``` Binary mode avoids JSON parsing overhead for each frame. ### Metering The server meters WebSocket connections using `per-second` mode. The server tracks connection duration and settles at each interval: ``` amount = seconds_connected * rate ``` ### Next steps * [Accept streaming payments →](/guides/streaming) * [HTTP transport →](/protocol/transports/http) ## Channel State The ChannelState PDA account layout. *** Each channel is stored as a Program Derived Address (PDA) on Solana. The PDA holds all financial state for the channel. ### PDA derivation ``` Seeds: [b"amp-channel", funder_pubkey, recipient_pubkey, nonce_le_bytes] ``` The vault token account is a separate PDA: ``` Seeds: [b"amp-vault", channel_state_pubkey] ``` ### Account layout Total size: 329 bytes (+ 8 byte discriminator = 337 bytes) | Field | Type | Size | Description | | ------------------- | ---------------- | ---- | ------------------------------------ | | `bump` | u8 | 1 | PDA bump seed | | `funder` | Pubkey | 32 | Channel funder (payer / agent) | | `recipient` | Pubkey | 32 | Channel recipient (service provider) | | `mint` | Pubkey | 32 | SPL token mint (e.g. USDC) | | `vault` | Pubkey | 32 | Token account holding channel funds | | `balance` | u64 | 8 | Current available balance | | `total_deposited` | u64 | 8 | Lifetime deposits | | `total_consumed` | u64 | 8 | Lifetime settled amount | | `rate_limit` | u64 | 8 | Max tokens per settle interval | | `settle_interval` | i64 | 8 | Seconds between settlements | | `last_settle_ts` | i64 | 8 | Last settlement timestamp | | `nonce` | u64 | 8 | Channel nonce | | `status` | u8 | 1 | 0 = Active, 1 = Closed | | `created_at` | i64 | 8 | Creation timestamp | | `delegate` | `Option` | 33 | Optional delegate pubkey | | `delegate_limit` | u64 | 8 | Max delegate consumption | | `delegate_consumed` | u64 | 8 | Current delegate consumption | | `stratum_enabled` | bool | 1 | Opt into multi-channel netting | | `stratum_cycle` | i64 | 8 | Netting cycle interval | | `stratum_authority` | `Option` | 33 | Stratum engine pubkey | | `parent_channel` | `Option` | 33 | Upstream channel (for chaining) | | `child_channels` | u8 | 1 | Active downstream channels | | `max_chain_depth` | u8 | 1 | Max chain depth (default: 3) | | `chain_depth` | u8 | 1 | Current chain depth (0 = root) | ### Status values | Value | Meaning | | ----- | ----------------------------------------------- | | 0 | Active — channel is open and accepting requests | | 1 | Closed — channel is closed, accounts reclaimed | ### Querying channels Find channels by funder: ```typescript const channels = await program.account.channelState.all([ { memcmp: { offset: 9, bytes: funderPubkey.toBase58() } } ]) ``` Find channels by recipient: ```typescript const channels = await program.account.channelState.all([ { memcmp: { offset: 41, bytes: recipientPubkey.toBase58() } } ]) ``` ### Next steps * [Instructions →](/on-chain/instructions) * [Program →](/on-chain/program) ## On-Chain Overview The AMP Solana program and on-chain state. *** AMP's on-chain component is a Solana program (Anchor-compatible) that manages channel state, token custody, and settlement verification. All financial state is stored on-chain as PDAs. Off-chain components (SDKs, metering) interact with this program. ### Program The `amp-channel` program handles 6 instructions: open, top-up, settle, close, delegate, and chain. Deployed to Solana devnet. [Program details →](/on-chain/program) ### Channel State Each channel is a PDA with 24 fields tracking balances, settlement timing, delegation, and chain state. [Channel state layout →](/on-chain/channel-state) ### Instructions Six instructions covering the full channel lifecycle, plus delegation and chaining. [Instruction reference →](/on-chain/instructions) ### Architecture ``` Client SDK ←→ Server Middleware ←→ AMP Program ←→ SPL Token Vault │ │ │ off-chain off-chain on-chain (signing) (metering) (state + custody) ``` On-chain transactions only occur at channel open, settlement, and close. All consumption is off-chain. ## Instructions The six instructions of the amp-channel program. *** ### open\_channel Create a new channel between funder and recipient. **Arguments:** | Arg | Type | Description | | ----------------- | ---- | ------------------------------------- | | `deposit` | u64 | Initial deposit (token smallest unit) | | `rate_limit` | u64 | Max tokens per settle interval | | `settle_interval` | i64 | Seconds between settlements (min: 60) | | `nonce` | u64 | Channel nonce | **Accounts:** funder (signer), recipient, mint, channelState (init, PDA), vault (init, PDA), funderTokenAccount, systemProgram, tokenProgram ```typescript await program.methods .openChannel(new BN(1_000_000), new BN(500_000), new BN(3600), new BN(0)) .accounts({ funder: funder.publicKey, recipient: recipient.publicKey, mint, channelState: channelPDA, vault: vaultPDA, funderTokenAccount: funderAta, systemProgram: SystemProgram.programId, tokenProgram: TOKEN_PROGRAM_ID, }) .signers([funder]).rpc() ``` *** ### top\_up Add funds to an active channel. **Arguments:** `amount: u64` **Accounts:** funder (signer), channelState, vault, funderTokenAccount, tokenProgram ```typescript await program.methods.topUp(new BN(500_000)) .accounts({ funder: funder.publicKey, channelState: channelPDA, vault: vaultPDA, funderTokenAccount: funderAta, tokenProgram: TOKEN_PROGRAM_ID }) .signers([funder]).rpc() ``` *** ### settle Transfer owed funds from vault to recipient. **Arguments:** `amount: u64` **Constraints:** channel active, amount within balance and rate limit, settle interval elapsed. **Accounts:** authority (signer — recipient or stratum authority), channelState, vault, recipientTokenAccount, tokenProgram ```typescript await program.methods.settle(new BN(200_000)) .accounts({ authority: recipient.publicKey, channelState: channelPDA, vault: vaultPDA, recipientTokenAccount: recipientAta, tokenProgram: TOKEN_PROGRAM_ID }) .signers([recipient]).rpc() ``` *** ### close\_channel Close a channel with optional final settlement. Refunds remaining balance. **Arguments:** `final_settle_amount: u64` **Constraints:** channel active, no active child channels, closer is funder or recipient. **Accounts:** closer (signer), funder, recipient, channelState (closed), vault (closed), funderTokenAccount, recipientTokenAccount, tokenProgram, parentChannelState (optional) ```typescript await program.methods.closeChannel(new BN(100_000)) .accounts({ closer: funder.publicKey, funder: funder.publicKey, recipient: recipient.publicKey, channelState: channelPDA, vault: vaultPDA, funderTokenAccount: funderAta, recipientTokenAccount: recipientAta, tokenProgram: TOKEN_PROGRAM_ID, parentChannelState: null }) .signers([funder]).rpc() ``` *** ### set\_delegate Assign a delegate who can consume against the channel. **Arguments:** `delegate_pubkey: Pubkey`, `limit: u64` **Constraints:** channel active, funder is signer, limit within balance. ```typescript await program.methods.setDelegate(delegatePubkey, new BN(3_000_000)) .accounts({ funder: funder.publicKey, channelState: channelPDA }) .signers([funder]).rpc() ``` *** ### chain\_channel Create a downstream channel funded from an upstream channel's vault. **Arguments:** `amount: u64`, `rate_limit: u64`, `settle_interval: i64`, `nonce: u64` **Constraints:** upstream active, signer is upstream recipient, chain depth within limit. **Accounts:** upstreamRecipient (signer), upstreamChannel, upstreamVault, downstreamRecipient, mint, downstreamChannel (init), downstreamVault (init), systemProgram, tokenProgram ```typescript await program.methods .chainChannel(new BN(3_000_000), new BN(3_000_000), new BN(60), new BN(nonce)) .accounts({ upstreamRecipient: serviceB.publicKey, upstreamChannel: upstreamPDA, upstreamVault: upstreamVaultPDA, downstreamRecipient: serviceC.publicKey, mint, downstreamChannel: downstreamPDA, downstreamVault: downstreamVaultPDA, systemProgram: SystemProgram.programId, tokenProgram: TOKEN_PROGRAM_ID, }) .signers([serviceB]).rpc() ``` ## Program The amp-channel Solana program. *** ### Deployment | Field | Value | | ---------- | ------------------------------------------------------------------------------------------------------------------------------------------------- | | Program ID | [`2KQCaQ9j8YtewZ4QjmDfnsVANZXLBcPSYFhAj2eUNaPP`](https://explorer.solana.com/address/2KQCaQ9j8YtewZ4QjmDfnsVANZXLBcPSYFhAj2eUNaPP?cluster=devnet) | | Network | Solana Devnet | | Framework | Anchor 0.30.1 | | Owner | `BPFLoaderUpgradeab1e11111111111111111111111` | | Source | [github.com/valeo-cash/amp/programs/amp-channel](https://github.com/valeo-cash/amp/tree/main/programs/amp-channel) | ### Instructions The program implements 6 instructions: | Instruction | Description | | --------------- | -------------------------------------------------- | | `open_channel` | Create a new channel and deposit tokens | | `top_up` | Add funds to an existing channel | | `settle` | Transfer owed funds to the recipient | | `close_channel` | Final settlement + refund + close accounts | | `set_delegate` | Assign consumption rights to a delegate | | `chain_channel` | Create a downstream channel from an upstream vault | [Full instruction reference →](/on-chain/instructions) ### Accounts | Account | Description | | ------------------ | ----------------------------------------- | | ChannelState (PDA) | Persistent financial state for a channel | | Vault (PDA) | SPL token account holding deposited funds | [Channel state layout →](/on-chain/channel-state) ### Future programs The AMP ecosystem spec defines two additional programs (not yet deployed): | Program | Description | Status | | ---------------- | --------------------------------------- | ------------- | | `amp-registry` | On-chain service directory | Spec complete | | `amp-reputation` | Reputation scoring from channel history | Spec complete | ## Build with an LLM Use your coding agent to add AMP payments in seconds. *** Paste one of these prompts into your coding agent (Cursor, Windsurf, Claude, etc.) to add AMP payments to your project. ### Add payments to your API ``` Reference https://amp.valeoprotocol.io/quickstart/server Add AMP payments to my Express server. Charge $0.01 per call in USDC on Solana. ``` ### Build an agent that pays for APIs ``` Reference https://amp.valeoprotocol.io/quickstart/agent Add AMP payment support to my agent. Budget $10 USDC. Use amp.fetch() as a drop-in replacement for fetch(). ``` ### Add per-tool pricing to an MCP server ``` Reference https://amp.valeoprotocol.io/protocol/transports/mcp Add AMP per-tool pricing to my MCP server. generate_image costs $0.05/call, search_web costs $0.005/call. ``` ### Key resources for your LLM * [Full protocol spec](https://github.com/valeo-cash/amp/blob/main/SPEC.md) — the complete technical reference * [Server quickstart](/quickstart/server) — Express middleware setup * [Agent quickstart](/quickstart/agent) — AMPClient usage * [TypeScript SDK](/sdk/typescript) — API surface and types * [On-chain instructions](/on-chain/instructions) — Anchor program interface ## Channel Chaining Create agent supply chains where services pay sub-services from the same budget. *** Channel chaining enables value flow through a chain of services. When Service B needs to call Service C to fulfill a request, Service B can create a downstream channel funded from the upstream channel — no separate capital required. ### The problem ``` Agent A ──$10──→ Service B (inference API) ├── needs Service C (GPU) — costs $3 └── needs Service D (data) — costs $1 Without chaining: Service B needs its own $4 to pay C and D With chaining: Service B forwards from Agent A's channel ``` ### How it works 1. Agent A opens a channel with Service B ($10 deposit) 2. Service B creates a downstream channel to Service C ($3 from upstream) 3. Service B creates another downstream channel to Service D ($1 from upstream) 4. Agent A's channel now has $6 available for Service B, $3 allocated to C, $1 to D 5. When downstream channels close, remaining funds return to Service B's balance ### Creating a downstream channel ```typescript await program.methods .chainChannel( new BN(3_000_000), // $3 from upstream new BN(3_000_000), // rate limit new BN(60), // settle interval new BN(Date.now()) // nonce ) .accounts({ upstreamRecipient: serviceB.publicKey, upstreamChannel: upstreamPDA, upstreamVault: upstreamVaultPDA, downstreamRecipient: serviceC.publicKey, mint, downstreamChannel: downstreamPDA, downstreamVault: downstreamVaultPDA, systemProgram: SystemProgram.programId, tokenProgram: TOKEN_PROGRAM_ID, }) .signers([serviceB]) .rpc() ``` ### Chain depth limits Chains have a maximum depth (default: 3) to prevent infinite chains: * Depth 0: Agent → Service (root channel) * Depth 1: Service → Sub-service * Depth 2: Sub-service → Sub-sub-service The root channel funder sets `max_chain_depth` at channel open. ### Closing chained channels Closing works bottom-up: 1. Leaf channels (no children) must close first 2. Remaining balance from a downstream channel returns to the upstream vault 3. Once all children are closed, the parent can close ### Next steps * [On-chain instructions →](/on-chain/instructions) * [Multi-channel netting →](/ecosystem/stratum) ## Accept Delegated Payments Let agents delegate channel budget to sub-agents. *** Delegation lets an agent assign a portion of its channel budget to another keypair. The delegate can consume services against the channel up to a specified limit — no separate capital required. ### How it works 1. Agent A opens a channel with $10 2. Agent A delegates $3 to Agent B's pubkey 3. Agent B makes requests using Agent A's channel, signing with its own key 4. The server verifies Agent B's signature against the channel's `delegate` field ### Setting a delegate ```typescript import { Keypair } from "@solana/web3.js" // Agent A's channel is already open const subAgentKeypair = Keypair.generate() await program.methods .setDelegate(subAgentKeypair.publicKey, new BN(3_000_000)) // $3 limit .accounts({ funder: agentA.publicKey, channelState: channelPDA, }) .signers([agentA]) .rpc() ``` ### Sub-agent consumption The sub-agent signs requests with its own keypair. The server checks the `delegate` field of the on-chain ChannelState: ```typescript // Sub-agent signs seq numbers with its own key const seqBuf = Buffer.alloc(8) seqBuf.writeBigUInt64LE(BigInt(seq)) const sig = nacl.sign.detached(seqBuf, subAgentKeypair.secretKey) // Send request with the same channel PDA fetch("https://api.example.com/v1/data", { headers: { "AMP-Channel": channelPDA.toBase58(), "AMP-Seq": seq.toString(), "AMP-Sig": bs58.encode(sig), }, }) ``` ### Server-side validation The server middleware automatically checks both the funder and delegate pubkeys when verifying signatures. No additional server configuration is needed. ### On-chain state The ChannelState PDA stores: | Field | Description | | ------------------- | --------------------------------------- | | `delegate` | The delegate's pubkey (or null) | | `delegate_limit` | Maximum amount the delegate can consume | | `delegate_consumed` | Amount the delegate has consumed so far | ### Next steps * [Channel chaining →](/guides/chaining) * [Delegation protocol details →](/protocol/delegation) ## Accept Per-Call Payments Charge a fixed amount for each API request. *** Per-call metering is the simplest mode. Every request costs the same fixed amount, regardless of payload size or processing time. ### Server setup ```typescript import { AMP } from "@valeo/amp-server" const amp = new AMP({ wallet: serverKeypair, connection, pricing: { mode: "per-call", rate: "10000", // $0.01 per call (USDC, 6 decimals) token: "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", minDeposit: "1000000", settleInterval: 3600, }, }) app.use("/v1", amp.middleware()) app.get("/v1/data", (req, res) => { res.json({ result: "paid content", balance: req.amp?.balance?.toString() }) }) ``` ### How metering works 1. Each request that passes validation increments a counter. 2. The meter tracks: call count, first/last sequence numbers, period timestamps. 3. At the settlement interval, the server submits one on-chain transaction: `amount = callCount * rate` ### Rate string shorthand The rate string `"0.001/call"` is syntactic sugar: | String | Mode | Rate (smallest unit) | | -------------- | -------- | -------------------- | | `"0.001/call"` | per-call | 1000 | | `"0.01/call"` | per-call | 10000 | | `"1.00/call"` | per-call | 1000000 | ### Per-route pricing Different routes can have different rates: ```typescript const amp = new AMP({ wallet: serverKeypair, connection, pricing: { mode: "per-call", rate: "1000", token: "...", minDeposit: "1000000", settleInterval: 3600 }, routes: { "/v1/inference": { mode: "per-call", rate: "100000" }, // $0.10 "/v1/search": { mode: "per-call", rate: "5000" }, // $0.005 }, }) ``` ### Next steps * [Accept streaming payments →](/guides/streaming) * [Settlement details →](/protocol/settlement) ## Accept Streaming Payments Charge for time-based services with per-second metering. *** For streaming connections, long-running tasks, or any service where cost scales with time, use `per-second` metering. The server accumulates usage continuously and settles the total at each interval. ### Server setup ```typescript import { AMP } from "@valeo/amp-server" const amp = new AMP({ wallet: serverKeypair, connection, pricing: { mode: "per-second", rate: "1000", // $0.001 per second token: "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", minDeposit: "5000000", settleInterval: 3600, }, }) app.get("/v1/stream", amp.middleware(), (req, res) => { res.setHeader("Content-Type", "text/event-stream") res.setHeader("Connection", "keep-alive") const interval = setInterval(() => { res.write(`data: ${JSON.stringify({ tick: Date.now() })}\n\n`) }, 1000) req.on("close", () => clearInterval(interval)) }) ``` ### Metering modes | Mode | Unit | Use case | | ------------- | ------------------------ | ------------------------ | | `per-call` | Fixed per request | Standard APIs | | `per-second` | Per second of connection | Streaming, WebSocket | | `per-byte` | Per byte transferred | Data-heavy endpoints | | `per-compute` | Per compute unit | Variable-cost operations | ### Per-byte example ```typescript const amp = new AMP({ // ... pricing: { mode: "per-byte", rate: "1", // $0.000001 per byte token: "...", minDeposit: "1000000", settleInterval: 3600, }, }) ``` ### Next steps * [Metering protocol details →](/protocol/metering) * [Transport bindings for WebSocket →](/protocol/transports/websocket) ## Service Registry On-chain directory for discovering AMP-enabled services. *** :::info **Status:** Specification complete, program pending deployment. ::: The AMP Service Registry is a Solana program that maintains a directory of AMP-enabled services. Any service provider can register. Agents query the registry to discover services by category, price, and reputation. ### How it works 1. Service provider calls `register_service` with their endpoint, pricing, and description 2. The registry creates a `ServiceEntry` PDA for the provider 3. Agents query the registry using `getProgramAccounts` with filters 4. When channels are opened/settled, the registry statistics update via CPI ### ServiceEntry account | Field | Type | Description | | ------------------ | ------ | ------------------------------------ | | `provider` | Pubkey | Service provider's pubkey | | `endpoint` | String | Service URL or MCP connection string | | `category` | u8 | Service category (0-7) | | `rate` | u64 | Cost per unit | | `token` | Pubkey | Accepted SPL token mint | | `min_deposit` | u64 | Minimum channel deposit | | `reputation_score` | u64 | From on-chain reputation system | | `total_channels` | u64 | Lifetime channels opened | | `total_settled` | u64 | Lifetime volume settled | | `active_channels` | u32 | Currently active channels | | `active` | bool | Whether service is available | ### Categories | Value | Category | | ----- | -------------------------------------- | | 0 | Inference (LLM, image gen, embeddings) | | 1 | Data (search, scraping, enrichment) | | 2 | Compute (GPU, serverless, batch) | | 3 | Storage (IPFS, Arweave, S3) | | 4 | Communication (email, SMS) | | 5 | Financial (pricing, market data) | | 6 | Identity (KYC, verification) | | 7 | Other | ### Querying (future SDK) ```typescript import { AMPRegistry } from "@valeo/amp-client" const services = await registry.search({ category: "inference", maxRate: "10000", minReputation: 5000, sortBy: "reputation", }) ``` ## On-Chain Reputation Deterministic trust scores derived from channel history. *** :::info **Status:** Specification complete, program pending deployment. ::: AMP includes a reputation system derived from channel history. Scores are computed on-chain from verifiable settlement data — no external oracle, no subjective rating. ### Score calculation ``` base_score = (channels_completed / channels_opened) * 5000 volume_bonus = min(log2(total_volume / 1_000_000) * 500, 2500) streak_bonus = min(current_streak * 50, 1500) dispute_penalty = dispute_count * 500 score = clamp(base_score + volume_bonus + streak_bonus - dispute_penalty, 0, 10000) ``` Score range: **0 to 10,000**. Higher is better. ### ReputationAccount | Field | Type | Description | | -------------------------- | ------ | ----------------------------- | | `entity` | Pubkey | Agent or service being scored | | `total_channels_opened` | u64 | Lifetime channels | | `total_channels_completed` | u64 | Clean closes | | `total_volume` | u64 | Lifetime settlement volume | | `total_settlements` | u64 | Successful settlements | | `dispute_count` | u64 | Disputed closes | | `current_streak` | u64 | Consecutive clean settlements | | `score` | u64 | Computed score (0-10000) | ### Reputation-based pricing Services can offer tiered pricing based on agent reputation: ```json { "pricing": { "default": { "mode": "per-call", "rate": "10000" }, "reputation_tiers": [ { "min_score": 5000, "rate": "8000" }, { "min_score": 8000, "rate": "5000" }, { "min_score": 9500, "rate": "3000" } ] } } ``` Higher reputation = lower prices. Incentivizes good behavior. ### How scores update | Event | Effect | | ------------------------- | -------------------------------------------- | | `open_channel` | Increment `total_channels_opened` | | `settle` | Increment `total_settlements`, extend streak | | `close_channel` (clean) | Increment `total_channels_completed` | | `close_channel` (dispute) | Increment `dispute_count`, reset streak | ## Multi-Channel Netting (Stratum) Batch settlements across all channels in one cycle. *** :::info **Status:** Specification complete, integration pending. ::: AMP channels can opt into multilateral netting through Valeo Stratum. Instead of each channel settling independently, Stratum aggregates all settlements in a netting cycle and produces the minimum set of on-chain transfers. ### Why netting matters Without netting (3 channels, 3 transactions): * Agent A owes Service B: $5.00 * Agent A owes Service C: $3.00 * Service C owes Agent A: $2.00 With netting (2 transactions): * Agent A owes Service B: $5.00 * Agent A owes Service C: $1.00 ($3 - $2) At scale (100 agents, 100 services), netting reduces on-chain transactions by 60-80%. ### Enabling netting When opening a channel, set `stratum_enabled`: ```typescript const channel = await amp.openChannel({ to: serviceB.pubkey, deposit: 10.00, stratumEnabled: true, stratumCycle: 3600, }) ``` ### How it works 1. Metering proofs are submitted to the Stratum netting engine (off-chain) 2. At each cycle, Stratum calculates net obligations across all opted-in channels 3. Stratum submits the minimum set of `settle` instructions 4. All channels are updated in one cycle ### Channel state fields | Field | Description | | ------------------- | ------------------------------------------------- | | `stratum_enabled` | Whether channel opts into netting | | `stratum_cycle` | Netting interval in seconds | | `stratum_authority` | Stratum engine pubkey (authorized to call settle) | The `stratum_authority` can call `settle` on behalf of the recipient, but cannot close channels or modify any other state. ### Fallback If Stratum is unavailable, channels fall back to normal per-channel settlement by the recipient. Netting is an optimization, not a requirement.