# 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"
}
}
```