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.
- package/.cursor/skills/ape-claw/SKILL.md +322 -0
- package/LICENSE +21 -0
- package/README.md +826 -0
- package/allowlists/opensea-slug-overrides.json +13 -0
- package/allowlists/recommended.apechain.json +322 -0
- package/config/clawbots.example.json +3 -0
- package/config/policy.example.json +27 -0
- package/data/starter-pack-bundle.json +1 -0
- package/data/starter-pack.json +495 -0
- package/docs/ACP_BOUNTIES.md +108 -0
- package/docs/APECLAW_V2_ALPHA.md +206 -0
- package/docs/AUTONOMY_AND_SUBSTRATE.md +69 -0
- package/docs/CLAWBOTS_AND_INVITES.md +102 -0
- package/docs/CLI_GUIDE.md +124 -0
- package/docs/CONTRIBUTING.md +130 -0
- package/docs/DASHBOARD_GUIDE.md +108 -0
- package/docs/GLOBAL_BACKEND.md +145 -0
- package/docs/ONCHAIN_V2_GUIDE.md +140 -0
- package/docs/PRODUCT_OVERVIEW.md +127 -0
- package/docs/README.md +40 -0
- package/docs/SKILLCARDS_AND_IMPORTER.md +147 -0
- package/docs/STARTER_PACK.md +297 -0
- package/docs/SUPPORTED_NETWORKS.md +58 -0
- package/docs/TELEMETRY_AND_EVENTS.md +103 -0
- package/docs/THE_POD_RUNNER.md +198 -0
- package/docs/V1_WORKFLOWS.md +108 -0
- package/docs/V2_ONCHAIN_SKILLS.md +157 -0
- package/docs/WEB4_PLAN_STATUS.md +95 -0
- package/docs/WEB4_SWARM_MODEL.md +104 -0
- package/docs/archive/AUTONOMY_AND_SUBSTRATE.md +66 -0
- package/docs/archive/WEB4_PLAN_STATUS.md +93 -0
- package/docs/archive/WEB4_SWARM_MODEL.md +98 -0
- package/docs/developer/01-architecture.md +345 -0
- package/docs/developer/02-contracts.md +1034 -0
- package/docs/developer/03-writing-modules.md +513 -0
- package/docs/developer/04-skillcard-spec.md +336 -0
- package/docs/developer/05-backend-api.md +1079 -0
- package/docs/developer/06-telemetry.md +798 -0
- package/docs/developer/07-testing.md +546 -0
- package/docs/developer/08-contributing.md +211 -0
- package/docs/operator/01-quickstart.md +49 -0
- package/docs/operator/02-dashboard.md +174 -0
- package/docs/operator/03-cli-reference.md +818 -0
- package/docs/operator/04-skills-library.md +169 -0
- package/docs/operator/05-pod-operations.md +314 -0
- package/docs/operator/06-deployment.md +299 -0
- package/docs/operator/07-safety-and-policy.md +311 -0
- package/docs/operator/08-troubleshooting.md +457 -0
- package/docs/operator/09-env-reference.md +238 -0
- package/docs/social/STARTER_PACK_THREAD.md +209 -0
- package/package.json +77 -0
- package/skillcards/import-sources.json +93 -0
- package/skillcards/seed/acp-bounty-poll.v1.json +38 -0
- package/skillcards/seed/acp-bounty-post.v1.json +55 -0
- package/skillcards/seed/acp-browse.v1.json +41 -0
- package/skillcards/seed/acp-fulfill-and-route.v1.json +56 -0
- package/skillcards/seed/apeclaw-bridge-relay.v1.json +46 -0
- package/skillcards/seed/apeclaw-nft-autobuy.v1.json +60 -0
- package/skillcards/seed/apeclaw-receipt-recorder.v1.json +64 -0
- package/skillcards/seed/humanizer.v1.json +74 -0
- package/skillcards/seed/otherside-navigator.v1.json +116 -0
- package/skillcards/seed/stonkbrokers-launcher.v1.json +280 -0
- package/skillcards/seed/walkie-p2p.v1.json +66 -0
- package/src/cli/index.mjs +8 -0
- package/src/cli.mjs +1929 -0
- package/src/lib/bridge-relay.mjs +294 -0
- package/src/lib/clawbots.mjs +94 -0
- package/src/lib/io.mjs +36 -0
- package/src/lib/market.mjs +233 -0
- package/src/lib/nft-opensea.mjs +159 -0
- package/src/lib/paths.mjs +17 -0
- package/src/lib/pod-init.mjs +40 -0
- package/src/lib/policy.mjs +112 -0
- package/src/lib/rpc.mjs +49 -0
- package/src/lib/telemetry.mjs +92 -0
- package/src/lib/v2-onchain-abi.mjs +294 -0
- package/src/lib/v2-skillcard.mjs +27 -0
- package/src/server/index.mjs +169 -0
- package/src/server/logger.mjs +21 -0
- package/src/server/middleware/auth.mjs +90 -0
- package/src/server/middleware/body-limit.mjs +35 -0
- package/src/server/middleware/cors.mjs +33 -0
- package/src/server/middleware/rate-limit.mjs +44 -0
- package/src/server/routes/chat.mjs +178 -0
- package/src/server/routes/clawbots.mjs +182 -0
- package/src/server/routes/events.mjs +95 -0
- package/src/server/routes/health.mjs +72 -0
- package/src/server/routes/pod.mjs +64 -0
- package/src/server/routes/quotes.mjs +161 -0
- package/src/server/routes/skills.mjs +239 -0
- package/src/server/routes/static.mjs +161 -0
- package/src/server/routes/v2.mjs +48 -0
- package/src/server/sse.mjs +73 -0
- package/src/server/storage/file-backend.mjs +295 -0
- package/src/server/storage/index.mjs +37 -0
- package/src/server/storage/sqlite-backend.mjs +380 -0
- package/src/telemetry-server.mjs +1604 -0
- package/ui/css/dashboard.css +792 -0
- package/ui/css/skills.css +689 -0
- package/ui/docs.html +840 -0
- package/ui/favicon-180.png +0 -0
- package/ui/favicon-192.png +0 -0
- package/ui/favicon-32.png +0 -0
- package/ui/favicon-lobster.png +0 -0
- package/ui/favicon.svg +10 -0
- package/ui/index.html +2957 -0
- package/ui/js/dashboard.js +1766 -0
- package/ui/js/skills.js +1621 -0
- package/ui/pod.html +909 -0
- package/ui/shared/motion.css +286 -0
- package/ui/shared/motion.js +170 -0
- package/ui/shared/sidebar-nav.css +379 -0
- package/ui/shared/sidebar-nav.js +137 -0
- 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
|