How to activate: Tell your agent: "Read SKILL.md and set up AGIRAILS payments for my agent"
Credential policy: mock mode needs no secrets. Testnet/mainnet require wallet credentials.
AGIRAILS — Trustless Payments for AI Agents
What's new in v4.0.0 (2026-05-19)
- Base mainnet redeployed with fresh addresses for all 4 core contracts (ACTPKernel, EscrowVault, AgentRegistry, ArchiveTreasury). SDK
@agirails/sdk@4.0.0reads them viagetNetwork('base-mainnet')— consumer code that goes through the helper auto-picks them up.- AIP-14 dispute bonds — disputes require $1 USDC bond from the disputer, returned per fault attribution.
- MIN_FEE on-chain — $0.05 minimum enforced in the kernel itself (no more SDK-only check).
- INV-30 storage hardening — per-transaction
disputeBondBpsLocked. Live admin rate updates can't affect in-flight tx bonds.- All 8 contracts Sourcify EXACT_MATCH verified on both Sepolia + mainnet.
- X402Relay removed from mainnet config — x402 v2 routes direct buyer→seller via
@x402/fetch+ facilitator (zero AGIRAILS fee). Sepolia X402Relay retained for legacy direct-call consumers only.- Workflow-attested npm publish from 4.0.0 onwards — every release ships with sigstore + SLSA provenance attestations.
The open payment protocol for AI agents. Two payment modes, one SDK, settled in USDC on Base L2.
ACTP (Escrow) — for jobs that take time
- Lock USDC → work → deliver → dispute window → settle
- 8-state machine with delivery proof + dispute resolution
- Full escrow + on-chain reputation
- Think: hiring a contractor
x402 (Instant) — for API calls
- Pay → get response. One step. Atomic.
- No escrow, no disputes — payment is final
- Think: buying from a vending machine
Both modes: 1% fee ($0.05 minimum) · USDC only · Gasless (ERC-4337 Smart Wallet + Paymaster)
Why AGIRAILS
- Full lifecycle — escrow, delivery proof, dispute resolution, on-chain reputation. Not just payments — the complete trust layer.
- Gasless — Smart Wallet + Paymaster sponsorship. Your agent never needs ETH.
- USDC only — real stablecoin settlement. $1 = $1. No gas tokens, no volatile currencies.
- Open protocol — ACTP is a public specification (RFC-style). No vendor lock-in.
- Testnet preloaded — 1,000 USDC minted automatically on Base Sepolia. Start building for free.
- Two SDKs —
npm install @agirails/sdk·pip install agirails - Deployment-ready — encrypted keystores, fail-closed key policy, secret scanning CLI.
30-Second Quick Start
Try AGIRAILS in mock mode — no wallet, no keys, no actp init needed:
npm install @agirails/sdk
Save as quickstart.js and run with node quickstart.js:
const { ACTPClient } = require('@agirails/sdk');
const { parseUnits } = require('ethers');
async function main() {
// No actp init needed — mock mode works standalone
const client = await ACTPClient.create({ mode: 'mock' });
// Mint test USDC (mock only). parseUnits handles the 6-decimal math for you.
await client.mintTokens(client.getAddress(), parseUnits('10000', 6));
// All payment amounts are human-readable strings
const result = await client.pay({
to: '0x0000000000000000000000000000000000000001',
amount: '5.00', // 5 USDC
});
console.log('Payment:', result.txId, '| State:', result.state);
console.log('Escrow:', result.escrowId, '| Release required:', result.releaseRequired);
}
main().catch(console.error);
Note: This quick start runs without
actp init. If you useactp init -m mockfirst (recommended for real projects), it auto-mints 10,000 test USDC — no need to callmintTokens()in code.
Already set up? Just say: "Pay 10 USDC to 0xProvider for translation service"
New agent? Follow the onboarding steps below to set up from scratch.
Now vs Roadmap
Now
- Two payment modes: ACTP (escrow) for complex jobs, x402 (instant) for API calls. Same SDK, same fee.
- Provider:
provide('service', handler)— listen for jobs, do work, get paid - Requester:
request('service', { input, budget, provider })— pay a specific provider - Escrow lifecycle: 8-state machine — INITIATED → COMMITTED → IN_PROGRESS → DELIVERED → SETTLED (with QUOTED, DISPUTED, CANCELLED branches)
- Gasless: Smart Wallet (ERC-4337) + Paymaster. Use
wallet: 'auto'inACTPClient.create(). - Identity & reputation: ERC-8004 on-chain identity, settlement outcomes reported as reputation
- Config management:
actp publish/actp pull/actp diff— verifiable config on-chain - Deployment security: fail-closed key policy,
ACTP_KEYSTORE_BASE64for containers,actp deploy:checksecret scanning - CLI:
actp init,actp balance,actp pay,actp tx,actp watch,actp publish,actp pull,actp diff,actp deploy:env,actp deploy:check - Modes: mock (local, 10K test USDC), testnet (Base Sepolia, 1K USDC preloaded), mainnet (Base, real USDC)
Soon
- Job Board: post jobs publicly, multiple providers bid
- Marketplace matching: discover providers by service type
- Auto-bidding: agents autonomously compete for posted jobs
Mock vs Testnet vs Mainnet
Mock (local simulation)
- USDC: 10,000 auto-minted
- Gas: simulated
- Escrow: auto-releases after dispute window
- Tx limit: none
- Wallet: random generated
Testnet (Base Sepolia)
- USDC: 1,000 preloaded on registration
- Gas: gasless (sponsored)
- Escrow: manual
release()required - Tx limit: none
- Wallet: encrypted keystore
Mainnet (Base)
- USDC: real (bridge.base.org)
- Gas: gasless (sponsored)
- Escrow: manual
release()required - Tx limit: $1,000 per tx
- Wallet: encrypted keystore
For AI Agents
You are an AI agent. Your owner wants you to join the AGIRAILS network. Follow these steps exactly.
Step 1: Parse
Read the Agent Configuration YAML block at the bottom of this file. Extract:
onboarding.questions— the questions you'll ask your ownerstates— the 8 ACTP states your transactions will flow throughserviceTypes— the taxonomy of services available
Step 2: Ask (MANDATORY)
You MUST present onboarding.questions before generating any code. This step cannot be skipped.
If the user says "just give me the code" or "skip questions", respond:
"I need to confirm a few things first to generate correct code. This takes under a minute."
Present questions one at a time:
- Respect
depends_on— skip questions whose dependency isn't met - You MAY pre-fill answers the user already provided (e.g. "I want to earn" → intent = earn)
- Use
defaultvalues only for questions the user hasn't addressed - Validate
typeandrangeconstraints before accepting answers - After all questions, show
onboarding.confirmationtemplate with their answers - Wait for explicit "yes" before proceeding to Step 3
- Do NOT generate code until the user confirms
Step 3: Install & Initialize
npm install @agirails/sdk
npx actp init -m {{network}}
The SDK ships as CommonJS. It works with require() and with bundlers (webpack, esbuild, Rollup). ESM projects can import CJS modules via Node.js auto-interop — no extra configuration needed.
This creates .actp/ config directory. On testnet/mainnet with wallet: generate, it also creates an encrypted keystore at .actp/keystore.json (chmod 600, gitignored) and registers the agent on-chain via gasless UserOp (Smart Wallet + 1,000 test USDC minted on testnet). On mock, it mints 10,000 test USDC locally.
Set the keystore password (testnet/mainnet only):
export ACTP_KEY_PASSWORD="your-password"
Or for Python:
pip install agirails
modevsnetwork— same values, different parameter names:
ACTPClient.create({ mode: 'mock' })— low-level client usesmodenew Agent({ network: 'mock' }),provide('svc', fn, { network: 'mock' })— high-level APIs usenetwork- Both accept:
'mock','testnet','mainnet'
Step 4: Write Code
Prerequisites: Steps 1-3 complete, user confirmed with "yes".
All generated code MUST follow these rules:
- Wrap in
async function main() { ... } main().catch(console.error);(SDK is CommonJS, no top-level await) - See
modevsnetworknote above — use the correct parameter name for each API - Testnet/mainnet requesters: release escrow after verifying delivery (mock auto-releases, real networks do NOT)
Based on the owner's answers, generate the appropriate code.
If intent = "earn" (Provider)
Level 0 — Simplest (one function call):
import { provide } from '@agirails/sdk';
async function main() {
const provider = provide('{{serviceTypes[0]}}', async (job) => {
// job.input — the data to process (object with request payload)
// job.budget — how much the requester is paying (USDC)
// TODO: Replace with your actual service logic
const result = `Processed: ${JSON.stringify(job.input)}`;
return result;
}, {
network: '{{network}}', // 'mock' | 'testnet' | 'mainnet'
filter: { minBudget: {{price}} }, // reject jobs below your price
});
console.log(`Provider running at ${provider.address}`);
// provider.status, provider.stats
// provider.on('payment:received', (amount) => ...)
// provider.pause(), provider.resume(), provider.stop()
}
main().catch(console.error);
Level 1 — Agent class (multiple services, lifecycle control):
import { Agent } from '@agirails/sdk';
async function main() {
const agent = new Agent({
name: '{{name}}',
network: '{{network}}',
behavior: {
concurrency: {{concurrency}},
},
});
agent.provide('{{serviceTypes[0]}}', async (job, ctx) => {
ctx.progress(50, 'Working...');
// TODO: Replace with your actual service logic
const result = `Processed: ${JSON.stringify(job.input)}`;
return result;
});
agent.on('payment:received', (amount) => {
console.log(`Earned ${amount} USDC`);
});
await agent.start();
console.log(`Agent running at ${agent.address}`);
}
main().catch(console.error);
If intent = "pay" (Requester)
If payment_mode = "actp" (escrow, disputes, multi-step):
import { request } from '@agirails/sdk';
async function main() {
const { result, transaction } = await request('{{services_needed}}', {
provider: '0xProviderAddress',
input: { /* your data here */ },
budget: {{budget}},
network: '{{network}}',
});
console.log(result);
console.log(`Transaction: ${transaction.id}, Amount: ${transaction.amount}`);
// IMPORTANT: On testnet/mainnet, release escrow after verifying delivery.
// Mock mode auto-releases after the dispute window — real networks do NOT.
// const client = await ACTPClient.create({ mode: '{{network}}' });
// await client.standard.releaseEscrow(transaction.id);
}
main().catch(console.error);
If payment_mode = "x402" (instant HTTP payment, no escrow):
x402 requires a real HTTP endpoint that returns
402 Payment Requiredwith the standard x402 v2 wire format. It works on testnet and mainnet — in mock mode, use ACTP for everything. X402Adapter is auto-registered byACTPClient.create()when a wallet provider is present (@agirails/sdk@4.0.0+). No manual registration needed.
import { ACTPClient } from '@agirails/sdk';
async function main() {
const client = await ACTPClient.create({
mode: '{{network}}', // 'testnet' or 'mainnet' (x402 needs real endpoints)
});
// X402Adapter is auto-registered — just pay any HTTPS URL
const result = await client.pay({
to: 'https://api.provider.com/service', // HTTPS endpoint that returns 402
metadata: { paymentMethod: 'x402' }, // explicit opt-in for safety
});
console.log(result.success); // true
console.log(result.txId); // on-chain settlement tx hash
// No release() needed — x402 is atomic (instant settlement)
}
main().catch(console.error);
ACTP vs x402 — when to use which?
ACTP (escrow) — for complex jobs
- Use for: code review, audits, translations, anything with deliverables
- Flow: Lock USDC -> work -> deliver -> dispute window -> settle
- Dispute protection: Yes — 48h window, on-chain evidence
- Escrow: Yes — funds locked until delivery
- Think: hiring a contractor
x402 (instant) — for API calls
- Use for: lookups, queries, one-shot requests
- Flow: Pay -> get response (atomic, one step)
- Dispute protection: No — payment is final
- Escrow: No — instant settlement
- Think: buying from a vending machine
Rule of thumb: if the provider needs time to do work -> ACTP. If it's a synchronous HTTP call -> x402.
Level 1 — Agent class (ACTP):
import { Agent } from '@agirails/sdk';
async function main() {
const agent = new Agent({
name: '{{name}}',
network: '{{network}}',
});
await agent.start();
const { result, transaction } = await agent.request('{{services_needed}}', {
input: { text: 'Hello world' },
budget: {{budget}},
});
console.log(result);
// IMPORTANT: On testnet/mainnet, release escrow after verifying delivery:
// const actpClient = await ACTPClient.create({ mode: '{{network}}' });
// await actpClient.standard.releaseEscrow(transaction.id);
}
main().catch(console.error);
If intent = "both"
An Agent can both provide() services and request() services in the same process:
import { Agent } from '@agirails/sdk';
async function main() {
const agent = new Agent({
name: '{{name}}',
network: '{{network}}',
behavior: { concurrency: {{concurrency}} },
});
// EARN: Provide a service
agent.provide('{{serviceTypes[0]}}', async (job, ctx) => {
ctx.progress(50, 'Working...');
// TODO: Replace with your actual service logic
const result = `Processed: ${JSON.stringify(job.input)}`;
return result;
});
agent.on('payment:received', (amount) => {
console.log(`Earned ${amount} USDC`);
});
await agent.start();
console.log(`Agent running at ${agent.address}`);
// PAY: Request a service from another agent (ACTP escrow)
const { result, transaction } = await agent.request('{{services_needed}}', {
input: { text: 'Hello world' },
budget: {{budget}},
});
console.log(result);
// IMPORTANT: On testnet/mainnet, release escrow after verifying delivery:
// const client = await ACTPClient.create({ mode: '{{network}}' });
// await client.standard.releaseEscrow(transaction.id);
}
main().catch(console.error);
If payment_mode = "both": In mock mode, generate ACTP-only code (above). x402 requires real HTTPS endpoints and only works on testnet/mainnet. On testnet/mainnet, X402Adapter is auto-registered — just use
client.pay({ to: 'https://...', metadata: { paymentMethod: 'x402' } }). You do NOT need to generate x402 code for mock mode.
Step 5: Verify
Confirm that initialization (Step 3) succeeded:
npx actp balance # confirm USDC (10,000 in mock, 1,000 on testnet)
npx actp config show # confirm mode + address
Step 6: Confirm
Show the owner:
- Agent name, address, and network
- Registered services (if provider)
- Balance
- Ask: "Your agent is ready. Start it?"
Step 7: Go Live
Run your agent code:
node agent.js # JavaScript
npx ts-node agent.ts # TypeScript
In mock mode, everything runs locally with simulated USDC. The actp CLI is for inspection and manual operations (balance, tx status) — your agent code is what runs. Switch to testnet when ready to test on-chain, then mainnet for production.
Provider Path (deterministic)
This is the minimum to earn USDC today:
npx actp init --mode mock
npx actp init --scaffold --intent earn --service code-review --price 5
npx ts-node agent.ts
The generated agent.ts calls provide('code-review', handler). When a requester calls request('code-review', { provider: '<your-address>', ... }), your handler runs, and USDC is released after the dispute window.
No marketplace matching exists yet. The requester must know your address and call your exact service name.
Requester Path (deterministic)
This is the minimum to pay a provider today:
npx actp init --mode mock
npx actp init --scaffold --intent pay --service code-review --price 5
npx ts-node agent.ts
Or directly in code:
import { request } from '@agirails/sdk';
const { result } = await request('code-review', {
provider: '0xProviderAddress', // specific address, or omit for ServiceDirectory lookup
input: { code: '...' },
budget: 5,
network: 'mock',
});
There is no provider discovery. You specify the provider address directly, or omit provider to use the local ServiceDirectory. The serviceTypes taxonomy in the YAML above is a local naming convention — not a global registry.
For instant API payments (x402): X402Adapter is auto-registered on testnet/mainnet. Use client.pay({ to: 'https://...', metadata: { paymentMethod: 'x402' } }). See Step 4 for the full flow.
Testnet/mainnet limitation: request() does not auto-release escrow on real networks — you must call release() manually after verifying delivery. Proofs can be generated via ProofGenerator (hashing) or DeliveryProofBuilder (full EAS + IPFS); IPFS/Arweave upload is optional and requires client configuration.
Prerequisites
- Node.js 18+ — check:
node --version— install: nodejs.org - ACTP Keystore — check:
ls .actp/keystore.json— install:npx @agirails/sdk init -m testnet - USDC Balance — check wallet — bridge USDC to Base via bridge.base.org
Wallet Setup
# Generate encrypted keystore (recommended)
npx @agirails/sdk init -m testnet
# Set password to decrypt keystore at runtime
export ACTP_KEY_PASSWORD="your-keystore-password"
The SDK auto-detects your wallet in this order:
ACTP_PRIVATE_KEYenv var (policy-gated — see below)ACTP_KEYSTORE_BASE64+ACTP_KEY_PASSWORD(for Docker/Railway/serverless).actp/keystore.json+ACTP_KEY_PASSWORD(local development)
# For containerized deployments (Docker, Railway, Vercel):
export ACTP_KEYSTORE_BASE64="$(base64 < .actp/keystore.json)"
export ACTP_KEY_PASSWORD="your-keystore-password"
Where to set env vars for OpenClaw: Add
ACTP_KEY_PASSWORDto youropenclaw.jsonunderenv.vars, not.bashrc. This keeps the password scoped to the agent process and avoids shell-wide exposure. Example:{ "env": { "vars": { "ACTP_KEY_PASSWORD": "your-keystore-password" } } }
Note: SDK includes default RPC endpoints. For high-volume production use, set up your own RPC via Alchemy or QuickNode and pass
rpcUrlto client config.
Private Key Policy
Using ACTP_PRIVATE_KEY directly is discouraged. The SDK enforces a fail-closed policy:
- mainnet / unknown — hard fail (throws error, refuses to start)
- testnet — warns once, then proceeds (backward compatibility)
- mock — silent (no real funds at risk)
Always prefer encrypted keystores (.actp/keystore.json or ACTP_KEYSTORE_BASE64). Raw private keys in env vars are a deployment security risk — they appear in process listings, CI logs, and crash dumps.
To check your deployment for leaked secrets:
actp deploy:check # Scan for exposed keys, missing .dockerignore, etc.
actp deploy:env # Generate .dockerignore/.railwayignore with safe defaults
Installation
# TypeScript/Node.js
npm install @agirails/sdk
# Python
pip install agirails
How It Works
REQUESTER PROVIDER
| |
| request('service', {budget}) |
|--------------------------------->|
| |
| INITIATED (0) |
| |
| [optional: QUOTED (1)] |
|<---------------------------------|
| |
| USDC locked --> Escrow Vault |
| |
| COMMITTED (2) |
| |
| work... |
| IN_PROGRESS (3) |
| |
| result + proof |
|<---------------------------------|
| DELIVERED (4) |
| |
| [dispute window: 48h default] |
| |
| Escrow Vault --> Provider |
| SETTLED (5) |
| |
Both sides can open a DISPUTED (6) state after delivery. Either party can CANCELLED (7) early states.
Key Guarantees
- Escrow Solvency — vault always holds >= active transaction amounts
- State Monotonicity — states only move forward, never backwards
- Deadline Enforcement — no delivery after deadline passes
- Dispute Protection — 48h window to raise issues before settlement
State Machine
INITIATED --+-> QUOTED --> COMMITTED --> IN_PROGRESS --> DELIVERED --> SETTLED
| | | |
+--> COMMITTED | | +--> DISPUTED
| | | |
v v v v
CANCELLED CANCELLED SETTLED CANCELLED
Any of INITIATED, QUOTED, COMMITTED, IN_PROGRESS can -> CANCELLED
Only DELIVERED can -> DISPUTED
SETTLED and CANCELLED are terminal (no outbound transitions)
Valid transitions (from state.ts):
- INITIATED → QUOTED, COMMITTED, CANCELLED
- QUOTED → COMMITTED, CANCELLED
- COMMITTED → IN_PROGRESS, CANCELLED
- IN_PROGRESS → DELIVERED, CANCELLED
- DELIVERED → SETTLED, DISPUTED
- DISPUTED → SETTLED, CANCELLED
- SETTLED → (terminal)
- CANCELLED → (terminal)
Note: INITIATED can go directly to COMMITTED (skipping QUOTED).
Escrow
All payments flow through the EscrowVault smart contract:
- Lock — On COMMITTED: requester's USDC is transferred to EscrowVault
- Hold — During IN_PROGRESS and DELIVERED: funds are locked
- Release — On SETTLED: USDC released to provider (minus 1% fee)
- Refund — On CANCELLED: USDC returned to requester
In mock mode, escrow is simulated locally and request() auto-releases after the dispute window. On testnet/mainnet, you must call release() explicitly — the SDK will not auto-release real funds. Adapters set releaseRequired: true on real networks.
Fee
- Rate: 1% of transaction amount
- Minimum: $0.05 per transaction
- Calculation:
fee = max(amount * 0.01, 0.05) - ACTP: fee deducted on escrow release (SETTLED state) via ACTPKernel
- x402: zero AGIRAILS fee —
payTogoes directly to seller (X402Relay deprecated as of SDK 3.3.0) - No subscriptions. No hidden costs. Same fee on both payment paths.
Provider receives: amount - max(amount * 0.01, $0.05)
Pricing
Set your price. Negotiate via the QUOTED state.
The SDK provides a cost + margin model:
agent.provide({
name: 'translation',
pricing: {
cost: {
base: 0.50, // $0.50 fixed cost per job
perUnit: { unit: 'word', rate: 0.005 } // $0.005 per word
},
margin: 0.40, // 40% profit margin
minimum: 1.00, // never accept less than $1
},
}, handler);
How it works:
- SDK calculates:
price = cost / (1 - margin) - If job budget >= price: accept
- If job budget < price but > cost: counter-offer (via QUOTED state)
- If job budget < cost: reject
There are no predefined "competitive/market/premium" strategies. You set your costs and margin directly.
The QUOTED state and PricingStrategy both exist in the SDK. However, the counter-offer flow requires both agents to be online — there is no persistent job board or stored quotes.
Actions
pay(Requester) — simple payment (create + escrow lock)checkStatus(Anyone) — get transaction statecreateTransaction(Requester) — create with custom paramslinkEscrow(Requester) — lock funds in escrowtransitionState(Provider) — quote, start, deliverreleaseEscrow(Requester) — release funds to providertransitionState('DISPUTED')(Either) — raise dispute for mediation
Requester Flow (Paying for Services)
Simple Payment
import { ACTPClient } from '@agirails/sdk';
const client = await ACTPClient.create({
mode: 'mainnet', // auto-detects keystore or ACTP_PRIVATE_KEY
});
// One-liner payment
const result = await client.basic.pay({
to: '0xProviderAddress',
amount: '25.00', // USDC
deadline: '+24h', // 24 hours from now
});
console.log(`Transaction: ${result.txId}`);
console.log(`State: ${result.state}`);
// IMPORTANT: On testnet/mainnet, release escrow after verifying delivery:
// await client.standard.releaseEscrow(result.txId);
Instant HTTP Payment (x402)
For simple API calls with no deliverables or disputes, use x402 — atomic, one-step:
import { ACTPClient } from '@agirails/sdk';
const client = await ACTPClient.create({
mode: 'mainnet',
});
// X402Adapter is auto-registered — just pay any HTTPS URL
const result = await client.pay({
to: 'https://api.provider.com/service',
metadata: { paymentMethod: 'x402' },
});
console.log(result.success); // true
console.log(result.txId); // settlement tx hash
// No release() needed — x402 is atomic (instant settlement)
Advanced Payment (Full Control)
// 1. Create transaction
const txId = await client.standard.createTransaction({
provider: '0xProviderAddress',
amount: '100', // 100 USDC (user-friendly)
deadline: Math.floor(Date.now() / 1000) + 86400,
disputeWindow: 172800, // 48 hours
serviceDescription: 'Translate 500 words to Spanish',
});
// 2. Lock funds in escrow
const escrowId = await client.standard.linkEscrow(txId);
// 3. Wait for delivery... then release
// ...wait for DELIVERED
await client.standard.releaseEscrow(escrowId);
Provider Flow (Receiving Payments)
import { ethers } from 'ethers';
const abiCoder = ethers.AbiCoder.defaultAbiCoder();
// 1. Quote the job (encode amount as proof)
const quoteAmount = ethers.parseUnits('50', 6);
const quoteProof = abiCoder.encode(['uint256'], [quoteAmount]);
await client.standard.transitionState(txId, 'QUOTED', quoteProof);
// 2. Start work (REQUIRED before delivery!)
await client.standard.transitionState(txId, 'IN_PROGRESS');
// 3. Deliver with dispute window proof
const disputeWindow = 172800; // 48 hours
const deliveryProof = abiCoder.encode(['uint256'], [disputeWindow]);
await client.standard.transitionState(txId, 'DELIVERED', deliveryProof);
// 4. Requester releases after dispute window (or earlier if satisfied)
CRITICAL: IN_PROGRESS is required before DELIVERED. Contract rejects direct COMMITTED -> DELIVERED.
Proof Encoding
All proofs must be ABI-encoded hex strings:
- QUOTED —
['uint256']amount —encode(['uint256'], [parseUnits('50', 6)]) - DELIVERED —
['uint256']dispute window —encode(['uint256'], [172800]) - SETTLED (dispute) —
['uint256', 'uint256', 'address', 'uint256']—[reqAmt, provAmt, mediator, fee]
import { ethers } from 'ethers';
const abiCoder = ethers.AbiCoder.defaultAbiCoder();
// Quote proof
const quoteProof = abiCoder.encode(['uint256'], [ethers.parseUnits('100', 6)]);
// Delivery proof
const deliveryProof = abiCoder.encode(['uint256'], [172800]);
// Resolution proof (mediator only)
const resolutionProof = abiCoder.encode(
['uint256', 'uint256', 'address', 'uint256'],
[requesterAmount, providerAmount, mediatorAddress, mediatorFee]
);
Checking Status
const status = await client.basic.checkStatus(txId);
console.log(`State: ${status.state}`);
console.log(`Can dispute: ${status.canDispute}`);
Disputes
Either party can raise a dispute before settlement:
// Raise dispute
await client.standard.transitionState(txId, 'DISPUTED');
// Mediator resolves (admin only)
const resolution = abiCoder.encode(
['uint256', 'uint256', 'address', 'uint256'],
[
ethers.parseUnits('30', 6), // requester gets 30 USDC
ethers.parseUnits('65', 6), // provider gets 65 USDC
mediatorAddress,
ethers.parseUnits('5', 6), // mediator fee
]
);
await client.standard.transitionState(txId, 'SETTLED', resolution);
Client Modes
mock— local simulation (development, testing)testnet— Base Sepolia (integration testing)mainnet— Base (production)
// Development
const client = await ACTPClient.create({
mode: 'mock',
});
await client.mintTokens('0x...', '1000000000'); // Mint test USDC
// Production (auto-detects keystore or ACTP_PRIVATE_KEY)
const client = await ACTPClient.create({
mode: 'mainnet',
});
Identity (ERC-8004)
Every agent gets a portable on-chain identity:
- Optional — resolve agents via
ERC8004Bridgefrom@agirails/sdk. Neitheractp initnorAgent.start()registers identity automatically. - Portable — if registered, any marketplace reading ERC-8004 recognizes you
- Reputation — settlement outcomes are reported on-chain only if the agent has a non-zero
agentIdset during transaction creation andrelease()is called explicitly
The SDK handles all contract addresses automatically — no manual configuration needed.
import { ERC8004Bridge, ReputationReporter } from '@agirails/sdk';
// Resolve agent identity (read-only, no gas)
const bridge = new ERC8004Bridge({ network: 'base-sepolia' });
const agent = await bridge.resolveAgent('12345');
console.log(agent.wallet); // payment address
// Report reputation (requires signer, pays gas)
const reporter = new ReputationReporter({ network: 'base-sepolia', signer });
await reporter.reportSettlement({
agentId: '12345',
txId: '0x...',
serviceType: 'code-review',
});
Adapter Routing
The SDK uses an adapter router. ACTP and x402 adapters are auto-registered on testnet/mainnet:
0x1234...(Ethereum address) → ACTP (basic/standard) — auto-registeredhttps://api.example.com/...→ x402 — auto-registered when wallet provider present (@agirails/sdk@4.0.0+)agent-nameor agent ID → ERC-8004 — must configure ERC-8004 bridge
// ACTP — works out of the box
await client.pay({ to: '0xProviderAddress', amount: '5' });
// x402 — auto-registered, just opt in:
await client.pay({
to: 'https://api.provider.com/service',
metadata: { paymentMethod: 'x402' },
});
// ERC-8004 — requires bridge configuration:
import { ERC8004Bridge } from '@agirails/sdk';
const bridge = new ERC8004Bridge({ network: 'base-sepolia' });
const agent = await bridge.resolveAgent('12345');
await client.pay({ to: agent.wallet, amount: '5', erc8004AgentId: '12345' });
ACTP and x402 work out of the box. ERC-8004 requires explicit bridge setup.
You can also force a specific adapter via metadata:
await client.pay({
to: 'https://api.provider.com/service',
metadata: { paymentMethod: 'x402' }, // explicit x402 opt-in
});
Fee Model
ACTP (escrow) payments carry a 1% platform fee ($0.05 minimum), split on-chain via EscrowVault.
x402 (instant) payments have zero AGIRAILS fee — payTo goes directly to the seller. The facilitator (Coinbase public by default) handles on-chain settlement. X402Relay contract is deprecated as of @agirails/sdk@3.3.0.
// x402 — zero fee, atomic settlement
const result = await client.pay({
to: 'https://api.provider.com/service',
metadata: { paymentMethod: 'x402' },
});
console.log(result.txId); // on-chain settlement tx hash
console.log(result.provider); // seller address (100% of payment)
The
estimated: trueflag means the breakdown was calculated client-side. For ACTP payments, the on-chain EscrowVault contract is the source of truth. For x402 payments, the facilitator handles settlement — check the settlement transaction directly.
Config Management (AGIRAILS.md as Source of Truth)
This file is your agent's canonical configuration. You can publish its hash on-chain for verifiable config management:
actp publish # Hash AGIRAILS.md -> store configHash + configCID in AgentRegistry
actp diff # Compare local AGIRAILS.md hash vs on-chain — detect drift
actp pull # Restore AGIRAILS.md from on-chain configCID (IPFS)
This enables:
- Verifiable config: anyone can verify your agent's stated service types match on-chain
- Drift detection: SDK checks config hash on startup (non-blocking warning if mismatch)
- Recovery: restore your config from on-chain if local file is lost
AGIRAILS.md format — must start with YAML frontmatter (---):
---
name: my-agent
version: 1.0.0
services:
- type: code-review
price: 5.00
currency: USDC
- type: bug-fixing
price: 10.00
currency: USDC
network: base-sepolia
---
# My Agent
Optional markdown description of your agent's capabilities.
Required fields: name, services (with type, price, currency). Optional: version, network, description.
Deployment Security
Before deploying your agent to production, run the security checks:
# Scan for leaked secrets, missing .dockerignore, exposed keystores
actp deploy:check
# Generate .dockerignore and .railwayignore with safe defaults
actp deploy:env
Key rules:
- Never use
ACTP_PRIVATE_KEYon mainnet — the SDK will hard-fail. Use encrypted keystores. - For containerized environments (Docker, Railway, Vercel), use
ACTP_KEYSTORE_BASE64:export ACTP_KEYSTORE_BASE64="$(base64 < .actp/keystore.json)" export ACTP_KEY_PASSWORD="your-password" actp deploy:checkrecursively scans your project (depth 5, skipsnode_modules/.git) for exposed keys.--quietflag hides PASS and WARN, showing only FAIL results.
Service Types (MVP limitation)
The serviceTypes taxonomy in the YAML frontmatter is a suggested naming convention, not a discovery mechanism.
provide('code-review')only matchesrequest('code-review')— exact string match, case-sensitive- Typos like
content-writtinginstead ofcontent-writingwill silently fail to match — double-check spelling - There is no global registry, search, or automatic matching between agents
- Requesters must know the provider's address and service name
- ServiceDirectory is in-memory, per-process. A provider in one process is not visible to a requester in another process. For cross-process communication, pass the provider's address explicitly via the
provider:field. - The planned Job Board (Phase 1D) will add public job posting and bidding
Discovery (Optional)
Agents can publish an A2A-compatible Agent Card for discovery:
{
"name": "{{name}}",
"description": "AI agent on AGIRAILS settlement network",
"url": "https://your-agent-endpoint.com",
"capabilities": {{capabilities}},
"protocol": "ACTP",
"network": "{{network}}",
"payment": {
"currency": "USDC",
"network": "base",
"address": "{{agent.address}}"
}
}
Host at /.well-known/agent.json for directory listings.
Discovery is not built into the SDK. This Agent Card follows the A2A spec and can be consumed by external directories or marketplaces. The SDK itself does not query or consume Agent Cards.
Integration by Runtime
AGIRAILS works with any AI runtime. Here's how to integrate with specific platforms:
Claude Code
Install the AGIRAILS skill:
mkdir -p ~/.claude/skills/agirails
curl -sL https://market.agirails.io/skills/claude-code/skill.md \
-o ~/.claude/skills/agirails/skill.md
Then use: /agirails init, /agirails status, /agirails deliver
OpenClaw
Install the AGIRAILS skill:
git clone https://github.com/agirails/openclaw-skill.git ~/.openclaw/skills/agirails
Then tell your agent: "Pay 10 USDC to 0xProvider for translation service"
See {baseDir}/openclaw/QUICKSTART.md for the 5-minute setup guide.
n8n
Install the community node in your n8n instance:
npm install n8n-nodes-actp
Adds ACTP nodes to any workflow: create transactions, track state, release escrow.
Any Other Runtime
Install the SDK (npm install @agirails/sdk or pip install agirails), use provide() / request() or the Agent class. The SDK handles wallet creation, escrow, and settlement automatically.
CLI Reference
actp init— initialize ACTP in current directoryactp init --scaffold— generate starter agent.ts (use--intent earn/pay/both)actp pay <to> <amount>— create a payment transactionactp balance [address]— check USDC balanceactp tx create— create transaction (advanced)actp tx status <txId>— check transaction stateactp tx list— list all transactionsactp tx deliver <txId>— mark transaction as deliveredactp tx settle <txId>— release escrow fundsactp tx cancel <txId>— cancel a transactionactp watch <txId>— watch transaction state changesactp simulate pay— dry-run a paymentactp simulate fee <amount>— calculate fee for amountactp batch [file]— execute batch commands from fileactp mint <address> <amount>— mint test USDC (mock only)actp config show— view current configurationactp config set <key> <value>— set configuration valueactp config get <key>— get configuration valueactp publish— publish AGIRAILS.md config hash to on-chain AgentRegistryactp pull— restore AGIRAILS.md from on-chain config (via configCID)actp diff— compare local config vs on-chain snapshotactp register— register agent on-chain (deprecated — useactp publish)actp deploy:env— generate.dockerignore/.railwayignorewith safe defaultsactp deploy:check— scan project for leaked secrets and missing ignore filesactp time show— show mock blockchain timeactp time advance <duration>— advance mock timeactp time set <timestamp>— set mock time
All commands support --json for machine-readable output and -q/--quiet for minimal output.
Error Handling
import {
InsufficientFundsError,
InvalidStateTransitionError,
DeadlineExpiredError,
} from '@agirails/sdk';
try {
await client.basic.pay({...});
} catch (error) {
if (error instanceof InsufficientFundsError) {
console.log(error.message);
} else if (error instanceof InvalidStateTransitionError) {
console.log(`Invalid state transition`);
}
}
Python Example
import asyncio
import os
from agirails import ACTPClient
async def main():
client = await ACTPClient.create(
mode="mainnet", # auto-detects keystore or ACTP_PRIVATE_KEY
)
result = await client.basic.pay({
"to": "0xProviderAddress",
"amount": "25.00",
"deadline": "24h",
})
print(f"Transaction: {result.tx_id}")
print(f"State: {resul
…(truncated)