# Multi-Agent Setup Source: https://developers.fd.xyz/agent-wallet/advanced/multi-agent Deploy and manage multiple agent wallets across teams and use cases. # Multi-Agent Setup When you move beyond a single agent, each agent should have its own wallet. This guide covers architecture patterns, budget management, and operational practices for multi-agent deployments. ## Why Separate Wallets? Each agent gets its own wallet for four reasons: 1. **Isolation** — a compromised or misbehaving agent can only affect its own wallet, not others 2. **Clear audit trail** — every transaction is attributed to a specific agent's wallet address 3. **Individual budgets** — each agent gets funded according to its role and risk profile 4. **Independent control** — you can pause, refund, or shut down one agent without affecting the rest Shared wallets make it impossible to attribute spending, enforce per-agent budgets, or contain damage from a single agent's failure. ## Architecture Patterns ### Independent Agents Each agent has its own wallet and operates on its own tasks. No coordination between agents. ``` Agent Alpha (research) → Wallet A ($50) Agent Beta (trading) → Wallet B ($500) Agent Gamma (payments) → Wallet C ($200) ``` **Best for:** Different agents doing different things with different risk profiles. ### Team of Agents Multiple agents serve one workflow, each handling a specific role. Each has its own wallet scoped to its function. ``` Analyst Agent → Wallet (read-only balance checks) Executor Agent → Wallet ($1,000 for swaps and transfers) Reporter Agent → Wallet (no funds needed — read-only) ``` **Best for:** Complex workflows where different agents need different financial capabilities. ### Scaled Fleet Many identical agents doing the same task in parallel, each with their own wallet. ``` Data Agent 1 → Wallet ($50) Data Agent 2 → Wallet ($50) Data Agent 3 → Wallet ($50) ... Data Agent N → Wallet ($50) ``` **Best for:** Parallel processing where each agent buys data, pays for API access, or executes trades independently. ## Setting Up Multiple Wallets Each wallet is tied to a District Pass. For multi-agent setups, each agent connects with its own credentials. ### MCP Configuration Each agent gets its own MCP connection, authenticated with its own District Pass: ```json theme={null} // Agent Alpha { "mcpServers": { "finance-district": { "type": "streamable-http", "url": "https://mcp.fd.xyz" } } } ``` Each agent authenticates independently via OAuth PKCE. The wallet associated with each District Pass is isolated and independent. ### FDX CLI Configuration For agents using the [FDX CLI](/agent-wallet/ai-integration/cli), each authenticates separately: ```bash theme={null} # Agent Alpha's environment fdx setup # Authenticates with Agent Alpha's District Pass # Agent Beta's environment fdx setup # Authenticates with Agent Beta's District Pass ``` ## Budget Management Apply the [pocket money philosophy](/agent-wallet/concepts/trust) per agent: | Agent Role | Suggested Funding | Refill Strategy | | ---------------------- | ----------------- | -------------------- | | Research / data buying | \$10–50 | Per session | | API access payment | \$50–200 | Weekly | | Active trading | \$500–2,000 | Based on performance | | Payment processing | Task-appropriate | Per batch | **Fund individually.** Each agent gets only what it needs. A research agent doesn't need the same budget as a trading agent. **Refill rather than pre-fund.** Keep balances low and refill when needed. Each refill is a checkpoint to review the agent's activity. **Monitor on-chain.** Check each agent's wallet address on a block explorer to review spending patterns and verify the agent is operating as expected. ## Monitoring With each agent having its own wallet address, monitoring is straightforward: * **Per-agent activity** — look up each wallet address on the relevant block explorer * **Balance tracking** — check balances via MCP, CLI, or block explorer * **Spending patterns** — review transaction history to verify agents are operating within expectations For fleet deployments, you can script balance checks across all agent wallets: ```bash theme={null} #!/bin/bash AGENTS=("agent-alpha" "agent-beta" "agent-gamma") for agent in "${AGENTS[@]}"; do echo "=== $agent ===" fdx call getWalletOverview --chainKey ethereum done ``` ## Scaling Considerations **Start small.** Deploy one or two agents, validate the workflow, then scale. **Identical configurations.** For fleet patterns, use the same agent code with different wallet credentials. The only difference between agents should be their District Pass. **Graceful shutdown.** Before decommissioning an agent, withdraw its remaining funds. The wallet and its on-chain history remain accessible even after the agent stops operating. # Smart Account Source: https://developers.fd.xyz/agent-wallet/advanced/smart-account Account Abstraction features — programmable rules, batch transactions, and gas abstraction. # Smart Account Smart Accounts extend your EOA wallet with ERC-4337 Account Abstraction capabilities — batch transactions, gas abstraction, session keys, and programmable rules. Built on Alchemy's audited [LightAccount](https://github.com/alchemyplatform/light-account) contract. Smart Accounts are currently available to **select pilot users** only. This page documents the architecture and capabilities for early access users. [Contact us](/overview/resources/support) if you're interested in pilot access. For an overview of how Smart Accounts fit into the wallet architecture, see [Wallet Architecture](/agent-wallet/wallet-architecture). This page goes deeper into the specific features. ## How It Works When you deploy a Smart Account: 1. Your **EOA wallet stays unchanged** — same address, same balances, same functionality 2. A **LightAccount smart contract** is deployed on your chosen chain 3. Your **EOA becomes the owner** of that contract 4. You now have **two accounts** — the original EOA and the new Smart Account 5. The Smart Account has its own address and can hold its own assets The Smart Account is a contract account — its behavior is defined by code rather than a single private key. This is what enables batch transactions, gas abstraction, and session keys. ## Batch Transactions Execute multiple operations in a single atomic transaction. All succeed or all revert — no partial execution. **Why this matters for agents:** * Swap USDC to ETH **and** transfer ETH to a recipient in one transaction * Approve a token **and** deposit into a vault atomically * Reduce gas costs by combining operations * Eliminate the risk of one step succeeding while another fails **Example use case:** An agent performing a DeFi strategy that requires approve → swap → deposit. Without batching, a failure at the deposit step leaves the agent with swapped tokens in an unintended state. With batching, the entire sequence either completes or reverts cleanly. ## Gas Abstraction With a standard EOA, your agent needs native tokens (ETH, MATIC, etc.) on every chain just to pay for gas — even if your agent only works with stablecoins. Gas abstraction eliminates this friction. **What gas abstraction enables:** * **Pay gas in stablecoins** — use USDC for gas instead of managing ETH balances * **Sponsor gas** — a paymaster covers gas fees on behalf of your agent * **Eliminate gas management** — your agent doesn't need to maintain native token balances across chains This is particularly useful for agent deployments where funding and monitoring native token balances on multiple chains adds operational complexity. ## Session Keys Session keys provide **time-limited, scoped access** to Smart Account operations. Instead of granting full wallet control, you create a temporary key that can only perform specific operations within a specific time window. **Parameters you can set:** * **Duration** — how long the session key is valid (hours, days) * **Allowed operations** — which wallet operations the key can perform (transfers only, swaps only, etc.) * **Spending limits** — maximum amount per transaction or per session * **Token restrictions** — which tokens the key can interact with **Use case:** Grant a specific agent 24-hour access to swap up to 100 USDC, with no ability to transfer funds out. When the session expires, access is automatically revoked. ## Programmable Rules Define on-chain rules that constrain what the Smart Account can do: * **Maximum transaction value** — cap the amount per operation * **Allowlisted addresses** — restrict which addresses the account can send to * **Token restrictions** — limit which tokens the account can interact with * **Time-based constraints** — only allow operations during specific time windows These rules are enforced by the smart contract itself — they can't be bypassed by the agent or by any API call. They're on-chain guarantees. ## Deploying a Smart Account Deployment is self-service through the MCP Server, AI Assistant, or Web App: 1. **Choose a chain** — select which EVM chain to deploy on (e.g., Base, Ethereum, Polygon) 2. **Ensure gas availability** — your EOA must hold enough native tokens on that chain to cover deployment gas 3. **Deploy** — trigger deployment through your preferred interface 4. **Fund the Smart Account** — transfer assets from your EOA or external sources Smart Accounts are **EVM only** — Solana is not supported. You can deploy to one or more EVM chains, choosing based on where you need Smart Account features. After deployment, both your EOA and Smart Account continue to function. Use the EOA for simple operations and the Smart Account when you need batch transactions, gas abstraction, or session keys. ## When to Use a Smart Account | Scenario | EOA Sufficient? | Smart Account Needed? | | --------------------------------- | --------------- | --------------------- | | Simple transfers and swaps | ✓ | Not needed | | Agent holding and spending tokens | ✓ | Not needed | | Multi-step atomic operations | ✗ | ✓ Batch transactions | | Agent without native gas tokens | ✗ | ✓ Gas abstraction | | Time-limited agent access | ✗ | ✓ Session keys | | On-chain spending rules | ✗ | ✓ Programmable rules | **Rule of thumb:** if your agent does straightforward transfers, swaps, and payments, an EOA is all you need. Consider a Smart Account when you need atomicity, gas flexibility, or scoped access. # Agent Frameworks Source: https://developers.fd.xyz/agent-wallet/ai-integration/agent-frameworks Integrate Agent Wallet with LangChain, CrewAI, AutoGen, and other agent frameworks. # Agent Frameworks Agent Wallet integrates with popular AI agent frameworks through two approaches: **MCP-native** (if the framework supports MCP) or **FDX CLI wrapping** (the framework calls [FDX](/agent-wallet/ai-integration/cli) as a shell tool). ## Integration Approaches | Framework | MCP Support | Recommended Approach | | --------------- | ------------------------------ | -------------------- | | LangChain | Via MCP adapter | MCP native | | CrewAI | Via MCP adapter or shell tools | FDX CLI wrapping | | AutoGen | Via MCP adapter | MCP native | | Semantic Kernel | Via adapter | MCP native | | Custom | Depends on implementation | MCP SDK or FDX CLI | **MCP-native** is preferred when available — it gives the agent direct access to wallet tools through the standard MCP tool-calling interface. **FDX CLI wrapping** works with any framework that can execute shell commands, which covers essentially every agent framework. ## LangChain ### MCP Integration (Recommended) ```python theme={null} from langchain_mcp_adapters.client import MultiServerMCPClient from langgraph.prebuilt import create_react_agent from langchain_anthropic import ChatAnthropic # Connect to Agent Wallet MCP server async with MultiServerMCPClient( { "finance-district": { "url": "https://mcp.fd.xyz", "transport": "streamable-http", } } ) as client: tools = client.get_tools() agent = create_react_agent( ChatAnthropic(model="claude-sonnet-4-20250514"), tools ) result = await agent.ainvoke( {"messages": [{"role": "user", "content": "Check my wallet balance"}]} ) ``` ### FDX CLI Tool Wrapper ```python theme={null} import subprocess import json from langchain.tools import tool @tool def wallet(method: str) -> str: """Execute an FDX wallet command. Pass an MCP method name and arguments. Example: 'getWalletOverview --chainKey ethereum' Run 'fdx call' for available methods.""" result = subprocess.run( ["fdx", "call"] + method.split(), capture_output=True, text=True ) return result.stdout # Use in an agent agent = create_react_agent(llm, [wallet]) ``` ## CrewAI ```python theme={null} from crewai import Agent, Task, Crew from crewai.tools import tool import subprocess import json @tool("Agent Wallet") def wallet(method: str) -> str: """Manage crypto wallet via FDX. Pass an MCP method name and arguments. Example: 'getWalletOverview --chainKey ethereum' or 'transferTokens --chainKey ethereum --recipientAddress 0x... --amount 5'""" result = subprocess.run( ["fdx", "call"] + method.split(), capture_output=True, text=True ) return result.stdout financial_agent = Agent( role="Financial Agent", goal="Manage wallet operations and execute transactions", tools=[wallet], verbose=True ) task = Task( description="Check the wallet balance and swap 10 USDC for ETH if balance allows", agent=financial_agent ) crew = Crew(agents=[financial_agent], tasks=[task]) result = crew.kickoff() ``` ## AutoGen ```python theme={null} from autogen import ConversableAgent import subprocess import json def wallet_command(method: str) -> str: result = subprocess.run( ["fdx", "call"] + method.split(), capture_output=True, text=True ) return result.stdout assistant = ConversableAgent( name="financial_assistant", llm_config={"model": "claude-sonnet-4-20250514"}, ) # Register wallet as a callable function assistant.register_for_llm( name="wallet", description="Execute FDX wallet commands. Pass MCP method name and arguments." )(wallet_command) ``` ## Custom MCP Client For frameworks with direct MCP support or when building your own agent: ```typescript theme={null} import { Client } from "@modelcontextprotocol/sdk/client/index.js"; import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js"; async function createWalletClient() { const transport = new StreamableHTTPClientTransport( new URL("https://mcp.fd.xyz"), ); const client = new Client({ name: "my-agent", version: "1.0.0" }); await client.connect(transport); return client; } // Use in your agent logic const client = await createWalletClient(); // Check balance const balance = await client.callTool({ name: "check_balance", arguments: {}, }); // Transfer const transfer = await client.callTool({ name: "transfer", arguments: { to: "0x1234...5678", amount: "5", token: "USDC", }, }); ``` See the [MCP SDK documentation](https://modelcontextprotocol.io/docs) for full client implementation details. ## Best Practices **Use MCP when possible.** The MCP approach gives your agent richer context — tool descriptions, parameter schemas, and error details that help the LLM make better decisions. **FDX output is always JSON.** All `fdx call` output is structured JSON, making it straightforward for agents to parse. **Write clear tool descriptions.** The LLM uses your tool description to decide when and how to call the tool. Be specific about what each command does. **Test on testnet first.** Use the [Testnet Faucet](/overview/developer-tools/faucet) to get test tokens. Validate your agent's behavior before connecting a funded wallet. **Handle errors gracefully.** Agents should check for insufficient balance, failed transactions, and network errors — and communicate these clearly rather than retrying blindly. # Agent Skills Source: https://developers.fd.xyz/agent-wallet/ai-integration/agent-skills Install Agent Wallet skills to give AI agents crypto wallet capabilities. # Agent Skills [Agent Skills](https://agentskills.io/) are a standardized way to describe what an AI agent or service can do. Instead of hardcoding tool configurations, agents and platforms can discover capabilities dynamically — finding and using wallet operations on the fly. Finance District publishes Agent Wallet skills following the [Agent Skills specification](https://agentskills.io/specification). These skills enable AI agents to authenticate, manage wallets, send tokens, swap via DEXs, earn DeFi yield, and more using the [FDX CLI](/agent-wallet/ai-integration/cli). ## Available Skills | Skill | Description | | -------------------- | ------------------------------------------------------------------- | | **authenticate** | Sign in to the wallet via OAuth 2.1 (browser or device flow) | | **wallet-overview** | Check balances, holdings, and transaction history across all chains | | **send-tokens** | Transfer tokens to any address on any EVM chain or Solana | | **swap-tokens** | Swap tokens via decentralized exchanges on any supported chain | | **fund-wallet** | Add funds via web onramp or direct transfer to wallet address | | **yield-strategies** | Discover, deposit into, and withdraw from DeFi yield strategies | | **smart-accounts** | Deploy and manage multi-signature smart accounts | | **pay-for-service** | Access paid API endpoints via the x402 payment protocol | | **help-and-support** | Get help, onboarding guidance, and report issues | ## Installation ### Prerequisites Install the [FDX CLI](/agent-wallet/ai-integration/cli) globally: ```bash theme={null} npm install -g @financedistrict/fdx ``` Once installed, the `fdx` command is available directly — no `npx` needed. ### Install Skills Install all Agent Wallet skills with [Vercel's Skills CLI](https://skills.sh/): ```bash theme={null} npx skills add financedistrict-platform/fd-agent-wallet-skills ``` This downloads all skills from the repository and makes them available to your agent. Skills are automatically detected when relevant tasks are given to the agent. You can also install individual skills by specifying the skill name: `bash npx skills add financedistrict-platform/fd-agent-wallet-skills/wallet-overview npx skills add financedistrict-platform/fd-agent-wallet-skills/send-tokens npx skills add financedistrict-platform/fd-agent-wallet-skills/swap-tokens ` ## Usage Skills are automatically available once installed. The agent will use them when relevant tasks are detected. Try these prompts: > "Sign in to my wallet" > "Show my balance on Ethereum" > "Send 10 USDC to 0x1234...abcd on Base" > "Swap 5 USDC for ETH on Arbitrum" > "Find yield strategies for my USDC" > "Pay for the API at [https://api.example.com/data](https://api.example.com/data)" > "Deploy a new smart account on Base" ## Key Differentiators What sets Agent Wallet skills apart from other crypto agent integrations: * **Multi-chain** — Supports all EVM chains and Solana. Not locked to a single chain. * **DEX swaps** — Token swaps execute through decentralized exchanges, not centralized order books. * **DeFi yield** — Discover and invest in yield strategies across protocols like Aave, Compound, and more. * **Smart accounts** — Deploy and manage multi-sig wallets on-chain (ERC-4337). * **Multi-asset x402** — Pay for services with any supported asset on any chain. ## Discovery via MCP In addition to the Skills CLI, agents can discover wallet capabilities through MCP directly. When an MCP client connects to `https://mcp.fd.xyz`, it automatically discovers all available tools through MCP's standard `listTools()` method: ```typescript theme={null} const { tools } = await client.listTools(); // Returns all available wallet operations with schemas ``` This is the primary discovery path for MCP-native clients — your agent connects and immediately knows what it can do. The Agent Skills approach is complementary, serving agents and platforms that use the [Agent Skills specification](https://agentskills.io/specification) rather than MCP. ## Building Composable Agents Agent Skills enable composability — combining wallet capabilities with other agent skills to create complex workflows: **Example: E-commerce agent** * Discovers wallet skills (payment capability) * Discovers product search skills (finding items) * Discovers shipping skills (logistics) * Composes them into an end-to-end purchasing workflow **Example: Research agent** * Discovers wallet skills (payment for premium APIs) * Discovers data analysis skills (processing) * Automatically pays for data sources via x402 as part of research tasks The agent doesn't need to know about Agent Wallet in advance — it discovers payment capability when it needs it and integrates it into its workflow. ## Contributing To add a new skill to the repository: 1. Create a folder in `./skills/` with a lowercase, hyphenated name 2. Add a `SKILL.md` file with YAML frontmatter and instructions 3. Follow the [Agent Skills specification](https://agentskills.io/specification) for the complete format See the [skills repository](https://github.com/financedistrict-platform/fd-agent-wallet-skills) for the full source. # AI Assistant Source: https://developers.fd.xyz/agent-wallet/ai-integration/ai-assistant Finance District's web-based conversational interface for wallet management. # AI Assistant The AI Assistant is Finance District's conversational web interface for managing your wallet. Log in, chat in natural language, and your wallet does what you ask — no MCP configuration, no CLI installation, no technical setup. ## Getting Started 1. Go to [fd.xyz](https://fd.xyz) and log in with your District Pass 2. Navigate to the AI Assistant 3. Start chatting That's it. The AI Assistant connects to the same wallet and capabilities as MCP and CLI — just through a chat interface. ## What You Can Do The AI Assistant supports the same wallet capabilities available through MCP and CLI: **Check your wallet:** > "What's my balance?" > "What's my wallet address on Base?" > "Show my recent transactions" **Transfer tokens:** > "Send 5 USDC to 0x1234...5678" > "Transfer 0.01 ETH to alice.eth on Ethereum" **Swap tokens:** > "Swap 10 USDC for ETH" > "How much ETH would I get for 50 USDC?" **Explore yield:** > "What yield opportunities are available for USDC?" > "Put 100 USDC into the best yield option" **Pay merchants:** > "Pay 2 USDC to merchant 0xABC...123" The assistant understands context and can handle multi-step requests: > "Check if I have at least 20 USDC, and if so, swap half of it for ETH" ## Who It's For The AI Assistant serves two audiences: **Developers exploring Agent Wallet** — use it to test wallet capabilities before integrating via MCP or CLI. It's the fastest way to see what the wallet can do without writing configuration. **Non-technical users** — manage your wallet through conversation without needing to understand MCP, command lines, or blockchain details. ## AI Assistant vs. MCP vs. CLI | Feature | AI Assistant | MCP Server | CLI | | -------------- | ---------------------------------------------- | ------------------------- | --------------------------------------------- | | Setup required | None — just log in | MCP client config | npm install + auth | | Primary user | Humans | AI agents | Developers, automated agents | | Automation | No — interactive only | Yes | Yes | | Programmable | No | Yes — tool calling | Yes — scriptable | | Best for | Exploration, simple tasks, non-technical users | Primary agent integration | Scripts, agent frameworks, terminal workflows | ## Limitations The AI Assistant is an interactive web interface, not a programmable endpoint: * **Not automatable** — you can't script it or call it from code * **Not for agent integration** — use [MCP Server](/agent-wallet/ai-integration/mcp-server) or [CLI](/agent-wallet/ai-integration/cli) for that * **Session-based** — requires you to be logged in and chatting actively For autonomous agent operations, the MCP Server and CLI are the right tools. # FDX CLI Source: https://developers.fd.xyz/agent-wallet/ai-integration/cli Command-line interface for autonomous AI agents to operate wallets. # FDX — Agent Wallet CLI FDX is a command-line interface to the Finance District MCP wallet server. It gives AI agents crypto wallet capabilities — hold, send, swap, and earn yield on assets across multiple chains — without managing private keys. FDX is designed for AI agents and agent frameworks that need wallet tooling but don't natively support the [Model Context Protocol (MCP)](https://modelcontextprotocol.io/). Instead of integrating an MCP client, agents invoke `fdx call ` from the command line and parse JSON output. * **No Key Management** — OAuth 2.1 secured. No seed phrases. No private key files. * **Agent-Native** — Structured JSON input/output designed for tool-calling agents. * **Multi-Chain** — Ethereum, BSC, Arbitrum, Base, Solana. One wallet, all chains. * **DeFi Enabled** — Transfer, swap, and earn yield through integrated DeFi protocols. * **Smart Accounts** — Account abstraction with multi-signature support (ERC-4337). For full documentation, architecture details, and development instructions, see the [FDX GitHub repository](https://github.com/financedistrict-platform/fd-agent-wallet-cli). ## Quick Start Install globally: ```bash theme={null} npm install -g @financedistrict/fdx ``` Run the setup: ```bash theme={null} fdx setup ``` Check that authentication succeeded: ```bash theme={null} fdx status ``` ## Authentication FDX uses OAuth 2.1 with the **Device Authorization Grant** ([RFC 8628](https://datatracker.ietf.org/doc/html/rfc8628)). Authentication is always tied to a user identity — the agent acts as a delegate on the user's behalf. When you run `fdx setup`, the CLI retrieves a short one-time code and prints it alongside a verification URL: ``` ────────────────────────────────────────────────────────── Verification URL: https://microsoft.com/devicelogin Enter code: ABCD-1234 ────────────────────────────────────────────────────────── ``` Open the URL on any device (or have your agent navigate to it), enter the code, and complete sign-in. The CLI polls in the background and stores the token once authorization is confirmed. The agent never needs a browser. The token is still issued to a user — identity and accountability stay intact. This works everywhere — Docker containers, CI pipelines, remote servers, and autonomous agents. No API keys or bearer tokens to manage. Tokens are stored securely in the OS credential store (Keychain on macOS, libsecret on Linux, DPAPI on Windows) and refreshed automatically. To remove stored credentials: ```bash theme={null} fdx logout ``` ## Usage Invoke any MCP tool via the CLI: ```bash theme={null} # Check wallet overview fdx call getWalletOverview --chainKey ethereum # Send tokens fdx call transferTokens --chainKey ethereum --recipientAddress 0xABC... --amount 0.1 # Discover yield strategies fdx call discoverYieldStrategies --chainKey base ``` All output is JSON, making it easy for agents to parse: ```bash theme={null} fdx call getMyInfo | jq '.email' ``` Run `fdx call` without arguments to see all available methods. ## SDK Usage FDX can also be used as a Node.js library: ```javascript theme={null} const { createClientFromEnv } = require("@financedistrict/fdx"); const client = createClientFromEnv(); const result = await client.getWalletOverview({ chainKey: "ethereum" }); console.log(result.data); ``` ## Configuration | Variable | Description | Default | | ---------------- | --------------------------------------------------------------- | -------------------- | | `FDX_MCP_SERVER` | MCP server URL | `https://mcp.fd.xyz` | | `FDX_STORE_PATH` | Token store path | `~/.fdx/auth.json` | | `FDX_LOG_PATH` | Log file path | `~/.fdx/fdx.log` | | `FDX_LOG_LEVEL` | Log verbosity (`debug` \| `info` \| `warn` \| `error` \| `off`) | `info` | ## Using FDX with Agent Frameworks FDX is designed to work with agent frameworks where the agent can execute shell commands. ### Tool-calling agents Most agent frameworks let you define custom tools. Wrap FDX as a shell tool: ```python theme={null} import subprocess import json def wallet_command(method: str, **kwargs) -> dict: """Execute an FDX wallet command and return JSON result.""" args = ["fdx", "call", method] for key, value in kwargs.items(): args.extend([f"--{key}", str(value)]) result = subprocess.run(args, capture_output=True, text=True) return json.loads(result.stdout) # Check wallet overview overview = wallet_command("getWalletOverview", chainKey="ethereum") # Transfer tokens wallet_command("transferTokens", chainKey="ethereum", recipientAddress="0x1234...5678", amount="5") ``` ### Script-based automation ```bash theme={null} #!/bin/bash BALANCE=$(fdx call getWalletOverview --chainKey base | jq -r '.balances[0].amount') echo "Current balance: $BALANCE" ``` For deeper integration patterns with specific frameworks, see [Agent Frameworks](/agent-wallet/ai-integration/agent-frameworks). ## FDX vs. MCP Server FDX and MCP server connect to the same backend and offer the same capabilities: | Aspect | MCP Server | FDX CLI | | ---------- | ------------------------- | ------------------------------------------------------- | | Interface | MCP protocol (tool calls) | Terminal commands (`fdx call`) | | Connection | MCP client → server | FDX → MCP client → server | | Best for | AI agents in MCP clients | Scripts, automation, agent frameworks with shell access | | Setup | Config file in MCP client | `npm install -g` + `fdx setup` | | Auth | OAuth PKCE | Device Authorization Grant | | Output | MCP tool responses | JSON | Choose MCP when your agent or AI client supports MCP natively. Choose FDX when you need terminal access or are integrating with a framework that runs shell commands. # MCP Compatible Clients Source: https://developers.fd.xyz/agent-wallet/ai-integration/mcp-clients Configure Agent Wallet MCP server with Claude Desktop, Cursor, n8n, and other MCP clients. # MCP Compatible Clients The Agent Wallet MCP server works with any MCP-compatible client. Below are setup guides for popular clients. The server URL and authentication are the same regardless of which client you use. **Server details for all clients:** | Property | Value | | --------- | -------------------------------- | | URL | `https://mcp.fd.xyz` | | Transport | Streamable HTTP | | Auth | OAuth PKCE (browser-based login) | ## Claude Desktop Edit your Claude Desktop config file: * **macOS:** `~/Library/Application Support/Claude/claude_desktop_config.json` * **Windows:** `%APPDATA%\Claude\claude_desktop_config.json` ```json theme={null} { "mcpServers": { "finance-district": { "type": "streamable-http", "url": "https://mcp.fd.xyz" } } } ``` Restart Claude Desktop. On first connection, a browser window opens for you to log in with your District Pass. Once authenticated, the connection persists across sessions. **Verify:** Ask Claude "What Finance District tools do you have?" ## Cursor Open Cursor Settings → MCP Servers → Add new server: ```json theme={null} { "mcpServers": { "finance-district": { "type": "streamable-http", "url": "https://mcp.fd.xyz" } } } ``` Restart Cursor after saving. The browser-based OAuth flow will trigger on first connection. **Verify:** In Cursor's AI chat, ask "What Finance District wallet tools are available?" ## Windsurf Open Windsurf Settings → MCP Configuration: ```json theme={null} { "mcpServers": { "finance-district": { "type": "streamable-http", "url": "https://mcp.fd.xyz" } } } ``` ## Claude Code Claude Code supports MCP servers directly from the command line: ```bash theme={null} claude mcp add finance-district --transport streamable-http https://mcp.fd.xyz ``` The OAuth flow triggers on first use. After authenticating, Claude Code can use wallet tools in your coding sessions. ## n8n n8n supports MCP servers as tools for AI Agent nodes: 1. Open your n8n workflow 2. Add or configure an **AI Agent** node 3. In the agent's tool configuration, add an MCP server 4. Set the server URL to `https://mcp.fd.xyz` with Streamable HTTP transport 5. Configure OAuth authentication with your District Pass credentials The agent node can then use wallet capabilities as tools within your n8n workflows. ## Custom MCP Client For developers building their own MCP client or integrating into a custom agent framework, you'll need to handle both the OAuth PKCE authentication flow and **client registration** with the Agent Wallet MCP server. ### Client Registration Before an MCP client can authenticate, it must be registered with the authorization server. The Agent Wallet MCP server supports two registration methods: CIMD lets your client publish its metadata (name, redirect URIs, grant types) as a JSON document at a publicly accessible URL. The authorization server fetches this document to verify your client's identity during the OAuth flow. **How it works:** 1. You host a JSON metadata document at a URL you control (e.g., `https://your-app.com/.well-known/oauth-client`) 2. Your client uses that URL as its `client_id` in the OAuth flow 3. The authorization server fetches the document to validate the client This is the **recommended approach** — no registration step is needed on our side, and you maintain full control of your client metadata. See the [MCP Authorization specification](https://modelcontextprotocol.io/specification/2025-03-26/basic/authorization) for the full metadata document schema. DCR ([RFC 7591](https://datatracker.ietf.org/doc/html/rfc7591)) allows clients to register programmatically by posting their metadata to a registration endpoint. The server responds with a `client_id` that the client uses for subsequent OAuth flows. DCR on the Agent Wallet MCP server is **restricted to whitelisted clients**. If you need DCR access for your custom client, [reach out to us on Discord](https://discord.gg/fdxyz) to request whitelisting. Popular MCP clients — Claude Desktop, Cursor, Windsurf, Claude Code, and n8n — are already registered and work out of the box. Client registration only applies when you're building a custom MCP client or integrating the Agent Wallet MCP server into your own agent framework. ### Code Example ```typescript theme={null} import { Client } from "@modelcontextprotocol/sdk/client/index.js"; import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js"; const transport = new StreamableHTTPClientTransport( new URL("https://mcp.fd.xyz"), ); const client = new Client({ name: "my-agent", version: "1.0.0", }); await client.connect(transport); // Discover available tools const { tools } = await client.listTools(); console.log( "Available tools:", tools.map((t) => t.name), ); // Call a tool const result = await client.callTool({ name: "check_balance", arguments: {}, }); ``` The MCP SDK's Streamable HTTP transport handles the OAuth PKCE flow natively. See the [MCP SDK documentation](https://modelcontextprotocol.io/docs) for full implementation details. ## Troubleshooting **Browser window doesn't open for authentication** * Ensure your MCP client supports OAuth PKCE with Streamable HTTP transport * Some older MCP client versions may not support remote server authentication — update to the latest version **Connection drops after a while** * OAuth tokens refresh automatically. If the refresh fails, restart your client to trigger a new authentication flow **Tools aren't showing up** * Verify the server URL is exactly `https://mcp.fd.xyz` * Verify the transport type is set to `streamable-http` (not `sse` or `stdio`) * Restart your MCP client after adding the configuration # MCP Server Source: https://developers.fd.xyz/agent-wallet/ai-integration/mcp-server Connect AI agents to Agent Wallet via the Model Context Protocol (MCP) server. # MCP Server The Agent Wallet MCP server is the primary integration point for AI agents. It exposes wallet capabilities as tools that any MCP-compatible client can discover and use — Claude Desktop, Cursor, Windsurf, n8n, or your own custom agent. The server is **remote and hosted** at `https://mcp.fd.xyz`. There's nothing to install, no local processes to run, and no infrastructure to manage. ## What is MCP? [MCP (Model Context Protocol)](https://modelcontextprotocol.io/) is an open standard by Anthropic for connecting AI assistants to external tools and data sources. Think of it as a universal adapter — any MCP-compatible client can connect to any MCP server and discover what it can do. Agent Wallet publishes wallet operations as MCP tools. When your AI agent connects, it automatically discovers all available capabilities and can use them through natural language. ## Connection Details | Property | Value | | -------------- | --------------------------------------- | | Server URL | `https://mcp.fd.xyz` | | Transport | Streamable HTTP | | Authentication | OAuth Authorization Code Flow with PKCE | No API keys or bearer tokens to manage. When your MCP client connects for the first time, it triggers an OAuth flow that opens a browser for you to authenticate with your [District Pass](/overview/fd-id). ## Setup Add the Agent Wallet MCP server to your client's configuration: Edit your config file: * **macOS:** `~/Library/Application Support/Claude/claude_desktop_config.json` * **Windows:** `%APPDATA%\Claude\claude_desktop_config.json` ```json theme={null} { "mcpServers": { "finance-district": { "type": "streamable-http", "url": "https://mcp.fd.xyz" } } } ``` Restart Claude Desktop. A browser window will open for authentication on first connect. Open Cursor Settings → MCP Servers → Add new server: ```json theme={null} { "mcpServers": { "finance-district": { "type": "streamable-http", "url": "https://mcp.fd.xyz" } } } ``` Open Windsurf Settings → MCP Configuration: ```json theme={null} { "mcpServers": { "finance-district": { "type": "streamable-http", "url": "https://mcp.fd.xyz" } } } ``` ```bash theme={null} claude mcp add finance-district --transport streamable-http https://mcp.fd.xyz ``` For more clients, see [MCP Compatible Clients](/agent-wallet/ai-integration/mcp-clients). ## Verify the Connection After configuring your client, verify the agent can reach the wallet: > "What Finance District tools do you have?" The agent should list the available wallet capabilities. Then try: > "What's my wallet balance?" If you see your balance (even if it's zero), the connection is working. ## Capabilities The MCP server exposes wallet operations as tools that your agent discovers automatically. Rather than documenting specific tool names (which may evolve), here's what your agent can do once connected: ### Query Operations * **Check balances** — view holdings across all tokens and chains * **Get wallet info** — wallet addresses, supported chains, configuration * **View transaction history** — recent transactions with details * **Check swap quotes** — preview exchange rates before committing ### Transaction Operations * **Transfer tokens** — send to any address on any supported chain * **Swap tokens** — exchange between token pairs with best-rate routing * **Commerce payments** — pay Prism-enabled merchants via x402 * **Yield operations** — explore and deploy funds to yield opportunities Your agent discovers these capabilities automatically through MCP's standard tool discovery. You don't need to configure which operations are available — the full set is exposed when you connect. The agent discovers all available tools dynamically. As new capabilities are added to the MCP server, your agent gets access to them automatically — no config changes needed. ## Authentication Authentication uses **OAuth Authorization Code Flow with PKCE** — the same standard used by major web applications: 1. Your MCP client connects to `https://mcp.fd.xyz` 2. The server responds with an OAuth authorization URL 3. Your client opens a browser window 4. You log in with your District Pass (email + confirmation code) 5. The OAuth flow completes and your client receives a token 6. Subsequent requests use this token automatically **Token refresh** is handled automatically by the OAuth flow. You shouldn't need to re-authenticate unless you explicitly revoke access. **Revoking access** — if you want to disconnect a specific MCP client, you can revoke its OAuth token through the Signer Service web interface. **Client registration** — popular MCP clients (Claude Desktop, Cursor, Windsurf, Claude Code, n8n) are pre-registered and work out of the box. If you're building a custom MCP client, see [Client Registration](/agent-wallet/ai-integration/mcp-clients#client-registration) for supported registration methods. ## Error Handling Common scenarios your agent may encounter: | Error | Meaning | Resolution | | ----------------------- | ---------------------------------------------------- | ------------------------------------------------------------ | | Authentication failed | OAuth token expired or revoked | Re-authenticate through browser flow | | Insufficient balance | Wallet doesn't have enough tokens for the operation | Fund the wallet with more tokens | | Transaction failed | On-chain error (gas, nonce, network issue) | Retry — transient errors resolve on retry | | Unsupported chain/token | Requested operation on an unsupported chain or token | Check [Supported Networks](/agent-wallet/supported-networks) | MCP clients typically display tool errors in the conversation. The agent can often explain what went wrong and suggest a fix. # Security Model Source: https://developers.fd.xyz/agent-wallet/concepts/security TEE key management, non-custodial architecture, and how Agent Wallet protects your funds. # Security Model Agent Wallet uses a non-custodial architecture with private keys secured inside AWS Nitro Enclaves. Finance District is an infrastructure provider — we don't hold custody of your wallet or keys. This is by design and architecture, similar to services like [Turnkey](https://www.turnkey.com/). You have full control over your wallet at all times. ## The Problem with Current Approaches If your AI agent needs a wallet today, you're probably doing one of these: **Environment variables.** Generate a private key, store it in `.env`, use it in your agent code. This is the most common pattern in agent ecosystems — and the most dangerous. One compromised server, one leaked log, one misconfired deploy, and the key is gone. This isn't theoretical — credential leaks and key exposure are already being documented across agent platforms. **Application config.** Private key in a JSON config, YAML file, or secrets manager that the application reads at startup. Better than `.env`, but still accessible to anything with file or process access on the host. **Agent-managed keys.** The agent generates and manages its own key material, typically held in memory or the agent's storage. If the agent is compromised — or if the orchestrator framework has a vulnerability — the key goes with it. All of these patterns share one fundamental flaw: the key exists somewhere the host system can reach it. ## Trusted Execution Environment (TEE) Agent Wallet uses [AWS Nitro Enclaves](https://aws.amazon.com/ec2/nitro/nitro-enclaves/) for key management. Nitro Enclaves are isolated compute environments that run on dedicated, hardened hardware — separate from the host instance's CPU, memory, and storage. **How it works:** 1. Your private key is **generated inside** the Nitro Enclave 2. The key **never leaves** the enclave 3. All transaction **signing happens inside** the enclave 4. The host operating system **cannot access** the enclave's memory or storage 5. Even Finance District infrastructure operators **cannot extract** key material The enclave receives signing requests, performs the cryptographic operation internally, and returns the signed transaction. At no point is the raw private key exposed to any external process, API, or human. ```mermaid theme={null} flowchart LR A[Agent] -->|Signing Request| S subgraph enclave["AWS Nitro Enclave"] K["Private Key\n(generated here, never leaves)"] --> S[Sign Transaction] end S -->|Signed Transaction| A ``` ### Comparison | Approach | Where Keys Live | Who Can Access | Risk | | ---------------------- | ---------------------- | ----------------------------------- | ------------------------------------------ | | Environment variables | Host memory / disk | Anyone with server access | High — one breach exposes keys | | Application config | Config files | App + anyone with file access | High | | Agent-managed | Agent memory / storage | Agent + orchestrator | Medium — agent compromise = key compromise | | **TEE (Agent Wallet)** | **Nitro Enclave** | **Nobody — signing happens inside** | **Low — hardware isolation** | ## Non-Custodial Architecture Finance District is an infrastructure provider, not a custodian. The architecture is non-custodial by design: * **We don't hold your keys.** Keys exist inside Nitro Enclaves. There is no mechanism for Finance District to extract, copy, or use your private keys. * **You have full control.** You can withdraw funds, export your keys, and manage your wallet at any time. * **You can export your keys.** Through the Signer Service web interface, you can request a full key export and back up your private keys independently. This means you are never locked into Finance District infrastructure. This is the same non-custodial model used by infrastructure providers like Turnkey — the platform secures and manages keys on your behalf, but you retain ownership and can take your keys elsewhere at any time. Key export means you can always self-custody. Agent Wallet is infrastructure you choose to use, not a walled garden you're locked into. ## What We Don't Do To be explicit about the security boundaries: * **We don't store keys in environment variables** — keys live in Nitro Enclaves, not on the host * **We don't trust the host environment** — the enclave is isolated from the host OS, memory, and storage * **We don't hold custody** — no Finance District employee or system can access your private keys * **We don't let agents manage key material** — agents call signing operations; they never touch the key itself * **We don't expose raw private keys through any interface** — MCP, CLI, AI Assistant, and Web App all interact with signing as a service, not with keys directly ## Audit Trail Every on-chain transaction your wallet makes is recorded on the blockchain and visible through any block explorer. Agent Wallet does not maintain a separate proprietary logging layer — the blockchain itself is your audit trail. To review your wallet's activity: * Look up your wallet address on the relevant chain's block explorer (Etherscan, Basescan, Solscan, etc.) * Every transaction includes sender, recipient, amount, token, timestamp, and transaction hash * On-chain history is immutable and independently verifiable This means your audit trail doesn't depend on Finance District — it's public, permanent, and cryptographically guaranteed by the blockchain. ## Redundancy and Key Safety The system has built-in redundancy and backup procedures. Simple hardware failures do not result in key loss — the infrastructure is designed to survive individual component failures without interrupting service. If you want additional peace of mind: * **Export your keys** at any time through the Signer Service web interface * **Back up exported keys** using your own secure storage (hardware wallet, encrypted backup, etc.) * **Self-custody as fallback** — exported keys work with any standard wallet software ## Your Controls You — the wallet owner — have full control at all times: * **View activity** — check your transaction history on-chain via block explorers * **Withdraw funds** — move your funds out of the wallet whenever you want * **Export keys** — request a full key export through the Signer Service interface * **Fund conservatively** — the [pocket money approach](/agent-wallet/concepts/trust) is your primary risk control The non-custodial architecture means these controls are inherent to the design, not features that can be revoked. Your wallet is yours. # Trust & Constraints Source: https://developers.fd.xyz/agent-wallet/concepts/trust The 'pocket money' philosophy — sensible constraints for AI agent financial autonomy. # Trust & Constraints How much financial autonomy should you give an AI agent? The answer is simpler than most people think. ## The Pocket Money Philosophy Think of funding an agent wallet like giving pocket money. You put in what's appropriate for the task — not your entire portfolio. The agent operates autonomously within that scope. If something goes wrong, you lose the pocket money, not everything. This is deliberately simpler than: * **Complex policy engines** that require extensive configuration before your agent can do anything * **Role-based permission systems** that add integration overhead and maintenance burden * **Multi-approval workflows** that defeat the purpose of autonomous operation The constraint **is** the balance. If you fund a wallet with $50, the worst case is losing $50. No policy engine needed. No configuration. The constraint is immediate, obvious, and enforced by the blockchain itself. Don't give an agent more than you'd be comfortable losing. This one rule handles 90% of trust decisions. ## How to Think About Funding **Start small.** Always. Fund what you need for the immediate task, see how the agent performs, and increase as you gain confidence. **Match funding to the task.** A research agent that buys API access needs $10–50. A trading agent running a strategy might need $500. An enterprise payment agent might need more. Let the task dictate the amount. **Refill rather than pre-fund.** It's better to add $100 five times than to deposit $500 upfront. Each refill is a checkpoint — a moment to review what the agent did with the previous funds. ## Setting Constraints ### Balance Is the Primary Constraint The amount in the wallet is the most powerful constraint you have. It's: * **Enforced by the blockchain** — the agent literally cannot spend more than what's in the wallet * **Immediately effective** — no configuration, no policy language, no deployment * **Transparent** — you can check the balance at any time via any block explorer * **Universal** — works the same regardless of which interface the agent uses ### Progressive Trust 1. **Test phase** — Fund with test tokens on a testnet. Let the agent experiment freely. Cost: zero. 2. **Small real funds** — Fund with \$10–50 on mainnet. Verify the agent does what you expect. 3. **Working amount** — Once you trust the behavior, fund what the use case actually needs. 4. **Scale up** — Increase funding as the agent proves itself over time. Each step is a conscious decision. There's no automation that escalates trust — you decide when to increase the balance. ## Trust Patterns by Use Case | Use Case | Suggested Funding | Constraints | Wallet Level | | ------------------------------ | ------------------ | ----------------------------------------- | ------------- | | Testing and exploration | Test tokens (free) | None — let it experiment | EOA | | Agent buying API access | \$10–50 | Fund per session | EOA | | Bot making small purchases | \$100–500 | Refill periodically | EOA | | Agent with recurring tasks | \$500–2,000 | Review activity weekly | EOA | | Multi-agent deployment | Per-agent budgets | Individual funding per agent | EOA | | Higher-value production | Task-appropriate | Consider Smart Account for added controls | Smart Account | | Enterprise with approval needs | Custom | Granular permissions, multi-party control | Delegated | Most entries in this table are EOA — because most agent use cases work perfectly with just sensible balance management. ## When Simple Constraints Aren't Enough The pocket money approach works for the vast majority of use cases. But some scenarios call for more: **You need batch transactions or gas abstraction** → [Smart Account](/agent-wallet/wallet-architecture#level-2-smart-account) adds programmable on-chain logic, session keys, and gas sponsorship. **You need on-chain spending limits** → [Delegated wallet](/agent-wallet/wallet-architecture#level-3-delegated-wallet) adds granular spending controls enforced by the smart contract itself. **You need multi-party approval** → Delegated wallets support approval workflows for high-value operations. The key principle: don't over-engineer trust controls before you need them. Start with sensible funding. Upgrade only when the use case genuinely demands it. ## Common Mistakes **Over-funding early.** Developers deposit large amounts "to avoid having to refill." This removes the natural safety net. Fund small, refill often. **Over-engineering controls.** Building complex permission systems before you've validated the agent's behavior. Start with pocket money. Add controls when you have real data on what the agent does. **Under-monitoring.** The pocket money approach doesn't mean "fund and forget." Check your wallet's on-chain activity periodically. Block explorers make this trivial. **Treating all agents the same.** A research agent and a trading agent have very different risk profiles. Fund each according to its purpose, not with a blanket amount. # X402 Payments Source: https://developers.fd.xyz/agent-wallet/concepts/x402-payments How Agent Wallet connects to Prism merchants via the x402 payment protocol. # X402 Payments X402 is an open payment protocol for machine-to-machine stablecoin payments. When your agent accesses a Prism-enabled API or service, x402 handles the payment flow automatically — the agent gets a payment request, the wallet pays, and the agent gets the resource. No human intervention. This page covers the **Agent Wallet side** — how your agent pays via x402. For the merchant/seller side (accepting payments), see [Prism: X402 Protocol](/prism/concepts/x402). ## How It Works X402 builds on the HTTP `402 Payment Required` status code. The flow is straightforward: ```mermaid theme={null} sequenceDiagram participant A as Agent participant M as Merchant API participant B as Blockchain A->>M: HTTP Request M-->>A: 402 Payment Required Note right of M: amount, token, recipient A->>B: Wallet signs & submits payment Note over B: Prism verifies payment A->>M: Retry with payment proof M-->>A: 200 OK + resource ``` **Step by step:** 1. Your agent makes a standard HTTP request to a Prism-protected endpoint 2. The endpoint returns HTTP `402 Payment Required` with payment details — amount, token, recipient address, and chain 3. The agent's wallet capability processes the payment — signing the stablecoin transfer inside the TEE 4. The transaction settles on-chain 5. Prism verifies the payment on behalf of the merchant 6. The agent receives the requested resource or confirmation From the agent's perspective, this is seamless. The agent encounters a paywall, pays it, and gets what it asked for. ## Using x402 with Your Agent When your agent is connected via MCP and encounters a 402 payment requirement, the wallet handles it through the commerce payment capability. **Example prompt:** > "Access the premium data API at [https://api.example.com/market-data](https://api.example.com/market-data)" What happens behind the scenes: 1. The agent calls the API 2. Gets a 402 response with payment requirements (e.g., 0.50 USDC on Base) 3. The agent uses the wallet's payment capability to settle the required amount 4. The agent retries the request with payment proof 5. The agent receives the market data **Direct payment prompt:** > "Pay 2 USDC to the merchant at 0xABC...123 on Base" The agent can also make explicit payments to known merchant addresses without encountering a 402 flow first. ## What Makes x402 Different X402 is designed for **machines paying machines**, not humans clicking "Pay Now": | Feature | Traditional Payment | x402 | | ----------------- | ------------------------------------- | --------------------------- | | Interaction model | Human clicks buttons | Agent handles automatically | | Settlement | Days (credit card) | Seconds (on-chain) | | Currency | Fiat | Stablecoins | | Intermediaries | Payment processor, bank, card network | Direct on-chain | | API-native | Requires separate payment integration | Built into HTTP itself | For AI agents, this matters because: * **No browser needed** — the payment happens in the HTTP flow, not in a checkout UI * **No credentials to manage** — the wallet signs the transaction; no credit card numbers or API payment keys * **Instant settlement** — the merchant can verify payment in seconds, not days * **Micropayment friendly** — stablecoin transactions can be very small (fractions of a dollar) ## Supported Stablecoins X402 payments through Agent Wallet support stablecoins on any chain where Prism merchants accept payments. Common options: * **USDC** — widely supported across EVM chains * **FDUSD** — supported on select chains The specific tokens and chains available for x402 depend on what the merchant has configured via Prism. Your agent's wallet needs to hold the required stablecoin on the required chain. See [Supported Networks](/agent-wallet/supported-networks) for the full chain and token details. ## The Agent Wallet + Prism Connection X402 is where the two Finance District products meet: * **Agent Wallet** is the buyer side — your agent has funds and wants to pay for services * **Prism** is the seller side — merchants accept stablecoin payments from AI agents When an agent with an Agent Wallet pays a Prism merchant via x402, the entire transaction stays within one ecosystem. No intermediaries, no bridge services, no delays. The agent pays, Prism verifies, the merchant gets their funds. Learn how merchants accept agent payments Deep dive into the x402 protocol specification # Overview Source: https://developers.fd.xyz/agent-wallet/overview Agent Wallet — AI-native wallet infrastructure. Give your AI agents genuine financial autonomy. # Agent Wallet Agent Wallet is AI-native wallet infrastructure that gives AI agents their own wallets — not borrowed access to a human's. Every user gets EVM and Solana wallets provisioned automatically at signup, with full capabilities out of the box: transfers, swaps, yield generation, and commerce protocol payments. Private keys are secured in a Trusted Execution Environment (TEE), and the architecture is non-custodial — operators maintain ultimate control at all times. Unlike connecting an AI to an existing human wallet, Agent Wallet is built for agents from day one. It's optimized for agentic workflows, assisted conversations, and autonomous financial operations — so the experience is smooth whether an agent is executing a multi-step trade or a user is chatting with an AI assistant. ## Why AI-Native Matters Today, if your AI agent needs to handle money, you have three options: | Approach | How It Works | Limitation | | ----------------------------- | -------------------------------------------------------------- | -------------------------------------------------------- | | **Human wallet + AI wrapper** | Agent connects to a user's MetaMask or Coinbase via API | No true autonomy — the human remains the wallet owner | | **DIY wallet** | A few lines of code to create an address, keys in env vars | Insecure, limited to basic transfers, no production path | | **AI-native (Agent Wallet)** | Agent gets its own wallet, operates within defined constraints | Genuine autonomy with production-grade security | The DIY approach gets you basic transfers, but swaps take hundreds of lines of untested code. Yield generation, commerce payments, and secure key management aren't included. And storing private keys in environment variables isn't theoretical risk — credential leaks are already being documented in agent ecosystems. Agent Wallet provides all of this out of the box, with TEE-secured key management, so you can focus on what your agent does rather than building financial infrastructure. ## Four Interfaces, One Wallet Agent Wallet is infrastructure — a core service exposed through multiple interfaces. All four connect to the same underlying wallet and capabilities. | Interface | Primary User | Best For | | ------------------------------------------------------------- | -------------------------------- | -------------------------------------------------------------------- | | **[MCP Server](/agent-wallet/ai-integration/mcp-server)** | AI agents and assistants | Any MCP-compatible client — Claude, Cursor, n8n, custom agents | | **[CLI](/agent-wallet/ai-integration/cli)** | Developers and autonomous agents | Terminal-based workflows, scripting, server-side automation | | **[AI Assistant](/agent-wallet/ai-integration/ai-assistant)** | Humans (non-technical) | Conversational web interface for wallet management | | **Web App** | Operators | Traditional wallet UI — manage wallets, view balances, on-ramp funds | The MCP Server is the primary integration point for AI agents. The CLI is a lightweight wrapper around an MCP client — same capabilities, accessible from the command line. The AI Assistant and Web App serve humans who need to manage or monitor wallets directly. ## Core Capabilities When you sign up, EVM and Solana wallets are provisioned for you automatically — no extra steps required. From there, your agent (or you) can: * **Send & Receive** — Transfer tokens to any address on supported chains * **Check Balances** — View holdings across all provisioned wallets * **Swap** — Exchange between token pairs with best-rate routing * **Earn Yield** — Explore yield opportunities and deploy funds to vaults * **Commerce Payments** — Pay Prism-enabled merchants via x402 and other commerce protocols * **On-Ramp** — Fund your wallet via credit card through the Web App All capabilities are available through MCP, CLI, and AI Assistant. The Web App provides additional management features like on-ramp and wallet configuration. Agent Wallet supports all EVM-compatible blockchains and Solana. Wallets for both are created by default for every user. ## Get Started Create your first agent wallet and connect via MCP Connect your AI agent to the hosted MCP server Install the CLI for terminal-based agent workflows TEE key management and non-custodial architecture # Quick Start Source: https://developers.fd.xyz/agent-wallet/quickstart Create your first agent wallet and connect an AI agent in minutes. # Quick Start Get from zero to a working agent wallet connected via MCP. No blockchain knowledge required. ## Prerequisites * A **[District Pass](/overview/fd-id)** — if you don't have one yet, sign up with just your email * An **MCP-compatible client** — Claude Desktop, Cursor, Windsurf, or any client that supports remote MCP servers When you sign up for a District Pass, EVM and Solana wallets are created for you automatically. There's no separate "create wallet" step. ## Step 1: Sign Up for a District Pass If you already have a District Pass from using another Finance District service, skip to Step 2. 1. Go to [fd.xyz](https://fd.xyz) and click **Sign Up** 2. Enter your email address 3. Enter the confirmation code sent to your inbox 4. Done — your District Pass is active and your wallets are ready That's it. You now have EVM and Solana wallets provisioned and waiting. ## Step 2: Fund Your Wallet Your wallet exists but starts with a zero balance. To do anything useful, you need to add funds. **For testing:** * Use the [Testnet Faucet](/overview/developer-tools/faucet) to get free test tokens on supported testnets **For real transactions:** * **Transfer** tokens from another wallet to your agent wallet address (visible in the Web App) * **On-ramp** via credit card through the Web App Start small — the "pocket money" approach. Fund only what's appropriate for what you're testing. You can always add more later. ## Step 3: Connect via MCP The Agent Wallet MCP server is hosted at `https://mcp.fd.xyz` and uses the Streamable HTTP transport. Authentication is handled via OAuth PKCE — your MCP client will open a browser window for you to log in with your District Pass. Add the server to your MCP client configuration: Edit your Claude Desktop config file: * **macOS:** `~/Library/Application Support/Claude/claude_desktop_config.json` * **Windows:** `%APPDATA%\Claude\claude_desktop_config.json` ```json theme={null} { "mcpServers": { "finance-district": { "type": "streamable-http", "url": "https://mcp.fd.xyz" } } } ``` Restart Claude Desktop. On first connection, a browser window will open for you to authenticate with your District Pass. Open Cursor Settings → MCP Servers → Add new server: ```json theme={null} { "mcpServers": { "finance-district": { "type": "streamable-http", "url": "https://mcp.fd.xyz" } } } ``` Install and authenticate via the [FDX CLI](/agent-wallet/ai-integration/cli): ```bash theme={null} npm install -g @financedistrict/fdx fdx setup ``` This prints a one-time code and verification URL. Open the URL on any device, enter the code, and complete sign-in. Once authenticated, the CLI is ready to use. The MCP server supports the **Streamable HTTP** transport protocol. No API keys or bearer tokens to manage — OAuth PKCE handles authentication automatically. For other MCP clients, see [MCP Compatible Clients](/agent-wallet/ai-integration/mcp-clients). ## Step 4: Try It Out Once connected, your AI agent has access to all wallet capabilities. Try these: **Check your balance:** > "What's the balance of my wallet?" **Get your wallet address:** > "What's my wallet address on Base?" **Send a transfer (testnet):** > "Send 5 USDC to 0x1234...5678 on Base Sepolia" **Swap tokens:** > "Swap 10 USDC for ETH" **Explore yield:** > "What yield opportunities are available for my USDC?" The agent discovers available capabilities automatically through the MCP protocol — it knows what tools are available and how to use them. ## What's Next? Full MCP server documentation and capabilities reference Command-line interface for autonomous agent workflows EOA → Smart Account → Delegated wallet progression How TEE protects your keys Let your agent pay Prism merchants Ready-to-use prompts for common wallet operations # Example Prompts Source: https://developers.fd.xyz/agent-wallet/resources/example-prompts Ready-to-use prompts for Agent Wallet — copy, paste, go. # Example Prompts Prompts for MCP-connected agents and the AI Assistant. [FDX CLI](/agent-wallet/ai-integration/cli) equivalents included where applicable. ## Wallet Basics **Check balance:** > "What's my wallet balance?" ```bash theme={null} fdx call getWalletOverview --chainKey ethereum ``` **Check specific token:** > "How much USDC do I have?" ```bash theme={null} fdx call getWalletOverview --chainKey base ``` **Get wallet address:** > "What's my wallet address on Base?" ```bash theme={null} fdx call getMyInfo ``` **View supported chains:** > "What chains does my wallet support?" ## Transfers **Send tokens:** > "Send 5 USDC to 0x1234...5678" ```bash theme={null} fdx call transferTokens --chainKey ethereum --recipientAddress 0x1234...5678 --amount 5 ``` **Send on a specific chain:** > "Send 10 USDC to 0x1234...5678 on Base" ```bash theme={null} fdx call transferTokens --chainKey base --recipientAddress 0x1234...5678 --amount 10 ``` **Transfer with confirmation:** > "Send 50 USDC to 0xABC...DEF — confirm before executing" ## Swaps **Simple swap:** > "Swap 10 USDC for ETH" ```bash theme={null} fdx call swapTokens --chainKey ethereum --fromToken USDC --toToken ETH --amount 10 ``` **Check quote first:** > "How much ETH would I get for 50 USDC?" **Swap with conditions:** > "Swap 100 USDC for ETH, but only if the rate is better than 0.0003 ETH per USDC" ## Yield **Explore opportunities:** > "What yield opportunities are available for my USDC?" ```bash theme={null} fdx call discoverYieldStrategies --chainKey base ``` **Deploy to yield:** > "Put 100 USDC into the best yield opportunity" **Check yield status:** > "How are my yield positions performing?" ## Commerce Payments **Pay a merchant:** > "Pay 2 USDC to the merchant at 0xABC...123 on Base" **Access a paid API:** > "Access the premium data API at [https://api.example.com/market-data](https://api.example.com/market-data)" The agent handles the x402 payment flow automatically — it encounters the 402 response, pays, and retrieves the data. ## Transaction History **Recent transactions:** > "Show my last 5 transactions" ```bash theme={null} fdx call getTransactionHistory --chainKey ethereum ``` **Spending summary:** > "How much have I spent this week?" **Filter by type:** > "Show all my swaps from the last 7 days" ## Multi-Step Operations These prompts ask the agent to reason and execute multiple steps: **Conditional transfer:** > "If I have more than 100 USDC, send 50 to 0x1234...5678" **Research and swap:** > "Check the current ETH price. If it's below \$2,500, swap 50 USDC for ETH." **Portfolio check:** > "Show me my complete portfolio — balances across all chains and tokens" **Multi-chain overview:** > "What's my total USDC balance across all chains?" ## Tips for Effective Prompts **Be specific about tokens and amounts.** "Send 5 USDC" is better than "send some money." **Specify chains when it matters.** "Send USDC on Base" avoids ambiguity when you hold USDC on multiple chains. **Ask for confirmation on large amounts.** "Swap 500 USDC for ETH — confirm before executing" gives you a chance to review. **Use natural language.** The agent understands context. "Check my balance and swap half my USDC for ETH" works fine. # Supported Networks Source: https://developers.fd.xyz/agent-wallet/supported-networks Chains and tokens supported by Agent Wallet. # Supported Networks Agent Wallet supports all EVM-compatible blockchains and Solana. When you create a District Pass, wallets for both ecosystems are provisioned automatically. ## Chains Agent Wallet is not limited to a fixed list of chains. It works across the full EVM ecosystem — any EVM-compatible chain can be used. Solana is also supported natively. ## Tokens Agent Wallet works like any standard Web3 wallet — it can hold, send, and receive **any token** on supported chains. There are no restrictions on which tokens the wallet can interact with. Popular stablecoin options: | Stablecoin | Symbol | Issuer | Primary Chains | | ----------------- | ------ | ------------- | ----------------------------- | | First Digital USD | FDUSD | First Digital | Ethereum, BSC | | USD Coin | USDC | Circle | Ethereum, Base, BSC, Arbitrum | ## Testnets Use testnets during development. All Agent Wallet capabilities work identically with test tokens. | Testnet | Chain ID | Mainnet Equivalent | | ---------------- | -------- | ------------------ | | Ethereum Sepolia | 11155111 | Ethereum | | Base Sepolia | 84532 | Base | | BSC Testnet | 97 | BSC | | Arbitrum Sepolia | 421614 | Arbitrum | Need test tokens? See the [Testnet Faucet](/overview/developer-tools/faucet) page. # Wallet Architecture Source: https://developers.fd.xyz/agent-wallet/wallet-architecture Progressive wallet architecture — EOA, Smart Account, and Delegated wallets. # Wallet Architecture Agent Wallet uses a progressive architecture: start with the simplest wallet type and add capabilities only when you need them. Most agent use cases — 90% or more — work perfectly with a basic EOA wallet. Smart Accounts and Delegated wallets exist for advanced scenarios, but you shouldn't reach for them until your use case demands it. ```mermaid theme={null} flowchart LR L1["**Level 1: EOA Wallet**\nStandard wallet\nHold, send, receive,\nswap, yield, pay\n\n_Default · EVM + Solana_"] --> L2["**Level 2: Smart Account**\nERC-4337 smart contract\nowned by your EOA\nBatch tx, gas abstraction,\nsession keys\n\n_Deploy when needed · EVM only_"] --> L3["**Level 3: Delegated**\nSmart Account +\ngranular spending\ncontrols and delegation\n\n_Advanced use cases · EVM only_"] ``` ## Level 1: EOA Wallet Every user gets an EOA (Externally Owned Account) wallet automatically when they create a District Pass — one for EVM-compatible chains and one for Solana. No deployment, no gas cost, no extra steps. An EOA is a standard blockchain wallet controlled by a private key. In Agent Wallet, that key is generated and stored inside a TEE (Trusted Execution Environment) — never exposed to the host system, never stored in environment variables. **Capabilities:** * Hold tokens across all supported EVM chains and Solana * Send and receive transfers * Swap between token pairs * Explore and deploy to yield vaults * Pay Prism merchants via commerce protocols * Full access through MCP, CLI, AI Assistant, and Web App **Best for:** The vast majority of agent use cases. Testing, prototyping, production agents with moderate transaction volumes, personal wallets. **Limitations:** Single signer (the TEE-held key), gas must be paid individually per transaction, no programmable on-chain rules. The EOA wallet is your starting point and remains functional even after you deploy a Smart Account. It becomes the **owner** of the Smart Account contract. ## Level 2: Smart Account A Smart Account is a lightweight ERC-4337 compatible smart contract account deployed on-chain, owned by your EOA wallet. It's based on Alchemy's widely-used and audited [LightAccount](https://github.com/alchemyplatform/light-account) contract. The key distinction: your EOA wallet doesn't go away. It continues to function as before. The Smart Account is an **additional** on-chain account that your EOA controls as its owner. **What this unlocks:** * **Batch transactions** — execute multiple operations in a single atomic transaction * **Gas abstraction** — sponsor gas fees or pay gas in tokens other than the chain's native token * **Session keys** — grant time-limited, scoped access to specific operations * **Programmable rules** — on-chain logic that governs what the account can do * **ERC-4337 compatibility** — works with any platform or DeFi protocol that supports account abstraction **Deployment:** * Self-service — deploy via MCP Server, AI Assistant, or Web App * You choose which chain(s) to deploy on (usually one or two chains where you need Smart Account features) * Your EOA wallet must hold enough native tokens (ETH, MATIC, etc.) on that chain to pay for the contract deployment gas * EVM only — Solana is not supported for Smart Accounts **Best for:** Production agents that benefit from batched operations, gas abstraction, or session keys. Multi-step workflows where atomicity matters. Smart Accounts are currently available to **select pilot users** only. If you're interested in early access, [contact us](/overview/resources/support). ## Level 3: Delegated Wallet A Delegated wallet is the same Smart Account contract with additional capabilities for granular spending control. Think of it as Level 2 with fine-grained permissions layered on top. **What delegation adds:** * **Spending limits** — cap how much an agent can spend per transaction, per day, or per time window * **Token restrictions** — limit which tokens the account can interact with * **Operation scoping** — restrict the types of operations allowed * **Multi-party control** — require multiple approvals for high-value operations **Best for:** High-value deployments, enterprise scenarios, and situations where you need precise control over what an agent is allowed to do on-chain. Delegated wallets are currently available to **select pilot users** only. ## Choosing the Right Level | Use Case | Recommended | Why | | ----------------------------------- | ------------- | ----------------------------------------------------------- | | Testing and prototyping | EOA | Zero setup, works immediately | | Single agent, standard operations | EOA | More than sufficient — most agents live here | | Agent doing swaps, transfers, yield | EOA | All capabilities are available at Level 1 | | Batched multi-step operations | Smart Account | Atomic execution, gas savings | | Gas sponsorship for agents | Smart Account | Pay gas in stablecoins or sponsor from another account | | Time-limited agent access | Smart Account | Session keys scope what the agent can do and when | | Controlled spending budgets | Delegated | On-chain spending limits beyond the "pocket money" approach | | Enterprise with approval workflows | Delegated | Multi-party control, granular permissions | **The general guidance:** start with EOA. If you find yourself needing batch transactions, gas abstraction, or session keys, consider a Smart Account. If you need on-chain spending controls or multi-party approval, look at Delegated. ## How Upgrade Works Upgrading doesn't replace your EOA — it adds a Smart Account alongside it: 1. Your **EOA wallet stays as-is** — same address, same balances, same functionality 2. You **deploy a Smart Account** on your chosen chain(s) via MCP, AI Assistant, or Web App 3. Your EOA becomes the **owner** of the Smart Account contract 4. You now have **two accounts**: your original EOA and the new Smart Account 5. Assets on the EOA remain on the EOA — you move what you need to the Smart Account The Smart Account has its own address on each chain where it's deployed. You fund it by transferring from your EOA or from external sources. Moving from Smart Account to Delegated is a configuration change on the same contract — no redeployment needed. # Agent Wallet Source: https://developers.fd.xyz/changelog/agent-wallet Agent Wallet changelog — new features, improvements, and updates. # Agent Wallet Changelog ## February 17, 2026 ### Documentation Launch * Full Agent Wallet documentation published in the Developer Hub * [Wallet Architecture](/agent-wallet/wallet-architecture) — Progressive EOA → Smart Account → Delegated architecture * [Security Model](/agent-wallet/concepts/security) — TEE key management with AWS Nitro Enclaves * [Trust & Risk](/agent-wallet/concepts/trust) — Pocket money philosophy and progressive trust ### MCP Server * MCP Server documentation for connecting AI agents via Model Context Protocol * Client setup guides for Claude Desktop, Cursor, Windsurf, and custom clients * [Agent Skills](/agent-wallet/ai-integration/agent-skills) reference covering all wallet capabilities ### Interfaces * [CLI](/agent-wallet/ai-integration/cli) documentation with full command reference * [AI Assistant](/agent-wallet/ai-integration/ai-assistant) guide for the conversational wallet interface * [Example Prompts](/agent-wallet/resources/example-prompts) — ready-to-use prompts for common tasks ### Advanced * [Smart Account](/agent-wallet/advanced/smart-account) guide for ERC-4337 batch transactions, gas abstraction, and session keys * [Multi-Agent](/agent-wallet/advanced/multi-agent) patterns for fleet deployments # Changelog Source: https://developers.fd.xyz/changelog/overview Product updates, new features, and improvements across Finance District. # Changelog Stay up to date with the latest changes across Finance District products. Select a product from the sidebar to see its changelog. Updates to AI-native wallet infrastructure, MCP server, and interfaces. Updates to the payment gateway, SDKs, commerce protocols, and API. # Prism Source: https://developers.fd.xyz/changelog/prism Prism changelog — new features, improvements, and updates. # Prism Changelog ## February 17, 2026 ### Documentation Launch * Full Prism documentation published in the Developer Hub * [How It Works](/prism/concepts/how-it-works) — Two-layer architecture (Prism orchestration + Spectrum settlement) * [Commerce Protocols](/prism/concepts/commerce-protocols) — x402 live, ACP and UCP coming soon ### x402 Protocol * [x402 Documentation](/prism/concepts/x402) — Complete protocol flow, payment requirements, pricing models * HTTP 402 machine-to-machine stablecoin payments live on all supported chains ### SDK Integration * Server-side SDK guides for TypeScript ([Express](/prism/sdk/typescript/express), [NestJS](/prism/sdk/typescript/nestjs), [Next.js](/prism/sdk/typescript/nextjs), [Fastify](/prism/sdk/typescript/fastify)), Python ([FastAPI](/prism/sdk/python/fastapi), [Flask](/prism/sdk/python/flask), [Django](/prism/sdk/python/django)), and Java ([Spring Boot](/prism/sdk/java/servlet)) * `@1stdigital/prism-express` package documented with middleware configuration ### API Reference * [Prism Gateway API](/prism/api-reference/introduction) documented at `prism-gw.fd.xyz` * [Endpoints](/prism/api-reference/endpoints) — Create Charge, Get Charge, Verify Payment, List Charges, Get Balance ### Production * [Prism Console](/prism/production/console) guide for merchant dashboard at `prism.fd.xyz` * [Webhooks](/prism/production/webhooks) — Setup, payload format, signature verification, retry policy * [Network Support](/prism/production/network-support) — Ethereum, Base, BSC, Arbitrum (mainnet + testnet) # AI Tools Source: https://developers.fd.xyz/overview/developer-tools/ai-tools Feed Finance District documentation to your AI coding tools via llms.txt, MCP, and contextual actions. # AI Tools The Finance District Developer Hub is built to be consumed by AI. Every page is available in machine-readable formats so your coding assistant — Claude Code, Cursor, Windsurf, ChatGPT, or anything else — can have full context about Prism SDK integration, x402 payment flows, and Agent Wallet architecture while you build. ## llms.txt The Developer Hub publishes [llms.txt](https://llmstxt.org/) files — a standard format for giving LLMs structured knowledge about a site. | File | Description | | ----------------------------------------------------------- | ----------------------------------------------- | | [`/llms.txt`](https://developers.fd.xyz/llms.txt) | Index of all pages with titles and descriptions | | [`/llms-full.txt`](https://developers.fd.xyz/llms-full.txt) | Full content of every page in a single file | ### How to use **In a system prompt or custom instruction:** ``` Reference the Finance District developer documentation at: https://developers.fd.xyz/llms-full.txt ``` **In Claude Code or Claude Projects:** Add the llms-full.txt URL as a project knowledge source. Claude will then have complete context about all Finance District APIs, SDKs, and integration patterns. **In ChatGPT or any LLM:** Paste the URL or the contents of `llms-full.txt` into the conversation when asking for integration help. ## Docs MCP Server Every page in the Developer Hub is also available through an MCP server. This lets AI tools query the documentation dynamically rather than loading all of it up front. ### Cursor In Cursor Settings → MCP Servers, add: ```json theme={null} { "mcpServers": { "finance-district-docs": { "url": "https://developers.fd.xyz/mcp" } } } ``` Cursor will be able to search and retrieve relevant documentation pages on demand as you work. ### Claude Code ```bash theme={null} claude mcp add fd-docs --transport streamable-http https://developers.fd.xyz/mcp ``` ### VS Code (GitHub Copilot) In your VS Code MCP configuration (`.vscode/mcp.json`): ```json theme={null} { "servers": { "finance-district-docs": { "type": "http", "url": "https://developers.fd.xyz/mcp" } } } ``` ### Windsurf In Windsurf Settings → MCP Configuration: ```json theme={null} { "mcpServers": { "finance-district-docs": { "url": "https://developers.fd.xyz/mcp" } } } ``` ## Per-Page Contextual Actions Every page in the Developer Hub has contextual actions in the top-right corner. Use these to quickly pipe content to your preferred AI tool: * **Copy as Markdown** — Copy the page content in a format LLMs understand well * **Open in ChatGPT / Claude / Perplexity** — Opens a new conversation pre-loaded with the page content * **Open in Cursor / VS Code** — Adds the page as context in your editor's AI chat * **Connect via MCP** — Get the MCP server configuration for your tool These are the fastest way to get a single page into your AI tool's context. ## Tips for AI-Assisted Development * **Start with llms-full.txt** for broad context — it's a single file with everything. Use the MCP server when you want your tool to search docs dynamically. * **Include your SDK package** (`@1stdigital/prism-express`, `finance-district`, etc.) in the AI tool's context alongside the docs. The type definitions contain detailed documentation. * **Reference the [x402 specification](https://www.x402.org/)** alongside FD docs for protocol-aware code generation. * **Ask for test generation** — an AI tool with full docs context can generate test cases covering the 402 response → payment → verification flow. Looking to connect your AI agent to Agent Wallet capabilities (wallets, balances, transactions)? That's a different MCP server. See [MCP Compatible Clients](/agent-wallet/ai-integration/mcp-clients) for Agent Wallet MCP setup. # Testnet Faucet Source: https://developers.fd.xyz/overview/developer-tools/faucet Get testnet stablecoins for development and testing. # Testnet Faucet To test Prism integrations and Agent Wallet transactions without real funds, you'll need testnet stablecoins. The [First Digital Labs Testnet Faucet](https://www.firstdigitallabs.com/testnet-faucet) provides test FDUSD and other testnet tokens for development. Get test FDUSD and other stablecoins on supported testnet chains ## Getting Test Tokens 1. Visit the [First Digital Labs Testnet Faucet](https://www.firstdigitallabs.com/testnet-faucet) 2. Enter your wallet address (your Agent Wallet address or any testnet address) 3. Select the token and testnet chain 4. Request tokens Tokens typically arrive within a few seconds on testnet chains. ## Using Test Tokens **Testing Prism integrations:** * Set up your SDK middleware with testnet chain IDs (e.g., Base Sepolia: `84532`) * Use test stablecoins to simulate the full x402 payment flow * Webhooks fire normally on testnet — test your full payment pipeline **Testing Agent Wallet:** * Fund your agent wallet with test stablecoins * Test transfers, swaps, and commerce payments without risk ## Testnet Chains | Testnet | Chain ID | Mainnet Equivalent | | ---------------- | -------- | ------------------ | | Ethereum Sepolia | 11155111 | Ethereum | | Base Sepolia | 84532 | Base | | BSC Testnet | 97 | BSC | | Arbitrum Sepolia | 421614 | Arbitrum | For native gas tokens (testnet ETH, etc.), use the respective chain faucets (e.g., [Base Sepolia Faucet](https://www.coinbase.com/faucets/base-ethereum-goerli-faucet)). ## Other Faucets Beyond test FDUSD, you'll likely need native gas tokens and other test tokens. These directories list faucets across chains: | Resource | Description | | ------------------------------------------------------------------------------------ | ---------------------------------------------------------------------- | | [Alchemy Faucets](https://www.alchemy.com/faucets) | Faucets for Ethereum Sepolia, Base Sepolia, Arbitrum Sepolia, and more | | [Chainlink Faucets](https://faucets.chain.link/) | Multi-chain faucet — testnet ETH, LINK, and native gas tokens | | [QuickNode Faucets](https://faucet.quicknode.com/) | Aggregated faucets for major testnets | | [Coinbase Base Faucet](https://www.coinbase.com/faucets/base-ethereum-goerli-faucet) | Base Sepolia ETH | | [BNB Chain Faucet](https://www.bnbchain.org/en/testnet-faucet) | BSC Testnet BNB | # District Pass Source: https://developers.fd.xyz/overview/fd-id Sign up for Finance District and get access to Agent Wallet, Prism, and the full ecosystem. # District Pass District Pass is your single identity across all Finance District products. One account gives you access to Agent Wallet, Prism, and the full ecosystem. Signup requires only an email — no KYC, no wallet connection, no complex onboarding. ## Sign Up Visit [app.fd.xyz](https://app.fd.xyz) to create your District Pass. Provide your email address. A confirmation code will be sent to your inbox. Enter the confirmation code. Your District Pass is now active. EVM and Solana wallets are created for you automatically — no extra steps required. That's it. You now have a District Pass with wallets ready to use across Agent Wallet and Prism. ## Authentication District Pass can be used for secure authentication across all interfaces: * **MCP clients** (Claude Desktop, Cursor, etc.) — When connecting to the Agent Wallet MCP server, you'll be prompted to authenticate via browser. The MCP client handles the flow automatically. * **FDX CLI** — Run `fdx setup` to authenticate via the Device Authorization Grant. The CLI prints a one-time code and verification URL. Tokens are stored securely in the OS credential store. * **Web App & AI Assistant** — Standard email-based login at [app.fd.xyz](https://app.fd.xyz). * **Prism Console** — Log in at [prism.fd.xyz](https://prism.fd.xyz) with your District Pass credentials. Tokens are managed automatically by each interface — you don't need to handle authentication details directly. ## Identity Philosophy Every wallet, every transaction, and every permission in Finance District traces back to an identity — someone who can be held accountable. This is the foundation of the trust model. District Pass authenticates using **Authorization Code + PKCE**, an interactive flow that establishes user context. This is a deliberate choice. The alternative — **client credentials** — authenticates a service, not a person. It tells us that *some machine* is calling, but not *who* it represents. That's machine-to-machine authentication: useful for backend integrations, but it produces anonymous activity. There is no one on the other end to trust, to audit, or to hold accountable. We don't want machines as users. We want identities as users. Today, identity means a human. A District Pass account is a person — and every agent wallet, balance, and action under that account belongs to that person. This is what makes the system auditable: key custody has an owner, spending has a source, and trust has a name behind it. But identity won't always mean human. As agentic platforms mature, autonomous agents will authenticate, navigate the web, and act with increasing independence. When an agent can establish its own identity — provable, persistent, and accountable — it becomes a user in the same sense a human is. Not a machine executing instructions, but an entity with an identity that the system and other participants can verify and trust. Finance District is built for that future. The identity model doesn't change — it extends. Whether the user behind a District Pass is a person today or an autonomous agent tomorrow, the principle is the same: every action traces back to an accountable identity. ## What's Next? Create your first agent wallet and connect via MCP Set up Prism and accept your first AI agent payment # Introduction Source: https://developers.fd.xyz/overview/introduction Finance District Developer Hub — financial access for everyone, financial autonomy for AI. # Welcome to Finance District Every great financial center — the City of London, Wall Street, Singapore — is more than a single institution. It's an ecosystem: banks, exchanges, payment networks, and service providers that connect and reinforce each other. Finance District applies the same principle to digital finance. **Finance District is an open ecosystem for digital financial infrastructure.** It brings together products, protocols, and developer tools that work independently — but become more powerful together. Financial access for everyone. Financial autonomy for AI. ## Our Flagship Solutions Today, the ecosystem has two flagship solutions: AI-native wallet infrastructure giving agents their own wallets — non-custodial, MCP-native, production-ready. Hold, send, swap, earn yield, and make payments. The payment gateway purpose-built for agentic commerce — enabling merchants and platforms to accept payments from AI shopping agents with instant stablecoin settlement and zero chargebacks. **[Agent Wallet](/agent-wallet/overview)** is buyer-side infrastructure. It gives AI agents their own wallets with secure key management inside hardware enclaves. Agents can hold stablecoins, make transfers, swap tokens, deploy to yield vaults, and pay merchants — all through MCP, CLI, or a conversational AI assistant. **[Prism](/prism/overview)** is the seller-side payment gateway. It enables e-commerce platforms and merchants to accept payments from AI shopping agents through a single integration that supports emerging commerce protocols (x402 today, ACP and UCP coming soon), with instant stablecoin settlement and zero chargebacks. Think of Prism as Stripe for agentic commerce. Together, they form the foundation of the Finance District ecosystem — but each is a standalone product. Agent Wallet works with any merchant or protocol. Prism accepts payments from any Web3 wallet, not just Agent Wallet. ## Build on Finance District Finance District is designed as an ecosystem others can build on. Whether you're building AI agents that need financial capabilities, platforms that want to accept agent payments, or entirely new financial products — the infrastructure is here. ## Quick Paths | I want to... | Start here | | ------------------------------- | ----------------------------------------------------- | | Give my AI agent a wallet | [Agent Wallet Quick Start](/agent-wallet/quickstart) | | Accept payments from AI agents | [Prism Quick Start](/prism/quickstart) | | Connect an agent via MCP | [MCP Server](/agent-wallet/ai-integration/mcp-server) | | Integrate a server-side SDK | [SDK Overview](/prism/sdk/overview) | | Understand x402 payments | [x402 Protocol](/prism/concepts/x402) | | Get test tokens for development | [Testnet Faucet](/overview/developer-tools/faucet) | ## Prerequisites To use any Finance District product, you need a [District Pass](/overview/fd-id) — a single identity across the entire ecosystem. Signup takes seconds: just an email address and a confirmation code. # Support & Community Source: https://developers.fd.xyz/overview/resources/support Get help, connect with developers, and report issues. # Support & Community ## Get Help | Channel | Best For | Link | | ----------- | ----------------------------------- | ------------------------------------------------------ | | Discord | Community questions, real-time chat | [discord.gg/fdxyz](https://discord.gg/fdxyz) | | GitHub | Bug reports, feature requests | [github.com/1stdigital](https://github.com/1stdigital) | | X (Twitter) | Announcements and updates | [@FD\_XYZ](https://x.com/FD_XYZ) | ## Discord The [Finance District Discord](https://discord.gg/fdxyz) is the primary community hub for developers. Join to ask questions, share what you're building, and get help from the team and community. ## Report a Bug File bug reports on [GitHub Issues](https://github.com/1stdigital). Include: * Which product (Agent Wallet or Prism) * Steps to reproduce * Expected vs. actual behavior * SDK version, chain, and token involved (if applicable) * Error messages or logs ## Feature Requests Submit feature requests via GitHub Issues or discuss them in Discord. The team prioritizes based on community demand and ecosystem impact. ## Useful Links * **Website:** [fd.xyz](https://fd.xyz) * **Prism Console:** [prism.fd.xyz](https://prism.fd.xyz) * **Agent Wallet App:** [app.fd.xyz](https://app.fd.xyz) * **MCP Server:** `https://mcp.fd.xyz` * **Prism Gateway:** `https://prism-gw.fd.xyz` * **Discord:** [discord.gg/fdxyz](https://discord.gg/fdxyz) * **GitHub:** [github.com/1stdigital](https://github.com/1stdigital) * **X:** [@FD\_XYZ](https://x.com/FD_XYZ) # Endpoints Source: https://developers.fd.xyz/prism/api-reference/endpoints Prism Gateway API endpoint reference — charges, transactions, and webhooks. # Endpoints Detailed reference for each Prism Gateway API endpoint. All endpoints use the base URL `https://prism-gw.fd.xyz` and require an API key in the `X-API-Key` header. See [Introduction](/prism/api-reference/introduction) for authentication and error handling details. *** ## Create Charge Create a new payment charge. Returns the charge details including payment requirements that an AI agent uses to submit payment. ``` POST /v1/charges ``` ### Request Body | Field | Type | Required | Description | | ------------- | ------ | -------- | -------------------------------------------------------- | | `amount` | string | Yes | Amount in USD (e.g., `"0.50"`) | | `token` | string | Yes | Token symbol (`FDUSD` or `USDC`) | | `chain` | string | Yes | Chain identifier (`base`, `ethereum`, `bsc`, `arbitrum`) | | `description` | string | No | Human-readable description | | `metadata` | object | No | Arbitrary key-value metadata | ```bash theme={null} curl -X POST https://prism-gw.fd.xyz/v1/charges \ -H "X-API-Key: your-api-key" \ -H "Content-Type: application/json" \ -d '{ "amount": "0.50", "token": "USDC", "chain": "base", "description": "Premium API access", "metadata": { "endpoint": "/api/premium/data", "request_id": "req_123" } }' ``` ### Response ```json theme={null} { "success": true, "data": { "id": "ch_abc123def456", "amount": "500000", "token": "USDC", "chain": "base", "status": "pending", "payment_address": "0xMerchantWallet1234567890abcdef1234567890ab", "description": "Premium API access", "metadata": { "endpoint": "/api/premium/data", "request_id": "req_123" }, "expires_at": "2025-01-15T10:35:00Z", "created_at": "2025-01-15T10:30:00Z" } } ``` *** ## Get Charge Retrieve details of a specific charge, including its current status and payment information. ``` GET /v1/charges/:id ``` ### Path Parameters | Parameter | Type | Description | | --------- | ------ | -------------------- | | `id` | string | Charge ID (`ch_...`) | ```bash theme={null} curl https://prism-gw.fd.xyz/v1/charges/ch_abc123def456 \ -H "X-API-Key: your-api-key" ``` ### Response ```json theme={null} { "success": true, "data": { "id": "ch_abc123def456", "amount": "500000", "token": "USDC", "chain": "base", "status": "completed", "payment_address": "0xMerchantWallet1234567890abcdef1234567890ab", "tx_hash": "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef", "from": "0xAgentWallet1234567890abcdef1234567890abcdef", "paid_at": "2025-01-15T10:30:45Z", "created_at": "2025-01-15T10:30:00Z" } } ``` ### Charge Statuses | Status | Description | | ----------- | ------------------------------------------ | | `pending` | Charge created, awaiting payment | | `completed` | Payment verified and settled on-chain | | `failed` | Payment failed verification | | `expired` | Charge expired before payment was received | | `cancelled` | Charge manually cancelled | *** ## Cancel Charge Cancel a pending charge. Only works for charges with `pending` status — charges that have already been paid cannot be cancelled. ``` DELETE /v1/charges/:id ``` ```bash theme={null} curl -X DELETE https://prism-gw.fd.xyz/v1/charges/ch_abc123def456 \ -H "X-API-Key: your-api-key" ``` ### Response ```json theme={null} { "success": true, "data": { "id": "ch_abc123def456", "status": "cancelled" } } ``` Returns `400` if the charge is not in `pending` status. *** ## Register Webhook Register a new webhook endpoint to receive payment event notifications. See [Webhooks](/prism/production/webhooks) for implementation details and signature verification. ``` POST /v1/webhooks ``` ### Request Body | Field | Type | Required | Description | | -------- | ------ | -------- | ---------------------------------- | | `url` | string | Yes | HTTPS URL to receive webhook POSTs | | `events` | array | Yes | Event types to subscribe to | ```bash theme={null} curl -X POST https://prism-gw.fd.xyz/v1/webhooks \ -H "X-API-Key: your-api-key" \ -H "Content-Type: application/json" \ -d '{ "url": "https://yourapp.com/webhooks/prism", "events": ["payment.completed", "payment.failed", "settlement.completed"] }' ``` ### Response ```json theme={null} { "success": true, "data": { "id": "wh_xyz789ghi012", "url": "https://yourapp.com/webhooks/prism", "events": ["payment.completed", "payment.failed", "settlement.completed"], "signing_secret": "whsec_abc123...", "created_at": "2025-01-15T10:00:00Z" } } ``` Store the `signing_secret` securely — it's only returned once at creation time. Use it to [verify webhook signatures](/prism/production/webhooks#signature-verification). *** ## List Transactions Retrieve a paginated list of transactions with optional filtering. ``` GET /v1/transactions ``` ### Query Parameters | Parameter | Type | Default | Description | | --------- | ------ | ------- | ---------------------------------------------- | | `limit` | number | 20 | Max results per page (max: 100) | | `offset` | number | 0 | Pagination offset | | `status` | string | — | Filter by status (`completed`, `failed`, etc.) | | `chain` | string | — | Filter by chain (`base`, `ethereum`, etc.) | | `from` | string | — | Start date, ISO 8601 | | `to` | string | — | End date, ISO 8601 | ```bash theme={null} curl "https://prism-gw.fd.xyz/v1/transactions?status=completed&chain=base&limit=50" \ -H "X-API-Key: your-api-key" ``` ### Response ```json theme={null} { "success": true, "data": { "transactions": [ { "id": "tx_abc123", "charge_id": "ch_abc123def456", "amount": "500000", "token": "USDC", "chain": "base", "status": "completed", "from": "0xAgentWallet1234567890abcdef1234567890abcdef", "to": "0xMerchantWallet1234567890abcdef1234567890ab", "tx_hash": "0x1234567890abcdef...", "created_at": "2025-01-15T10:30:45Z" } ], "total": 142, "limit": 50, "offset": 0 } } ``` # Introduction Source: https://developers.fd.xyz/prism/api-reference/introduction Prism Gateway REST API — create charges, verify payments, and manage your integration. # API Reference The Prism Gateway API is a REST API for creating charges, verifying payments, managing webhooks, and querying transactions. For most integrations, the [server-side SDKs](/prism/sdk/overview) are recommended — they handle authentication, protocol flow, and error handling automatically. The API is available for custom integrations or when you need direct control. ## Base URL ``` https://prism-gw.fd.xyz ``` Prism uses a single gateway URL. Testnet vs. mainnet is determined by the chain specified in your requests, not by the URL. ## Authentication Authenticate requests by including your API key in the `X-API-Key` header: ```bash theme={null} curl https://prism-gw.fd.xyz/v1/charges \ -H "X-API-Key: your-api-key" \ -H "Content-Type: application/json" ``` Get your API key from the [Prism Console](https://prism.fd.xyz) → **API Keys**. Keep your API key secret. Never expose it in client-side code, browser requests, or public repositories. ## Request Format * Content-Type: `application/json` * All request bodies are JSON * Use standard HTTP methods: `GET`, `POST`, `DELETE` ## Response Format Successful responses: ```json theme={null} { "success": true, "data": { "id": "ch_abc123def456", "amount": "10000", "token": "USDC", "chain": "base", "status": "pending" } } ``` Error responses: ```json theme={null} { "success": false, "error": { "code": "INVALID_AMOUNT", "message": "Amount must be greater than 0" } } ``` Common error codes: | HTTP Status | Error Code | Description | | ----------- | ----------------- | --------------------------------- | | 400 | `INVALID_REQUEST` | Malformed request body | | 401 | `UNAUTHORIZED` | Missing or invalid API key | | 404 | `NOT_FOUND` | Resource not found | | 422 | `INVALID_AMOUNT` | Invalid amount or token | | 429 | `RATE_LIMITED` | Too many requests | | 500 | `INTERNAL_ERROR` | Server error — retry with backoff | ## Endpoints Overview | Method | Endpoint | Description | | -------- | ------------------ | --------------------------- | | `POST` | `/v1/charges` | Create a new payment charge | | `GET` | `/v1/charges/:id` | Get charge details | | `DELETE` | `/v1/charges/:id` | Cancel a pending charge | | `POST` | `/v1/webhooks` | Register a webhook endpoint | | `GET` | `/v1/transactions` | List transactions | See [Endpoints](/prism/api-reference/endpoints) for full request/response documentation. ## Rate Limits The API enforces rate limits to ensure stability. Rate limit info is returned in response headers: | Header | Description | | ----------------------- | ------------------------------------- | | `X-RateLimit-Limit` | Maximum requests per window | | `X-RateLimit-Remaining` | Remaining requests in current window | | `X-RateLimit-Reset` | Unix timestamp when the window resets | When rate limited, the API returns **429 Too Many Requests**. Back off and retry after the reset time. ## SDKs For most integrations, use a server-side SDK instead of calling the API directly. The SDKs handle authentication, x402 protocol flow, payment verification, and type safety: Express, NestJS, Next.js, Fastify, and more FastAPI, Flask, Django Servlet-based applications # Commerce Protocols Source: https://developers.fd.xyz/prism/concepts/commerce-protocols x402, ACP, UCP — the emerging protocols enabling AI agents to shop, and how Prism abstracts them. # Commerce Protocols Multiple companies are building protocols for AI agent commerce — each covering a different part of the journey from product discovery to payment. Prism is protocol-agnostic: you integrate once, and Prism handles protocol translation behind the scenes. When a new protocol gains traction, Prism adds support without changing your integration. ## The Protocol Landscape | Layer | Protocol | Created By | Status | | ---------------------- | -------- | ------------------------ | -------------------------------- | | Settlement (payment) | x402 | Coinbase (open standard) | **Live** — Prism uses this today | | Checkout (transaction) | ACP | OpenAI | Coming soon | | Full Commerce Journey | UCP | Google | Coming soon | These protocols are complementary, not competing. x402 handles the settlement layer — the actual movement of money. ACP and UCP handle the commerce layer — product discovery, cart management, checkout flow. An agent might use ACP to find and select a product, then x402 (via Prism) to pay for it. ## x402 — Settlement Protocol x402 implements the HTTP 402 "Payment Required" status code for machine-to-machine stablecoin payments. It's Prism's primary protocol today. **How it works:** Agent requests a resource → server responds 402 with payment requirements → agent pays on-chain → server grants access. **Best for:** API monetization, data feeds, premium content, pay-per-request services — any endpoint where access requires payment. **Status:** Live. This is what you integrate with today. [Full x402 documentation →](/prism/concepts/x402) ## ACP — Agent Commerce Protocol Created by OpenAI for structured AI agent commerce. ACP covers the checkout flow: product discovery, cart management, and payment initiation. It's designed for scenarios where an agent needs to browse a catalog, select products, and complete a purchase — more structured than a simple API call. **How Prism integrates:** ACP handles the commerce flow (what the agent is buying). Prism handles the settlement (how the agent pays). When ACP support goes live with Prism, merchants get: * AI agents that can browse their product catalog programmatically * Structured checkout with stablecoin settlement via Prism * The same instant settlement and zero chargebacks as x402 **Best for:** E-commerce product purchases, multi-item orders, any scenario with a traditional checkout flow. **Status:** Coming soon. Prism is building ACP support. ## UCP — Universal Commerce Protocol Created by Google for the full commerce journey — from discovery to post-purchase. UCP is more comprehensive than ACP, covering the entire lifecycle: discovery, selection, purchase, fulfillment, returns, and support. **How Prism integrates:** Prism serves as the settlement and payment verification layer within the UCP flow. **Best for:** Complex multi-step purchases, scenarios requiring fulfillment tracking, subscription-like flows. **Status:** Coming soon. Prism is monitoring the specification and building support. ## How Prism Abstracts Protocol Complexity ```mermaid theme={null} flowchart LR X402[x402] --> P["Prism\n(Gateway)"] ACP[ACP] --> P UCP[UCP] --> P P --> App["Your Application\n(single SDK / API)"] ``` The key insight: **you integrate Prism once**. Prism handles protocol translation on the inbound side. Whether an agent pays via x402, initiates checkout via ACP, or goes through a full UCP commerce journey — your SDK integration and webhook handling stay the same. This protects your investment. As the protocol landscape evolves, you don't need to re-integrate. Prism adapts; your code doesn't change. ## What Should I Use Today? x402 is live and supported today. Ideal for API monetization and pay-per-request models. Integrate x402 payment protection in minutes with the Prism SDK. If you're building an e-commerce platform and planning for ACP or UCP, integrate Prism now with x402. When ACP and UCP support ships, your integration will gain those capabilities automatically — no code changes required. # How Prism Works Source: https://developers.fd.xyz/prism/concepts/how-it-works Architecture, transaction flow, and the two-layer design behind Prism. # How Prism Works Prism uses a two-layer architecture that separates business logic from on-chain settlement. You integrate at the orchestration layer — familiar APIs and SDKs. Settlement happens transparently on-chain through the Spectrum layer. Merchants never need to touch smart contracts directly. ## Architecture Overview ```mermaid theme={null} flowchart TB subgraph prism["Prism — Orchestration"] direction TB P1["Protocol translation · x402, ACP, UCP"] P2["Merchant management & onboarding"] P3["Transaction routing & charge creation"] P4["Webhooks & analytics"] P5["API: prism-gw.fd.xyz · Console: prism.fd.xyz"] end subgraph spectrum["Spectrum — Settlement"] direction TB S1["On-chain smart contract execution"] S2["Stablecoin settlement · FDUSD, USDC"] S3["Multi-chain · Ethereum, Base, BSC, Arbitrum"] S4["Payment verification & finality"] end prism --> spectrum ``` **Prism** is the orchestration layer. It handles everything the merchant interacts with: API credentials, charge creation, protocol translation, webhook delivery, and the Prism Console dashboard. When you integrate the Prism SDK or call the Prism Gateway API, you're talking to Prism. **Spectrum** is the settlement layer. It handles on-chain operations: verifying that stablecoin payments were made, confirming transactions on the blockchain, and ensuring finality. Spectrum operates across Ethereum, Base, BSC, and Arbitrum — both mainnets and testnets. This separation means Prism can evolve its business logic, add new commerce protocols, and improve the merchant experience — without changing the underlying settlement infrastructure. ## Transaction Lifecycle Here's what happens during a payment, step by step: An AI agent makes an HTTP request to the merchant's protected endpoint (e.g., `GET /api/premium/data`). The Prism SDK middleware intercepts the request and returns **HTTP 402 Payment Required**. The response includes payment requirements: amount, token, chain, recipient address, and a deadline. The agent's wallet reads the 402 response, constructs a stablecoin payment, and submits it on-chain. The agent retries the original request, this time including the payment proof (transaction signature) in the `X-PAYMENT` header. Prism forwards the payment proof to Spectrum, which verifies the on-chain transaction — correct amount, correct token, correct recipient, transaction confirmed. Once verified, settlement is complete. The stablecoins are in the merchant's wallet. Prism confirms the payment to the SDK middleware. The middleware allows the original request to proceed. The agent receives the requested resource. **Timing:** End-to-end settlement takes a few seconds, depending on the chain. Base transactions typically settle in \~2 seconds, while Ethereum takes \~12 seconds. After the request completes, Prism delivers a webhook notification to the merchant's configured endpoint with the full transaction details. ## Protocol Support Prism is protocol-agnostic. Today it supports x402; ACP and UCP support is coming soon. The architecture handles protocol translation, so your integration stays the same regardless of which protocol the agent uses. ### x402 (Live) The primary protocol for direct API and content monetization. The agent hits an endpoint, gets a 402 response, pays, and gets access. Simple HTTP-level flow. Best for API endpoints, data feeds, premium content, and any pay-per-request model. [Full x402 documentation →](/prism/concepts/x402) ### ACP — Agent Commerce Protocol (Coming Soon) Created by OpenAI for structured AI agent commerce. Covers product discovery, cart management, and checkout flow. Prism will serve as the settlement layer — ACP handles the commerce flow, Prism handles the money. ### UCP — Universal Commerce Protocol (Coming Soon) Created by Google for the full commerce journey from discovery to post-purchase. More comprehensive than ACP, covering fulfillment and returns. Prism integrates as the settlement and payment verification layer. [Protocol landscape overview →](/prism/concepts/commerce-protocols) ## Settlement All settlements happen in stablecoins — FDUSD and USDC. The transaction is final once confirmed on the blockchain. No chargebacks, no disputes, no clawbacks. Settlement flows directly from the agent's wallet to the merchant's wallet. No intermediary holds the funds. The merchant can hold stablecoins, swap to fiat via an off-ramp, or transfer to another wallet. | Chain | Settlement Time | Gas Cost | | -------- | --------------- | -------- | | Base | \~2 seconds | Very low | | Arbitrum | \~2 seconds | Very low | | BSC | \~3 seconds | Low | | Ethereum | \~12 seconds | Higher | [Settlement deep dive →](/prism/concepts/settlement) ## Prism Console The [Prism Console](https://prism.fd.xyz) is the management dashboard where merchants and platforms: * Create and manage API keys * Monitor transactions in real-time * Configure payment settings (accepted tokens, chains, pricing) * Set up webhook endpoints * View revenue analytics [Console guide →](/prism/production/console) # Stablecoin Settlement Source: https://developers.fd.xyz/prism/concepts/settlement How Prism settles payments in stablecoins — instant, final, no chargebacks. # Stablecoin Settlement All Prism payments settle in stablecoins on-chain. This is a deliberate design choice: stablecoins give you instant finality, zero chargebacks, and global reach — properties that card rails can't match, and properties that AI agents need to transact autonomously. ## Why Stablecoins? | | Traditional (Card Rails) | Prism (Stablecoin Rails) | | -------------------- | -------------------------- | ---------------------------- | | **Settlement speed** | 2–5 business days | Seconds | | **Chargebacks** | Possible for months | Transactions are final | | **Fees** | 2.9% + \$0.30 typical | Competitive settlement fees | | **Currency** | Fiat, with FX conversion | USD-denominated stablecoins | | **Merchant setup** | Merchant account required | Wallet address is sufficient | | **Machine-native** | Requires card numbers, PII | Wallet-to-wallet, no PII | The last point matters most for agentic commerce: AI agents can hold and transact with stablecoins directly. No credit card numbers, no bank accounts, no personally identifiable information. An agent wallet plus stablecoins is the native payment method for machine-to-machine commerce. ## Supported Stablecoins | Stablecoin | Symbol | Peg | Issuer | Supported Chains | | ----------------- | ------ | --- | ------------- | ----------------------------- | | USD Coin | USDC | USD | Circle | Ethereum, Base, BSC, Arbitrum | | First Digital USD | FDUSD | USD | First Digital | Ethereum, BSC | See [Network Support](/prism/production/network-support) for the full chain and token compatibility matrix. ## How Settlement Works An AI agent's wallet holds stablecoins. The agent initiates a payment via the x402 protocol (or ACP/UCP in the future) by submitting a signed payment with the request. The Spectrum settlement layer executes the stablecoin transfer from the agent's wallet to the merchant's wallet on-chain. Spectrum verifies the on-chain transaction: correct amount, correct token, correct recipient, transaction confirmed on the blockchain. Prism confirms the payment to the SDK middleware, which allows the original request to proceed. Prism delivers a webhook to the merchant's configured endpoint with full transaction details — amount, token, chain, transaction hash, and timestamp. Settlement is **direct wallet-to-wallet**. No intermediary holds the funds, no escrow by default. Once confirmed on-chain, the stablecoins are in the merchant's wallet — available immediately. ## Settlement on Different Chains Settlement happens on the chain specified in the payment requirements. Merchants configure their preferred chain(s) in the [Prism Console](https://prism.fd.xyz), and the 402 response tells the agent which chain to pay on. | Chain | Settlement Time | Gas Cost | Recommended For | | -------- | --------------- | -------- | ---------------------------- | | Base | \~2 seconds | Very low | Most use cases (recommended) | | Arbitrum | \~2 seconds | Very low | High-frequency payments | | BSC | \~3 seconds | Low | FDUSD settlement | | Ethereum | \~12 seconds | Higher | Large value transactions | **Chain selection guidance:** * **Base** is recommended for most use cases — fast settlement and minimal gas costs * Consider your agents' chain preferences — settlement is simplest when both sides use the same chain * Accept payments on multiple chains for maximum compatibility with different agent wallets Use testnet chains (Base Sepolia, Ethereum Sepolia, BSC Testnet, Arbitrum Sepolia) during development. Same settlement flow, no real funds required. ## Merchant Payouts Once stablecoins settle to the merchant's wallet, they're yours. Options include: * **Hold stablecoins** — Keep the USD-denominated balance as-is * **Off-ramp to fiat** — Convert to traditional currency via off-ramp services * **Transfer** — Move funds to another wallet or treasury address * **Re-invest** — Use stablecoins in DeFi or other on-chain activity The [Prism Console](https://prism.fd.xyz) shows settlement history, balances by chain, and transaction details for reconciliation. # x402 Protocol Source: https://developers.fd.xyz/prism/concepts/x402 The HTTP 402 payment protocol — how machines pay machines with stablecoins. # x402 Protocol x402 implements the long-reserved HTTP 402 "Payment Required" status code for machine-to-machine stablecoin payments. It's an open protocol — not proprietary to Finance District — and Prism uses it as the primary payment protocol for AI agent commerce. The flow is simple: a client requests a resource, the server responds with 402 and payment requirements, the client pays on-chain, and the server grants access. No API keys to exchange, no OAuth handshakes — just HTTP and stablecoins. This page covers x402 from the **merchant/seller** perspective — implementing it with Prism. For the **buyer/agent** side, see [x402 Payments in Agent Wallet](/agent-wallet/concepts/x402-payments). ## The Protocol Flow ```mermaid theme={null} sequenceDiagram participant A as Agent participant M as Merchant (Prism SDK) participant B as Blockchain A->>M: GET /api/data M-->>A: 402 Payment Required Note over M: x402Version: 1
network: "base", asset: "usdc"
amount: "10000"
recipient: "0xMerchant..." A->>B: On-chain stablecoin payment A->>M: GET /api/data
X-PAYMENT: { signed payment } M->>B: Verify via Spectrum B-->>M: Confirmed M-->>A: 200 OK
X-PAYMENT-RESPONSE: 0xTxHash
Body: { requested data } ``` 1. The agent makes a standard HTTP request 2. The Prism middleware returns **402** with a JSON body describing the accepted payment 3. The agent's wallet constructs and signs a stablecoin payment 4. The agent retries the request with the signed payment in the `X-PAYMENT` header 5. Prism's Spectrum layer settles the payment on-chain and verifies it 6. The response includes the transaction hash in `X-PAYMENT-RESPONSE` ## Implementing x402 with Prism You don't implement the x402 protocol directly — the Prism SDK handles it. You configure what to charge and the middleware handles the 402 response, payment verification, and settlement automatically. ```typescript theme={null} import { prismPaymentMiddleware } from "@1stdigital/prism-express"; app.use( prismPaymentMiddleware( { apiKey: process.env.PRISM_API_KEY, baseUrl: "https://prism-gw.fd.xyz", }, { "/api/premium": { price: "$0.01", description: "Premium API access", }, "/api/ai/generate": { price: "$0.50", description: "AI content generation", }, }, ), ); ``` Your endpoint code only runs after payment is confirmed. See the [Quick Start](/prism/quickstart) for a complete example or [SDK Overview](/prism/sdk/overview) for framework-specific guides. ## Payment Requirements (402 Response) When the middleware returns 402, the response body contains: | Field | Type | Description | | ------------------ | ------ | ---------------------------------------------------- | | `x402Version` | number | Protocol version (currently `1`) | | `paymentRequired` | bool | Always `true` for 402 responses | | `acceptedPayments` | array | List of accepted payment options | | `description` | string | Human-readable description of what's being purchased | | `priceUSD` | string | Price in USD | Each entry in `acceptedPayments` contains: | Field | Type | Description | | ------------- | ------ | ---------------------------------------------- | | `scheme` | string | Payment scheme (e.g., `eip3009`) | | `network` | string | Chain identifier (e.g., `base`, `eth-sepolia`) | | `asset` | string | Token (e.g., `usdc`) | | `amount` | string | Amount in token base units | | `recipient` | string | Merchant's wallet address | | `nonce` | string | Unique nonce for replay protection | | `validBefore` | number | Unix timestamp deadline for payment | ## Payment Verification When the agent sends a request with the `X-PAYMENT` header: 1. Prism parses the signed payment from the header 2. Forwards it to the Spectrum settlement layer 3. Spectrum executes the on-chain transfer and verifies: correct amount, correct token, correct recipient, valid signature 4. If settlement succeeds, the request proceeds and the transaction hash is returned in the `X-PAYMENT-RESPONSE` header 5. If settlement fails, the middleware returns 402 with an error The merchant never needs to verify payments manually — the middleware handles everything between the 402 and the 200. ## Pricing Models ### Fixed Pricing Set a static price per endpoint — every request costs the same: ```typescript theme={null} { "/api/weather": { price: "$0.001", description: "Weather data" } } ``` ### Tiered Pricing Different prices for different endpoints: ```typescript theme={null} { "/api/basic/*": { price: "$0.0001", description: "Basic API tier" }, "/api/premium/*": { price: "$0.01", description: "Premium API tier" }, "/api/ai/generate": { price: "$0.50", description: "AI generation" } } ``` ### Dynamic Pricing For endpoints where the price depends on the request, apply per-route middleware: ```typescript theme={null} app.get( "/api/compute", prismPaymentMiddleware(config, { "/api/compute": { price: calculatePrice(req), // dynamic based on request description: "Compute resources", }, }), (req, res) => { res.json({ result: "Computed" }); }, ); ``` ## x402 Specification x402 is an open standard. Learn more at [x402.org](https://www.x402.org/). Prism implements the specification and handles all the protocol complexity through the SDK — you configure prices, and the middleware does the rest. # Overview Source: https://developers.fd.xyz/prism/overview Prism — the payment gateway purpose-built for agentic commerce. # Prism Prism is a payment gateway that enables merchants and e-commerce platforms to accept payments from AI shopping agents. When an AI assistant purchases something on behalf of a user, Prism handles the stablecoin settlement — instant finality, zero chargebacks. A single integration covers multiple commerce protocols including x402, ACP, and UCP, so you're ready regardless of which protocol your buyers' agents speak. Think of Prism as Stripe for agentic commerce. Where Stripe settles in fiat via card rails, Prism settles in stablecoins on-chain. Both can coexist — different rails for different types of buyers. ## Why Agentic Commerce? E-commerce platforms are already building for AI agents. Major headless platforms are implementing protocols like ACP (OpenAI) and UCP (Google) — enabling AI agents to discover products, add to cart, and check out programmatically. But these protocols handle the **commerce flow**. They don't handle **settlement**. That's the gap Prism fills. Platforms have built the checkout experience. Prism completes the stack with settlement infrastructure — the layer that actually moves money between agent wallets and merchants. ## How It Works ```mermaid theme={null} sequenceDiagram participant A as User's AI Agent participant M as Merchant (Prism SDK) participant B as Blockchain A->>M: GET /api/product M-->>A: 402 Payment Required Note right of M: amount, token, chain A->>B: Stablecoin payment A->>M: GET /api/product (with payment proof) M->>B: Verify on-chain B-->>M: Confirmed M-->>A: 200 OK (product/data) ``` 1. An AI agent requests a resource from the merchant's protected endpoint 2. Prism middleware responds with **HTTP 402** — payment required, specifying amount, token, and chain 3. The agent's wallet sends a stablecoin payment on-chain 4. The agent retries the request with payment proof 5. Prism verifies the on-chain transaction via the Spectrum settlement layer 6. The request proceeds — the merchant has the money, settled and final When the agent uses an [Agent Wallet](/agent-wallet/overview), the entire flow stays within one ecosystem — agent wallet to Prism to merchant settlement. No intermediaries. ## Core Capabilities * **Agent Payment Acceptance** — Accept purchases initiated by AI agents shopping on behalf of users * **Stablecoin Settlement** — Instant on-chain settlement in FDUSD and USDC * **Zero Chargebacks** — Stablecoin transactions are final. No disputes, no clawbacks * **Protocol Agnostic** — x402 live today; ACP and UCP support coming soon. One integration covers all * **Platform-First Architecture** — Integrate once, enable all your merchants * **Multi-Chain Support** — Settlement on Ethereum, Base, BSC, and Arbitrum ## Two Integration Paths ### For Platforms Integrate Prism as a payment option in your platform. Your merchants can accept agent payments without individual integrations — you handle it once at the platform level. ### For Merchants Connect directly via the Prism Gateway API or server-side SDKs. Accept agent purchases and receive instant stablecoin settlement to your wallet. Accept your first AI agent payment in minutes Two-layer architecture and transaction lifecycle Server-side SDKs for TypeScript, Python, and Java Prism Gateway REST API documentation # Prism Console Source: https://developers.fd.xyz/prism/production/console Manage your Prism integration — API keys, transactions, analytics, and settings. # Prism Console The [Prism Console](https://prism.fd.xyz) is the management dashboard for your Prism integration. It's where you create API keys, monitor transactions, configure payment settings, and set up webhooks. Log in with your [District Pass](/overview/fd-id). ## Getting Started 1. Go to [prism.fd.xyz](https://prism.fd.xyz) and sign in with your District Pass 2. Create a new project or use the default project 3. You'll land on the dashboard showing an overview of your integration Each project has its own API keys, payment configuration, and transaction history. Use separate projects for different applications or environments. ## API Keys Navigate to **API Keys** to create and manage credentials for the Prism Gateway. * **Generate a key** — Click "Create API Key" and give it a descriptive name * **Testnet vs. mainnet** — Each project can have keys for testnet chains and mainnet chains * **Rotate keys** — Generate a new key and deprecate the old one without downtime * **Security** — API keys authenticate via the `X-API-Key` header. Never expose them in client-side code, public repositories, or logs Treat your API key like a password. If compromised, rotate it immediately in the Console. ## Transaction Monitoring The **Transactions** view shows every payment processed through your integration: * **Real-time feed** — Transactions appear as they're processed * **Details** — Each transaction shows amount, token, chain, status, timestamp, payer address, and transaction hash * **Status tracking** — Pending, completed, failed, and settled states * **Filtering** — Filter by date range, status, chain, or token * **Export** — Download transaction data for reconciliation Click any transaction to see the full details, including the on-chain transaction link. ## Payment Configuration Under **Settings → Payments**, configure how your endpoints accept payments: * **Accepted tokens** — Select which stablecoins to accept (FDUSD, USDC) * **Accepted chains** — Select which chains to settle on (Ethereum, Base, BSC, Arbitrum) * **Merchant wallet** — Set the wallet address where settlement funds are received * **Default pricing** — Configure default price and currency for your endpoints Accept payments on multiple chains for maximum compatibility with different agent wallets. Base is recommended as the default — fastest settlement and lowest gas costs. ## Analytics The **Analytics** dashboard provides insight into your payment activity: * **Revenue overview** — Total revenue, daily, weekly, and monthly trends * **Transaction volume** — Number of transactions over time * **Breakdown by endpoint** — See which endpoints generate the most revenue * **Chain distribution** — Which chains your payments settle on ## Webhook Configuration Under **Settings → Webhooks**, configure endpoints to receive real-time payment notifications: * **Add endpoint** — Register a URL to receive webhook events * **Select events** — Choose which event types to subscribe to * **Signing secret** — Copy the secret for [signature verification](/prism/production/webhooks) * **Test delivery** — Send a test webhook to verify your endpoint is working * **Delivery log** — View delivery history, response codes, and retry status See [Webhooks](/prism/production/webhooks) for implementation details and code examples. ## Team Management Under **Settings → Team**, manage who has access to your project: * **Invite members** — Add team members by email * **Roles** — Assign roles: Admin (full access), Developer (API keys and transactions), Viewer (read-only) * **Remove access** — Revoke team member access at any time # Network Support Source: https://developers.fd.xyz/prism/production/network-support Supported blockchains, tokens, and environments for Prism settlement. # Network Support Prism supports settlement on Ethereum, Base, BSC, and Arbitrum — both mainnets and their corresponding testnets. Merchants configure which chains and tokens to accept in the [Prism Console](https://prism.fd.xyz). ## Supported Chains ### Mainnet | Chain | Chain ID | Settlement Time | Gas Cost | Notes | | -------- | -------- | --------------- | -------- | ------------------------- | | Base | 8453 | \~2 seconds | Very low | Recommended for most uses | | Arbitrum | 42161 | \~2 seconds | Very low | High-frequency payments | | BSC | 56 | \~3 seconds | Low | FDUSD settlement | | Ethereum | 1 | \~12 seconds | Higher | Large value transactions | ### Testnet | Chain | Chain ID | Faucet | | ---------------- | -------- | -------------------------------------------------- | | Base Sepolia | 84532 | [Testnet Faucet](/overview/developer-tools/faucet) | | Arbitrum Sepolia | 421614 | [Testnet Faucet](/overview/developer-tools/faucet) | | BSC Testnet | 97 | [Testnet Faucet](/overview/developer-tools/faucet) | | Ethereum Sepolia | 11155111 | [Testnet Faucet](/overview/developer-tools/faucet) | Use testnet chains during development and testing. The Prism Gateway, SDK, and webhook flow work identically on testnet — no code changes needed to switch between testnet and mainnet. ## Supported Tokens | Token | Symbol | Chains | | ----------------- | ------ | ----------------------------- | | First Digital USD | FDUSD | Ethereum, BSC | | USD Coin | USDC | Ethereum, Base, BSC, Arbitrum | Prism supports settlement in any stablecoin that implements the **EIP-3009** (`transferWithAuthorization`) interface. FDUSD is available on Ethereum and BSC; USDC is available on all supported chains. ## Choosing a Chain **Base is recommended** for most use cases. It offers the fastest settlement times and lowest gas costs, making it ideal for high-frequency, low-value payments like API monetization. Consider these factors when choosing: | Factor | Recommendation | | -------------------------------- | -------------------------------- | | API monetization / micropayments | Base or Arbitrum (low gas, fast) | | Large value transactions | Ethereum (deepest liquidity) | | FDUSD settlement | BSC or Ethereum | | Maximum agent compatibility | Accept multiple chains | Accept payments on multiple chains to maximize compatibility with different agent wallets. Settlement is simplest when both the agent and merchant operate on the same chain. ## Environments Prism uses a single gateway URL for both testnet and mainnet operations. The chain specified in the payment configuration determines whether the transaction settles on a testnet or mainnet chain. | Component | URL | | ------------- | ------------------------- | | Prism Gateway | `https://prism-gw.fd.xyz` | | Prism Console | `https://prism.fd.xyz` | * Use testnet chain IDs during development (e.g., Base Sepolia: `84532`) * Use mainnet chain IDs for production (e.g., Base: `8453`) * API keys are the same across environments * The [Testnet Faucet](/overview/developer-tools/faucet) provides test tokens for development # Webhooks Source: https://developers.fd.xyz/prism/production/webhooks Receive real-time payment notifications via webhooks. # Webhooks Webhooks deliver real-time notifications about payment events. Prism POSTs a signed JSON payload to your configured endpoint whenever a payment is completed, fails, or settles. Use webhooks to update order status, trigger fulfillment, log transactions, or sync with your backend. ## Setup Add an HTTP POST endpoint to your application that accepts JSON payloads. Go to [Prism Console](https://prism.fd.xyz) → **Settings → Webhooks** → **Add Endpoint**. Enter your URL and select the events you want to receive. The Console generates a signing secret for your endpoint. Copy it — you'll need it to verify webhook signatures. Always verify the `X-Prism-Signature` header before processing events. See [Signature Verification](#signature-verification) below. ### Example Endpoint ```typescript theme={null} import express from "express"; import crypto from "crypto"; const app = express(); app.use(express.raw({ type: "application/json" })); const WEBHOOK_SECRET = process.env.PRISM_WEBHOOK_SECRET; app.post("/webhooks/prism", (req, res) => { const signature = req.headers["x-prism-signature"] as string; const payload = req.body.toString(); // Verify signature const expected = crypto .createHmac("sha256", WEBHOOK_SECRET) .update(payload) .digest("hex"); if (!crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected))) { return res.status(401).send("Invalid signature"); } const event = JSON.parse(payload); switch (event.type) { case "payment.completed": // Payment verified and settled on-chain handlePaymentCompleted(event.data); break; case "payment.failed": // Payment verification failed handlePaymentFailed(event.data); break; case "settlement.completed": // Funds settled to merchant wallet handleSettlement(event.data); break; } // Respond 200 quickly — process asynchronously if needed res.status(200).send("OK"); }); ``` ## Event Types | Event | Description | Triggered When | | ---------------------- | ---------------------------------------- | --------------------------------------------- | | `payment.pending` | Payment submitted, awaiting confirmation | After agent submits payment on-chain | | `payment.completed` | Payment verified and settled | After on-chain confirmation by Spectrum | | `payment.failed` | Payment verification failed | Invalid transaction, wrong amount, or expired | | `settlement.completed` | Funds settled to merchant wallet | After Spectrum settlement completes | ## Payload Format All webhook payloads follow the same structure: ```json theme={null} { "id": "evt_abc123def456", "type": "payment.completed", "created": "2025-01-15T10:30:00Z", "data": { "payment_id": "pay_xyz789ghi012", "amount": "10000", "token": "USDC", "chain": "base", "from": "0xAgentWallet1234567890abcdef1234567890abcdef", "to": "0xMerchantWallet1234567890abcdef1234567890ab", "tx_hash": "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef", "endpoint": "/api/premium/data", "status": "completed" } } ``` | Field | Description | | ----------------- | ----------------------------------------------- | | `id` | Unique event identifier | | `type` | Event type (see table above) | | `created` | ISO 8601 timestamp | | `data.payment_id` | Prism payment/charge identifier | | `data.amount` | Amount in token base units | | `data.token` | Token symbol (FDUSD, USDC) | | `data.chain` | Chain where settlement occurred | | `data.from` | Agent wallet address | | `data.to` | Merchant wallet address | | `data.tx_hash` | On-chain transaction hash | | `data.endpoint` | The merchant endpoint that triggered the charge | | `data.status` | Payment status | ## Signature Verification All webhooks include an `X-Prism-Signature` header containing an HMAC-SHA256 signature of the raw request body, using your webhook signing secret as the key. **Always verify signatures before processing events.** This prevents spoofed requests from triggering actions in your application. ```typescript theme={null} import crypto from "crypto"; function verifyWebhookSignature( payload: string, signature: string, secret: string ): boolean { const expected = crypto .createHmac("sha256", secret) .update(payload) .digest("hex"); return crypto.timingSafeEqual( Buffer.from(signature), Buffer.from(expected) ); } ``` ```python theme={null} import hmac import hashlib def verify_webhook_signature(payload: bytes, signature: str, secret: str) -> bool: expected = hmac.new( secret.encode(), payload, hashlib.sha256 ).hexdigest() return hmac.compare_digest(signature, expected) ``` Use constant-time comparison (`timingSafeEqual` / `hmac.compare_digest`) to prevent timing attacks. Do not use `===` or `==` for signature comparison. ## Retry Policy If your endpoint returns a non-2xx response, times out, or is unreachable, Prism retries delivery with exponential backoff: | Attempt | Delay After Failure | | ------- | ------------------- | | 1 | Immediate | | 2 | 5 minutes | | 3 | 30 minutes | | 4 | 2 hours | | 5 | 24 hours | After 5 failed attempts, the event is marked as failed in the Console. You can view delivery logs and manually retry from **Settings → Webhooks → Delivery Log**. **Best practices:** * Respond with 200 as quickly as possible — do heavy processing asynchronously * Use a queue (SQS, Redis, etc.) for webhook processing in high-volume scenarios * Implement idempotency using the event `id` to handle duplicate deliveries ## Testing Webhooks * **Console test button** — Send a test event from the Prism Console to verify your endpoint is reachable and responding correctly * **Local development** — Use a tunneling tool like [ngrok](https://ngrok.com) to expose your local endpoint for testing * **Testnet** — All testnet transactions trigger real webhook deliveries, so you can test the full flow without real funds # Quick Start Source: https://developers.fd.xyz/prism/quickstart Accept your first AI agent payment with Prism in minutes. # Quick Start This guide takes you from zero to accepting AI agent payments. You'll set up a server endpoint that requires stablecoin payment via the x402 protocol — any AI agent with a compatible wallet can pay and access it. ## Prerequisites * A [District Pass](/overview/fd-id) account * A server-side application (Node.js, Python, or Java) * A wallet address to receive settlement funds For testing, you'll use testnet chains — no real funds needed. Use the [Testnet Faucet](/overview/faucet) to get test tokens for simulating agent payments. ## Step 1: Get Your API Credentials 1. Log in to the [Prism Console](https://prism.fd.xyz) with your District Pass 2. Create a new project or use the default project 3. Navigate to **API Keys** and generate a key 4. Copy your API key — you'll need it for the SDK configuration Keep your API key secret. Never expose it in client-side code or commit it to version control. ## Step 2: Install the SDK `bash npm install @1stdigital/prism-express ` `bash pip install finance-district ` ```xml theme={null} xyz.financedistrict prism-sdk LATEST ``` See [SDK Overview](/prism/sdk/overview) for all supported frameworks including NestJS, Next.js, FastAPI, Flask, Django, and more. ## Step 3: Add Payment Middleware Protect an endpoint with x402 payment verification. When an agent hits this endpoint without paying, it gets a `402 Payment Required` response with payment instructions. After paying, the request goes through. ```typescript theme={null} import express from "express"; import { prismPaymentMiddleware } from "@1stdigital/prism-express"; const app = express(); app.use( prismPaymentMiddleware( { apiKey: process.env.PRISM_API_KEY, baseUrl: "https://prism-gw.fd.xyz", }, { "/api/premium": { price: "$0.01", description: "Premium API access", }, } ) ); app.get("/api/premium", (req, res) => { res.json({ message: "Premium content", payer: req.payer, // wallet address that paid }); }); app.listen(3000, () => console.log("Server running on port 3000")); ``` ```python theme={null} from fastapi import FastAPI, Depends from finance_district import require_payment, PaymentVerified app = FastAPI() @app.get("/api/premium") async def premium_content( payment: PaymentVerified = Depends(require_payment(0.01, "USD")) ): return {"content": "Premium data", "payer": payment.payer} ``` ```python theme={null} from flask import Flask, jsonify from finance_district import require_payment app = Flask(__name__) @app.route("/api/premium") @require_payment(amount=0.01, currency="USD") def premium_content(): return jsonify({"content": "Premium data"}) ``` ## Step 4: Test the Integration Start your server and make a request to the protected endpoint: ```bash theme={null} curl http://localhost:3000/api/premium ``` You should get a `402 Payment Required` response: ```json theme={null} { "x402Version": 1, "paymentRequired": true, "acceptedPayments": [ { "scheme": "eip3009", "network": "base-sepolia", "asset": "usdc", "amount": "10000", "recipient": "0xYourWallet..." } ], "description": "Premium API access", "priceUSD": "0.01" } ``` This confirms the middleware is working. Any AI agent with a compatible wallet (including [Agent Wallet](/agent-wallet/overview)) can now pay and access the endpoint automatically. To test the full payment flow, use the [Agent Wallet CLI](/agent-wallet/ai-integration/cli) or connect an Agent Wallet via [MCP](/agent-wallet/ai-integration/mcp-server) and ask the agent to access your endpoint. ## Step 5: Go Live When you're ready for production: 1. Switch to mainnet in your Prism Console project settings 2. Generate a mainnet API key 3. Update your wallet address to your production wallet 4. Configure [webhooks](/prism/production/webhooks) for payment notifications 5. Monitor transactions in the [Prism Console](https://prism.fd.xyz) Prism uses testnet blockchains for development — same API, same gateway URL, different chain IDs. No separate sandbox environment needed. ## What's Next? Framework-specific integration for Express, NestJS, FastAPI, Django, and more Two-layer architecture and transaction lifecycle Real-time payment notifications Prism Gateway REST API documentation # Java Servlet Integration Source: https://developers.fd.xyz/prism/sdk/java/servlet Protect Java web applications with blockchain micropayments ## Overview The **prism-servlet** package provides a generic Servlet Filter for payment-protecting Java web applications using the x402 protocol. It works with any servlet-based framework (Tomcat, Jetty, Spring Boot, Jakarta EE, etc.). Works with any Servlet 3.0+ container Captures output before settlement validation Standard javax.servlet.Filter interface *** ## Installation ```xml theme={null} org.fdtech.prism prism-servlet 1.0.0 ``` ```gradle theme={null} implementation 'org.fdtech.prism:prism-servlet:1.0.0' ``` *** ## Quick Start ### Configuration via web.xml ```xml theme={null} PrismFilter org.fdtech.prism.servlet.PrismFilter apiKey dev-key-123 baseUrl https://prism-api.test.1stdigital.tech routes /api/premium:0.01:Premium API access /api/weather:$0.001:Weather data access PrismFilter /api/* ``` ### Servlet Implementation ```java theme={null} import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.*; import java.io.IOException; @WebServlet("/api/premium") public class PremiumServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // Access payer address (set by PrismFilter) String payer = (String) request.getAttribute("prism_payer"); response.setContentType("application/json"); response.getWriter().write(String.format( "{\"message\":\"Premium content\",\"payer\":\"%s\"}", payer )); } } ``` *** ## Configuration ### PrismConfig ```java theme={null} import org.fdtech.prism.core.PrismConfig; PrismConfig config = new PrismConfig( "your-api-key", // Required: API key "https://prism-api.test.1stdigital.tech", // Optional: Base URL true // Optional: Debug mode ); ``` ### Programmatic Configuration ```java theme={null} import org.fdtech.prism.servlet.PrismFilter; import javax.servlet.FilterRegistration; // In ServletContextInitializer or similar public class WebAppConfig { public void configureFilter(ServletContext context) { PrismConfig config = new PrismConfig("dev-key-123"); PrismFilter filter = new PrismFilter(config); // Add route configuration filter.addRoute("/api/premium", 0.01, "Premium API"); filter.addRoute("/api/weather", "$0.001", "Weather data"); // Register filter FilterRegistration.Dynamic registration = context.addFilter("PrismFilter", filter); registration.addMappingForUrlPatterns( null, false, "/api/*" ); } } ``` *** ## Route Configuration Format ### In web.xml Routes are configured as colon-separated values, one per line: ``` routes /api/premium:0.01:Premium API access /api/weather:$0.001:Weather data /api/data/*:0.005:Data API (wildcard) ``` **Format:** `path:price:description` * **path**: Exact path or wildcard (`/api/*`) * **price**: USD amount (`0.01` = $0.01 USD, or `$0.001\` string format) * **description**: Human-readable description *** ### Programmatic Configuration ```java theme={null} PrismFilter filter = new PrismFilter(config); // Exact paths filter.addRoute("/api/premium", 0.01, "Premium API"); filter.addRoute("/api/weather", "$0.001", "Weather data"); // Wildcard matching filter.addRoute("/api/data/*", 0.005, "Data API"); // Multiple route groups filter.addRoute("/api/basic/*", "$0.0001", "Basic tier"); filter.addRoute("/api/premium/*", "$0.01", "Premium tier"); ``` *** ## Accessing Payment Information Payment info is stored as request attributes: ```java theme={null} @WebServlet("/api/premium") public class PremiumServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // Get payer wallet address String payer = (String) request.getAttribute("prism_payer"); // Returns: "0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb" or null // Get full payment object Map payment = (Map) request.getAttribute("prism_payment"); /* { "scheme": "eip3009", "network": "eth-sepolia", "asset": "usdc", "amount": "10000", "recipient": "0x...", "signature": "0x..." } */ response.setContentType("application/json"); response.getWriter().write( new JSONObject() .put("message", "Premium content") .put("payer", payer) .put("network", payment.get("network")) .toString() ); } } ``` *** ### Safe Access Helper ```java theme={null} public class PaymentUtils { public static String getPayer(HttpServletRequest request) { return (String) request.getAttribute("prism_payer"); } public static Map getPayment(HttpServletRequest request) { return (Map) request.getAttribute("prism_payment"); } public static boolean hasPayer(HttpServletRequest request) { return request.getAttribute("prism_payer") != null; } } // Usage in servlet @WebServlet("/api/data") public class DataServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { if (!PaymentUtils.hasPayer(request)) { response.sendError(402, "Payment required"); return; } String payer = PaymentUtils.getPayer(request); response.setContentType("application/json"); response.getWriter().write( "{\"data\":[1,2,3],\"payer\":\"" + payer + "\"}" ); } } ``` *** ## Settlement Validation The Java Servlet filter uses **HttpServletResponseWrapper** to capture servlet output before settlement validation: ```java theme={null} // Internal implementation (automatic!) public class ResponseWrapper extends HttpServletResponseWrapper { private ByteArrayOutputStream buffer = new ByteArrayOutputStream(); private PrintWriter writer = new PrintWriter(buffer); @Override public PrintWriter getWriter() { return writer; } public byte[] getCapturedOutput() { writer.flush(); return buffer.toByteArray(); } } // In PrismFilter.doFilter() ResponseWrapper wrapper = new ResponseWrapper(response); chain.doFilter(request, wrapper); // Execute servlet - output goes to buffer // Validate settlement BEFORE sending to client String settlementHeader = middleware.getSettlementHeader(paymentInfo); if (settlementHeader == null) { // ❌ Settlement failed - send error instead of buffered output response.setStatus(402); response.setContentType("application/json"); response.getWriter().write( "{\"error\":\"Payment settlement failed\"}" ); } else { // ✅ Settlement succeeded - send buffered output response.setHeader("X-PAYMENT-RESPONSE", settlementHeader); response.getOutputStream().write(wrapper.getCapturedOutput()); } ``` **Key Points:** * Servlet writes to **wrapper**, not real response * Output is **buffered in memory** * After settlement check, either send buffered data or error * Works with all response types (JSON, HTML, binary, etc.) See [Settlement Validation](/concepts/settlement-validation) for details. *** ## Error Handling ### Payment Errors When payment is missing or invalid: ```json theme={null} HTTP/1.1 402 Payment Required { "x402Version": 1, "paymentRequired": true, "acceptedPayments": [ { "scheme": "eip3009", "network": "eth-sepolia", "asset": "usdc", "amount": "10000", "recipient": "0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb", "nonce": "0xabc123...", "validBefore": 1735430400 } ], "description": "Premium API access", "priceUSD": "0.01" } ``` *** ### Custom Error Handling ```java theme={null} @WebServlet("/api/premium") public class PremiumServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { try { String payer = (String) request.getAttribute("prism_payer"); if (payer == null) { sendPaymentRequired(response); return; } // Process request String result = processRequest(payer); response.setContentType("application/json"); response.getWriter().write(result); } catch (Exception e) { sendError(response, e); } } private void sendPaymentRequired(HttpServletResponse response) throws IOException { response.setStatus(402); response.setContentType("application/json"); response.getWriter().write( "{\"error\":\"Payment required\"}" ); } private void sendError(HttpServletResponse response, Exception e) throws IOException { response.setStatus(500); response.setContentType("application/json"); response.getWriter().write( "{\"error\":\"" + e.getMessage() + "\"}" ); } } ``` *** ## Spring Boot Integration ### Configuration Class ```java theme={null} import org.springframework.boot.web.servlet.FilterRegistrationBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.fdtech.prism.servlet.PrismFilter; import org.fdtech.prism.core.PrismConfig; @Configuration public class PrismConfig { @Bean public FilterRegistrationBean prismFilter() { PrismConfig config = new PrismConfig( "dev-key-123", "https://prism-api.test.1stdigital.tech" ); PrismFilter filter = new PrismFilter(config); filter.addRoute("/api/premium", 0.01, "Premium API"); filter.addRoute("/api/weather", "$0.001", "Weather data"); FilterRegistrationBean registration = new FilterRegistrationBean<>(); registration.setFilter(filter); registration.addUrlPatterns("/api/*"); registration.setOrder(1); // Execute early return registration; } } ``` ### Spring Boot Controller ```java theme={null} import org.springframework.web.bind.annotation.*; import javax.servlet.http.HttpServletRequest; import java.util.Map; @RestController @RequestMapping("/api") public class ApiController { @GetMapping("/premium") public Map premium(HttpServletRequest request) { String payer = (String) request.getAttribute("prism_payer"); return Map.of( "message", "Premium content", "payer", payer ); } @GetMapping("/weather") public Map weather(HttpServletRequest request) { String payer = (String) request.getAttribute("prism_payer"); return Map.of( "location", "San Francisco", "temperature", 72, "condition", "Sunny", "payer", payer ); } } ``` *** ## Testing ### JUnit 5 Tests ```java theme={null} import org.junit.jupiter.api.Test; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.web.client.TestRestTemplate; import org.springframework.http.*; import static org.junit.jupiter.api.Assertions.*; @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) class PaymentIntegrationTest { @Autowired private TestRestTemplate restTemplate; @Test void testPaymentRequired() { ResponseEntity response = restTemplate.getForEntity("/api/premium", String.class); assertEquals(HttpStatus.PAYMENT_REQUIRED, response.getStatusCode()); assertTrue(response.getBody().contains("paymentRequired")); } @Test void testValidPayment() { // Create mock payment header String payment = "{\"scheme\":\"eip3009\",\"signature\":\"0x" + "0".repeat(130) + "\"}"; HttpHeaders headers = new HttpHeaders(); headers.set("X-PAYMENT", payment); HttpEntity entity = new HttpEntity<>(headers); ResponseEntity response = restTemplate.exchange( "/api/premium", HttpMethod.GET, entity, String.class ); // In test mode, mock signatures accepted assertEquals(HttpStatus.OK, response.getStatusCode()); assertTrue(response.getBody().contains("Premium content")); } } ``` *** ### Mock Servlet Testing ```java theme={null} import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.mockito.Mock; import org.mockito.MockitoAnnotations; import javax.servlet.FilterChain; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import static org.mockito.Mockito.*; class PrismFilterTest { @Mock private HttpServletRequest request; @Mock private HttpServletResponse response; @Mock private FilterChain chain; private PrismFilter filter; @BeforeEach void setUp() { MockitoAnnotations.openMocks(this); PrismConfig config = new PrismConfig("test-key"); filter = new PrismFilter(config); filter.addRoute("/api/premium", 0.01, "Test"); } @Test void testPaymentRequired() throws Exception { when(request.getRequestURI()).thenReturn("/api/premium"); when(request.getHeader("X-PAYMENT")).thenReturn(null); filter.doFilter(request, response, chain); verify(response).setStatus(402); verify(chain, never()).doFilter(any(), any()); } } ``` *** ## Production Deployment ### Environment Configuration ```properties theme={null} # application.properties (Spring Boot) prism.api-key=${PRISM_API_KEY} prism.base-url=${PRISM_BASE_URL:https://prism-api.1stdigital.tech} prism.debug=${PRISM_DEBUG:false} # Routes configuration prism.routes[0].path=/api/premium prism.routes[0].price=0.01 prism.routes[0].description=Premium API prism.routes[1].path=/api/weather prism.routes[1].price=$0.001 prism.routes[1].description=Weather data ``` ### Configuration Class ```java theme={null} import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.context.annotation.Configuration; @Configuration @ConfigurationProperties(prefix = "prism") public class PrismProperties { private String apiKey; private String baseUrl; private boolean debug; private List routes; // Getters and setters public static class RouteConfig { private String path; private String price; private String description; // Getters and setters } } ``` *** ### Logging & Monitoring ```java theme={null} import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class PremiumServlet extends HttpServlet { private static final Logger logger = LoggerFactory.getLogger(PremiumServlet.class); @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String payer = (String) request.getAttribute("prism_payer"); if (payer != null) { logger.info("Payment succeeded: payer={}, path={}", payer, request.getRequestURI()); } else { logger.warn("Payment required: path={}, ip={}", request.getRequestURI(), request.getRemoteAddr()); } // Process request... } } ``` *** ## Examples ### AI Agent API ```java theme={null} @WebServlet("/api/ai/chat") public class AIChatServlet extends HttpServlet { @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String payer = (String) request.getAttribute("prism_payer"); // Parse request body BufferedReader reader = request.getReader(); String message = new JSONObject(reader.readLine()) .getString("message"); // Call AI service String aiResponse = callAIService(message); // Return response response.setContentType("application/json"); response.getWriter().write( new JSONObject() .put("response", aiResponse) .put("payer", payer) .put("creditsUsed", 1) .toString() ); } } ``` *** ### REST API with JAX-RS ```java theme={null} import javax.ws.rs.*; import javax.ws.rs.core.*; @Path("/api") public class ApiResource { @Context private HttpServletRequest request; @GET @Path("/premium") @Produces(MediaType.APPLICATION_JSON) public Response premium() { String payer = (String) request.getAttribute("prism_payer"); return Response.ok() .entity(Map.of( "message", "Premium content", "payer", payer )) .build(); } } ``` *** ## Troubleshooting **Check filter mapping in web.xml:** ```xml theme={null} PrismFilter /api/* ``` **Verify filter order:** Prism filter should execute BEFORE authentication filters. **Debug with logging:** ```java theme={null} @Override protected void doGet(HttpServletRequest request, ...) { System.out.println("URI: " + request.getRequestURI()); System.out.println("Payment header: " + request.getHeader("X-PAYMENT")); System.out.println("Payer: " + request.getAttribute("prism_payer")); } ``` **Check:** Route matches filter URL pattern, payment header present and valid. **Common causes:** 1. Insufficient balance 2. Invalid signature 3. Nonce reuse 4\. Network timeout **Check logs:** Enable debug mode in PrismConfig. **If you see encoding problems:** * Make sure to set `Content-Type` before writing * Use `getWriter()` for text, `getOutputStream()` for binary * Don't mix `getWriter()` and `getOutputStream()` # SDK Overview Source: https://developers.fd.xyz/prism/sdk/overview Integrate Finance District into your existing application ## Introduction Finance District provides SDKs for TypeScript, Python, and Java, making it simple to integrate the **Prism X402 Payment Protocol** into your existing applications. Our SDKs handle authentication, request signing, and payment verification with minimal setup. ## Find Your Integration Pick the framework you're already using: Add payment middleware to Express apps Guards and decorators for NestJS API routes and middleware for Next.js Plugin for Fastify applications Middleware and decorators for Django Dependencies for FastAPI routes Decorators for Flask routes Filters for servlet-based apps Native Node.js HTTP server ## Language SDKs If you don't use any of the frameworks above, start with the base SDK for your language: Node.js and TypeScript applications Python applications Java applications ## What You Get * **🔐 Authentication** - API key management and request signing * **💳 Payment Verification** - X402 protocol support * **🛡️ Error Handling** - Proper error types and validation * **📝 Type Safety** - Full type definitions * **🚀 Framework Integration** - Drop-in middleware/decorators * **⚡ Performance** - Production-ready ## Quick Example Here's how simple it is to add payment verification to an existing route: ```typescript Express theme={null} app.get('/premium-content', fdPayment({ amount: 100, currency: 'USD' }), (req, res) => { res.json({ content: 'Your premium content' }); } ); ``` ```python Django theme={null} @require_payment(amount=100, currency='USD') def premium_content(request): return JsonResponse({'content': 'Your premium content'}) ``` ```python FastAPI theme={null} @app.get('/premium-content') async def premium_content( payment: PaymentVerified = Depends(require_payment(100, 'USD')) ): return {'content': 'Your premium content'} ``` ```python Flask theme={null} @app.route('/premium-content') @require_payment(amount=100, currency='USD') def premium_content(): return jsonify({'content': 'Your premium content'}) ``` ## Need Help? Report issues or request features # Python SDK Source: https://developers.fd.xyz/prism/sdk/python Flask & FastAPI middleware for x402 payment protocol **Status:** 🔜 Coming Q1 2026 The Python SDK is currently in development. This page shows the planned API and features. ## Planned Features Decorator-based middleware for Flask Dependency injection for FastAPI Full async support with asyncio Complete type annotations Data validation with Pydantic Command-line tools for testing ## Installation (Planned) `bash pip install prism-flask # or pip install prism-fastapi ` `bash poetry add prism-flask # or poetry add prism-fastapi ` `txt prism-flask==1.0.0 # or prism-fastapi==1.0.0 ` ## Planned API ### Flask Example ```python theme={null} from flask import Flask, jsonify from prism_flask import require_payment, init_prism app = Flask(__name__) # Initialize Prism init_prism( app, api_key='your-api-key', use_sandbox=True ) # Public endpoint (no payment) @app.route('/') def home(): return jsonify({'message': 'Welcome to Prism API'}) # Protected endpoint (requires payment) @app.route('/api/weather') @require_payment(price=0.001, description='Weather data') def weather(): # Access payment info from flask import g payment = g.payment return jsonify({ 'location': 'San Francisco', 'temperature': 72, 'condition': 'Sunny', 'payment': { 'network': payment.network, 'tx_hash': payment.tx_hash } }) # Premium endpoint (higher price) @app.route('/api/premium') @require_payment(price=0.01, description='Premium data') def premium(): return jsonify({ 'premium': True, 'data': 'Premium content' }) if __name__ == '__main__': app.run(debug=True) ``` ### FastAPI Example ```python theme={null} from fastapi import FastAPI, Depends from prism_fastapi import PrismMiddleware, require_payment, PaymentInfo app = FastAPI() # Add Prism middleware app.add_middleware( PrismMiddleware, api_key='your-api-key', use_sandbox=True ) # Public endpoint (no payment) @app.get('/') async def home(): return {'message': 'Welcome to Prism API'} # Protected endpoint (requires payment) @app.get('/api/weather') async def weather( payment: PaymentInfo = Depends(require_payment(0.001, 'Weather data')) ): return { 'location': 'San Francisco', 'temperature': 72, 'condition': 'Sunny', 'payment': { 'network': payment.network, 'tx_hash': payment.tx_hash } } # Premium endpoint (higher price) @app.get('/api/premium') async def premium( payment: PaymentInfo = Depends(require_payment(0.01, 'Premium data')) ): return { 'premium': True, 'data': 'Premium content', 'paid_on': payment.network } ``` ### Configuration ```python theme={null} # Flask from prism_flask import PrismConfig config = PrismConfig( api_key='your-api-key', use_sandbox=True, base_url=None, # Optional custom URL timeout=10.0, # Request timeout in seconds retries=3 # Failed request retries ) init_prism(app, config) ``` ```python theme={null} # FastAPI from prism_fastapi import PrismMiddleware, PrismConfig app.add_middleware( PrismMiddleware, config=PrismConfig( api_key='your-api-key', use_sandbox=True, timeout=10.0, retries=3 ) ) ``` ### Payment Info Model ```python theme={null} from pydantic import BaseModel from typing import Optional class PaymentInfo(BaseModel): scheme: str network: str asset: str amount: str pay_to: str tx_hash: Optional[str] = None valid_after: int valid_before: int nonce: str class Config: # Allow snake_case field names alias_generator = lambda s: s ``` ### Error Handling (Flask) ```python theme={null} from flask import Flask, jsonify from prism_flask import PrismError, require_payment app = Flask(__name__) @app.errorhandler(PrismError) def handle_prism_error(error): return jsonify({ 'error': error.code, 'message': error.message, 'details': error.details }), error.status_code @app.route('/api/data') @require_payment(price=0.01) def get_data(): return jsonify({'data': 'content'}) ``` ### Error Handling (FastAPI) ```python theme={null} from fastapi import FastAPI, Request from fastapi.responses import JSONResponse from prism_fastapi import PrismException app = FastAPI() @app.exception_handler(PrismException) async def prism_exception_handler( request: Request, exc: PrismException ): return JSONResponse( status_code=exc.status_code, content={ 'error': exc.code, 'message': exc.message, 'details': exc.details } ) ``` ### Advanced Flask Patterns #### Blueprint Support ```python theme={null} from flask import Blueprint from prism_flask import require_payment api_bp = Blueprint('api', __name__, url_prefix='/api') @api_bp.route('/data') @require_payment(price=0.001, description='API data') def get_data(): return {'data': 'content'} app.register_blueprint(api_bp) ``` #### Dynamic Pricing ```python theme={null} from flask import request from prism_flask import require_payment, get_payment_price @app.route('/api/search') @require_payment(price=lambda: calculate_search_price(request.args)) def search(): query = request.args.get('q') results = perform_search(query) return {'results': results} def calculate_search_price(args): # Simple query: $0.001 # Complex query: $0.01 if len(args.get('q', '')) > 50: return 0.01 return 0.001 ``` #### Custom Middleware ```python theme={null} from flask import Flask, g, request from prism_flask import PrismClient app = Flask(__name__) prism = PrismClient(api_key='key', use_sandbox=True) @app.before_request def check_payment(): # Custom payment logic if request.path.startswith('/api/paid'): auth_header = request.headers.get('X-PAYMENT-AUTHORIZATION') if not auth_header: requirements = prism.get_payment_requirements( resource=request.path, amount='1000000' ) return requirements.to_402_response() payment = prism.verify_payment(auth_header) if payment.valid: g.payment = payment else: return {'error': 'Invalid payment'}, 401 ``` ### Advanced FastAPI Patterns #### Dependency Injection ```python theme={null} from fastapi import FastAPI, Depends from prism_fastapi import PrismClient, PaymentInfo app = FastAPI() # Inject Prism client def get_prism_client(): return PrismClient( api_key='your-api-key', use_sandbox=True ) @app.get('/api/data') async def get_data( prism: PrismClient = Depends(get_prism_client), payment: PaymentInfo = Depends(require_payment(0.001)) ): # Manually settle payment if needed await prism.settle_payment(payment.nonce) return {'data': 'content'} ``` #### Background Tasks ```python theme={null} from fastapi import BackgroundTasks from prism_fastapi import require_payment, PaymentInfo @app.get('/api/process') async def process_data( background_tasks: BackgroundTasks, payment: PaymentInfo = Depends(require_payment(0.01)) ): # Add settlement to background tasks background_tasks.add_task( settle_and_log, payment.nonce, payment.network ) return {'status': 'processing'} async def settle_and_log(nonce: str, network: str): # Settle payment in background await prism.settle_payment(nonce) logger.info(f'Payment settled: {nonce} on {network}') ``` #### WebSocket Support ```python theme={null} from fastapi import WebSocket from prism_fastapi import verify_payment_ws @app.websocket('/ws/stream') async def websocket_stream(websocket: WebSocket): await websocket.accept() # Wait for payment payment = await verify_payment_ws( websocket, price=0.001, description='Real-time stream' ) if payment.valid: # Stream data while True: data = await get_real_time_data() await websocket.send_json(data) await asyncio.sleep(1) else: await websocket.close(code=1008, reason='Payment required') ``` ### Testing Support #### Flask Testing ```python theme={null} import pytest from prism_flask.testing import MockPrismClient @pytest.fixture def client(app): app.config['TESTING'] = True with app.test_client() as client: yield client @pytest.fixture def mock_prism(app): # Mock Prism client for testing mock = MockPrismClient() mock.setup_valid_payment( nonce='0x123...', tx_hash='0xabc...' ) app.prism_client = mock return mock def test_payment_required(client): response = client.get('/api/weather') assert response.status_code == 402 assert 'X-PAYMENT' in response.headers def test_valid_payment(client, mock_prism): response = client.get( '/api/weather', headers={'X-PAYMENT-AUTHORIZATION': 'valid-token'} ) assert response.status_code == 200 assert response.json['temperature'] == 72 ``` #### FastAPI Testing ```python theme={null} import pytest from fastapi.testclient import TestClient from prism_fastapi.testing import MockPrismMiddleware @pytest.fixture def client(app): # Replace middleware with mock app.user_middleware = [ m for m in app.user_middleware if not isinstance(m, PrismMiddleware) ] app.add_middleware(MockPrismMiddleware) return TestClient(app) def test_payment_required(client): response = client.get('/api/weather') assert response.status_code == 402 assert 'x-payment' in response.headers def test_valid_payment(client): response = client.get( '/api/weather', headers={'x-payment-authorization': 'mock-valid-token'} ) assert response.status_code == 200 assert response.json()['temperature'] == 72 ``` ### CLI Tools (Planned) ```bash theme={null} # Initialize new Prism project prism init --framework flask # Test payment flow prism test --endpoint http://localhost:5000/api/weather # Generate payment signature prism sign \ --from 0xAIAgent... \ --to 0xProvider... \ --amount 1000000 \ --nonce 0x123... \ --private-key /path/to/key # Verify payment prism verify \ --signature 0xabc... \ --nonce 0x123... \ --network base-sepolia # Monitor payments prism monitor --api-key your-key --follow ``` ## Planned Features Roadmap * Flask decorator support - FastAPI dependency injection - Basic payment verification - Type hints and Pydantic models * WebSocket support - Background task integration - Advanced caching - CLI tools * Django middleware - Starlette support - Sanic integration - Tornado handlers * Celery integration - Redis caching - PostgreSQL tracking - Prometheus metrics ## Why Python SDK? Python dominates ML/AI applications Clean, readable decorator syntax Native async/await with FastAPI Rich ecosystem of libraries ## Expected Use Cases ### AI/ML APIs ```python theme={null} from fastapi import FastAPI from prism_fastapi import require_payment, PaymentInfo app = FastAPI() @app.post('/api/inference') async def run_inference( model_input: ModelInput, payment: PaymentInfo = Depends(require_payment(0.01, 'AI inference')) ): # Run expensive ML model result = await ml_model.predict(model_input) # Track usage await track_usage( model='gpt-4', payment_network=payment.network, amount=payment.amount ) return {'result': result} ``` ### Data APIs ```python theme={null} from flask import Flask from prism_flask import require_payment app = Flask(__name__) @app.route('/api/stock/') @require_payment(price=0.005, description='Real-time stock data') def get_stock(symbol): quote = fetch_stock_quote(symbol) return { 'symbol': symbol, 'price': quote.price, 'change': quote.change } @app.route('/api/historical/') @require_payment(price=0.05, description='Historical stock data') def get_historical(symbol): data = fetch_historical_data(symbol) return {'symbol': symbol, 'data': data} ``` ### Content APIs ```python theme={null} from fastapi import FastAPI from fastapi.responses import StreamingResponse from prism_fastapi import require_payment @app.get('/api/video/{video_id}') async def stream_video( video_id: str, payment: PaymentInfo = Depends(require_payment(0.05, 'Video streaming')) ): async def video_stream(): async for chunk in get_video_chunks(video_id): yield chunk return StreamingResponse( video_stream(), media_type='video/mp4' ) ``` ## Get Notified Want to be notified when the Python SDK is released? Get email notifications for Python SDK updates ## Contribute Interested in contributing to the Python SDK development? Share ideas and feedback ## Alternative Solutions While waiting for the Python SDK, you can: Use Prism Gateway REST API with `requests` or `httpx` ```python theme={null} import requests gateway_url = 'https://prism-api.test.1stdigital.tech' headers = {'x-api-key': api_key} # Get payment requirements response = requests.post( f'{gateway_url}/api/v1/payment/requirements', headers=headers, json={ 'resource': '/api/premium', 'amount': '1000000' } ) requirements = response.json() ``` Complete REST API documentation Use TypeScript SDK with Node.js backend ```typescript theme={null} import express from 'express'; import { prismPaymentMiddleware } from '@1stdigital/prism-express'; const app = express(); app.use(prismPaymentMiddleware( { apiKey: 'key', useSandbox: true }, { '/api/data': { price: 0.01 } } )); ``` Full TypeScript documentation # Django Integration Source: https://developers.fd.xyz/prism/sdk/python/django Protect Django views with blockchain micropayments ## Overview The **prism-django** package provides middleware for Django applications, integrating seamlessly with Django's request/response cycle and middleware stack. Integrates with Django middleware stack Works with Django ORM and models Compatible with Django Admin *** ## Installation ```bash theme={null} pip install prism-django django ``` `bash poetry add prism-django django ` ``` prism-django>=1.0.0 django>=4.2.0 ``` *** ## Quick Start ```python theme={null} # settings.py MIDDLEWARE = [ 'django.middleware.security.SecurityMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', 'django.middleware.common.CommonMiddleware', 'django.middleware.csrf.CsrfViewMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware', # Add Prism middleware 'prism_django.middleware.PrismPaymentMiddleware', ] # Prism configuration PRISM_CONFIG = { 'API_KEY': 'dev-key-123', 'BASE_URL': 'https://prism-api.test.1stdigital.tech', 'DEBUG': True, 'ROUTES': { '/api/premium/': { 'price': 0.01, # $0.01 USD 'description': 'Premium API' } } } # views.py from django.http import JsonResponse def premium_view(request): # Access payer address payer = getattr(request, 'prism_payer', None) return JsonResponse({ 'message': 'Premium content', 'payer': payer }) # urls.py from django.urls import path from . import views urlpatterns = [ path('api/premium/', views.premium_view), ] ``` *** ## Configuration ### Settings.py ```python theme={null} PRISM_CONFIG = { 'API_KEY': 'your-api-key', 'BASE_URL': 'https://prism-gateway.com', 'DEBUG': False, 'ROUTES': { '/api/premium/': { 'price': 0.01, 'description': 'Premium API' }, '/api/weather/': { 'price': 0.001, 'description': 'Weather data' }, '/api/data/*': { 'price': 0.005, 'description': 'Data API (wildcard)' } } } ``` *** ## Route Protection ### URL Patterns ```python theme={null} # urls.py from django.urls import path from . import views urlpatterns = [ # Protected routes (configured in PRISM_CONFIG) path('api/premium/', views.premium_view), path('api/weather/', views.weather_view), # Free routes (not in PRISM_CONFIG) path('api/free/', views.free_view), ] ``` *** ## Accessing Payment Info ```python theme={null} from django.http import JsonResponse def premium_view(request): # Access payer address payer = getattr(request, 'prism_payer', None) # '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb' # Access payment object payment = getattr(request, 'prism_payment', None) if payment: return JsonResponse({ 'message': 'Premium data', 'payer': payer, 'network': payment.get('network'), 'asset': payment.get('asset') }) return JsonResponse({'message': 'No payment info'}) ``` *** ## Settlement Validation Django middleware intercepts responses to validate settlement: ```python theme={null} # Internal implementation (automatic!) class PrismPaymentMiddleware: def process_response(self, request, response): # Validate settlement settlement_result = core.settlement_callback(...) if not settlement_result or not settlement_result.get("success"): # ❌ Settlement failed return JsonResponse( { 'error': 'Payment settlement failed', 'details': settlement_result.get('errorReason') }, status=402 ) # ✅ Settlement succeeded response['X-PAYMENT-RESPONSE'] = settlement_result['transaction'] return response ``` *** ## Class-Based Views ```python theme={null} from django.views import View from django.http import JsonResponse class PremiumAPIView(View): def get(self, request): payer = getattr(request, 'prism_payer', None) return JsonResponse({ 'message': 'Premium content', 'payer': payer }) def post(self, request): payer = getattr(request, 'prism_payer', None) # Process POST data data = request.POST return JsonResponse({ 'message': 'Data received', 'payer': payer, 'data': dict(data) }) ``` *** ## Django REST Framework ```python theme={null} # views.py from rest_framework.views import APIView from rest_framework.response import Response from rest_framework import status class PremiumAPIView(APIView): def get(self, request): payer = getattr(request, 'prism_payer', None) return Response({ 'message': 'Premium content', 'payer': payer }) # settings.py INSTALLED_APPS = [ # ... 'rest_framework', ] REST_FRAMEWORK = { 'DEFAULT_RENDERER_CLASSES': [ 'rest_framework.renderers.JSONRenderer', ] } ``` *** ## Testing ```python theme={null} from django.test import TestCase, Client import json class PrismPaymentTests(TestCase): def setUp(self): self.client = Client() def test_payment_required_without_header(self): response = self.client.get('/api/premium/') self.assertEqual(response.status_code, 402) data = json.loads(response.content) self.assertTrue(data['paymentRequired']) def test_access_granted_with_valid_payment(self): payment = json.dumps({ 'scheme': 'eip3009', 'signature': '0x' + '0' * 130 }) response = self.client.get( '/api/premium/', HTTP_X_PAYMENT=payment ) self.assertEqual(response.status_code, 200) data = json.loads(response.content) self.assertIn('payer', data) def test_wildcard_route(self): response = self.client.get('/api/data/anything/') self.assertEqual(response.status_code, 402) ``` *** ## Production Deployment ### Settings for Production ```python theme={null} # settings.py import os from pathlib import Path # Production settings DEBUG = False ALLOWED_HOSTS = os.getenv('ALLOWED_HOSTS', '').split(',') # Prism configuration from environment PRISM_CONFIG = { 'API_KEY': os.environ['PRISM_API_KEY'], 'BASE_URL': os.getenv('PRISM_BASE_URL', 'https://prism-gateway.com'), 'DEBUG': DEBUG, 'ROUTES': { '/api/premium/': { 'price': 0.01, 'description': 'Premium API' } } } # Security SECURE_SSL_REDIRECT = True SESSION_COOKIE_SECURE = True CSRF_COOKIE_SECURE = True # CORS (if needed) INSTALLED_APPS += ['corsheaders'] MIDDLEWARE.insert(0, 'corsheaders.middleware.CorsMiddleware') CORS_ALLOWED_ORIGINS = os.getenv('CORS_ORIGINS', '').split(',') ``` ### WSGI Deployment (Gunicorn) ```bash theme={null} # Install Gunicorn pip install gunicorn # Run with Gunicorn gunicorn myproject.wsgi:application \ --bind 0.0.0.0:8000 \ --workers 4 \ --timeout 120 \ --access-logfile - \ --error-logfile - ``` ### Docker Deployment ```dockerfile theme={null} FROM python:3.11-slim WORKDIR /app COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt COPY . . RUN python manage.py collectstatic --noinput EXPOSE 8000 CMD ["gunicorn", "myproject.wsgi:application", "--bind", "0.0.0.0:8000"] ``` *** ## Type Hints ```python theme={null} from typing import Optional, Dict, Any from django.http import JsonResponse, HttpRequest def premium_view(request: HttpRequest) -> JsonResponse: payer: Optional[str] = getattr(request, 'prism_payer', None) payment: Optional[Dict[str, Any]] = getattr(request, 'prism_payment', None) return JsonResponse({ 'message': 'Premium content', 'payer': payer, 'network': payment.get('network') if payment else None }) ``` # FastAPI Integration Source: https://developers.fd.xyz/prism/sdk/python/fastapi Protect FastAPI endpoints with blockchain micropayments ## Overview The **prism-fastapi** package provides async middleware for FastAPI applications, leveraging Python's modern async/await syntax for high-performance payment validation. Full async support with Python asyncio Works with FastAPI's Depends system Full type hint support with Pydantic *** ## Installation ```bash theme={null} pip install prism-fastapi fastapi uvicorn ``` `bash poetry add prism-fastapi fastapi uvicorn ` ``` prism-fastapi>=1.0.0 fastapi>=0.100.0 uvicorn[standard]>=0.23.0 ``` *** ## Quick Start ```python theme={null} from fastapi import FastAPI, Request from prism_fastapi import prism_payment_middleware, PrismMiddlewareConfig app = FastAPI() # Configure Prism middleware prism_config = PrismMiddlewareConfig( api_key="dev-key-123", base_url="https://prism-api.test.1stdigital.tech", routes={ "/api/premium": { "price": 0.01, # $0.01 USD "description": "Premium API" } } ) # Add middleware app.middleware("http")(prism_payment_middleware(prism_config)) # Protected endpoint @app.get("/api/premium") async def premium_endpoint(request: Request): payer = request.state.prism_payer # Wallet address return { "message": "Premium content", "payer": payer } # Run: uvicorn main:app --reload ``` *** ## Configuration ```python theme={null} from dataclasses import dataclass from typing import Dict, Union @dataclass class RoutePaymentConfig: price: Union[float, str] # USD price description: str mime_type: str = "application/json" @dataclass class PrismMiddlewareConfig: api_key: str base_url: str = "https://prism-gateway.com" debug: bool = False routes: Dict[str, RoutePaymentConfig] = None ``` *** ## Route Protection ### Multiple Routes ```python theme={null} from prism_fastapi import PrismMiddlewareConfig, RoutePaymentConfig config = PrismMiddlewareConfig( api_key="your-api-key", routes={ "/api/premium": RoutePaymentConfig( price=0.01, description="Premium API" ), "/api/weather": RoutePaymentConfig( price=0.001, description="Weather data" ), "/api/data/*": RoutePaymentConfig( price=0.005, description="Data API (wildcard)" ) } ) app.middleware("http")(prism_payment_middleware(config)) ``` *** ## Accessing Payment Info ```python theme={null} from fastapi import FastAPI, Request @app.get("/api/premium") async def premium_endpoint(request: Request): # Access payer address payer = request.state.prism_payer # '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb' # Access payment object payment = getattr(request.state, 'prism_payment', None) if payment: return { "message": "Premium data", "payer": payer, "network": payment.get("network"), "asset": payment.get("asset") } return {"message": "No payment info"} ``` *** ## Settlement Validation FastAPI middleware intercepts responses to validate settlement: ```python theme={null} # Internal implementation (automatic!) @app.middleware("http") async def prism_payment_middleware(request: Request, call_next): # ... payment validation ... # Call endpoint response = await call_next(request) # Validate settlement settlement_result = await core.settlement_callback(...) if not settlement_result or not settlement_result.get("success"): # ❌ Settlement failed return JSONResponse( status_code=402, content={ "error": "Payment settlement failed", "details": settlement_result.get("errorReason") } ) # ✅ Settlement succeeded response.headers["X-PAYMENT-RESPONSE"] = settlement_result["transaction"] return response ``` *** ## Dependency Injection ### Payer Dependency ```python theme={null} from fastapi import Depends, Request def get_payer(request: Request) -> str: return getattr(request.state, 'prism_payer', None) @app.get("/api/premium") async def premium_endpoint(payer: str = Depends(get_payer)): return { "message": "Premium content", "payer": payer } ``` *** ## Testing ```python theme={null} import pytest from fastapi.testclient import TestClient from main import app client = TestClient(app) def test_payment_required_without_header(): response = client.get("/api/premium") assert response.status_code == 402 assert response.json()["paymentRequired"] is True def test_access_granted_with_valid_payment(): payment = { "scheme": "eip3009", "signature": "0x" + "0" * 130 } response = client.get( "/api/premium", headers={"X-PAYMENT": str(payment)} ) assert response.status_code == 200 assert "payer" in response.json() @pytest.mark.asyncio async def test_async_endpoint(): async with AsyncClient(app=app, base_url="http://test") as ac: response = await ac.get("/api/premium") assert response.status_code == 402 ``` *** ## Production Deployment ```python theme={null} # main.py from fastapi import FastAPI from prism_fastapi import prism_payment_middleware, PrismMiddlewareConfig import os app = FastAPI( title="Prism Payment API", docs_url="/docs" if os.getenv("ENV") != "production" else None ) # Configure Prism config = PrismMiddlewareConfig( api_key=os.environ["PRISM_API_KEY"], base_url=os.getenv("PRISM_BASE_URL", "https://prism-gateway.com"), debug=os.getenv("DEBUG", "false").lower() == "true", routes={ "/api/premium": { "price": 0.01, "description": "Premium API" } } ) app.middleware("http")(prism_payment_middleware(config)) # CORS (if needed) from fastapi.middleware.cors import CORSMiddleware app.add_middleware( CORSMiddleware, allow_origins=os.getenv("ALLOWED_ORIGINS", "*").split(","), allow_credentials=True, allow_methods=["*"], allow_headers=["*"] ) if __name__ == "__main__": import uvicorn uvicorn.run( "main:app", host="0.0.0.0", port=int(os.getenv("PORT", 8000)), reload=os.getenv("ENV") != "production" ) ``` ### Docker Deployment ```dockerfile theme={null} FROM python:3.11-slim WORKDIR /app COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt COPY . . EXPOSE 8000 CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"] ``` *** ## Type Hints ```python theme={null} from typing import Optional, Dict, Any from fastapi import Request @app.get("/api/premium") async def premium_endpoint(request: Request) -> Dict[str, Any]: payer: Optional[str] = getattr(request.state, 'prism_payer', None) payment: Optional[Dict[str, Any]] = getattr(request.state, 'prism_payment', None) return { "message": "Premium content", "payer": payer, "network": payment.get("network") if payment else None } ``` # Flask Integration Source: https://developers.fd.xyz/prism/sdk/python/flask Protect Flask APIs with blockchain micropayments ## Overview The **prism-flask** package provides Flask middleware for payment-protecting your API routes using the x402 protocol. It uses Flask's decorator pattern and request context for seamless integration. Flask decorators and context managers Payment info in `g.prism_payer` Single function call to configure *** ## Installation ```bash theme={null} pip install prism-flask ``` `bash poetry add prism-flask ` ```txt theme={null} prism-flask==1.0.0 ``` *** ## Quick Start ```python theme={null} from flask import Flask, g, jsonify from prism_flask import prism_payment_middleware from prism_sdk_core import PrismMiddlewareConfig, RoutePaymentConfig app = Flask(__name__) # Configure Prism middleware prism_payment_middleware( app, config=PrismMiddlewareConfig( api_key='dev-key-123', base_url='https://prism-api.test.1stdigital.tech' ), routes={ '/api/premium': RoutePaymentConfig( price=0.01, description='Premium API access' ) } ) @app.route('/api/premium') def premium(): payer = getattr(g, 'prism_payer', None) return jsonify({ 'message': 'Premium content!', 'payer': payer }) if __name__ == '__main__': app.run(port=5000) ``` *** ## Configuration ### Middleware Configuration ```python theme={null} from prism_sdk_core import PrismMiddlewareConfig config = PrismMiddlewareConfig( api_key='your-api-key', # Required: Your Prism API key base_url='https://...', # Optional: Gateway URL debug=True # Optional: Enable debug logging ) ``` ### Route Configuration ```python theme={null} from prism_sdk_core import RoutePaymentConfig routes = { '/api/premium': RoutePaymentConfig( price=0.01, # Price in USD (0.01 = $0.01) description='Premium API' # Human-readable description ) } ``` *** ## Route Protection Patterns ### Exact Path Matching ```python theme={null} prism_payment_middleware( app, config=config, routes={ '/api/premium': RoutePaymentConfig( price=0.01, description='Premium API' ), '/api/weather': RoutePaymentConfig( price='$0.001', description='Weather data' ) } ) # ✅ Protected: GET /api/premium # ✅ Protected: GET /api/weather # ❌ Not protected: GET /api/public ``` *** ### Wildcard Matching ```python theme={null} prism_payment_middleware( app, config=config, routes={ '/api/*': RoutePaymentConfig( price=0.005, description='API access' ) } ) # ✅ Protected: GET /api/users # ✅ Protected: GET /api/posts/123 # ❌ Not protected: GET /public ``` *** ### Multiple Route Groups ```python theme={null} prism_payment_middleware( app, config=config, routes={ # Free tier '/api/basic/*': RoutePaymentConfig( price='$0.0001', description='Basic API' ), # Premium tier '/api/premium/*': RoutePaymentConfig( price='$0.01', description='Premium API' ), # Specific expensive endpoint '/api/ai/generate': RoutePaymentConfig( price='$0.50', description='AI content generation' ) } ) ``` *** ## Accessing Payment Information Payment info is stored in Flask's `g` context object: ```python theme={null} from flask import g @app.route('/api/premium') def premium(): # Access payer wallet address payer = getattr(g, 'prism_payer', None) # Returns: '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb' or None # Access full payment object payment = getattr(g, 'prism_payment', None) """ { 'scheme': 'eip3009', 'network': 'eth-sepolia', 'asset': 'usdc', 'amount': '10000', 'recipient': '0x...', 'nonce': '0x...', 'validBefore': 1735430400, 'signature': '0x...' } """ return jsonify({ 'message': 'Premium content', 'payer': payer, 'network': payment.get('network') if payment else None }) ``` *** ### Safe Access Helper ```python theme={null} def get_payer(): """Safely get payer address from request context""" return getattr(g, 'prism_payer', None) def get_payment(): """Safely get full payment object from request context""" return getattr(g, 'prism_payment', None) @app.route('/api/data') def data(): payer = get_payer() if not payer: return jsonify({'error': 'Payment required'}), 402 return jsonify({ 'data': [1, 2, 3], 'payer': payer }) ``` *** ## Settlement Validation The Flask middleware uses **@app.after\_request** decorator to validate settlement before sending data: ```python theme={null} # Internal implementation (automatic!) @app.after_request def settlement_validation(response): """Validate settlement before sending response""" settlement_result = g.get('prism_settlement_result') if not settlement_result or not settlement_result.get('success'): # ❌ Settlement failed - return error instead of data error_reason = (settlement_result or {}).get('errorReason', 'Settlement processing failed') return jsonify({ 'x402Version': 1, 'error': 'Payment settlement failed', 'details': error_reason }), 402 # ✅ Settlement succeeded - add header and return original response response.headers['X-PAYMENT-RESPONSE'] = settlement_result['transaction'] return response ``` **Key Points:** * `@app.after_request` runs **after** view function but **before** sending to client * Can return a **different response** if settlement fails * Works with all Flask response types (`jsonify()`, `make_response()`, etc.) See [Settlement Validation](/concepts/settlement-validation) for details. *** ## Error Handling ### Payment Errors When a request lacks valid payment: ```json theme={null} HTTP/1.1 402 Payment Required { "x402Version": 1, "paymentRequired": true, "acceptedPayments": [ { "scheme": "eip3009", "network": "eth-sepolia", "asset": "usdc", "amount": "10000", "recipient": "0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb", "nonce": "0xabc123...", "validBefore": 1735430400 } ], "description": "Premium API access", "priceUSD": "0.01" } ``` *** ### Gateway Errors ```json theme={null} HTTP/1.1 500 Internal Server Error { "x402Version": 1, "error": "Gateway Error", "details": "Failed to contact payment gateway", "gateway": { "traceId": "0HNGT483NH6I8:00000001", "timestamp": "2025-12-18T10:30:00Z" } } ``` *** ### Custom Error Handlers ```python theme={null} from werkzeug.exceptions import HTTPException @app.errorhandler(402) def handle_payment_required(error): """Custom handler for payment required errors""" return jsonify({ 'error': 'Payment required', 'message': 'Please provide payment to access this resource', 'documentation': 'https://docs.prism.1stdigital.tech' }), 402 @app.errorhandler(500) def handle_internal_error(error): """Custom handler for internal errors""" app.logger.error(f'Internal error: {error}') return jsonify({ 'error': 'Internal server error', 'message': 'Something went wrong' }), 500 ``` *** ## Testing ### Unit Testing with pytest ```python theme={null} import pytest from flask import Flask from prism_flask import prism_payment_middleware from prism_sdk_core import PrismMiddlewareConfig, RoutePaymentConfig @pytest.fixture def app(): """Create test Flask app""" app = Flask(__name__) prism_payment_middleware( app, config=PrismMiddlewareConfig( api_key='test-key', base_url='http://test-gateway' ), routes={ '/api/premium': RoutePaymentConfig( price=0.01, description='Test' ) } ) @app.route('/api/premium') def premium(): from flask import g, jsonify payer = getattr(g, 'prism_payer', None) return jsonify({'message': 'Premium', 'payer': payer}) return app @pytest.fixture def client(app): """Create test client""" return app.test_client() def test_payment_required(client): """Test 402 response without payment""" response = client.get('/api/premium') assert response.status_code == 402 assert response.json['paymentRequired'] == True def test_valid_payment(client): """Test access with valid payment""" # Mock payment header payment = { 'scheme': 'eip3009', 'network': 'eth-sepolia', 'asset': 'usdc', 'amount': '10000', 'signature': '0x' + '0' * 130 } response = client.get( '/api/premium', headers={'X-PAYMENT': str(payment)} ) # In test mode, mock signatures are accepted assert response.status_code == 200 assert response.json['message'] == 'Premium' ``` *** ### Integration Testing ```python theme={null} import requests import json def test_full_payment_flow(): """Test complete payment flow with real Gateway""" base_url = 'http://localhost:5000' # 1. Request without payment response = requests.get(f'{base_url}/api/premium') assert response.status_code == 402 payment_req = response.json()['acceptedPayments'][0] # 2. Sign payment with wallet (use web3.py) from eth_account import Account from eth_account.messages import encode_structured_data private_key = 'your-test-private-key' account = Account.from_key(private_key) # EIP-712 structured data structured_data = { 'types': { 'EIP712Domain': [...], 'TransferAuthorization': [...] }, 'domain': {...}, 'message': payment_req } signed = account.sign_message(encode_structured_data(structured_data)) payment = { **payment_req, 'signature': signed.signature.hex() } # 3. Send request with signed payment response = requests.get( f'{base_url}/api/premium', headers={'X-PAYMENT': json.dumps(payment)} ) assert response.status_code == 200 assert 'X-PAYMENT-RESPONSE' in response.headers # Transaction hash ``` *** ## Production Deployment ### Environment Variables ```bash theme={null} # .env PRISM_API_KEY=your-production-api-key PRISM_BASE_URL=https://prism-api.1stdigital.tech FLASK_ENV=production ``` ### Production Configuration ```python theme={null} import os from flask import Flask from prism_flask import prism_payment_middleware from prism_sdk_core import PrismMiddlewareConfig, RoutePaymentConfig app = Flask(__name__) # Load config from environment prism_payment_middleware( app, config=PrismMiddlewareConfig( api_key=os.getenv('PRISM_API_KEY'), base_url=os.getenv('PRISM_BASE_URL'), debug=os.getenv('FLASK_ENV') != 'production' ), routes={ '/api/premium': RoutePaymentConfig( price=0.01, description='Premium API' ) } ) ``` *** ### Logging & Monitoring ```python theme={null} import logging from flask import Flask, g # Configure logging logging.basicConfig( level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s' ) logger = logging.getLogger(__name__) app = Flask(__name__) @app.before_request def log_request(): """Log incoming requests""" logger.info(f'Request: {request.method} {request.path}') @app.after_request def log_response(response): """Log payment events""" if response.status_code == 402: logger.info(f'Payment required: {request.path}') elif response.status_code == 200 and hasattr(g, 'prism_payer'): payer = getattr(g, 'prism_payer') tx_hash = response.headers.get('X-PAYMENT-RESPONSE') logger.info(f'Payment succeeded: {request.path} | Payer: {payer} | TX: {tx_hash}') return response ``` *** ### WSGI Deployment (Gunicorn) ```bash theme={null} # Install Gunicorn pip install gunicorn # Run with multiple workers gunicorn -w 4 -b 0.0.0.0:5000 app:app ``` ```python theme={null} # gunicorn.conf.py bind = "0.0.0.0:5000" workers = 4 worker_class = "sync" timeout = 30 keepalive = 2 # Logging accesslog = "/var/log/gunicorn/access.log" errorlog = "/var/log/gunicorn/error.log" loglevel = "info" ``` *** ## Type Hints Full type hint support with mypy: ```python theme={null} from typing import Optional, Dict, Any from flask import Flask, g, jsonify, Response from prism_flask import prism_payment_middleware from prism_sdk_core import PrismMiddlewareConfig, RoutePaymentConfig app: Flask = Flask(__name__) def get_payer() -> Optional[str]: """Get payer address from request context""" return getattr(g, 'prism_payer', None) def get_payment() -> Optional[Dict[str, Any]]: """Get payment object from request context""" return getattr(g, 'prism_payment', None) @app.route('/api/premium') def premium() -> Response: """Payment-protected endpoint""" payer: Optional[str] = get_payer() return jsonify({ 'message': 'Premium content', 'payer': payer }) ``` *** ## Examples ### AI Agent API ```python theme={null} from flask import Flask, request, jsonify, g from prism_flask import prism_payment_middleware from prism_sdk_core import PrismMiddlewareConfig, RoutePaymentConfig app = Flask(__name__) prism_payment_middleware( app, config=PrismMiddlewareConfig(api_key='your-key'), routes={ '/api/ai/chat': RoutePaymentConfig( price='$0.01', description='AI chat response' ), '/api/ai/image': RoutePaymentConfig( price='$0.50', description='AI image generation' ) } ) @app.route('/api/ai/chat', methods=['POST']) def ai_chat(): """AI chat endpoint""" data = request.get_json() message = data.get('message') payer = getattr(g, 'prism_payer') # Call AI service response = call_ai_service(message) return jsonify({ 'response': response, 'payer': payer, 'credits_used': 1 }) ``` *** ### Content Paywall ```python theme={null} @app.route('/articles/') def get_article(article_id): """Payment-protected article access""" payer = getattr(g, 'prism_payer') article = db.get_article(article_id) return jsonify({ 'id': article.id, 'title': article.title, 'content': article.content, 'author': article.author, 'payer': payer }) ``` *** ### Premium API with Rate Limiting ```python theme={null} from flask_limiter import Limiter from flask_limiter.util import get_remote_address limiter = Limiter( app=app, key_func=get_remote_address, default_limits=["100 per hour"] ) @app.route('/api/premium') @limiter.limit("10 per minute") # Additional rate limit for premium def premium(): """Rate-limited premium endpoint""" payer = getattr(g, 'prism_payer') return jsonify({ 'message': 'Premium content', 'payer': payer }) ``` *** ## Troubleshooting **Make sure you installed the correct package:** ```bash theme={null} pip install prism-flask # Not prism-sdk-core alone ``` **Check Python version:** ```bash theme={null} python --version # Requires Python 3.8+ ``` **Check:** 1. Payment header is properly formatted JSON string 2. Middleware is configured BEFORE route definitions 3. Route path matches configured route pattern 4. Payment signature is valid **Debug:** `python @app.before_request def debug_payment(): print(f'Path: {request.path}') print(f'Payment header:{" "} {request.headers.get("X-PAYMENT")}') print(f'Payer:{" "} {getattr(g, "prism_payer", "NOT SET")}') ` **Common causes:** 1. Insufficient balance in sender's wallet 2. Token allowance not set for USDC contract 3. Network congestion (transaction timeout) 4. Nonce already used (replay attack prevention) **Check logs:** `python import logging logging.basicConfig(level=logging.DEBUG) ` **Install type stubs:** ```bash theme={null} pip install types-Flask ``` **Configure mypy:** ```ini theme={null} # mypy.ini [mypy] python_version = 3.8 plugins = sqlmypy ``` *** ## Flask-CORS Integration Enable CORS for browser-based clients: ```python theme={null} from flask import Flask from flask_cors import CORS from prism_flask import prism_payment_middleware app = Flask(__name__) # Configure CORS CORS(app, resources={ r"/api/*": { "origins": ["https://yourapp.com"], "methods": ["GET", "POST"], "allow_headers": ["Content-Type", "X-PAYMENT"], "expose_headers": ["X-PAYMENT-RESPONSE"] } }) # Then configure Prism prism_payment_middleware(app, config, routes) ``` *** ## Blueprint Support Works with Flask Blueprints: ```python theme={null} from flask import Blueprint, g, jsonify api_bp = Blueprint('api', __name__, url_prefix='/api') @api_bp.route('/premium') def premium(): """Blueprint route - payment still works!""" payer = getattr(g, 'prism_payer') return jsonify({'message': 'Premium', 'payer': payer}) # Register blueprint app.register_blueprint(api_bp) # Configure Prism with blueprint routes prism_payment_middleware( app, config=config, routes={ '/api/premium': RoutePaymentConfig(price=0.01, description='Premium') } ) ``` # TypeScript / JavaScript SDK Source: https://developers.fd.xyz/prism/sdk/typescript Express.js middleware for x402 payment protocol ## Overview The **Prism Express Middleware** is a drop-in solution for protecting your Node.js API routes with blockchain-based micropayments using the x402 protocol. Works out of the box with Express.js Full TypeScript support with IntelliSense Per-route pricing with wildcards ## What's New in v1.1 **Latest Version:** 1.1.0 - Enhanced error handling and Gateway integration **New error classes** for better debugging and monitoring: * `PrismGatewayError` - Preserves Gateway status codes and trace IDs * `PrismNetworkError` - Network connectivity issues (503) * `PrismConfigError` - SDK misconfiguration (500) * `PrismPaymentError` - Invalid payment (402) * `PrismValidationError` - Request validation (400) **Benefits:** * ✅ Type-safe error handling with `instanceof` checks * ✅ Gateway trace IDs for backend correlation * ✅ Timestamps for log searching * ✅ Original status codes preserved (not all converted to 500) **Debug production issues faster** with Gateway trace IDs: ```typescript theme={null} if (error instanceof PrismGatewayError) { console.log('Trace ID:', error.traceId); // "0HNGT483NH6I8:00000001" console.log('Timestamp:', error.timestamp); console.log('Details:', error.details); // Full error message from Gateway } ``` Include trace IDs in support tickets for faster resolution. **Implemented** `verifyPayment()` endpoint integration: * Cryptographic signature verification via Gateway * Payer address extracted and stored in `res.locals.payer` * Proper error handling for verification failures **Better error messages** for end-users: ```json theme={null} { "x402Version": 1, "error": "Internal Error", "details": "Database query failed: column p.ProviderId does not exist", "gateway": { "traceId": "0HNGT483NH6I8:00000001", "timestamp": "2025-11-06T13:13:00Z" } } ``` **Before:** Generic "Failed to generate payment requirements"\ **After:** Detailed error with trace ID and timestamp ## Installation ```bash theme={null} npm install @1stdigital/prism-express ``` ```bash theme={null} yarn add @1stdigital/prism-express ``` ```bash theme={null} pnpm add @1stdigital/prism-express ``` **Package Registry:** GitHub Packages **Scope:** `@1stdigital` **Package:** `prism-express` ## Quick Start ```bash theme={null} npm install @1stdigital/prism-express ``` ```typescript theme={null} import express from 'express'; import { prismPaymentMiddleware } from '@1stdigital/prism-express'; ``` ```typescript theme={null} const app = express(); app.use( prismPaymentMiddleware( { apiKey: process.env.PRISM_API_KEY }, { '/api/premium': { price: 0.01, description: 'Premium API access' } } ) ); ``` ```typescript theme={null} app.get('/api/premium', (req, res) => { res.json({ data: 'Premium content' }); }); app.listen(3000); ``` ## Basic Usage ```typescript theme={null} import express from "express"; import { prismPaymentMiddleware } from "@1stdigital/prism-express"; const app = express(); // Configure payment middleware app.use( prismPaymentMiddleware( { apiKey: "dev-key-123", }, { "/api/weather": { price: 0.001, description: "Weather API access", mimeType: "application/json", }, "/api/premium/*": { price: 0.01, description: "Premium API endpoints", }, }, ), ); // Public endpoint (no payment) app.get("/", (req, res) => { res.json({ message: "Welcome to Prism API" }); }); // Protected endpoint (requires payment) app.get("/api/weather", (req, res) => { res.json({ location: "San Francisco", temperature: 72, condition: "Sunny", }); }); // Protected premium endpoint app.get("/api/premium/data", (req, res) => { res.json({ premium: true, data: "This is premium content", }); }); app.listen(3000, () => { console.log("Server running on http://localhost:3000"); }); ``` ## Configuration ### Middleware Config The first parameter to `prismPaymentMiddleware` is the global configuration: ```typescript theme={null} interface PrismMiddlewareConfig { apiKey: string; // Required: Your Prism API key baseUrl?: string; // Optional: Gateway URL (defaults to test environment) timeout?: number; // Optional: Request timeout in ms (default: 10000) retries?: number; // Optional: Failed request retries (default: 3) } ``` Your Prism API key from [Client Portal](https://prism.1stdigital.tech) Store in environment variable: `process.env.PRISM_API_KEY` Prism Gateway URL. Defaults to test environment. * Test: `https://prism-api.test.1stdigital.tech` (default) * Production: `https://prism-api.1stdigital.tech` ```typescript theme={null} { apiKey: 'key', baseUrl: 'https://prism-api.1stdigital.tech' // Production } ``` Request timeout in milliseconds Gateway calls that exceed timeout will fail gracefully Number of retry attempts for failed Gateway requests ### Route Configuration The second parameter defines which routes require payment: ```typescript theme={null} interface RoutePaymentConfig { price: number | string; // Required: Price per request description?: string; // Optional: Human-readable description mimeType?: string; // Optional: Response content type maxTimeoutSeconds?: number; // Optional: Payment timeout resource?: string; // Optional: Custom resource URL } ``` Price to charge per request **Formats:** - Number: `0.001` (USDC) - String: `"$0.001"` (explicit USD) - String: `"0.001 USDC"` (explicit token) Use small decimals for micropayments: `0.001` = \$0.001 = 0.1¢ Human-readable description shown to users ```typescript theme={null} { price: 0.01, description: 'Weather API - Current conditions' } ``` Response content type **Common values:** - `application/json` - `text/html` - `image/png` - `video/mp4` Maximum time (seconds) to wait for payment After timeout, payment authorization expires Custom resource URL (overrides request path) ```typescript theme={null} { price: 0.01, resource: 'https://api.example.com/v2/premium' } ``` ## Route Patterns ### Exact Match ```typescript theme={null} { '/api/weather': { price: 0.001, description: 'Weather data' } } ``` Matches: * ✅ `/api/weather` * ❌ `/api/weather/forecast` * ❌ `/api/weather-data` ### Wildcard Routes ```typescript theme={null} { '/api/premium/*': { price: 0.01, description: 'All premium endpoints' } } ``` Matches: * ✅ `/api/premium/data` * ✅ `/api/premium/analytics` * ✅ `/api/premium/users/123` * ❌ `/api/public/data` ### Multiple Routes ```typescript theme={null} { '/api/basic': { price: 0.001, description: 'Basic tier' }, '/api/premium': { price: 0.01, description: 'Premium tier' }, '/api/enterprise/*': { price: 0.1, description: 'Enterprise tier' } } ``` ### Route Priority When multiple patterns match, **most specific wins**: ```typescript theme={null} { '/api/*': { price: 0.001 }, // Generic '/api/premium/*': { price: 0.01 }, // More specific '/api/premium/gold': { price: 0.05 } // Most specific (used) } ``` Request to `/api/premium/gold` → uses `0.05` price ## Accessing Payment Info Payment details are available in route handlers via `res.locals.payment`: ```typescript theme={null} app.get("/api/premium", (req, res) => { const payment = res.locals.payment; console.log("Payment scheme:", payment.scheme); console.log("Network:", payment.network); console.log("Token:", payment.asset); console.log("Amount:", payment.amount); console.log("Transaction hash:", payment.txHash); res.json({ data: "Premium content" }); }); ``` ### Payment Object Type ```typescript theme={null} interface PaymentInfo { scheme: string; // 'exact' | 'range' | 'any' network: string; // 'base-sepolia' | 'ethereum' | 'bsc' asset: string; // Token contract address amount: string; // Amount in smallest unit payTo: string; // Provider wallet address txHash?: string; // Transaction hash (after settlement) validAfter: number; // Unix timestamp validBefore: number; // Unix timestamp nonce: string; // Unique payment nonce } ``` ## Error Handling The SDK provides structured error handling with specific error classes, detailed error messages, and Gateway tracing information. ### Error Classes All Prism errors extend the base `PrismError` class and include: * **Status code** - HTTP status code to return * **Error code** - Machine-readable error identifier * **Message** - Human-readable error description * **Details** - Additional context (varies by error type) **When:** Prism Gateway API returns 4xx or 5xx response **Properties:** * `statusCode` - Original HTTP status from Gateway (400, 401, 500, etc.) * `message` - Error title from Gateway * `details` - Detailed error description * `traceId` - Gateway trace ID for debugging * `timestamp` - Error timestamp from Gateway **Example Response:** ```json theme={null} { "x402Version": 1, "error": "Internal Error", "details": "Database query failed: column p.ProviderId does not exist", "gateway": { "traceId": "0HNGT483NH6I8:00000001", "timestamp": "2025-11-06T13:13:00.2646107Z" } } ``` **Usage:** ```typescript theme={null} import { PrismGatewayError } from '@1stdigital/prism-core'; app.use((err, req, res, next) => { if (err instanceof PrismGatewayError) { // Log with trace ID for backend correlation logger.error('Gateway error', { traceId: err.traceId, statusCode: err.statusCode, details: err.details }); // Alert on 5xx errors if (err.statusCode >= 500) { alertOps('Prism Gateway error', { traceId: err.traceId }); } } next(err); }); ``` **When:** Cannot reach Prism Gateway (timeout, DNS error, connection refused) **Status Code:** 503 Service Unavailable **Properties:** * `message` - Network error description * `originalError` - Original axios/network error **Example Response:** ```json theme={null} { "x402Version": 1, "error": "Payment service unavailable", "details": "Could not connect to Prism Gateway. Please try again later.", "retryAfter": 60 } ``` **Usage (Retry Logic):** ```typescript theme={null} import { PrismNetworkError } from '@1stdigital/prism-core'; async function getPaymentRequirementsWithRetry(client, request, maxRetries = 3) { for (let i = 0; i < maxRetries; i++) { try { return await client.getPaymentRequirements(request); } catch (error) { if (error instanceof PrismNetworkError) { console.log(`Network error, retrying (${i + 1}/${maxRetries})...`); await sleep(1000 * Math.pow(2, i)); // Exponential backoff continue; } throw error; // Non-network error, don't retry } } throw new Error('Max retries exceeded'); } ``` **When:** SDK is misconfigured (invalid API key format, bad URL, etc.) **Status Code:** 500 Internal Server Error **Properties:** * `message` - Configuration error description * `details` - Configuration problem details **Example Response:** ```json theme={null} { "x402Version": 1, "error": "Payment service misconfigured", "details": "Please contact the administrator." } ``` **Usage:** ```typescript theme={null} import { PrismConfigError } from '@1stdigital/prism-core'; app.use((err, req, res, next) => { if (err instanceof PrismConfigError) { // Alert developers - this is a code/config issue alertDevs('Prism SDK misconfiguration', { error: err.message, details: err.details }); // Don't expose config details to clients return res.status(500).json({ error: 'Service temporarily unavailable' }); } next(err); }); ``` **When:** Payment payload is malformed or invalid **Status Code:** 402 Payment Required **Properties:** * `message` - Payment validation error * `details` - Why payment is invalid **Example Response:** ```json theme={null} { "x402Version": 1, "error": "Invalid payment authorization", "details": "Signature verification failed" } ``` **When:** Request data fails validation (negative price, missing fields, etc.) **Status Code:** 400 Bad Request **Properties:** * `message` - Validation error message * `field` - Field that failed validation * `details` - Validation error details **Example Response:** ```json theme={null} { "x402Version": 1, "error": "Validation failed", "details": "requestedAmount must be a positive number", "field": "requestedAmount" } ``` ### Error Response Format All error responses follow a consistent structure: ```typescript theme={null} interface PrismErrorResponse { x402Version: number; // Always 1 error: string; // Human-readable error message details?: string; // Additional error details gateway?: { // Gateway-specific info (if applicable) traceId?: string; // Trace ID for backend correlation timestamp?: string; // ISO 8601 timestamp }; retryAfter?: number; // Seconds to wait before retry (503 errors) } ``` ### Advanced Error Handling #### 1. Monitoring & Alerting with Trace IDs ```typescript theme={null} import { PrismGatewayError, PrismNetworkError } from "@1stdigital/prism-core"; app.use((err, req, res, next) => { if (err instanceof PrismGatewayError) { // Log to monitoring service with Gateway trace ID logger.error("Prism Gateway Error", { traceId: err.traceId, timestamp: err.timestamp, statusCode: err.statusCode, message: err.message, details: err.details, endpoint: req.path, method: req.method, }); // Alert on critical errors (5xx) if (err.statusCode >= 500) { alerting.critical("Prism Gateway Internal Error", { traceId: err.traceId, endpoint: req.path, message: err.message, }); } // Return error with trace ID for support return res.status(err.statusCode).json({ x402Version: 1, error: err.message, details: err.details, support: { traceId: err.traceId, message: "Please contact support with this trace ID", }, }); } if (err instanceof PrismNetworkError) { // Alert ops - Gateway is down alerting.critical("Prism Gateway Unreachable", { error: err.message, originalError: err.originalError?.code, }); return res.status(503).json({ x402Version: 1, error: "Payment service temporarily unavailable", retryAfter: 60, }); } next(err); }); ``` #### 2. Retry Logic with Exponential Backoff ```typescript theme={null} import { PrismClient, PrismNetworkError, PrismGatewayError, } from "@1stdigital/prism-core"; async function makePaymentRequestWithRetry( client: PrismClient, request: any, maxRetries = 3, ) { for (let attempt = 0; attempt < maxRetries; attempt++) { try { return await client.getPaymentRequirements(request); } catch (error) { // Retry on network errors if (error instanceof PrismNetworkError) { const delay = 1000 * Math.pow(2, attempt); // 1s, 2s, 4s console.log( `Network error, retrying in ${delay}ms (${attempt + 1}/${maxRetries})`, ); await sleep(delay); continue; } // Retry on Gateway 5xx errors (server issues) if (error instanceof PrismGatewayError && error.statusCode >= 500) { if (attempt < maxRetries - 1) { const delay = 1000 * Math.pow(2, attempt); console.log( `Gateway error ${error.statusCode}, retrying in ${delay}ms`, ); await sleep(delay); continue; } } // Don't retry on 4xx errors (client errors) if (error instanceof PrismGatewayError && error.statusCode < 500) { throw error; } throw error; } } throw new Error("Max retries exceeded"); } function sleep(ms: number) { return new Promise((resolve) => setTimeout(resolve, ms)); } ``` #### 3. User-Friendly Error Messages ```typescript theme={null} import { PrismGatewayError } from "@1stdigital/prism-core"; app.use((err, req, res, next) => { if (err instanceof PrismGatewayError) { // Map Gateway errors to user-friendly messages const userMessages: Record = { 400: "Invalid payment request. Please check your payment details.", 401: "Payment authentication failed. Please try again.", 403: "Payment not authorized. Please contact support.", 404: "Payment resource not found.", 500: "Payment service is experiencing issues. Please try again later.", 502: "Payment gateway temporarily unavailable.", 503: "Payment service under maintenance. Please try again soon.", }; const userMessage = userMessages[err.statusCode] || "Payment processing failed."; return res.status(err.statusCode).json({ error: userMessage, support: { message: "If the problem persists, contact support with this information:", traceId: err.traceId, timestamp: err.timestamp, }, }); } next(err); }); ``` #### 4. Error Metrics & Analytics ```typescript theme={null} import { PrismGatewayError, PrismNetworkError } from "@1stdigital/prism-core"; import { Counter, Histogram } from "prom-client"; const paymentErrorCounter = new Counter({ name: "prism_payment_errors_total", help: "Total payment errors by type and status", labelNames: ["error_type", "status_code"], }); const gatewayLatency = new Histogram({ name: "prism_gateway_latency_seconds", help: "Prism Gateway API latency", }); app.use((err, req, res, next) => { if (err instanceof PrismGatewayError) { paymentErrorCounter.inc({ error_type: "gateway", status_code: err.statusCode, }); // Track error by category if (err.statusCode >= 500) { metrics.increment("prism.gateway.server_error"); } else if (err.statusCode >= 400) { metrics.increment("prism.gateway.client_error"); } } if (err instanceof PrismNetworkError) { paymentErrorCounter.inc({ error_type: "network", status_code: 503, }); metrics.increment("prism.gateway.unreachable"); } next(err); }); ``` ### Debugging with Trace IDs When reporting issues to Prism support, always include the **trace ID** from error responses: ```typescript theme={null} // Extract trace ID from error response app.get("/api/paid", async (req, res) => { try { // ... payment logic } catch (error) { if (error instanceof PrismGatewayError) { console.error("Payment failed", { traceId: error.traceId, // ← Include in bug reports timestamp: error.timestamp, statusCode: error.statusCode, message: error.message, }); // Return trace ID to client for support tickets return res.status(error.statusCode).json({ error: "Payment processing failed", supportInfo: { traceId: error.traceId, message: "Please contact support with this trace ID", }, }); } } }); ``` **Example support ticket:** ``` Subject: Payment Gateway Error - 500 Internal Server Error Gateway Trace ID: 0HNGT483NH6I8:00000001 Timestamp: 2025-11-06T13:13:00.2646107Z Endpoint: POST /api/v1/payment/requirements Error: "42703: column p.ProviderId does not exist" ``` ## Advanced Usage ### Environment-Based Configuration ```typescript theme={null} const config = { apiKey: process.env.PRISM_API_KEY, }; app.use(prismPaymentMiddleware(config, routes)); ``` **.env.development:** ```bash theme={null} PRISM_API_KEY=dev-key-123 NODE_ENV=development ``` **.env.production:** ```bash theme={null} PRISM_API_KEY=prod-key-456 NODE_ENV=production ``` ### Accessing Payment Details After successful payment verification, payment details are available in `res.locals`: ```typescript theme={null} app.get("/api/premium", (req, res) => { // Payment payload (decoded from X-PAYMENT header) const payment = res.locals.payment; // Payer wallet address (from verification) const payerAddress = res.locals.payer; console.log("Payment received from:", payerAddress); console.log("Payment scheme:", payment.scheme); console.log("Network:", payment.network); res.json({ data: "Premium content", paidBy: payerAddress, }); }); ``` ### Rate Limiting with Payments Combine with rate limiting for hybrid monetization: ```typescript theme={null} import rateLimit from "express-rate-limit"; // Free tier: 100 requests/hour const freeLimiter = rateLimit({ windowMs: 60 * 60 * 1000, max: 100, message: "Free tier limit reached. Please upgrade or pay per request.", }); app.use("/api/free", freeLimiter); // Paid tier: No rate limit, but requires payment app.use( "/api/paid", prismPaymentMiddleware(config, { "/api/paid/*": { price: 0.001 }, }), ); ``` ## TypeScript Support ### Full Type Definitions ```typescript theme={null} import { prismPaymentMiddleware, PrismMiddlewareConfig, RoutePaymentConfig, PaymentInfo, PrismError, } from "@1stdigital/prism-express"; // Type-safe configuration const config: PrismMiddlewareConfig = { apiKey: process.env.PRISM_API_KEY!, timeout: 15000, }; const routes: Record = { "/api/data": { price: 0.001, description: "Data access", }, }; // Type-safe middleware app.use(prismPaymentMiddleware(config, routes)); // Type-safe payment info app.get("/api/data", (req, res) => { const payment: PaymentInfo | undefined = res.locals.payment; if (payment) { console.log(`Paid ${payment.amount} on ${payment.network}`); } res.json({ data: "Content" }); }); ``` ### Express Types Extension Add types to `res.locals`: ```typescript theme={null} // types/express.d.ts import { PaymentInfo } from "@1stdigital/prism-express"; declare global { namespace Express { interface Locals { payment?: PaymentInfo; prismPrice?: number; } } } ``` ## Testing ### Unit Tests ```typescript theme={null} import request from "supertest"; import express from "express"; import { prismPaymentMiddleware } from "@1stdigital/prism-express"; describe("Prism Payment Middleware", () => { let app: express.Application; beforeEach(() => { app = express(); app.use( prismPaymentMiddleware( { apiKey: "test-key" }, { "/api/paid": { price: 0.01 } }, ), ); app.get("/api/paid", (req, res) => { res.json({ data: "content" }); }); }); it("should return 402 for unpaid request", async () => { const res = await request(app).get("/api/paid"); expect(res.status).toBe(402); expect(res.headers["x-payment"]).toBeDefined(); }); it("should allow request with valid payment", async () => { const paymentHeader = generateValidPayment(); const res = await request(app) .get("/api/paid") .set("X-PAYMENT-AUTHORIZATION", paymentHeader); expect(res.status).toBe(200); expect(res.body.data).toBe("content"); }); }); ``` ### Integration Tests with Sandbox ```typescript theme={null} import { ethers } from "ethers"; describe("Payment Flow Integration", () => { it("should complete full payment cycle", async () => { // 1. Request protected resource const res1 = await request(app).get("/api/paid"); expect(res1.status).toBe(402); // 2. Parse payment requirements const payment = JSON.parse(res1.headers["x-payment"]); // 3. Sign authorization (with test wallet) const wallet = new ethers.Wallet(process.env.TEST_PRIVATE_KEY!); const signature = await wallet.signTypedData( payment.typedData.domain, { TransferWithAuthorization: payment.typedData.types.TransferWithAuthorization, }, payment.typedData.message, ); // 4. Retry with signature const res2 = await request(app) .get("/api/paid") .set( "X-PAYMENT-AUTHORIZATION", JSON.stringify({ signature, ...payment.typedData.message, }), ); expect(res2.status).toBe(200); expect(res2.body.data).toBe("content"); }); }); ``` ## Performance Optimization ### Caching Payment Requirements ```typescript theme={null} import NodeCache from "node-cache"; const cache = new NodeCache({ stdTTL: 300 }); // 5 minutes app.use((req, res, next) => { const cacheKey = `payment:${req.path}`; const cached = cache.get(cacheKey); if (cached) { res.set("X-PAYMENT", JSON.stringify(cached)); return res.status(402).json({ error: "Payment required" }); } next(); }); app.use(prismPaymentMiddleware(config, routes)); ``` ### Connection Pooling ```typescript theme={null} const config = { apiKey: process.env.PRISM_API_KEY, timeout: 5000, retries: 2, // Use HTTP keep-alive httpAgent: new http.Agent({ keepAlive: true }), httpsAgent: new https.Agent({ keepAlive: true }), }; ``` ## Migration Guide ### From v0.x to v1.x ```bash theme={null} npm install @1stdigital/prism-express@latest ``` ```typescript theme={null} // Old import { prismMiddleware } from '@prism/middleware'; // New import { prismPaymentMiddleware } from '@1stdigital/prism-express'; ``` ```typescript theme={null} // Old prismMiddleware({ key: 'xxx', sandbox: true }, routes) // New prismPaymentMiddleware({ apiKey: 'xxx' }, routes) ``` ## Troubleshooting **Symptoms:** Routes always return 200, no payment required **Solutions:** * Verify middleware is registered **before** route handlers * Check route paths match exactly (case-sensitive) * Ensure `apiKey` is valid * Check Gateway connectivity (`curl https://prism-api.test.1stdigital.tech/health`) **Symptoms:** `503 Service Unavailable` or `PrismNetworkError` **Solutions:** * Check network connectivity to Gateway * Verify no firewall blocking outbound HTTPS to `prism-api.test.1stdigital.tech` * Check Gateway status page * Implement retry logic (see Error Handling section) **Example:** ```typescript theme={null} import { PrismNetworkError } from '@1stdigital/prism-core'; if (error instanceof PrismNetworkError) { console.error('Gateway unreachable:', error.message); // Retry or alert ops team } ``` **Symptoms:** `401 Unauthorized` or `PrismGatewayError` with status 401 **Solutions:** * Verify API key in [Client Portal](https://prism.1stdigital.tech) * Check environment variable: `echo $PRISM_API_KEY` * Ensure key hasn't been revoked * Use correct environment (sandbox vs production) **Check error details:** ```typescript theme={null} if (error instanceof PrismGatewayError && error.statusCode === 401) { console.error('Auth error:', { traceId: error.traceId, details: error.details }); } ``` **Symptoms:** `PrismGatewayError` with status 500 and trace ID **Solutions:** * **This is a Gateway backend issue** - not your code * Extract and log the `traceId` from error * Report to Prism support with trace ID **Example:** ```typescript theme={null} if (error instanceof PrismGatewayError && error.statusCode >= 500) { console.error('Gateway internal error:', { traceId: error.traceId, // ← Include in support ticket timestamp: error.timestamp, details: error.details }); // Alert ops to contact Prism support alertOps(`Gateway error - Trace ID: ${error.traceId}`); } ``` **Symptoms:** Type errors during build **Solutions:** * Install type definitions: `npm install -D @types/express` * Update `tsconfig.json`: `"moduleResolution": "node"` * Ensure TypeScript version >= 4.5 ## API Reference ### `prismPaymentMiddleware(config, routes)` Creates Express middleware for x402 payment protection. **Parameters:** * `config` (PrismMiddlewareConfig) - Global configuration * `routes` (Record\) - Route payment settings **Returns:** Express middleware function **Example:** ```typescript theme={null} const middleware = prismPaymentMiddleware( { apiKey: "key" }, { "/api/paid": { price: 0.01 } }, ); app.use(middleware); ``` ### Error Classes #### `PrismError` (Base Class) Base error class for all Prism SDK errors. **Properties:** * `message` (string) - Error message * `code` (string) - Error code (e.g., 'GATEWAY\_ERROR', 'NETWORK\_ERROR') * `statusCode` (number) - HTTP status code * `details` (any) - Additional error context **Methods:** * `toJSON()` - Convert error to JSON representation **Example:** ```typescript theme={null} import { PrismError } from "@1stdigital/prism-core"; if (error instanceof PrismError) { console.log(error.code); // 'GATEWAY_ERROR' console.log(error.statusCode); // 500 console.log(error.toJSON()); // { name, message, code, statusCode, details } } ``` #### `PrismGatewayError extends PrismError` Gateway returned an error response (4xx or 5xx). **Additional Properties:** * `traceId` (string | undefined) - Gateway trace ID for debugging * `timestamp` (string | undefined) - ISO 8601 timestamp from Gateway **Example:** ```typescript theme={null} import { PrismGatewayError } from "@1stdigital/prism-core"; app.use((err, req, res, next) => { if (err instanceof PrismGatewayError) { logger.error("Gateway error", { traceId: err.traceId, statusCode: err.statusCode, message: err.message, details: err.details, }); } }); ``` #### `PrismNetworkError extends PrismError` Network connectivity issues (timeout, DNS error, connection refused). **Additional Properties:** * `originalError` (any) - Original network error from axios **Status Code:** Always 503 **Example:** ```typescript theme={null} import { PrismNetworkError } from "@1stdigital/prism-core"; if (error instanceof PrismNetworkError) { console.log("Gateway unreachable:", error.message); console.log("Original error:", error.originalError); // Implement retry logic or alert ops } ``` #### `PrismConfigError extends PrismError` SDK misconfiguration (invalid API key format, bad URL, etc.). **Status Code:** Always 500 **Example:** ```typescript theme={null} import { PrismConfigError } from "@1stdigital/prism-core"; if (error instanceof PrismConfigError) { // Developer/ops issue - fix configuration alertDevs("Prism SDK misconfigured: " + error.message); } ``` #### `PrismPaymentError extends PrismError` Invalid payment payload or authorization. **Status Code:** Always 402 **Example:** ```typescript theme={null} import { PrismPaymentError } from "@1stdigital/prism-core"; if (error instanceof PrismPaymentError) { // Client sent invalid payment return res.status(402).json({ error: error.message, details: error.details, }); } ``` #### `PrismValidationError extends PrismError` Request validation failed. **Additional Properties:** * `field` (string | undefined) - Field that failed validation **Status Code:** Always 400 **Example:** ```typescript theme={null} import { PrismValidationError } from "@1stdigital/prism-core"; if (error instanceof PrismValidationError) { return res.status(400).json({ error: "Validation failed", field: error.field, message: error.message, }); } ``` ### Error Codes | Code | Error Class | Description | | ------------------ | -------------------- | -------------------------------- | | `GATEWAY_ERROR` | PrismGatewayError | Gateway returned error (4xx/5xx) | | `NETWORK_ERROR` | PrismNetworkError | Cannot reach Gateway | | `CONFIG_ERROR` | PrismConfigError | Invalid SDK configuration | | `PAYMENT_ERROR` | PrismPaymentError | Invalid payment | | `VALIDATION_ERROR` | PrismValidationError | Request validation failed | | `UNKNOWN_ERROR` | PrismError | Unclassified error | ## Resources View source code and examples Package on GitHub Packages Prism Gateway REST API docs Complete code examples # Express.js Integration Source: https://developers.fd.xyz/prism/sdk/typescript/express Protect Express APIs with blockchain micropayments ## Overview The **@1stdigital/prism-express** package provides Express.js middleware for payment-protecting your API routes using the x402 protocol. It's the reference implementation for the Prism SDK and offers the most straightforward integration. Single middleware function, 5 lines of code Exact paths, wildcards, and patterns Access payer address in req.payer *** ## Installation ```bash theme={null} npm install @1stdigital/prism-express ``` `bash yarn add @1stdigital/prism-express ` ```bash theme={null} pnpm add @1stdigital/prism-express ``` *** ## Quick Start ```javascript theme={null} const express = require("express"); const { prismPaymentMiddleware } = require("@1stdigital/prism-express"); const app = express(); // Configure Prism middleware app.use( prismPaymentMiddleware( { apiKey: process.env.PRISM_API_KEY || "dev-key-123", baseUrl: "https://prism-api.test.1stdigital.tech", }, { "/api/premium": { price: 0.01, // 0.01 ETH description: "Premium API access", }, }, ), ); // Protected route - payment verified automatically app.get("/api/premium", (req, res) => { res.json({ message: "Premium content!", payer: req.payer, // Wallet address that paid }); }); app.listen(3000); ``` *** ## Configuration ### Middleware Configuration ```typescript theme={null} interface PrismMiddlewareConfig { apiKey: string; // Your Prism API key baseUrl?: string; // Gateway URL (optional, defaults to production) debug?: boolean; // Enable debug logging (default: false) } ``` ### Route Configuration ```typescript theme={null} interface RoutePaymentConfig { price: number | string; // Price in USD (0.01 = $0.01 or '$0.001' string format) description: string; // Human-readable description mimeType?: string; // Response MIME type (default: 'application/json') } type RouteConfig = Record; ``` *** ## Route Protection Patterns ### Exact Path Matching ```javascript theme={null} app.use( prismPaymentMiddleware(config, { "/api/premium": { price: 0.01, description: "Premium API", }, "/api/weather": { price: "$0.001", description: "Weather data", }, }), ); // ✅ Protected: GET /api/premium // ✅ Protected: GET /api/weather // ❌ Not protected: GET /api/public ``` *** ### Wildcard Matching ```javascript theme={null} app.use( prismPaymentMiddleware(config, { "/api/*": { price: 0.005, description: "API access", }, }), ); // ✅ Protected: GET /api/users // ✅ Protected: GET /api/posts/123 // ✅ Protected: POST /api/comments // ❌ Not protected: GET /public ``` *** ### Multiple Route Groups ```javascript theme={null} app.use( prismPaymentMiddleware(config, { // Free tier (low price) "/api/basic/*": { price: "$0.0001", description: "Basic API", }, // Premium tier (higher price) "/api/premium/*": { price: "$0.01", description: "Premium API", }, // Specific expensive endpoint "/api/ai/generate": { price: "$0.50", description: "AI content generation", }, }), ); ``` *** ### Dynamic Pricing (per-route middleware) ```javascript theme={null} // Global middleware for common routes app.use( prismPaymentMiddleware(config, { "/api/*": { price: 0.001, description: "API" }, }), ); // Override for specific route with custom middleware app.get( "/api/expensive", prismPaymentMiddleware(config, { "/api/expensive": { price: 0.1, description: "Expensive operation", }, }), (req, res) => { res.json({ result: "Expensive computation" }); }, ); ``` *** ## Accessing Payment Information The middleware adds payment information to the request object: ```javascript theme={null} app.get("/api/premium", (req, res) => { // Access payer wallet address const payer = req.payer; // '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb' // Access full payment object (added to res.locals) const payment = res.locals.payment; /* { scheme: 'eip3009', network: 'eth-sepolia', asset: 'usdc', amount: '10000', recipient: '0x...', nonce: '0x...', validBefore: 1735430400, signature: '0x...' } */ res.json({ message: "Premium content", payer, payment: { network: payment.network, asset: payment.asset, }, }); }); ``` *** ## Settlement Validation The Express middleware uses **res.end() interception** to validate settlement before sending data: ```javascript theme={null} // Internal implementation (you don't need to write this!) const originalEnd = res.end.bind(res); res.end = async function (chunk, encoding, callback) { // Perform settlement BEFORE sending response const settlementResult = await core.settlementCallback(...); if (!settlementResult || !settlementResult.success) { // ❌ Settlement failed - send error instead of data res.status(402); return originalEnd(JSON.stringify({ error: 'Payment settlement failed', details: settlementResult?.errorReason })); } // ✅ Settlement succeeded - send original data with transaction hash res.setHeader('X-PAYMENT-RESPONSE', settlementResult.transaction); return originalEnd(chunk, encoding, callback); }; ``` **Key Points:** * Works with `res.json()`, `res.send()`, `res.sendFile()`, etc. * Settlement happens **before** data reaches the client * Original response is replaced with 402 error if settlement fails See [Settlement Validation](/concepts/settlement-validation) for details. *** ## Error Handling ### Payment Errors When a request lacks valid payment, the middleware returns **402 Payment Required**: ```json theme={null} HTTP/1.1 402 Payment Required { "x402Version": 1, "paymentRequired": true, "acceptedPayments": [ { "scheme": "eip3009", "network": "eth-sepolia", "asset": "usdc", "amount": "10000", "recipient": "0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb", "nonce": "0xabc123...", "validBefore": 1735430400 } ], "description": "Premium API access", "priceUSD": "0.01" } ``` *** ### Gateway Errors If the Prism Gateway is unreachable or returns an error: ```json theme={null} HTTP/1.1 500 Internal Server Error { "x402Version": 1, "error": "Gateway Error", "details": "Failed to contact payment gateway", "gateway": { "traceId": "0HNGT483NH6I8:00000001", "timestamp": "2025-12-18T10:30:00Z" } } ``` **Include the `traceId` when contacting support!** *** ### Settlement Errors If payment verification succeeds but settlement fails: ```json theme={null} HTTP/1.1 402 Payment Required { "x402Version": 1, "error": "Payment settlement failed", "details": "Insufficient balance" } ``` *** ### Custom Error Handling You can add Express error handlers after the Prism middleware: ```javascript theme={null} app.use(prismPaymentMiddleware(config, routes)); // Custom error handler app.use((err, req, res, next) => { if (err.name === "PrismPaymentError") { // Handle Prism-specific errors console.error("Payment error:", err.message); res.status(err.statusCode || 402).json({ error: err.message, code: err.code, }); } else { // Handle other errors next(err); } }); ``` *** ## Testing ### Unit Testing with Mocks ```javascript theme={null} const request = require("supertest"); const express = require("express"); const { prismPaymentMiddleware } = require("@1stdigital/prism-express"); describe("Payment-protected routes", () => { let app; beforeEach(() => { app = express(); app.use( prismPaymentMiddleware( { apiKey: "test-key", baseUrl: "http://test-gateway" }, { "/api/premium": { price: 0.01, description: "Test" } }, ), ); app.get("/api/premium", (req, res) => { res.json({ message: "Premium", payer: req.payer }); }); }); test("returns 402 without payment", async () => { const response = await request(app).get("/api/premium"); expect(response.status).toBe(402); expect(response.body.paymentRequired).toBe(true); }); test("allows access with valid payment", async () => { // Mock payment header (in real tests, sign with wallet) const mockPayment = JSON.stringify({ scheme: "eip3009", network: "eth-sepolia", asset: "usdc", amount: "10000", signature: "0x" + "0".repeat(130), }); const response = await request(app) .get("/api/premium") .set("X-PAYMENT", mockPayment); // In test mode, mock signatures are accepted expect(response.status).toBe(200); expect(response.body.message).toBe("Premium"); }); }); ``` *** ### Integration Testing ```javascript theme={null} // test-server.js const app = require("./app"); // Your Express app const request = require("supertest"); // Use real test gateway process.env.PRISM_BASE_URL = "https://prism-api.test.1stdigital.tech"; process.env.PRISM_API_KEY = "your-test-key"; test("full payment flow", async () => { // 1. Request without payment const res1 = await request(app).get("/api/premium"); expect(res1.status).toBe(402); const paymentReq = res1.body.acceptedPayments[0]; // 2. Sign payment with wallet (use ethers.js) const wallet = new ethers.Wallet(process.env.TEST_PRIVATE_KEY); const signature = await wallet.signTypedData(/* EIP-712 domain and message */); const payment = { ...paymentReq, signature, }; // 3. Send request with signed payment const res2 = await request(app) .get("/api/premium") .set("X-PAYMENT", JSON.stringify(payment)); expect(res2.status).toBe(200); expect(res2.headers["x-payment-response"]).toBeTruthy(); // Transaction hash }); ``` *** ## Production Deployment ### Environment Variables ```bash theme={null} # .env PRISM_API_KEY=your-production-api-key PRISM_BASE_URL=https://prism-api.1stdigital.tech NODE_ENV=production ``` ### Production Configuration ```javascript theme={null} const app = express(); app.use( prismPaymentMiddleware( { apiKey: process.env.PRISM_API_KEY, baseUrl: process.env.PRISM_BASE_URL, debug: process.env.NODE_ENV !== "production", // Disable debug in prod }, { "/api/premium": { price: 0.01, description: "Premium API", }, }, ), ); ``` *** ### Monitoring & Logging ```javascript theme={null} const winston = require("winston"); const logger = winston.createLogger({ /* config */ }); // Log payment events app.use((req, res, next) => { const originalJson = res.json.bind(res); res.json = function (data) { if (res.statusCode === 402) { logger.info("Payment required", { path: req.path, ip: req.ip, }); } else if (res.statusCode === 200 && req.payer) { logger.info("Payment succeeded", { path: req.path, payer: req.payer, transaction: res.getHeader("x-payment-response"), }); } return originalJson(data); }; next(); }); ``` *** ### Rate Limiting Combine with rate limiting to prevent abuse: ```javascript theme={null} const rateLimit = require("express-rate-limit"); const limiter = rateLimit({ windowMs: 15 * 60 * 1000, // 15 minutes max: 100, // Max 100 requests per window message: "Too many requests, please try again later", }); // Apply rate limiting BEFORE payment middleware app.use(limiter); app.use(prismPaymentMiddleware(config, routes)); ``` *** ## TypeScript Support Full TypeScript support with type definitions: ```typescript theme={null} import express, { Request, Response } from "express"; import { prismPaymentMiddleware, PrismMiddlewareConfig, RoutePaymentConfig, } from "@1stdigital/prism-express"; const app = express(); const config: PrismMiddlewareConfig = { apiKey: process.env.PRISM_API_KEY!, baseUrl: "https://prism-api.test.1stdigital.tech", debug: false, }; const routes: Record = { "/api/premium": { price: 0.01, description: "Premium API access", }, }; app.use(prismPaymentMiddleware(config, routes)); // Request is augmented with payer property app.get("/api/premium", (req: Request, res: Response) => { const payer: string | undefined = req.payer; // Type-safe! res.json({ message: "Premium content", payer, }); }); ``` *** ## API Reference ### `prismPaymentMiddleware(config, routes)` Creates Express middleware for payment protection. **Parameters:** * `config: PrismMiddlewareConfig` - SDK configuration * `routes: Record` - Protected routes **Returns:** `express.RequestHandler` *** ### `PrismMiddlewareConfig` ```typescript theme={null} interface PrismMiddlewareConfig { apiKey: string; // Required: Your Prism API key baseUrl?: string; // Optional: Gateway URL (default: production) debug?: boolean; // Optional: Enable debug logging (default: false) } ``` *** ### `RoutePaymentConfig` ```typescript theme={null} interface RoutePaymentConfig { price: number | string; // Required: Price in ETH or USD description: string; // Required: Human-readable description mimeType?: string; // Optional: Response MIME type } ``` *** ### Request Augmentation ```typescript theme={null} declare global { namespace Express { interface Request { payer?: string; // Wallet address of payer (added by middleware) } } } ``` *** ## Examples ### AI Agent API ```javascript theme={null} app.use( prismPaymentMiddleware(config, { "/api/ai/chat": { price: "$0.01", description: "AI chat response", }, "/api/ai/image": { price: "$0.50", description: "AI image generation", }, }), ); app.post("/api/ai/chat", express.json(), (req, res) => { const { message } = req.body; const payer = req.payer; // Call AI service const response = callAI(message); res.json({ response, payer, creditsUsed: 1, }); }); ``` *** ### Content Paywall ```javascript theme={null} app.use( prismPaymentMiddleware(config, { "/articles/:id": { price: "$0.001", description: "Article access", }, }), ); app.get("/articles/:id", async (req, res) => { const article = await db.articles.findById(req.params.id); res.json({ title: article.title, content: article.content, author: article.author, payer: req.payer, }); }); ``` *** ## Troubleshooting **Check:** 1. Payment signature is valid (use correct private key) 2. Payment hasn't expired (`validBefore` timestamp) 3. Nonce hasn't been used before (replay protection) 4. Network matches (`eth-sepolia` vs `eth-mainnet`) 5. Amount matches exactly (don't modify `amount` field) **Possible causes:** 1. Insufficient balance in sender's wallet 2. Token allowance not set for USDC contract 3. Network congestion (transaction timeout) 4. Gateway blockchain RPC node down **Solution:** Client should retry after fixing the issue. **Check order:** `javascript // ❌ Wrong order app.get('/api/premium', handler); // Handler registered first app.use(prismPaymentMiddleware(config, routes)); // Middleware too late // ✅ Correct order app.use(prismPaymentMiddleware(config, routes)); // Middleware first app.get('/api/premium', handler); // Handler after ` **Install type definitions:** ```bash theme={null} npm install --save-dev @types/express ``` **Import types:** ```typescript theme={null} import { Request, Response } from 'express'; ``` # Fastify Integration Source: https://developers.fd.xyz/prism/sdk/typescript/fastify Protect Fastify APIs with blockchain micropayments ## Overview The **@1stdigital/prism-fastify** package provides a Fastify plugin for payment-protecting your API routes using the x402 protocol. It leverages Fastify's hook system for high-performance payment validation. Fastify's async hooks for minimal overhead Standard Fastify plugin pattern Full TypeScript support with decorators *** ## Installation ```bash theme={null} npm install @1stdigital/prism-fastify fastify ``` `bash yarn add @1stdigital/prism-fastify fastify ` ```bash theme={null} pnpm add @1stdigital/prism-fastify fastify ``` *** ## Quick Start ```typescript theme={null} import Fastify from "fastify"; import prismPlugin from "@1stdigital/prism-fastify"; const app = Fastify({ logger: true }); // Register Prism plugin await app.register(prismPlugin, { apiKey: process.env.PRISM_API_KEY || "dev-key-123", baseUrl: "https://prism-api.test.1stdigital.tech", routes: { "/api/premium": { price: 0.01, // $0.01 USD description: "Premium API access", }, }, }); // Protected route - payment verified automatically app.get("/api/premium", async (request, reply) => { return { message: "Premium content!", payer: request.prismPayer, // Wallet address }; }); await app.listen({ port: 3000 }); ``` *** ## Configuration ### Plugin Options ```typescript theme={null} interface PrismFastifyOptions { apiKey: string; // Your Prism API key baseUrl?: string; // Gateway URL (optional) debug?: boolean; // Enable debug logging routes: Record; } interface RoutePaymentConfig { price: number | string; // Price in USD description: string; // Human-readable description mimeType?: string; // Response MIME type } ``` *** ## Route Protection ### Plugin Registration ```typescript theme={null} await app.register(prismPlugin, { apiKey: "your-api-key", routes: { "/api/premium": { price: 0.01, description: "Premium API", }, "/api/weather": { price: "$0.001", description: "Weather data", }, "/api/data/*": { price: 0.005, description: "Data API (wildcard)", }, }, }); ``` *** ## Accessing Payment Info Payment info is added to the request object: ```typescript theme={null} app.get("/api/premium", async (request, reply) => { // Access payer wallet address const payer = request.prismPayer; // '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb' // Access full payment object const payment = request.prismPayment; /* { scheme: 'eip3009', network: 'eth-sepolia', asset: 'usdc', amount: '10000', signature: '0x...' } */ return { message: "Premium content", payer, network: payment?.network, }; }); ``` *** ## Settlement Validation Fastify uses the **onSend hook** to validate settlement before sending data: ```typescript theme={null} // Internal implementation (automatic!) fastify.addHook('onSend', async (request, reply, payload) => { const settlementResult = await core.settlementCallback(...); if (!settlementResult || !settlementResult.success) { // ❌ Settlement failed - replace payload reply.code(402); reply.header('Content-Type', 'application/json'); return JSON.stringify({ error: 'Payment settlement failed', details: settlementResult?.errorReason }); } // ✅ Settlement succeeded - add header and return payload reply.header('X-PAYMENT-RESPONSE', settlementResult.transaction); return payload; }); ``` See [Settlement Validation](/concepts/settlement-validation) for details. *** ## TypeScript Support Extend Fastify types for payment properties: ```typescript theme={null} import "fastify"; declare module "fastify" { interface FastifyRequest { prismPayer?: string; prismPayment?: { scheme: string; network: string; asset: string; amount: string; signature: string; }; } } // Now TypeScript knows about payment properties app.get("/api/data", async (request, reply) => { const payer: string | undefined = request.prismPayer; // Type-safe! return { data: [1, 2, 3], payer }; }); ``` *** ## Testing ```typescript theme={null} import { test } from "tap"; import Fastify from "fastify"; import prismPlugin from "@1stdigital/prism-fastify"; test("payment required without header", async (t) => { const app = Fastify(); await app.register(prismPlugin, { apiKey: "test-key", routes: { "/api/premium": { price: 0.01, description: "Test" }, }, }); app.get("/api/premium", async () => ({ message: "Premium" })); const response = await app.inject({ method: "GET", url: "/api/premium", }); t.equal(response.statusCode, 402); t.ok(response.json().paymentRequired); }); test("access granted with valid payment", async (t) => { const app = Fastify(); await app.register(prismPlugin, { apiKey: "test-key", routes: { "/api/premium": { price: 0.01, description: "Test" }, }, }); app.get("/api/premium", async (request) => ({ message: "Premium", payer: request.prismPayer, })); const payment = JSON.stringify({ scheme: "eip3009", signature: "0x" + "0".repeat(130), }); const response = await app.inject({ method: "GET", url: "/api/premium", headers: { "X-PAYMENT": payment }, }); t.equal(response.statusCode, 200); t.equal(response.json().message, "Premium"); }); ``` *** ## Production Deployment ```typescript theme={null} import Fastify from "fastify"; import prismPlugin from "@1stdigital/prism-fastify"; const app = Fastify({ logger: { level: process.env.LOG_LEVEL || "info", }, }); await app.register(prismPlugin, { apiKey: process.env.PRISM_API_KEY!, baseUrl: process.env.PRISM_BASE_URL, debug: process.env.NODE_ENV !== "production", routes: { "/api/premium": { price: 0.01, description: "Premium API", }, }, }); // Start server await app.listen({ port: parseInt(process.env.PORT || "3000"), host: "0.0.0.0", }); ``` # HTTP (Node.js) Integration Source: https://developers.fd.xyz/prism/sdk/typescript/http Protect raw Node.js HTTP APIs with blockchain micropayments ## Overview The **@1stdigital/prism-http** package provides middleware for vanilla Node.js HTTP servers, without requiring any framework. Perfect for lightweight APIs or custom HTTP implementations. Works with raw http/https modules No Express, Fastify, or other framework needed Minimal overhead, maximum performance *** ## Installation ```bash theme={null} npm install @1stdigital/prism-http ``` ```bash theme={null} yarn add @1stdigital/prism-http ``` *** ## Quick Start ```javascript theme={null} const http = require("http"); const { prismPaymentMiddleware } = require("@1stdigital/prism-http"); // Create middleware const middleware = prismPaymentMiddleware( { apiKey: "dev-key-123", baseUrl: "https://prism-api.test.1stdigital.tech", }, { "/api/premium": { price: 0.01, // $0.01 USD description: "Premium API", }, }, ); // Create HTTP server const server = http.createServer((req, res) => { // Apply middleware middleware(req, res, () => { // This runs only if payment verified if (req.url === "/api/premium") { res.writeHead(200, { "Content-Type": "application/json" }); res.end( JSON.stringify({ message: "Premium content", payer: req.payer, // Wallet address }), ); } else { res.writeHead(404); res.end("Not Found"); } }); }); server.listen(3000); ``` *** ## Configuration ```typescript theme={null} interface PrismMiddlewareConfig { apiKey: string; baseUrl?: string; debug?: boolean; } interface RoutePaymentConfig { price: number | string; // USD price description: string; mimeType?: string; } ``` *** ## Routing ### URL-based Routing ```javascript theme={null} const server = http.createServer((req, res) => { middleware(req, res, () => { const url = new URL(req.url, `http://${req.headers.host}`); if (url.pathname === "/api/premium") { res.writeHead(200, { "Content-Type": "application/json" }); res.end( JSON.stringify({ message: "Premium", payer: req.payer, }), ); } else if (url.pathname === "/api/weather") { res.writeHead(200, { "Content-Type": "application/json" }); res.end( JSON.stringify({ location: "SF", temperature: 72, payer: req.payer, }), ); } else { res.writeHead(404); res.end("Not Found"); } }); }); ``` *** ## Accessing Payment Info ```javascript theme={null} const server = http.createServer((req, res) => { middleware(req, res, () => { // Access payer address const payer = req.payer; // '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb' // Access payment object (in res.locals) const payment = res.locals?.payment; res.writeHead(200, { "Content-Type": "application/json" }); res.end( JSON.stringify({ message: "Data", payer, network: payment?.network, }), ); }); }); ``` *** ## Settlement Validation HTTP middleware intercepts `res.end()` to validate settlement: ```javascript theme={null} // Internal implementation (automatic!) const originalEnd = res.end.bind(res); res.end = async function(chunk, encoding, callback) { const settlementResult = await core.settlementCallback(...); if (!settlementResult || !settlementResult.success) { // ❌ Settlement failed res.statusCode = 402; res.setHeader('Content-Type', 'application/json'); return originalEnd(JSON.stringify({ error: 'Payment settlement failed' })); } // ✅ Settlement succeeded res.setHeader('X-PAYMENT-RESPONSE', settlementResult.transaction); return originalEnd(chunk, encoding, callback); }; ``` *** ## Testing ```javascript theme={null} const http = require("http"); const { prismPaymentMiddleware } = require("@1stdigital/prism-http"); describe("HTTP Payment Middleware", () => { let server; beforeAll(() => { const middleware = prismPaymentMiddleware( { apiKey: "test-key" }, { "/api/premium": { price: 0.01, description: "Test" } }, ); server = http.createServer((req, res) => { middleware(req, res, () => { res.writeHead(200); res.end(JSON.stringify({ message: "OK", payer: req.payer })); }); }); server.listen(0); // Random port }); afterAll(() => server.close()); test("returns 402 without payment", async () => { const port = server.address().port; const response = await fetch(`http://localhost:${port}/api/premium`); expect(response.status).toBe(402); }); }); ``` # NestJS Integration Source: https://developers.fd.xyz/prism/sdk/typescript/nestjs Protect NestJS APIs with blockchain micropayments using decorators ## Overview The **@1stdigital/prism-nestjs** package provides a full-featured NestJS module with decorators, guards, and interceptors for payment-protecting your API routes. @Payment() and @Payer() decorators PrismModule.forRoot() configuration Works with NestJS DI system *** ## Installation ```bash theme={null} npm install @1stdigital/prism-nestjs @nestjs/core @nestjs/common ``` `bash yarn add @1stdigital/prism-nestjs @nestjs/core @nestjs/common ` ```bash theme={null} pnpm add @1stdigital/prism-nestjs @nestjs/core @nestjs/common ``` *** ## Quick Start ```typescript theme={null} // app.module.ts import { Module } from "@nestjs/common"; import { PrismModule } from "@1stdigital/prism-nestjs"; import { AppController } from "./app.controller"; @Module({ imports: [ PrismModule.forRoot({ apiKey: process.env.PRISM_API_KEY || "dev-key-123", baseUrl: "https://prism-api.test.1stdigital.tech", routes: { "/api/premium": { price: 0.01, // $0.01 USD description: "Premium API", }, }, }), ], controllers: [AppController], }) export class AppModule {} // app.controller.ts import { Controller, Get } from "@nestjs/common"; import { Payment, Payer } from "@1stdigital/prism-nestjs"; @Controller("api") export class AppController { @Get("premium") @Payment() // Requires payment! getPremiumData(@Payer() payer: string) { return { message: "Premium content", payer, }; } } ``` *** ## Configuration ### Module Options ```typescript theme={null} PrismModule.forRoot({ apiKey: "your-api-key", baseUrl: "https://prism-gateway.com", debug: true, routes: { "/api/premium": { price: 0.01, description: "Premium API", }, "/api/data/*": { price: 0.005, description: "Data API (wildcard)", }, }, }); ``` *** ## Decorators ### @Payment() Decorator ```typescript theme={null} import { Controller, Get } from "@nestjs/common"; import { Payment } from "@1stdigital/prism-nestjs"; @Controller("api") export class DataController { // Payment required @Get("premium") @Payment() getPremium() { return { data: "Premium content" }; } // No payment required @Get("free") getFree() { return { data: "Free content" }; } } ``` ### @Payer() Decorator ```typescript theme={null} import { Controller, Get } from "@nestjs/common"; import { Payment, Payer } from "@1stdigital/prism-nestjs"; @Controller("api") export class DataController { @Get("premium") @Payment() getPremium(@Payer() payer: string) { // payer = '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb' return { message: "Premium data", payer, }; } } ``` *** ## Accessing Payment Info ### Request Context ```typescript theme={null} import { Controller, Get, Req } from "@nestjs/common"; import { Payment } from "@1stdigital/prism-nestjs"; import { Request } from "express"; @Controller("api") export class DataController { @Get("premium") @Payment() getPremium(@Req() request: Request) { // Access via request object const payer = request.payer; const payment = request.locals?.payment; return { message: "Premium data", payer, network: payment?.network, }; } } ``` *** ## Settlement Validation NestJS uses an **Interceptor** to validate settlement: ```typescript theme={null} // Internal implementation (automatic!) @Injectable() export class PrismSettlementInterceptor implements NestInterceptor { async intercept(context: ExecutionContext, next: CallHandler) { return next.handle().pipe( mergeMap(async (data) => { const response = context.switchToHttp().getResponse(); const settlementResult = await this.settlementCallback(...); if (!settlementResult || !settlementResult.success) { // ❌ Settlement failed response.status(402); return { error: 'Payment settlement failed', details: settlementResult?.errorReason }; } // ✅ Settlement succeeded response.header('X-PAYMENT-RESPONSE', settlementResult.transaction); return data; }) ); } } ``` *** ## Testing ```typescript theme={null} import { Test, TestingModule } from "@nestjs/testing"; import { INestApplication } from "@nestjs/common"; import * as request from "supertest"; import { AppModule } from "./app.module"; describe("PrismModule (e2e)", () => { let app: INestApplication; beforeAll(async () => { const moduleFixture: TestingModule = await Test.createTestingModule({ imports: [AppModule], }).compile(); app = moduleFixture.createNestApplication(); await app.init(); }); afterAll(async () => { await app.close(); }); it("returns 402 without payment", () => { return request(app.getHttpServer()) .get("/api/premium") .expect(402) .expect((res) => { expect(res.body.paymentRequired).toBe(true); }); }); it("returns 200 with valid payment", () => { const payment = JSON.stringify({ scheme: "eip3009", signature: "0x" + "0".repeat(130), }); return request(app.getHttpServer()) .get("/api/premium") .set("X-PAYMENT", payment) .expect(200) .expect((res) => { expect(res.body.message).toBe("Premium content"); }); }); }); ``` *** ## Production Deployment ```typescript theme={null} // main.ts import { NestFactory } from "@nestjs/core"; import { AppModule } from "./app.module"; async function bootstrap() { const app = await NestFactory.create(AppModule, { logger: ["error", "warn", "log"], }); // Enable CORS if needed app.enableCors({ origin: process.env.ALLOWED_ORIGINS?.split(","), credentials: true, }); const port = parseInt(process.env.PORT || "3000"); await app.listen(port); console.log(`NestJS + Prism running on port ${port}`); } bootstrap(); ``` # Next.js Integration Source: https://developers.fd.xyz/prism/sdk/typescript/nextjs Protect Next.js API routes with blockchain micropayments ## Overview The **@1stdigital/prism-nextjs** package provides middleware for Next.js API routes and App Router route handlers. Supports both Pages Router and App Router architectures. Support for Next.js 13+ App Router Works with traditional API routes Compatible with Edge Runtime *** ## Installation ```bash theme={null} npm install @1stdigital/prism-nextjs next ``` `bash yarn add @1stdigital/prism-nextjs next ` ```bash theme={null} pnpm add @1stdigital/prism-nextjs next ``` *** ## Quick Start (App Router) ```typescript theme={null} // app/api/premium/route.ts import { NextRequest, NextResponse } from "next/server"; import { withPrismPayment } from "@1stdigital/prism-nextjs"; async function handler(request: NextRequest) { // Access payer from request const payer = (request as any).payer; return NextResponse.json({ message: "Premium content", payer, }); } // Wrap handler with payment middleware export const GET = withPrismPayment(handler, { apiKey: process.env.PRISM_API_KEY || "dev-key-123", baseUrl: "https://prism-api.test.1stdigital.tech", price: 0.01, // $0.01 USD description: "Premium API", }); ``` *** ## Quick Start (Pages Router) ```typescript theme={null} // pages/api/premium.ts import { NextApiRequest, NextApiResponse } from "next"; import { createPrismMiddleware } from "@1stdigital/prism-nextjs"; const prismMiddleware = createPrismMiddleware({ apiKey: process.env.PRISM_API_KEY || "dev-key-123", baseUrl: "https://prism-api.test.1stdigital.tech", }); export default async function handler( req: NextApiRequest, res: NextApiResponse, ) { // Apply middleware const paymentResult = await prismMiddleware(req, res, { price: 0.01, // $0.01 USD description: "Premium API", }); if (!paymentResult) return; // Middleware handled response // Payment verified res.status(200).json({ message: "Premium content", payer: (req as any).payer, }); } ``` *** ## Configuration ### App Router Options ```typescript theme={null} interface PrismRouteConfig { apiKey: string; baseUrl?: string; debug?: boolean; price: number | string; // USD price description: string; mimeType?: string; } ``` ### Pages Router Options ```typescript theme={null} interface PrismMiddlewareConfig { apiKey: string; baseUrl?: string; debug?: boolean; } interface RoutePaymentConfig { price: number | string; description: string; mimeType?: string; } ``` *** ## App Router Routes ### Single Route ```typescript theme={null} // app/api/premium/route.ts import { withPrismPayment } from "@1stdigital/prism-nextjs"; async function handler(request: NextRequest) { return NextResponse.json({ data: "Premium" }); } export const GET = withPrismPayment(handler, { apiKey: process.env.PRISM_API_KEY!, price: 0.01, description: "Premium API", }); ``` ### Multiple Methods ```typescript theme={null} // app/api/data/route.ts import { withPrismPayment } from "@1stdigital/prism-nextjs"; async function getHandler(request: NextRequest) { return NextResponse.json({ data: "GET data" }); } async function postHandler(request: NextRequest) { const body = await request.json(); return NextResponse.json({ received: body }); } export const GET = withPrismPayment(getHandler, { apiKey: process.env.PRISM_API_KEY!, price: 0.01, description: "Get data", }); export const POST = withPrismPayment(postHandler, { apiKey: process.env.PRISM_API_KEY!, price: 0.02, description: "Post data", }); ``` *** ## Accessing Payment Info ### App Router ```typescript theme={null} async function handler(request: NextRequest) { // Payer address attached to request const payer = (request as any).payer; // '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb' // Payment object in custom property const payment = (request as any).prismPayment; return NextResponse.json({ message: "Data", payer, network: payment?.network, }); } ``` ### Pages Router ```typescript theme={null} export default async function handler( req: NextApiRequest, res: NextApiResponse, ) { const paymentResult = await prismMiddleware(req, res, { price: 0.01, description: "Premium", }); if (!paymentResult) return; // Access payer const payer = (req as any).payer; const payment = (res as any).locals?.payment; res.json({ payer, network: payment?.network }); } ``` *** ## Settlement Validation Next.js wraps the Response object to validate settlement: ```typescript theme={null} // Internal implementation (automatic!) async function withPrismPayment(handler, config) { return async (request: NextRequest) => { // ... payment validation ... // Execute handler const response = await handler(request); // Validate settlement before returning const settlementResult = await core.settlementCallback(...); if (!settlementResult || !settlementResult.success) { // ❌ Settlement failed return NextResponse.json( { error: 'Payment settlement failed' }, { status: 402 } ); } // ✅ Settlement succeeded response.headers.set('X-PAYMENT-RESPONSE', settlementResult.transaction); return response; }; } ``` *** ## Testing ```typescript theme={null} import { NextRequest } from "next/server"; import { GET } from "./app/api/premium/route"; describe("Next.js Payment Routes", () => { it("returns 402 without payment", async () => { const request = new NextRequest("http://localhost:3000/api/premium"); const response = await GET(request); expect(response.status).toBe(402); const body = await response.json(); expect(body.paymentRequired).toBe(true); }); it("returns 200 with valid payment", async () => { const payment = JSON.stringify({ scheme: "eip3009", signature: "0x" + "0".repeat(130), }); const request = new NextRequest("http://localhost:3000/api/premium", { headers: { "X-PAYMENT": payment }, }); const response = await GET(request); expect(response.status).toBe(200); const body = await response.json(); expect(body.message).toBe("Premium content"); }); }); ``` *** ## Production Deployment ```typescript theme={null} // next.config.js module.exports = { env: { PRISM_API_KEY: process.env.PRISM_API_KEY, PRISM_BASE_URL: process.env.PRISM_BASE_URL }, // Enable edge runtime (optional) experimental: { runtime: 'edge' } }; // Vercel deployment // vercel.json { "env": { "PRISM_API_KEY": "@prism-api-key", "PRISM_BASE_URL": "@prism-base-url" } } ```