PACT: Provable Agent to Agent Compute Trust

May 16, 2026 · View on GitHub

PACT. Settlement protocol for verifiable AI as a Service on 0G mainnet

Settlement protocol for verifiable AI as a Service on 0G mainnet. Buyers pay sellers per inference. TEE attestation guarantees model and execution. Reputation accrues to the seller's ERC-7857 INFT. Sell the agent, sell the reputation.

npm sdk npm mcp mainnet license

Watch the PACT 5-minute demo on YouTube
Watch the 5-minute demo on YouTube ↗


Status

LIVE on 0G mainnet (chainId 16661). 7 contracts deployed. Bond staked. First end to end settled job: 0xbb36752d…7df48 on 2026-05-15. Job #2, buyer escrowed 0.001 $0G, seller submitted a TEE attested ECDSA signature, contract verified on chain, atomic settlement (95% seller, 5% protocol fee).

ContractAddressExplorer
PactRegistry0x152A5a433A6592df57d7F77B7B01eEE00C481C2dchainscan ↗
PactEscrow0xB2b762Df53294923d3eaD00d8118AD37388dD4aAchainscan ↗
AttestationVerifier0x765C857B6764c90B0093Ea16f6103902665D0aa2chainscan ↗
ReputationVault0x1574E42D7fF268384408430D5b76C88f37b8a72Bchainscan ↗
SlashingArbiter0x324E5b2183134EB239C7E934438831a15abe7C00chainscan ↗
AgentNFT (proxy)0xe76dBE7FCf8c7F784b05DF88996bd63CA2c4d7D6chainscan ↗
AgentNFT (impl)0x4EC0DCac00A274Fb69F54cAb62370b2c71989CE4chainscan ↗

Demo: https://trypact.xyz. Vercel + Singapore edge region (sin1) for lowest latency to 0G mainnet RPC. Local: http://localhost:3001. Explorer: https://explorer.trypact.xyz. Same site, alternate entry point. Indexer API: https://api.trypact.xyz. REST cache of every settled job, service, seller. Try /v1/services, /v1/jobs, /healthz. Docs: https://docs.trypact.xyz. Full GitBook with concepts, guides, and reference. Source at gitbook/. Demo video: https://www.youtube.com/watch?v=PiBCQAHUaRg. 5 minutes, beat-by-beat walkthrough on 0G mainnet. Script at docs/DEMO_SCRIPT.md. Deploy guide: docs/DEPLOY.md. Reproducible Vercel + custom domain + WC Cloud setup. SDK: @trypact/sdk on npm. pnpm add @trypact/sdk viem. MCP server: drop a URL (https://mcp.trypact.xyz/mcp) or the npm package (@trypact/mcp-server) into ~/.claude/mcp.json. Your AI agent gains tools to browse the registry, verify TEE attestations, and pay other AI agents on chain. Install guide.


What you can do, by intent

I want toGo here
Try a real settled job in my browserhttps://trypact.xyz/marketplace, pick a service, click "Run an inference"
Verify a settled attestation cryptographicallyhttps://trypact.xyz/verify/2?autoplay=1. Local ECDSA recovery, no server trust.
See live state machine plus on chain txs for a jobhttps://trypact.xyz/jobs/2
Browse the explorer (services plus jobs feed)https://explorer.trypact.xyz
Integrate as a buyer in 25 lines of TSSDK quickstart ↓. pnpm add @trypact/sdk viem
Plug PACT into Claude or Cursor as MCP toolsMCP quickstart ↓. Paste one URL into your agent config.
Register an agent as a seller and start earningBecome a seller ↓
Query the on chain registry without an SDKIndexer API ↓. REST, no auth, CORS open.
Call the contracts directly (cast / ethers / web3)Contracts cheat sheet ↓
Understand the moat (TEE plus ECDSA recovery)How verification works ↓
Reproduce the end to end loop locallyRun it yourself ↓
Read the full docsdocs.trypact.xyz. Concepts, guides, reference.

What it is, in one sentence

PACT is the settlement layer for AI as a Service on 0G. Buyers pay sellers for inference work, with cryptographic guarantee that the work was done by the exact agent INFT they paid for, by the registered TEE broker provider, on the model the seller committed to. Payment auto releases on attestation. Reputation accrues to the INFT.


Integrate in 25 lines (SDK)

import { PactClient } from "@trypact/sdk";
import { createPublicClient, createWalletClient, http } from "viem";
import { privateKeyToAccount } from "viem/accounts";

const chain = {
  id: 16661, name: "0G Mainnet",
  nativeCurrency: { name: "OG", symbol: "OG", decimals: 18 },
  rpcUrls: { default: { http: ["https://evmrpc.0g.ai"] } },
} as const;

const account = privateKeyToAccount(process.env.BUYER_KEY as `0x${string}`);
const pact = new PactClient({
  publicClient: createPublicClient({ chain, transport: http() }),
  walletClient: createWalletClient({ account, chain, transport: http() }),
});

// Escrow funds, watch through settlement, verify the TEE attestation locally.
const result = await pact.run({
  serviceId: 1n,
  prompt: "Audit this Solidity contract for reentrancy vulnerabilities",
});

console.log(result.verified.ok);                  // true on authentic attestation
console.log(result.verified.recoveredSigner);     // matches service.signingAddress
console.log(result.txHashes.createJob);           // chainscan it

Full SDK docs: packages/sdk/README.md. npmjs.com/package/@trypact/sdk.


Plug into Claude or Cursor (MCP)

PACT ships a Model Context Protocol server. Any MCP compatible AI agent (Claude Desktop, Cursor, Cline, Continue, Windsurf, etc.) gains tools to browse the on chain registry, verify TEE attestations, and pay other AI agents per inference, autonomously, with no human approval per call.

Two ways to attach.

Hosted (URL, zero install). Read only.

For browsing the registry and verifying attestations from any agent. No npm install, no local process, no key. Drop into your MCP config:

{
  "mcpServers": {
    "trypact": {
      "url": "https://mcp.trypact.xyz/mcp"
    }
  }
}

Exposes the 4 read tools: list_services, get_service, get_job, verify_attestation. Hits 0G mainnet directly. Stateless. Every request is independent, no session, concurrency safe by construction.

Local (npm plus private key). Full, includes run.

For paying agents. Install the npm package so your key never leaves your machine:

{
  "mcpServers": {
    "trypact": {
      "command": "npx",
      "args": ["-y", "@trypact/mcp-server"],
      "env": {
        "PACT_PRIVATE_KEY": "0x<your-burner-key>"
      }
    }
  }
}

Restart the agent. The five tools appear under the trypact namespace:

ToolEffect
pact.list_servicesBrowse the on chain registry
pact.get_serviceFetch one service's pricing plus signing address
pact.get_jobInspect any historical job
pact.verify_attestationLocal ECDSA recovery on a settled job (pure crypto, no RPC)
pact.runPay a service for one inference, watch through settlement, verify the TEE signature, return the output

Now the conversation goes:

You: "Find me a Solidity audit agent on PACT and audit this contract." Claude: (calls pact.list_services) "Service #1 is zai-org/GLM-5-FP8, 0.001 $0G per call. Want me to use it?" You: "Yes." Claude: (calls pact.run) "Paid 0.001 $0G, job #5 settled in 42s. Recovered signer 0x4C1b…7ee8 matches the registered TEE key. Attestation verified. Here's the audit: …"

This is agent to agent settlement with cryptographic proof. The agentic economy primitive.

Full MCP docs: packages/mcp-server/README.md. npmjs.com/package/@trypact/mcp-server.


Become a seller

Sellers register an agent service, stake a bond against fraud, and earn 95% of every settled call (the other 5% is a protocol fee).

1. Mint an Agent INFT (ERC-7857). Your seller identity plus reputation accumulator. Transferable: sell the agent, sell the reputation.

AgentNFT.mint(to=sellerAddress, metadataURI="ipfs://…")  // → inftTokenId

2. Register the service on PactRegistry:

PactRegistry.registerService(
    bytes32 capabilityHash,        // hash of the capability spec
    string  modelId,               // e.g. "zai-org/GLM-5-FP8"
    address providerAddress,       // 0G Compute provider you delegate to
    address signingAddress,        // TEE bound key that signs attestations
    string  providerIdentity,      // e.g. "openrouter"
    string  providerType,          // "centralized" | "decentralized" | "self"
    bool    targetSeparated,       // true = provider ≠ seller (delegated TEE)
    uint128 pricePerCall,          // wei of \$0G
    uint64  maxInputBytes,         // input cap (e.g. 8192)
    bytes   inftMetadataURI        // bound to your AgentNFT tokenId
);

3. Stake the bond on SlashingArbiter (minimum 5 $0G). Bond stays bonded for the life of the service. It slashes on cryptographic fraud and is reclaimable via requestWithdrawal then withdrawBond after the dispute window.

SlashingArbiter.stakeBond{value: 5 ether}(serviceId);

4. Run the seller agent (or write your own. The protocol doesn't care). Reference implementation watches JobCreated, calls 0G Compute Direct broker, submits the TEE attestation:

# Reference seller agent (apps/seller-reference/)
cp apps/seller-reference/.env.example apps/seller-reference/.env
# set PACT_PRIVATE_KEY, PACT_SERVICE_ID, ZG_PROVIDER_ADDRESS
pnpm --filter @pact/seller-reference setup    # one time bond stake
pnpm --filter @pact/seller-reference run      # long running watcher

The reference agent uses @0gfoundation/0g-compute-ts-sdk for the TEE inference call and viem.recoverMessageAddress as a local sanity check before submitting the attestation on chain. Full source: apps/seller-reference/src/.


Query the indexer API

Public, no auth, CORS open. Hosted at https://api.trypact.xyz. Built on top of @trypact/sdk so the contract layer remains the single source of truth. The indexer is just a fast read cache.

EndpointReturns
GET /healthzUptime, last indexed block, in memory counters
GET /v1/servicesFull service catalog (every registered seller)
GET /v1/services/:idOne service by id
GET /v1/jobs?limit=NRecent jobs across all services, newest first
GET /v1/jobs/:idOne job's full state plus attestation bytes
GET /v1/sellers/:addressEvery service plus job for one seller address
GET /v1/statsAggregate counters (settled, expired, slashed, total $0G settled)

The indexer is a Railway hosted Express process. Source at apps/indexer/. Polls JobCreated plus 5 state change events. Falls back to on chain reads if a block range is missed.


Run it yourself

Reproduces the e2e flow against live 0G mainnet. Needs about 6 $0G in a burner wallet (5 for bond plus about 1 for gas and a few test jobs).

# Clone + install
git clone https://github.com/winsznx/pact.git
cd pact
pnpm install

# Configure env
cp apps/seller-reference/.env.example apps/seller-reference/.env
# Edit: set PACT_PRIVATE_KEY to a burner with ≥6 \$0G on 0G mainnet.
# Same key is used for both seller (in this hackathon scope) and the
# e2e buyer test. Net cost per loop is about 0.003 \$0G of gas.

# Test the contracts (Foundry suite, 56/56 passing)
pnpm --filter @pact/contracts test

# Run the frontend locally (or skip; production is live at trypact.xyz)
pnpm --filter @pact/web dev
# → http://localhost:3001/marketplace/1. Click "Run an inference".

# Or run the protocol end to end from CLI:
pnpm --filter @pact/seller-reference setup     # stakes 5 \$0G bond (idempotent)
pnpm --filter @pact/seller-reference run run & # start the seller watcher
pnpm --filter @pact/seller-reference test-e2e  # buyer test, returns when Settled
# or:
./apps/seller-reference/scripts/e2e.sh         # the orchestrated pipeline

Expect the full loop (createJob → inference → submitAttestation → Settled) to land in about 60 seconds on 0G mainnet.


Architecture

                   ┌────────────────────────────────────────────────┐
                   │             0G MAINNET (chainId 16661)         │
                   │                                                │
                   │   ┌─────────────────┐    ┌──────────────────┐  │
   Buyer ──pay──▶  │   │   PactEscrow    │◄───┤ AttestationVerif │  │
                   │   │  (escrow + jobs)│    │ (EIP-191 + ECDSA)│  │
                   │   └────────┬────────┘    └──────────────────┘  │
                   │            │ atomic settle (95 / 5)            │
                   │            ▼                                   │
                   │   ┌─────────────────┐    ┌──────────────────┐  │
                   │   │  PactRegistry   │    │ SlashingArbiter  │  │
                   │   │ (Service + INFT)│    │ (bond + dispute) │  │
                   │   └────────┬────────┘    └────────┬─────────┘  │
                   │            │                     │             │
                   │            ▼                     │             │
                   │   ┌─────────────────┐            │             │
                   │   │ ReputationVault │◄───────────┘             │
                   │   │ (sqrt-weighted, │   slash on fraud →       │
                   │   │  INFT-bound)    │     bond redistributed   │
                   │   └─────────────────┘                          │
                   │            ▲                                   │
                   │            │ tokenURI / ownerOf                │
                   │   ┌─────────────────┐                          │
                   │   │   AgentNFT      │                          │
                   │   │   (ERC-7857)    │                          │
                   │   └─────────────────┘                          │
                   └────────────────────────────────────────────────┘

                              JobCreated  │  submitAttestation

            ┌─────────────────────────────┴───────────────────────────┐
            │           Seller reference agent (Node.js)              │
            │                                                         │
            │   poll PactEscrow.nextJobId every 3s                    │
            │      │                                                  │
            │      ▼                                                  │
            │   ┌──────────────────────────────┐    ┌───────────────┐ │
            │   │ @0gfoundation/0g-compute-sdk │───►│  0G Compute   │ │
            │   │  - acknowledgeProviderSigner │    │  Direct broker│ │
            │   │  - chat/completions          │    │  (TEE)        │ │
            │   │  - GET /signature/{chatId}   │◄───│               │ │
            │   └──────────────┬───────────────┘    └───────────────┘ │
            │                  ▼                                      │
            │   viem.recoverMessageAddress (local sanity check)       │
            │                  │                                      │
            │                  ▼                                      │
            │   PactEscrow.submitAttestation(jobId, outputRoot,       │
            │       chatId, text, signature)                          │
            └─────────────────────────────────────────────────────────┘

Job state machine

Every job moves through this strict transition graph. Each transition emits an event and is visible on chainscan in real time.

                ┌─── reclaimExpired() ────► Expired
                │       (after timeout)
   Pending ─────┤
   (createJob)  │                                      ┌─── arbitrate() ───► Settled
                │                                      │       (dispute fails)
                └── submitAttestation() ──► Attested ──┤
                       (TEE sig verified)              │
                                                      ├─── (24h passes) ───► Settled
                                                      │       (95% seller / 5% protocol)

                                                      └─── dispute() ───► Disputed

                                                                            ├─► Settled (dispute fails: 90% to seller, 10% protocol)
                                                                            └─► Slashed (dispute wins: bond → 70% disputer, 20% protocol, 10% burned)

Source: packages/contracts/src/PactEscrow.sol. The state is enum JobState { Pending, Sealed, Attested, Settled, Expired, Disputed, Slashed } and the JS mirror lives at packages/sdk/src/types.ts.

5 0G primitives, each load bearing

PrimitiveWhere in PACT
0G ChainAll 7 protocol contracts (PactRegistry, PactEscrow, AttestationVerifier, ReputationVault, SlashingArbiter, AgentNFT proxy plus impl)
0G ComputeTEE attested inference via Direct broker. Every settled job's text plus signature is pulled from ${endpoint}/v1/proxy/signature/${chatId}.
0G StorageEncrypted output blob plus KV reputation history (v0.2 path. v0.1 uses local handoff).
ERC-7857 INFTSeller identity plus reputation accumulator. INFT transferable means reputation transferable.
0G DAJob event log (production scale path).

How verification works

Every settled job carries a 5 field colon separated canonical text signed by the provider's TEE bound key:

<contentHash>:<usageHash>:<providerType>:<providerIdentity>:<tlsCertFingerprint>

AttestationVerifier.sol runs the EXACT same primitive client browsers run for personal_sign:

bytes32 digest = MessageHashUtils.toEthSignedMessageHash(attestationText);
address signer = ECDSA.recover(digest, signature);
require(signer == svc.signingAddress, "AttestationInvalid");

The frontend at /verify/[jobId] demonstrates this in your browser with viem's recoverMessageAddress. Same bytes, same hash, same recovered address. Try it live: https://trypact.xyz/verify/2?autoplay=1

If the recovered address matches the service's registered signingAddress, the attestation is authentic. If they differ, anyone can call dispute() and the seller's bond gets slashed. 70% to the disputer, 20% to the protocol, the remainder burned.


Contracts cheat sheet

For integrators who want to skip the SDK and call the contracts directly (via cast, ethers, web3, viem, foundry). Every function is non reentrant where it touches state. Full NatSpec on every external function. See packages/contracts/src/.

Buyer surface (PactEscrow)

// Escrow funds, open a job. msg.value is the price for one inference.
function createJob(uint256 serviceId, bytes calldata encryptedInput, uint64 timeout)
    external payable returns (uint256 jobId);

// Reclaim escrow if the seller hasn't attested by `timeout`.
function reclaimExpired(uint256 jobId) external;

// Challenge a settled attestation. Requires posting a dispute bond.
function dispute(uint256 jobId) external payable;

// Read job state, escrow balance, etc.
function getJob(uint256 jobId) external view returns (Job memory);
function jobEscrowBalance(uint256 jobId) external view returns (uint128);
function nextJobId() external view returns (uint256);

Seller surface (PactRegistry, PactEscrow, SlashingArbiter)

// PactRegistry: register, update, delist
function registerService(...) external returns (uint256 serviceId);     // (full sig above)
function updateService(uint256 serviceId, uint128 newPrice, bool active) external;
function rotateSigningAddress(uint256 serviceId, address newSigningKey) external;
function delistService(uint256 serviceId) external;
function getSellerServices(address seller) external view returns (uint256[] memory);

// PactEscrow: submit the TEE attestation that settles the job
function submitAttestation(
    uint256 jobId,
    bytes32 outputRoot,
    bytes32 chatId,
    bytes calldata attestationText,        // the 5 field canonical text
    bytes calldata attestationSignature    // 65 byte ECDSA(secp256k1)
) external;

// SlashingArbiter: bond lifecycle
function stakeBond(uint256 serviceId) external payable;                 // ≥ 5 \$0G
function requestWithdrawal(uint256 serviceId) external;                 // start timer
function withdrawBond(uint256 serviceId) external;                      // after window

Public reads (anyone)

PactRegistry.getService(serviceId)                  → Service struct
PactRegistry.nextServiceId()                        → uint256
ReputationVault.getReputation(serviceId)            → Reputation struct
ReputationVault.getBuyerWeight(address buyer)       → uint128 (sqrt weighted)
ReputationVault.getBuyerTotalVolume(address buyer)  → uint128 (wei settled)
SlashingArbiter.bondOf(serviceId)                   → uint128
AgentNFT.tokenURI(tokenId) / .ownerOf(tokenId)      → ERC-7857 standard

Addresses are in the Status table above and exported from @pact/shared for in repo consumers.


Economics

WhatValueWhere
Protocol fee5% of every settled job (95% to seller)PactEscrow.PROTOCOL_FEE_BPS = 500
Minimum seller bond5 $0G per serviceSlashingArbiter.MIN_BOND = 5e18
Successful slash split70% disputer / 20% protocol / 10% burnedSlashingArbiter.SLASH_DISPUTER_BPS = 7000
Failed dispute split90% to seller / 10% protocol (anti griefing)SlashingArbiter
Dispute window24h after SettledPactEscrow
Default job timeoutBuyer specified (typical 300s)createJob.timeout
Buyer reputation weightsqrt(buyer_total_paid_wei)ReputationVault.getBuyerWeight

Sybil resistance is enforced by the sqrt weighted buyer reputation. A brand new buyer's review contributes near zero. A long tail buyer who has cumulatively paid more gets proportionally more weight. Reputation accrues to the INFT, not the seller wallet. Transfer the INFT, transfer the reputation.


Hackathon

Track 3: Agentic Economy and Autonomous Applications (sub themes: financial rails plus operational tools). Full rubric mapping in docs/MASTER_PRD.md §1.2.

HackQuest criterionOur claim
0G integration depth5/5 primitives, each structurally necessary
Technical completeness7 mainnet contracts, end to end demo loop verified on chain
Product fitFills 0G's own published roadmap gap (the "AI Agent Marketplace coming soon")
UX and demoThree shareable demo moments. Premium B2B aesthetic. Every tx visible.
Team and docsThis PRD, NatSpec on every external function, ~14 day commit history, judge reproducible README, full GitBook at docs.trypact.xyz

Repo layout

apps/
  web/                   Next.js 16 frontend (landing, marketplace, jobs, verify, explorer, seller)
  seller-reference/      Node.js seller agent (JobCreated watcher → 0G Compute → submitAttestation)
  indexer/               Express read cache (api.trypact.xyz, Railway)
  mcp-http/              Hosted MCP server (mcp.trypact.xyz, Railway)
packages/
  contracts/             Foundry. Solidity 0.8.24 + cancun. 56/56 tests passing.
  sdk/                   @trypact/sdk. Buyer TypeScript SDK (npm, published).
  mcp-server/            @trypact/mcp-server. Stdio MCP for local install (npm, published).
  shared/                Addresses + ABIs + chain constants, workspace linked.
gitbook/                 Full docs site, synced to docs.trypact.xyz
docs/
  MASTER_PRD.md          Canonical spec (v0.4)
  AGENT_PROGRESS.md      Build log (each phase exit)
  DEMO_SCRIPT.md         5 minute video script
  X_POST.md              Submission time social copy
  HACKQUEST_FORM.md      Pre filled HackQuest answers
  design/                Antimetal derived design system
scripts/
  day0/                  Phase 0 evidence (preserved as historical truth)

What's in v0.1 (this submission) vs planned

Capabilityv0.1 (shipped)Planned
7 mainnet contracts + 56/56 Foundry tests(shipped)
Buyer SDK + stdio MCP + hosted MCP(shipped)
TEE attestation via 0G Compute Direct broker(shipped)
Local ECDSA verification UI (browser)(shipped)
Indexer + explorer subdomain(shipped)
Full docs site at docs.trypact.xyz (GitBook)(shipped)
0G Storage for encrypted output blobsLocal handoffv0.2: encrypted blob via 0g-storage-client
0G DA for job event log at scaleIndexer cachev0.2: production scale audit trail
Multi seller marketplace (sellers beyond Service 1)Single seller in scopev0.2: open registration via UI
Reputation lease and INFT secondary marketRead paths wiredv0.2: INFT marketplace integration

The v0.2 items are tracked in docs/MASTER_PRD.md §22 (Roadmap).


License

Apache License 2.0. Permissive. Contracts and SDKs are free for any use.

Contributors

Built solo by Tim (@winsznx) for 0G APAC Hackathon Track 3, May 2026. PR's welcome on github.com/winsznx/pact.