repositories
loading repo index
repositories
loading repo index
repository
loading code, commits, and activity
public Clawd ADK gateway launch mirror
stars
latest
clone command
git clone gitlawb://did:key:z6Mkq5mY...iFZ5/my-project-publ...git clone gitlawb://did:key:z6Mkq5mY.../my-project-publ...2fa351d6docs: add automaton and perps launch sources16d ago| #1 | /** |
| #2 | * ERC-8004 On-Chain Agent Registration |
| #3 | * |
| #4 | * Registers the automaton on-chain as a Trustless Agent via ERC-8004. |
| #5 | * Uses the Identity Registry on Base mainnet. |
| #6 | * |
| #7 | * Contract: 0x8004A169FB4a3325136EB29fA0ceB6D2e539a432 (Base) |
| #8 | * Reputation: 0x8004BAa17C55a88189AE136b182e5fdA19dE9b63 (Base) |
| #9 | */ |
| #10 | |
| #11 | import { |
| #12 | createPublicClient, |
| #13 | createWalletClient, |
| #14 | http, |
| #15 | encodeFunctionData, |
| #16 | parseAbi, |
| #17 | type Address, |
| #18 | type PrivateKeyAccount, |
| #19 | } from "viem"; |
| #20 | import { base, baseSepolia } from "viem/chains"; |
| #21 | import type { |
| #22 | RegistryEntry, |
| #23 | ReputationEntry, |
| #24 | DiscoveredAgent, |
| #25 | AutomatonDatabase, |
| #26 | } from "../types.js"; |
| #27 | |
| #28 | // ─── Contract Addresses ────────────────────────────────────── |
| #29 | |
| #30 | const CONTRACTS = { |
| #31 | mainnet: { |
| #32 | identity: "0x8004A169FB4a3325136EB29fA0ceB6D2e539a432" as Address, |
| #33 | reputation: "0x8004BAa17C55a88189AE136b182e5fdA19dE9b63" as Address, |
| #34 | chain: base, |
| #35 | }, |
| #36 | testnet: { |
| #37 | identity: "0x8004A169FB4a3325136EB29fA0ceB6D2e539a432" as Address, |
| #38 | reputation: "0x8004BAa17C55a88189AE136b182e5fdA19dE9b63" as Address, |
| #39 | chain: baseSepolia, |
| #40 | }, |
| #41 | } as const; |
| #42 | |
| #43 | // ─── ABI (minimal subset needed for registration) ──────────── |
| #44 | |
| #45 | const IDENTITY_ABI = parseAbi([ |
| #46 | "function register(string agentURI) external returns (uint256 agentId)", |
| #47 | "function updateAgentURI(uint256 agentId, string newAgentURI) external", |
| #48 | "function agentURI(uint256 agentId) external view returns (string)", |
| #49 | "function ownerOf(uint256 tokenId) external view returns (address)", |
| #50 | "function totalSupply() external view returns (uint256)", |
| #51 | "function balanceOf(address owner) external view returns (uint256)", |
| #52 | ]); |
| #53 | |
| #54 | const REPUTATION_ABI = parseAbi([ |
| #55 | "function leaveFeedback(uint256 agentId, uint8 score, string comment) external", |
| #56 | "function getFeedback(uint256 agentId) external view returns (tuple(address from, uint8 score, string comment, uint256 timestamp)[])", |
| #57 | ]); |
| #58 | |
| #59 | type Network = "mainnet" | "testnet"; |
| #60 | |
| #61 | /** |
| #62 | * Register the automaton on-chain with ERC-8004. |
| #63 | * Returns the agent ID (NFT token ID). |
| #64 | */ |
| #65 | export async function registerAgent( |
| #66 | account: PrivateKeyAccount, |
| #67 | agentURI: string, |
| #68 | network: Network = "mainnet", |
| #69 | db: AutomatonDatabase, |
| #70 | ): Promise<RegistryEntry> { |
| #71 | const contracts = CONTRACTS[network]; |
| #72 | const chain = contracts.chain; |
| #73 | |
| #74 | const publicClient = createPublicClient({ |
| #75 | chain, |
| #76 | transport: http(), |
| #77 | }); |
| #78 | |
| #79 | const walletClient = createWalletClient({ |
| #80 | account, |
| #81 | chain, |
| #82 | transport: http(), |
| #83 | }); |
| #84 | |
| #85 | // Call register(agentURI) |
| #86 | const hash = await walletClient.writeContract({ |
| #87 | address: contracts.identity, |
| #88 | abi: IDENTITY_ABI, |
| #89 | functionName: "register", |
| #90 | args: [agentURI], |
| #91 | }); |
| #92 | |
| #93 | // Wait for transaction receipt |
| #94 | const receipt = await publicClient.waitForTransactionReceipt({ hash }); |
| #95 | |
| #96 | // Extract agentId from Transfer event logs |
| #97 | // The register function mints an ERC-721 token |
| #98 | let agentId = "0"; |
| #99 | for (const log of receipt.logs) { |
| #100 | if (log.topics.length >= 4) { |
| #101 | // Transfer(address from, address to, uint256 tokenId) |
| #102 | agentId = BigInt(log.topics[3]!).toString(); |
| #103 | break; |
| #104 | } |
| #105 | } |
| #106 | |
| #107 | const entry: RegistryEntry = { |
| #108 | agentId, |
| #109 | agentURI, |
| #110 | chain: `eip155:${chain.id}`, |
| #111 | contractAddress: contracts.identity, |
| #112 | txHash: hash, |
| #113 | registeredAt: new Date().toISOString(), |
| #114 | }; |
| #115 | |
| #116 | db.setRegistryEntry(entry); |
| #117 | return entry; |
| #118 | } |
| #119 | |
| #120 | /** |
| #121 | * Update the agent's URI on-chain. |
| #122 | */ |
| #123 | export async function updateAgentURI( |
| #124 | account: PrivateKeyAccount, |
| #125 | agentId: string, |
| #126 | newAgentURI: string, |
| #127 | network: Network = "mainnet", |
| #128 | db: AutomatonDatabase, |
| #129 | ): Promise<string> { |
| #130 | const contracts = CONTRACTS[network]; |
| #131 | const chain = contracts.chain; |
| #132 | |
| #133 | const walletClient = createWalletClient({ |
| #134 | account, |
| #135 | chain, |
| #136 | transport: http(), |
| #137 | }); |
| #138 | |
| #139 | const hash = await walletClient.writeContract({ |
| #140 | address: contracts.identity, |
| #141 | abi: IDENTITY_ABI, |
| #142 | functionName: "updateAgentURI", |
| #143 | args: [BigInt(agentId), newAgentURI], |
| #144 | }); |
| #145 | |
| #146 | // Update in DB |
| #147 | const entry = db.getRegistryEntry(); |
| #148 | if (entry) { |
| #149 | entry.agentURI = newAgentURI; |
| #150 | entry.txHash = hash; |
| #151 | db.setRegistryEntry(entry); |
| #152 | } |
| #153 | |
| #154 | return hash; |
| #155 | } |
| #156 | |
| #157 | /** |
| #158 | * Leave reputation feedback for another agent. |
| #159 | */ |
| #160 | export async function leaveFeedback( |
| #161 | account: PrivateKeyAccount, |
| #162 | agentId: string, |
| #163 | score: number, |
| #164 | comment: string, |
| #165 | network: Network = "mainnet", |
| #166 | db: AutomatonDatabase, |
| #167 | ): Promise<string> { |
| #168 | const contracts = CONTRACTS[network]; |
| #169 | const chain = contracts.chain; |
| #170 | |
| #171 | const walletClient = createWalletClient({ |
| #172 | account, |
| #173 | chain, |
| #174 | transport: http(), |
| #175 | }); |
| #176 | |
| #177 | const hash = await walletClient.writeContract({ |
| #178 | address: contracts.reputation, |
| #179 | abi: REPUTATION_ABI, |
| #180 | functionName: "leaveFeedback", |
| #181 | args: [BigInt(agentId), score, comment], |
| #182 | }); |
| #183 | |
| #184 | return hash; |
| #185 | } |
| #186 | |
| #187 | /** |
| #188 | * Query the registry for an agent by ID. |
| #189 | */ |
| #190 | export async function queryAgent( |
| #191 | agentId: string, |
| #192 | network: Network = "mainnet", |
| #193 | ): Promise<DiscoveredAgent | null> { |
| #194 | const contracts = CONTRACTS[network]; |
| #195 | const chain = contracts.chain; |
| #196 | |
| #197 | const publicClient = createPublicClient({ |
| #198 | chain, |
| #199 | transport: http(), |
| #200 | }); |
| #201 | |
| #202 | try { |
| #203 | const [uri, owner] = await Promise.all([ |
| #204 | publicClient.readContract({ |
| #205 | address: contracts.identity, |
| #206 | abi: IDENTITY_ABI, |
| #207 | functionName: "agentURI", |
| #208 | args: [BigInt(agentId)], |
| #209 | }), |
| #210 | publicClient.readContract({ |
| #211 | address: contracts.identity, |
| #212 | abi: IDENTITY_ABI, |
| #213 | functionName: "ownerOf", |
| #214 | args: [BigInt(agentId)], |
| #215 | }), |
| #216 | ]); |
| #217 | |
| #218 | return { |
| #219 | agentId, |
| #220 | owner: owner as string, |
| #221 | agentURI: uri as string, |
| #222 | }; |
| #223 | } catch { |
| #224 | return null; |
| #225 | } |
| #226 | } |
| #227 | |
| #228 | /** |
| #229 | * Get the total number of registered agents. |
| #230 | */ |
| #231 | export async function getTotalAgents( |
| #232 | network: Network = "mainnet", |
| #233 | ): Promise<number> { |
| #234 | const contracts = CONTRACTS[network]; |
| #235 | const chain = contracts.chain; |
| #236 | |
| #237 | const publicClient = createPublicClient({ |
| #238 | chain, |
| #239 | transport: http(), |
| #240 | }); |
| #241 | |
| #242 | try { |
| #243 | const supply = await publicClient.readContract({ |
| #244 | address: contracts.identity, |
| #245 | abi: IDENTITY_ABI, |
| #246 | functionName: "totalSupply", |
| #247 | }); |
| #248 | return Number(supply); |
| #249 | } catch { |
| #250 | return 0; |
| #251 | } |
| #252 | } |
| #253 | |
| #254 | /** |
| #255 | * Check if an address has a registered agent. |
| #256 | */ |
| #257 | export async function hasRegisteredAgent( |
| #258 | address: Address, |
| #259 | network: Network = "mainnet", |
| #260 | ): Promise<boolean> { |
| #261 | const contracts = CONTRACTS[network]; |
| #262 | const chain = contracts.chain; |
| #263 | |
| #264 | const publicClient = createPublicClient({ |
| #265 | chain, |
| #266 | transport: http(), |
| #267 | }); |
| #268 | |
| #269 | try { |
| #270 | const balance = await publicClient.readContract({ |
| #271 | address: contracts.identity, |
| #272 | abi: IDENTITY_ABI, |
| #273 | functionName: "balanceOf", |
| #274 | args: [address], |
| #275 | }); |
| #276 | return Number(balance) > 0; |
| #277 | } catch { |
| #278 | return false; |
| #279 | } |
| #280 | } |
| #281 |