A dual-protocol (x402 + AP2) agentic payment service for Open Agent Skills Ecosystem (including OpenClaw, Claude Code, Codex, Junie, OpenCode, GitHub Copilot, Gemini CLI, etc.), with web3 & web2 gateway support, AWS KMS key management, policy engine compliance, audit trail, and human-in-the-loop confirmation.
- Overview
- Architecture
- Supported Protocols
- Payment Backends
- Security
- Policy Engine
- Audit Trail & Logging
- Database Schema
- Installation
- Configuration Reference (YAML)
- CLI Reference
- Web API Reference
- OpenClaw Chat Integration
- Usage Examples
- Development
- Troubleshooting
- License
agent-payments-skill is an Open Agent Skills Ecosystem compliant skill that enables AI agents to autonomously initiate, validate, and execute payments across both blockchain (web3) and traditional (web2) payment rails.
| Capability | Details |
|---|---|
| Dual protocol support | x402 (HTTP 402 + onchain settlement) and AP2 (Google's mandate-based agent payments) |
| Web3 transactions | Ethereum, Base, Polygon via Viem β native ETH and ERC-20 (USDC, etc.) |
| Web2 gateways | Stripe, PayPal, Visa Direct, Mastercard Send |
| Key management | AWS KMS encryption/decryption; encrypted at-rest storage in SQLite |
| Policy engine | Per-tx limits, daily/weekly/monthly aggregates, time-of-day, blacklist/whitelist, currency restrictions |
| Human-in-the-loop | Automatic escalation on policy violations via CLI prompt, chat prompt, or web API |
| Audit trail | Every action logged to SQLite audit_log table + Winston (stdout/stderr/file) |
| Three interfaces | OpenClaw (or other agent) chat, CLI (agent-payments), REST web API |
| Fully configurable | Single YAML file controls all behavior |
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Agent Payments Skill β
β βββββββββββββ βββββββββββββββββ ββββββββββββ β
β β Chat UI β β CLI (term) β β Web API β β
β βββββββ¬ββββββ βββββββββ¬ββββββββ ββββββ¬ββββββ β
β β β β β
β βΌ βΌ βΌ β
β βββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β Protocol Router β β
β β (AI output parser β PaymentIntent β routing) β β
β βββββββββββββ¬ββββββββββββββββββ¬ββββββββββββββββββββ β
β β β β
β ββββββββββΌβββββββ ββββββββΌβββββββββ β
β β x402 Client β β AP2 Client β β
β β (HTTP 402) β β (Mandates) β β
β ββββββββββ¬βββββββ ββββββββ¬βββββββββ β
β β β β
β βββββββββββββΌβββββββββββββββββΌββββββββββββββββ β
β β Policy Engine β β
β β (compliance checks before execution) β β
β β ββββββββββββββββββββββββββββββββββββββββ β β
β β β β’ Single tx limit β’ Blacklist β β β
β β β β’ Daily/Weekly/Mo β’ Whitelist β β β
β β β β’ Time-of-day β’ Currency β β β
β β ββββββββββββββββββββββββββββββββββββββββ β β
β β β (violation?) βββΊ Human Confirm β β
β βββββββββΌβββββββββββββββββββββββββββββββββββββ β
β β β
β βββββββββΌβββββββββββββββββββββββββββββββββββββββββββ β
β β Payment Execution β β
β β ββββββββββββ ββββββββββ ββββββββββ ββββββββ β β
β β β Viem β β Stripe β β PayPal β β Visa β β ββββββββββ β
β β β (ETH/ β β β β β β MC β β βAWS KMS β β
β β β ERC20) β β β β β β β β β(decryptβ β
β β ββββββββββββ ββββββββββ ββββββββββ ββββββββ βββββ keys) β β
β ββββββββββββββββββββ¬ββββββββββββββββββββββββββββββββ ββββββββββ β
β β β
β ββββββββββββββββββββΌββββββββββββββββββββββββββββββββ β
β β SQLite β β
β β ββββββββββββββββ ββββββββββββ ββββββββββββββ β β
β β βencrypted_keysβ β transac- β β audit_log β β β
β β β β β tions β β β β β
β β ββββββββββββββββ ββββββββββββ ββββββββββββββ β β
β ββββββββββββββββββββββββββββββββββββββββββββββββββββ β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
agent-payments-skill/
βββ SKILL.md # Open Agent Skills Ecosystem compliant skill definition (YAML frontmatter + markdown)
βββ package.json # npm package manifest
βββ tsconfig.json # TypeScript compiler config
βββ config/
β βββ default.yaml # Master YAML configuration
βββ src/
β βββ index.ts # Main entry point / orchestrator
β βββ cli.ts # CLI interface (Commander.js)
β βββ web-api.ts # REST API (Express)
β βββ config/
β β βββ loader.ts # YAML config loader + Zod validation
β βββ protocols/
β β βββ router.ts # Protocol router + AI output parser
β β βββ x402/
β β β βββ client.ts # x402 HTTP 402 client
β β βββ ap2/
β β βββ client.ts # AP2 mandate-based client
β βββ payments/
β β βββ web3/
β β β βββ ethereum.ts # Viem-based ETH/ERC-20 tx producer
β β βββ web2/
β β βββ gateways.ts # Stripe, PayPal, Visa, Mastercard
β βββ kms/
β β βββ aws-kms.ts # AWS KMS encrypt/decrypt
β βββ db/
β β βββ sqlite.ts # SQLite init + migrations
β β βββ key-store.ts # Encrypted key CRUD
β β βββ transactions.ts # Transaction records + aggregates
β β βββ audit.ts # Audit trail read/write
β βββ policy/
β β βββ engine.ts # Policy rule evaluator
β β βββ feedback.ts # Human confirmation (CLI/chat/API)
β βββ logging/
β βββ logger.ts # Winston multi-transport logger
βββ data/ # (created at runtime)
β βββ payments.db # SQLite database
βββ logs/ # (created at runtime)
βββ payment-skill.log # File log output
User/Agent input
β
βΌ
ββββββββββββββββββββ ββββββββββββββββββββββββ
β Parse AI Output ββββββΊβ Validate JSON Schema β
β (regex + JSON) β β (Zod PaymentIntent) β
ββββββββββββββββββββ ββββββββββββ¬ββββββββββββ
β
βΌ
ββββββββββββββββββββββββ
β Protocol Router β
β (x402 or AP2, web3 β
β or web2 detection) β
ββββββββββββ¬ββββββββββββ
β
βΌ
ββββββββββββββββββββββββ
β Create Transaction β
β Record (SQLite) β
ββββββββββββ¬ββββββββββββ
β
βΌ
βββββββββββββββββββββββββ
β Policy Engine ββββ rules from YAML
β β’ limits check ββββ aggregates from SQLite
β β’ blacklist/whitelist β
β β’ time restrictions β
ββββββββββββ¬βββββββββββββ
β
βββββββββ΄βββββββββ
β violations? β
βββββ¬βββββββββ¬ββββ
yes β β no
βΌ β
βββββββββββββββββ β
β Human Confirm β β
β (CLI/Chat/API)β β
βββββββββ¬ββββββββ β
reject β confirm β
βΌ β βββββββββ
REJECT β β
βΌ βΌ
ββββββββββββββββββββββββ
β Decrypt Keys (KMS) β
ββββββββββββ¬ββββββββββββ
β
ββββββββββββΌβββββββββββ
β Execute Payment β
β (Viem / Stripe / β
β PayPal / Visa / β
β Mastercard) β
ββββββββββββ¬βββββββββββ
β
ββββββββββββΌβββββββββββ
β Update Transaction β
β + Audit Log β
βββββββββββββββββββββββ
x402 is an open payment protocol built by Coinbase that revives the HTTP
402 Payment Required status code for internet-native stablecoin payments. It is stateless,
HTTP-native, and developer-friendly.
How the skill uses x402:
- Discovery β The client sends a
GETrequest to a resource URL. If402is returned, the response body (or headers) contains payment details:scheme,network,amount,payTo,asset, andmaxTimeoutSeconds. - Payment β The skill signs an EIP-3009
transferWithAuthorizationusing the wallet's private key (decrypted from KMS) via Viem. The signed payload is Base64-encoded and sent as theX-PAYMENTheader on a retriedGETrequest. - Settlement β The resource server (or its facilitator) verifies and settles the payment
onchain. A
200 OKresponse is returned with the resource and anX-PAYMENT-RESPONSEheader containing the settlement receipt (includingtxHash).
Supported networks: Ethereum Mainnet, Base, Polygon (configurable).
Supported assets: USDC (default), any ERC-20 with known contract addresses.
Reference: x402 GitHub Β· x402 Docs Β· ERC-8004 Spec
AP2 is Google's open protocol for AI agent-driven payments. It uses cryptographically signed Mandates β verifiable credentials that capture user intent and constraints β to enable agents to transact on behalf of humans.
AP2 Mandate Types:
| Mandate | Purpose |
|---|---|
| IntentMandate | Captures the user's initial intent (e.g., "buy running shoes under $100") with a max spend ceiling. Signed by the user. |
| CartMandate | Locks a specific cart of items and price. Created after the agent finds products. |
| PaymentMandate | Authorizes actual payment execution. Contains payment method reference and final amount. |
How the skill uses AP2:
- Create Mandate β From a
PaymentIntent, the skill constructs an AP2 mandate with intent details, amount constraints, validity window, and delegator info. - Sign Mandate β The mandate is sent to a credential provider for user signature (ECDSA-based verifiable credential).
- Obtain Credentials β Payment credentials are retrieved using the signed mandate and the desired payment method type.
- Submit Payment β The signed mandate + credentials are sent to the merchant's payment processor for execution.
Supported payment methods: Card (Visa/MC via gateway), PayPal, Stripe, Crypto (transparently routed through the appropriate web2/web3 backend).
Reference: AP2 Specification Β· Google Announcement
The protocol router (src/protocols/router.ts) is the entry point for all payment requests. It:
- Parses AI output β Extracts JSON
PaymentIntentfrom free-form AI text using multiple strategies:- Fenced JSON code blocks (
```json ... ```) - Generic code blocks (
``` ... ```) - Raw JSON containing a
"protocol"field - The entire text as JSON (for direct API input)
- Fenced JSON code blocks (
- Validates with Zod schema enforcement
- Routes to the correct protocol + payment backend based on:
- Explicit
gatewayfield (if provided) - Currency-based heuristic (crypto currencies β web3/viem, fiat β web2/stripe)
- Protocol hint (x402 β web3, AP2 β either)
- Explicit
The Viem-based transaction producer (src/payments/web3/ethereum.ts) supports:
| Operation | Function | Details |
|---|---|---|
| Send ETH | sendEth() |
Native ETH transfer on any supported EVM chain |
| Send ERC-20 | sendErc20() |
Token transfer (USDC, USDT, DAI, etc.) using transfer() ABI |
| Wait for confirmation | waitForConfirmation() |
Polls for on-chain receipt |
Supported chains (configurable via YAML):
| Chain | Chain ID | Default RPC |
|---|---|---|
| Ethereum Mainnet | 1 | https://mainnet.infura.io/v3/... |
| Base | 8453 | https://mainnet.base.org |
| Polygon | 137 | https://polygon-rpc.com |
Well-known USDC addresses are built-in per chain. Custom token addresses can be passed directly.
Uses the official Stripe Node.js SDK.
- Creates a
PaymentIntentviastripe.paymentIntents.create() - Amount converted to cents (integer)
- Metadata includes protocol, recipient, and any custom fields
- API key decrypted from AWS KMS at runtime
Uses PayPal's REST Checkout API v2.
- OAuth2 client credentials flow for access token
- Creates an Order via
POST /v2/checkout/orders - Returns approval URL for user authorization
- Client ID and secret decrypted from AWS KMS
Uses Visa Direct Push Funds Transfer API.
POST /visadirect/fundstransfer/v1/pushfundstransactions- Basic auth (user/password from KMS)
- Supports person-to-merchant and person-to-person transfers
Uses Mastercard Send Transfer API.
POST /send/v1/partners/transfers/payment- OAuth 1.0a authentication (consumer key + signing key from KMS)
- Supports credit and debit funding sources
All sensitive credentials (wallet private keys, API tokens, passwords) are encrypted using AWS KMS before storage and decrypted only at the moment of use.
Important: AWS access credentials are only loaded from environment variables. They are never stored in configuration files or the database.
| Operation | Description |
|---|---|
encryptAndStore() |
Encrypts plaintext via KMS.Encrypt, stores ciphertext blob in SQLite |
retrieveAndDecrypt() |
Reads ciphertext from SQLite, decrypts via KMS.Decrypt, returns plaintext |
Plaintext values are never logged or persisted. The audit log records the key alias and type, but never the decrypted value.
The encrypted_keys table stores AWS KMS-encrypted blobs:
| Column | Type | Description |
|---|---|---|
id |
TEXT PK | UUID |
key_type |
TEXT | web3_private_key, stripe_token, paypal_token, visa_token, mastercard_token |
key_alias |
TEXT UNIQUE | Human-readable name (e.g., default_wallet, stripe_api_key) |
ciphertext |
BLOB | AWS KMS encrypted payload |
kms_key_id |
TEXT | KMS key ARN used for encryption |
created_at |
TEXT | ISO 8601 timestamp |
updated_at |
TEXT | ISO 8601 timestamp |
| Variable | Required | Description |
|---|---|---|
AWS_ACCESS_KEY_ID |
β | AWS IAM access key for KMS |
AWS_SECRET_ACCESS_KEY |
β | AWS IAM secret key for KMS |
AWS_SESSION_TOKEN |
β | Optional, for temporary credentials / STS |
AWS_KMS_KEY_ID |
β | KMS key ARN or alias (e.g., alias/agent-payments) |
AWS_REGION |
β | Overrides kms.region in config (fallback: config value) |
CONFIG_PATH |
β | Override default config file path (for web API) |
β οΈ Never commit these values to source control. Use a secrets manager,.envfile with appropriate.gitignore, or container environment injection.
The policy engine (src/policy/engine.ts) acts as a compliance interceptor. It evaluates
every payment intent against a configurable rule set before any real transaction is executed.
| Rule | Config Key | Description |
|---|---|---|
| Single transaction limit | policy.rules.single_transaction.max_amount_usd |
Maximum USD equivalent for any one payment |
| Daily aggregate limit | policy.rules.daily.max_total_usd |
Max total USD in a rolling 24-hour window |
| Daily transaction count | policy.rules.daily.max_transaction_count |
Max number of transactions in 24 hours |
| Weekly aggregate limit | policy.rules.weekly.max_total_usd |
Max total USD in a rolling 7-day window |
| Weekly transaction count | policy.rules.weekly.max_transaction_count |
Max transactions in 7 days |
| Monthly aggregate limit | policy.rules.monthly.max_total_usd |
Max total USD in a rolling 30-day window |
| Monthly transaction count | policy.rules.monthly.max_transaction_count |
Max transactions in 30 days |
| Time-of-day restrictions | policy.rules.time_restrictions |
Restrict payments to specific UTC hours and days of week |
| Blacklist | policy.rules.blacklist |
Block payments to specific addresses/merchant IDs |
| Whitelist | policy.rules.whitelist |
Only allow payments to specific addresses (when enabled) |
| Currency restrictions | policy.rules.allowed_currencies |
Only allow payments in listed currencies |
Aggregate limits are computed against the transactions table in SQLite using rolling windows:
Daily window: NOW - 24 hours β NOW
Weekly window: NOW - 7 days β NOW
Monthly window: NOW - 30 days β NOW
The engine queries:
SELECT COALESCE(SUM(amount_usd), 0) as total_usd, COUNT(*) as count
FROM transactions
WHERE status IN ('executed', 'approved', 'pending', 'awaiting_confirmation')
AND created_at >= ? AND created_at < ?This ensures that even pending/awaiting-confirmation transactions count toward limits, preventing circumvention by rapid-fire requests.
When a policy violation with severity: "block" is detected and
require_human_confirmation_on_violation is true, the system pauses execution and requests
human confirmation:
| Channel | Behavior |
|---|---|
| CLI | Interactive terminal prompt with violation details. User types yes or no. |
| Chat | Returns a Markdown-formatted confirmation prompt. User replies confirm <txId> or reject <txId>. |
| Web API | Returns HTTP 202 with the tx.id. Client must POST /api/v1/confirm/:txId with {"confirmed": true}. |
Confirmation details logged include:
- Transaction ID
- All violated rules
- Who confirmed/rejected (CLI, chat, web_api)
- Optional rejection reason
- Timestamp
The skill implements a dual-write audit strategy: structured records in SQLite and multi-target log output via Winston.
Every significant action writes to the audit_log table:
| Column | Type | Description |
|---|---|---|
id |
INTEGER PK | Auto-incrementing |
timestamp |
TEXT | ISO 8601 UTC |
level |
TEXT | info Β· warn Β· error Β· critical |
category |
TEXT | payment Β· policy Β· kms Β· protocol Β· auth Β· system |
action |
TEXT | Specific action identifier (see table below) |
tx_id |
TEXT | Related transaction ID (nullable) |
actor |
TEXT | agent Β· human Β· system Β· cli Β· web_api |
details |
TEXT | JSON payload with full context |
ip_address |
TEXT | Requesting IP (web API only) |
user_agent |
TEXT | HTTP User-Agent (web API only) |
Tracked actions:
| Category | Action | Trigger |
|---|---|---|
system |
skill_bootstrapped |
On startup |
protocol |
intent_routed |
After protocol router decision |
protocol |
x402_payment_submitted |
After x402 payment sent |
protocol |
ap2_mandate_signed |
After AP2 mandate signature |
protocol |
ap2_payment_submitted |
After AP2 payment execution |
payment |
eth_transfer_sent |
After Viem ETH tx broadcast |
payment |
erc20_transfer_sent |
After Viem ERC-20 tx broadcast |
payment |
web3_payment_confirmed |
After on-chain confirmation |
payment |
stripe_intent_created |
After Stripe PaymentIntent |
payment |
paypal_order_created |
After PayPal Order creation |
payment |
visa_payment_submitted |
After Visa Direct push |
payment |
mastercard_payment_submitted |
After MC Send transfer |
payment |
payment_rejected_by_human |
On human rejection |
payment |
payment_execution_failed |
On any execution error |
policy |
violations_detected |
When rules are violated |
policy |
human_confirmed |
Human approved despite violation |
policy |
human_rejected |
Human rejected |
kms |
key_stored |
New key encrypted and stored |
kms |
key_encrypted_and_stored |
Via encryptAndStore() |
kms |
key_decrypted |
Key decrypted for use |
kms |
key_deleted |
Key removed from store |
Winston is configured with multiple transports (all configurable):
| Transport | Config Key | Default |
|---|---|---|
| Console (stdout) | logging.stdout |
true |
| Console (stderr for errors) | logging.stderr_errors |
true |
| File | logging.file.enabled |
true |
| File path | logging.file.path |
./logs/payment-skill.log |
| Max file size | logging.file.max_size_mb |
50 MB |
| Max file count (rotation) | logging.file.max_files |
10 |
Audit log entries are simultaneously written to both SQLite and Winston, ensuring coverage
even if one subsystem fails. Audit writes use try/catch internally and never crash the
payment flow.
Three tables are created during initialization (src/db/sqlite.ts):
CREATE TABLE encrypted_keys (
id TEXT PRIMARY KEY,
key_type TEXT NOT NULL,
key_alias TEXT NOT NULL UNIQUE,
ciphertext BLOB NOT NULL,
kms_key_id TEXT NOT NULL,
created_at TEXT NOT NULL DEFAULT (datetime('now')),
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
);CREATE TABLE transactions (
id TEXT PRIMARY KEY,
protocol TEXT NOT NULL, -- 'x402' | 'ap2'
gateway TEXT, -- 'viem' | 'stripe' | 'paypal' | 'visa' | 'mastercard'
action TEXT NOT NULL,
amount REAL NOT NULL,
amount_usd REAL NOT NULL,
currency TEXT NOT NULL,
recipient TEXT NOT NULL,
network TEXT,
status TEXT NOT NULL,
tx_hash TEXT,
error_message TEXT,
policy_violations TEXT, -- JSON array
confirmed_by TEXT, -- 'auto' | 'human'
metadata TEXT, -- JSON
created_at TEXT NOT NULL DEFAULT (datetime('now')),
executed_at TEXT,
completed_at TEXT
);
-- Indexes: idx_transactions_created, idx_transactions_status, idx_transactions_recipientTransaction statuses:
pending β policy_check β awaiting_confirmation β approved β executed
β rejected β failed
CREATE TABLE audit_log (
id INTEGER PRIMARY KEY AUTOINCREMENT,
timestamp TEXT NOT NULL DEFAULT (datetime('now')),
level TEXT NOT NULL,
category TEXT NOT NULL,
action TEXT NOT NULL,
tx_id TEXT,
actor TEXT,
details TEXT, -- JSON
ip_address TEXT,
user_agent TEXT
);
-- Indexes: idx_audit_timestamp, idx_audit_tx_id, idx_audit_category- Node.js β₯ 18.x (for native
fetch) - npm β₯ 9.x (or pnpm/yarn)
- AWS Account with KMS key configured
- SQLite (bundled via
better-sqlite3, no system dependency needed)
# 1. Clone / copy the skill directory
git clone https://github.com/sentient-agi/agent-payments-skill.git
cd agent-payments-skill
# 2. Install dependencies
npm install
# 3. Build TypeScript
npm run build
# 4. Set required environment variables
export AWS_ACCESS_KEY_ID="your-aws-access-key"
export AWS_SECRET_ACCESS_KEY="your-aws-secret-key"
export AWS_KMS_KEY_ID="arn:aws:kms:us-east-1:123456789012:key/your-key-id"
# 5. Customize configuration
cp config/default.yaml config/production.yaml
# Edit config/production.yaml with your RPC URLs, gateway settings, policy rules
# 6. Store encrypted keys (first-time setup)
npx agent-payments keys store --alias default_wallet --type web3_private_key --value "0xYOUR_PRIVATE_KEY"
npx agent-payments keys store --alias stripe_api_key --type stripe_token --value "sk_live_YOUR_STRIPE_KEY"
npx agent-payments keys store --alias paypal_client_id --type paypal_token --value "YOUR_PAYPAL_CLIENT_ID"
npx agent-payments keys store --alias paypal_secret --type paypal_token --value "YOUR_PAYPAL_SECRET"
# 7. Register open agents skill
npx skills add ./agent-payments-skillOnce installed, activate in your OpenClaw configuration:
{
"skills": {
"allow": ["agent-payments"]
}
}The agent will now be able to use the payment skill when it detects payment-related prompts.
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
# Agent Payments Skill β Master Configuration
# File: config/default.yaml
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
# ββ Skill Metadata βββββββββββββββββββββββββββββββββββββββββββββββββββ
skill:
name: agent-payments # Skill identifier (matches SKILL.md)
version: 0.2.0 # Skill version
# ββ SQLite Database ββββββββββββββββββββββββββββββββββββββββββββββββββ
database:
path: "./data/payments.db" # Path to SQLite database file
# (directory created automatically)
wal_mode: true # Enable WAL journal mode (recommended
# for concurrent reads during writes)
busy_timeout_ms: 5000 # SQLite busy timeout in milliseconds
# ββ Protocols ββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
protocols:
x402:
enabled: true # Enable/disable x402 protocol
facilitator_url: "https://x402.org/facilitator"
# Facilitator URL for settlement
# verification. Coinbase default:
# https://x402.org/facilitator
default_network: "base" # Default chain if not specified in intent
default_asset: "USDC" # Default token if not specified
timeout_ms: 30000 # HTTP request timeout for x402 ops
ap2:
enabled: true # Enable/disable AP2 protocol
mandate_issuer: "https://your-ap2-issuer.example.com"
# URL of your AP2 mandate issuer /
# merchant payment processor
credential_provider_url: "https://credentials.example.com"
# AP2 credential provider for mandate
# signing and payment credential retrieval
timeout_ms: 30000 # HTTP request timeout
# ββ Web3 Networks ββββββββββββββββββββββββββββββββββββββββββββββββββββ
# Each key is a network name used in PaymentIntent.network
web3:
ethereum:
enabled: true
rpc_url: "https://mainnet.infura.io/v3/YOUR_INFURA_PROJECT_ID"
chain_id: 1
base:
enabled: true
rpc_url: "https://mainnet.base.org"
chain_id: 8453
polygon:
enabled: true
rpc_url: "https://polygon-rpc.com"
chain_id: 137
# Add more EVM-compatible chains as needed:
# arbitrum:
# enabled: true
# rpc_url: "https://arb1.arbitrum.io/rpc"
# chain_id: 42161
# ββ Web2 Payment Gateways βββββββββββββββββββββββββββββββββββββββββββ
web2:
stripe:
enabled: true
api_version: "2025-01-27.acacia" # Stripe API version string
paypal:
enabled: true
environment: "sandbox" # "sandbox" or "live"
base_url: "https://api-m.sandbox.paypal.com"
# Live: https://api-m.paypal.com
visa:
enabled: true
base_url: "https://sandbox.api.visa.com"
# Live: https://api.visa.com
mastercard:
enabled: true
base_url: "https://sandbox.api.mastercard.com"
# Live: https://api.mastercard.com
# ββ AWS KMS ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
# NOTE: AWS credentials (AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY)
# must be set as environment variables. They are NEVER read from this file.
kms:
enabled: true # Enable AWS KMS integration
region: "us-east-1" # AWS region for KMS API calls
key_id_env: "AWS_KMS_KEY_ID" # Name of env var holding the KMS key ARN
# ββ Policy Engine ββββββββββββββββββββββββββββββββββββββββββββββββββββ
policy:
enabled: true # Master switch for policy enforcement
rules:
# Per-transaction limits
single_transaction:
max_amount_usd: 1000.00 # Max USD per single payment
# Rolling daily window (24 hours)
daily:
max_total_usd: 5000.00 # Max aggregate USD per day
max_transaction_count: 50 # Max number of transactions per day
# Rolling weekly window (7 days)
weekly:
max_total_usd: 25000.00
max_transaction_count: 200
# Rolling monthly window (30 days)
monthly:
max_total_usd: 80000.00
max_transaction_count: 500
# Time-of-day restrictions (evaluated in UTC)
time_restrictions:
enabled: false # Set to true to enforce
allowed_hours:
start: 8 # 08:00 UTC (inclusive)
end: 22 # 22:00 UTC (exclusive)
allowed_days: [1, 2, 3, 4, 5]
# JS weekday: 0=Sun, 1=Mon, ... 6=Sat
# Default: MonβFri only
# Blacklist β block payments to these addresses/IDs
blacklist:
enabled: true
addresses:
- "0x0000000000000000000000000000000000000000"
# Add more:
# - "0xDEADBEEF..."
# Whitelist β if enabled, ONLY these addresses are allowed
whitelist:
enabled: false # Usually disabled (restrictive)
addresses: []
# - "0xALLOWED_ADDRESS_1"
# - "merchant-id-1"
# Allowed currencies
allowed_currencies:
- "USDC"
- "ETH"
- "USD"
- "EUR"
# - "DAI"
# - "USDT"
# When violations occur, require human confirmation before proceeding
require_human_confirmation_on_violation: true
# ββ Logging & Audit βββββββββββββββββββββββββββββββββββββββββββββββββ
logging:
level: "info" # Minimum log level: debug|info|warn|error
stdout: true # Log to stdout (console)
stderr_errors: true # Route error/warn to stderr
file:
enabled: true # Log to file
path: "./logs/payment-skill.log"
max_size_mb: 50 # Max single file size before rotation
max_files: 10 # Keep N rotated files
audit:
sqlite: true # Write audit records to SQLite audit_log
verbose: true # Include full request/response payloads
# in audit details JSON
# ββ Web API Server ββββββββββββββββββββββββββββββββββββββββββββββββββ
web_api:
enabled: true
host: "0.0.0.0" # Bind address
port: 3402 # Listen port (3402 = "x402" π)
cors_origins:
- "http://localhost:*" # Allowed CORS origins (wildcard supported)
# - "https://your-app.example.com"
# ββ CLI Behavior ββββββββββββββββββββββββββββββββββββββββββββββββββββ
cli:
interactive_confirmation: true # Prompt for confirmation on violations
colored_output: true # ANSI color codes in terminal output| Section | Purpose | Key Settings |
|---|---|---|
skill |
Metadata | name, version β must match SKILL.md |
database |
SQLite | path, wal_mode, busy_timeout_ms |
protocols.x402 |
x402 client | facilitator_url, default_network, default_asset |
protocols.ap2 |
AP2 client | mandate_issuer, credential_provider_url |
web3.<network> |
EVM chains | rpc_url, chain_id, enabled |
web2.<gateway> |
Payment APIs | Gateway-specific URLs, API versions |
kms |
AWS KMS | region, key_id_env (credentials from env vars only) |
policy |
Compliance | All rule definitions + human confirmation toggle |
logging |
Observability | Multi-transport config, audit detail level |
web_api |
REST server | host, port, cors_origins |
cli |
Terminal UX | Confirmation mode, color output |
The CLI is available as agent-payments (via npm bin) or npx agent-payments.
| Flag | Description | Default |
|---|---|---|
-c, --config <path> |
Path to YAML config file | config/default.yaml |
-V, --version |
Print version | β |
-h, --help |
Show help | β |
agent-payments pay [options]| Option | Required | Description |
|---|---|---|
--protocol <x402|ap2> |
β | Protocol to use |
--amount <string> |
β | Decimal amount (e.g., "10.50") |
--currency <string> |
β | Currency code (USDC, ETH, USD, EUR) |
--to <string> |
β | Recipient address or merchant ID |
--network <string> |
β | Blockchain network (ethereum, base, polygon, web2) |
--gateway <string> |
β | Payment gateway (viem, stripe, paypal, visa, mastercard) |
--description <string> |
β | Human-readable description |
--wallet <string> |
β | Wallet key alias in encrypted store (default: default_wallet) |
Examples:
# x402 USDC payment on Base
agent-payments pay \
--protocol x402 \
--amount 5.00 \
--currency USDC \
--to 0x742d35Cc6635C0532925a3b844Bc9e7595f2bD65 \
--network base
# AP2 Stripe payment
agent-payments pay \
--protocol ap2 \
--amount 49.99 \
--currency USD \
--to merchant-12345 \
--gateway stripe \
--description "Monthly subscription"
# PayPal payment with custom config
agent-payments pay \
--config config/production.yaml \
--protocol ap2 \
--amount 25.00 \
--currency USD \
--to seller@example.com \
--gateway paypalExtract a PaymentIntent JSON from free-form AI text.
# Direct text
agent-payments parse '{"protocol":"x402","action":"pay","amount":"10","currency":"USDC","recipient":"0x..."}'
# From stdin (pipe AI model output)
echo '... AI response with embedded JSON ...' | agent-payments parse -Manage encrypted keys/tokens stored in SQLite via AWS KMS.
# Store a new encrypted key
agent-payments keys store \
--alias default_wallet \
--type web3_private_key \
--value "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80"
# List all stored keys (metadata only β no plaintext)
agent-payments keys list
# Delete a key
agent-payments keys delete stripe_api_keyKey types:
| Type | Alias Convention | Used By |
|---|---|---|
web3_private_key |
default_wallet |
Viem transaction signing |
stripe_token |
stripe_api_key |
Stripe SDK initialization |
paypal_token |
paypal_client_id, paypal_secret |
PayPal OAuth2 |
visa_token |
visa_user_id, visa_password |
Visa Direct auth |
mastercard_token |
mastercard_consumer_key, mastercard_signing_key |
MC Send auth |
agent-payments tx <transaction-id>Outputs the full transaction record as JSON, including status, policy violations, tx hash, and timestamps.
agent-payments audit [options]| Option | Description |
|---|---|
--category <string> |
Filter: payment, policy, kms, protocol, system |
--tx <string> |
Filter by transaction ID |
--since <ISO 8601> |
Filter by timestamp (e.g., 2026-02-10T00:00:00Z) |
--limit <number> |
Max results (default: 50) |
Examples:
# All payment audit entries from today
agent-payments audit --category payment --since 2026-02-10T00:00:00Z
# Audit trail for a specific transaction
agent-payments audit --tx "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
# Recent policy violations
agent-payments audit --category policy --limit 20http://<host>:<port>/api/v1
Default: http://0.0.0.0:3402/api/v1
Start the server:
# Via npm script
npm run web
# Or directly
node dist/web-api.js
# With custom config
CONFIG_PATH=config/production.yaml node dist/web-api.jsHealth check endpoint.
Response 200:
{
"status": "ok",
"skill": "agent-payments",
"version": "0.2.0"
}Execute a payment from a PaymentIntent.
Request body:
{
"protocol": "x402",
"action": "pay",
"amount": "10.00",
"currency": "USDC",
"recipient": "0x742d35Cc6635C0532925a3b844Bc9e7595f2bD65",
"network": "base",
"gateway": "viem",
"description": "API access payment",
"metadata": {},
"walletKeyAlias": "default_wallet"
}| Field | Type | Required | Description |
|---|---|---|---|
protocol |
"x402" | "ap2" |
β | Payment protocol |
action |
"pay" |
β | Action (only "pay" supported) |
amount |
string | β | Decimal amount |
currency |
string | β | Currency code |
recipient |
string | β | Destination address or merchant ID |
network |
string | β | Blockchain network name |
gateway |
string | β | Explicit gateway selection |
description |
string | β | Human-readable description |
metadata |
object | β | Arbitrary metadata |
walletKeyAlias |
string | β | Key alias (default: "default_wallet") |
Response 200 (success):
{
"success": true,
"tx": {
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"protocol": "x402",
"gateway": "viem",
"amount": 10.0,
"amount_usd": 10.0,
"currency": "USDC",
"recipient": "0x742d35Cc6635C0532925a3b844Bc9e7595f2bD65",
"network": "base",
"status": "executed",
"created_at": "2026-02-10T12:00:00.000Z"
},
"txHash": "0xabc123...",
"policyResult": {
"allowed": true,
"violations": [],
"requiresHumanConfirmation": false
},
"confirmationRequired": false
}Response 202 (confirmation required):
{
"success": false,
"tx": { "id": "...", "status": "awaiting_confirmation", "..." : "..." },
"policyResult": {
"allowed": true,
"violations": [
{
"rule": "single_transaction.max_amount_usd",
"message": "Amount $1500.00 exceeds single transaction limit of $1000.00",
"severity": "block"
}
],
"requiresHumanConfirmation": true
},
"confirmationRequired": true,
"confirmationPrompt": "Confirmation required for tx a1b2c3d4... POST /api/v1/confirm/a1b2c3d4..."
}Response 400 (validation/execution error):
{
"error": "Amount must be a decimal string"
}Parse a PaymentIntent from free-form AI output text.
Request body:
{
"text": "I've processed the request. Here's the payment:\n```json\n{\"protocol\":\"x402\",\"action\":\"pay\",\"amount\":\"5.00\",\"currency\":\"USDC\",\"recipient\":\"0x...\"}\n```"
}Response 200:
{
"found": true,
"intent": {
"protocol": "x402",
"action": "pay",
"amount": "5.00",
"currency": "USDC",
"recipient": "0x..."
}
}Response 200 (no intent found):
{
"found": false,
"intent": null
}Confirm or reject a pending transaction that requires human approval.
URL parameters:
txIdβ Transaction ID from the202response
Request body:
{
"confirmed": true,
"reason": "Approved by finance team"
}| Field | Type | Required | Description |
|---|---|---|---|
confirmed |
boolean | β | true to approve, false to reject |
reason |
string | β | Optional note (especially useful for rejections) |
Response 200:
{
"success": true,
"message": "Transaction a1b2c3d4... confirmed"
}Response 404:
{
"error": "No pending confirmation for tx a1b2c3d4..."
}List all transactions awaiting human confirmation.
Response 200:
[
{
"txId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"amount": 1500.0,
"currency": "USD",
"recipient": "merchant-12345",
"violations": [
{
"rule": "single_transaction.max_amount_usd",
"message": "Amount $1500.00 exceeds single transaction limit of $1000.00",
"severity": "block"
}
]
}
]Retrieve a transaction record by ID.
Response 200:
{
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"protocol": "x402",
"gateway": "viem",
"action": "pay",
"amount": 10.0,
"amount_usd": 10.0,
"currency": "USDC",
"recipient": "0x742d35Cc6635C0532925a3b844Bc9e7595f2bD65",
"network": "base",
"status": "executed",
"tx_hash": "0xabc123def456...",
"error_message": null,
"policy_violations": null,
"confirmed_by": "auto",
"metadata": "{}",
"created_at": "2026-02-10T12:00:00",
"executed_at": "2026-02-10T12:00:05",
"completed_at": null
}Response 404:
{
"error": "Transaction not found"
}Query the audit log with optional filters.
Query parameters:
| Param | Type | Description |
|---|---|---|
category |
string | Filter: payment, policy, kms, protocol, system |
tx_id |
string | Filter by transaction ID |
since |
string | ISO 8601 timestamp lower bound |
limit |
number | Max results (default: 100) |
Example:
GET /api/v1/audit?category=policy&since=2026-02-10T00:00:00Z&limit=20
Response 200:
[
{
"id": 42,
"timestamp": "2026-02-10T12:00:03",
"level": "warn",
"category": "policy",
"action": "violations_detected",
"tx_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"actor": "system",
"details": "{\"violations\":[{\"rule\":\"single_transaction.max_amount_usd\",\"message\":\"...\"}]}",
"ip_address": null,
"user_agent": null
}
]All endpoints return errors in a consistent format:
{
"error": "Human-readable error description"
}| HTTP Status | Meaning |
|---|---|
200 |
Success |
202 |
Accepted β payment pending human confirmation |
400 |
Bad request (validation error, execution failure) |
404 |
Resource not found (transaction, pending confirmation) |
500 |
Internal server error |
When the skill is activated in OpenClaw, the agent uses the SKILL.md instructions to output
structured payment intents during conversation.
The agent is instructed to output this exact JSON when a payment is needed:
{
"protocol": "x402 | ap2",
"action": "pay",
"amount": "<decimal string>",
"currency": "USDC | ETH | USD | EUR",
"recipient": "<address or merchant ID>",
"network": "ethereum | base | polygon | web2",
"gateway": "viem | visa | mastercard | paypal | stripe | null",
"description": "<human-readable description>",
"metadata": {}
}The protocol router extracts this JSON from the agent's message (even when surrounded by natural language) using regex-based extraction.
The SKILL.md instructs the agent:
| Signal | Routed To |
|---|---|
| Target is an HTTP resource returning 402 | x402 |
| User mentions "x402", "stablecoin", "USDC", "onchain" | x402 |
| Payment involves a mandate, delegated purchase | AP2 |
| Traditional card/gateway payment via agent | AP2 |
| Crypto currency (USDC, ETH, DAI) | web3 (Viem) |
| Fiat currency (USD, EUR) | web2 (Stripe default) |
When a policy violation is detected during a chat-initiated payment:
-
Skill returns a Markdown prompt to the agent, which presents it to the user:
β οΈ **Payment Requires Your Confirmation** | Field | Value | |-------|-------| | Transaction ID | `a1b2c3d4...` | | Protocol | x402 | | Amount | 1500 USDC ($1500.00 USD) | | Recipient | `0x742d...` | | Gateway | viem | **Policy Violations:** - **single_transaction.max_amount_usd**: Amount $1500.00 exceeds limit of $1000.00 Reply **"confirm a1b2c3d4"** to proceed or **"reject a1b2c3d4"** to cancel. -
User responds in the chat with
confirm <txId>orreject <txId> -
Skill parses the response and resumes/cancels the payment
Send 5 USDC on Base to a recipient:
export AWS_ACCESS_KEY_ID="AKIA..."
export AWS_SECRET_ACCESS_KEY="..."
export AWS_KMS_KEY_ID="arn:aws:kms:..."
agent-payments pay \
--protocol x402 \
--amount 5.00 \
--currency USDC \
--to 0x742d35Cc6635C0532925a3b844Bc9e7595f2bD65 \
--network base \
--wallet default_walletOutput:
2026-02-10T12:00:00.000Z [info] Transaction created { id: 'a1b2...', protocol: 'x402' }
2026-02-10T12:00:00.010Z [info] Policy check passed { amountUsd: 5 }
2026-02-10T12:00:00.020Z [info] web3: Preparing ERC-20 transfer { to: '0x742d...', amount: '5.00' }
2026-02-10T12:00:02.500Z [info] web3: ERC-20 transfer sent { txHash: '0xdef456...' }
2026-02-10T12:00:15.000Z [info] web3: Transaction confirmed { status: 'success', block: '12345678' }
βββββββββββββββββββββββββββββββββββββββ
β
Payment executed successfully!
TX Hash: 0xdef456...
Internal TX ID: a1b2c3d4-e5f6-7890-abcd-ef1234567890
βββββββββββββββββββββββββββββββββββββββ
# Start the web API
npm run web
# In another terminal, execute a payment
curl -X POST http://localhost:3402/api/v1/payment \
-H "Content-Type: application/json" \
-d '{
"protocol": "ap2",
"action": "pay",
"amount": "49.99",
"currency": "USD",
"recipient": "merchant-shop-12345",
"gateway": "stripe",
"description": "Premium subscription",
"metadata": {"plan": "annual"}
}'Response:
{
"success": true,
"tx": {
"id": "b2c3d4e5-f678-9012-bcde-f12345678901",
"protocol": "ap2",
"gateway": "stripe",
"amount": 49.99,
"amount_usd": 49.99,
"currency": "USD",
"status": "executed"
},
"web2Result": {
"gateway": "stripe",
"transaction_id": "pi_3Abc123...",
"status": "pending",
"amount": "49.99",
"currency": "USD"
},
"policyResult": {
"allowed": true,
"violations": [],
"requiresHumanConfirmation": false
},
"confirmationRequired": false
}User prompt in OpenClaw chat:
"Pay 10 USDC to 0x742d35Cc6635C0532925a3b844Bc9e7595f2bD65 on Base for API access"
The agent responds with embedded JSON (per SKILL.md instructions):
I'll process that payment for you:
{ "protocol": "x402", "action": "pay", "amount": "10.00", "currency": "USDC", "recipient": "0x742d35Cc6635C0532925a3b844Bc9e7595f2bD65", "network": "base", "gateway": "viem", "description": "API access payment" }
The skill's protocol router extracts this JSON, validates it, runs policy checks, and executes the payment.
Via Web API:
# 1. Submit a payment that exceeds the single-transaction limit
curl -X POST http://localhost:3402/api/v1/payment \
-H "Content-Type: application/json" \
-d '{
"protocol": "ap2",
"action": "pay",
"amount": "1500.00",
"currency": "USD",
"recipient": "vendor-99",
"gateway": "stripe"
}'
# Response: 202 with confirmationRequired: true
# {
# "confirmationRequired": true,
# "confirmationPrompt": "Confirmation required for tx abc123... POST /api/v1/confirm/abc123..."
# }
# 2. Check pending confirmations
curl http://localhost:3402/api/v1/pending
# 3. Approve the payment
curl -X POST http://localhost:3402/api/v1/confirm/abc123... \
-H "Content-Type: application/json" \
-d '{"confirmed": true, "reason": "One-time approved by CFO"}'# Store a wallet private key
agent-payments keys store \
--alias trading_wallet \
--type web3_private_key \
--value "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80"
# Store Stripe API key
agent-payments keys store \
--alias stripe_api_key \
--type stripe_token \
--value "sk_live_abcdefghijklmnop"
# List all keys (plaintext is NEVER shown)
agent-payments keys list
# ββββββββββββ¬ββββββββββββββββββββ¬ββββββββββββββββββ¬ββββββββββββββββββββββββββββββ¬ββββββββββββββββββββββ
# β id β key_type β key_alias β kms_key_id β created_at β
# ββββββββββββΌββββββββββββββββββββΌββββββββββββββββββΌββββββββββββββββββββββββββββββΌββββββββββββββββββββββ€
# β a1b2... β web3_private_key β trading_wallet β arn:aws:kms:us-east-1:... β 2026-02-10 12:00:00 β
# β c3d4... β stripe_token β stripe_api_key β arn:aws:kms:us-east-1:... β 2026-02-10 12:01:00 β
# ββββββββββββ΄ββββββββββββββββββββ΄ββββββββββββββββββ΄ββββββββββββββββββββββββββββββ΄ββββββββββββββββββββββ
# Delete a key
agent-payments keys delete trading_walletnpm run build # Compile TypeScript to dist/npm test # Run Jest test suitenpm run dev # ts-node src/index.ts| Package | Version | Purpose |
|---|---|---|
@coinbase/x402 |
^2.1.0 |
x402 facilitator SDK |
viem |
^2.28.0 |
Ethereum wallet, signing, tx building |
stripe |
^17.0.0 |
Stripe payment SDK |
@paypal/paypal-server-sdk |
^2.0.0 |
PayPal payments |
@aws-sdk/client-kms |
^3.750.0 |
AWS KMS encrypt/decrypt |
better-sqlite3 |
^11.8.0 |
SQLite driver (native, synchronous) |
yaml |
^2.7.0 |
YAML config parsing |
express |
^5.1.0 |
Web API server |
commander |
^13.1.0 |
CLI framework |
winston |
^3.17.0 |
Multi-transport logging |
zod |
^3.24.0 |
Schema validation |
uuid |
^11.1.0 |
UUID generation for IDs |
readline-sync |
^1.4.10 |
CLI interactive prompts |
| Symptom | Cause | Fix |
|---|---|---|
AWS KMS key ID not found in env var |
Missing AWS_KMS_KEY_ID environment variable |
Export AWS_KMS_KEY_ID before running |
Encrypted key not found for alias 'X' |
Key not stored yet | Run agent-payments keys store --alias X ... |
Network 'X' is disabled in configuration |
Chain disabled in YAML | Set web3.X.enabled: true in config |
x402 protocol is disabled in configuration |
Protocol toggle | Set protocols.x402.enabled: true |
Could not parse a valid payment intent |
AI output doesn't contain valid JSON | Ensure agent uses the exact JSON schema from SKILL.md |
SQLITE_BUSY errors |
Concurrent writes | Increase database.busy_timeout_ms or ensure WAL mode |
Policy violations detected (unexpected) |
Aggregate limits hit | Check agent-payments audit --category policy and adjust policy.rules |
| Web API not starting | Port conflict | Change web_api.port in config |
-
Set log level to
debuginconfig/default.yaml:logging: level: "debug"
-
Check the audit log for full context:
agent-payments audit --limit 20
-
Inspect a specific transaction:
agent-payments tx <transaction-id>
-
Query SQLite directly:
sqlite3 data/payments.db "SELECT * FROM transactions ORDER BY created_at DESC LIMIT 10;" sqlite3 data/payments.db "SELECT * FROM audit_log ORDER BY timestamp DESC LIMIT 20;"
This project is licensed under the Apache 2.0 License. See the LICENSE-APACHE file for the details.
Built with π€π΅ for the Open Agent Skills Ecosystem and for the OpenClaw ecosystem. Protocols: x402 Β· AP2
