ERC-8183 Agentic Commerce

Job escrow for multi-step agent workflows with lifecycle management.

Overview

The AgenticCommerceProtocol (ACP) implements ERC-8183 job escrow for complex agent-to-agent workflows. Unlike simple per-request payments (x402), ACP handles multi-step jobs where a client agent posts work, a provider agent delivers it, and an evaluator approves release.

A 1% platform fee on job completion funds protocol development. This is enforced at the contract level and is mathematically unbypassable.

Job Lifecycle

OpenFundedSubmittedCompleted

Open — Job created with provider, evaluator, and expiry. No funds locked yet.

Funded — Client has deposited the budget into escrow. Provider can start work.

Submitted — Provider has submitted a deliverable hash. Evaluator reviews.

Completed — Evaluator approved. Provider receives 99% of budget, protocol gets 1%.

Why Encrypted Budgets?

When job budgets are visible on-chain, competitors can monitor how much an agent spends on each task, revealing its operational strategy and priorities. Provider agents can also collude to price-discriminate based on a client's spending history. By funding escrow with FHE-encrypted cUSDC, the budget amount stays hidden from everyone except the parties involved. The escrow contract performs all arithmetic — fee calculation, payment release, refunds — on encrypted values, so the public ledger only shows that a job was created and completed, never how much was paid.

Creating a Job

// Create job (separate steps)
function createJob(
  address provider,
  address evaluator,
  uint256 expiredAt,
  string calldata description,
  address hook
) external returns (uint256 jobId)

// Create + set budget + fund in one TX
function createAndFund(
  address provider,
  address evaluator,
  uint256 expiredAt,
  string calldata description,
  address hook,
  uint256 budget
) external returns (uint256 jobId)
create-job.tstypescript
const acp = new ethers.Contract(
  "0xBCA8d5ce6D57f36c7aF71954e9F7f86773a02F22",
  ACP_ABI,
  wallet
);

// Approve USDC for escrow
await usdc.approve(acp.target, 10_000_000n); // 10 USDC

// Create and fund job in one TX
const tx = await acp.createAndFund(
  providerAddress,   // who does the work
  evaluatorAddress,  // who approves
  Math.floor(Date.now() / 1000) + 86400, // expires in 24h
  "Analyze market data for ETH/USDC pair",
  ethers.ZeroAddress, // no hook
  10_000_000n         // 10 USDC budget
);

Submit and Complete

// Provider submits deliverable
function submit(uint256 jobId, bytes32 deliverable) external
// deliverable = keccak256 hash of the work output
// Emits: JobSubmitted(jobId, provider, deliverable)

// Evaluator approves and releases payment
function complete(uint256 jobId, bytes32 reason) external
// 99% to provider, 1% to protocol treasury
// Emits: JobCompleted(jobId, evaluator, reason)
// Emits: PaymentReleased(jobId, provider, amount)

Reject and Refund

// Client can reject Open or Funded jobs
// Evaluator can reject Funded or Submitted jobs
function reject(uint256 jobId, bytes32 reason) external
// Refunds escrowed funds to client
// Emits: JobRejected(jobId, rejector, reason)

// Client claims refund on expired job
function claimRefund(uint256 jobId) external
// Only works on Funded/Submitted jobs past expiry
// Emits: Refunded(jobId, client, amount)

Hooks (IACPHook)

Optional hook contracts can be attached to jobs. They are called after each action with a 100,000 gas limit. If the hook reverts, the parent operation still succeeds.

interface IACPHook {
  function afterAction(
    uint256 jobId,
    bytes4 selector,  // which function was called
    bytes calldata data
  ) external;
}

// If hook reverts:
event HookFailed(uint256 indexed jobId, bytes4 indexed selector);

All Functions

FunctionCallerDescription
createJobAnyoneCreate a new job
createAndFundAnyoneCreate + budget + fund in one TX
setProviderClientSet/update provider on Open job
setBudgetClientSet budget on Open job
fundClientFund with front-running protection
submitProviderSubmit deliverable hash
completeEvaluatorApprove and release payment
rejectClient/EvaluatorReject and refund
claimRefundClientClaim refund on expired job

Events

event JobCreated(uint256 indexed jobId, address indexed client, address indexed provider, address evaluator, uint256 expiredAt);
event ProviderSet(uint256 indexed jobId, address indexed provider);
event BudgetSet(uint256 indexed jobId, uint256 amount);
event JobFunded(uint256 indexed jobId, address indexed client, uint256 amount);
event JobSubmitted(uint256 indexed jobId, address indexed provider, bytes32 deliverable);
event JobCompleted(uint256 indexed jobId, address indexed evaluator, bytes32 reason);
event JobRejected(uint256 indexed jobId, address indexed rejector, bytes32 reason);
event PaymentReleased(uint256 indexed jobId, address indexed provider, uint256 amount);
event Refunded(uint256 indexed jobId, address indexed client, uint256 amount);
event HookFailed(uint256 indexed jobId, bytes4 indexed selector);