Skip to content

sentient-agi/agentic-payments-bot

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

80 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

πŸ€–πŸ’΅ Agentic Payment Service for Open Agent Skills Ecosystem.

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.

TypeScript OpenClaw Skill License: Apache 2.0


Table of Contents


Overview

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.

Key Capabilities

Capability Details
Dual protocol support x402 (HTTP 402 + onchain settlement) and AP2 (Google's mandate-based agent payments)
Dual role: server + client Acts as a payment gateway (accepts payments from external agents via x402/AP2 server endpoints) and as a payment client (makes payments to external services via all backends)
Web3 transactions Ethereum, Base, Polygon via Viem β€” native ETH and ERC-20 (USDC, etc.)
Web2 gateways Stripe, PayPal, Visa Direct, Mastercard Send, Google Pay, Apple Pay
Protocol gateways x402 remote resource payment, AP2 remote mandate submission β€” paying any service that supports these protocols
Key management Pluggable KMS providers: AWS KMS, OS Keyring (KDE Wallet / GNOME Keyring / macOS Keychain / Windows Credential Manager), D-Bus Secret Service, GnuPG, Local AES-256-GCM
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

Architecture

System Diagram

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                           Agent Payments Skill                                    β”‚
β”‚                                                                                   β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ SERVER SIDE (Accept Payments) ───────────────────────────┐  β”‚
β”‚  β”‚                                                                             β”‚  β”‚
β”‚  β”‚  External Agents ──► x402 Paywall Middleware (HTTP 402 flow)                β”‚  β”‚
β”‚  β”‚                      AP2 Mandate Endpoints (mandate lifecycle)  ──► Payment β”‚  β”‚
β”‚  β”‚                                                                   Execution β”‚  β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β”‚
β”‚                                                                                   β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”   β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”   β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”                                 β”‚
β”‚  β”‚  Chat UI  β”‚   β”‚   CLI (term)  β”‚   β”‚ Web API  β”‚                                 β”‚
β”‚  β””β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”˜   β””β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”˜   β””β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”˜                                 β”‚
β”‚        β”‚                 β”‚                β”‚                                       β”‚
β”‚        β–Ό                 β–Ό                β–Ό                                       β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”                              β”‚
β”‚  β”‚              Protocol Router                    β”‚                              β”‚
β”‚  β”‚  (AI output parser β†’ PaymentIntent β†’ routing)   β”‚                              β”‚
β”‚  β””β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜                              β”‚
β”‚       β”‚          β”‚         β”‚          β”‚                                           β”‚
β”‚  β”Œβ”€β”€β”€β”€β–Όβ”€β”€β”€β” β”Œβ”€β”€β”€β–Όβ”€β”€β”€β”€β” β”Œβ”€β”€β”€β–Όβ”€β”€β”€β”€β” β”Œβ”€β”€β”€β–Όβ”€β”€β”€β”€β”                                      β”‚
β”‚  β”‚ web3   β”‚ β”‚ web2   β”‚ β”‚ x402   β”‚ β”‚ ap2    β”‚                                      β”‚
β”‚  β”‚(Viem)  β”‚ β”‚(Stripe β”‚ β”‚(remote β”‚ β”‚(remote β”‚                                      β”‚
β”‚  β”‚        β”‚ β”‚PayPal  β”‚ β”‚resourceβ”‚ β”‚mandate β”‚                                      β”‚
β”‚  β”‚        β”‚ β”‚Visa MC β”‚ β”‚client) β”‚ β”‚client) β”‚                                      β”‚
β”‚  β”‚        β”‚ β”‚GPay    β”‚ β”‚        β”‚ β”‚        β”‚                                      β”‚
β”‚  β”‚        β”‚ β”‚APay)   β”‚ β”‚        β”‚ β”‚        β”‚                                      β”‚
β”‚  β””β”€β”€β”€β”€β”¬β”€β”€β”€β”˜ β””β”€β”€β”€β”¬β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”¬β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”¬β”€β”€β”€β”€β”˜                                      β”‚
β”‚       β”‚         β”‚          β”‚          β”‚                                           β”‚
β”‚  β”Œβ”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”                                  β”‚
β”‚  β”‚            Policy Engine                    β”‚                                  β”‚
β”‚  β”‚  (compliance checks before execution)       β”‚                                  β”‚
β”‚  β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”‚                                  β”‚
β”‚  β”‚  β”‚ β€’ Single tx limit    β€’ Blacklist      β”‚  β”‚                                  β”‚
β”‚  β”‚  β”‚ β€’ Daily/Weekly/Mo    β€’ Whitelist      β”‚  β”‚                                  β”‚
β”‚  β”‚  β”‚ β€’ Time-of-day        β€’ Currency       β”‚  β”‚                                  β”‚
β”‚  β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β”‚                                  β”‚
β”‚  β”‚       β”‚ (violation?) ──► Human Confirm      β”‚                                  β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜                                  β”‚
β”‚          β”‚                                                                        β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”               β”‚
β”‚  β”‚          Payment Execution                  β”‚  β”‚  KMS Provider β”‚               β”‚
β”‚  β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”  β”‚  β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚               β”‚
β”‚  β”‚  β”‚ Viem   β”‚ β”‚ Stripe β”‚ β”‚ PayPal β”‚ β”‚ Visa β”‚  β”‚  β”‚ β”‚ AWS KMS   β”‚ β”‚               β”‚
β”‚  β”‚  β”‚(ETH/   β”‚ β”‚        β”‚ β”‚        β”‚ β”‚ MC   β”‚  β”‚  β”‚ β”‚ OS Keyringβ”‚ β”‚               β”‚
β”‚  β”‚  β”‚ ERC20) β”‚ β”‚        β”‚ β”‚        β”‚ β”‚ GPay β”‚  β”‚  β”‚ β”‚ D-Bus SS  β”‚ β”‚               β”‚
β”‚  β”‚  β”‚        β”‚ β”‚        β”‚ β”‚        β”‚ β”‚ APay β”‚  │◄─│ β”‚ GnuPG     β”‚ β”‚               β”‚
β”‚  β”‚  β”‚        β”‚ β”‚        β”‚ β”‚        β”‚ β”‚ x402 β”‚  β”‚  β”‚ β”‚ Local AES β”‚ β”‚               β”‚
β”‚  β”‚  β”‚        β”‚ β”‚        β”‚ β”‚        β”‚ β”‚ AP2  β”‚  β”‚  β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚               β”‚
β”‚  β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”˜  β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜               β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜                                  β”‚
β”‚                 β”‚                                                                 β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”                                  β”‚
β”‚  β”‚                  SQLite                     β”‚                                  β”‚
β”‚  β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚                                  β”‚
β”‚  β”‚  β”‚encrypted_keysβ”‚ β”‚transac-  β”‚ β”‚audit_log β”‚ β”‚                                  β”‚
β”‚  β”‚  β”‚              β”‚ β”‚tions     β”‚ β”‚          β”‚ β”‚                                  β”‚
β”‚  β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚                                  β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜                                  β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Directory Structure

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
β”œβ”€β”€ .env.example                      # Environment variable template
β”œβ”€β”€ .gitignore
β”œβ”€β”€ 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) β€” includes x402/AP2 server endpoints
β”‚   β”œβ”€β”€ config/
β”‚   β”‚   └── loader.ts                 # YAML config loader + Zod validation
β”‚   β”œβ”€β”€ protocols/
β”‚   β”‚   β”œβ”€β”€ router.ts                 # Protocol router + AI output parser
β”‚   β”‚   β”œβ”€β”€ x402/
β”‚   β”‚   β”‚   β”œβ”€β”€ client.ts             # x402 HTTP 402 client (paying for resources)
β”‚   β”‚   β”‚   └── server.ts             # x402 paywall middleware & settlement (accepting payments)
β”‚   β”‚   └── ap2/
β”‚   β”‚       β”œβ”€β”€ client.ts             # AP2 mandate-based client (submitting mandates)
β”‚   β”‚       └── server.ts             # AP2 mandate lifecycle server (processing mandates)
β”‚   β”œβ”€β”€ payments/
β”‚   β”‚   β”œβ”€β”€ web3/
β”‚   β”‚   β”‚   └── ethereum.ts           # Viem-based ETH/ERC-20 tx producer
β”‚   β”‚   └── web2/
β”‚   β”‚       └── gateways.ts           # Stripe, PayPal, Visa, MasterCard, Google Pay, Apple Pay
β”‚   β”œβ”€β”€ kms/
β”‚   β”‚   β”œβ”€β”€ provider.ts               # KmsProvider interface (shared contract)
β”‚   β”‚   β”œβ”€β”€ factory.ts                # Provider factory (selects backend from config)
β”‚   β”‚   β”œβ”€β”€ aws-kms.ts                # Public API: encryptAndStore / retrieveAndDecrypt
β”‚   β”‚   β”œβ”€β”€ aws-kms-provider.ts       # AWS KMS provider implementation
β”‚   β”‚   β”œβ”€β”€ os-keyring-provider.ts    # OS Keyring via @aspect-build/keytar
β”‚   β”‚   β”œβ”€β”€ dbus-secret-service-provider.ts  # Linux D-Bus Secret Service (dbus-next)
β”‚   β”‚   β”œβ”€β”€ gpg-provider.ts           # GnuPG encryption for headless Linux
β”‚   β”‚   └── local-aes-provider.ts     # Local AES-256-GCM (fallback / dry-run)
β”‚   β”œβ”€β”€ dry-run/
β”‚   β”‚   β”œβ”€β”€ crypto.ts                 # Local AES-256-GCM encryption (no KMS)
β”‚   β”‚   β”œβ”€β”€ stubs.ts                  # Gateway stub responses (success/failure/random)
β”‚   β”‚   └── wallet.ts                 # Viem key generation + local encrypt/store
β”‚   β”œβ”€β”€ 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

Data Flow

Client Side β€” Making Payments (Agent β†’ External Services)

User/Agent input
       β”‚
       β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”     β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Parse AI Output  │────►│ Validate JSON Schema β”‚
β”‚ (regex + JSON)   β”‚     β”‚ (Zod PaymentIntent)  β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜     β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                                    β”‚
                                    β–Ό
                         β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
                         β”‚   Protocol Router    β”‚
                         β”‚ (detect gateway:     β”‚
                         β”‚  web3/web2/x402/ap2) β”‚
                         β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                                    β”‚
                                    β–Ό
                         β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
                         β”‚ 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)  β”‚
                    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                              β”‚
              β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
              β”‚               β”‚                β”‚
     β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”
     β”‚ web3 (Viem)   β”‚ β”‚ web2        β”‚ β”‚ Protocol      β”‚
     β”‚ ETH / ERC-20  β”‚ β”‚ Stripe      β”‚ β”‚ Clients       β”‚
     β”‚               β”‚ β”‚ PayPal      β”‚ β”‚               β”‚
     β”‚ Direct chain  β”‚ β”‚ Visa / MC   β”‚ β”‚ x402: discoverβ”‚
     β”‚ transactions  β”‚ β”‚ GPay / APay β”‚ β”‚ β†’ sign β†’ pay  β”‚
     β”‚               β”‚ β”‚             β”‚ β”‚ β†’ get resourceβ”‚
     β”‚               β”‚ β”‚             β”‚ β”‚               β”‚
     β”‚               β”‚ β”‚             β”‚ β”‚ AP2: mandate  β”‚
     β”‚               β”‚ β”‚             β”‚ β”‚ β†’ sign β†’ cred β”‚
     β”‚               β”‚ β”‚             β”‚ β”‚ β†’ submit      β”‚
     β””β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”˜
              β”‚               β”‚                β”‚
              β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                              β”‚
                    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
                    β”‚  Update Transaction β”‚
                    β”‚  + Audit Log        β”‚
                    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Server Side β€” Accepting Payments (External Agents β†’ This Service)

                    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
                    β”‚       External Agent Request         β”‚
                    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                                β”‚          β”‚
               x402 path        β”‚          β”‚        AP2 path
         β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜          └──────────────────────┐
         β”‚                                                        β”‚
         β–Ό                                                        β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”                          β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ GET /x402/premium/data β”‚                          β”‚ POST /ap2/mandates       β”‚
β”‚ (any paywall route)    β”‚                          β”‚ (accept mandate)         β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜                          β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
            β”‚                                                    β”‚
     β”Œβ”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”                                             β–Ό
     β”‚ X-PAYMENT   β”‚                              β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
     β”‚ header?     β”‚                              β”‚ POST /ap2/sign-mandate   β”‚
     β””β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”˜                              β”‚ (credential provider)    β”‚
     no β”‚      β”‚ yes                              β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
        β–Ό      β”‚                                               β”‚
 β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”β”‚                                               β–Ό
 β”‚ HTTP 402   β”‚β”‚                              β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
 β”‚ + payment  β”‚β”‚                              β”‚ POST /ap2/payment-credentialsβ”‚
 β”‚ requirem.  β”‚β”‚                              β”‚ (issue scoped tokens)        β”‚
 β”‚ in X-PAY-  β”‚β”‚                              β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
 β”‚ MENT hdr   β”‚β”‚                                           β”‚
 β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜β”‚                                           β–Ό
               β–Ό                              β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”                   β”‚ POST /ap2/process-payment    β”‚
    β”‚ Validate payload:   β”‚                   β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
    β”‚ β€’ auth fields       β”‚                                β”‚
    β”‚ β€’ amount β‰₯ required β”‚                                β”‚
    β”‚ β€’ time bounds       β”‚                                β”‚
    β”‚ β€’ payTo matches     β”‚                                β”‚
    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜                                β”‚
               β”‚                                           β”‚
               β–Ό                                           β–Ό
    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”             β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
    β”‚ Submit to on-chain  β”‚             β”‚ Route to internal backend:   β”‚
    β”‚ facilitator for     β”‚             β”‚ β€’ stripe  β€’ paypal  β€’ card   β”‚
    β”‚ settlement          β”‚             β”‚ β€’ crypto (Viem ETH/ERC-20)   β”‚
    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜             β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
               β”‚                                       β”‚
               β–Ό                                       β–Ό
    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”             β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
    β”‚ HTTP 200            β”‚             β”‚ Return AP2PaymentResult:     β”‚
    β”‚ + resource data     β”‚             β”‚ { mandate_id, status,        β”‚
    β”‚ + X-PAYMENT-RESPONSEβ”‚             β”‚   transaction_id, receipt }  β”‚
    β”‚   (settlement proof)β”‚             β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜                            β”‚
               β”‚                                       β”‚
               β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                              β”‚
                   β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
                   β”‚  Audit Log (SQLite  β”‚
                   β”‚  + Winston)         β”‚
                   β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Quick Start

A quick start guide with Docker and Docker Compose.

Usage

Quick start (dry-run, no credentials needed):

# Build and start both services
DRY_RUN=true docker compose up --build -d

# Start everything (first run auto-configures OpenClaw + installs skill)
docker compose up -d

# Or explicitly run skill installation first
docker compose --profile setup run --rm install-skill
docker compose up -d

# Check the payment API health
curl http://localhost:3402/api/v1/health

# Run the demo via the CLI helper
DRY_RUN=true docker compose run --rm cli demo

# Run CLI commands
docker compose run --rm --profile cli cli demo --stub-mode success

# Quick dry-run test
DRY_RUN=true docker compose up -d

# Check logs
docker compose logs -f openclaw-payments

# View audit log
DRY_RUN=true docker compose run --rm cli audit --limit 30

# Store a key via CLI
DRY_RUN=true docker compose run --rm cli keys list

Production (with real credentials):

# Create a .env file with your secrets
cat > .env <<EOF
AWS_ACCESS_KEY_ID=AKIA...
AWS_SECRET_ACCESS_KEY=...
AWS_KMS_KEY_ID=arn:aws:kms:us-east-1:123456789012:key/...
LLM_PROVIDER=anthropic
LLM_API_KEY=sk-ant-...
OPENCLAW_GATEWAY_TOKEN=your-gateway-token
EOF

# Start
docker compose up --build -d

# Tail logs
docker compose logs -f openclaw-payments

# Stop
docker compose down

Pair OpenClaw with a messaging channel (e.g. Telegram):

# Run the OpenClaw CLI inside the running container
docker compose exec openclaw-payments openclaw pairing approve telegram

What this gives you

Container Service Port Description
openclaw-payments Payment Skill Web API 3402 REST API for payments, parsing, confirmations, audit
openclaw-payments OpenClaw Gateway 18789 Agent gateway (Telegram, Slack, WhatsApp, etc.)
openclaw-payments OpenClaw Bridge 18790 Internal bridge for multi-channel routing
openclaw-payments-cli CLI (on-demand) β€” Runs openclaw-payment CLI commands against the shared SQLite DB

Both services share the same SQLite database and encrypted key store through Docker volumes [1]. The payment skill is auto-registered as an OpenClaw skill via the symlink into ~/.openclaw/skills/ [2], so the agent discovers it at startup through the standard skill loading mechanism.


Supported Protocols

x402 Protocol

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.

The skill implements x402 in both directions β€” as a client (paying for resources) and as a server (accepting payments from external agents).

x402 Client (Paying for Resources)

When the skill needs to pay for an x402-protected resource (e.g., a premium API endpoint):

  1. Discovery β€” The client sends a GET request to a resource URL. If 402 is returned, the response body (or X-PAYMENT header) contains payment details: scheme, network, amount, payTo, asset, and maxTimeoutSeconds.
  2. Payment β€” The skill signs an EIP-3009 transferWithAuthorization using the wallet's private key (decrypted from KMS) via Viem. The signed payload is Base64-encoded and sent as the X-PAYMENT header on a retried GET request.
  3. Settlement β€” The resource server (or its facilitator) verifies and settles the payment onchain. A 200 OK response is returned with the resource and an X-PAYMENT-RESPONSE header containing the settlement receipt (including txHash).

Trigger: Set gateway: "x402" in the PaymentIntent, or use a URL as the recipient with protocol: "x402". The protocol router will automatically detect this and route to the x402 client.

x402 Server (Accepting Payments)

When the skill acts as a payment provider that accepts x402 payments from external agents:

  1. Paywall β€” Protected routes use the x402Paywall Express middleware. When an agent requests a resource without an X-PAYMENT header, the server responds with HTTP 402 and includes payment requirements in the X-PAYMENT response header (Base64-encoded JSON).
  2. Verification β€” When the agent retries with an X-PAYMENT request header containing a signed payment payload, the middleware validates the authorization fields (amount, recipient, time bounds) and submits the payload to the on-chain facilitator for settlement.
  3. Access β€” After successful settlement, the middleware attaches an X-PAYMENT-RESPONSE header with the settlement receipt and passes the request through to the actual resource handler.

Server endpoints:

Endpoint Method Description
/api/v1/x402/premium/data GET Example paywall-protected resource
/api/v1/x402/pricing GET List all registered x402-priced resources
/api/v1/x402/verify POST Verify a settlement transaction hash

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 Protocol (Agent Payments Protocol)

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.

The skill implements AP2 in both directions β€” as a client (submitting mandates to external services) and as a server (accepting and processing mandates from external agents).

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.

AP2 Client (Submitting Mandates)

When the skill needs to pay an external AP2-compliant service:

  1. Create Mandate β€” From a PaymentIntent, the skill constructs an AP2 mandate with intent details, amount constraints, validity window, and delegator info.
  2. Sign Mandate β€” The mandate is sent to a credential provider for user signature (ECDSA-based verifiable credential).
  3. Obtain Credentials β€” Payment credentials are retrieved using the signed mandate and the desired payment method type.
  4. Submit Payment β€” The signed mandate + credentials are sent to the merchant's payment processor for execution.

Trigger: Set gateway: "ap2" in the PaymentIntent, or use a URL as the recipient with protocol: "ap2". The protocol router will automatically detect this and route to the AP2 client.

AP2 Server (Processing Mandates)

When the skill acts as a payment provider that accepts AP2 mandates from external agents:

  1. Accept Mandate β€” External agents POST a mandate to /api/v1/ap2/mandates. The server validates the mandate structure, constraints, and expiry.
  2. Sign Mandate β€” Agents can request mandate signing via /api/v1/ap2/sign-mandate (credential provider role).
  3. Issue Credentials β€” Agents obtain tokenized payment credentials scoped to a mandate via /api/v1/ap2/payment-credentials.
  4. Execute Payment β€” Agents submit a mandate + payment method to /api/v1/ap2/process-payment. The server routes the payment to the appropriate internal backend (Stripe, PayPal, Viem, etc.) and returns the result.

Server endpoints:

Endpoint Method Description
/api/v1/ap2/mandates POST Accept a new mandate from an agent
/api/v1/ap2/mandates GET List all mandates
/api/v1/ap2/mandates/:id GET Get mandate status
/api/v1/ap2/sign-mandate POST Sign a mandate (credential provider)
/api/v1/ap2/payment-credentials POST Issue tokenized payment credentials
/api/v1/ap2/process-payment POST Execute mandate against internal payment backends

Supported payment methods: Card (Visa/MC via gateway), PayPal, Stripe, Crypto (transparently routed through the appropriate web2/web3 backend).

Reference: AP2 Specification Β· Google Announcement

Protocol Router

The protocol router (src/protocols/router.ts) is the entry point for all payment requests. It:

  1. Parses AI output β€” Extracts JSON PaymentIntent from 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)
  2. Validates with Zod schema enforcement
  3. Routes to the correct protocol + payment backend based on:
    • Explicit gateway field (if provided): viem, stripe, paypal, visa, mastercard, googlepay, applepay, x402, or ap2
    • URL detection (recipient starting with http:// or https://):
      • protocol: "x402" + URL recipient β†’ x402 remote resource payment
      • protocol: "ap2" + URL recipient β†’ AP2 remote mandate submission
    • Currency-based heuristic (crypto currencies β†’ web3/viem, fiat β†’ web2/stripe)
    • Protocol hint (x402 β†’ web3, AP2 β†’ either)

Routing matrix:

Gateway Value Payment Type Description
viem web3 Direct ETH/ERC-20 transfer via Viem
stripe web2 Stripe Payment Intents
paypal web2 PayPal Orders API
visa web2 Visa Direct Push Payments
mastercard web2 Mastercard Send API
googlepay web2 Google Pay token processing
applepay web2 Apple Pay token processing
x402 x402 Remote x402 resource payment (discover β†’ pay β†’ access)
ap2 ap2 Remote AP2 mandate submission (create β†’ sign β†’ pay)

Payment Backends

Web3 β€” Ethereum (Viem)

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.

Web2 β€” Stripe

Uses the official Stripe Node.js SDK.

  • Creates a PaymentIntent via stripe.paymentIntents.create()
  • Amount converted to cents (integer)
  • Metadata includes protocol, recipient, and any custom fields
  • API key decrypted from AWS KMS at runtime

Web2 β€” PayPal

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

Web2 β€” Visa Direct

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

Web2 β€” Mastercard Send

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

Web2 β€” Google Pay

Uses the Google Pay API for server-side payment token processing.

  • The client obtains a payment token via the Google Pay JS API and passes it to the skill in metadata.paymentToken
  • The server processes the token against the Google Pay payment gateway endpoint
  • Supports PAN_ONLY and CRYPTOGRAM_3DS authentication methods
  • Configurable card networks: Visa, Mastercard, Amex, Discover, JCB
  • Merchant ID and merchant key decrypted from AWS KMS at runtime
  • Environment configurable between TEST and PRODUCTION

Required metadata fields:

Field Required Description
paymentToken βœ… Encrypted payment token from the Google Pay JS API client
countryCode ❌ ISO 3166-1 alpha-2 country code (default: US)

Web2 β€” Apple Pay

Uses the Apple Pay API for server-side merchant validation and payment token processing.

  • The client obtains an encrypted payment token via the Apple Pay JS API and passes it to the skill in metadata.paymentToken
  • Optional server-to-server merchant session validation with Apple's servers (when metadata.validationURL is provided)
  • Token is forwarded to the payment processor for decryption and authorization
  • Configurable supported networks: Visa, Mastercard, Amex, Discover
  • Merchant capabilities: 3DS, credit, and debit support
  • Requires a verified domain registered with Apple
  • Merchant ID, certificate, key, and processor key decrypted from AWS KMS

Required metadata fields:

Field Required Description
paymentToken βœ… Encrypted payment token from the Apple Pay JS API client
validationURL ❌ Apple's merchant validation URL (for session validation step)

x402 β€” Remote Resource Payment

The x402 client (src/protocols/x402/client.ts) is integrated as a payment backend alongside Viem, Stripe, PayPal, etc. When the protocol router determines the payment should go through x402 (e.g., the recipient is a URL of an x402-protected resource), the orchestrator invokes the full x402 client flow:

  1. Discover β€” GET the resource URL. If 402 is returned, parse payment requirements from the X-PAYMENT header or response body.
  2. Sign β€” Build an EIP-3009 authorization payload and sign it with the wallet's private key.
  3. Pay β€” Re-GET the resource with the signed X-PAYMENT header.
  4. Receive β€” Obtain the resource data and settlement proof from X-PAYMENT-RESPONSE.

Trigger: Set gateway: "x402" in the PaymentIntent, or use a URL as the recipient with protocol: "x402".

Example intent:

{
  "protocol": "x402",
  "action": "pay",
  "amount": "1.00",
  "currency": "USDC",
  "recipient": "https://api.example.com/premium/data",
  "network": "base",
  "gateway": "x402"
}

In dry-run mode, the x402 client flow is fully stubbed β€” no real HTTP requests or on-chain transactions are made. The stub returns simulated settlement responses.

AP2 β€” Remote Mandate Payment

The AP2 client (src/protocols/ap2/client.ts) is integrated as a payment backend for paying any AP2-compliant external service. When the protocol router determines the payment should go through AP2 (e.g., the recipient is a URL of an AP2 payment processor), the orchestrator invokes the full AP2 client flow:

  1. Create Mandate β€” Build an AP2 mandate from the PaymentIntent with intent details, amount constraints, validity window, and delegator info.
  2. Sign Mandate β€” Send the mandate to the configured credential provider for user signature.
  3. Get Credentials β€” Obtain tokenized payment credentials for the signed mandate.
  4. Submit Payment β€” Send the signed mandate + credentials to the merchant's payment processor.

Trigger: Set gateway: "ap2" in the PaymentIntent, or use a URL as the recipient with protocol: "ap2".

Example intent:

{
  "protocol": "ap2",
  "action": "pay",
  "amount": "49.99",
  "currency": "USD",
  "recipient": "https://merchant.example.com/ap2/process-payment",
  "gateway": "ap2",
  "description": "Premium subscription",
  "metadata": {
    "payment_method_type": "stripe"
  }
}

In dry-run mode, the AP2 client flow is fully stubbed β€” no real HTTP requests are made. The stub returns simulated mandate and payment responses.


Security

The skill uses a pluggable KMS (Key Management System) provider architecture for all secret management. Every sensitive credential β€” web3 wallet private keys (Viem), API tokens (Stripe, PayPal, Visa, Mastercard, Google Pay, Apple Pay), and authentication secrets β€” flows through a single pair of functions:

Function Description
encryptAndStore(keyAlias, keyType, plaintext) Encrypt and persist a secret
retrieveAndDecrypt(keyAlias) Fetch and decrypt a secret for use

All payment consumers (ethereum.ts, gateways.ts, cli.ts) call these same two functions regardless of which KMS backend is active. Plaintext values are never logged or persisted.

KMS Provider System

Provider Overview

The kms.provider configuration field selects which backend handles secret encryption and storage:

Provider Config Value Description Platforms
AWS KMS aws-kms Cloud HSM-backed encryption via AWS Key Management Service. Ciphertext stored in SQLite. All (requires AWS credentials)
OS Keyring os-keyring OS-native keyring integration via @aspect-build/keytar. Secrets stored in the platform's native credential store. Linux (KDE Wallet / GNOME Keyring), macOS (Keychain), Windows (Credential Manager)
D-Bus Secret Service dbus-secret Linux-only pure JavaScript client for the freedesktop.org Secret Service API via dbus-next. No native compilation required. Linux (GNOME Keyring, KDE Wallet with Secret Service bridge)
GnuPG gpg Asymmetric encryption via gpg2 CLI. Ideal for headless Linux servers without a desktop session. Ciphertext stored in SQLite. Linux, macOS, Windows (gpg4win)
Local AES local-aes Local AES-256-GCM symmetric encryption. Key sourced from an environment variable (auto-generated if missing). Ciphertext stored in SQLite. All (zero external dependencies)

Provider Selection Logic

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                     KMS Provider Selection Logic                         β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚                                                                          β”‚
β”‚  config.kms.provider = ?                                                 β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”β”‚
β”‚  β”‚ "aws-kms"        β”‚ AwsKmsProvider                                    β”‚β”‚
β”‚  β”‚                  β”‚ β†’ AWS KMS encrypt/decrypt + SQLite ciphertext     β”‚β”‚
β”‚  β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”‚
β”‚  β”‚ "os-keyring"     β”‚ if Linux:                                         β”‚β”‚
β”‚  β”‚                  β”‚   linux_keyring_backend = "keytar"?               β”‚β”‚
β”‚  β”‚                  β”‚     β†’ OsKeyringProvider (@aspect-build/keytar)    β”‚β”‚
β”‚  β”‚                  β”‚   linux_keyring_backend = "dbus-next"?            β”‚β”‚
β”‚  β”‚                  β”‚     β†’ DbusSecretServiceProvider (dbus-next)       β”‚β”‚
β”‚  β”‚                  β”‚ if macOS / Windows:                               β”‚β”‚
β”‚  β”‚                  β”‚     β†’ OsKeyringProvider (@aspect-build/keytar)    β”‚β”‚
β”‚  β”‚                  β”‚ ⚠ auto-fallback β†’ LocalAesProvider if headless    β”‚β”‚
β”‚  β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”‚
β”‚  β”‚ "dbus-secret"    β”‚ DbusSecretServiceProvider (dbus-next, Linux only) β”‚β”‚
β”‚  β”‚                  β”‚ ⚠ auto-fallback β†’ LocalAesProvider if headless    β”‚β”‚
β”‚  β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”‚
β”‚  β”‚ "gpg"            β”‚ GpgProvider (gpg2 CLI)                            β”‚β”‚
β”‚  β”‚                  β”‚ β†’ GPG encrypt/decrypt + SQLite ciphertext         β”‚β”‚
β”‚  β”‚                  β”‚ Requires gpg_key_id in config                     β”‚β”‚
β”‚  β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”‚
β”‚  β”‚ "local-aes"      β”‚ LocalAesProvider                                  β”‚β”‚
β”‚  β”‚                  β”‚ β†’ AES-256-GCM + SQLite ciphertext                 β”‚β”‚
β”‚  β”‚                  β”‚ Key from env var (auto-generated if missing)      β”‚β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜β”‚
β”‚                                                                          β”‚
β”‚  dry_run.enabled = true?                                                 β”‚
β”‚  β†’ Always uses local AES (bypasses provider entirely)                    β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

AWS KMS Provider

Uses AWS KMS for HSM-backed encryption. Ciphertext blobs are stored in the encrypted_keys SQLite table.

Configuration:

kms:
  enabled: true
  provider: "aws-kms"
  region: "us-east-1"
  key_id_env: "AWS_KMS_KEY_ID"

Required environment variables:

  • AWS_ACCESS_KEY_ID
  • AWS_SECRET_ACCESS_KEY
  • AWS_KMS_KEY_ID (KMS key ARN or alias)
  • AWS_SESSION_TOKEN (optional, for temporary credentials)

Important: AWS credentials are only loaded from environment variables. They are never stored in configuration files or the database.

OS Keyring Provider

Integrates with the operating system's native credential store using @aspect-build/keytar (a maintained fork of Atom's keytar). Secrets are managed directly by the OS β€” no ciphertext is stored in SQLite.

Platform Backend Notes
Linux (KDE) KDE Wallet (KWallet) Accessed via D-Bus org.kde.KWallet or Secret Service bridge
Linux (GNOME) GNOME Keyring Accessed via D-Bus org.freedesktop.secrets
macOS Keychain (Security.framework) Transparent integration
Windows Credential Manager (DPAPI) Transparent integration

Configuration (Linux with keytar):

kms:
  enabled: true
  provider: "os-keyring"
  linux_keyring_backend: "keytar"   # native addon

Configuration (Linux with dbus-next, no native compilation):

kms:
  enabled: true
  provider: "os-keyring"
  linux_keyring_backend: "dbus-next"   # pure JS

Configuration (macOS / Windows):

kms:
  enabled: true
  provider: "os-keyring"
  # linux_keyring_backend is ignored on non-Linux

Note: @aspect-build/keytar is a native Node.js addon (C++ / N-API) and requires compilation or prebuilt binaries. If you want to avoid native compilation on Linux, use linux_keyring_backend: "dbus-next" or the dbus-secret provider directly.

KDE Wallet compatibility check:

Ensure the Secret Service API is available on your KDE system:

dbus-send --session --print-reply \
  --dest=org.freedesktop.secrets \
  /org/freedesktop/secrets org.freedesktop.DBus.Peer.Ping

If this fails, enable the KDE Wallet Secret Service integration in System Settings β†’ KDE Wallet β†’ Secret Service integration.

D-Bus Secret Service Provider

A pure JavaScript (zero native compilation) provider that communicates directly with the freedesktop.org Secret Service API over D-Bus using dbus-next.

Works with:

  • GNOME Keyring (native Secret Service provider)
  • KDE Wallet (via its Secret Service bridge β€” kwalletd5/kwalletd6)

Configuration:

kms:
  enabled: true
  provider: "dbus-secret"

This provider can also be selected indirectly:

kms:
  enabled: true
  provider: "os-keyring"
  linux_keyring_backend: "dbus-next"   # routes to D-Bus Secret Service

Linux-only. This provider is not available on macOS or Windows.

GnuPG Provider

Uses GnuPG (gpg2) for asymmetric encryption. Ideal for headless Linux servers and CI/CD environments that have no desktop session, no D-Bus, and no AWS credentials β€” but do have a GPG keypair.

Secrets are encrypted with the public key and stored as ASCII-armored ciphertext in the encrypted_keys SQLite table. Decryption uses the corresponding private key from the local GPG keyring. If the private key has a passphrase, gpg-agent handles the prompt.

Configuration:

kms:
  enabled: true
  provider: "gpg"
  gpg_key_id: "agent-payments@yourcompany.com"   # fingerprint or email
  gpg_binary: "gpg2"                             # path to gpg binary

Setup β€” generate a dedicated GPG keypair:

# Generate a key (non-interactive)
gpg2 --batch --gen-key <<EOF
Key-Type: RSA
Key-Length: 4096
Subkey-Type: RSA
Subkey-Length: 4096
Name-Real: Agent Payments
Name-Email: agent-payments@yourcompany.com
Expire-Date: 2y
%no-protection
%commit
EOF

# Verify it was created
gpg2 --list-keys agent-payments@yourcompany.com

For Docker / CI: Import the key at container startup:

echo "$GPG_PRIVATE_KEY_BASE64" | base64 -d | gpg2 --batch --import

Local AES Provider

Symmetric AES-256-GCM encryption using a 256-bit key from an environment variable. Ciphertext stored in SQLite. This is the simplest provider β€” zero external dependencies, works everywhere.

Configuration:

kms:
  enabled: true
  provider: "local-aes"

The encryption key is read from the DRYRUN_ENCRYPTION_KEY environment variable (64 hex characters = 256 bits). If the variable is missing on first run, a random key is auto-generated and appended to the .env file.

⚠️ Guard the .env file carefully. If you lose the encryption key, previously encrypted entries become unreadable. The .env file is in .gitignore by default.

Example Configurations

Desktop Linux (KDE) with keytar:
kms:
  enabled: true
  provider: "os-keyring"
  linux_keyring_backend: "keytar"
Desktop Linux (GNOME) with dbus-next (no native compilation):
kms:
  enabled: true
  provider: "os-keyring"
  linux_keyring_backend: "dbus-next"
Headless Linux server (GPG):
kms:
  enabled: true
  provider: "gpg"
  gpg_key_id: "agent-payments@yourcompany.com"
  gpg_binary: "gpg2"
Headless Linux / Docker (local AES):
kms:
  enabled: true
  provider: "local-aes"
macOS / Windows desktop:
kms:
  enabled: true
  provider: "os-keyring"
Production cloud:
kms:
  enabled: true
  provider: "aws-kms"
  region: "us-east-1"
  key_id_env: "AWS_KMS_KEY_ID"

Provider Comparison Matrix

AWS KMS OS Keyring D-Bus Secret Service GnuPG Local AES
Encryption AES-256 (HSM-backed) OS-managed (DPAPI / Keychain / kernel) Same as OS Keyring RSA/ECC asymmetric AES-256-GCM
At-rest storage SQLite (ciphertext) OS keyring DB OS keyring DB SQLite (ciphertext) SQLite (ciphertext)
Key custody AWS (cloud HSM) OS user session OS user session Local GPG keyring Env var / .env file
Headless server βœ… ❌ (needs D-Bus session) ❌ (needs D-Bus session) βœ… βœ…
Native addon required No Yes (keytar) No (pure JS) No (CLI) No
Linux (KDE) βœ… βœ… (KWallet) βœ… (KWallet bridge) βœ… βœ…
Linux (GNOME) βœ… βœ… (gnome-keyring) βœ… (gnome-keyring) βœ… βœ…
macOS βœ… βœ… (Keychain) ❌ βœ… βœ…
Windows βœ… βœ… (Credential Manager) ❌ βœ… (gpg4win) βœ…
Docker / CI βœ… ❌ ❌ βœ… (import keyring) βœ…
Web3 private key βœ… (66 bytes) βœ… βœ… βœ… βœ…
API tokens βœ… βœ… βœ… βœ… βœ…

Security Comparison

AWS KMS OS Keyring D-Bus Secret Service GPG Local AES
Encryption AES-256 (HSM-backed) OS-managed (DPAPI/Keychain/kernel) Same as OS Keyring RSA/ECC asymmetric AES-256-GCM
At-rest storage SQLite (ciphertext) OS keyring DB OS keyring DB SQLite (ciphertext) SQLite (ciphertext)
Key custody AWS (cloud HSM) OS user session OS user session Local GPG keyring Env var / .env file
Headless server βœ… ❌ (needs D-Bus) ❌ (needs D-Bus) βœ… βœ…
Native addon No Yes (keytar) No (pure JS) No (CLI) No
Linux KDE βœ… βœ… (KWallet) βœ… (KWallet bridge) βœ… βœ…
Linux GNOME βœ… βœ… (gnome-keyring) βœ… (gnome-keyring) βœ… βœ…
macOS βœ… βœ… (Keychain) ❌ βœ… βœ…
Windows βœ… βœ… (Credential Mgr) ❌ βœ… (gpg4win) βœ…
Docker/CI βœ… ❌ ❌ βœ… (if keyring imported) βœ…

Headless Fallback Behavior

The OS Keyring and D-Bus Secret Service providers are designed for desktop environments with an active user session. When running on a headless server (no X11 / Wayland, no D-Bus session bus), these providers will automatically fall back to the Local AES provider.

The fallback is logged and audited:

[warn] OS keyring unavailable (headless or missing D-Bus session).
       Falling back to local-aes provider.

This means you can safely set provider: "os-keyring" in your default config and deploy to both desktop and server environments β€” the skill will adapt automatically.

Recommended per-environment configuration:

Environment Recommended Provider
Developer desktop (Linux KDE/GNOME) os-keyring (with linux_keyring_backend: "keytar" or "dbus-next")
Developer desktop (macOS) os-keyring
Developer desktop (Windows) os-keyring
Production cloud (AWS) aws-kms
Headless Linux server (with GPG) gpg
Headless Linux / Docker (simple) local-aes
CI/CD pipeline local-aes or aws-kms

Encrypted Key Storage (SQLite)

For providers that store ciphertext (AWS KMS, GnuPG, Local AES), the encrypted_keys table holds the encrypted blobs:

Column Type Description
id TEXT PK UUID
key_type TEXT web3_private_key, stripe_token, paypal_token, visa_token, mastercard_token, googlepay_token, applepay_token
key_alias TEXT UNIQUE Human-readable name (e.g., default_wallet, stripe_api_key)
ciphertext BLOB Encrypted payload
kms_key_id TEXT Provider identifier: KMS key ARN, gpg:<key_id>, local-aes256, or dryrun-local-aes256
created_at TEXT ISO 8601 timestamp
updated_at TEXT ISO 8601 timestamp

Note: The OS Keyring and D-Bus Secret Service providers store secrets directly in the OS credential store and do not use the encrypted_keys SQLite table.

Environment Variables

Variable Required Provider Description
AWS_ACCESS_KEY_ID βœ… (for aws-kms) aws-kms AWS IAM access key for KMS
AWS_SECRET_ACCESS_KEY βœ… (for aws-kms) aws-kms AWS IAM secret key for KMS
AWS_SESSION_TOKEN ❌ aws-kms Optional, for temporary credentials / STS
AWS_KMS_KEY_ID βœ… (for aws-kms) aws-kms KMS key ARN or alias (e.g., alias/agent-payments)
AWS_REGION ❌ aws-kms Overrides kms.region in config
DRYRUN_ENCRYPTION_KEY ❌ local-aes 256-bit hex key (64 chars). Auto-generated if missing.
CONFIG_PATH ❌ All Override default config file path (for web API)

⚠️ Never commit secret values to source control. Use a secrets manager, .env file with appropriate .gitignore, or container environment injection.


Policy Engine

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 Types

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

Time-Based Aggregate Tracking

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.

Human Confirmation Feedback Loop

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

Audit Trail & Logging

The skill implements a dual-write audit strategy: structured records in SQLite and multi-target log output via Winston.

SQLite Audit Log

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 client payment sent
protocol ap2_mandate_signed After AP2 client mandate signature
protocol ap2_payment_submitted After AP2 client payment execution
x402_server payment_required x402 server returned 402 to an agent
x402_server payment_settled x402 server settled a payment from an agent
x402_server settlement_failed x402 server settlement failed
ap2_server mandate_accepted AP2 server accepted a mandate from an agent
ap2_server mandate_signed AP2 server signed a mandate
ap2_server credentials_issued AP2 server issued payment credentials
ap2_server payment_processed AP2 server executed a mandate payment
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 googlepay_payment_processed After Google Pay token processed
payment applepay_payment_processed After Apple Pay token processed
payment x402_remote_payment_completed After x402 client resource access
payment ap2_remote_payment_completed After AP2 client mandate submission
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 Logger (stdout/stderr/file)

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.


Database Schema

Three tables are created during initialization (src/db/sqlite.ts):

encrypted_keys

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'))
);

transactions

CREATE TABLE transactions (
    id                TEXT PRIMARY KEY,
    protocol          TEXT NOT NULL,        -- 'x402' | 'ap2'
    gateway           TEXT,                 -- 'viem' | 'stripe' | 'paypal' | 'visa' | 'mastercard' | 'googlepay' | 'applepay'
    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_recipient

Transaction statuses:

pending β†’ policy_check β†’ awaiting_confirmation β†’ approved β†’ executed
                       β†˜ rejected                       β†˜ failed

audit_log

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

Installation

Prerequisites

  • Node.js β‰₯ 18.x (for native fetch)
  • npm β‰₯ 9.x (or pnpm/yarn)
  • SQLite (bundled via better-sqlite3, no system dependency needed)
  • KMS backend (one of the following):
    • AWS Account with KMS key configured (for aws-kms provider)
    • Desktop session with KDE Wallet / GNOME Keyring / macOS Keychain / Windows Credential Manager (for os-keyring provider)
    • GnuPG keypair (for gpg provider on headless Linux)
    • Nothing (for local-aes provider β€” zero-dependency fallback)

Steps

# 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. (Optional) Install OS keyring support
#    @aspect-build/keytar β€” native addon for OS keyring (Linux/macOS/Windows)
npm install @aspect-build/keytar --save-optional
#    dbus-next β€” pure JS D-Bus client for Linux Secret Service API
npm install dbus-next --save-optional

# 4. Build TypeScript
npm run build

# 5. Configure KMS provider in config/default.yaml (or config/production.yaml)
#    See "KMS Provider System" section for all options.
#
#    For AWS KMS (default):
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"
#
#    For OS Keyring β€” no env vars needed (just set kms.provider: "os-keyring")
#    For Local AES β€” no env vars needed (key auto-generates)
#    For GPG β€” set kms.provider: "gpg" and kms.gpg_key_id in YAML

# 6. Customize configuration
cp config/default.yaml config/production.yaml
# Edit config/production.yaml with your RPC URLs, gateway settings, policy rules

# 7. 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"

# Google Pay credentials
npx agent-payments keys store --alias googlepay_merchant_id --type googlepay_token --value "YOUR_GOOGLE_MERCHANT_ID"
npx agent-payments keys store --alias googlepay_merchant_key --type googlepay_token --value "YOUR_GOOGLE_MERCHANT_KEY"

# Apple Pay credentials
npx agent-payments keys store --alias applepay_merchant_id --type applepay_token --value "merchant.com.yourapp"
npx agent-payments keys store --alias applepay_merchant_cert --type applepay_token --value "BASE64_ENCODED_CERT"
npx agent-payments keys store --alias applepay_merchant_key --type applepay_token --value "BASE64_ENCODED_KEY"
npx agent-payments keys store --alias applepay_processor_key --type applepay_token --value "YOUR_PROCESSOR_API_KEY"

# 8. Register open agents skill
npx skills add ./agent-payments-skill

OpenClaw Activation

Once 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.


Dry-Run Mode

Dry-run mode lets you explore every feature of the skill β€” protocol routing, policy engine, human-in-the-loop confirmation, audit trail, CLI, and web API β€” without any real payments, real blockchain transactions, or AWS credentials.

What Happens in Dry-Run

Component Production Dry-Run
AWS KMS Encrypts/decrypts via real KMS API Bypassed β€” local AES-256-GCM with a key from DRYRUN_ENCRYPTION_KEY env var
Encryption key KMS key ARN 256-bit hex key, auto-generated on first run and written to .env
Wallet keys Viem generatePrivateKey() β†’ encrypted via KMS Viem generatePrivateKey() β†’ encrypted via local AES β†’ stored in SQLite
Web3 payments Real on-chain transactions via Viem Stub: returns fake tx hash, simulated confirmation
Web2 payments Real API calls to Stripe / PayPal / Visa / MC / Google Pay / Apple Pay Stub: returns fake transaction IDs, simulated status
x402 remote payments Real HTTP to x402 resource, on-chain settlement Stub: returns fake tx hash and simulated resource data
AP2 remote payments Real HTTP to AP2 mandate issuer + credential provider Stub: returns fake mandate ID and simulated payment result
x402 server (paywall) Verifies payment and settles via facilitator Stub: returns simulated settlement success
AP2 server (mandates) Processes mandates against real payment backends Stubs: routes to stubbed backends (Stripe/PayPal/Viem stubs)
Policy engine βœ… runs normally βœ… runs normally
Human confirmation βœ… prompts on violations βœ… prompts on violations
Audit trail βœ… writes to SQLite + Winston βœ… writes to SQLite + Winston (tagged with dryrun_* actions)
Transaction records βœ… stored in SQLite βœ… stored in SQLite

How Dry-Run Works End-to-End

  1. Activation β€” Enable via dry_run.enabled: true in YAML, or pass --dry-run on the CLI.

  2. Encryption key β€” On first run, if DRYRUN_ENCRYPTION_KEY is not set, a random 256-bit key is generated, written to .env, and loaded into process.env. All subsequent runs reuse it.

  3. Wallet generation β€” bootstrap() calls ensureDryRunWallet("default_wallet") which uses Viem's generatePrivateKey(), encrypts the key with AES-256-GCM (local, no KMS), and stores the ciphertext in the encrypted_keys SQLite table with kms_key_id = "dryrun-local-aes256".

  4. Key storage/retrieval β€” encryptAndStore() and retrieveAndDecrypt() in aws-kms.ts check isDryRun() at the top. If true, they delegate to dry-run/wallet.ts which uses dry-run/crypto.ts β€” never touching AWS.

  5. Payment execution β€” sendEth(), sendErc20(), and executeWeb2Payment() detect dry-run and call the appropriate stub from dry-run/stubs.ts. Stubs simulate latency, return fake tx hashes / order IDs, and respect the stub_mode setting (success / failure / random).

  6. Policy engine runs normally β€” even in dry-run, all policy checks and human confirmation flows work identically. This lets you demo the full compliance flow.

  7. Demo command β€” openclaw-payment demo forces dry-run on, runs 6 sample payments covering all gateways and an over-limit scenario, and prints results:

openclaw-payment demo --stub-mode random

Enabling Dry-Run

Option 1 β€” YAML configuration:

dry_run:
  enabled: true
  encryption_key_env: "DRYRUN_ENCRYPTION_KEY"
  stub_mode: "success"        # "success" | "failure" | "random"
  simulated_latency_ms: 500   # fake network delay

Option 2 β€” CLI flag (overrides YAML):

openclaw-payment --dry-run pay \
  --protocol x402 --amount 5 --currency USDC \
  --to 0x742d35Cc6635C0532925a3b844Bc9e7595f2bD65 --network base

Option 3 β€” Demo command (always forces dry-run):

openclaw-payment demo
openclaw-payment demo --stub-mode random
openclaw-payment demo --stub-mode failure

Encryption Key Lifecycle

First run (DRYRUN_ENCRYPTION_KEY not set)
    β”‚
    β”œβ”€ Generate random 256-bit key
    β”œβ”€ Write to .env:  DRYRUN_ENCRYPTION_KEY=<64 hex chars>
    β”œβ”€ Set in process.env for current session
    β”‚
    β–Ό
Subsequent runs
    β”‚
    β”œβ”€ dotenv loads .env on startup
    β”œβ”€ DRYRUN_ENCRYPTION_KEY is present
    └─ Same key reuses β†’ same wallet is decryptable

⚠️ The .env file is in .gitignore. If you delete it, a new key is generated and previously encrypted entries become unreadable. For reproducible demos, you can set DRYRUN_ENCRYPTION_KEY to a fixed 64-char hex string.

Wallet Key Generation

On bootstrap in dry-run mode, the skill:

  1. Calls Viem's generatePrivateKey() to produce a valid secp256k1 private key
  2. Encrypts it with AES-256-GCM using the local dry-run key
  3. Stores the ciphertext in the encrypted_keys SQLite table (with kms_key_id = "dryrun-local-aes256")
  4. Derives the public address via privateKeyToAccount() and logs it

On subsequent runs, if default_wallet already exists in SQLite, the existing key is reused (decrypted locally, address re-derived).

Stub Modes

Mode Behavior Use Case
success All simulated payments return success Happy-path demos, integration testing
failure All simulated payments return failure Error-handling demos, policy engine testing
random ~70% success, ~30% failure Realistic mixed-outcome demos

Stubs also simulate configurable network latency (simulated_latency_ms) to make the demo feel realistic.

Stub Response Examples

Web3 (Viem) stub β€” success:

{
  "txHash": "0x8f3a1b2c4d5e6f7a8b9c0d1e2f3a4b5caaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
  "network": "base",
  "from": "0x1234...abcd",
  "to": "0x742d...f2bD65",
  "amount": "5.00",
  "currency": "USDC"
}

Stripe stub β€” success:

{
  "gateway": "stripe",
  "transaction_id": "pi_dryrun_a1b2c3d4e5f6",
  "status": "success",
  "amount": "49.99",
  "currency": "USD"
}

PayPal stub β€” success:

{
  "gateway": "paypal",
  "transaction_id": "PAYPAL-DRYRUN-A1B2C3D4E5",
  "status": "success",
  "amount": "25.00",
  "currency": "USD",
  "receipt_url": "https://sandbox.paypal.com/dryrun/approval"
}

Visa Direct stub β€” failure (stub_mode: failure):

{
  "gateway": "visa",
  "transaction_id": "VISA-DRYRUN-1739184000000",
  "status": "failed",
  "amount": "100.00",
  "currency": "USD",
  "error": "[DRY-RUN] Visa push funds declined"
}

Mastercard Send stub β€” success:

{
  "gateway": "mastercard",
  "transaction_id": "MC-DRYRUN-1739184000000",
  "status": "success",
  "amount": "75.00",
  "currency": "USD"
}

Google Pay stub β€” success:

{
  "gateway": "googlepay",
  "transaction_id": "GPAY-DRYRUN-A1B2C3D4E5F6",
  "status": "success",
  "amount": "35.00",
  "currency": "USD"
}

Apple Pay stub β€” success:

{
  "gateway": "applepay",
  "transaction_id": "APAY-DRYRUN-A1B2C3D4E5F6",
  "status": "success",
  "amount": "59.99",
  "currency": "USD",
  "receipt_url": "https://sandbox.apple.com/dryrun/receipt"
}

x402 remote resource stub β€” success:

{
  "data": { "dryRun": true, "message": "Simulated x402 resource access" },
  "txHash": "0x8f3a1b2c4d5e6f7a8b9c0d1e2f3a4b5caaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
  "network": "base"
}

AP2 remote mandate stub β€” success:

{
  "mandate_id": "mandate_dryrun_1739184000000",
  "status": "success",
  "transaction_id": "ap2-dryrun-a1b2c3d4e5f6",
  "receipt": {
    "amount": "19.99",
    "currency": "USD",
    "timestamp": "2026-03-06T12:00:00.000Z",
    "reference": "REF-DRYRUN-1739184000000"
  }
}

Demo Command

The demo command runs 11 pre-built sample payments across all supported gateways, protocol clients, and an over-limit policy scenario:

openclaw-payment demo --stub-mode success
πŸ§ͺ ════════════════════════════════════════════════
   AGENTIC PAYMENT SKILL β€” INTERACTIVE DEMO
   Stub mode: success
═══════════════════════════════════════════════════

─── 1️⃣   x402 USDC payment on Base (web3) ───
  βœ… Success | TX: 0x8f3a1b2c...

─── 2️⃣   AP2 Stripe payment (web2) ───
  βœ… Success | ID: pi_dryrun_a1b2c3d4e5f6

─── 3️⃣   AP2 PayPal payment (web2) ───
  βœ… Success | ID: PAYPAL-DRYRUN-A1B2C3D4E5

─── 4️⃣   x402 ETH transfer on Ethereum (web3) ───
  βœ… Success | TX: 0x9c4b2d3e...

─── 5️⃣   AP2 Visa Direct payment (web2) ───
  βœ… Success | ID: VISA-DRYRUN-1739184000000

─── 6️⃣   AP2 Mastercard Send payment (web2) ───
  βœ… Success | ID: MC-DRYRUN-1739184000000

─── 7️⃣   AP2 Google Pay payment (web2) ───
  βœ… Success | ID: GPAY-DRYRUN-A1B2C3D4E5F6

─── 8️⃣   AP2 Apple Pay payment (web2) ───
  βœ… Success | ID: APAY-DRYRUN-A1B2C3D4E5F6

─── 9️⃣   x402 remote resource payment (x402 client) ───
  βœ… Success | x402 TX: 0xabc123def456...

─── πŸ”Ÿ  AP2 remote mandate payment (AP2 client) ───
  βœ… Success | Mandate: mandate_dryrun_1739184000

─── ⚠️   Over-limit payment (triggers policy engine) ───
  ❌ Not executed: Payment rejected by human confirmation.
  ⚠️  Policy: [single_transaction.max_amount_usd] Amount $99999.99 exceeds limit of $1000.00

══════════════════════════════════════════════════
  Demo complete. All transactions are in SQLite.
  Run: openclaw-payment audit --limit 30
══════════════════════════════════════════════════

After the demo, inspect the results:

# View all transactions created during the demo
sqlite3 data/payments.db "SELECT id, protocol, gateway, amount, currency, status FROM transactions ORDER BY created_at DESC LIMIT 10;"

# View the full audit trail
openclaw-payment audit --limit 30

# Check the generated wallet
openclaw-payment keys list

Dry-Run Configuration Reference

dry_run:
  # Master toggle. Overridden by --dry-run CLI flag.
  enabled: false

  # Name of the environment variable holding the 256-bit AES key
  # (64 hex characters). If empty on first run, auto-generated
  # and appended to .env in the project root.
  encryption_key_env: "DRYRUN_ENCRYPTION_KEY"

  # How stubs behave:
  #   "success" β€” all stubs return successful responses
  #   "failure" β€” all stubs return error responses
  #   "random"  β€” ~70% success, ~30% failure (randomized)
  stub_mode: "success"

  # Simulated network latency in milliseconds.
  # Set to 0 for instant responses (useful in tests).
  simulated_latency_ms: 500

Dry-Run Audit Actions

In dry-run mode, audit log entries use dryrun_* action prefixes so they are easily distinguishable from production entries:

Action Trigger
dryrun_mode_activated On bootstrap when dry-run is enabled
dryrun_wallet_generated New wallet key generated and stored
dryrun_key_stored Any key/token encrypted with local AES
dryrun_key_decrypted Key retrieved and decrypted locally
dryrun_eth_transfer Simulated ETH transfer
dryrun_erc20_transfer Simulated ERC-20 transfer
dryrun_stripe Simulated Stripe payment
dryrun_paypal Simulated PayPal payment
dryrun_visa Simulated Visa Direct payment
dryrun_mastercard Simulated Mastercard Send payment
dryrun_googlepay Simulated Google Pay payment
dryrun_applepay Simulated Apple Pay payment
dryrun_x402_remote Simulated x402 remote resource access (client)
dryrun_ap2_remote Simulated AP2 remote mandate submission (client)
dryrun_web2_executed Web2 payment stub completed
dryrun_web3_confirmed Web3 tx stub confirmed

Quick Start (Zero Setup Demo)

Run the entire skill with zero AWS credentials and zero payment gateway accounts:

# 1. Clone and install
git clone https://github.com/sentient-agi/agentic-payments-bot.git
cd agentic-payments-bot
npm install
npm run build

# 2. Run the demo (no env vars needed β€” key auto-generates)
npx openclaw-payment demo

# 3. Try individual payments
npx openclaw-payment --dry-run pay \
  --protocol x402 --amount 10 --currency USDC \
  --to 0x742d35Cc6635C0532925a3b844Bc9e7595f2bD65 --network base

npx openclaw-payment --dry-run pay \
  --protocol ap2 --amount 29.99 --currency USD \
  --to merchant-test --gateway stripe

# 4. Trigger a policy violation (default limit is $1000)
npx openclaw-payment --dry-run pay \
  --protocol ap2 --amount 5000 --currency USD \
  --to big-purchase --gateway paypal

# 5. Inspect results
npx openclaw-payment --dry-run audit --limit 20
npx openclaw-payment --dry-run keys list

# 6. Start the web API in dry-run
npx openclaw-payment --dry-run &  # or set dry_run.enabled: true in YAML
curl http://localhost:3402/api/v1/health
curl -X POST http://localhost:3402/api/v1/payment \
  -H "Content-Type: application/json" \
  -d '{"protocol":"x402","action":"pay","amount":"5","currency":"USDC","recipient":"0x742d35Cc6635C0532925a3b844Bc9e7595f2bD65","network":"base"}'

Configuration Reference (YAML)

Full Annotated Configuration

# ═══════════════════════════════════════════════════════════════════════
# 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

    # Server-side (paywall) configuration
    # When enabled, the web API exposes x402-protected endpoints
    # that external agents can pay to access.
    server:
      enabled: true                # Enable x402 server endpoints
      pay_to_address: "0x..."      # Wallet address that receives x402 payments
                                   # Override via X402_PAY_TO_ADDRESS env var
      default_price: "1000000"     # Default price in asset base units
                                   # (e.g. "1000000" = 1 USDC with 6 decimals)
      default_description: "Access to premium agentic data feed"

  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

    # Server-side (mandate processor) configuration
    # When enabled, the web API exposes AP2 mandate lifecycle endpoints
    # for external agents to submit mandates and trigger payments.
    server:
      enabled: true                # Enable AP2 server endpoints
      agent_id: "openclaw-payments-agent"
                                   # Agent ID used when this service acts
                                   # as an AP2 client

# ── 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

  googlepay:
    enabled: true
    environment: "TEST"              # "TEST" or "PRODUCTION"
    base_url: "https://pay.google.com/gp/p"
    allowed_card_networks:           # Card networks accepted via Google Pay
      - "AMEX"
      - "DISCOVER"
      - "JCB"
      - "MASTERCARD"
      - "VISA"
    allowed_auth_methods:            # Token authentication methods
      - "PAN_ONLY"                   # PAN with expiry + billing address
      - "CRYPTOGRAM_3DS"             # 3D Secure device token

  applepay:
    enabled: true
    base_url: "https://apple-pay-gateway.apple.com/paymentservices"
                                     # Apple Pay payment services endpoint
    domain: "your-domain.example.com"
                                     # Must be verified with Apple
    display_name: "OpenClaw Payments"
                                     # Shown on the Apple Pay payment sheet
    supported_networks:              # Card networks accepted via Apple Pay
      - "visa"
      - "masterCard"
      - "amex"
      - "discover"
    merchant_capabilities:           # Supported capabilities
      - "supports3DS"
      - "supportsCredit"
      - "supportsDebit"

# ── 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 KMS integration

  # ── Provider Selection ─────────────────────────────────────────────
  # Selects which backend handles secret encryption/storage.
  #
  #   "aws-kms"     β€” AWS KMS (production cloud). Requires AWS env vars.
  #   "os-keyring"  β€” OS-native keyring: KDE Wallet / GNOME Keyring
  #                    (Linux), Keychain (macOS), Credential Manager
  #                    (Windows). Uses @aspect-build/keytar. Falls back
  #                    to local-aes on headless systems without D-Bus.
  #   "dbus-secret" β€” Linux-only: D-Bus Secret Service API via dbus-next
  #                    (pure JS, no native compilation). Works with
  #                    GNOME Keyring and KDE Wallet (Secret Service
  #                    bridge). Falls back to local-aes on headless.
  #   "gpg"         β€” GnuPG encryption. Ideal for headless Linux
  #                    servers without D-Bus. Requires a GPG keypair.
  #   "local-aes"   β€” Local AES-256-GCM. Key from DRYRUN_ENCRYPTION_KEY
  #                    env var (auto-generated if missing). No external
  #                    dependencies.
  provider: "aws-kms"

  # ── AWS KMS Settings (only when provider is "aws-kms") ─────────────
  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
  # AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY from environment

  # ── Linux Keyring Backend (only when provider is "os-keyring") ─────
  # Selects the library used for OS keyring access on Linux:
  #   "keytar"    β€” @aspect-build/keytar (native addon, full cross-platform)
  #   "dbus-next" β€” Pure JS D-Bus Secret Service client (Linux only,
  #                  no native compilation needed)
  linux_keyring_backend: "keytar"

  # ── GnuPG Settings (only when provider is "gpg") ──────────────────
  # gpg_key_id: "your-fingerprint-or-email@example.com"
  #                                # GPG key fingerprint or email. Required
  #                                # when provider is "gpg".
  # gpg_binary: "gpg2"            # Path to gpg binary (default: gpg2)

# ── 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

Configuration Sections

Section Purpose Key Settings
skill Metadata name, version β€” must match SKILL.md
database SQLite path, wal_mode, busy_timeout_ms
protocols.x402 x402 client + server facilitator_url, default_network, default_asset, server.enabled, server.pay_to_address, server.default_price
protocols.ap2 AP2 client + server mandate_issuer, credential_provider_url, server.enabled, server.agent_id
web3.<network> EVM chains rpc_url, chain_id, enabled
web2.<gateway> Payment APIs Gateway-specific URLs, API versions
kms Key management provider, region, key_id_env, linux_keyring_backend, gpg_key_id, gpg_binary
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

CLI Reference

The CLI is available as agent-payments (via npm bin) or npx agent-payments.

Global Options

Flag Description Default
-c, --config <path> Path to YAML config file config/default.yaml
-V, --version Print version β€”
-h, --help Show help β€”

pay β€” Execute a Payment

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, merchant ID, or URL (for x402/AP2 remote payments)
--network <string> ❌ Blockchain network (ethereum, base, polygon, web2)
--gateway <string> ❌ Payment gateway (viem, stripe, paypal, visa, mastercard, googlepay, applepay, x402, ap2)
--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 paypal

# Visa Direct payment
agent-payments pay \
  --protocol ap2 \
  --amount 100.00 \
  --currency USD \
  --to 4111111111111111 \
  --gateway visa

# Mastercard Send payment
agent-payments pay \
  --protocol ap2 \
  --amount 75.00 \
  --currency USD \
  --to 5500000000000004 \
  --gateway mastercard

# x402 remote resource payment (pay for a URL)
agent-payments pay \
  --protocol x402 \
  --amount 1.00 \
  --currency USDC \
  --to https://api.premium-service.com/v1/data \
  --network base \
  --gateway x402

# AP2 remote mandate submission (pay via AP2 to a URL)
agent-payments pay \
  --protocol ap2 \
  --amount 19.99 \
  --currency USD \
  --to https://merchant.example.com/ap2/process-payment \
  --gateway ap2

parse β€” Parse AI Output

Extract 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 -

keys β€” Key Management

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_key

Key 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
googlepay_token googlepay_merchant_id, googlepay_merchant_key Google Pay token processing
applepay_token applepay_merchant_id, applepay_merchant_cert, applepay_merchant_key, applepay_processor_key Apple Pay merchant validation & token processing

tx β€” Transaction Lookup

agent-payments tx <transaction-id>

Outputs the full transaction record as JSON, including status, policy violations, tx hash, and timestamps.

audit β€” Query Audit Log

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 20

Web API Reference

Base URL

http://<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.js

Endpoints


GET /api/v1/health

Health check endpoint.

Response 200:

{
  "status": "ok",
  "skill": "agentic-payments-bot",
  "version": "0.5.0",
  "dryRun": false,
  "protocols": {
    "x402": { "enabled": true },
    "ap2": { "enabled": true }
  }
}

POST /api/v1/payment

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, merchant ID, or URL (for x402/AP2 remote payments)
network string ❌ Blockchain network name
gateway string ❌ Explicit gateway selection: viem, stripe, paypal, visa, mastercard, googlepay, applepay, x402, ap2
description string ❌ Human-readable description
metadata object ❌ Arbitrary metadata (e.g., paymentToken for GPay/APay, payment_method_type for AP2)
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,
  "dryRun": 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"
}

POST /api/v1/parse

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
}

POST /api/v1/confirm/:txId

Confirm or reject a pending transaction that requires human approval.

URL parameters:

  • txId β€” Transaction ID from the 202 response

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..."
}

GET /api/v1/pending

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"
      }
    ]
  }
]

GET /api/v1/transactions/:txId

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

GET /api/v1/audit

Query the audit log with optional filters.

Query parameters:

Param Type Description
category string Filter: payment, policy, kms, protocol, system, x402_server, ap2_server
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
  }
]

x402 Server Endpoints

These endpoints allow external agents and services to pay you via the x402 protocol.


GET /api/v1/x402/premium/data (Paywall-Protected)

An example x402-protected resource. Returns premium data after successful payment.

Without X-PAYMENT header β€” Response 402:

The server returns payment requirements in the X-PAYMENT response header:

HTTP/1.1 402 Payment Required
X-PAYMENT: eyJzY2hlbWUiOiJleGFjdCIsIm5ldHdvcmsiOi4uLn0=
Content-Type: application/json

{
  "error": "Payment Required",
  "accepts": {
    "scheme": "exact",
    "network": "base",
    "maxAmountRequired": "1000000",
    "resource": "/api/v1/x402/premium/data",
    "description": "Access to premium agentic data feed",
    "mimeType": "application/json",
    "payTo": "0x...",
    "maxTimeoutSeconds": 60,
    "asset": "USDC"
  }
}

With valid X-PAYMENT header β€” Response 200:

The agent sends a Base64-encoded signed payment payload:

GET /api/v1/x402/premium/data
X-PAYMENT: eyJ4NDAyVmVyc2lvbiI6MSwi...

On successful settlement:

HTTP/1.1 200 OK
X-PAYMENT-RESPONSE: eyJzdWNjZXNzIjp0cnVlLCJ0eEhhc2giOi4uLn0=
Content-Type: application/json

{
  "data": "This is premium data, paid for via x402.",
  "timestamp": "2026-03-06T12:00:00.000Z",
  "source": "agentic-payments-bot"
}

x402 Payment Flow (from the agent's perspective):

Agent                                Server
  β”‚                                    β”‚
  │── GET /api/v1/x402/premium/data ──►│
  β”‚                                    β”‚
  │◄── 402 + X-PAYMENT (requirements)──│
  β”‚                                    β”‚
  β”‚   [sign EIP-3009 authorization]    β”‚
  β”‚                                    β”‚
  │── GET /api/v1/x402/premium/data ──►│
  β”‚   X-PAYMENT: <signed payload>      β”‚
  β”‚                                    │── [verify + settle on-chain]
  β”‚                                    β”‚
  │◄── 200 + X-PAYMENT-RESPONSE ───────│
  β”‚   + resource data                  β”‚

GET /api/v1/x402/pricing

List all registered x402-priced resources.

Response 200:

{
  "resources": [
    {
      "route": "/api/v1/x402/premium/data",
      "maxAmountRequired": "1000000",
      "asset": "USDC",
      "network": "base",
      "payTo": "0x...",
      "description": "Access to premium agentic data feed",
      "mimeType": "application/json"
    }
  ]
}

POST /api/v1/x402/verify

Verify a settlement transaction hash through the facilitator.

Request body:

{
  "txHash": "0xabc123...",
  "network": "base"
}

Response 200:

{
  "verified": true,
  "details": {
    "blockNumber": 12345678,
    "from": "0x...",
    "to": "0x...",
    "value": "1000000"
  }
}

AP2 Server Endpoints

These endpoints allow external agents to pay you via the AP2 mandate protocol. The server acts as a mandate issuer, credential provider, and payment processor.


POST /api/v1/ap2/mandates

Accept a new mandate from an external agent.

Request body:

{
  "mandate_id": "mandate_1709712000_abc123",
  "version": "1.0",
  "intent": {
    "action": "pay",
    "description": "Purchase premium API access",
    "amount": {
      "value": "49.99",
      "currency": "USD"
    },
    "recipient": {
      "id": "merchant-001",
      "name": "Example Merchant"
    }
  },
  "constraints": {
    "max_amount": "49.99",
    "valid_from": "2026-03-06T00:00:00.000Z",
    "valid_until": "2026-03-06T01:00:00.000Z",
    "single_use": true
  },
  "delegator": {
    "agent_id": "claude-agent-001",
    "user_id": "user-12345"
  }
}

Response 201:

{
  "mandate_id": "mandate_1709712000_abc123",
  "status": "accepted"
}

Response 400:

{
  "error": "Mandate has expired"
}

Response 409:

{
  "error": "Mandate ID already exists",
  "mandate_id": "mandate_1709712000_abc123"
}

GET /api/v1/ap2/mandates

List all accepted mandates.

Response 200:

{
  "mandates": [
    {
      "mandate_id": "mandate_1709712000_abc123",
      "status": "accepted",
      "amount": "49.99",
      "currency": "USD",
      "agent_id": "claude-agent-001",
      "created_at": "2026-03-06T12:00:00.000Z",
      "executed_at": null
    }
  ]
}

GET /api/v1/ap2/mandates/:mandateId

Get the status of a specific mandate.

Response 200:

{
  "mandate_id": "mandate_1709712000_abc123",
  "status": "executed",
  "created_at": "2026-03-06T12:00:00.000Z",
  "executed_at": "2026-03-06T12:01:00.000Z",
  "transaction_id": "pi_3Abc123..."
}

Response 404:

{
  "error": "Mandate not found"
}

POST /api/v1/ap2/sign-mandate

Sign a mandate (acting as a credential provider). In production, this verifies the delegator's identity and signs the mandate with the server's ECDSA key.

Request body:

{
  "mandate": {
    "mandate_id": "mandate_1709712000_abc123",
    "version": "1.0",
    "intent": { "..." : "..." },
    "constraints": { "..." : "..." },
    "delegator": { "..." : "..." }
  }
}

Response 200:

{
  "signed_mandate": {
    "mandate_id": "mandate_1709712000_abc123",
    "version": "1.0",
    "intent": { "..." : "..." },
    "constraints": { "..." : "..." },
    "delegator": { "..." : "..." },
    "signature": "sig_1709712000_a1b2c3d4"
  }
}

POST /api/v1/ap2/payment-credentials

Issue tokenized payment credentials for a specific mandate and payment method.

Request body:

{
  "mandate_id": "mandate_1709712000_abc123",
  "payment_method_type": "stripe"
}

Response 200:

{
  "type": "stripe",
  "details": {
    "token": "tok_mandate_1709712000_abc123_1709712060000",
    "scoped_to_mandate": "mandate_1709712000_abc123",
    "max_amount": "49.99",
    "currency": "USD"
  }
}

Response 409 (mandate already executed):

{
  "error": "Mandate already executed (single-use)"
}

POST /api/v1/ap2/process-payment

Execute a mandate against the server's internal payment backends. This is the final step in the AP2 flow β€” the agent submits a mandate and payment method, and the server routes the payment to Stripe, PayPal, Viem, or any other configured backend.

Request body:

{
  "mandate": {
    "mandate_id": "mandate_1709712000_abc123",
    "version": "1.0",
    "intent": {
      "action": "pay",
      "description": "Premium API access",
      "amount": { "value": "49.99", "currency": "USD" },
      "recipient": { "id": "merchant-001" }
    },
    "constraints": {
      "max_amount": "49.99",
      "valid_from": "2026-03-06T00:00:00.000Z",
      "valid_until": "2026-03-06T01:00:00.000Z",
      "single_use": true
    },
    "delegator": { "agent_id": "claude-agent-001" },
    "signature": "sig_1709712000_a1b2c3d4"
  },
  "payment_method": {
    "type": "stripe",
    "details": {
      "token": "tok_mandate_1709712000_abc123_1709712060000"
    }
  }
}

Supported payment_method.type values:

Type Backend Description
stripe Stripe Routes to Stripe Payment Intents API
paypal PayPal Routes to PayPal Orders API
card Stripe (default) Generic card payment, routes to Stripe
crypto Viem On-chain transfer (ETH or ERC-20). Specify details.network and optionally details.wallet_alias.
bank_transfer β€” Not yet supported

Response 200 (success):

{
  "mandate_id": "mandate_1709712000_abc123",
  "status": "success",
  "transaction_id": "pi_3Abc123...",
  "receipt": {
    "amount": "49.99",
    "currency": "USD",
    "timestamp": "2026-03-06T12:01:00.000Z",
    "reference": "pi_3Abc123..."
  }
}

Response 202 (pending):

{
  "mandate_id": "mandate_1709712000_abc123",
  "status": "pending",
  "transaction_id": "pi_3Abc123..."
}

Response 400 (failed):

{
  "mandate_id": "mandate_1709712000_abc123",
  "status": "failed",
  "error": "Mandate has expired"
}

Error Responses

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

OpenClaw Chat Integration

When the skill is activated in OpenClaw, the agent uses the SKILL.md instructions to output structured payment intents during conversation.

Payment Intent JSON Schema

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 or URL>",
  "network": "ethereum | base | polygon | web2",
  "gateway": "viem | visa | mastercard | paypal | stripe | googlepay | applepay | x402 | ap2 | 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.

Protocol Detection Heuristics

The SKILL.md instructs the agent:

Signal Routed To
Target is an HTTP resource returning 402 x402 β†’ x402 gateway (client)
Recipient is a URL + protocol: "x402" x402 β†’ x402 gateway (client, remote resource payment)
User mentions "x402", "stablecoin", "USDC", "onchain" x402
Payment involves a mandate, delegated purchase AP2
Recipient is a URL + protocol: "ap2" AP2 β†’ ap2 gateway (client, remote mandate submission)
Traditional card/gateway payment via agent AP2
User mentions "Google Pay", "GPay" AP2 β†’ googlepay gateway
User mentions "Apple Pay" AP2 β†’ applepay gateway
Crypto currency (USDC, ETH, DAI) web3 (Viem)
Fiat currency (USD, EUR) web2 (Stripe default)

Chat Confirmation Flow

When a policy violation is detected during a chat-initiated payment:

  1. 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.
    
  2. User responds in the chat with confirm <txId> or reject <txId>

  3. Skill parses the response and resumes/cancels the payment


Usage Examples

Example 1 β€” x402 USDC Payment via CLI

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_wallet

Output:

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
═══════════════════════════════════════

Example 2 β€” AP2 Stripe Payment via Web API

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

Example 3 β€” AI Chat-Driven Payment

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.

Example 4 β€” Policy Violation & Human Confirmation

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"}'

Example 5 β€” Key Management

# 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_wallet

Example 6 β€” Mastercard Send Payment

Via CLI:

agent-payments pay \
  --protocol ap2 \
  --amount 75.00 \
  --currency USD \
  --to 5500000000000004 \
  --gateway mastercard \
  --description "Freelancer payout via Mastercard Send"

Via Web API:

curl -X POST http://localhost:3402/api/v1/payment \
  -H "Content-Type: application/json" \
  -d '{
    "protocol": "ap2",
    "action": "pay",
    "amount": "75.00",
    "currency": "USD",
    "recipient": "5500000000000004",
    "gateway": "mastercard",
    "description": "Freelancer payout via Mastercard Send"
  }'

Response:

{
  "success": true,
  "tx": {
    "id": "c3d4e5f6-7890-abcd-ef12-345678901234",
    "protocol": "ap2",
    "gateway": "mastercard",
    "amount": 75.0,
    "amount_usd": 75.0,
    "currency": "USD",
    "status": "executed"
  },
  "web2Result": {
    "gateway": "mastercard",
    "transaction_id": "MC-TXN-12345678",
    "status": "success",
    "amount": "75.00",
    "currency": "USD"
  },
  "policyResult": {
    "allowed": true,
    "violations": [],
    "requiresHumanConfirmation": false
  },
  "confirmationRequired": false,
  "dryRun": false
}

Example 7 β€” Google Pay Payment via Web API

curl -X POST http://localhost:3402/api/v1/payment \
  -H "Content-Type: application/json" \
  -d '{
    "protocol": "ap2",
    "action": "pay",
    "amount": "35.00",
    "currency": "USD",
    "recipient": "merchant-gpay-001",
    "gateway": "googlepay",
    "description": "In-app purchase via Google Pay",
    "metadata": {
      "paymentToken": "<encrypted-token-from-google-pay-js-api>",
      "countryCode": "US"
    }
  }'

Response:

{
  "success": true,
  "tx": {
    "id": "d4e5f678-9012-bcde-f123-456789012345",
    "protocol": "ap2",
    "gateway": "googlepay",
    "amount": 35.0,
    "amount_usd": 35.0,
    "currency": "USD",
    "status": "executed"
  },
  "web2Result": {
    "gateway": "googlepay",
    "transaction_id": "GPAY-abc123def456",
    "status": "success",
    "amount": "35.00",
    "currency": "USD"
  },
  "policyResult": {
    "allowed": true,
    "violations": [],
    "requiresHumanConfirmation": false
  },
  "confirmationRequired": false
}

Note: The paymentToken in metadata must be the encrypted payment token obtained from the client-side Google Pay JS API. The server never generates this token β€” it only processes it.

Example 8 β€” Apple Pay Payment via Web API

curl -X POST http://localhost:3402/api/v1/payment \
  -H "Content-Type: application/json" \
  -d '{
    "protocol": "ap2",
    "action": "pay",
    "amount": "59.99",
    "currency": "USD",
    "recipient": "merchant-applepay-001",
    "gateway": "applepay",
    "description": "Checkout via Apple Pay",
    "metadata": {
      "paymentToken": "<encrypted-token-from-apple-pay-js-api>",
      "validationURL": "https://apple-pay-gateway-cert.apple.com/paymentservices/startSession"
    }
  }'

Response:

{
  "success": true,
  "tx": {
    "id": "e5f67890-1234-cdef-0123-567890123456",
    "protocol": "ap2",
    "gateway": "applepay",
    "amount": 59.99,
    "amount_usd": 59.99,
    "currency": "USD",
    "status": "executed"
  },
  "web2Result": {
    "gateway": "applepay",
    "transaction_id": "APAY-xyz789abc012",
    "status": "success",
    "amount": "59.99",
    "currency": "USD",
    "receipt_url": "https://example.com/receipt/xyz789"
  },
  "policyResult": {
    "allowed": true,
    "violations": [],
    "requiresHumanConfirmation": false
  },
  "confirmationRequired": false
}

Note: The paymentToken must be the encrypted token from the client-side Apple Pay JS API. The optional validationURL triggers server-to-server merchant session validation with Apple before the token is processed.

Example 9 β€” x402 Paywall (External Agent Paying You)

This demonstrates the server side β€” an external agent pays for access to a resource protected by the x402 paywall middleware.

Step 1 β€” Agent discovers payment requirements:

# Agent requests the resource β€” gets 402 + payment details
curl -v http://localhost:3402/api/v1/x402/premium/data

Response:

< HTTP/1.1 402 Payment Required
< X-PAYMENT: eyJzY2hlbWUiOiJleGFjdCIsIm5ldHdvcmsiOiJiYXNlIiwi...
<
{
  "error": "Payment Required",
  "accepts": {
    "scheme": "exact",
    "network": "base",
    "maxAmountRequired": "1000000",
    "asset": "USDC",
    "payTo": "0x...",
    "resource": "/api/v1/x402/premium/data",
    "description": "Access to premium agentic data feed"
  }
}

Step 2 β€” Agent signs and submits payment:

# Agent retries with a signed X-PAYMENT header
curl -v http://localhost:3402/api/v1/x402/premium/data \
  -H "X-PAYMENT: eyJ4NDAyVmVyc2lvbiI6MSwisc2NoZW1lIjoiZXhhY3QiLC..."

Response (success):

< HTTP/1.1 200 OK
< X-PAYMENT-RESPONSE: eyJzdWNjZXNzIjp0cnVlLCJ0eEhhc2giOiIweGFiYy4uLiJ9
<
{
  "data": "This is premium data, paid for via x402.",
  "timestamp": "2026-03-06T12:00:00.000Z",
  "source": "agentic-payments-bot"
}

Step 3 β€” Check available pricing:

curl http://localhost:3402/api/v1/x402/pricing

Response:

{
  "resources": [
    {
      "route": "/api/v1/x402/premium/data",
      "maxAmountRequired": "1000000",
      "asset": "USDC",
      "network": "base",
      "payTo": "0x...",
      "description": "Access to premium agentic data feed"
    }
  ]
}

Example 10 β€” AP2 Mandate (External Agent Paying You)

This demonstrates the server side β€” an external agent submits an AP2 mandate and the server processes the payment internally.

Step 1 β€” Agent submits a mandate:

curl -X POST http://localhost:3402/api/v1/ap2/mandates \
  -H "Content-Type: application/json" \
  -d '{
    "mandate_id": "mandate_1709712000_demo",
    "version": "1.0",
    "intent": {
      "action": "pay",
      "description": "API access subscription",
      "amount": { "value": "29.99", "currency": "USD" },
      "recipient": { "id": "merchant-001" }
    },
    "constraints": {
      "max_amount": "29.99",
      "valid_from": "2026-03-06T00:00:00.000Z",
      "valid_until": "2026-03-07T00:00:00.000Z",
      "single_use": true
    },
    "delegator": {
      "agent_id": "external-agent-001",
      "user_id": "user-42"
    }
  }'

Response:

{
  "mandate_id": "mandate_1709712000_demo",
  "status": "accepted"
}

Step 2 β€” Agent requests mandate signing:

curl -X POST http://localhost:3402/api/v1/ap2/sign-mandate \
  -H "Content-Type: application/json" \
  -d '{
    "mandate": {
      "mandate_id": "mandate_1709712000_demo",
      "version": "1.0",
      "intent": {
        "action": "pay",
        "description": "API access subscription",
        "amount": { "value": "29.99", "currency": "USD" },
        "recipient": { "id": "merchant-001" }
      },
      "constraints": {
        "max_amount": "29.99",
        "valid_from": "2026-03-06T00:00:00.000Z",
        "valid_until": "2026-03-07T00:00:00.000Z",
        "single_use": true
      },
      "delegator": { "agent_id": "external-agent-001" }
    }
  }'

Step 3 β€” Agent obtains payment credentials:

curl -X POST http://localhost:3402/api/v1/ap2/payment-credentials \
  -H "Content-Type: application/json" \
  -d '{
    "mandate_id": "mandate_1709712000_demo",
    "payment_method_type": "stripe"
  }'

Step 4 β€” Agent submits for payment processing:

curl -X POST http://localhost:3402/api/v1/ap2/process-payment \
  -H "Content-Type: application/json" \
  -d '{
    "mandate": {
      "mandate_id": "mandate_1709712000_demo",
      "version": "1.0",
      "intent": {
        "action": "pay",
        "description": "API access subscription",
        "amount": { "value": "29.99", "currency": "USD" },
        "recipient": { "id": "merchant-001" }
      },
      "constraints": {
        "max_amount": "29.99",
        "valid_from": "2026-03-06T00:00:00.000Z",
        "valid_until": "2026-03-07T00:00:00.000Z",
        "single_use": true
      },
      "delegator": { "agent_id": "external-agent-001" },
      "signature": "sig_1709712000_a1b2c3d4"
    },
    "payment_method": {
      "type": "stripe",
      "details": { "token": "tok_mandate_1709712000_demo_1709712060000" }
    }
  }'

Response:

{
  "mandate_id": "mandate_1709712000_demo",
  "status": "success",
  "transaction_id": "pi_3Abc123...",
  "receipt": {
    "amount": "29.99",
    "currency": "USD",
    "timestamp": "2026-03-06T12:01:00.000Z",
    "reference": "pi_3Abc123..."
  }
}

Step 5 β€” Verify mandate status:

curl http://localhost:3402/api/v1/ap2/mandates/mandate_1709712000_demo

Response:

{
  "mandate_id": "mandate_1709712000_demo",
  "status": "executed",
  "created_at": "2026-03-06T12:00:00.000Z",
  "executed_at": "2026-03-06T12:01:00.000Z",
  "transaction_id": "pi_3Abc123..."
}

Example 11 β€” x402 Remote Resource Payment (Paying Another Service)

This demonstrates the client side β€” your agent pays for access to an x402-protected resource hosted by another service.

Via Web API:

curl -X POST http://localhost:3402/api/v1/payment \
  -H "Content-Type: application/json" \
  -d '{
    "protocol": "x402",
    "action": "pay",
    "amount": "1.00",
    "currency": "USDC",
    "recipient": "https://api.premium-service.com/v1/data",
    "network": "base",
    "gateway": "x402",
    "description": "Access premium data via x402"
  }'

Response:

{
  "success": true,
  "tx": {
    "id": "f6789012-3456-def0-1234-567890abcdef",
    "protocol": "x402",
    "gateway": "x402",
    "amount": 1.0,
    "currency": "USDC",
    "recipient": "https://api.premium-service.com/v1/data",
    "status": "executed"
  },
  "txHash": "0xabc123...",
  "x402Result": {
    "data": { "premium": "content" },
    "txHash": "0xabc123...",
    "network": "base"
  },
  "policyResult": {
    "allowed": true,
    "violations": [],
    "requiresHumanConfirmation": false
  },
  "confirmationRequired": false,
  "dryRun": false
}

Via CLI:

agent-payments pay \
  --protocol x402 \
  --amount 1.00 \
  --currency USDC \
  --to https://api.premium-service.com/v1/data \
  --network base \
  --gateway x402

Example 12 β€” AP2 Remote Mandate Payment (Paying Another Service)

This demonstrates the client side β€” your agent creates and submits a mandate to an AP2-compliant external payment processor.

Via Web API:

curl -X POST http://localhost:3402/api/v1/payment \
  -H "Content-Type: application/json" \
  -d '{
    "protocol": "ap2",
    "action": "pay",
    "amount": "79.99",
    "currency": "USD",
    "recipient": "https://merchant.example.com/ap2/process-payment",
    "gateway": "ap2",
    "description": "Annual subscription via AP2",
    "metadata": {
      "payment_method_type": "card"
    }
  }'

Response:

{
  "success": true,
  "tx": {
    "id": "a7890123-4567-ef01-2345-67890abcdef1",
    "protocol": "ap2",
    "gateway": "ap2",
    "amount": 79.99,
    "currency": "USD",
    "status": "executed"
  },
  "ap2Result": {
    "mandate_id": "mandate_1709712000_xyz",
    "transaction_id": "ap2-tx-abc123",
    "status": "success"
  },
  "policyResult": {
    "allowed": true,
    "violations": [],
    "requiresHumanConfirmation": false
  },
  "confirmationRequired": false,
  "dryRun": false
}

Development

Build

npm run build        # Compile TypeScript to dist/

Run Tests

npm test             # Run Jest test suite

Run in Development

npm run dev          # ts-node src/index.ts

Project Dependencies

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 (aws-kms provider)
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

Optional dependencies (install only for the KMS providers you need):

Package Version Purpose Install Command
@aspect-build/keytar * OS Keyring integration (native addon) β€” KDE Wallet, GNOME Keyring, macOS Keychain, Windows Credential Manager npm install @aspect-build/keytar --save-optional
dbus-next * Linux D-Bus Secret Service API (pure JS, no native compilation) npm install dbus-next --save-optional

Troubleshooting

Common Issues

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, or switch to a different kms.provider
Unknown KMS provider: 'X' Invalid kms.provider value in config Use one of: aws-kms, os-keyring, dbus-secret, gpg, local-aes
OS keyring unavailable ... Falling back to local-aes No D-Bus session (headless server) Expected behavior β€” use gpg or local-aes provider explicitly, or install a D-Bus session
GPG provider requires 'kms.gpg_key_id' Missing GPG key ID in config Set kms.gpg_key_id to your GPG key fingerprint or email
gpg2: command not found GnuPG not installed Install gnupg2 package, or set kms.gpg_binary to the correct path
D-Bus Secret Service: key not found Secret not stored in keyring Store the key first via agent-payments keys store, or check that the correct keyring is unlocked
KDE Wallet prompt on every access KWallet locked or Secret Service bridge disabled Unlock KDE Wallet, or enable Secret Service integration in KDE System Settings
@aspect-build/keytar build failure Missing C++ build tools for native addon Install build-essential (Linux), Xcode CLI tools (macOS), or Visual Studio Build Tools (Windows). Or use linux_keyring_backend: "dbus-next" to avoid native compilation.
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
Google Pay requires a 'paymentToken' in metadata Missing client-side token Ensure the Google Pay JS API token is passed in metadata.paymentToken
Apple Pay requires a 'paymentToken' in metadata Missing client-side token Ensure the Apple Pay JS API token is passed in metadata.paymentToken
Apple Pay merchant validation failed Invalid cert or domain Verify domain is registered with Apple and applepay_merchant_cert is valid
x402 settlement failed: Facilitator rejected Invalid payment payload or facilitator unreachable Check facilitator_url in config, verify the signed authorization fields (amount, payTo, time bounds)
AP2 mandate has expired Mandate valid_until is in the past Create a new mandate with a future expiry
Mandate already executed (single-use) Attempting to reuse a single-use mandate Create a new mandate for each payment
Invalid X-PAYMENT header Malformed Base64 or JSON in x402 payment header Ensure the X-PAYMENT header is valid Base64-encoded JSON matching X402PaymentPayload
Unsupported payment method type: X AP2 payment method not implemented Use one of: stripe, paypal, card, crypto

Debugging

  1. Set log level to debug in config/default.yaml:

    logging:
      level: "debug"
  2. Check the audit log for full context:

    agent-payments audit --limit 20
  3. Inspect a specific transaction:

    agent-payments tx <transaction-id>
  4. 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;"

License

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

About

πŸ€–πŸ’΅ Agentic Payment Service for Open Agent Skills Ecosystem.

Topics

Resources

License

Stars

Watchers

Forks

Packages