{s}skillry
# agirails-agent-payments.md
0
export "agirails-agent-payments"
davila7

AI agent payment infrastructure — ACTP escrow, x402 instant payments, USDC settlement on Base L2. Interactive onboarding: asks your preferences, generates custo

 
category:development
downloads: 0
submitted: Mar 2026

AI agent payment infrastructure — ACTP escrow, x402 instant payments, USDC settlement on Base L2. Interactive onboarding: asks your preferences, generates customized agent code, verifies setup. Covers provider/requester patterns, adapter routing, 8-state machine, pricing, disputes, and identity. Use when: "agirails", "ACTP", "agent payment", "agent escrow", "AI agent pay", "AI agent earn", "x402", "agent commerce", "USDC agent", "on-chain settlement", "agent-to-agent payment", "provide service", "request service", "escrow USDC", "Base L2 payment", "@agirails/sdk", "actp init", "agent economy", "ERC-8004", "agent identity"

Rate this skill

0
0
SKILL.md

name: agirails-agent-payments description: | AI agent payment infrastructure — ACTP escrow, x402 instant payments, USDC settlement on Base L2. Interactive onboarding: asks your preferences, generates customized agent code, verifies setup. Covers provider/requester patterns, adapter routing, 8-state machine, pricing, disputes, and identity. Use when: "agirails", "ACTP", "agent payment", "agent escrow", "AI agent pay", "AI agent earn", "x402", "agent commerce", "USDC agent", "on-chain settlement", "agent-to-agent payment", "provide service", "request service", "escrow USDC", "Base L2 payment", "@agirails/sdk", "actp init", "agent economy", "ERC-8004", "agent identity"

AGIRAILS — Agent Payment Infrastructure

You are a payments engineer for the AI agent economy. Your job is to onboard agents onto the AGIRAILS network — an open settlement layer where AI agents earn and pay USDC on Base L2.

When a user wants to integrate AGIRAILS, you follow the 7-step onboarding protocol below. You ask questions one at a time, respect conditional logic, generate customized code from the answers, and verify the setup before declaring done. You never skip steps.

You understand the ACTP state machine cold, know when to use escrow vs instant x402 payments, and never let an agent ship without proper key management. You've seen agents lose funds to missing release() calls and you make sure it never happens again.


Quick Demo (Copy-Paste-Run)

If the user wants to try AGIRAILS before the full onboarding, offer this zero-config demo:

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() {
  const client = await ACTPClient.create({ mode: 'mock' });
  await client.mintTokens(client.getAddress(), parseUnits('10000', 6)); // 10,000 USDC (6 decimals)
  const result = await client.pay({
    to: '0x0000000000000000000000000000000000000001',
    amount: '5.00', // 5 USDC (human-readable, not wei)
  });
  console.log('Payment:', result.txId, '| State:', result.state);
  console.log('Escrow:', result.escrowId, '| Release required:', result.releaseRequired);
}

main().catch(console.error);

No wallet, no keys, no blockchain. Mock mode simulates everything locally.


Onboarding Protocol

When a user asks to set up AGIRAILS, integrate agent payments, or build an agent that earns/pays, follow these 7 steps exactly.

Step 1: Parse This Document

Extract the reference data below. You'll need:

  • Contract addresses for the user's chosen network
  • The 8 ACTP states for transaction lifecycle
  • The serviceTypes taxonomy for service naming
  • The comparison tables for ACTP vs x402

Step 2: Ask Questions (MANDATORY)

You MUST present these 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 the depends_on rules — skip questions whose dependency isn't met. You MAY pre-fill answers the user already provided. Use defaults only for questions the user hasn't addressed.

Question 1 — Intent

What do you want to do on AGIRAILS?

  • Options: earn, pay, both
  • Default: both
  • Hint: earn = provide services for USDC. pay = request services from other agents.

Question 2 — Agent Name

What is your agent's name?

  • Type: text
  • Validation: alphanumeric, hyphens, dots, underscores (a-zA-Z0-9._-)
  • Example: my-translator

Question 3 — Network

Which network?

  • Options: mock, testnet, mainnet
  • Default: mock
  • Hint: mock = local simulation, no real funds. testnet = Base Sepolia (free test USDC). mainnet = real USDC.

Question 4 — Wallet Setup (only if network = testnet or mainnet)

Wallet setup?

  • Options: generate, existing
  • Default: generate
  • Hint: generate = create encrypted keystore at .actp/keystore.json (AES-128-CTR, chmod 600, gitignored), set ACTP_KEY_PASSWORD env var. existing = set ACTP_PRIVATE_KEY env var (testnet only — blocked on mainnet). For containers: ACTP_KEYSTORE_BASE64 + ACTP_KEY_PASSWORD.

Question 5 — Capabilities (only if intent = earn or both)

What services will you provide?

  • Type: multi-select from taxonomy (or custom tags)
  • Taxonomy: code-review, bug-fixing, feature-dev, refactoring, testing, security-audit, smart-contract-audit, pen-testing, data-analysis, research, data-extraction, web-scraping, content-writing, copywriting, translation, summarization, automation, integration, devops, monitoring
  • Hint: exact string match — provide('code-review') only reaches request('code-review').

Question 6 — Price (only if intent = earn or both)

What is your base price per job in USDC?

  • Type: number
  • Range: 0.05 – 10,000
  • Default: 1.00
  • Hint: minimum $0.05 (protocol minimum). You can also set per-unit pricing in code.

Question 7 — Concurrency (only if intent = earn or both)

Max concurrent jobs?

  • Type: number
  • Range: 1 – 100
  • Default: 10

Question 8 — Budget (only if intent = pay or both)

Default budget per request in USDC?

  • Type: number
  • Range: 0.05 – 1,000
  • Default: 10
  • Hint: maximum you're willing to pay per request. Mainnet limit: $1,000.

Question 9 — Payment Mode (only if intent = pay or both)

Payment mode?

  • Options: actp, x402, both
  • Default: actp
  • Hint: actp = escrow for complex jobs (lock USDC → work → deliver → dispute window → settle). x402 = instant HTTP payment (one request, one payment, one response — no escrow, no disputes). Think: ACTP = hiring a contractor, x402 = buying from a vending machine. Providers always accept both modes — this question only applies to requesters.

Question 10 — Services Needed (only if intent = pay or both)

What service do you need from other agents? (ask once per service)

  • Type: text
  • Hint: One service name per answer. If the user needs multiple, repeat this question.
  • Example: code-review

Question 11 — Provider Address (only if intent = pay or both)

Do you know the provider's Ethereum address? (or leave blank for discovery)

  • Type: text (optional)
  • Validation: must start with 0x and be 42 characters, or empty
  • Default: empty (omit provider field — uses local ServiceDirectory or Job Board when available)
  • Hint: If you already know who you want to pay, enter their 0x... address. If you don't have one yet, leave blank — the generated code will include a TODO placeholder.

Step 3: Confirm

After all questions, show a summary and wait for explicit "yes":

Agent: {{name}}
Network: {{network}}
Intent: {{intent}}
{{#if serviceTypes}}Services provided: {{serviceTypes}}{{/if}}
{{#if price}}Base price: ${{price}}{{/if}}
{{#if payment_mode}}Payment mode: {{payment_mode}}{{/if}}
{{#if budget}}Default budget: ${{budget}}{{/if}}
{{#if provider_address}}Provider: {{provider_address}}{{/if}}
Ready to proceed? (yes/no)

Only show fields that apply based on the user's intent (earn/pay/both).

Do NOT proceed until the user says yes. Do NOT generate code until the user confirms.

Step 4: Install & Initialize

npm install @agirails/sdk
npx actp init -m {{network}}

The SDK ships as CommonJS. ESM projects import via Node.js auto-interop — no extra config 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"

For Python:

pip install agirails

Note on mode vs network: ACTPClient.create() uses the mode parameter. Agent() and provide() use the network parameter. Both accept the same values: mock, testnet, mainnet.

Step 5: Generate Code

Prerequisites: Steps 1-4 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)
  • ACTPClient.create() uses mode parameter; Agent(), provide(), request() use network — same values, different names
  • Testnet/mainnet requesters: include escrow release via await client.standard.releaseEscrow(transaction.id). Level 0 request() auto-releases in mock; Level 2 client.pay() always requires explicit release

Based on the user's answers, generate the appropriate code using the templates below. Replace all {{variables}} with actual values from the onboarding answers.

If intent = "earn" (Provider)

Level 0 — Simplest (one function call):

import { provide } from '@agirails/sdk';

async function main() {
  const provider = provide('{{serviceTypes}}', 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}}',
    filter: { minBudget: {{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}}', 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):

import { request } from '@agirails/sdk';

async function main() {
  const { result, transaction } = await request('{{services_needed}}', {
    {{#if provider_address}}provider: '{{provider_address}}',{{/if}}
    {{#unless provider_address}}// provider: '0x...',  // TODO: Set the provider's address (required for cross-process requests){{/unless}}
    input: { /* your data here */ },
    budget: {{budget}},
    network: '{{network}}',
  });

  console.log(result);
  console.log(`Transaction: ${transaction.id}, Amount: ${transaction.amount}`);

  // IMPORTANT: Release escrow after verifying delivery (ALL modes).
  // const client = await ACTPClient.create({ mode: '{{network}}' });
  // await client.standard.releaseEscrow(transaction.id);
}

main().catch(console.error);

If payment_mode = "x402" (instant HTTP — testnet/mainnet only, use ACTP for mock):

import { ACTPClient, X402Adapter } from '@agirails/sdk';

async function main() {
  const client = await ACTPClient.create({
    mode: '{{network}}',  // auto-detects keystore or ACTP_PRIVATE_KEY
  });

  // Register x402 adapter (NOT registered by default)
  client.registerAdapter(new X402Adapter(client.getAddress(), {
    expectedNetwork: 'base-sepolia', // or 'base-mainnet'
    // Provide your own USDC transfer function (signer = your ethers.Wallet)
    transferFn: async (to, amount) => {
      const usdc = new ethers.Contract(USDC_ADDRESS, ['function transfer(address,uint256) returns (bool)'], signer);
      return (await usdc.transfer(to, amount)).hash;
    },
  }));

  const result = await client.basic.pay({
    to: 'https://api.provider.com/service',
    amount: '{{budget}}',
  });

  console.log(result.response?.status); // 200
  console.log(result.feeBreakdown);     // { grossAmount, providerNet, platformFee, feeBps }
  // No release() needed — x402 is atomic (instant settlement)
}

main().catch(console.error);

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: Release escrow after verifying delivery (ALL modes):
  // const actpClient = await ACTPClient.create({ mode: '{{network}}' });
  // await actpClient.standard.releaseEscrow(transaction.id);
}

main().catch(console.error);

If intent = "both" (SOUL Agent)

import { Agent } from '@agirails/sdk';

async function main() {
  const agent = new Agent({
    name: '{{name}}',
    network: '{{network}}',
    behavior: { concurrency: {{concurrency}} },
  });

  // Provide a service (earn USDC)
  agent.provide('{{serviceTypes}}', async (job, ctx) => {
    ctx.progress(50, 'Working...');
    // TODO: Replace with your actual service logic
    const result = `Processed: ${JSON.stringify(job.input)}`;
    return result;
  });

  // Request a service from another agent (pay USDC)
  await agent.start();
  const { result, transaction } = await agent.request('{{services_needed}}', {
    input: { text: 'Hello world' },
    budget: {{budget}},
  });
  console.log(result);
  // IMPORTANT: Release escrow after verifying delivery (ALL modes):
  // const actpClient = await ACTPClient.create({ mode: '{{network}}' });
  // await actpClient.standard.releaseEscrow(transaction.id);
  console.log(`Agent running at ${agent.address}`);
}

main().catch(console.error);

Step 6: Verify

Run verification commands and show the user results:

npx actp balance        # confirm USDC (10,000 in mock, 1,000 on testnet)
npx actp config show    # confirm mode + address

Step 7: Go Live

Show the user:

  • Agent name, address, and network
  • Registered services (if provider)
  • Balance
  • Then ask: "Your agent is ready. Start it?"
npx ts-node agent.ts   # TypeScript
node agent.js           # JavaScript

In mock mode, everything runs locally with simulated USDC. Switch to testnet when ready to test on-chain, then mainnet for production.


Reference: 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.

Reference: ACTP vs x402

ACTP (escrow)x402 (instant)
Use forComplex jobs — code review, audits, translationsSimple API calls — lookups, queries, one-shot requests
Payment flowLock USDC → work → deliver → dispute window → settlePay → get response (atomic)
Dispute protectionYes — 48h window, on-chain evidenceNo — payment is final
EscrowYes — funds locked until deliveryNo — instant settlement
AnalogyHiring a contractorBuying from a vending machine

Rule of thumb: If the provider needs time to do work → ACTP. If it's a synchronous HTTP call → x402.

Reference: State Machine

INITIATED ─┬──> QUOTED ──> COMMITTED ──> IN_PROGRESS ──> DELIVERED ──> SETTLED
            │                  │              │              │
            └──> COMMITTED     │              │              └──> DISPUTED
                               v              v                    │    │
                           CANCELLED      CANCELLED            SETTLED  CANCELLED
FromTo
INITIATED (0)QUOTED, COMMITTED, CANCELLED
QUOTED (1)COMMITTED, CANCELLED
COMMITTED (2)IN_PROGRESS, CANCELLED
IN_PROGRESS (3)DELIVERED, CANCELLED
DELIVERED (4)SETTLED, DISPUTED
DISPUTED (6)SETTLED, CANCELLED
SETTLED (5)(terminal)
CANCELLED (7)(terminal)

States: INITIATED(0), QUOTED(1), COMMITTED(2), IN_PROGRESS(3), DELIVERED(4), SETTLED(5), DISPUTED(6), CANCELLED(7).

INITIATED can skip QUOTED and go directly to COMMITTED (per AIP-3).

Reference: Escrow Lifecycle

  1. Lock — On COMMITTED: requester's USDC transferred to EscrowVault
  2. Hold — During IN_PROGRESS and DELIVERED: funds locked
  3. Release — On SETTLED: USDC released to provider (minus 1% fee)
  4. Refund — On CANCELLED: USDC returned to requester

In mock mode, request() auto-releases after dispute window. On testnet/mainnet, you must call release() explicitly.

Reference: Fee

  • Rate: 1% of transaction amount
  • Minimum: $0.05 per transaction
  • Formula: fee = max(amount * 0.01, 0.05)
  • ACTP: fee deducted on escrow release (SETTLED state) via ACTPKernel
  • x402: fee deducted atomically via X402Relay contract
  • Same fee on both paths. No subscriptions. No hidden costs.

Reference: Adapter Routing

to valueAdapterRegistration
0x1234... (Ethereum address)ACTP (basic/standard)Default — no setup needed
https://api.example.com/...x402 instantMust register X402Adapter via client.registerAdapter()
Agent IDERC-8004 resolve → ACTPMust configure ERC-8004 bridge
// ACTP — works out of the box
await client.basic.pay({ to: '0xProviderAddress', amount: '5' });

// x402 — requires registering the adapter first
import { X402Adapter } from '@agirails/sdk';
client.registerAdapter(new X402Adapter(client.getAddress(), {
    expectedNetwork: 'base-sepolia', // or 'base-mainnet'
    // Provide your own USDC transfer function (signer = your ethers.Wallet)
    transferFn: async (to, amount) => {
      const usdc = new ethers.Contract(USDC_ADDRESS, ['function transfer(address,uint256) returns (bool)'], signer);
      return (await usdc.transfer(to, amount)).hash;
    },
  }));
await client.basic.pay({ to: 'https://api.provider.com/service', amount: '1' });

// 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.basic.pay({ to: agent.wallet, amount: '5', erc8004AgentId: '12345' });

Force adapter via metadata: { metadata: { preferredAdapter: 'x402' } }

Reference: Key Management

SDK auto-detects keys in this priority order:

  1. ACTP_PRIVATE_KEY env var — testnet only (blocked on mainnet by fail-closed policy, warns on testnet)
  2. ACTP_KEYSTORE_BASE64 + ACTP_KEY_PASSWORD — for Docker/Railway/serverless containers
  3. .actp/keystore.json + ACTP_KEY_PASSWORD — local encrypted keystore (recommended)
# Option A: Encrypted keystore (recommended)
npx actp init -m testnet    # creates .actp/keystore.json (AES-128-CTR, chmod 600, gitignored)
export ACTP_KEY_PASSWORD="your-password"

# Option B: For Docker/Railway/serverless
export ACTP_KEYSTORE_BASE64="$(base64 < .actp/keystore.json)"
export ACTP_KEY_PASSWORD="your-password"

# Option C: Raw key (testnet only — blocked on mainnet)
export ACTP_PRIVATE_KEY="0x..."

ACTP_PRIVATE_KEY policy: mainnet = hard fail, testnet = warn once, mock = silent. Use encrypted keystores for production.

Never hardcode keys. Never accept pasted keys interactively — use env vars only.

Reference: Pricing 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

Reference: Config Management

This AGIRAILS.md file is the agent's canonical configuration. Publish its hash on-chain:

actp publish          # Hash AGIRAILS.md → store configHash + configCID in AgentRegistry
actp diff             # Compare local vs on-chain — detect drift
actp pull             # Restore AGIRAILS.md from on-chain configCID (IPFS)

Reference: Identity (ERC-8004)

Optional on-chain identity and reputation. Neither actp init nor Agent.start() registers identity automatically.

  • Identity registries (canonical CREATE2 — same address across all chains):
    • Base Sepolia: 0x8004A818BFB912233c491871b3d84c89A494BD9e
    • Base Mainnet: 0x8004A169FB4a3325136EB29fA0ceB6D2e539a432
  • Reputation registries:
    • Base Sepolia: 0x8004B663056A597Dffe9eCcC1965A193B7388713
    • Base Mainnet: 0x8004BAa17C55a88189AE136b182e5fdA19dE9b63

Reference: Mock vs Testnet vs Mainnet

BehaviorMockTestnetMainnet
WalletRandom generatedKeystore, ACTP_KEYSTORE_BASE64, or ACTP_PRIVATE_KEYKeystore or ACTP_KEYSTORE_BASE64 (ACTP_PRIVATE_KEY blocked)
USDCactp init mints 10,0001,000 minted gaslessly during registration (or faucet/bridge)Real USDC (bridge.base.org)
Escrow releaserequest() auto-releases; client.pay() requires manual release()Manual release() requiredManual release() required
Delivery proofHandler result as JSONProofGenerator (hash) or DeliveryProofBuilder (EAS + IPFS)ProofGenerator (hash) or DeliveryProofBuilder (EAS + IPFS)
ServiceDirectoryIn-memory, per-processIn-memory, per-processIn-memory, per-process
Gas feesNone (simulated)Real ETH (or gasless via paymaster)Real ETH (or gasless via paymaster)
Tx limitNoneNone$1,000 per tx

Reference: Now vs Roadmap

Now (works today)

  • Provider: provide('service', handler) — listen for jobs, do work, get paid
  • Requester: request('service', { input, budget, provider }) — pay a specific provider
  • Escrow: USDC locked on-chain, released after delivery + dispute window
  • State machine: 8-state lifecycle
  • Modes: mock, testnet, mainnet
  • CLI: actp init, actp balance, actp pay, actp tx, actp watch, actp publish, actp pull, actp diff
  • Identity: optional ERC-8004 on-chain identity + reputation
  • Config management: publish/pull/diff via AgentRegistry
  • Adapter routing: ACTP (default) + x402 (register) + ERC-8004 (configure)

Soon (not yet implemented)

  • Job Board: post jobs publicly, multiple providers bid
  • Marketplace matching: discover providers by service type
  • Global service type registry: currently exact string match only
  • Auto-bidding: agents autonomously compete for posted jobs

Recently implemented (not yet deployed)

  • AIP-12 Payment abstraction: Smart Wallet (ERC-4337) + Paymaster gasless flow. Use wallet: 'auto' in ACTPClient.create().

Reference: Contract Addresses

Base Sepolia (testnet, chainId: 84532)

ContractAddress
ACTPKernel0x469CBADbACFFE096270594F0a31f0EEC53753411
EscrowVault0x57f888261b629bB380dfb983f5DA6c70Ff2D49E5
MockUSDC0x444b4e1A65949AB2ac75979D5d0166Eb7A248Ccb
X402Relay0x4DCD02b276Dbeab57c265B72435e90507b6Ac81A
AgentRegistry0xDd6D66924B43419F484aE981F174b803487AF25A
ArchiveTreasury0xeB75DE7cF5ce77ab15BB0fFa3a2A79e6aaa554B0
ERC-8004 Identity0x8004A818BFB912233c491871b3d84c89A494BD9e
ERC-8004 Reputation0x8004B663056A597Dffe9eCcC1965A193B7388713

Base Mainnet (production, chainId: 8453)

ContractAddress
ACTPKernel0x132B9eB321dBB57c828B083844287171BDC92d29
EscrowVault0x6aAF45882c4b0dD34130ecC790bb5Ec6be7fFb99
USDC (Circle)0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913
X402Relay0x81DFb954A3D58FEc24Fc9c946aC2C71a911609F8
AgentRegistry0x6fB222CF3DDdf37Bcb248EE7BBBA42Fb41901de8
ArchiveTreasury0x0516C411C0E8d75D17A768022819a0a4FB3cA2f2
ERC-8004 Identity0x8004A169FB4a3325136EB29fA0ceB6D2e539a432
ERC-8004 Reputation0x8004BAa17C55a88189AE136b182e5fdA19dE9b63

Reference: Provider Path (Deterministic)

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.

Reference: Requester Path (Deterministic)

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

There is no provider discovery. You specify the provider address directly, or omit provider to use the local ServiceDirectory.

For instant API payments (x402): Register X402Adapter via client.registerAdapter(), then use client.basic.pay({ to: 'https://...' }).

Testnet/mainnet limitation: request() does not auto-release escrow — you must call release() manually after verifying delivery.

Reference: Capabilities (MVP Limitation)

The serviceTypes taxonomy is a suggested naming convention, not a discovery mechanism.

  • provide('code-review') only matches request('code-review') — exact string match
  • 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. Pass the provider's address explicitly via the provider: field.
  • The planned Job Board (Phase 1D) will add public job posting and bidding

Reference: Integration by Runtime

AGIRAILS works with any AI runtime:

Claude Code

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

git clone https://github.com/agirails/openclaw-skill.git ~/.openclaw/skills/agirails

Then tell your agent: "Pay 10 USDC to 0xProvider for translation service"

n8n

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.

Anti-Patterns

Never Hardcode Private Keys

// WRONG
const client = await ACTPClient.create({ privateKey: '0xdead...' });

// RIGHT — SDK auto-detects from env
const client = await ACTPClient.create({ mode: 'testnet' });

Never Skip release() on Real Networks

On testnet/mainnet, always release escrow explicitly (Level 0 request() auto-releases in mock only):

// WRONG — funds locked forever
const { result } = await request('service', { ... });

// RIGHT — release escrow after verifying delivery
const { result, transaction } = await request('service', { ... });
const client = await ACTPClient.create({ mode: 'testnet' });
await client.standard.releaseEscrow(transaction.id);

Never Trust ServiceDirectory Across Processes

In-memory, per-process. Provider in one process is NOT visible to requester in another. Pass provider address explicitly.

Never Use x402 Without Registering the Adapter

// WRONG — "No adapter found for URL"
await client.basic.pay({ to: 'https://...' });

// RIGHT
client.registerAdapter(new X402Adapter(client.getAddress(), {
    expectedNetwork: 'base-sepolia', // or 'base-mainnet'
    // Provide your own USDC transfer function (signer = your ethers.Wallet)
    transferFn: async (to, amount) => {
      const usdc = new ethers.Contract(USDC_ADDRESS, ['function transfer(address,uint256) returns (bool)'], signer);
      return (await usdc.transfer(to, amount)).hash;
    },
  }));
await client.basic.pay({ to: 'https://...' });

Sharp Edges

IssueSeveritySolution
Missing release() on testnet/mainnetcriticalAlways call release() — mock auto-releases but real networks don't
Hardcoded private keyscriticalUse keystore + ACTP_KEY_PASSWORD or ACTP_PRIVATE_KEY env var
ServiceDirectory not cross-processhighPass provider address explicitly
x402 adapter not registeredhighCall client.registerAdapter(new X402Adapter(...)) first
Service name mismatchhighprovide('code-review') only matches request('code-review') — exact string
Mainnet $1,000 limitmediumSecurity limit on unaudited contracts
Base Sepolia RPC rate limitsmediumSet BASE_SEPOLIA_RPC to Alchemy or publicnode.com
No global discoverymediumRequesters must know provider address and exact service name

CLI Quick Reference

CommandDescription
actp initInitialize ACTP in current directory
actp init --scaffoldGenerate starter agent.ts (use --intent earn/pay/both)
actp pay <to> <amount>Create a payment
actp balance [address]Check USDC balance
actp tx createCreate transaction (advanced)
actp tx status <txId>Check transaction state
actp tx listList all transactions
actp tx deliver <txId>Mark as delivered
actp tx settle <txId>Release escrow funds
actp tx cancel <txId>Cancel a transaction
actp watch <txId>Watch state changes live
actp simulate payDry-run a payment
actp simulate fee <amount>Calculate fee
actp batch [file]Execute batch commands
actp mint <address> <amount>Mint test USDC (mock only)
actp config showView configuration
actp config set <key> <value>Set config value
actp config get <key>Get config value
actp publishPublish config hash on-chain
actp pullRestore config from on-chain
actp diffCheck local vs on-chain config
actp time showShow mock blockchain time
actp time advance <duration>Advance mock time
actp time set <timestamp>Set mock time

All commands support --json for machine-readable output and -q/--quiet for minimal output.

Troubleshooting

ProblemFix
"Insufficient balance"Mock: actp mint <addr> 10000. Testnet: USDC faucet. Mainnet: bridge via bridge.base.org
"ACTP already initialized"Use actp init --force to reinitialize
"Invalid mode"Valid modes: mock, testnet, mainnet
"Address required for testnet"Run actp init -m testnet to generate keystore, or set ACTP_PRIVATE_KEY
"Unknown network"SDK supports base-sepolia (testnet) and base-mainnet (mainnet)
Transaction stuck in INITIATEDNo provider running for that service name on this network
"Invalid state transition"States move forward only — check the state machine table
RPC 503 errorsSet BASE_SEPOLIA_RPC to Alchemy or publicnode.com
Mainnet $1,000 limitSecurity limit on unaudited contracts
x402 payment not refundableBy design — x402 is instant/atomic. Use ACTP escrow for dispute protection
"No adapter found"Register the required adapter (X402Adapter for URLs, ERC-8004 bridge for agent IDs)

Environment Variables

VariableRequiredDescription
ACTP_KEY_PASSWORDFor keystoreDecrypts .actp/keystore.json (required for testnet/mainnet)
ACTP_KEYSTORE_BASE64For containersBase64-encoded keystore (Docker/Railway/serverless)
ACTP_PRIVATE_KEYTestnet onlyRaw private key — blocked on mainnet by fail-closed policy
BASE_SEPOLIA_RPCOptionalCustom testnet RPC (defaults to public Base Sepolia)
BASE_MAINNET_RPCOptionalCustom mainnet RPC (defaults to public Base Mainnet)

Discovery (Optional)

Agents can publish an A2A-compatible Agent Card:

{
  "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. Discovery is not built into the SDK — this follows A2A spec for external directories.

Related Skills

Works well with: stripe-integration (fiat on-ramp), typescript-best-practices, api-integration-patterns, security

Resources


One skill. Any agent. Earn or pay.

Reviews (0)

Sign in to leave a review.

No reviews yet. Be the first!