Skip to content

Commit 72f5504

Browse files
committed
feat: add x402 payment tool example
Demonstrates how to build a payment-enabled tool for NeMo Agent Toolkit that handles HTTP 402 (Payment Required) responses using the x402 protocol (https://github.com/coinbase/x402). Includes: - PaidAPITool: LangChain tool with 402 detection and payment retry - SpendingPolicy: Per-transaction and daily caps, recipient allowlist - x402 helpers: Parse 402 responses, sign payment proofs - Mock paid API server for testing without real transactions - Configuration example for NeMo Agent Toolkit Security architecture: - Wallet key isolated from agent context window - Spending policy enforced before signing (not after) - Recipient allowlisting - Audit logging for all payments Related: NVIDIA/NeMo-Agent-Toolkit#1806
1 parent 27ac411 commit 72f5504

11 files changed

Lines changed: 1054 additions & 0 deletions

File tree

Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
<!--
2+
SPDX-FileCopyrightText: Copyright (c) 2025-2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
3+
SPDX-License-Identifier: Apache-2.0
4+
5+
Licensed under the Apache License, Version 2.0 (the "License");
6+
you may not use this file except in compliance with the License.
7+
You may obtain a copy of the License at
8+
9+
http://www.apache.org/licenses/LICENSE-2.0
10+
11+
Unless required by applicable law or agreed to in writing, software
12+
distributed under the License is distributed on an "AS IS" BASIS,
13+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
See the License for the specific language governing permissions and
15+
limitations under the License.
16+
-->
17+
18+
# x402 Payment Tool for NeMo Agent Toolkit
19+
20+
## Overview
21+
22+
### The Problem
23+
24+
AI agents increasingly need to interact with paid APIs and services autonomously. When an agent encounters an HTTP 402 (Payment Required) response, it needs to:
25+
26+
- Evaluate whether the data justifies the cost
27+
- Construct and authorize a payment
28+
- Retry the request with payment proof attached
29+
- Track spending against configurable budget limits
30+
31+
Unlike regular API errors, payment operations are **irreversible** — a bug doesn't just waste a retry, it transfers real value. This requires security guarantees beyond what standard tool integrations provide.
32+
33+
### The Solution
34+
35+
This example demonstrates a payment-enabled tool for NeMo Agent Toolkit that handles [x402](https://github.com/coinbase/x402) payment negotiation within an agent loop. The x402 protocol uses HTTP's previously unused 402 status code to enable machine-to-machine payments.
36+
37+
### How It Works
38+
39+
1. **Agent requests data** from a paid API via the payment tool
40+
2. **API responds with 402** including payment requirements (amount, recipient, token)
41+
3. **Tool evaluates the cost** against the agent's spending policy
42+
4. **Wallet signs the payment** (signing key isolated from the agent process)
43+
5. **Tool retries with payment proof** attached as an HTTP header
44+
6. **Agent receives the data** and continues its task
45+
46+
### Key Security Properties
47+
48+
| Property | Implementation | Why It Matters |
49+
|----------|---------------|----------------|
50+
| **Bounded blast radius** | On-chain spending policies (per-tx cap, daily limit) | A hallucinating LLM cannot drain the wallet |
51+
| **Key isolation** | Wallet signer runs in a separate process | MCP server compromise doesn't leak private keys |
52+
| **Allowlisted recipients** | Only pre-approved addresses can receive payments | Prevents payments to arbitrary addresses |
53+
| **Audit trail** | Every payment logged with tx hash, amount, recipient | Full accountability for autonomous spending |
54+
55+
## Architecture
56+
57+
```
58+
┌─────────────────────────┐
59+
│ NeMo Agent Toolkit │
60+
│ (ReAct Agent Loop) │
61+
│ │
62+
│ ┌───────────────────┐ │
63+
│ │ x402 Payment │ │ ┌──────────────┐
64+
│ │ Tool │──┼────▶│ Paid API │
65+
│ │ │ │ │ (returns 402)│
66+
│ │ - cost eval │ │ └──────────────┘
67+
│ │ - budget check │ │
68+
│ │ - retry w/ proof │ │
69+
│ └────────┬──────────┘ │
70+
│ │ │
71+
└───────────┼──────────────┘
72+
73+
┌────────▼────────┐
74+
│ Wallet Signer │
75+
│ (isolated │
76+
│ process) │
77+
│ │
78+
│ - private key │
79+
│ - spend policy │
80+
│ - tx signing │
81+
└─────────────────┘
82+
```
83+
84+
## Prerequisites
85+
86+
- Python >= 3.11
87+
- NeMo Agent Toolkit >= 1.4.0
88+
- An Ethereum wallet with USDC (Base network recommended for low fees)
89+
- Access to an x402-enabled API endpoint (or use the included mock server for testing)
90+
91+
## Quick Start
92+
93+
### 1. Install
94+
95+
```bash
96+
cd examples/x402_payment_tool
97+
pip install -e .
98+
```
99+
100+
### 2. Configure
101+
102+
```bash
103+
# Copy the example config
104+
cp src/nat_x402_payment/configs/payment-agent.example.yml src/nat_x402_payment/configs/payment-agent.yml
105+
106+
# Set your wallet private key (NEVER commit this)
107+
export WALLET_PRIVATE_KEY="your-private-key-here"
108+
109+
# Or use a separate signer process (recommended for production)
110+
export WALLET_SIGNER_URL="http://localhost:8900"
111+
```
112+
113+
### 3. Run with Mock Server (Testing)
114+
115+
```bash
116+
# Start the mock x402 server
117+
python scripts/mock_x402_server.py &
118+
119+
# Run the agent
120+
nat run --config src/nat_x402_payment/configs/payment-agent.yml
121+
```
122+
123+
### 4. Run with Real x402 API
124+
125+
```bash
126+
# Update payment-agent.yml with your x402 API endpoint
127+
nat run --config src/nat_x402_payment/configs/payment-agent.yml
128+
```
129+
130+
## Configuration
131+
132+
### Spending Policy
133+
134+
Edit `configs/payment-agent.yml` to set spending limits:
135+
136+
```yaml
137+
payment_tool:
138+
max_per_transaction: 0.10 # Max USDC per single payment
139+
max_daily_spend: 5.00 # Daily spending cap
140+
allowed_recipients: # Allowlisted payment recipients
141+
- "0x..." # Your x402 API provider
142+
require_confirmation: false # Set true for human-in-the-loop
143+
```
144+
145+
### Wallet Isolation Modes
146+
147+
| Mode | Security | Setup Complexity | Use Case |
148+
|------|----------|-----------------|----------|
149+
| **Inline** | Basic | Low | Development/testing |
150+
| **Separate process** | High | Medium | Production |
151+
| **Hardware signer** | Highest | High | Enterprise |
152+
153+
## Example Output
154+
155+
```
156+
Agent: I need to fetch premium market data for the user's query.
157+
Tool: Requesting https://api.example.com/v1/market-data?symbol=NVDA
158+
Tool: Received HTTP 402 — Payment Required
159+
Tool: Cost: 0.05 USDC | Budget remaining: 4.95 USDC | Policy: APPROVED
160+
Tool: Payment signed (tx: 0xabc...def) — retrying with proof
161+
Tool: Data received (200 OK)
162+
Agent: Based on the premium market data, NVDA is currently trading at...
163+
```
164+
165+
## References
166+
167+
- [x402 Protocol (Coinbase)](https://github.com/coinbase/x402) — HTTP 402 payment standard
168+
- [agent-wallet-sdk](https://github.com/up2itnow0822/agent-wallet-sdk) — Non-custodial agent wallets with spending policies
169+
- [agentpay-mcp](https://github.com/up2itnow0822/agentpay-mcp) — MCP payment server implementation
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
# SPDX-FileCopyrightText: Copyright (c) 2025-2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
2+
# SPDX-License-Identifier: Apache-2.0
3+
#
4+
# Licensed under the Apache License, Version 2.0 (the "License");
5+
# you may not use this file except in compliance with the License.
6+
# You may obtain a copy of the License at
7+
#
8+
# http://www.apache.org/licenses/LICENSE-2.0
9+
#
10+
# Unless required by applicable law or agreed to in writing, software
11+
# distributed under the License is distributed on an "AS IS" BASIS,
12+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
# See the License for the specific language governing permissions and
14+
# limitations under the License.
15+
16+
[build-system]
17+
build-backend = "setuptools.build_meta"
18+
requires = ["setuptools >= 64", "setuptools-scm>=8"]
19+
20+
[tool.setuptools_scm]
21+
git_describe_command = "git describe --long --first-parent"
22+
root = "../.."
23+
24+
[project]
25+
name = "nat_x402_payment"
26+
dynamic = ["version"]
27+
dependencies = [
28+
"nvidia-nat[langchain]>=1.4.0a0,<1.5.0",
29+
"httpx>=0.27.0",
30+
"eth-account>=0.13.0",
31+
"pyyaml>=6.0",
32+
]
33+
requires-python = ">=3.11,<3.14"
34+
description = "x402 payment-enabled tool for NeMo Agent Toolkit — handle HTTP 402 payment negotiation in agent loops"
35+
keywords = ["ai", "payments", "x402", "nvidia", "agents", "wallet"]
36+
classifiers = ["Programming Language :: Python"]
37+
38+
[project.entry-points.'nat.components']
39+
nat_x402_payment = "nat_x402_payment.register"
40+
41+
[tool.setuptools.packages.find]
42+
where = ["src"]
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
# SPDX-FileCopyrightText: Copyright (c) 2025-2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
2+
# SPDX-License-Identifier: Apache-2.0
3+
#
4+
# Licensed under the Apache License, Version 2.0 (the "License");
5+
# you may not use this file except in compliance with the License.
6+
# You may obtain a copy of the License at
7+
#
8+
# http://www.apache.org/licenses/LICENSE-2.0
9+
#
10+
# Unless required by applicable law or agreed to in writing, software
11+
# distributed under the License is distributed on an "AS IS" BASIS,
12+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
# See the License for the specific language governing permissions and
14+
# limitations under the License.
15+
16+
"""Mock paid API server for testing the x402 payment tool.
17+
18+
This server simulates an API that requires x402 payments:
19+
- GET /data → Returns 402 with payment requirements
20+
- GET /data + X-Payment header → Returns premium data (200)
21+
- GET /free → Returns free data (200, no payment needed)
22+
23+
Usage:
24+
python scripts/mock_paid_api.py
25+
# Server runs on http://localhost:8402
26+
"""
27+
28+
from __future__ import annotations
29+
30+
import json
31+
import time
32+
import uuid
33+
from http.server import BaseHTTPRequestHandler, HTTPServer
34+
35+
MOCK_RECIPIENT = "0x1234567890abcdef1234567890abcdef12345678"
36+
MOCK_TOKEN = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913" # USDC on Base
37+
PORT = 8402
38+
39+
40+
class PaidAPIHandler(BaseHTTPRequestHandler):
41+
"""HTTP handler that simulates x402 payment flows."""
42+
43+
def do_GET(self) -> None:
44+
if self.path == "/free":
45+
self._send_json(200, {"data": "This is free data. No payment required."})
46+
return
47+
48+
if self.path == "/data":
49+
# Check for payment proof
50+
payment = self.headers.get("X-Payment")
51+
if payment:
52+
# Payment received — return premium data
53+
self._send_json(
54+
200,
55+
{
56+
"data": {
57+
"title": "Premium Market Analysis",
58+
"summary": "BTC showing strong support at $84k with institutional "
59+
"accumulation. ETH/BTC ratio recovering. AI token sector "
60+
"outperforming market by 340% YTD.",
61+
"confidence": 0.87,
62+
"generated_at": time.strftime("%Y-%m-%dT%H:%M:%SZ"),
63+
},
64+
"payment_status": "accepted",
65+
},
66+
)
67+
else:
68+
# No payment — return 402 with x402 requirements
69+
self._send_json(
70+
402,
71+
{
72+
"payment": {
73+
"amount": 10000, # 0.01 USDC (6 decimals)
74+
"recipient": MOCK_RECIPIENT,
75+
"token": MOCK_TOKEN,
76+
"network": "base",
77+
"description": "Premium market analysis report",
78+
"nonce": uuid.uuid4().hex,
79+
"expires_at": int(time.time()) + 300,
80+
}
81+
},
82+
)
83+
return
84+
85+
self._send_json(404, {"error": "Not found"})
86+
87+
def _send_json(self, status: int, body: dict) -> None:
88+
self.send_response(status)
89+
self.send_header("Content-Type", "application/json")
90+
self.end_headers()
91+
self.wfile.write(json.dumps(body, indent=2).encode())
92+
93+
def log_message(self, format: str, *args: object) -> None:
94+
"""Override to add payment status to logs."""
95+
print(f"[MOCK API] {args[0]}")
96+
97+
98+
def main() -> None:
99+
server = HTTPServer(("localhost", PORT), PaidAPIHandler)
100+
print(f"Mock paid API running on http://localhost:{PORT}")
101+
print(f" GET /data → 402 (requires payment) or 200 (with payment)")
102+
print(f" GET /free → 200 (always free)")
103+
server.serve_forever()
104+
105+
106+
if __name__ == "__main__":
107+
main()
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
# SPDX-FileCopyrightText: Copyright (c) 2025-2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
2+
# SPDX-License-Identifier: Apache-2.0
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
# SPDX-FileCopyrightText: Copyright (c) 2025-2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
2+
# SPDX-License-Identifier: Apache-2.0
3+
4+
# x402 Payment Tool — Agent Configuration
5+
# Copy to payment-agent.yml and customize for your deployment
6+
7+
agent:
8+
type: react
9+
llm:
10+
model: meta/llama-3.3-70b-instruct # Or any NIM-compatible model
11+
temperature: 0.1
12+
tools:
13+
- fetch_paid_api # x402 payment-enabled fetch tool
14+
15+
# Payment tool configuration (also settable via environment variables)
16+
payment_tool:
17+
# Per-transaction limit in USDC
18+
max_per_transaction: 0.10
19+
20+
# Daily spending cap in USDC
21+
max_daily_spend: 5.00
22+
23+
# Allowlisted payment recipient addresses (empty = allow all)
24+
allowed_recipients: []
25+
26+
# Require human confirmation before each payment
27+
require_confirmation: false
28+
29+
# Wallet mode: "inline" (dev) or "remote" (production)
30+
# Inline: set WALLET_PRIVATE_KEY env var
31+
# Remote: set WALLET_SIGNER_URL env var
32+
wallet_mode: inline
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
# SPDX-FileCopyrightText: Copyright (c) 2025-2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
2+
# SPDX-License-Identifier: Apache-2.0
3+
4+
# Example configuration for an agent with x402 payment capabilities.
5+
# Copy this file to payment-agent.yml and customize.
6+
7+
agent:
8+
name: payment-enabled-agent
9+
description: An agent that can access paid APIs using x402 payments
10+
11+
# LLM configuration — uses NVIDIA NIM
12+
llm:
13+
model: meta/llama-3.3-70b-instruct
14+
api_base: https://integrate.api.nvidia.com/v1
15+
# Set NVIDIA_API_KEY environment variable
16+
17+
tools:
18+
- paid_api_request # Registered via nat.components entry point
19+
20+
# Payment configuration
21+
payment:
22+
# Spending limits — enforced BEFORE any payment is signed
23+
max_per_transaction_usd: 1.00
24+
max_daily_usd: 10.00
25+
26+
# Allowlisted recipient addresses (leave empty to allow all)
27+
allowed_recipients: []
28+
29+
# If true, agent asks user for confirmation before each payment
30+
require_confirmation: false
31+
32+
# Set to true for testing without real blockchain transactions
33+
mock: true
34+
35+
# Network: "base" for mainnet, "base-sepolia" for testnet
36+
network: base-sepolia

0 commit comments

Comments
 (0)