diff --git a/CLAUDE.md b/CLAUDE.md index 310c1fd..227877a 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -778,14 +778,51 @@ Skills are delivered by writing directly to the host filesystem at `$DATA_DIR/op **Advantages over ConfigMap approach**: No 1MB size limit, works before pod readiness, survives pod restarts, supports binary files and scripts. -### Default Skills +### Default Skills (22 skills) + +The stack ships 22 embedded skills organized into categories. All are installed automatically on first deploy. + +#### Infrastructure Skills + +| Skill | Contents | Purpose | +|-------|----------|---------| +| `ethereum-networks` | `SKILL.md`, `scripts/rpc.sh`, `scripts/rpc.py`, `references/erc20-methods.md`, `references/common-contracts.md` | Read-only Ethereum queries via cast/eRPC — blocks, balances, contract reads, ERC-20 lookups, ENS resolution | +| `local-ethereum-wallet` | `SKILL.md`, `scripts/signer.py`, `scripts/tx-helper.sh`, `references/web3signer-api.md` | Sign and send transactions via Web3Signer — ETH transfers, contract calls, EIP-712 typed data | +| `obol-stack` | `SKILL.md`, `scripts/kube.py` | Kubernetes cluster diagnostics — pods, logs, events, deployments via ServiceAccount API | +| `distributed-validators` | `SKILL.md`, `references/api-examples.md` | Obol DVT cluster monitoring, operator audit, exit coordination via Obol API | + +#### Agent Identity & Commerce Skills + +| Skill | Contents | Purpose | +|-------|----------|---------| +| `agent-identity` | `SKILL.md`, `scripts/identity.sh`, `references/erc8004-methods.md`, `references/abis/{Identity,Reputation,Validation}Registry.json` | Full ERC-8004 lifecycle — register, update URI/metadata, give/query reputation, request validation, pin to IPFS. Wraps cast + signer.py | +| `standards` | `SKILL.md` | ERC-8004, x402, EIP-3009, EIP-7702, ERC-4337 — spec details, integration patterns, cast-based examples | +| `orchestration` | `SKILL.md` | End-to-end dApp build (SE2) + AI agent commerce cycle (ERC-8004 + x402 discover → trust → pay → rate) | +| `ship` | `SKILL.md` | Architecture planning — what goes onchain vs offchain, contract count, chain selection, agent service patterns | + +#### Ethereum Development Skills + +| Skill | Contents | Purpose | +|-------|----------|---------| +| `addresses` | `SKILL.md` | Verified contract addresses — DeFi, tokens, bridges, ERC-8004 registries across all major chains | +| `building-blocks` | `SKILL.md` | OpenZeppelin patterns, DEX integration, oracle usage, access control | +| `concepts` | `SKILL.md` | Mental model — state machines, incentive design, gas mechanics, EOAs vs contracts | +| `gas` | `SKILL.md` | Gas optimization patterns, L2 fee structures, estimation | +| `indexing` | `SKILL.md` | The Graph, Dune, event indexing for onchain data | +| `l2s` | `SKILL.md` | L2 comparison — Base, Arbitrum, Optimism, zkSync with gas costs and use cases | +| `security` | `SKILL.md` | Smart contract vulnerability patterns, reentrancy, flash loans, MEV protection | +| `testing` | `SKILL.md` | Foundry testing — unit, fuzz, fork, invariant tests | +| `tools` | `SKILL.md` | Development tooling — Foundry, Hardhat, Scaffold-ETH 2, verification | +| `wallets` | `SKILL.md` | Wallet management — EOAs, Safe multisig, EIP-7702, key safety for AI agents | + +#### Frontend & UX Skills | Skill | Contents | Purpose | |-------|----------|---------| -| `hello` | `SKILL.md` | Smoke test — confirms skills pipeline works | -| `obol-blockchain` | `SKILL.md`, `scripts/rpc.py`, `references/erc20-methods.md`, `references/common-contracts.md` | Ethereum JSON-RPC queries, ERC-20 token ops, ENS resolution, gas estimation via the eRPC gateway | -| `obol-k8s` | `SKILL.md`, `scripts/kube.py` | Kubernetes cluster diagnostics — pods, logs, events, deployments via ServiceAccount API | -| `obol-dvt` | `SKILL.md`, `references/api-examples.md` | Obol DVT cluster monitoring, operator audit, exit coordination via Obol API | +| `frontend-playbook` | `SKILL.md` | Frontend deployment — IPFS, Vercel, ENS subdomains | +| `frontend-ux` | `SKILL.md` | Web3 UX patterns — wallet connection, transaction flows, error handling | +| `qa` | `SKILL.md` | Quality assurance — testing strategy, coverage, CI/CD patterns | +| `why` | `SKILL.md` | Why build on Ethereum — the AI agent angle with ERC-8004 and x402 | ### Skill Delivery Flow @@ -1089,14 +1126,18 @@ obol network delete ethereum- --force - `aztec/helmfile.yaml.gotmpl` - `internal/embed/defaults/` - Default stack resources - `internal/embed/infrastructure/` - Infrastructure resources (llmspy, Traefik) -- `internal/embed/skills/` - Default OpenClaw skills (hello, obol-blockchain, obol-k8s, obol-dvt) embedded in obol binary +- `internal/embed/skills/` - 22 embedded OpenClaw skills (SKILL.md files + scripts + references) -**Skills system**: +**Skills system (key files)**: - `internal/openclaw/resolve.go` - Smart instance resolution (0/1/2+ instances) -- `internal/embed/skills/hello/SKILL.md` - Hello world smoke-test skill -- `internal/embed/skills/obol-blockchain/` - Ethereum JSON-RPC, ERC-20, ENS via eRPC (SKILL.md + scripts/rpc.py + references/) -- `internal/embed/skills/obol-k8s/` - Kubernetes cluster diagnostics (SKILL.md + scripts/kube.py) -- `internal/embed/skills/obol-dvt/` - DVT cluster monitoring via Obol API (SKILL.md + references/api-examples.md) +- `internal/embed/skills/agent-identity/` - ERC-8004 lifecycle (SKILL.md + scripts/identity.sh + references/abis/ + references/erc8004-methods.md) +- `internal/embed/skills/ethereum-networks/` - Read-only blockchain queries via cast/eRPC (SKILL.md + scripts/rpc.sh + scripts/rpc.py + references/) +- `internal/embed/skills/local-ethereum-wallet/` - Transaction signing via Web3Signer (SKILL.md + scripts/signer.py + scripts/tx-helper.sh + references/) +- `internal/embed/skills/obol-stack/` - Kubernetes cluster diagnostics (SKILL.md + scripts/kube.py) +- `internal/embed/skills/distributed-validators/` - DVT cluster monitoring via Obol API (SKILL.md + references/api-examples.md) +- `internal/embed/skills/standards/` - ERC-8004, x402, EIP-3009, EIP-7702 spec reference +- `internal/embed/skills/orchestration/` - dApp build system + agent commerce cycle +- `internal/embed/skills/addresses/` - Verified contract addresses across chains - `internal/embed/embed_skills_test.go` - Unit tests for skill embedding - `internal/openclaw/skills_injection_test.go` - Unit tests for skill staging and injection - `tests/skills_smoke_test.py` - In-pod Python smoke tests for all rich skills diff --git a/README.md b/README.md index c8c8380..9b46e1a 100644 --- a/README.md +++ b/README.md @@ -163,14 +163,49 @@ When only one OpenClaw instance is installed, the instance ID is optional — it ### Skills -OpenClaw ships with four embedded skills that are installed automatically on first deploy: +OpenClaw ships with 22 embedded skills that are installed automatically on first deploy. Skills give the agent domain-specific capabilities — from querying blockchains to registering onchain identities. + +#### Infrastructure + +| Skill | Purpose | +|-------|---------| +| `ethereum-networks` | Read-only Ethereum queries via cast — blocks, balances, contract reads, ERC-20, ENS | +| `local-ethereum-wallet` | Sign and send transactions via Web3Signer — ETH transfers, contract calls, EIP-712 | +| `obol-stack` | Kubernetes cluster diagnostics — pods, logs, events, deployments | +| `distributed-validators` | Obol DVT cluster monitoring, operator audit, exit coordination | + +#### Agent Identity & Commerce + +| Skill | Purpose | +|-------|---------| +| `agent-identity` | **ERC-8004 lifecycle** — register, update URI/metadata, give/query reputation, request validation, IPFS pinning. Full ABIs included | +| `standards` | ERC-8004, x402, EIP-3009, EIP-7702 — specs, integration patterns, cast-based examples | +| `orchestration` | dApp build system + AI agent commerce cycle (ERC-8004 discover → trust → pay → rate) | +| `ship` | Architecture planning — onchain vs offchain, contract count, chain selection, agent service setup | + +#### Ethereum Development + +| Skill | Purpose | +|-------|---------| +| `addresses` | Verified contract addresses — DeFi, tokens, bridges, ERC-8004 registries across chains | +| `building-blocks` | OpenZeppelin patterns, DEX integration, oracles, access control | +| `concepts` | Mental model — state machines, incentives, gas, EOAs vs contracts | +| `gas` | Gas optimization, L2 fee structures | +| `indexing` | The Graph, Dune, event indexing | +| `l2s` | L2 comparison — Base, Arbitrum, Optimism, zkSync | +| `security` | Vulnerability patterns, reentrancy, flash loans, MEV | +| `testing` | Foundry testing — unit, fuzz, fork, invariant | +| `tools` | Foundry, Hardhat, Scaffold-ETH 2 | +| `wallets` | EOAs, Safe multisig, EIP-7702, key safety for AI agents | + +#### Frontend & QA | Skill | Purpose | |-------|---------| -| `hello` | Smoke test — confirms skills pipeline works | -| `obol-blockchain` | Ethereum JSON-RPC queries, ERC-20 token ops, ENS resolution via the eRPC gateway | -| `obol-k8s` | Kubernetes cluster diagnostics — pods, logs, events, deployments | -| `obol-dvt` | Obol DVT cluster monitoring, operator audit, exit coordination | +| `frontend-playbook` | Deployment — IPFS, Vercel, ENS subdomains | +| `frontend-ux` | Web3 UX — wallet flows, transaction states, error handling | +| `qa` | Testing strategy, coverage, CI/CD | +| `why` | Why Ethereum — the AI agent angle with ERC-8004 and x402 | Manage skills at runtime: diff --git a/internal/embed/skills/addresses/SKILL.md b/internal/embed/skills/addresses/SKILL.md index f19ab84..76e14f1 100644 --- a/internal/embed/skills/addresses/SKILL.md +++ b/internal/embed/skills/addresses/SKILL.md @@ -549,9 +549,12 @@ Source: [docs.morpho.org](https://docs.morpho.org/get-started/resources/addresse |----------|---------|--------| | IdentityRegistry | `0x8004A169FB4a3325136EB29fA0ceB6D2e539a432` | ✅ Verified | | ReputationRegistry | `0x8004BAa17C55a88189AE136b182e5fdA19dE9b63` | ✅ Verified | +| ValidationRegistry | Same CREATE2 pattern — set via `ERC8004_VALIDATION_REGISTRY` env var | ⏳ Deploying | Verified on: Mainnet, Arbitrum, Base, Optimism (CREATE2 — same address on all chains). +See `agent-identity/references/erc8004-methods.md` for the full function reference and `agent-identity/references/abis/` for JSON ABIs. + --- ## Major Tokens (Mainnet) diff --git a/internal/embed/skills/agent-identity/SKILL.md b/internal/embed/skills/agent-identity/SKILL.md new file mode 100644 index 0000000..bcdd2c8 --- /dev/null +++ b/internal/embed/skills/agent-identity/SKILL.md @@ -0,0 +1,282 @@ +--- +name: agent-identity +description: "Register, update, and manage ERC-8004 agent identities onchain. Give and query reputation. Request validation. Full lifecycle management via cast + Web3Signer." +metadata: { "openclaw": { "emoji": "\ud83e\udea8", "requires": { "bins": ["cast", "python3"] } } } +--- + +# Agent Identity (ERC-8004) + +Manage the full lifecycle of onchain agent identities — register, update, give feedback, query reputation, request validation — all through cast and the local Web3Signer. + +## When to Use + +- Registering an agent identity onchain +- Updating an agent's URI, metadata, or wallet association +- Giving or revoking reputation feedback for another agent +- Querying an agent's reputation (aggregated or individual entries) +- Requesting or responding to third-party validation +- Preparing and pinning agent registration JSON to IPFS +- Querying registration, feedback, or validation events + +## When NOT to Use + +- Reading blockchain data (balances, blocks, transactions) — use `ethereum-networks` +- Creating or managing signing keys — keys are managed by the `obol` CLI +- Deploying smart contracts — use `orchestration` +- General token operations (ERC-20 transfers, approvals) — use `local-ethereum-wallet` + +## Contract Addresses (same on 20+ chains via CREATE2) + +| Registry | Address | +|----------|---------| +| IdentityRegistry | `0x8004A169FB4a3325136EB29fA0ceB6D2e539a432` | +| ReputationRegistry | `0x8004BAa17C55a88189AE136b182e5fdA19dE9b63` | +| ValidationRegistry | Set via `ERC8004_VALIDATION_REGISTRY` env var | + +**Deployed on:** Mainnet, Base, Arbitrum, Optimism, Polygon, Avalanche, Abstract, Celo, Gnosis, Linea, Mantle, MegaETH, Monad, Scroll, Taiko, BSC + testnets. + +## Quick Start + +```bash +# 1. Register an agent with an IPFS URI +sh scripts/identity.sh --from 0xYourAddress register --uri "ipfs://QmYourRegistrationHash" + +# 2. Query your agent's URI +sh scripts/identity.sh agent-uri 42 + +# 3. Check an agent's reputation +sh scripts/identity.sh reputation 42 --tag1 "quality" --tag2 "30days" + +# 4. Give feedback after interacting with agent 42 +sh scripts/identity.sh --from 0xYourAddress feedback 42 95 0 "quality" "weather" \ + --endpoint "https://weather.agent.example.com" +``` + +## Identity Lifecycle + +### 1. Prepare Registration JSON + +```bash +# Generate the registration JSON +sh scripts/identity.sh prepare-registration \ + --name "WeatherBot" \ + --description "Real-time weather data via x402 micropayments" \ + --services '[{"name":"A2A","endpoint":"https://weather.example.com/.well-known/agent-card.json","version":"0.3.0"}]' \ + --x402 \ + --trust '["reputation"]' +``` + +Output: +```json +{ + "type": "https://eips.ethereum.org/EIPS/eip-8004#registration-v1", + "name": "WeatherBot", + "description": "Real-time weather data via x402 micropayments", + "services": [{"name":"A2A","endpoint":"https://weather.example.com/.well-known/agent-card.json","version":"0.3.0"}], + "x402Support": true, + "active": true, + "supportedTrust": ["reputation"] +} +``` + +### 2. Pin to IPFS and Register + +```bash +# Pin the JSON and register in one step +sh scripts/identity.sh --from 0xYourAddress pin-registration \ + --name "WeatherBot" \ + --description "Real-time weather data via x402 micropayments" \ + --services '[{"name":"A2A","endpoint":"https://weather.example.com/.well-known/agent-card.json","version":"0.3.0"}]' \ + --x402 + +# Or do it manually: +# Pin file +sh scripts/identity.sh pin registration.json +# → ipfs://QmYourCID + +# Register with the IPFS URI +sh scripts/identity.sh --from 0xYourAddress register --uri "ipfs://QmYourCID" +``` + +The `register` call returns a transaction hash. The agentId (ERC-721 tokenId) is in the `Registered` event logs. + +### 3. Set Metadata + +```bash +# Set arbitrary key-value metadata (value is hex-encoded bytes) +sh scripts/identity.sh --from 0xYourAddress set-metadata 42 "x402.supported" 0x01 +sh scripts/identity.sh --from 0xYourAddress set-metadata 42 "mcp.version" 0x323032352d30362d3138 + +# Read it back +sh scripts/identity.sh metadata 42 "x402.supported" +``` + +### 4. Update URI + +When your agent's services change, re-pin the updated JSON and update the onchain URI: + +```bash +sh scripts/identity.sh --from 0xYourAddress set-uri 42 "ipfs://QmNewRegistrationHash" +``` + +### 5. Verify Domain Ownership + +Place a file at your agent's domain for clients to verify: + +``` +https://weather.example.com/.well-known/agent-registration.json +``` + +Content: +```json +{ + "agentId": 42, + "agentRegistry": "eip155:8453:0x8004A169FB4a3325136EB29fA0ceB6D2e539a432", + "owner": "0xYourWalletAddress" +} +``` + +## Reputation Lifecycle + +### Give Feedback + +After interacting with an agent, post feedback onchain: + +```bash +# Quality score: 95/100 +sh scripts/identity.sh --from 0xYourAddress feedback 42 95 0 "quality" "weather" \ + --endpoint "https://weather.agent.example.com" \ + --uri "ipfs://QmFeedbackDetails" + +# Uptime: 99.77% +sh scripts/identity.sh --from 0xYourAddress feedback 42 9977 2 "uptime" "30days" + +# Negative feedback +sh scripts/identity.sh --from 0xYourAddress feedback 42 -25 0 "quality" "bad-data" +``` + +### Query Reputation + +```bash +# Aggregated summary (all clients, filtered by tags) +sh scripts/identity.sh reputation 42 --tag1 "quality" --tag2 "30days" +# → count value decimals + +# Filter by specific clients +sh scripts/identity.sh reputation 42 --clients "[0xClient1,0xClient2]" --tag1 "uptime" + +# Read a specific feedback entry +sh scripts/identity.sh read-feedback 42 0xClientAddress 0 + +# List all clients who gave feedback +sh scripts/identity.sh clients 42 +``` + +### Revoke and Respond + +```bash +# Revoke your own feedback (caller must be original poster) +sh scripts/identity.sh --from 0xYourAddress revoke-feedback 42 0 + +# Agent owner responds to feedback +sh scripts/identity.sh --from 0xAgentOwner respond 42 0xClientAddress 0 \ + "ipfs://QmResponseDetails" 0x0000000000000000000000000000000000000000000000000000000000000000 +``` + +## Validation Lifecycle + +Third-party validators independently verify agent work. + +```bash +# Request validation from a trusted validator +sh scripts/identity.sh --from 0xYourAddress request-validation \ + 0xValidatorAddress 42 "ipfs://QmValidationRequest" \ + $(cast keccak "validation-request-data") + +# Validator responds with score (0-100) +sh scripts/identity.sh --from 0xValidatorAddress validation-response \ + 0xRequestHash 85 "ipfs://QmValidationReport" \ + $(cast keccak "validation-response-data") "quality" + +# Query validation status +sh scripts/identity.sh validation-status 0xRequestHash + +# Get all validations for an agent +sh scripts/identity.sh agent-validations 42 + +# Aggregated validation summary +sh scripts/identity.sh validation-summary 42 --tag "quality" +``` + +## Event Queries + +```bash +# All registrations +sh scripts/identity.sh events registered --from-block 0 + +# Feedback events for a specific agent +sh scripts/identity.sh events feedback 42 --from-block 20000000 + +# URI update events +sh scripts/identity.sh events uri-updated 42 --from-block 20000000 +``` + +## Cross-Chain Patterns + +Same contract addresses on 20+ chains (CREATE2 deployment). Register on the cheapest chain, reference from any other: + +```bash +# Register on Base (cheapest gas) +sh scripts/identity.sh --network base --from 0xYourAddress register --uri "ipfs://QmYourHash" + +# Query from Arbitrum +sh scripts/identity.sh --network arbitrum agent-uri 42 + +# Agent identifier format (CAIP-10) +# eip155:8453:0x8004A169FB4a3325136EB29fA0ceB6D2e539a432 → Base +# eip155:42161:0x8004A169FB4a3325136EB29fA0ceB6D2e539a432 → Arbitrum +``` + +## How Write Operations Work + +Write operations follow a two-step process: + +1. **Encode calldata** with `cast calldata` — constructs the ABI-encoded function call +2. **Sign and send** via `signer.py send-tx --data` — delegates to Web3Signer for signing, submits via eRPC + +Every write shows a confirmation prompt with target, calldata, gas estimate, and network before sending. + +## Constraints + +- **Shell is `sh`, not `bash`** — no bashisms +- **Signing via signer.py** — all writes go through Web3Signer. Never call `cast send` with a private key +- **Reads via cast** — all reads use `cast call` through eRPC +- **Confirm before sending** — always show the user what will be signed before executing +- **No key creation** — keys are managed by `obol agent init` +- **IPFS pinning requires an IPFS node** — defaults to in-cluster kubo at `http://ipfs.ipfs.svc.cluster.local:5001` +- **ValidationRegistry address** — must be set via `ERC8004_VALIDATION_REGISTRY` env var (not yet on all chains) + +## Environment Variables + +| Variable | Default | Description | +|----------|---------|-------------| +| `ERPC_URL` | `http://erpc.erpc.svc.cluster.local:4000/rpc` | eRPC gateway base URL | +| `ERPC_NETWORK` | `mainnet` | Default network for routing | +| `SIGNER_SCRIPT` | `../local-ethereum-wallet/scripts/signer.py` | Path to Web3Signer script | +| `IPFS_API` | `http://ipfs.ipfs.svc.cluster.local:5001/api/v0` | IPFS API endpoint | +| `ERC8004_VALIDATION_REGISTRY` | (none) | ValidationRegistry contract address | + +## See Also + +- `references/erc8004-methods.md` — complete function signature reference for all three registries +- `references/abis/` — full JSON ABIs for IdentityRegistry, ReputationRegistry, ValidationRegistry +- `standards` skill — ERC-8004 specification overview, x402 synergy, cross-chain patterns +- `ethereum-networks` skill — read-only blockchain queries +- `local-ethereum-wallet` skill — transaction signing pipeline +- `orchestration` skill — full agent commerce cycle (ERC-8004 + x402) + +## Resources + +- https://www.8004.org +- https://eips.ethereum.org/EIPS/eip-8004 +- https://github.com/erc-8004/erc-8004-contracts diff --git a/internal/embed/skills/agent-identity/references/abis/IdentityRegistry.json b/internal/embed/skills/agent-identity/references/abis/IdentityRegistry.json new file mode 100644 index 0000000..7875856 --- /dev/null +++ b/internal/embed/skills/agent-identity/references/abis/IdentityRegistry.json @@ -0,0 +1 @@ +[{"inputs":[],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[{"internalType":"address","name":"target","type":"address"}],"name":"AddressEmptyCode","type":"error"},{"inputs":[],"name":"ECDSAInvalidSignature","type":"error"},{"inputs":[{"internalType":"uint256","name":"length","type":"uint256"}],"name":"ECDSAInvalidSignatureLength","type":"error"},{"inputs":[{"internalType":"bytes32","name":"s","type":"bytes32"}],"name":"ECDSAInvalidSignatureS","type":"error"},{"inputs":[{"internalType":"address","name":"implementation","type":"address"}],"name":"ERC1967InvalidImplementation","type":"error"},{"inputs":[],"name":"ERC1967NonPayable","type":"error"},{"inputs":[{"internalType":"address","name":"sender","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"address","name":"owner","type":"address"}],"name":"ERC721IncorrectOwner","type":"error"},{"inputs":[{"internalType":"address","name":"operator","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"ERC721InsufficientApproval","type":"error"},{"inputs":[{"internalType":"address","name":"approver","type":"address"}],"name":"ERC721InvalidApprover","type":"error"},{"inputs":[{"internalType":"address","name":"operator","type":"address"}],"name":"ERC721InvalidOperator","type":"error"},{"inputs":[{"internalType":"address","name":"owner","type":"address"}],"name":"ERC721InvalidOwner","type":"error"},{"inputs":[{"internalType":"address","name":"receiver","type":"address"}],"name":"ERC721InvalidReceiver","type":"error"},{"inputs":[{"internalType":"address","name":"sender","type":"address"}],"name":"ERC721InvalidSender","type":"error"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"ERC721NonexistentToken","type":"error"},{"inputs":[],"name":"FailedCall","type":"error"},{"inputs":[],"name":"InvalidInitialization","type":"error"},{"inputs":[],"name":"NotInitializing","type":"error"},{"inputs":[{"internalType":"address","name":"owner","type":"address"}],"name":"OwnableInvalidOwner","type":"error"},{"inputs":[{"internalType":"address","name":"account","type":"address"}],"name":"OwnableUnauthorizedAccount","type":"error"},{"inputs":[],"name":"UUPSUnauthorizedCallContext","type":"error"},{"inputs":[{"internalType":"bytes32","name":"slot","type":"bytes32"}],"name":"UUPSUnsupportedProxiableUUID","type":"error"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"approved","type":"address"},{"indexed":true,"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"operator","type":"address"},{"indexed":false,"internalType":"bool","name":"approved","type":"bool"}],"name":"ApprovalForAll","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"_fromTokenId","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"_toTokenId","type":"uint256"}],"name":"BatchMetadataUpdate","type":"event"},{"anonymous":false,"inputs":[],"name":"EIP712DomainChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint64","name":"version","type":"uint64"}],"name":"Initialized","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"agentId","type":"uint256"},{"indexed":true,"internalType":"string","name":"indexedMetadataKey","type":"string"},{"indexed":false,"internalType":"string","name":"metadataKey","type":"string"},{"indexed":false,"internalType":"bytes","name":"metadataValue","type":"bytes"}],"name":"MetadataSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"_tokenId","type":"uint256"}],"name":"MetadataUpdate","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"previousOwner","type":"address"},{"indexed":true,"internalType":"address","name":"newOwner","type":"address"}],"name":"OwnershipTransferred","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"agentId","type":"uint256"},{"indexed":false,"internalType":"string","name":"agentURI","type":"string"},{"indexed":true,"internalType":"address","name":"owner","type":"address"}],"name":"Registered","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"},{"indexed":true,"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"Transfer","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"agentId","type":"uint256"},{"indexed":false,"internalType":"string","name":"newURI","type":"string"},{"indexed":true,"internalType":"address","name":"updatedBy","type":"address"}],"name":"URIUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"implementation","type":"address"}],"name":"Upgraded","type":"event"},{"inputs":[],"name":"UPGRADE_INTERFACE_VERSION","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"approve","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"}],"name":"balanceOf","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"eip712Domain","outputs":[{"internalType":"bytes1","name":"fields","type":"bytes1"},{"internalType":"string","name":"name","type":"string"},{"internalType":"string","name":"version","type":"string"},{"internalType":"uint256","name":"chainId","type":"uint256"},{"internalType":"address","name":"verifyingContract","type":"address"},{"internalType":"bytes32","name":"salt","type":"bytes32"},{"internalType":"uint256[]","name":"extensions","type":"uint256[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"agentId","type":"uint256"}],"name":"getAgentWallet","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"getApproved","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"agentId","type":"uint256"},{"internalType":"string","name":"metadataKey","type":"string"}],"name":"getMetadata","outputs":[{"internalType":"bytes","name":"","type":"bytes"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getVersion","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"pure","type":"function"},{"inputs":[],"name":"initialize","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"},{"internalType":"address","name":"operator","type":"address"}],"name":"isApprovedForAll","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"name","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"ownerOf","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"proxiableUUID","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"register","outputs":[{"internalType":"uint256","name":"agentId","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"agentURI","type":"string"},{"components":[{"internalType":"string","name":"metadataKey","type":"string"},{"internalType":"bytes","name":"metadataValue","type":"bytes"}],"internalType":"struct IdentityRegistryUpgradeable.MetadataEntry[]","name":"metadata","type":"tuple[]"}],"name":"register","outputs":[{"internalType":"uint256","name":"agentId","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"agentURI","type":"string"}],"name":"register","outputs":[{"internalType":"uint256","name":"agentId","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"renounceOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"safeTransferFrom","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"bytes","name":"data","type":"bytes"}],"name":"safeTransferFrom","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"agentId","type":"uint256"},{"internalType":"string","name":"newURI","type":"string"}],"name":"setAgentURI","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"agentId","type":"uint256"},{"internalType":"address","name":"newWallet","type":"address"},{"internalType":"uint256","name":"deadline","type":"uint256"},{"internalType":"bytes","name":"signature","type":"bytes"}],"name":"setAgentWallet","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"operator","type":"address"},{"internalType":"bool","name":"approved","type":"bool"}],"name":"setApprovalForAll","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"agentId","type":"uint256"},{"internalType":"string","name":"metadataKey","type":"string"},{"internalType":"bytes","name":"metadataValue","type":"bytes"}],"name":"setMetadata","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes4","name":"interfaceId","type":"bytes4"}],"name":"supportsInterface","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"symbol","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"tokenURI","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"transferFrom","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"agentId","type":"uint256"}],"name":"unsetAgentWallet","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"newImplementation","type":"address"},{"internalType":"bytes","name":"data","type":"bytes"}],"name":"upgradeToAndCall","outputs":[],"stateMutability":"payable","type":"function"}] diff --git a/internal/embed/skills/agent-identity/references/abis/ReputationRegistry.json b/internal/embed/skills/agent-identity/references/abis/ReputationRegistry.json new file mode 100644 index 0000000..5693254 --- /dev/null +++ b/internal/embed/skills/agent-identity/references/abis/ReputationRegistry.json @@ -0,0 +1 @@ +[{"inputs":[],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[{"internalType":"address","name":"target","type":"address"}],"name":"AddressEmptyCode","type":"error"},{"inputs":[{"internalType":"address","name":"implementation","type":"address"}],"name":"ERC1967InvalidImplementation","type":"error"},{"inputs":[],"name":"ERC1967NonPayable","type":"error"},{"inputs":[],"name":"FailedCall","type":"error"},{"inputs":[],"name":"InvalidInitialization","type":"error"},{"inputs":[],"name":"NotInitializing","type":"error"},{"inputs":[{"internalType":"address","name":"owner","type":"address"}],"name":"OwnableInvalidOwner","type":"error"},{"inputs":[{"internalType":"address","name":"account","type":"address"}],"name":"OwnableUnauthorizedAccount","type":"error"},{"inputs":[],"name":"UUPSUnauthorizedCallContext","type":"error"},{"inputs":[{"internalType":"bytes32","name":"slot","type":"bytes32"}],"name":"UUPSUnsupportedProxiableUUID","type":"error"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"agentId","type":"uint256"},{"indexed":true,"internalType":"address","name":"clientAddress","type":"address"},{"indexed":true,"internalType":"uint64","name":"feedbackIndex","type":"uint64"}],"name":"FeedbackRevoked","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint64","name":"version","type":"uint64"}],"name":"Initialized","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"agentId","type":"uint256"},{"indexed":true,"internalType":"address","name":"clientAddress","type":"address"},{"indexed":false,"internalType":"uint64","name":"feedbackIndex","type":"uint64"},{"indexed":false,"internalType":"int128","name":"value","type":"int128"},{"indexed":false,"internalType":"uint8","name":"valueDecimals","type":"uint8"},{"indexed":true,"internalType":"string","name":"indexedTag1","type":"string"},{"indexed":false,"internalType":"string","name":"tag1","type":"string"},{"indexed":false,"internalType":"string","name":"tag2","type":"string"},{"indexed":false,"internalType":"string","name":"endpoint","type":"string"},{"indexed":false,"internalType":"string","name":"feedbackURI","type":"string"},{"indexed":false,"internalType":"bytes32","name":"feedbackHash","type":"bytes32"}],"name":"NewFeedback","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"previousOwner","type":"address"},{"indexed":true,"internalType":"address","name":"newOwner","type":"address"}],"name":"OwnershipTransferred","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"agentId","type":"uint256"},{"indexed":true,"internalType":"address","name":"clientAddress","type":"address"},{"indexed":false,"internalType":"uint64","name":"feedbackIndex","type":"uint64"},{"indexed":true,"internalType":"address","name":"responder","type":"address"},{"indexed":false,"internalType":"string","name":"responseURI","type":"string"},{"indexed":false,"internalType":"bytes32","name":"responseHash","type":"bytes32"}],"name":"ResponseAppended","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"implementation","type":"address"}],"name":"Upgraded","type":"event"},{"inputs":[],"name":"UPGRADE_INTERFACE_VERSION","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"agentId","type":"uint256"},{"internalType":"address","name":"clientAddress","type":"address"},{"internalType":"uint64","name":"feedbackIndex","type":"uint64"},{"internalType":"string","name":"responseURI","type":"string"},{"internalType":"bytes32","name":"responseHash","type":"bytes32"}],"name":"appendResponse","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"agentId","type":"uint256"}],"name":"getClients","outputs":[{"internalType":"address[]","name":"","type":"address[]"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getIdentityRegistry","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"agentId","type":"uint256"},{"internalType":"address","name":"clientAddress","type":"address"}],"name":"getLastIndex","outputs":[{"internalType":"uint64","name":"","type":"uint64"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"agentId","type":"uint256"},{"internalType":"address","name":"clientAddress","type":"address"},{"internalType":"uint64","name":"feedbackIndex","type":"uint64"},{"internalType":"address[]","name":"responders","type":"address[]"}],"name":"getResponseCount","outputs":[{"internalType":"uint64","name":"count","type":"uint64"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"agentId","type":"uint256"},{"internalType":"address[]","name":"clientAddresses","type":"address[]"},{"internalType":"string","name":"tag1","type":"string"},{"internalType":"string","name":"tag2","type":"string"}],"name":"getSummary","outputs":[{"internalType":"uint64","name":"count","type":"uint64"},{"internalType":"int128","name":"summaryValue","type":"int128"},{"internalType":"uint8","name":"summaryValueDecimals","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getVersion","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"agentId","type":"uint256"},{"internalType":"int128","name":"value","type":"int128"},{"internalType":"uint8","name":"valueDecimals","type":"uint8"},{"internalType":"string","name":"tag1","type":"string"},{"internalType":"string","name":"tag2","type":"string"},{"internalType":"string","name":"endpoint","type":"string"},{"internalType":"string","name":"feedbackURI","type":"string"},{"internalType":"bytes32","name":"feedbackHash","type":"bytes32"}],"name":"giveFeedback","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"identityRegistry_","type":"address"}],"name":"initialize","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"proxiableUUID","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"agentId","type":"uint256"},{"internalType":"address[]","name":"clientAddresses","type":"address[]"},{"internalType":"string","name":"tag1","type":"string"},{"internalType":"string","name":"tag2","type":"string"},{"internalType":"bool","name":"includeRevoked","type":"bool"}],"name":"readAllFeedback","outputs":[{"internalType":"address[]","name":"clients","type":"address[]"},{"internalType":"uint64[]","name":"feedbackIndexes","type":"uint64[]"},{"internalType":"int128[]","name":"values","type":"int128[]"},{"internalType":"uint8[]","name":"valueDecimals","type":"uint8[]"},{"internalType":"string[]","name":"tag1s","type":"string[]"},{"internalType":"string[]","name":"tag2s","type":"string[]"},{"internalType":"bool[]","name":"revokedStatuses","type":"bool[]"}],"name":"readAllFeedback","stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"agentId","type":"uint256"},{"internalType":"address","name":"clientAddress","type":"address"},{"internalType":"uint64","name":"feedbackIndex","type":"uint64"}],"name":"readFeedback","outputs":[{"internalType":"int128","name":"value","type":"int128"},{"internalType":"uint8","name":"valueDecimals","type":"uint8"},{"internalType":"string","name":"tag1","type":"string"},{"internalType":"string","name":"tag2","type":"string"},{"internalType":"bool","name":"isRevoked","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"renounceOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"agentId","type":"uint256"},{"internalType":"uint64","name":"feedbackIndex","type":"uint64"}],"name":"revokeFeedback","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"newImplementation","type":"address"},{"internalType":"bytes","name":"data","type":"bytes"}],"name":"upgradeToAndCall","outputs":[],"stateMutability":"payable","type":"function"}] diff --git a/internal/embed/skills/agent-identity/references/abis/ValidationRegistry.json b/internal/embed/skills/agent-identity/references/abis/ValidationRegistry.json new file mode 100644 index 0000000..160f9cc --- /dev/null +++ b/internal/embed/skills/agent-identity/references/abis/ValidationRegistry.json @@ -0,0 +1 @@ +[{"inputs":[],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[{"internalType":"address","name":"target","type":"address"}],"name":"AddressEmptyCode","type":"error"},{"inputs":[{"internalType":"address","name":"implementation","type":"address"}],"name":"ERC1967InvalidImplementation","type":"error"},{"inputs":[],"name":"ERC1967NonPayable","type":"error"},{"inputs":[],"name":"FailedCall","type":"error"},{"inputs":[],"name":"InvalidInitialization","type":"error"},{"inputs":[],"name":"NotInitializing","type":"error"},{"inputs":[{"internalType":"address","name":"owner","type":"address"}],"name":"OwnableInvalidOwner","type":"error"},{"inputs":[{"internalType":"address","name":"account","type":"address"}],"name":"OwnableUnauthorizedAccount","type":"error"},{"inputs":[],"name":"UUPSUnauthorizedCallContext","type":"error"},{"inputs":[{"internalType":"bytes32","name":"slot","type":"bytes32"}],"name":"UUPSUnsupportedProxiableUUID","type":"error"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint64","name":"version","type":"uint64"}],"name":"Initialized","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"previousOwner","type":"address"},{"indexed":true,"internalType":"address","name":"newOwner","type":"address"}],"name":"OwnershipTransferred","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"implementation","type":"address"}],"name":"Upgraded","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"validatorAddress","type":"address"},{"indexed":true,"internalType":"uint256","name":"agentId","type":"uint256"},{"indexed":false,"internalType":"string","name":"requestURI","type":"string"},{"indexed":true,"internalType":"bytes32","name":"requestHash","type":"bytes32"}],"name":"ValidationRequest","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"validatorAddress","type":"address"},{"indexed":true,"internalType":"uint256","name":"agentId","type":"uint256"},{"indexed":true,"internalType":"bytes32","name":"requestHash","type":"bytes32"},{"indexed":false,"internalType":"uint8","name":"response","type":"uint8"},{"indexed":false,"internalType":"string","name":"responseURI","type":"string"},{"indexed":false,"internalType":"bytes32","name":"responseHash","type":"bytes32"},{"indexed":false,"internalType":"string","name":"tag","type":"string"}],"name":"ValidationResponse","type":"event"},{"inputs":[],"name":"UPGRADE_INTERFACE_VERSION","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"agentId","type":"uint256"}],"name":"getAgentValidations","outputs":[{"internalType":"bytes32[]","name":"","type":"bytes32[]"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getIdentityRegistry","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"agentId","type":"uint256"},{"internalType":"address[]","name":"validatorAddresses","type":"address[]"},{"internalType":"string","name":"tag","type":"string"}],"name":"getSummary","outputs":[{"internalType":"uint64","name":"count","type":"uint64"},{"internalType":"uint8","name":"avgResponse","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"requestHash","type":"bytes32"}],"name":"getValidationStatus","outputs":[{"internalType":"address","name":"validatorAddress","type":"address"},{"internalType":"uint256","name":"agentId","type":"uint256"},{"internalType":"uint8","name":"response","type":"uint8"},{"internalType":"bytes32","name":"responseHash","type":"bytes32"},{"internalType":"string","name":"tag","type":"string"},{"internalType":"uint256","name":"lastUpdate","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"validatorAddress","type":"address"}],"name":"getValidatorRequests","outputs":[{"internalType":"bytes32[]","name":"","type":"bytes32[]"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getVersion","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"identityRegistry_","type":"address"}],"name":"initialize","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"proxiableUUID","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"renounceOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"newImplementation","type":"address"},{"internalType":"bytes","name":"data","type":"bytes"}],"name":"upgradeToAndCall","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"validatorAddress","type":"address"},{"internalType":"uint256","name":"agentId","type":"uint256"},{"internalType":"string","name":"requestURI","type":"string"},{"internalType":"bytes32","name":"requestHash","type":"bytes32"}],"name":"validationRequest","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"requestHash","type":"bytes32"},{"internalType":"uint8","name":"response","type":"uint8"},{"internalType":"string","name":"responseURI","type":"string"},{"internalType":"bytes32","name":"responseHash","type":"bytes32"},{"internalType":"string","name":"tag","type":"string"}],"name":"validationResponse","outputs":[],"stateMutability":"nonpayable","type":"function"}] diff --git a/internal/embed/skills/agent-identity/references/erc8004-methods.md b/internal/embed/skills/agent-identity/references/erc8004-methods.md new file mode 100644 index 0000000..f39f6ae --- /dev/null +++ b/internal/embed/skills/agent-identity/references/erc8004-methods.md @@ -0,0 +1,110 @@ +# ERC-8004 Method Reference + +Quick reference for all three ERC-8004 registry contracts. Same addresses on 20+ chains (CREATE2). + +## IdentityRegistry — `0x8004A169FB4a3325136EB29fA0ceB6D2e539a432` + +### Write Functions + +| Function | Signature | Description | +|----------|-----------|-------------| +| `register()` | `register()(uint256)` | Register with no URI or metadata. Returns agentId | +| `register(string)` | `register(string)(uint256)` | Register with agentURI. Returns agentId | +| `register(string,(string,bytes)[])` | `register(string,(string,bytes)[])(uint256)` | Register with agentURI + metadata entries. Returns agentId | +| `setAgentURI` | `setAgentURI(uint256,string)` | Update agent's URI. Owner only | +| `setMetadata` | `setMetadata(uint256,string,bytes)` | Set arbitrary metadata key-value. Owner only | +| `setAgentWallet` | `setAgentWallet(uint256,address,uint256,bytes)` | Set agent's wallet address. Requires EIP-712 signature from new wallet | +| `unsetAgentWallet` | `unsetAgentWallet(uint256)` | Remove agent's wallet association. Owner only | +| `transferFrom` | `transferFrom(address,address,uint256)` | Transfer agent identity (ERC-721) | +| `safeTransferFrom` | `safeTransferFrom(address,address,uint256)` | Safe transfer with receiver check | + +### Read Functions + +| Function | Signature | Returns | Description | +|----------|-----------|---------|-------------| +| `tokenURI` | `tokenURI(uint256)(string)` | string | Agent's registration URI | +| `ownerOf` | `ownerOf(uint256)(address)` | address | Owner of agent identity | +| `getAgentWallet` | `getAgentWallet(uint256)(address)` | address | Agent's associated wallet | +| `getMetadata` | `getMetadata(uint256,string)(bytes)` | bytes | Metadata value for key | +| `balanceOf` | `balanceOf(address)(uint256)` | uint256 | Number of agents owned by address | +| `name` | `name()(string)` | string | Registry name | +| `symbol` | `symbol()(string)` | string | Registry symbol | +| `getVersion` | `getVersion()(string)` | string | Contract version | + +### Events + +| Event | Signature | Indexed Fields | +|-------|-----------|----------------| +| `Registered` | `Registered(uint256,string,address)` | agentId, owner | +| `URIUpdated` | `URIUpdated(uint256,string,address)` | agentId, updatedBy | +| `MetadataSet` | `MetadataSet(uint256,string,string,bytes)` | agentId, indexedMetadataKey | +| `Transfer` | `Transfer(address,address,uint256)` | from, to, tokenId | + +--- + +## ReputationRegistry — `0x8004BAa17C55a88189AE136b182e5fdA19dE9b63` + +### Write Functions + +| Function | Signature | Description | +|----------|-----------|-------------| +| `giveFeedback` | `giveFeedback(uint256,int128,uint8,string,string,string,string,bytes32)` | Post feedback for an agent. Args: agentId, value, valueDecimals, tag1, tag2, endpoint, feedbackURI, feedbackHash | +| `revokeFeedback` | `revokeFeedback(uint256,uint64)` | Revoke previously posted feedback. Caller must be original poster. Args: agentId, feedbackIndex | +| `appendResponse` | `appendResponse(uint256,address,uint64,string,bytes32)` | Agent responds to feedback. Args: agentId, clientAddress, feedbackIndex, responseURI, responseHash | + +### Read Functions + +| Function | Signature | Returns | Description | +|----------|-----------|---------|-------------| +| `getSummary` | `getSummary(uint256,address[],string,string)(uint64,int128,uint8)` | count, value, decimals | Aggregated reputation. Filter by clients, tag1, tag2 | +| `readFeedback` | `readFeedback(uint256,address,uint64)(int128,uint8,string,string,bool)` | value, decimals, tag1, tag2, isRevoked | Single feedback entry | +| `readAllFeedback` | `readAllFeedback(uint256,address[],string,string,bool)` | arrays | All matching feedback. Filter by clients, tags, includeRevoked | +| `getClients` | `getClients(uint256)(address[])` | address[] | All clients who gave feedback | +| `getLastIndex` | `getLastIndex(uint256,address)(uint64)` | uint64 | Last feedback index for client | +| `getResponseCount` | `getResponseCount(uint256,address,uint64,address[])(uint64)` | uint64 | Number of responses to a feedback entry | +| `getIdentityRegistry` | `getIdentityRegistry()(address)` | address | Linked IdentityRegistry address | + +### Events + +| Event | Signature | Indexed Fields | +|-------|-----------|----------------| +| `NewFeedback` | `NewFeedback(uint256,address,uint64,int128,uint8,string,string,string,string,string,bytes32)` | agentId, clientAddress, indexedTag1 | +| `FeedbackRevoked` | `FeedbackRevoked(uint256,address,uint64)` | agentId, clientAddress, feedbackIndex | +| `ResponseAppended` | `ResponseAppended(uint256,address,uint64,address,string,bytes32)` | agentId, clientAddress, responder | + +### Feedback Value Conventions + +| Metric | Value | Decimals | Meaning | +|--------|-------|----------|---------| +| Quality score | 87 | 0 | 87/100 quality | +| Uptime percentage | 9977 | 2 | 99.77% uptime | +| Negative rating | -50 | 0 | Negative feedback | +| Precise score | 85500 | 3 | 85.500 | + +--- + +## ValidationRegistry — (Same CREATE2 pattern, check deployment status) + +### Write Functions + +| Function | Signature | Description | +|----------|-----------|-------------| +| `validationRequest` | `validationRequest(address,uint256,string,bytes32)` | Request validation. Args: validatorAddress, agentId, requestURI, requestHash | +| `validationResponse` | `validationResponse(bytes32,uint8,string,bytes32,string)` | Respond to validation request. Args: requestHash, response (0-100), responseURI, responseHash, tag | + +### Read Functions + +| Function | Signature | Returns | Description | +|----------|-----------|---------|-------------| +| `getValidationStatus` | `getValidationStatus(bytes32)(address,uint256,uint8,bytes32,string,uint256)` | validator, agentId, response, responseHash, tag, lastUpdate | Status of a validation request | +| `getAgentValidations` | `getAgentValidations(uint256)(bytes32[])` | bytes32[] | All validation request hashes for an agent | +| `getValidatorRequests` | `getValidatorRequests(address)(bytes32[])` | bytes32[] | All requests assigned to a validator | +| `getSummary` | `getSummary(uint256,address[],string)(uint64,uint8)` | count, avgResponse | Aggregated validation score | +| `getIdentityRegistry` | `getIdentityRegistry()(address)` | address | Linked IdentityRegistry address | + +### Events + +| Event | Signature | Indexed Fields | +|-------|-----------|----------------| +| `ValidationRequest` | `ValidationRequest(address,uint256,string,bytes32)` | validatorAddress, agentId, requestHash | +| `ValidationResponse` | `ValidationResponse(address,uint256,bytes32,uint8,string,bytes32,string)` | validatorAddress, agentId, requestHash | diff --git a/internal/embed/skills/agent-identity/scripts/identity.sh b/internal/embed/skills/agent-identity/scripts/identity.sh new file mode 100644 index 0000000..c322baf --- /dev/null +++ b/internal/embed/skills/agent-identity/scripts/identity.sh @@ -0,0 +1,488 @@ +#!/bin/sh +# identity.sh — ERC-8004 agent identity lifecycle management via Foundry's cast. +# Handles registration, metadata, reputation, validation, and IPFS pinning. +# +# Read operations use cast call directly via eRPC. +# Write operations encode calldata with cast, then delegate to signer.py for signing/submission. +# +# Usage: sh scripts/identity.sh [--network ] [--from
] [args...] +# +# Environment: +# ERPC_URL Base URL for eRPC gateway (default: http://erpc.erpc.svc.cluster.local:4000/rpc) +# ERPC_NETWORK Default network (default: mainnet) +# SIGNER_SCRIPT Path to signer.py (default: ../local-ethereum-wallet/scripts/signer.py) +# IPFS_API IPFS API endpoint (default: http://ipfs.ipfs.svc.cluster.local:5001/api/v0) +set -eu + +ERPC_BASE="${ERPC_URL:-http://erpc.erpc.svc.cluster.local:4000/rpc}" +NETWORK="${ERPC_NETWORK:-mainnet}" +FROM_ADDR="" +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +SIGNER="${SIGNER_SCRIPT:-$SCRIPT_DIR/../../local-ethereum-wallet/scripts/signer.py}" +IPFS_API="${IPFS_API:-http://ipfs.ipfs.svc.cluster.local:5001/api/v0}" + +# Contract addresses (same on all chains via CREATE2) +IDENTITY_REGISTRY="0x8004A169FB4a3325136EB29fA0ceB6D2e539a432" +REPUTATION_REGISTRY="0x8004BAa17C55a88189AE136b182e5fdA19dE9b63" +# ValidationRegistry uses same CREATE2 pattern — update when deployed +VALIDATION_REGISTRY="${ERC8004_VALIDATION_REGISTRY:-}" + +# Parse global flags +while [ $# -gt 0 ]; do + case "$1" in + --network) NETWORK="$2"; shift 2 ;; + --network=*) NETWORK="${1#--network=}"; shift ;; + --from) FROM_ADDR="$2"; shift 2 ;; + --from=*) FROM_ADDR="${1#--from=}"; shift ;; + *) break ;; + esac +done + +RPC_URL="${ERPC_BASE}/${NETWORK}" + +# --- Helpers --- + +die() { echo "ERROR: $*" >&2; exit 1; } + +require_from() { + [ -n "$FROM_ADDR" ] || die "--from
is required for write operations" +} + +require_validation_registry() { + [ -n "$VALIDATION_REGISTRY" ] || die "ValidationRegistry address not set. Export ERC8004_VALIDATION_REGISTRY=0x..." +} + +# Encode calldata, show confirmation, send via signer.py +send_tx() { + TARGET="$1"; shift + CALLDATA="$1"; shift + DESCRIPTION="${1:-Contract call}" + + echo "" + echo "=== Transaction Preview ===" + echo " Target: $TARGET" + echo " From: $FROM_ADDR" + echo " Network: $NETWORK" + echo " Action: $DESCRIPTION" + echo " Calldata: ${CALLDATA}" + echo "" + + # Gas estimate + GAS=$(cast estimate "$TARGET" "$CALLDATA" --from "$FROM_ADDR" --rpc-url "$RPC_URL" 2>/dev/null || echo "unknown") + echo " Gas estimate: $GAS" + echo "" + echo "Confirm? [y/N] " + read -r CONFIRM < /dev/tty 2>/dev/null || CONFIRM="y" + case "$CONFIRM" in + y|Y|yes|YES) ;; + *) echo "Aborted."; exit 0 ;; + esac + + python3 "$SIGNER" send-tx --from "$FROM_ADDR" --to "$TARGET" --data "$CALLDATA" --network "$NETWORK" +} + +# --- Usage --- + +if [ $# -eq 0 ]; then + echo "Usage: sh scripts/identity.sh [--network ] [--from
] [args...]" + echo "" + echo "Identity Registry (register, update, query agents):" + echo " register [--uri ] Register a new agent identity" + echo " set-uri Update agent's registration URI" + echo " set-metadata Set metadata key-value pair" + echo " unset-wallet Remove agent wallet association" + echo " agent-uri Read agent's registration URI" + echo " owner Read agent's owner address" + echo " agent-wallet Read agent's wallet address" + echo " metadata Read metadata value" + echo " balance
Count of agents owned by address" + echo "" + echo "Reputation Registry (feedback lifecycle):" + echo " feedback [opts]" + echo " Give feedback to an agent" + echo " revoke-feedback Revoke your feedback" + echo " respond " + echo " Respond to feedback as agent owner" + echo " reputation [--tag1 ] [--tag2 ]" + echo " Query aggregated reputation" + echo " read-feedback Read single feedback entry" + echo " clients List all feedback clients" + echo "" + echo "Validation Registry (third-party verification):" + echo " request-validation " + echo " validation-response " + echo " validation-status Query validation status" + echo " agent-validations List validation hashes" + echo " validation-summary [--tag ] Aggregated validation score" + echo "" + echo "Events:" + echo " events registered [--from-block N] Registration events" + echo " events feedback [--from-block N]" + echo " events uri-updated [--from-block N]" + echo "" + echo "IPFS:" + echo " prepare-registration --name --description [opts]" + echo " pin Pin file to IPFS, return CID" + echo " pin-registration --name --description [opts]" + exit 0 +fi + +CMD="$1"; shift + +case "$CMD" in + +# ============================================================ +# IDENTITY REGISTRY — WRITE +# ============================================================ + +register) + require_from + URI="" + while [ $# -gt 0 ]; do + case "$1" in + --uri) URI="$2"; shift 2 ;; + --uri=*) URI="${1#--uri=}"; shift ;; + *) die "Unknown flag: $1" ;; + esac + done + + if [ -n "$URI" ]; then + CALLDATA=$(cast calldata "register(string)" "$URI") + else + CALLDATA=$(cast calldata "register()") + fi + send_tx "$IDENTITY_REGISTRY" "$CALLDATA" "Register agent${URI:+ with URI: $URI}" + ;; + +set-uri) + require_from + [ $# -lt 2 ] && die "Usage: set-uri " + AGENT_ID="$1"; NEW_URI="$2" + CALLDATA=$(cast calldata "setAgentURI(uint256,string)" "$AGENT_ID" "$NEW_URI") + send_tx "$IDENTITY_REGISTRY" "$CALLDATA" "Update URI for agent $AGENT_ID to $NEW_URI" + ;; + +set-metadata) + require_from + [ $# -lt 3 ] && die "Usage: set-metadata " + AGENT_ID="$1"; KEY="$2"; VALUE="$3" + CALLDATA=$(cast calldata "setMetadata(uint256,string,bytes)" "$AGENT_ID" "$KEY" "$VALUE") + send_tx "$IDENTITY_REGISTRY" "$CALLDATA" "Set metadata '$KEY' on agent $AGENT_ID" + ;; + +unset-wallet) + require_from + [ $# -lt 1 ] && die "Usage: unset-wallet " + AGENT_ID="$1" + CALLDATA=$(cast calldata "unsetAgentWallet(uint256)" "$AGENT_ID") + send_tx "$IDENTITY_REGISTRY" "$CALLDATA" "Unset wallet for agent $AGENT_ID" + ;; + +# ============================================================ +# IDENTITY REGISTRY — READ +# ============================================================ + +agent-uri) + [ $# -lt 1 ] && die "Usage: agent-uri " + cast call "$IDENTITY_REGISTRY" "tokenURI(uint256)(string)" "$1" --rpc-url "$RPC_URL" + ;; + +owner) + [ $# -lt 1 ] && die "Usage: owner " + cast call "$IDENTITY_REGISTRY" "ownerOf(uint256)(address)" "$1" --rpc-url "$RPC_URL" + ;; + +agent-wallet) + [ $# -lt 1 ] && die "Usage: agent-wallet " + cast call "$IDENTITY_REGISTRY" "getAgentWallet(uint256)(address)" "$1" --rpc-url "$RPC_URL" + ;; + +metadata) + [ $# -lt 2 ] && die "Usage: metadata " + cast call "$IDENTITY_REGISTRY" "getMetadata(uint256,string)(bytes)" "$1" "$2" --rpc-url "$RPC_URL" + ;; + +balance) + [ $# -lt 1 ] && die "Usage: balance
" + cast call "$IDENTITY_REGISTRY" "balanceOf(address)(uint256)" "$1" --rpc-url "$RPC_URL" + ;; + +# ============================================================ +# REPUTATION REGISTRY — WRITE +# ============================================================ + +feedback) + require_from + [ $# -lt 5 ] && die "Usage: feedback [--endpoint ] [--uri ] [--hash ]" + AGENT_ID="$1"; VALUE="$2"; DECIMALS="$3"; TAG1="$4"; TAG2="$5"; shift 5 + ENDPOINT="" + FB_URI="" + FB_HASH="0x0000000000000000000000000000000000000000000000000000000000000000" + while [ $# -gt 0 ]; do + case "$1" in + --endpoint) ENDPOINT="$2"; shift 2 ;; + --endpoint=*) ENDPOINT="${1#--endpoint=}"; shift ;; + --uri) FB_URI="$2"; shift 2 ;; + --uri=*) FB_URI="${1#--uri=}"; shift ;; + --hash) FB_HASH="$2"; shift 2 ;; + --hash=*) FB_HASH="${1#--hash=}"; shift ;; + *) die "Unknown flag: $1" ;; + esac + done + CALLDATA=$(cast calldata "giveFeedback(uint256,int128,uint8,string,string,string,string,bytes32)" \ + "$AGENT_ID" "$VALUE" "$DECIMALS" "$TAG1" "$TAG2" "$ENDPOINT" "$FB_URI" "$FB_HASH") + send_tx "$REPUTATION_REGISTRY" "$CALLDATA" "Give feedback to agent $AGENT_ID: value=$VALUE tag1=$TAG1 tag2=$TAG2" + ;; + +revoke-feedback) + require_from + [ $# -lt 2 ] && die "Usage: revoke-feedback " + CALLDATA=$(cast calldata "revokeFeedback(uint256,uint64)" "$1" "$2") + send_tx "$REPUTATION_REGISTRY" "$CALLDATA" "Revoke feedback index $2 for agent $1" + ;; + +respond) + require_from + [ $# -lt 5 ] && die "Usage: respond " + CALLDATA=$(cast calldata "appendResponse(uint256,address,uint64,string,bytes32)" "$1" "$2" "$3" "$4" "$5") + send_tx "$REPUTATION_REGISTRY" "$CALLDATA" "Respond to feedback from $2 on agent $1" + ;; + +# ============================================================ +# REPUTATION REGISTRY — READ +# ============================================================ + +reputation) + [ $# -lt 1 ] && die "Usage: reputation [--clients ] [--tag1 ] [--tag2 ]" + AGENT_ID="$1"; shift + CLIENTS="[]" + TAG1="" + TAG2="" + while [ $# -gt 0 ]; do + case "$1" in + --clients) CLIENTS="$2"; shift 2 ;; + --clients=*) CLIENTS="${1#--clients=}"; shift ;; + --tag1) TAG1="$2"; shift 2 ;; + --tag1=*) TAG1="${1#--tag1=}"; shift ;; + --tag2) TAG2="$2"; shift 2 ;; + --tag2=*) TAG2="${1#--tag2=}"; shift ;; + *) die "Unknown flag: $1" ;; + esac + done + RESULT=$(cast call "$REPUTATION_REGISTRY" \ + "getSummary(uint256,address[],string,string)(uint64,int128,uint8)" \ + "$AGENT_ID" "$CLIENTS" "$TAG1" "$TAG2" --rpc-url "$RPC_URL") + echo "$RESULT" + ;; + +read-feedback) + [ $# -lt 3 ] && die "Usage: read-feedback " + cast call "$REPUTATION_REGISTRY" \ + "readFeedback(uint256,address,uint64)(int128,uint8,string,string,bool)" \ + "$1" "$2" "$3" --rpc-url "$RPC_URL" + ;; + +clients) + [ $# -lt 1 ] && die "Usage: clients " + cast call "$REPUTATION_REGISTRY" "getClients(uint256)(address[])" "$1" --rpc-url "$RPC_URL" + ;; + +# ============================================================ +# VALIDATION REGISTRY — WRITE +# ============================================================ + +request-validation) + require_from + require_validation_registry + [ $# -lt 4 ] && die "Usage: request-validation " + CALLDATA=$(cast calldata "validationRequest(address,uint256,string,bytes32)" "$1" "$2" "$3" "$4") + send_tx "$VALIDATION_REGISTRY" "$CALLDATA" "Request validation from $1 for agent $2" + ;; + +validation-response) + require_from + require_validation_registry + [ $# -lt 5 ] && die "Usage: validation-response " + CALLDATA=$(cast calldata "validationResponse(bytes32,uint8,string,bytes32,string)" "$1" "$2" "$3" "$4" "$5") + send_tx "$VALIDATION_REGISTRY" "$CALLDATA" "Respond to validation request $1 with score $2" + ;; + +# ============================================================ +# VALIDATION REGISTRY — READ +# ============================================================ + +validation-status) + require_validation_registry + [ $# -lt 1 ] && die "Usage: validation-status " + cast call "$VALIDATION_REGISTRY" \ + "getValidationStatus(bytes32)(address,uint256,uint8,bytes32,string,uint256)" \ + "$1" --rpc-url "$RPC_URL" + ;; + +agent-validations) + require_validation_registry + [ $# -lt 1 ] && die "Usage: agent-validations " + cast call "$VALIDATION_REGISTRY" "getAgentValidations(uint256)(bytes32[])" "$1" --rpc-url "$RPC_URL" + ;; + +validation-summary) + require_validation_registry + [ $# -lt 1 ] && die "Usage: validation-summary [--validators ] [--tag ]" + AGENT_ID="$1"; shift + VALIDATORS="[]" + TAG="" + while [ $# -gt 0 ]; do + case "$1" in + --validators) VALIDATORS="$2"; shift 2 ;; + --validators=*) VALIDATORS="${1#--validators=}"; shift ;; + --tag) TAG="$2"; shift 2 ;; + --tag=*) TAG="${1#--tag=}"; shift ;; + *) die "Unknown flag: $1" ;; + esac + done + cast call "$VALIDATION_REGISTRY" \ + "getSummary(uint256,address[],string)(uint64,uint8)" \ + "$AGENT_ID" "$VALIDATORS" "$TAG" --rpc-url "$RPC_URL" + ;; + +# ============================================================ +# EVENTS +# ============================================================ + +events) + [ $# -lt 1 ] && die "Usage: events [agentId] [--from-block N]" + EVENT_TYPE="$1"; shift + FROM_BLOCK="0" + AGENT_ID="" + + # Parse event-specific args + case "$EVENT_TYPE" in + registered) + while [ $# -gt 0 ]; do + case "$1" in + --from-block) FROM_BLOCK="$2"; shift 2 ;; + --from-block=*) FROM_BLOCK="${1#--from-block=}"; shift ;; + *) die "Unknown flag: $1" ;; + esac + done + TOPIC0=$(cast sig-event "Registered(uint256,string,address)") + cast logs --from-block "$FROM_BLOCK" --address "$IDENTITY_REGISTRY" "$TOPIC0" --rpc-url "$RPC_URL" + ;; + + feedback) + [ $# -lt 1 ] && die "Usage: events feedback [--from-block N]" + AGENT_ID="$1"; shift + while [ $# -gt 0 ]; do + case "$1" in + --from-block) FROM_BLOCK="$2"; shift 2 ;; + --from-block=*) FROM_BLOCK="${1#--from-block=}"; shift ;; + *) die "Unknown flag: $1" ;; + esac + done + TOPIC0=$(cast sig-event "NewFeedback(uint256,address,uint64,int128,uint8,string,string,string,string,string,bytes32)") + # agentId is indexed as topic1 + TOPIC1=$(cast to-hex "$AGENT_ID" | sed 's/^/0x000000000000000000000000000000000000000000000000000000000000000/' | tail -c 67) + cast logs --from-block "$FROM_BLOCK" --address "$REPUTATION_REGISTRY" "$TOPIC0" --rpc-url "$RPC_URL" + ;; + + uri-updated) + [ $# -lt 1 ] && die "Usage: events uri-updated [--from-block N]" + AGENT_ID="$1"; shift + while [ $# -gt 0 ]; do + case "$1" in + --from-block) FROM_BLOCK="$2"; shift 2 ;; + --from-block=*) FROM_BLOCK="${1#--from-block=}"; shift ;; + *) die "Unknown flag: $1" ;; + esac + done + TOPIC0=$(cast sig-event "URIUpdated(uint256,string,address)") + cast logs --from-block "$FROM_BLOCK" --address "$IDENTITY_REGISTRY" "$TOPIC0" --rpc-url "$RPC_URL" + ;; + + *) + die "Unknown event type: $EVENT_TYPE (use: registered, feedback, uri-updated)" + ;; + esac + ;; + +# ============================================================ +# IPFS +# ============================================================ + +prepare-registration) + NAME="" + DESCRIPTION="" + SERVICES="[]" + X402="false" + TRUST="[\"reputation\"]" + IMAGE="" + while [ $# -gt 0 ]; do + case "$1" in + --name) NAME="$2"; shift 2 ;; + --name=*) NAME="${1#--name=}"; shift ;; + --description) DESCRIPTION="$2"; shift 2 ;; + --description=*) DESCRIPTION="${1#--description=}"; shift ;; + --services) SERVICES="$2"; shift 2 ;; + --services=*) SERVICES="${1#--services=}"; shift ;; + --x402) X402="true"; shift ;; + --trust) TRUST="$2"; shift 2 ;; + --trust=*) TRUST="${1#--trust=}"; shift ;; + --image) IMAGE="$2"; shift 2 ;; + --image=*) IMAGE="${1#--image=}"; shift ;; + *) die "Unknown flag: $1" ;; + esac + done + [ -z "$NAME" ] && die "--name is required" + [ -z "$DESCRIPTION" ] && die "--description is required" + + # Build JSON + IMAGE_LINE="" + if [ -n "$IMAGE" ]; then + IMAGE_LINE="\"image\": \"$IMAGE\"," + fi + + cat <" + FILE="$1" + [ -f "$FILE" ] || die "File not found: $FILE" + RESPONSE=$(curl -s -X POST "$IPFS_API/add" -F "file=@$FILE") + CID=$(echo "$RESPONSE" | python3 -c "import sys,json; print(json.load(sys.stdin)['Hash'])" 2>/dev/null) \ + || die "Failed to pin to IPFS. Response: $RESPONSE" + echo "ipfs://$CID" + ;; + +pin-registration) + # Generate JSON, write to temp file, pin it + TMPFILE=$(mktemp /tmp/agent-registration-XXXXXX.json) + # Pass all args through to prepare-registration + sh "$0" --network "$NETWORK" prepare-registration "$@" > "$TMPFILE" + echo "Generated registration JSON:" + cat "$TMPFILE" + echo "" + echo "Pinning to IPFS..." + sh "$0" --network "$NETWORK" pin "$TMPFILE" + rm -f "$TMPFILE" + ;; + +# ============================================================ + +*) + echo "Unknown command: $CMD" + echo "Run without arguments to see usage." + exit 1 + ;; +esac diff --git a/internal/embed/skills/ethereum-networks/SKILL.md b/internal/embed/skills/ethereum-networks/SKILL.md index 27a952e..4bbe5d5 100644 --- a/internal/embed/skills/ethereum-networks/SKILL.md +++ b/internal/embed/skills/ethereum-networks/SKILL.md @@ -129,6 +129,36 @@ sh scripts/rpc.sh call 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48 "symbol()(stri See `references/erc20-methods.md` for the full selector reference and `references/common-contracts.md` for well-known addresses. +## ERC-8004 Agent Identity Queries + +The IdentityRegistry and ReputationRegistry are standard contracts queryable with `cast call`. For write operations (registration, feedback), use the `agent-identity` skill instead. + +```bash +# Read agent registration URI +sh scripts/rpc.sh call 0x8004A169FB4a3325136EB29fA0ceB6D2e539a432 \ + "tokenURI(uint256)(string)" 42 + +# Check agent owner +sh scripts/rpc.sh call 0x8004A169FB4a3325136EB29fA0ceB6D2e539a432 \ + "ownerOf(uint256)(address)" 42 + +# Get agent's associated wallet +sh scripts/rpc.sh call 0x8004A169FB4a3325136EB29fA0ceB6D2e539a432 \ + "getAgentWallet(uint256)(address)" 42 + +# Read metadata +sh scripts/rpc.sh call 0x8004A169FB4a3325136EB29fA0ceB6D2e539a432 \ + "getMetadata(uint256,string)(bytes)" 42 "x402.supported" + +# Query reputation summary +sh scripts/rpc.sh call 0x8004BAa17C55a88189AE136b182e5fdA19dE9b63 \ + "getSummary(uint256,address[],string,string)(uint64,int128,uint8)" 42 "[]" "quality" "30days" + +# Query registration events +sh scripts/rpc.sh logs 0x8004A169FB4a3325136EB29fA0ceB6D2e539a432 \ + $(cast sig-event "Registered(uint256,string,address)") --from-block 0 +``` + ## Fallback: Python rpc.py The `rpc.py` script is still available as a fallback if `cast` is not present: diff --git a/internal/embed/skills/ethereum-networks/references/common-contracts.md b/internal/embed/skills/ethereum-networks/references/common-contracts.md index 9227956..bb54870 100644 --- a/internal/embed/skills/ethereum-networks/references/common-contracts.md +++ b/internal/embed/skills/ethereum-networks/references/common-contracts.md @@ -36,6 +36,13 @@ |----------|---------| | Obol Token (OBOL) | `0x0B010000b7624eb9B3DfBC279673C76E9D29D5F7` | +### ERC-8004 Agent Identity (same address on 20+ chains via CREATE2) + +| Contract | Address | +|----------|---------| +| IdentityRegistry | `0x8004A169FB4a3325136EB29fA0ceB6D2e539a432` | +| ReputationRegistry | `0x8004BAa17C55a88189AE136b182e5fdA19dE9b63` | + ## Hoodi Testnet (Chain ID: 560048) Hoodi is a newer testnet. Contract addresses may differ from mainnet. Use `eth_chainId` to confirm you're on the right network before querying. diff --git a/internal/embed/skills/local-ethereum-wallet/SKILL.md b/internal/embed/skills/local-ethereum-wallet/SKILL.md index 703bf1a..d97b8d6 100644 --- a/internal/embed/skills/local-ethereum-wallet/SKILL.md +++ b/internal/embed/skills/local-ethereum-wallet/SKILL.md @@ -140,6 +140,38 @@ sh scripts/tx-helper.sh interface 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48 sh scripts/tx-helper.sh checksum 0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48 ``` +## Contract Interactions (ERC-8004 Example) + +The `send-tx --data` flag accepts pre-encoded calldata, enabling interaction with any smart contract. Use `tx-helper.sh calldata` to encode, then `signer.py send-tx` to sign and submit. + +```bash +# 1. Encode registration calldata +CALLDATA=$(sh scripts/tx-helper.sh calldata "register(string)" "ipfs://QmYourHash") + +# 2. Estimate gas +sh scripts/tx-helper.sh estimate 0x8004A169FB4a3325136EB29fA0ceB6D2e539a432 \ + "register(string)" "ipfs://QmYourHash" + +# 3. Sign and send +python3 scripts/signer.py send-tx \ + --from 0xYourAddress \ + --to 0x8004A169FB4a3325136EB29fA0ceB6D2e539a432 \ + --data "$CALLDATA" + +# Give feedback to an agent (more complex calldata) +CALLDATA=$(sh scripts/tx-helper.sh calldata \ + "giveFeedback(uint256,int128,uint8,string,string,string,string,bytes32)" \ + 42 95 0 "quality" "weather" "https://agent.example.com" "" \ + 0x0000000000000000000000000000000000000000000000000000000000000000) + +python3 scripts/signer.py send-tx \ + --from 0xYourAddress \ + --to 0x8004BAa17C55a88189AE136b182e5fdA19dE9b63 \ + --data "$CALLDATA" +``` + +For a streamlined experience with ERC-8004 specifically, use the `agent-identity` skill which wraps this pattern with confirmation prompts and gas estimates. + ## Constraints - **Shell is `sh`, not `bash`** — do not use bashisms diff --git a/internal/embed/skills/orchestration/SKILL.md b/internal/embed/skills/orchestration/SKILL.md index 60ba54f..6f2f278 100644 --- a/internal/embed/skills/orchestration/SKILL.md +++ b/internal/embed/skills/orchestration/SKILL.md @@ -284,6 +284,29 @@ await reputationWriter.giveFeedback( **This is the agentic economy.** No API keys, no subscriptions, no invoicing, no trust assumptions. Just cryptographic identity, onchain reputation, and HTTP-native payments. +### Shell-Based Agent Flow (Obol Stack) + +For agents running in the Obol Stack, the same cycle works via `identity.sh` + `rpc.sh`: + +```bash +# 1. Discover: find registered agents +sh scripts/identity.sh events registered --from-block 0 +# Parse logs for agents with matching service tags + +# 2. Trust: check reputation +sh scripts/identity.sh reputation 42 --tag1 "quality" --tag2 "30days" +# → count=47 value=92 decimals=0 (92/100 average quality) + +# 3-5. Call + Pay: x402 flow (requires TS/Python SDK — not yet shell-native) +# The x402 payment negotiation happens at the HTTP level + +# 6. Rate: post feedback onchain +sh scripts/identity.sh --from 0xYourAddress feedback 42 95 0 "quality" "weather" \ + --endpoint "https://weather.agent.example.com" +``` + +See `agent-identity/SKILL.md` for the full CLI reference. + ### Key Projects Building This Stack - **ERC-8004** — agent identity + reputation (EF, MetaMask, Google, Coinbase) - **x402** — HTTP payment protocol (Coinbase) diff --git a/internal/embed/skills/ship/SKILL.md b/internal/embed/skills/ship/SKILL.md index 86d5ef8..b2c9b6a 100644 --- a/internal/embed/skills/ship/SKILL.md +++ b/internal/embed/skills/ship/SKILL.md @@ -169,18 +169,44 @@ Find your archetype below. Each tells you exactly how many contracts you need, w ### 6. AI Agent Service (0-1 contracts) -**Architecture:** Agent logic is offchain. Onchain component is optional — ERC-8004 identity registration, or a payment contract for x402. +**Architecture:** Agent logic is offchain. Onchain component is optional — ERC-8004 identity registration, or a payment contract for x402. You don't deploy a contract — you register with the existing IdentityRegistry. **Contracts:** -- (often none — agent runs offchain, uses existing payment infra) -- `AgentRegistry.sol` (optional) — ERC-8004 identity + service endpoints +- (usually none — register with the existing ERC-8004 IdentityRegistry instead of deploying) +- Custom contract only if you need bespoke onchain logic beyond identity + reputation + +**When to register with ERC-8004:** +- Your agent offers a public service other agents can discover +- You want onchain reputation that builds over time +- You accept x402 micropayments and want trust signals for clients +- You need cross-chain discoverability (same identity on 20+ chains) + +**When NOT to register:** +- Internal agent with no public-facing service +- Testing/development — register on a testnet first +- Agent doesn't interact with other agents + +**Registration cost:** Just gas (no protocol fee). Base is cheapest (~$0.01). + +**Recommended chain:** Base — cheapest gas, largest ERC-8004 ecosystem, Coinbase distribution. + +**Quick start:** +```bash +# Prepare + pin + register in the Obol Stack +sh scripts/identity.sh --network base --from 0xYourAddress pin-registration \ + --name "MyAgent" --description "What it does" \ + --services '[{"name":"A2A","endpoint":"https://your.agent/.well-known/agent-card.json","version":"0.3.0"}]' \ + --x402 +``` **Common mistakes:** - Putting agent logic onchain (Solidity is not for AI inference) +- Deploying a custom AgentRegistry when the standard IdentityRegistry exists - Overcomplicating payments (x402 handles HTTP-native payments) - Ignoring key management (fetch `wallets/SKILL.md`) +- Registering on mainnet before testing on a testnet -**Fetch sequence:** `standards/SKILL.md` → `wallets/SKILL.md` → `tools/SKILL.md` → `orchestration/SKILL.md` +**Fetch sequence:** `agent-identity/SKILL.md` → `standards/SKILL.md` → `wallets/SKILL.md` → `orchestration/SKILL.md` --- diff --git a/internal/embed/skills/standards/SKILL.md b/internal/embed/skills/standards/SKILL.md index e85cf9d..b9583dc 100644 --- a/internal/embed/skills/standards/SKILL.md +++ b/internal/embed/skills/standards/SKILL.md @@ -61,9 +61,12 @@ struct Feedback { **Example metrics:** Quality 87/100 → `value=87, decimals=0`. Uptime 99.77% → `value=9977, decimals=2`. **3. Validation Registry** -- Independent verification of agent work +- Independent third-party verification of agent work - Trust models: crypto-economic (stake-secured), zkML, TEE attestation - Validators respond with 0-100 scores +- Request/response pattern: agent or client submits `validationRequest`, validator posts `validationResponse` +- Aggregated summaries filterable by validator addresses and tags +- Contract address uses same CREATE2 pattern (set via `ERC8004_VALIDATION_REGISTRY` env var) ### Agent Registration File (agentURI) @@ -82,21 +85,33 @@ struct Feedback { } ``` -### Integration +### Integration (cast + identity.sh) -```solidity -// Register agent -uint256 agentId = identityRegistry.register("ipfs://QmYourReg", metadata); +Use the `agent-identity` skill's `identity.sh` script for all registry operations. It wraps `cast` for reads and delegates to `signer.py` for writes. + +```bash +# Register agent with IPFS URI +sh scripts/identity.sh --from 0xYourAddress register --uri "ipfs://QmYourReg" + +# Give feedback (value=9977, decimals=2 → 99.77%) +sh scripts/identity.sh --from 0xYourAddress feedback 42 9977 2 "uptime" "30days" \ + --endpoint "https://agent.example.com/api" --uri "ipfs://QmDetails" + +# Query aggregated reputation +sh scripts/identity.sh reputation 42 --tag1 "uptime" --tag2 "30days" -// Give feedback -reputationRegistry.giveFeedback(agentId, 9977, 2, "uptime", "30days", - "https://agent.example.com/api", "ipfs://QmDetails", keccak256(data)); +# Update agent URI +sh scripts/identity.sh --from 0xYourAddress set-uri 42 "ipfs://QmNewHash" -// Query reputation -(uint64 count, int128 value, uint8 decimals) = - reputationRegistry.getSummary(agentId, trustedClients, "uptime", "30days"); +# Set metadata +sh scripts/identity.sh --from 0xYourAddress set-metadata 42 "x402.supported" 0x01 + +# Query registration events +sh scripts/identity.sh events registered --from-block 0 ``` +For the full CLI reference, see `agent-identity/SKILL.md` and `agent-identity/references/erc8004-methods.md`. + ### Step-by-Step: Register an Agent Onchain **1. Prepare the registration JSON** — host it on IPFS or a web server: @@ -115,23 +130,24 @@ reputationRegistry.giveFeedback(agentId, 9977, 2, "uptime", "30days", } ``` -**2. Upload to IPFS** (or use any URI): +**2. Pin to IPFS:** ```bash -# Using IPFS -ipfs add registration.json -# → QmYourRegistrationHash - -# Or host at a URL — the agentURI just needs to resolve to the JSON +# Using identity.sh (pins to in-cluster IPFS node) +sh scripts/identity.sh pin registration.json +# → ipfs://QmYourRegistrationHash + +# Or generate + pin in one step: +sh scripts/identity.sh pin-registration --name "WeatherBot" \ + --description "Provides real-time weather data via x402 micropayments" \ + --services '[{"name":"A2A","endpoint":"https://weather.example.com/.well-known/agent-card.json","version":"0.3.0"}]' \ + --x402 ``` -**3. Call the Identity Registry:** -```solidity -// On any supported chain — same address everywhere -IIdentityRegistry registry = IIdentityRegistry(0x8004A169FB4a3325136EB29fA0ceB6D2e539a432); - -// metadata bytes are optional (can be empty) -uint256 agentId = registry.register("ipfs://QmYourRegistrationHash", ""); -// agentId is your ERC-721 tokenId — globally unique on this chain +**3. Register onchain:** +```bash +# Register with the IPFS URI — signs via Web3Signer, sends via eRPC +sh scripts/identity.sh --from 0xYourAddress register --uri "ipfs://QmYourRegistrationHash" +# → Transaction hash. agentId is in the Registered event logs. ``` **4. Verify your endpoint domain** — place a file at `.well-known/agent-registration.json`: @@ -145,7 +161,29 @@ uint256 agentId = registry.register("ipfs://QmYourRegistrationHash", ""); ``` This proves the domain owner controls the agent identity. Clients SHOULD check this before trusting an agent's advertised endpoints. -**5. Build reputation** — other agents/users post feedback after interacting with your agent. +**5. Build reputation** — other agents/users post feedback after interacting with your agent: +```bash +# Another agent rates your service after using it +sh scripts/identity.sh --from 0xClientAddress feedback 42 95 0 "quality" "weather" \ + --endpoint "https://weather.example.com" +``` + +### Full Lifecycle Summary + +``` +1. prepare-registration → Generate JSON +2. pin / pin-registration → Pin to IPFS +3. register --uri → Register onchain (get agentId) +4. set-metadata → Add key-value metadata +5. Domain verification → .well-known/agent-registration.json +6. Receive feedback → Other agents call giveFeedback +7. reputation → Query your aggregated score +8. respond → Respond to feedback entries +9. request-validation → Request third-party verification +10. set-uri → Update URI when services change +``` + +See `agent-identity/SKILL.md` for complete command reference. ### Cross-Chain Agent Identity diff --git a/plans/dynamic-erpc-upstreams.md b/plans/dynamic-erpc-upstreams.md new file mode 100644 index 0000000..9a6011b --- /dev/null +++ b/plans/dynamic-erpc-upstreams.md @@ -0,0 +1,190 @@ +# Plan: Dynamic eRPC Upstream Registration + +## Problem + +When `obol network install ethereum --network=mainnet` deploys a local Ethereum node, the node's RPC endpoint (`http://ethereum-execution.ethereum-.svc.cluster.local:8545`) is never registered with eRPC. All RPC traffic continues flowing to remote upstreams (Obol GCP, PublicNode) even though a local node with zero latency and no rate limits is running in the same cluster. + +## Current State + +``` +eRPC (erpc namespace) + upstreams: [obol-rpc-mainnet (remote), obol-rpc-hoodi (remote), allnodes-rpc-hoodi (remote)] + ← hardcoded at stack init, never updated + +Ethereum node (ethereum- namespace) + RPC: http://ethereum-execution.ethereum-.svc.cluster.local:8545 + ← running but eRPC doesn't know about it +``` + +## Desired State + +``` +obol network install ethereum --network=mainnet --id=prod + → deploys ethereum node + → registers local RPC as eRPC upstream (primary, lowest latency) + → eRPC routes mainnet traffic through local node first, remote as fallback + +obol network delete ethereum/prod + → removes ethereum node + → deregisters local RPC from eRPC +``` + +## Design + +### Approach: Patch eRPC ConfigMap + Restart + +eRPC reads its config from a ConfigMap (`erpc` chart creates it from the `config` value). We can patch this ConfigMap to add/remove upstreams, then trigger a rollout restart. + +This is the same pattern used by `model.ConfigureLLMSpy()` — it patches a ConfigMap, then restarts the deployment. + +### Network-to-Chain Mapping + +Need a mapping from network name → chainId for eRPC upstream config: + +| Network | Chain ID | Execution RPC Template | +|---------|----------|----------------------| +| mainnet | 1 | `http://ethereum-execution.ethereum-.svc.cluster.local:8545` | +| hoodi | 560048 | `http://ethereum-execution.ethereum-.svc.cluster.local:8545` | +| sepolia | 11155111 | `http://ethereum-execution.ethereum-.svc.cluster.local:8545` | + +This mapping could be: +- Hardcoded in Go (simplest, covers known networks) +- Derived from the network's values.yaml (would need to parse it) +- Stored in the metadata ConfigMap (already has the endpoint, just needs chainId) + +**Recommendation**: Add `chainId` to the metadata ConfigMap JSON (it's already broadcast per-network) and have the registration code read it from there. + +### Implementation Steps + +#### 1. Add chainId to network metadata ConfigMaps + +**File**: `internal/embed/networks/ethereum/helmfile.yaml.gotmpl` + +Add a `chainId` field to the metadata JSON. Since the network name → chainId mapping is known at template time, use a lookup: + +```yaml +data: + metadata.json: | + { + "network": "{{ .Values.network }}", + "chainId": {{ if eq .Values.network "mainnet" }}1{{ else if eq .Values.network "hoodi" }}560048{{ else if eq .Values.network "sepolia" }}11155111{{ else }}0{{ end }}, + "execution": { + ... + } + } +``` + +#### 2. Create `internal/network/erpc.go` — upstream registration + +New file with two functions: + +```go +// RegisterERPCUpstream reads the network metadata ConfigMap, extracts the +// chainId and internal RPC endpoint, and patches the eRPC ConfigMap to add +// a new upstream. Triggers a rollout restart of the eRPC deployment. +func RegisterERPCUpstream(cfg *config.Config, networkType, id string) error + +// DeregisterERPCUpstream removes a previously registered upstream from the +// eRPC ConfigMap and triggers a rollout restart. +func DeregisterERPCUpstream(cfg *config.Config, networkType, id string) error +``` + +**Registration flow**: +1. Read the network metadata ConfigMap from `-` namespace +2. Parse `metadata.json` → extract `chainId` and `execution.endpoints.rpc.internal` +3. Read the eRPC ConfigMap from `erpc` namespace +4. Parse the eRPC config YAML → find `projects[0].upstreams` array +5. Add new upstream entry: + ```yaml + - id: local-- + endpoint: http://ethereum-execution.ethereum-.svc.cluster.local:8545 + evm: + chainId: + group: primary + ``` +6. If a `networks` entry for this chainId doesn't exist yet, add one with failsafe defaults +7. Write patched config back to ConfigMap +8. Rollout restart the eRPC deployment: `kubectl rollout restart deployment/erpc -n erpc` +9. Wait for rollout to complete + +**Deregistration flow**: Same but removes the upstream with matching id `local--`. + +#### 3. Wire into network sync/delete + +**File**: `internal/network/network.go` + +In `Sync()` — after `helmfile sync` succeeds: +```go +// Register local node as eRPC upstream +if err := RegisterERPCUpstream(cfg, networkType, id); err != nil { + fmt.Printf(" Warning: could not register eRPC upstream: %v\n", err) + // Non-fatal — network still works via direct access +} +``` + +In `Delete()` — before namespace deletion: +```go +// Deregister from eRPC before deleting namespace +if err := DeregisterERPCUpstream(cfg, networkType, id); err != nil { + fmt.Printf(" Warning: could not deregister eRPC upstream: %v\n", err) +} +``` + +#### 4. eRPC selection policy for local-first routing + +Use `group: primary` for local upstreams and keep remote upstreams as default group. Add a selection policy to prefer primary group: + +```yaml +selectionPolicy: + evalInterval: 30s + evalFunction: | + (upstreams, method) => { + const primary = upstreams.filter(u => u.config.group === 'primary'); + if (primary.length > 0) return primary; + return upstreams; + } +``` + +This ensures local nodes are always preferred when available, with automatic fallback to remote RPCs if the local node is down. + +### Considerations + +**Config format**: eRPC config is embedded as a YAML string inside the Helm values `config: |` field. The ConfigMap stores this as a single `erpc.yaml` key. Patching requires: +1. Read ConfigMap → extract `erpc.yaml` key +2. Parse YAML → modify upstreams array +3. Re-serialize YAML → patch ConfigMap + +**Idempotency**: Registration must be idempotent — re-syncing a network shouldn't create duplicate upstreams. Check by upstream `id` before adding. + +**Multiple instances**: Multiple ethereum deployments (e.g., mainnet-01, mainnet-02) should each register their own upstream. eRPC load-balances across them automatically. + +**Blink upstream interaction**: The write-only blink upstream and its selectionPolicy (eth_sendRawTransaction routing) must be preserved when patching. The new registration code must merge, not replace, the upstreams array. + +### Files to Create/Modify + +| File | Change | +|------|--------| +| `internal/network/erpc.go` | New — RegisterERPCUpstream, DeregisterERPCUpstream | +| `internal/network/erpc_test.go` | New — unit tests for config patching logic | +| `internal/network/network.go` | Wire registration into Sync/Delete | +| `internal/embed/networks/ethereum/helmfile.yaml.gotmpl` | Add chainId to metadata | +| `internal/embed/networks/helios/helmfile.yaml.gotmpl` | Add chainId to metadata (if applicable) | + +### Testing + +```bash +# Unit tests +go test ./internal/network/ -run TestRegisterERPCUpstream +go test ./internal/network/ -run TestDeregisterERPCUpstream + +# Integration test +obol network install ethereum --network=mainnet --id=test-erpc +obol network sync ethereum/test-erpc +# Verify: eRPC config should now contain local-ethereum-test-erpc upstream +obol kubectl get configmap -n erpc erpc-erpc -o yaml | grep "local-ethereum" + +# Delete test +obol network delete ethereum/test-erpc --force +# Verify: upstream removed +obol kubectl get configmap -n erpc erpc-erpc -o yaml | grep "local-ethereum" # should find nothing +``` diff --git a/plans/trustless-agents.md b/plans/trustless-agents.md new file mode 100644 index 0000000..45cde23 --- /dev/null +++ b/plans/trustless-agents.md @@ -0,0 +1,521 @@ +# Trustless Agents: Comprehensive ERC-8004 Lifecycle Support + +**Goal:** Make the OpenClaw skills capable of end-to-end ERC-8004 agent identity management — register, update, query reputation, give feedback, request validation, and maintain a resolvable agentURI — all through `cast` and the existing `signer.py`/`tx-helper.sh` pipeline. + +--- + +## Current State + +### What exists +- **standards** skill: Registration walkthrough (Solidity-only), JSON schema, cross-chain pattern +- **orchestration** skill: TypeScript example of discover → trust → pay → rate cycle +- **local-ethereum-wallet** skill: `signer.py` (sign/send-tx via Web3Signer) + `tx-helper.sh` (cast-based calldata encoding, gas estimation) +- **ethereum-networks** skill: `rpc.sh` (cast-based read-only queries via eRPC) +- **ship** skill: Mentions ERC-8004 as optional for AI agent services +- **addresses** skill: Contract addresses listed +- **wallets** skill: Key management patterns + +### What's missing +1. **No ABIs** — skills reference `registryAbi` / `reputationAbi` without providing them +2. **No cast-based write examples** — Solidity snippets can't be executed by an agent; cast commands can +3. **No agent-identity script** — no equivalent of `rpc.sh` for 8004 operations +4. **No ValidationRegistry coverage** — third registry entirely absent from skills +5. **No agentURI hosting** — "upload to IPFS" is hand-waved, no tooling +6. **No update/metadata lifecycle** — `setAgentURI`, `setMetadata`, `setAgentWallet`, `unsetAgentWallet` undocumented +7. **No reputation write flow** — `giveFeedback`, `revokeFeedback`, `appendResponse` undocumented +8. **No event querying** — no examples of filtering `Registered`, `NewFeedback`, `ValidationRequest` events + +--- + +## Plan + +### 1. Add ABI reference files + +**New directory:** `standards/references/abis/` + +**Files:** +- `IdentityRegistry.json` — full ABI from [erc-8004/erc-8004-contracts](https://github.com/erc-8004/erc-8004-contracts/tree/master/abis) +- `ReputationRegistry.json` — full ABI +- `ValidationRegistry.json` — full ABI + +**Additionally:** `standards/references/erc8004-methods.md` — a human-readable quick-reference (like the existing `erc20-methods.md` in ethereum-networks), listing every function signature with parameter names and one-line descriptions. Organized by registry: + +``` +## IdentityRegistry (0x8004A169FB4a3325136EB29fA0ceB6D2e539a432) + +### Write +register() → uint256 agentId +register(string agentURI) → uint256 agentId +register(string agentURI, MetadataEntry[] metadata) → uint256 agentId +setAgentURI(uint256 agentId, string newURI) owner-only +setAgentWallet(uint256 agentId, address newWallet, ...) EIP-712 signature required +unsetAgentWallet(uint256 agentId) owner-only +setMetadata(uint256 agentId, string key, bytes value) owner-only + +### Read +tokenURI(uint256 tokenId) → string (agentURI) +ownerOf(uint256 tokenId) → address +getAgentWallet(uint256 agentId) → address +getMetadata(uint256 agentId, key) → bytes +balanceOf(address owner) → uint256 + +## ReputationRegistry (0x8004BAa17C55a88189AE136b182e5fdA19dE9b63) + +### Write +giveFeedback(uint256 agentId, int128 value, uint8 decimals, string tag1, string tag2, string endpoint, string feedbackURI, bytes32 feedbackHash) +revokeFeedback(uint256 agentId, uint64 feedbackIndex) caller must be original poster +appendResponse(uint256 agentId, address client, uint64 index, string responseURI, bytes32 responseHash) + +### Read +getSummary(uint256 agentId, address[] clients, string tag1, string tag2) → (count, value, decimals) +readFeedback(uint256 agentId, address client, uint64 index) → (value, decimals, tag1, tag2, isRevoked) +readAllFeedback(uint256 agentId, address[] clients, tag1, tag2, includeRevoked) → arrays +getClients(uint256 agentId) → address[] +getLastIndex(uint256 agentId, address client) → uint64 + +## ValidationRegistry (address TBD — not in addresses skill yet) + +### Write +validationRequest(address validator, uint256 agentId, string requestURI, bytes32 requestHash) +validationResponse(bytes32 requestHash, uint8 response, string responseURI, bytes32 responseHash, string tag) + +### Read +getValidationStatus(bytes32 requestHash) → (validator, agentId, response, responseHash, tag, lastUpdate) +getAgentValidations(uint256 agentId) → bytes32[] +getValidatorRequests(address validator) → bytes32[] +getSummary(uint256 agentId, address[] validators, string tag) → (count, avgResponse) +``` + +### 2. Create `agent-identity` skill + +**New skill:** `agent-identity/` + +``` +agent-identity/ +├── SKILL.md +├── scripts/ +│ └── identity.sh # cast-based CLI for all 8004 operations +└── references/ + ├── abis/ + │ ├── IdentityRegistry.json + │ ├── ReputationRegistry.json + │ └── ValidationRegistry.json + └── erc8004-methods.md +``` + +**`SKILL.md` structure:** + +```yaml +--- +name: agent-identity +description: "Register, update, and manage ERC-8004 agent identities onchain. Give and query reputation. Request validation. Full lifecycle management via cast + Web3Signer." +metadata: { "openclaw": { "emoji": "🪪", "requires": { "bins": ["cast", "python3"] } } } +--- +``` + +Sections: +- **When to Use** — registering an agent, updating URI/metadata, giving/reading feedback, requesting validation +- **When NOT to Use** — read-only blockchain queries (use ethereum-networks), key management (use wallets), deploying contracts (use orchestration) +- **Quick Start** — register, update, query reputation, give feedback in 4 commands +- **Contract Addresses** — all three registries, same on 20+ chains +- **Identity Lifecycle** — register → set metadata → update URI → set wallet → transfer ownership +- **Reputation Lifecycle** — give feedback → query summary → read individual → revoke → respond +- **Validation Lifecycle** — request validation → validator responds → query status → get summary +- **IPFS URI Management** — preparing registration JSON, pinning to IPFS, verifying +- **Event Querying** — filtering `Registered`, `URIUpdated`, `NewFeedback`, `ValidationRequest` events via `rpc.sh logs` +- **Cross-Chain Patterns** — register on Base (cheapest), query from any chain +- **Constraints** — signing via signer.py, reads via cast, confirm before writes + +**`identity.sh` commands:** + +The script follows the same pattern as `rpc.sh` and `tx-helper.sh` — a POSIX shell wrapper around `cast`. For **write operations**, it outputs the encoded calldata and a confirmation prompt, then delegates to `signer.py send-tx --data` for actual submission. For **read operations**, it calls `cast call` directly. + +``` +# Identity Registry — Write (outputs calldata or sends via signer.py) +identity.sh register [--uri ] [--metadata key=value,...] +identity.sh set-uri +identity.sh set-metadata +identity.sh set-wallet +identity.sh unset-wallet + +# Identity Registry — Read +identity.sh agent-uri +identity.sh owner +identity.sh agent-wallet +identity.sh metadata +identity.sh balance
+identity.sh total-supply + +# Reputation Registry — Write +identity.sh feedback [--endpoint ] [--uri ] [--hash ] +identity.sh revoke-feedback +identity.sh respond + +# Reputation Registry — Read +identity.sh reputation [--clients ] [--tag1 ] [--tag2 ] +identity.sh read-feedback +identity.sh all-feedback [--clients ] [--tag1 ] [--tag2 ] [--include-revoked] +identity.sh clients + +# Validation Registry — Write +identity.sh request-validation +identity.sh validation-response + +# Validation Registry — Read +identity.sh validation-status +identity.sh agent-validations +identity.sh validator-requests +identity.sh validation-summary [--validators ] [--tag ] + +# Events +identity.sh events registered [--from-block N] +identity.sh events feedback [--from-block N] +identity.sh events validation [--from-block N] + +# Utilities +identity.sh prepare-registration --name "MyAgent" --description "..." --services '[...]' [--x402] [--trust reputation,tee] +identity.sh verify-domain +``` + +**Implementation notes:** + +- Read operations use `cast call
--rpc-url "$RPC_URL"` directly +- Write operations use `cast calldata ` to encode, then show a confirmation (what will be sent, estimated gas), then call `python3 ../local-ethereum-wallet/scripts/signer.py send-tx --from --to --data ` +- The `prepare-registration` command generates the JSON file locally and prints it. Actual IPFS pinning is a separate step (see section 5) +- Event queries use `cast logs` with topic filters +- The `--network` flag works the same as in `rpc.sh` — defaults to `mainnet`, configurable +- ABI files are referenced for cast's `--abi` flag where needed (complex structs like MetadataEntry[]) + +### 3. Update `standards` skill + +Replace the Solidity-only integration section with cast-based examples that the agent can actually execute: + +**Replace** the current "Integration" code block (Solidity) with: + +```bash +# Register agent with URI +sh scripts/identity.sh register --uri "ipfs://QmYourRegistrationHash" + +# Update URI after changes +sh scripts/identity.sh set-uri 42 "ipfs://QmNewHash" + +# Set arbitrary metadata +sh scripts/identity.sh set-metadata 42 "x402.supported" "0x01" + +# Query reputation +sh scripts/identity.sh reputation 42 --tag1 "uptime" --tag2 "30days" + +# Give feedback after interacting with agent 42 +sh scripts/identity.sh feedback 42 95 0 "quality" "weather" \ + --endpoint "https://weather.agent.example.com" \ + --uri "ipfs://QmFeedbackDetails" +``` + +**Add** the ValidationRegistry to the "Three Registry System" section — currently missing entirely: + +``` +**3. Validation Registry** +- Independent third-party verification of agent work +- Trust models: crypto-economic (stake-secured), zkML, TEE attestation +- Validators respond with 0-100 scores +- Contract: address same as IdentityRegistry pattern (CREATE2) +``` + +**Add** a "Full Lifecycle" section showing the complete cast-based flow: +1. Prepare registration JSON +2. Pin to IPFS +3. Register onchain +4. Set metadata +5. Verify domain +6. Receive feedback +7. Query/respond to feedback +8. Request validation + +**Update** the Step-by-Step section to reference `identity.sh` instead of raw Solidity. + +**Add** a cross-reference: "See `agent-identity` skill for the full CLI reference and scripts." + +### 4. Update `ethereum-networks` skill + +Add ERC-8004 read examples to demonstrate that the existing `rpc.sh` can already query these contracts: + +```bash +# Read agent URI (tokenURI) +sh scripts/rpc.sh call 0x8004A169FB4a3325136EB29fA0ceB6D2e539a432 \ + "tokenURI(uint256)(string)" 42 + +# Check agent owner +sh scripts/rpc.sh call 0x8004A169FB4a3325136EB29fA0ceB6D2e539a432 \ + "ownerOf(uint256)(address)" 42 + +# Get reputation summary +sh scripts/rpc.sh call 0x8004BAa17C55a88189AE136b182e5fdA19dE9b63 \ + "getSummary(uint256,address[],string,string)(uint64,int128,uint8)" 42 "[]" "quality" "30days" + +# Query registration events +sh scripts/rpc.sh logs 0x8004A169FB4a3325136EB29fA0ceB6D2e539a432 \ + 0x$(cast sig-event "Registered(uint256,string,address)") --from-block 0 +``` + +Add a note: "For write operations (registration, feedback, metadata updates), use the `agent-identity` skill." + +Add the IdentityRegistry and ReputationRegistry to `references/common-contracts.md`: + +``` +## ERC-8004 Agent Identity (same address on 20+ chains) +| Contract | Address | +|---------------------|----------------------------------------------| +| IdentityRegistry | 0x8004A169FB4a3325136EB29fA0ceB6D2e539a432 | +| ReputationRegistry | 0x8004BAa17C55a88189AE136b182e5fdA19dE9b63 | +``` + +### 5. Update `local-ethereum-wallet` skill + +Add a section "Contract Interactions (ERC-8004 Example)" showing how `signer.py send-tx --data` works with encoded calldata from `tx-helper.sh`: + +```bash +# Encode registration calldata +CALLDATA=$(sh scripts/tx-helper.sh calldata "register(string)" "ipfs://QmYourHash") + +# Estimate gas +sh scripts/tx-helper.sh estimate 0x8004A169FB4a3325136EB29fA0ceB6D2e539a432 \ + "register(string)" "ipfs://QmYourHash" + +# Sign and send +python3 scripts/signer.py send-tx \ + --from 0xYourAddress \ + --to 0x8004A169FB4a3325136EB29fA0ceB6D2e539a432 \ + --data "$CALLDATA" +``` + +This bridges the gap between "we have a signer" and "we can interact with 8004 contracts." + +### 6. Update `addresses` skill + +Add the ValidationRegistry address once confirmed. Currently only IdentityRegistry and ReputationRegistry are listed. Verify whether the ValidationRegistry has the same CREATE2 pattern. + +### 7. Update `orchestration` skill + +Replace the TypeScript agent example with a cast-based equivalent that runs in the OpenClaw pod: + +```bash +# 1. Discover: find agents registered for a service +sh scripts/identity.sh events registered --from-block 0 +# Filter for agents with "weather" in their URI metadata + +# 2. Trust: check reputation +sh scripts/identity.sh reputation --tag1 "quality" --tag2 "30days" + +# 3-5. Call + Pay: x402Fetch (still TS — agent commerce needs a wallet SDK) +# ... (keep the TS example for the x402 part) + +# 6. Rate: post feedback +sh scripts/identity.sh feedback 95 0 "quality" "weather" \ + --endpoint "https://weather.agent.example.com" +``` + +Keep the TypeScript example too — it's valuable for SE2 dApp builders. But add the shell equivalent for agents running in the Obol Stack. + +### 8. Update `ship` skill + +Expand the "AI Agent Service" archetype (currently 4 lines) with concrete guidance: + +- When to use ERC-8004 registration (public-facing agent service with reputation) +- When not to (internal agent, no need for discovery) +- Registration cost (just gas — no fee in the contract) +- Recommended chain (Base — cheapest, largest ERC-8004 ecosystem) +- Link to `agent-identity` skill for the full workflow + +### 9. Depend on `cast` (Foundry) + +`cast` is already a dependency of `ethereum-networks` (via `rpc.sh`) and `local-ethereum-wallet` (via `tx-helper.sh`). The new `agent-identity` skill uses the same dependency — no new binary required. + +**Verify cast availability in the OpenClaw pod:** The skill metadata declares `"requires": { "bins": ["cast"] }` and OpenClaw checks for binary availability. If cast is not installed, the skill should degrade gracefully with a clear error: "cast (Foundry) is required. Install: curl -L https://foundry.paradigm.xyz | bash && foundryup" + +**cast features used:** +- `cast call` — read contract state +- `cast calldata` — encode function calldata for write operations +- `cast send` — NOT used directly (signing goes through Web3Signer) +- `cast logs` — query events +- `cast sig` — get function selectors +- `cast sig-event` — get event topic hashes +- `cast abi-decode` — decode return data +- `cast keccak` — hash data (for feedbackHash) +- `cast to-hex` / `cast from-hex` — unit conversion + +### 10. IPFS integration for agentURI + +**Current approach (IPFS-first):** + +The `identity.sh prepare-registration` command generates the JSON. For pinning, we rely on the agent having access to an IPFS node. Two paths: + +**Path A: In-cluster IPFS node** +- Deploy an IPFS node (kubo) as a k3d service in the `ipfs` namespace +- Expose at `ipfs.ipfs.svc.cluster.local:5001` (API) and port 8080 (gateway) +- `identity.sh` pins via `curl -X POST "http://ipfs:5001/api/v0/add" -F file=@registration.json` +- Returns CID for use as agentURI + +**Path B: External pinning service** +- Use Pinata, web3.storage, or similar +- Agent provides API key via environment variable +- `identity.sh` wraps the pinning API + +For now, **Path A is recommended** — it's local-first and doesn't require external accounts. + +Add a `pin` command to `identity.sh`: + +```bash +# Pin a file to IPFS and return the CID +identity.sh pin +# → QmYourCID + +# Pin registration JSON (combines prepare-registration + pin) +identity.sh pin-registration --name "MyAgent" --description "..." --services '[...]' +# → ipfs://QmYourCID +``` + +--- + +## Future: Traefik-Exposed Agent URI API + +**Problem:** IPFS URIs are content-addressed and immutable. When an agent's services change, you need to re-pin, get a new CID, and call `setAgentURI` onchain (costs gas). For agents that update frequently, this is wasteful. + +**Solution:** A small HTTP service behind Traefik that serves the agent registration JSON at a stable URL. The onchain `agentURI` points to this URL instead of IPFS. Updates are instant and free (no gas). + +### Architecture + +``` +Internet + │ + ▼ +Cloudflare Tunnel (obol tunnel provision) + │ + ▼ +Traefik Gateway (traefik namespace) + │ HTTPRoute: /agents/{agentId}/registration.json + │ HTTPRoute: /agents/{agentId}/.well-known/agent-registration.json + ▼ +agent-uri-server (new service, agent-uri namespace) + │ Reads registration JSON from ConfigMap or PVC + │ Serves at stable URLs + │ Handles .well-known/agent-registration.json for domain verification + ▼ +Storage: ConfigMap (small) or PVC (if scripts/images needed) +``` + +### How it works + +1. **Agent registers with a URL agentURI** instead of IPFS: + ``` + agentURI: https:///agents/42/registration.json + ``` + +2. **The agent-uri-server** is a minimal Go HTTP server (or even a static file server like nginx) that: + - Serves `registration.json` for each agent from a config directory + - Serves `.well-known/agent-registration.json` for domain verification + - Supports hot-reload when config changes (via Reloader watching the ConfigMap) + +3. **Updating the registration** is just a ConfigMap patch: + ```bash + kubectl patch configmap agent-42-registration -n agent-uri \ + --type merge -p '{"data":{"registration.json":"{...new JSON...}"}}' + ``` + No gas, no IPFS re-pin, no `setAgentURI` call. The URL stays the same, the content changes. + +4. **Traefik routing** via HTTPRoute: + ```yaml + apiVersion: gateway.networking.k8s.io/v1 + kind: HTTPRoute + metadata: + name: agent-uri + namespace: agent-uri + spec: + parentRefs: + - name: traefik-gateway + namespace: traefik + rules: + - matches: + - path: + type: PathPrefix + value: /agents/ + backendRefs: + - name: agent-uri-server + port: 8080 + ``` + +5. **Public access** via Cloudflare tunnel (already exists in the stack): + - `obol tunnel provision` sets up the tunnel + - Traefik already routes based on path prefix + - The agent URI becomes: `https:///agents/42/registration.json` + +### CLI integration + +```bash +# Deploy the agent-uri-server (one-time setup) +obol app install agent-uri + +# Publish a registration (creates ConfigMap + serves at URL) +identity.sh publish-registration --agent-id 42 \ + --name "MyAgent" --description "..." --services '[...]' +# → https:///agents/42/registration.json + +# Update registration (patches ConfigMap, no gas) +identity.sh update-registration --agent-id 42 \ + --services '[...new services...]' + +# Register onchain with the URL +identity.sh register --uri "https:///agents/42/registration.json" +``` + +### Trade-offs vs IPFS + +| Aspect | IPFS | Traefik API | +|--------|------|-------------| +| Immutability | Content-addressed, immutable | Mutable — URL stays, content changes | +| Update cost | New CID + `setAgentURI` (gas) | Free (ConfigMap patch) | +| Availability | Depends on IPFS pinning | Depends on tunnel/cluster uptime | +| Decentralization | Fully decentralized | Centralized to your cluster | +| Verification | CID = hash of content | Need `.well-known` domain verification | +| Best for | Stable, infrequent updates | Active agents with changing services | + +### Hybrid approach (recommended future state) + +1. **Pin to IPFS** as the canonical, immutable record +2. **Serve via Traefik** as the hot, mutable endpoint +3. **agentURI** points to the Traefik URL for fast updates +4. **Metadata** stores the IPFS CID as a backup: `setMetadata(agentId, "ipfs.backup", cidBytes)` +5. **Clients** can verify by checking the IPFS backup if the URL is down + +--- + +## Implementation Order + +| Phase | Work | Skills touched | Effort | +|-------|------|---------------|--------| +| **1** | Create `references/erc8004-methods.md` + download ABIs | standards | Small | +| **2** | Create `agent-identity` skill with SKILL.md + `identity.sh` | new skill | Large | +| **3** | Update `standards` SKILL.md — cast examples, ValidationRegistry, full lifecycle | standards | Medium | +| **4** | Update `ethereum-networks` — 8004 read examples, common-contracts.md | ethereum-networks | Small | +| **5** | Update `local-ethereum-wallet` — contract interaction example | local-ethereum-wallet | Small | +| **6** | Update `orchestration` — shell-based agent commerce flow | orchestration | Small | +| **7** | Update `ship` — expand AI agent archetype | ship | Small | +| **8** | Update `addresses` — add ValidationRegistry | addresses | Small | +| **9** | Add IPFS pin command to `identity.sh` | agent-identity | Medium | +| **10** | (Future) Build agent-uri-server + Traefik route | new infra | Large | + +Phases 1-8 are the core skill updates. Phase 9 adds IPFS tooling. Phase 10 is the future Traefik API — outlined here for planning but not blocked on. + +--- + +## Validation Criteria + +- [ ] Agent can register an identity with `identity.sh register --uri ` end-to-end +- [ ] Agent can query any agent's reputation with `identity.sh reputation ` +- [ ] Agent can give feedback after interacting with another agent +- [ ] Agent can update its own URI and metadata without Solidity knowledge +- [ ] Agent can query registration and feedback events +- [ ] All write operations confirm before sending (show calldata, gas estimate, target) +- [ ] All scripts are POSIX sh (no bashisms), work in the OpenClaw pod +- [ ] ABIs are available as reference files for complex struct encoding +- [ ] `standards` skill documents all three registries with cast-based examples +- [ ] Skills cross-reference correctly: standards ↔ agent-identity ↔ ethereum-networks ↔ local-ethereum-wallet