ape-claw 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (114) hide show
  1. package/.cursor/skills/ape-claw/SKILL.md +322 -0
  2. package/LICENSE +21 -0
  3. package/README.md +826 -0
  4. package/allowlists/opensea-slug-overrides.json +13 -0
  5. package/allowlists/recommended.apechain.json +322 -0
  6. package/config/clawbots.example.json +3 -0
  7. package/config/policy.example.json +27 -0
  8. package/data/starter-pack-bundle.json +1 -0
  9. package/data/starter-pack.json +495 -0
  10. package/docs/ACP_BOUNTIES.md +108 -0
  11. package/docs/APECLAW_V2_ALPHA.md +206 -0
  12. package/docs/AUTONOMY_AND_SUBSTRATE.md +69 -0
  13. package/docs/CLAWBOTS_AND_INVITES.md +102 -0
  14. package/docs/CLI_GUIDE.md +124 -0
  15. package/docs/CONTRIBUTING.md +130 -0
  16. package/docs/DASHBOARD_GUIDE.md +108 -0
  17. package/docs/GLOBAL_BACKEND.md +145 -0
  18. package/docs/ONCHAIN_V2_GUIDE.md +140 -0
  19. package/docs/PRODUCT_OVERVIEW.md +127 -0
  20. package/docs/README.md +40 -0
  21. package/docs/SKILLCARDS_AND_IMPORTER.md +147 -0
  22. package/docs/STARTER_PACK.md +297 -0
  23. package/docs/SUPPORTED_NETWORKS.md +58 -0
  24. package/docs/TELEMETRY_AND_EVENTS.md +103 -0
  25. package/docs/THE_POD_RUNNER.md +198 -0
  26. package/docs/V1_WORKFLOWS.md +108 -0
  27. package/docs/V2_ONCHAIN_SKILLS.md +157 -0
  28. package/docs/WEB4_PLAN_STATUS.md +95 -0
  29. package/docs/WEB4_SWARM_MODEL.md +104 -0
  30. package/docs/archive/AUTONOMY_AND_SUBSTRATE.md +66 -0
  31. package/docs/archive/WEB4_PLAN_STATUS.md +93 -0
  32. package/docs/archive/WEB4_SWARM_MODEL.md +98 -0
  33. package/docs/developer/01-architecture.md +345 -0
  34. package/docs/developer/02-contracts.md +1034 -0
  35. package/docs/developer/03-writing-modules.md +513 -0
  36. package/docs/developer/04-skillcard-spec.md +336 -0
  37. package/docs/developer/05-backend-api.md +1079 -0
  38. package/docs/developer/06-telemetry.md +798 -0
  39. package/docs/developer/07-testing.md +546 -0
  40. package/docs/developer/08-contributing.md +211 -0
  41. package/docs/operator/01-quickstart.md +49 -0
  42. package/docs/operator/02-dashboard.md +174 -0
  43. package/docs/operator/03-cli-reference.md +818 -0
  44. package/docs/operator/04-skills-library.md +169 -0
  45. package/docs/operator/05-pod-operations.md +314 -0
  46. package/docs/operator/06-deployment.md +299 -0
  47. package/docs/operator/07-safety-and-policy.md +311 -0
  48. package/docs/operator/08-troubleshooting.md +457 -0
  49. package/docs/operator/09-env-reference.md +238 -0
  50. package/docs/social/STARTER_PACK_THREAD.md +209 -0
  51. package/package.json +77 -0
  52. package/skillcards/import-sources.json +93 -0
  53. package/skillcards/seed/acp-bounty-poll.v1.json +38 -0
  54. package/skillcards/seed/acp-bounty-post.v1.json +55 -0
  55. package/skillcards/seed/acp-browse.v1.json +41 -0
  56. package/skillcards/seed/acp-fulfill-and-route.v1.json +56 -0
  57. package/skillcards/seed/apeclaw-bridge-relay.v1.json +46 -0
  58. package/skillcards/seed/apeclaw-nft-autobuy.v1.json +60 -0
  59. package/skillcards/seed/apeclaw-receipt-recorder.v1.json +64 -0
  60. package/skillcards/seed/humanizer.v1.json +74 -0
  61. package/skillcards/seed/otherside-navigator.v1.json +116 -0
  62. package/skillcards/seed/stonkbrokers-launcher.v1.json +280 -0
  63. package/skillcards/seed/walkie-p2p.v1.json +66 -0
  64. package/src/cli/index.mjs +8 -0
  65. package/src/cli.mjs +1929 -0
  66. package/src/lib/bridge-relay.mjs +294 -0
  67. package/src/lib/clawbots.mjs +94 -0
  68. package/src/lib/io.mjs +36 -0
  69. package/src/lib/market.mjs +233 -0
  70. package/src/lib/nft-opensea.mjs +159 -0
  71. package/src/lib/paths.mjs +17 -0
  72. package/src/lib/pod-init.mjs +40 -0
  73. package/src/lib/policy.mjs +112 -0
  74. package/src/lib/rpc.mjs +49 -0
  75. package/src/lib/telemetry.mjs +92 -0
  76. package/src/lib/v2-onchain-abi.mjs +294 -0
  77. package/src/lib/v2-skillcard.mjs +27 -0
  78. package/src/server/index.mjs +169 -0
  79. package/src/server/logger.mjs +21 -0
  80. package/src/server/middleware/auth.mjs +90 -0
  81. package/src/server/middleware/body-limit.mjs +35 -0
  82. package/src/server/middleware/cors.mjs +33 -0
  83. package/src/server/middleware/rate-limit.mjs +44 -0
  84. package/src/server/routes/chat.mjs +178 -0
  85. package/src/server/routes/clawbots.mjs +182 -0
  86. package/src/server/routes/events.mjs +95 -0
  87. package/src/server/routes/health.mjs +72 -0
  88. package/src/server/routes/pod.mjs +64 -0
  89. package/src/server/routes/quotes.mjs +161 -0
  90. package/src/server/routes/skills.mjs +239 -0
  91. package/src/server/routes/static.mjs +161 -0
  92. package/src/server/routes/v2.mjs +48 -0
  93. package/src/server/sse.mjs +73 -0
  94. package/src/server/storage/file-backend.mjs +295 -0
  95. package/src/server/storage/index.mjs +37 -0
  96. package/src/server/storage/sqlite-backend.mjs +380 -0
  97. package/src/telemetry-server.mjs +1604 -0
  98. package/ui/css/dashboard.css +792 -0
  99. package/ui/css/skills.css +689 -0
  100. package/ui/docs.html +840 -0
  101. package/ui/favicon-180.png +0 -0
  102. package/ui/favicon-192.png +0 -0
  103. package/ui/favicon-32.png +0 -0
  104. package/ui/favicon-lobster.png +0 -0
  105. package/ui/favicon.svg +10 -0
  106. package/ui/index.html +2957 -0
  107. package/ui/js/dashboard.js +1766 -0
  108. package/ui/js/skills.js +1621 -0
  109. package/ui/pod.html +909 -0
  110. package/ui/shared/motion.css +286 -0
  111. package/ui/shared/motion.js +170 -0
  112. package/ui/shared/sidebar-nav.css +379 -0
  113. package/ui/shared/sidebar-nav.js +137 -0
  114. package/ui/skills.html +2879 -0
@@ -0,0 +1,1034 @@
1
+ # Smart Contracts Reference
2
+
3
+ Complete reference for all ApeClaw v2 smart contracts, including ABIs, function signatures, events, and viem integration examples.
4
+
5
+ ## Table of Contents
6
+
7
+ - [Core Contracts](#core-contracts)
8
+ - [SkillNFT](#skillnft)
9
+ - [SkillRegistry](#skillregistry)
10
+ - [IntentRegistry](#intentregistry)
11
+ - [ReceiptRegistry](#receiptregistry)
12
+ - [PodVault](#podvault)
13
+ - [Execution Contracts](#execution-contracts)
14
+ - [AgentAccount](#agentaccount)
15
+ - [PolicyEngine](#policyengine)
16
+ - [Module Contracts](#module-contracts)
17
+ - [ISkillModule](#iskillmodule)
18
+ - [SwapModule](#swapmodule)
19
+ - [BridgeModule](#bridgemodule)
20
+ - [NftBuyModule](#nftbuymodule)
21
+ - [System Flows](#system-flows)
22
+ - [Deployment Flow](#deployment-flow)
23
+ - [Execution Flow](#execution-flow)
24
+ - [Revenue Flow](#revenue-flow)
25
+
26
+ ---
27
+
28
+ ## Core Contracts
29
+
30
+ ### SkillNFT
31
+
32
+ **Purpose:** ERC-721 NFT contract that mints one token per skill, providing provenance and ownership tracking with optional EIP-2981 royalty support.
33
+
34
+ **Constructor:**
35
+ ```solidity
36
+ constructor()
37
+ ```
38
+ - No arguments
39
+ - Sets name: `"ApeClaw Skill"`, symbol: `"ACSKILL"`
40
+
41
+ **Key Functions:**
42
+
43
+ | Function | Signature | Description | Access Control |
44
+ |----------|-----------|-------------|----------------|
45
+ | `mintSkill` | `mintSkill(uint256 parentId) returns (uint256 skillId)` | Mints a new skill NFT with optional parent relationship | Public |
46
+ | `mintSkillWithRoyalty` | `mintSkillWithRoyalty(uint256 parentId, address receiver, uint96 feeBps) returns (uint256 skillId)` | Mints a skill NFT and sets EIP-2981 royalty receiver in one transaction | Public |
47
+ | `setSkillRoyalty` | `setSkillRoyalty(uint256 skillId, address receiver, uint96 feeBps)` | Updates royalty receiver for an existing skill NFT | Owner only |
48
+ | `parentSkillId` | `parentSkillId(uint256 skillId) returns (uint256)` | View function to get parent skill ID (0 = no parent) | Public |
49
+ | `nextSkillId` | `nextSkillId() returns (uint256)` | View function returning the next skill ID to be minted | Public |
50
+
51
+ **Events:**
52
+
53
+ | Event | Parameters |
54
+ |-------|------------|
55
+ | `SkillMinted` | `uint256 indexed skillId, address indexed owner, uint256 indexed parentId` |
56
+ | `SkillRoyaltySet` | `uint256 indexed skillId, address indexed receiver, uint96 feeBps` |
57
+
58
+ **viem Example:**
59
+
60
+ ```typescript
61
+ import { createWalletClient, http, parseEther } from 'viem'
62
+ import { privateKeyToAccount } from 'viem/accounts'
63
+ import { apechain } from 'viem/chains'
64
+
65
+ const account = privateKeyToAccount('0x...')
66
+ const client = createWalletClient({
67
+ account,
68
+ chain: apechain,
69
+ transport: http()
70
+ })
71
+
72
+ // Deploy SkillNFT
73
+ const skillNftAddress = await client.deployContract({
74
+ abi: SkillNFT_ABI,
75
+ bytecode: SkillNFT_BYTECODE,
76
+ args: []
77
+ })
78
+
79
+ // Mint a skill with royalty (5% to PodVault)
80
+ const hash = await client.writeContract({
81
+ address: skillNftAddress,
82
+ abi: SkillNFT_ABI,
83
+ functionName: 'mintSkillWithRoyalty',
84
+ args: [
85
+ 0n, // parentId (0 = no parent)
86
+ podVaultAddress, // receiver
87
+ 500n // feeBps (500 = 5%)
88
+ ]
89
+ })
90
+
91
+ // Read next skill ID
92
+ const nextId = await client.readContract({
93
+ address: skillNftAddress,
94
+ abi: SkillNFT_ABI,
95
+ functionName: 'nextSkillId'
96
+ })
97
+ ```
98
+
99
+ ---
100
+
101
+ ### SkillRegistry
102
+
103
+ **Purpose:** Immutable append-only registry that stores versioned skill metadata, linking each version to an offchain SkillCard via content hash and URI.
104
+
105
+ **Constructor:**
106
+ ```solidity
107
+ constructor(address skillNftAddress)
108
+ ```
109
+ - `skillNftAddress`: Address of the SkillNFT contract
110
+
111
+ **Key Functions:**
112
+
113
+ | Function | Signature | Description | Access Control |
114
+ |----------|-----------|-------------|----------------|
115
+ | `publishVersion` | `publishVersion(uint256 skillId, bytes32 versionHash, bytes32 contentHash, string calldata uri, uint8 riskTier)` | Publishes a new version of a skill to the registry | Public (permissionless) |
116
+ | `versionCount` | `versionCount(uint256 skillId) returns (uint256)` | Returns the number of versions published for a skill | Public |
117
+ | `getVersion` | `getVersion(uint256 skillId, uint256 idx) returns (SkillVersion)` | Retrieves a specific version by index | Public |
118
+
119
+ **Struct:**
120
+ ```solidity
121
+ struct SkillVersion {
122
+ bytes32 versionHash; // Semantic version identifier
123
+ bytes32 contentHash; // Hash of SkillCard JSON
124
+ string uri; // IPFS/Arweave/HTTP gateway URL
125
+ uint8 riskTier; // 0=unknown, 1=low, 2=medium, 3=high
126
+ uint64 publishedAt; // Unix timestamp
127
+ address publisher; // Address that published this version
128
+ }
129
+ ```
130
+
131
+ **Events:**
132
+
133
+ | Event | Parameters |
134
+ |-------|------------|
135
+ | `SkillVersionPublished` | `uint256 indexed skillId, bytes32 indexed versionHash, bytes32 indexed contentHash, address publisher, string uri, uint8 riskTier` |
136
+
137
+ **viem Example:**
138
+
139
+ ```typescript
140
+ import { keccak256, toBytes, stringToHex } from 'viem'
141
+
142
+ // Compute hashes (using canonical JSON stringify)
143
+ const versionHash = keccak256(stringToHex(skillcard.version))
144
+ const contentHash = keccak256(stringToHex(JSON.stringify(skillcard)))
145
+
146
+ // Publish a skill version
147
+ const hash = await client.writeContract({
148
+ address: skillRegistryAddress,
149
+ abi: SkillRegistry_ABI,
150
+ functionName: 'publishVersion',
151
+ args: [
152
+ skillId,
153
+ versionHash,
154
+ contentHash,
155
+ 'ipfs://Qm...', // URI
156
+ 1 // riskTier (1 = low)
157
+ ]
158
+ })
159
+
160
+ // Read version count
161
+ const count = await client.readContract({
162
+ address: skillRegistryAddress,
163
+ abi: SkillRegistry_ABI,
164
+ functionName: 'versionCount',
165
+ args: [skillId]
166
+ })
167
+
168
+ // Get latest version
169
+ const latestVersion = await client.readContract({
170
+ address: skillRegistryAddress,
171
+ abi: SkillRegistry_ABI,
172
+ functionName: 'getVersion',
173
+ args: [skillId, count - 1n]
174
+ })
175
+ ```
176
+
177
+ ---
178
+
179
+ ### IntentRegistry
180
+
181
+ **Purpose:** Minimal intent registry where users can publish intents (opaque hashes) for offchain solvers to observe and compete to fulfill.
182
+
183
+ **Constructor:**
184
+ ```solidity
185
+ constructor()
186
+ ```
187
+ - No arguments
188
+
189
+ **Key Functions:**
190
+
191
+ | Function | Signature | Description | Access Control |
192
+ |----------|-----------|-------------|----------------|
193
+ | `createIntent` | `createIntent(bytes32 intentHash, uint64 expiresAt) returns (uint256 intentId)` | Creates a new intent with optional expiration | Public |
194
+ | `cancelIntent` | `cancelIntent(uint256 intentId)` | Cancels an active intent | Creator only |
195
+ | `isActive` | `isActive(uint256 intentId) returns (bool)` | Checks if an intent is active (not cancelled, not expired) | Public |
196
+ | `intents` | `intents(uint256 intentId) returns (Intent)` | View function to get intent details | Public |
197
+ | `nextIntentId` | `nextIntentId() returns (uint256)` | View function returning the next intent ID | Public |
198
+
199
+ **Struct:**
200
+ ```solidity
201
+ struct Intent {
202
+ address creator;
203
+ bytes32 intentHash; // Hash of structured intent payload
204
+ uint64 createdAt;
205
+ uint64 expiresAt; // 0 = no expiry
206
+ bool cancelled;
207
+ }
208
+ ```
209
+
210
+ **Events:**
211
+
212
+ | Event | Parameters |
213
+ |-------|------------|
214
+ | `IntentCreated` | `uint256 indexed intentId, address indexed creator, bytes32 indexed intentHash, uint64 expiresAt` |
215
+ | `IntentCancelled` | `uint256 indexed intentId, address indexed creator` |
216
+
217
+ **viem Example:**
218
+
219
+ ```typescript
220
+ // Create an intent (expires in 1 hour)
221
+ const intentHash = keccak256(stringToHex(JSON.stringify(intentPayload)))
222
+ const expiresAt = BigInt(Math.floor(Date.now() / 1000) + 3600)
223
+
224
+ const hash = await client.writeContract({
225
+ address: intentRegistryAddress,
226
+ abi: IntentRegistry_ABI,
227
+ functionName: 'createIntent',
228
+ args: [intentHash, expiresAt]
229
+ })
230
+
231
+ // Check if intent is active
232
+ const active = await client.readContract({
233
+ address: intentRegistryAddress,
234
+ abi: IntentRegistry_ABI,
235
+ functionName: 'isActive',
236
+ args: [intentId]
237
+ })
238
+
239
+ // Cancel an intent
240
+ await client.writeContract({
241
+ address: intentRegistryAddress,
242
+ abi: IntentRegistry_ABI,
243
+ functionName: 'cancelIntent',
244
+ args: [intentId]
245
+ })
246
+ ```
247
+
248
+ ---
249
+
250
+ ### ReceiptRegistry
251
+
252
+ **Purpose:** Append-only registry for recording execution receipts tied to trace IDs, providing an onchain audit trail for skill executions.
253
+
254
+ **Constructor:**
255
+ ```solidity
256
+ constructor()
257
+ ```
258
+ - No arguments
259
+
260
+ **Key Functions:**
261
+
262
+ | Function | Signature | Description | Access Control |
263
+ |----------|-----------|-------------|----------------|
264
+ | `recordReceipt` | `recordReceipt(bytes32 traceIdHash, bytes32 contentHash, bytes32 subject, string calldata uri)` | Records a new receipt (append-only, one per traceIdHash) | Public (permissionless) |
265
+ | `isRecorded` | `isRecorded(bytes32 traceIdHash) returns (bool)` | Checks if a receipt exists for a trace ID | Public |
266
+ | `getReceipt` | `getReceipt(bytes32 traceIdHash) returns (Receipt)` | Retrieves a receipt by trace ID | Public |
267
+
268
+ **Struct:**
269
+ ```solidity
270
+ struct Receipt {
271
+ bytes32 traceIdHash; // Unique trace identifier
272
+ bytes32 contentHash; // Hash of receipt content
273
+ bytes32 subject; // Generic subject (agentId, skillId, etc.)
274
+ string uri; // IPFS/Arweave URI to full receipt
275
+ uint64 recordedAt; // Unix timestamp
276
+ address recorder; // Address that recorded this receipt
277
+ }
278
+ ```
279
+
280
+ **Events:**
281
+
282
+ | Event | Parameters |
283
+ |-------|------------|
284
+ | `ReceiptRecorded` | `bytes32 indexed traceIdHash, bytes32 indexed contentHash, bytes32 indexed subject, address recorder, string uri` |
285
+
286
+ **viem Example:**
287
+
288
+ ```typescript
289
+ // Record a receipt
290
+ const traceIdHash = keccak256(stringToHex(traceId))
291
+ const contentHash = keccak256(stringToHex(JSON.stringify(receiptData)))
292
+ const subject = keccak256(stringToHex(agentId))
293
+
294
+ const hash = await client.writeContract({
295
+ address: receiptRegistryAddress,
296
+ abi: ReceiptRegistry_ABI,
297
+ functionName: 'recordReceipt',
298
+ args: [
299
+ traceIdHash,
300
+ contentHash,
301
+ subject,
302
+ 'ipfs://Qm...' // URI to full receipt JSON
303
+ ]
304
+ })
305
+
306
+ // Check if receipt exists
307
+ const exists = await client.readContract({
308
+ address: receiptRegistryAddress,
309
+ abi: ReceiptRegistry_ABI,
310
+ functionName: 'isRecorded',
311
+ args: [traceIdHash]
312
+ })
313
+
314
+ // Get receipt
315
+ const receipt = await client.readContract({
316
+ address: receiptRegistryAddress,
317
+ abi: ReceiptRegistry_ABI,
318
+ functionName: 'getReceipt',
319
+ args: [traceIdHash]
320
+ })
321
+ ```
322
+
323
+ ---
324
+
325
+ ### PodVault
326
+
327
+ **Purpose:** Revenue split vault that receives native tokens and ERC-20 tokens, distributing them proportionally to members based on their shares (PaymentSplitter-style).
328
+
329
+ **Constructor:**
330
+ ```solidity
331
+ constructor(address[] memory members, uint256[] memory memberShares) payable
332
+ ```
333
+ - `members`: Array of member addresses
334
+ - `memberShares`: Array of share amounts (must match members length)
335
+
336
+ **Key Functions:**
337
+
338
+ | Function | Signature | Description | Access Control |
339
+ |----------|-----------|-------------|----------------|
340
+ | `releaseNative` | `releaseNative(address payable member)` | Releases pending native token payment to a member | Public (anyone can trigger) |
341
+ | `releaseToken` | `releaseToken(address token, address member)` | Releases pending ERC-20 token payment to a member | Public (anyone can trigger) |
342
+ | `pendingNative` | `pendingNative(address member) returns (uint256)` | Calculates pending native token payment for a member | Public |
343
+ | `pendingToken` | `pendingToken(address token, address member) returns (uint256)` | Calculates pending ERC-20 token payment for a member | Public |
344
+ | `memberCount` | `memberCount() returns (uint256)` | Returns the number of members | Public |
345
+ | `memberAt` | `memberAt(uint256 idx) returns (address)` | Returns member address at index | Public |
346
+ | `shares` | `shares(address member) returns (uint256)` | Returns share amount for a member | Public |
347
+ | `releasedNative` | `releasedNative(address member) returns (uint256)` | Returns total native tokens released to a member | Public |
348
+ | `releasedToken` | `releasedToken(address token, address member) returns (uint256)` | Returns total ERC-20 tokens released to a member | Public |
349
+
350
+ **Events:**
351
+
352
+ | Event | Parameters |
353
+ |-------|------------|
354
+ | `MemberAdded` | `address indexed member, uint256 shares` |
355
+ | `PaymentReleased` | `address indexed to, uint256 amount` |
356
+ | `ERC20PaymentReleased` | `address indexed token, address indexed to, uint256 amount` |
357
+ | `PaymentReceived` | `address indexed from, uint256 amount` |
358
+
359
+ **viem Example:**
360
+
361
+ ```typescript
362
+ // Deploy PodVault with members
363
+ const members = [member1Address, member2Address, member3Address]
364
+ const shares = [4000n, 3000n, 3000n] // Total: 10000 (100%)
365
+
366
+ const podVaultAddress = await client.deployContract({
367
+ abi: PodVault_ABI,
368
+ bytecode: PodVault_BYTECODE,
369
+ args: [members, shares]
370
+ })
371
+
372
+ // Send native tokens to vault (via SkillNFT royalty or direct transfer)
373
+ await client.sendTransaction({
374
+ to: podVaultAddress,
375
+ value: parseEther('1.0')
376
+ })
377
+
378
+ // Check pending payment for a member
379
+ const pending = await client.readContract({
380
+ address: podVaultAddress,
381
+ abi: PodVault_ABI,
382
+ functionName: 'pendingNative',
383
+ args: [member1Address]
384
+ })
385
+
386
+ // Release payment to member
387
+ const hash = await client.writeContract({
388
+ address: podVaultAddress,
389
+ abi: PodVault_ABI,
390
+ functionName: 'releaseNative',
391
+ args: [member1Address]
392
+ })
393
+
394
+ // Release ERC-20 token payment
395
+ await client.writeContract({
396
+ address: podVaultAddress,
397
+ abi: PodVault_ABI,
398
+ functionName: 'releaseToken',
399
+ args: [tokenAddress, member1Address]
400
+ })
401
+ ```
402
+
403
+ ---
404
+
405
+ ## Execution Contracts
406
+
407
+ ### AgentAccount
408
+
409
+ **Purpose:** Execution shell that runs onchain module skills with PolicyEngine hooks and records audit receipts to ReceiptRegistry.
410
+
411
+ **Constructor:**
412
+ ```solidity
413
+ constructor(address owner_, address policyEngine, address receiptRegistry)
414
+ ```
415
+ - `owner_`: Owner address (typically the agent's wallet)
416
+ - `policyEngine`: Address of PolicyEngine contract
417
+ - `receiptRegistry`: Address of ReceiptRegistry contract
418
+
419
+ **Key Functions:**
420
+
421
+ | Function | Signature | Description | Access Control |
422
+ |----------|-----------|-------------|----------------|
423
+ | `executeSkill` | `executeSkill(address module, bytes calldata input, uint256 value, bytes32 traceIdHash, bytes32 subjectHash, string calldata uri) external payable returns (bytes memory output)` | Executes a module skill with policy checks and receipt recording | Owner only |
424
+ | `setPolicyEngine` | `setPolicyEngine(address policyEngine)` | Updates the PolicyEngine address | Owner only |
425
+ | `setReceiptRegistry` | `setReceiptRegistry(address receiptRegistry)` | Updates the ReceiptRegistry address | Owner only |
426
+ | `policy` | `policy() returns (PolicyEngine)` | View function returning PolicyEngine address | Public |
427
+ | `receipts` | `receipts() returns (ReceiptRegistry)` | View function returning ReceiptRegistry address | Public |
428
+
429
+ **Input Format:**
430
+ The `input` parameter must be ABI-encoded as:
431
+ ```solidity
432
+ abi.encode(address target, bytes calldata data)
433
+ ```
434
+
435
+ **Events:**
436
+
437
+ | Event | Parameters |
438
+ |-------|------------|
439
+ | `SkillExecuted` | `bytes32 indexed traceIdHash, bytes32 indexed contentHash, address indexed module, address target, bytes4 selector, uint256 value, bool receiptRecorded` |
440
+
441
+ **viem Example:**
442
+
443
+ ```typescript
444
+ import { encodeAbiParameters, parseAbiParameters } from 'viem'
445
+
446
+ // Prepare input: encode target address and calldata
447
+ const target = swapRouterAddress
448
+ const calldata = encodeFunctionData({
449
+ abi: SwapRouter_ABI,
450
+ functionName: 'swapExactETHForTokens',
451
+ args: [minAmountOut, path, recipient, deadline]
452
+ })
453
+
454
+ const input = encodeAbiParameters(
455
+ parseAbiParameters('address target, bytes calldata data'),
456
+ [target, calldata]
457
+ )
458
+
459
+ // Execute skill
460
+ const traceIdHash = keccak256(stringToHex(traceId))
461
+ const subjectHash = keccak256(stringToHex(skillId))
462
+
463
+ const hash = await client.writeContract({
464
+ address: agentAccountAddress,
465
+ abi: AgentAccount_ABI,
466
+ functionName: 'executeSkill',
467
+ args: [
468
+ swapModuleAddress, // module
469
+ input, // encoded (target, calldata)
470
+ parseEther('0.1'), // value
471
+ traceIdHash, // trace ID
472
+ subjectHash, // subject (skillId hash)
473
+ 'ipfs://Qm...' // receipt URI
474
+ ],
475
+ value: parseEther('0.1') // Must match value parameter
476
+ })
477
+
478
+ // Listen for execution event
479
+ const receipt = await publicClient.waitForTransactionReceipt({ hash })
480
+ const event = receipt.logs.find(log =>
481
+ log.topics[0] === keccak256(stringToHex('SkillExecuted(bytes32,bytes32,address,address,bytes4,uint256,bool)'))
482
+ )
483
+ ```
484
+
485
+ ---
486
+
487
+ ### PolicyEngine
488
+
489
+ **Purpose:** Minimal policy hook that enforces allowlists and per-transaction value caps before AgentAccount executes a module.
490
+
491
+ **Constructor:**
492
+ ```solidity
493
+ constructor(address owner_)
494
+ ```
495
+ - `owner_`: Owner address (typically the agent's wallet)
496
+
497
+ **Key Functions:**
498
+
499
+ | Function | Signature | Description | Access Control |
500
+ |----------|-----------|-------------|----------------|
501
+ | `preCheck` | `preCheck(address module, address target, bytes4 selector, uint256 value) external view` | Validates module, target, selector, and value against policy | Public (called by AgentAccount) |
502
+ | `setMaxValuePerTx` | `setMaxValuePerTx(uint256 v)` | Sets maximum native token value per transaction | Owner only |
503
+ | `setModuleAllowed` | `setModuleAllowed(address module, bool allowed)` | Adds/removes module from allowlist | Owner only |
504
+ | `setTargetAllowed` | `setTargetAllowed(address target, bool allowed)` | Adds/removes target contract from allowlist | Owner only |
505
+ | `setSelectorAllowed` | `setSelectorAllowed(address target, bytes4 selector, bool allowed)` | Adds/removes function selector from allowlist | Owner only |
506
+ | `maxValuePerTx` | `maxValuePerTx() returns (uint256)` | View function returning max value per transaction | Public |
507
+ | `allowedModules` | `allowedModules(address module) returns (bool)` | View function checking if module is allowed | Public |
508
+ | `allowedTargets` | `allowedTargets(address target) returns (bool)` | View function checking if target is allowed | Public |
509
+ | `allowedSelectors` | `allowedSelectors(address target, bytes4 selector) returns (bool)` | View function checking if selector is allowed | Public |
510
+
511
+ **Events:**
512
+
513
+ | Event | Parameters |
514
+ |-------|------------|
515
+ | `ModuleAllowed` | `address indexed module, bool allowed` |
516
+ | `TargetAllowed` | `address indexed target, bool allowed` |
517
+ | `SelectorAllowed` | `address indexed target, bytes4 indexed selector, bool allowed` |
518
+ | `MaxValuePerTxSet` | `uint256 value` |
519
+
520
+ **viem Example:**
521
+
522
+ ```typescript
523
+ // Configure PolicyEngine
524
+ const policyEngineAddress = await client.deployContract({
525
+ abi: PolicyEngine_ABI,
526
+ bytecode: PolicyEngine_BYTECODE,
527
+ args: [ownerAddress]
528
+ })
529
+
530
+ // Set max value per transaction (1 ETH)
531
+ await client.writeContract({
532
+ address: policyEngineAddress,
533
+ abi: PolicyEngine_ABI,
534
+ functionName: 'setMaxValuePerTx',
535
+ args: [parseEther('1.0')]
536
+ })
537
+
538
+ // Allow modules
539
+ await client.writeContract({
540
+ address: policyEngineAddress,
541
+ abi: PolicyEngine_ABI,
542
+ functionName: 'setModuleAllowed',
543
+ args: [swapModuleAddress, true]
544
+ })
545
+
546
+ await client.writeContract({
547
+ address: policyEngineAddress,
548
+ abi: PolicyEngine_ABI,
549
+ functionName: 'setModuleAllowed',
550
+ args: [bridgeModuleAddress, true]
551
+ })
552
+
553
+ // Allow target contracts
554
+ await client.writeContract({
555
+ address: policyEngineAddress,
556
+ abi: PolicyEngine_ABI,
557
+ functionName: 'setTargetAllowed',
558
+ args: [uniswapRouterAddress, true]
559
+ })
560
+
561
+ // Allow specific function selectors
562
+ const swapSelector = '0x7ff36ab5' // swapExactETHForTokens
563
+ await client.writeContract({
564
+ address: policyEngineAddress,
565
+ abi: PolicyEngine_ABI,
566
+ functionName: 'setSelectorAllowed',
567
+ args: [uniswapRouterAddress, swapSelector, true]
568
+ })
569
+
570
+ // Check policy before execution
571
+ const maxValue = await client.readContract({
572
+ address: policyEngineAddress,
573
+ abi: PolicyEngine_ABI,
574
+ functionName: 'maxValuePerTx'
575
+ })
576
+
577
+ const moduleAllowed = await client.readContract({
578
+ address: policyEngineAddress,
579
+ abi: PolicyEngine_ABI,
580
+ functionName: 'allowedModules',
581
+ args: [swapModuleAddress]
582
+ })
583
+ ```
584
+
585
+ ---
586
+
587
+ ## Module Contracts
588
+
589
+ ### ISkillModule
590
+
591
+ **Purpose:** Minimal interface that all execution modules must implement.
592
+
593
+ **Interface:**
594
+ ```solidity
595
+ interface ISkillModule {
596
+ function execute(address agentAccount, bytes calldata input) external payable returns (bytes memory output);
597
+ }
598
+ ```
599
+
600
+ **Parameters:**
601
+ - `agentAccount`: Address of the AgentAccount calling this module
602
+ - `input`: ABI-encoded input (typically `(address target, bytes calldata data)`)
603
+ - Returns: `bytes memory output` - Opaque output from module execution
604
+
605
+ **viem Example:**
606
+
607
+ ```typescript
608
+ // Implementing a custom module
609
+ const CustomModule_ABI = [
610
+ {
611
+ type: 'function',
612
+ name: 'execute',
613
+ inputs: [
614
+ { name: 'agentAccount', type: 'address' },
615
+ { name: 'input', type: 'bytes' }
616
+ ],
617
+ outputs: [{ name: 'output', type: 'bytes' }],
618
+ stateMutability: 'payable'
619
+ }
620
+ ]
621
+
622
+ // Deploy custom module
623
+ const customModuleAddress = await client.deployContract({
624
+ abi: CustomModule_ABI,
625
+ bytecode: CustomModule_BYTECODE,
626
+ args: []
627
+ })
628
+
629
+ // Module will be called by AgentAccount.executeSkill
630
+ ```
631
+
632
+ ---
633
+
634
+ ### SwapModule
635
+
636
+ **Purpose:** Executes policy-gated swap calls to a target DEX router contract.
637
+
638
+ **Constructor:**
639
+ ```solidity
640
+ constructor()
641
+ ```
642
+ - No arguments
643
+
644
+ **Key Functions:**
645
+
646
+ | Function | Signature | Description | Access Control |
647
+ |----------|-----------|-------------|----------------|
648
+ | `execute` | `execute(address agentAccount, bytes calldata input) external payable returns (bytes memory output)` | Executes swap call to target router | Public (called by AgentAccount) |
649
+
650
+ **Events:**
651
+
652
+ | Event | Parameters |
653
+ |-------|------------|
654
+ | `SwapExecuted` | `address indexed agent, address indexed target, bytes4 indexed selector, uint256 value` |
655
+
656
+ **viem Example:**
657
+
658
+ ```typescript
659
+ // Deploy SwapModule
660
+ const swapModuleAddress = await client.deployContract({
661
+ abi: SwapModule_ABI,
662
+ bytecode: SwapModule_BYTECODE,
663
+ args: []
664
+ })
665
+
666
+ // Execute via AgentAccount (see AgentAccount example above)
667
+ // The module will decode input, call target with calldata, and return output
668
+ ```
669
+
670
+ ---
671
+
672
+ ### BridgeModule
673
+
674
+ **Purpose:** Executes policy-gated bridge calls to a target bridge contract.
675
+
676
+ **Constructor:**
677
+ ```solidity
678
+ constructor()
679
+ ```
680
+ - No arguments
681
+
682
+ **Key Functions:**
683
+
684
+ | Function | Signature | Description | Access Control |
685
+ |----------|-----------|-------------|----------------|
686
+ | `execute` | `execute(address agentAccount, bytes calldata input) external payable returns (bytes memory output)` | Executes bridge call to target bridge | Public (called by AgentAccount) |
687
+
688
+ **Events:**
689
+
690
+ | Event | Parameters |
691
+ |-------|------------|
692
+ | `BridgeExecuted` | `address indexed agent, address indexed target, bytes4 indexed selector, uint256 value` |
693
+
694
+ **viem Example:**
695
+
696
+ ```typescript
697
+ // Deploy BridgeModule
698
+ const bridgeModuleAddress = await client.deployContract({
699
+ abi: BridgeModule_ABI,
700
+ bytecode: BridgeModule_BYTECODE,
701
+ args: []
702
+ })
703
+
704
+ // Execute via AgentAccount (see AgentAccount example above)
705
+ ```
706
+
707
+ ---
708
+
709
+ ### NftBuyModule
710
+
711
+ **Purpose:** Executes policy-gated NFT purchase calls to a target marketplace contract (e.g., Seaport).
712
+
713
+ **Constructor:**
714
+ ```solidity
715
+ constructor()
716
+ ```
717
+ - No arguments
718
+
719
+ **Key Functions:**
720
+
721
+ | Function | Signature | Description | Access Control |
722
+ |----------|-----------|-------------|----------------|
723
+ | `execute` | `execute(address agentAccount, bytes calldata input) external payable returns (bytes memory output)` | Executes NFT buy call to target marketplace | Public (called by AgentAccount) |
724
+
725
+ **Events:**
726
+
727
+ | Event | Parameters |
728
+ |-------|------------|
729
+ | `NftBuyExecuted` | `address indexed agent, address indexed target, bytes4 indexed selector, uint256 value` |
730
+
731
+ **viem Example:**
732
+
733
+ ```typescript
734
+ // Deploy NftBuyModule
735
+ const nftBuyModuleAddress = await client.deployContract({
736
+ abi: NftBuyModule_ABI,
737
+ bytecode: NftBuyModule_BYTECODE,
738
+ args: []
739
+ })
740
+
741
+ // Execute via AgentAccount (see AgentAccount example above)
742
+ // Example: fulfill Seaport order
743
+ const seaportCalldata = encodeFunctionData({
744
+ abi: Seaport_ABI,
745
+ functionName: 'fulfillOrder',
746
+ args: [order, signature]
747
+ })
748
+
749
+ const input = encodeAbiParameters(
750
+ parseAbiParameters('address target, bytes calldata data'),
751
+ [seaportAddress, seaportCalldata]
752
+ )
753
+
754
+ await client.writeContract({
755
+ address: agentAccountAddress,
756
+ abi: AgentAccount_ABI,
757
+ functionName: 'executeSkill',
758
+ args: [
759
+ nftBuyModuleAddress,
760
+ input,
761
+ orderPrice,
762
+ traceIdHash,
763
+ subjectHash,
764
+ receiptUri
765
+ ],
766
+ value: orderPrice
767
+ })
768
+ ```
769
+
770
+ ---
771
+
772
+ ## System Flows
773
+
774
+ ### Deployment Flow
775
+
776
+ Contracts are deployed in the following order due to dependencies:
777
+
778
+ ```
779
+ 1. SkillNFT (no dependencies)
780
+
781
+ 2. SkillRegistry (depends on SkillNFT)
782
+
783
+ 3. IntentRegistry (no dependencies)
784
+
785
+ 4. ReceiptRegistry (no dependencies)
786
+
787
+ 5. PolicyEngine (no dependencies)
788
+
789
+ 6. AgentAccount (depends on PolicyEngine, ReceiptRegistry)
790
+
791
+ 7. Modules (SwapModule, BridgeModule, NftBuyModule - no dependencies)
792
+
793
+ 8. PodVault (no dependencies)
794
+ ```
795
+
796
+ **Deployment Script Example:**
797
+
798
+ ```typescript
799
+ // 1. Deploy SkillNFT
800
+ const skillNft = await deployContract('SkillNFT', [])
801
+
802
+ // 2. Deploy SkillRegistry
803
+ const skillRegistry = await deployContract('SkillRegistry', [skillNft.address])
804
+
805
+ // 3. Deploy IntentRegistry
806
+ const intentRegistry = await deployContract('IntentRegistry', [])
807
+
808
+ // 4. Deploy ReceiptRegistry
809
+ const receiptRegistry = await deployContract('ReceiptRegistry', [])
810
+
811
+ // 5. Deploy PolicyEngine
812
+ const policyEngine = await deployContract('PolicyEngine', [ownerAddress])
813
+
814
+ // 6. Deploy AgentAccount
815
+ const agentAccount = await deployContract('AgentAccount', [
816
+ ownerAddress,
817
+ policyEngine.address,
818
+ receiptRegistry.address
819
+ ])
820
+
821
+ // 7. Deploy Modules
822
+ const swapModule = await deployContract('SwapModule', [])
823
+ const bridgeModule = await deployContract('BridgeModule', [])
824
+ const nftBuyModule = await deployContract('NftBuyModule', [])
825
+
826
+ // 8. Deploy PodVault
827
+ const podVault = await deployContract('PodVault', [
828
+ [member1, member2, member3],
829
+ [4000n, 3000n, 3000n]
830
+ ])
831
+
832
+ // 9. Configure PolicyEngine
833
+ await policyEngine.write.setMaxValuePerTx([parseEther('1.0')])
834
+ await policyEngine.write.setModuleAllowed([swapModule.address, true])
835
+ await policyEngine.write.setModuleAllowed([bridgeModule.address, true])
836
+ await policyEngine.write.setModuleAllowed([nftBuyModule.address, true])
837
+ // ... configure targets and selectors
838
+ ```
839
+
840
+ ---
841
+
842
+ ### Execution Flow
843
+
844
+ The complete execution flow from user intent to onchain execution:
845
+
846
+ ```
847
+ User/Agent
848
+
849
+ 1. Creates Intent (IntentRegistry.createIntent)
850
+ - Emits IntentCreated event
851
+ - Offchain solvers observe intent
852
+
853
+ 2. Prepares Execution
854
+ - Encodes target + calldata
855
+ - Selects appropriate module
856
+ - Computes traceIdHash
857
+
858
+ 3. Calls AgentAccount.executeSkill
859
+ - Sends native value (if required)
860
+ - Passes module, input, traceIdHash
861
+
862
+ 4. AgentAccount.preCheck (PolicyEngine)
863
+ - Validates module is allowed
864
+ - Validates target is allowed
865
+ - Validates selector is allowed
866
+ - Validates value <= maxValuePerTx
867
+ - Reverts if any check fails
868
+
869
+ 5. Module.execute
870
+ - Decodes input (target, calldata)
871
+ - Calls target.call{value: msg.value}(calldata)
872
+ - Returns output bytes
873
+
874
+ 6. AgentAccount.postExecution
875
+ - Computes contentHash from (module, input, value, output)
876
+ - Attempts to record receipt (best-effort)
877
+ - Emits SkillExecuted event
878
+
879
+ 7. ReceiptRegistry.recordReceipt (if successful)
880
+ - Records traceIdHash → receipt mapping
881
+ - Emits ReceiptRecorded event
882
+ ```
883
+
884
+ **Example Execution:**
885
+
886
+ ```typescript
887
+ // 1. Create intent
888
+ const intentHash = keccak256(stringToHex(JSON.stringify(intentPayload)))
889
+ const intentId = await intentRegistry.write.createIntent([intentHash, 0n])
890
+
891
+ // 2. Prepare execution
892
+ const target = uniswapRouterAddress
893
+ const calldata = encodeFunctionData({
894
+ abi: UniswapRouter_ABI,
895
+ functionName: 'swapExactETHForTokens',
896
+ args: [minAmountOut, path, recipient, deadline]
897
+ })
898
+ const input = encodeAbiParameters(
899
+ parseAbiParameters('address target, bytes calldata data'),
900
+ [target, calldata]
901
+ )
902
+
903
+ // 3. Execute via AgentAccount
904
+ const traceIdHash = keccak256(stringToHex(traceId))
905
+ const subjectHash = keccak256(stringToHex(skillId))
906
+
907
+ const hash = await agentAccount.write.executeSkill({
908
+ args: [
909
+ swapModuleAddress,
910
+ input,
911
+ parseEther('0.1'),
912
+ traceIdHash,
913
+ subjectHash,
914
+ 'ipfs://Qm...'
915
+ ],
916
+ value: parseEther('0.1')
917
+ })
918
+
919
+ // 4. Wait for receipt
920
+ const receipt = await publicClient.waitForTransactionReceipt({ hash })
921
+ ```
922
+
923
+ ---
924
+
925
+ ### Revenue Flow
926
+
927
+ Revenue flows from SkillNFT royalties through PodVault to members:
928
+
929
+ ```
930
+ 1. Skill Execution
931
+ - User pays for skill execution (native tokens)
932
+ - SkillNFT may receive payment via EIP-2981 royalty
933
+
934
+ 2. SkillNFT Royalty (EIP-2981)
935
+ - NFT marketplace/executor calls royaltyInfo(skillId, salePrice)
936
+ - Returns: (receiver: PodVault, royaltyAmount)
937
+ - Marketplace sends royaltyAmount to PodVault
938
+
939
+ 3. PodVault Receives Payment
940
+ - Native tokens: via receive() or direct transfer
941
+ - ERC-20 tokens: via transfer() to PodVault address
942
+ - Emits PaymentReceived event
943
+
944
+ 4. Member Claims Payment
945
+ - Anyone can call releaseNative(member) or releaseToken(token, member)
946
+ - Calculates: (totalReceived * memberShares / totalShares) - alreadyReleased
947
+ - Transfers payment to member
948
+ - Updates releasedNative/releasedToken mappings
949
+ - Emits PaymentReleased event
950
+ ```
951
+
952
+ **Revenue Flow Example:**
953
+
954
+ ```typescript
955
+ // 1. SkillNFT minted with PodVault as royalty receiver (5%)
956
+ await skillNft.write.mintSkillWithRoyalty([
957
+ 0n, // parentId
958
+ podVaultAddress, // receiver
959
+ 500n // 5% = 500 bps
960
+ ])
961
+
962
+ // 2. NFT marketplace pays royalty to PodVault
963
+ const salePrice = parseEther('1.0')
964
+ const royaltyInfo = await skillNft.read.royaltyInfo([skillId, salePrice])
965
+ // Returns: [podVaultAddress, parseEther('0.05')]
966
+
967
+ // Marketplace sends royalty to PodVault
968
+ await marketplace.sendTransaction({
969
+ to: podVaultAddress,
970
+ value: parseEther('0.05')
971
+ })
972
+
973
+ // 3. Check pending payment for member
974
+ const pending = await podVault.read.pendingNative([memberAddress])
975
+ // Returns: (totalBalance * memberShares / totalShares) - released
976
+
977
+ // 4. Member claims payment
978
+ await podVault.write.releaseNative({
979
+ args: [memberAddress]
980
+ })
981
+
982
+ // 5. For ERC-20 tokens (e.g., APE token)
983
+ await podVault.write.releaseToken({
984
+ args: [apeTokenAddress, memberAddress]
985
+ })
986
+ ```
987
+
988
+ **Royalty Configuration:**
989
+
990
+ ```typescript
991
+ // Set royalty when minting
992
+ await skillNft.write.mintSkillWithRoyalty([
993
+ parentId,
994
+ podVaultAddress,
995
+ 500n // 5% = 500 basis points
996
+ ])
997
+
998
+ // Update royalty later (owner only)
999
+ await skillNft.write.setSkillRoyalty([
1000
+ skillId,
1001
+ podVaultAddress,
1002
+ 750n // 7.5% = 750 basis points
1003
+ ])
1004
+ ```
1005
+
1006
+ ---
1007
+
1008
+ ## Contract Addresses
1009
+
1010
+ After deployment, export these environment variables:
1011
+
1012
+ ```bash
1013
+ export APE_CLAW_V2_SKILL_NFT=<address>
1014
+ export APE_CLAW_V2_SKILL_REGISTRY=<address>
1015
+ export APE_CLAW_V2_INTENT_REGISTRY=<address>
1016
+ export APE_CLAW_V2_RECEIPT_REGISTRY=<address>
1017
+ export APE_CLAW_V2_POLICY_ENGINE=<address>
1018
+ export APE_CLAW_V2_AGENT_ACCOUNT=<address>
1019
+ export APE_CLAW_V2_POD_VAULT=<address>
1020
+ export APE_CLAW_V2_SWAP_MODULE=<address>
1021
+ export APE_CLAW_V2_BRIDGE_MODULE=<address>
1022
+ export APE_CLAW_V2_NFT_BUY_MODULE=<address>
1023
+ ```
1024
+
1025
+ Deployment addresses are saved to `state/v2-deployments/<network>.json`.
1026
+
1027
+ ---
1028
+
1029
+ ## Additional Resources
1030
+
1031
+ - [Architecture Overview](01-architecture.md) - System design and data flow
1032
+ - [Writing Modules](03-writing-modules.md) - Guide to creating custom ISkillModule implementations
1033
+ - [SkillCard Spec](04-skillcard-spec.md) - SkillCard JSON schema reference
1034
+ - [Testing Guide](07-testing.md) - Hardhat test examples