erc-8183-mcp
README.md
# ERC-8183 Agentic Commerce Demo
A complete learning application for the minimal ERC-8183 job lifecycle. It uses test-only mUSDC, MetaMask, Solidity, Hardhat, React, FastAPI, SQLAlchemy, and SQLite. The backend never holds private keys and no real funds should be used.
> ERC-8183 is still an Ethereum **Draft** as of August 3, 2026. The lifecycle and hook interface may change before the proposal becomes final. This project uses Solidity 0.8.24, a non-upgradeable core, and two optional policy hooks for learning.
Official source: <https://eips.ethereum.org/EIPS/eip-8183>
## What is included
- `contracts/`: `MockUSDC`, hook-enabled `AgenticCommerce`, separate Allowlist/Bidding hooks, tests, deployment, ABI export
- `backend/`: FastAPI metadata API, persistent ERC-8004 agent profiles, SQLAlchemy models, SQLite, Alembic migrations, tests
- `frontend/`: React/Vite dark UI, MetaMask, role-aware actions, ERC-8004 identity/reputation, bidding, delivery, evaluation
- `scripts/`: Linux/macOS shell and Windows PowerShell native setup/run/binary build scripts
- `HOOKS_GUIDE.md`: deployment, architecture, EIP-712 bidding, and manual/automated test instructions
- No Docker configuration. This package uses native Node.js and Python setup as requested.
## Architecture
```mermaid
flowchart TD
U["Client / Provider / Evaluator"] -->|MetaMask transactions| C["AgenticCommerce"]
C -->|escrow transfers| T["MockUSDC"]
C -->|trusted callbacks| H["Allowlist or Bidding Hook"]
U -->|metadata and bids| F["React UI"]
F -->|REST| B["FastAPI"]
B --> D["SQLite"]
F -->|ethers.js reads and writes| C
```
The blockchain is authoritative for lifecycle, role permissions, escrow, hashes, and settlement. SQLite stores only searchable off-chain data such as bids, URLs, descriptive metadata, and full reason text. A user signs every state-changing blockchain transaction in MetaMask.
## Exact state machine
| From | To | Caller | Function |
|---|---|---|---|
| Open | Funded | Client | `fund(jobId, expectedBudget)` |
| Open | Rejected | Client | `reject(jobId, reason)` |
| Funded | Submitted | Provider | `submit(jobId, deliverable)` |
| Funded | Rejected | Evaluator | `reject(jobId, reason)` |
| Funded | Expired | Anyone after deadline | `claimRefund(jobId)` |
| Submitted | Completed | Evaluator | `complete(jobId, reason)` |
| Submitted | Rejected | Evaluator | `reject(jobId, reason)` |
| Submitted | Expired | Anyone after deadline | `claimRefund(jobId)` |
Every other transition reverts. Completed, Rejected, and Expired are terminal.
## Lifecycle sequences
### Completed
```mermaid
sequenceDiagram
participant C as Client
participant A as AgenticCommerce
participant P as Provider
participant E as Evaluator
C->>A: createJob + setBudget
C->>A: approve token + fund(expectedBudget)
P->>A: submit(deliverableHash)
E->>A: complete(reasonHash)
A-->>P: release escrow
```
### Rejected
```mermaid
sequenceDiagram
participant C as Client
participant A as AgenticCommerce
participant E as Evaluator
C->>A: fund job
E->>A: reject(reasonHash)
A-->>C: refund full escrow
```
### Expired
```mermaid
sequenceDiagram
participant C as Client
participant A as AgenticCommerce
participant X as Any wallet
C->>A: fund job
Note over A: expiredAt reached
X->>A: claimRefund(jobId)
A-->>C: refund full escrow
```
### Optional provider and bidding
```mermaid
sequenceDiagram
participant C as Client
participant B as FastAPI bids
participant P as Provider
participant A as AgenticCommerce
C->>A: createJob(provider=0)
P->>B: submit off-chain bid
C->>B: select bid
C->>A: setProvider(winner)
C->>A: setBudget + fund
```
Bids remain off-chain, but the Bidding hook now verifies the winning provider's EIP-712 signature and committed amount before the provider is written on-chain. See `HOOKS_GUIDE.md` for the exact flow.
## Prerequisites
- Node.js 20 or 22 LTS recommended
- npm 10+
- Python 3.11 or 3.12
- MetaMask
- Sepolia test ETH for transaction gas
Node 24 may work but Hardhat 2 officially targets LTS Node releases; use Node 20/22 if you see a runtime warning.
## Fast native setup
### Windows PowerShell
```powershell
Set-ExecutionPolicy -Scope Process Bypass
.\scripts\setup-native.ps1
Copy-Item contracts\.env.example contracts\.env
Copy-Item backend\.env.example backend\.env
Copy-Item frontend\.env.example frontend\.env
```
### Linux or macOS
```bash
chmod +x scripts/*.sh
./scripts/setup-native.sh
cp contracts/.env.example contracts/.env
cp backend/.env.example backend/.env
cp frontend/.env.example frontend/.env
```
## Run locally with a Hardhat chain
Open three terminals:
```bash
# Terminal 1
./scripts/run-local-chain.sh
# Terminal 2: deploy and note the two addresses
npm --prefix contracts run deploy:local
# Put the core, token, and two hook addresses into frontend/.env, then:
./scripts/run-backend.sh
# Terminal 3
./scripts/run-frontend.sh
```
Windows users can run the matching `.ps1` scripts. Set `VITE_CHAIN_ID=31337` for the Hardhat network and import one of Hardhat's displayed test-only private keys into MetaMask. Add a local MetaMask network with RPC `http://127.0.0.1:8545` and chain ID `31337`.
## Run the tests
```bash
npm --prefix contracts test
cd backend
.venv/bin/pytest
cd ..
npm --prefix frontend run build
```
Also run `npm --prefix frontend run lint`. Existing pre-hook databases need migration `0002`; see `HOOKS_GUIDE.md` before starting the backend.
Existing databases also need the ERC-8004 profile and job-provenance migration:
```bash
cd backend
.venv/bin/alembic upgrade head
```
On Windows, use `.venv\Scripts\alembic.exe upgrade head`.
On Windows, replace `.venv/bin/pytest` with `.venv\Scripts\pytest.exe`.
## Sepolia deployment
1. Copy `contracts/.env.example` to `contracts/.env`.
2. Add a Sepolia RPC URL, a **test-only** deployer key, and optional Etherscan API key.
3. Deploy:
```bash
npm --prefix contracts run deploy:sepolia
```
4. Copy the printed token, core, Allowlist hook, and Bidding hook addresses into `frontend/.env`.
5. Set `VITE_CHAIN_ID=11155111`.
6. Set the same contract address and chain ID in `backend/.env`.
7. Rebuild/restart the frontend and backend.
Verify contracts:
```bash
cd contracts
npx hardhat verify --network sepolia MOCK_USDC_ADDRESS
npx hardhat verify --network sepolia AGENTIC_COMMERCE_ADDRESS MOCK_USDC_ADDRESS
npx hardhat verify --network sepolia ALLOWLIST_HOOK_ADDRESS AGENTIC_COMMERCE_ADDRESS OWNER_ADDRESS
npx hardhat verify --network sepolia BIDDING_HOOK_ADDRESS AGENTIC_COMMERCE_ADDRESS
```
Never commit `.env` or a private key. Use a wallet created only for Sepolia.
## Add MockUSDC to MetaMask
In MetaMask, choose **Import tokens**, select **Custom token**, paste the deployed `MockUSDC` address, use symbol `mUSDC`, and use 6 decimals. The UI's mint button calls the permissionless test faucet for 100 mUSDC.
## Deliverable hash
Both React and FastAPI calculate:
```text
keccak256(
abi.encode(
uint256 jobId,
address provider,
string deliverableUrl,
bytes32 fileHash,
uint256 submissionTimestamp
)
)
```
The exact ABI types, value order, URL bytes, provider address, file hash, and Unix timestamp must match. React uses `AbiCoder.defaultAbiCoder().encode`; Python uses `eth_abi.encode`. FastAPI recomputes the commitment and rejects mismatches. Only the `bytes32` commitment is submitted on-chain.
The UI saves the off-chain deliverable record before asking MetaMask to submit the commitment. This
prevents a confirmed blockchain transaction from losing its URL/file metadata when SQLite is busy.
Repeated clicks are guarded in the UI, and the backend treats the same deliverable commitment as an
idempotent request. Uploaded files use relative `/uploads/...` URLs so Vite can proxy them during
development and the bundled backend can serve them from the same origin in production.
Do not leave DB Browser for SQLite in an uncommitted edit transaction while the backend is running.
Choose **Write Changes** or **Revert Changes** before returning to the app; otherwise SQLite may lock
backend writes. The API waits briefly for transient locks and returns a descriptive HTTP 503 if the
database remains busy.
Reason text is stored in SQLite. The on-chain commitment is `keccak256(UTF-8 reason)` or `bytes32(0)` for an empty reason.
## API examples
Create a bid:
```bash
curl -X POST http://127.0.0.1:8000/api/jobs/1/bids \
-H "Content-Type: application/json" \
-d '{"provider_address":"0x2222222222222222222222222222222222222222","proposed_budget":"20","message":"I can deliver this job"}'
```
List bids and metadata:
```bash
curl http://127.0.0.1:8000/api/jobs/1/bids
curl http://127.0.0.1:8000/api/jobs/1/metadata
```
Interactive API documentation is available at <http://127.0.0.1:8000/docs>.
## Streamable HTTP MCP endpoint
The backend also mounts a Streamable HTTP MCP server at `/mcp/` (`/mcp` redirects there). In a local
run it is available at <http://127.0.0.1:8000/mcp/>. In a public deployment, serve the same FastAPI
app over HTTPS and set:
```env
CORS_ORIGINS=https://your-domain.example
MCP_ALLOWED_HOSTS=your-domain.example,your-domain.example:443
MCP_ALLOWED_ORIGINS=https://your-domain.example
```
The MCP wrapper exposes read-only ERC-8004/ ERC-8183 resources plus preparation tools for
deliverable hashes, evaluation reason hashes, and ERC-8004 feedback payloads. It does not hold
private keys or submit blockchain transactions.
See `docs/DEPLOY_STREAMABLE_HTTP_MCP.md` for public deployment steps and ERC-8004 registration
metadata.
For Railway-specific deployment, see `docs/DEPLOY_RAILWAY.md`. The root `railway.json` builds the
React frontend, installs backend dependencies, runs Alembic, and starts FastAPI with `/mcp/` exposed.
## ERC-8004 provider identity
The Sepolia integration uses these official registry deployments:
- Identity Registry: `0x8004A818BFB912233c491871b3d84c89A494BD9e`
- Reputation Registry: `0x8004B663056A597Dffe9eCcC1965A193B7388713`
Open **Agents**, select Ethereum Sepolia, enter an agent ID, and import it. The frontend reads
`getAgentWallet`, `tokenURI`, and `ownerOf` directly from the Identity Registry, decodes the
registration file, then persists the normalized profile through FastAPI. Base64 JSON data URIs,
URL-encoded JSON data URIs, HTTP(S) JSON, and basic `ipfs://` resolution through `ipfs.io` are
supported. HTTP and IPFS gateways must allow browser CORS requests.
On **Create Job**, choosing an ERC-8004 provider sends the imported `agentWallet` to the unchanged
ERC-8183 `createJob(address provider, ...)` call. The numeric `agentId`, registry, wallet, and name
are stored only as off-chain job provenance. Manual EOA providers and bidding jobs continue to use
their existing paths.
After an agent-backed job reaches Completed or Rejected, the recorded client or evaluator can post
ERC-8004 feedback. Completed jobs submit value `100`; rejected jobs submit value `0`; both use zero
decimals and tags `erc8183-job` plus the final status. The feedback hash commits to the job ID,
status, deliverable evidence, and evaluation reason. The UI blocks the agent owner and agentWallet
from posting self-feedback, and the Reputation Registry independently enforces its own restrictions.
### End-to-end Sepolia test with agent 9406
1. Run Alembic migration `0003`, then start the backend and frontend.
2. Connect MetaMask to Ethereum Sepolia as the client.
3. Open **Agents**, keep chain **Ethereum Sepolia**, enter `9406`, and click **Import from Identity Registry**.
4. Verify the imported card shows `ERC8183 Escrow Provider Agent`, agent ID `9406`, agent wallet
`0x85aC164b4A81eb9bFbE75b995f0D4e5F0fF0B35A`, owner, image, and the metadata active status.
5. Open **Create job**, select **ERC-8004 registered agent provider**, choose agent `9406`, enter an evaluator, and create the job.
6. On Job Details, verify the on-chain Provider equals the agent wallet and the provider identity card links to 8004scan.
7. As the client, set the budget, approve mUSDC, and fund escrow.
8. Switch MetaMask to the agentWallet account and submit a URL or file deliverable.
9. Switch to the evaluator and complete or reject the job with an optional reason.
10. While still connected as evaluator (or as a non-self client), click **Post ERC-8004 Feedback** and confirm the Reputation Registry transaction.
11. Open the agent’s 8004scan feedback tab and allow time for the indexer to refresh.
The current agent metadata reports `active: false`; the demo displays that registry metadata as-is
and does not treat it as evidence that a backend service is running.
## Contract interaction example
```javascript
const usdc = new ethers.Contract(mockUsdcAddress, mockUsdcAbi, clientSigner);
const commerce = new ethers.Contract(commerceAddress, commerceAbi, clientSigner);
const budget = ethers.parseUnits("20", 6);
await (await commerce.setBudget(jobId, budget)).wait();
await (await usdc.approve(commerceAddress, budget)).wait();
await (await commerce.fund(jobId, budget)).wait();
```
If the provider changes the budget to 30 mUSDC before the last line, `fund(jobId, 20 mUSDC)` reverts with `BudgetMismatch`.
## Single executable build
The binary build first creates the React static production bundle, then embeds it into a PyInstaller FastAPI executable.
Linux/macOS:
```bash
./scripts/build-backend-binary.sh
./backend/dist/erc8183-demo
```
Windows:
```powershell
.\scripts\build-backend-binary.ps1
.\backend\dist\erc8183-demo.exe
```
Open <http://127.0.0.1:8000>. The executable serves both the frontend and API. Build the binary on the same operating system on which it will run; PyInstaller is not a cross-compiler. Keep `.env` beside the working directory used to launch it.
## Manual Sepolia checklist
- [ ] Deploy `MockUSDC`, then deploy `AgenticCommerce` with its address.
- [ ] Update frontend/backend environment files and verify chain 11155111.
- [ ] Connect the client wallet and mint 100 test mUSDC.
- [ ] Create a plain job with a provider, evaluator, future deadline, and zero hook.
- [ ] Set 20 mUSDC budget; approve; fund; confirm 80/20 client/escrow balances.
- [ ] Connect provider; submit URL/file commitment; confirm Submitted.
- [ ] Connect evaluator; complete; confirm provider receives 20 mUSDC.
- [ ] Repeat Open rejection, Funded rejection, and Submitted rejection.
- [ ] Create short-deadline Funded and Submitted jobs; claim refunds after expiry.
- [ ] Test Allowlist creation, assignment, funding, and an unapproved participant failure.
- [ ] Test Bidding with two provider-signed bids, wait for the deadline, accept one, set the signed budget, and fund.
- [ ] Attempt forbidden actions and confirm the transaction reverts.
## Security and scope
The contracts use `SafeERC20`, `ReentrancyGuard`, checks-effects-interactions, custom errors, immutable payment-token selection, strict roles, exact state checks, deadline validation, expected-budget verification, ERC-165 hook validation, owner-controlled hook registration, `onlyACP` callbacks, and bounded hook gas. ERC-8004 is integrated as an external identity and reputation layer; it does not change ERC-8183 authorization or prove that an agent service is online. This demo has no platform fee, upgradeable proxy, ERC-4337, ERC-2771, EIP-7702, TEE, milestones, partial payments, or real-fund support.
The bid-accept endpoint checks the recorded client address supplied by the UI, but it does not provide cryptographic API authentication. On-chain `setProvider` is the authoritative protected action. Add signed API challenges before using the metadata service outside a learning environment.
This server cannot be deployed
Maintenance
ActivitySlowing
ResponsivenessNo issues