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,513 @@
1
+ # Writing Modules
2
+
3
+ How to create a new `ISkillModule` for ApeClaw.
4
+
5
+ ## Interface
6
+
7
+ Every module must implement the `ISkillModule` interface:
8
+
9
+ ```solidity
10
+ // From contracts/ISkillModule.sol
11
+ interface ISkillModule {
12
+ function execute(address agentAccount, bytes calldata input) external payable returns (bytes memory output);
13
+ }
14
+ ```
15
+
16
+ The interface is intentionally minimal:
17
+ - **`agentAccount`**: The `AgentAccount` contract address calling the module
18
+ - **`input`**: Opaque bytes payload (typically `abi.encode(target, calldata)`)
19
+ - **`output`**: Opaque bytes return value
20
+ - **`payable`**: Allows the module to receive ETH
21
+
22
+ ## Step-by-Step Guide
23
+
24
+ ### 1. Create the Contract
25
+
26
+ Create a new Solidity file in `contracts/`:
27
+
28
+ ```solidity
29
+ // SPDX-License-Identifier: MIT
30
+ pragma solidity ^0.8.24;
31
+
32
+ import { ISkillModule } from "./ISkillModule.sol";
33
+
34
+ contract StakeModule is ISkillModule {
35
+ event StakeExecuted(address indexed agent, address indexed target, bytes4 indexed selector, uint256 value);
36
+
37
+ function execute(address agentAccount, bytes calldata input) external payable returns (bytes memory output) {
38
+ (address target, bytes memory data) = abi.decode(input, (address, bytes));
39
+ require(target != address(0), "target required");
40
+
41
+ bytes4 selector;
42
+ assembly {
43
+ selector := mload(add(data, 32))
44
+ }
45
+
46
+ (bool ok, bytes memory ret) = target.call{ value: msg.value }(data);
47
+ require(ok, "call failed");
48
+
49
+ emit StakeExecuted(agentAccount, target, selector, msg.value);
50
+ return ret;
51
+ }
52
+ }
53
+ ```
54
+
55
+ ### 2. Implement execute()
56
+
57
+ The standard pattern decodes `input` as `(address target, bytes calldata data)` and forwards the call:
58
+
59
+ ```solidity
60
+ function execute(address agentAccount, bytes calldata input) external payable returns (bytes memory output) {
61
+ // Decode input: (target address, calldata bytes)
62
+ (address target, bytes memory data) = abi.decode(input, (address, bytes));
63
+ require(target != address(0), "target required");
64
+
65
+ // Extract function selector (first 4 bytes of calldata)
66
+ bytes4 selector;
67
+ assembly {
68
+ selector := mload(add(data, 32))
69
+ }
70
+
71
+ // Forward call to target
72
+ (bool ok, bytes memory ret) = target.call{ value: msg.value }(data);
73
+ require(ok, "call failed");
74
+
75
+ // Emit event for indexing
76
+ emit StakeExecuted(agentAccount, target, selector, msg.value);
77
+ return ret;
78
+ }
79
+ ```
80
+
81
+ **Note**: The `AgentAccount` contract enforces policy checks **before** calling your module, so you can trust that:
82
+ - The module is allowlisted
83
+ - The target is allowlisted
84
+ - The selector is allowlisted
85
+ - The value is within `maxValuePerTx`
86
+
87
+ ### 3. Deploy the Module
88
+
89
+ Add deployment to `contracts-scripts/deploy-and-seed-v2-alpha.js`:
90
+
91
+ ```javascript
92
+ console.log("[v2] Deploying StakeModule...");
93
+ const stakeModule = await viem.deployContract("StakeModule");
94
+ console.log("[v2] StakeModule:", stakeModule.address);
95
+ ```
96
+
97
+ Or deploy manually:
98
+
99
+ ```bash
100
+ npx hardhat run scripts/deploy-stake-module.js --network apechain
101
+ ```
102
+
103
+ ### 4. Register with PolicyEngine
104
+
105
+ The module must be allowlisted before it can be executed:
106
+
107
+ ```javascript
108
+ // From deploy-and-seed-v2-alpha.js
109
+ await policy.write.setModuleAllowed([stakeModule.address, true]);
110
+ console.log("[v2] Allowlisted StakeModule");
111
+ ```
112
+
113
+ ### 5. Register Target and Selector
114
+
115
+ If your module calls a specific staking contract, allowlist it:
116
+
117
+ ```javascript
118
+ const stakingContract = "0x..."; // Your staking contract address
119
+ const stakeSelector = "0x..."; // Function selector (e.g., stake(uint256))
120
+
121
+ await policy.write.setTargetAllowed([stakingContract, true]);
122
+ await policy.write.setSelectorAllowed([stakingContract, stakeSelector, true]);
123
+ ```
124
+
125
+ ### 6. Test with AgentAccount
126
+
127
+ Execute via `AgentAccount.executeSkill()`:
128
+
129
+ ```javascript
130
+ // From src/cli.mjs (example)
131
+ const input = abi.encode(
132
+ stakingContract, // target
133
+ encodeFunctionData({ // calldata
134
+ abi: stakingAbi,
135
+ functionName: "stake",
136
+ args: [amount]
137
+ })
138
+ );
139
+
140
+ await agentAccount.write.executeSkill([
141
+ stakeModule.address, // module
142
+ input, // input
143
+ parseEther("0.1"), // value
144
+ traceIdHash, // traceIdHash
145
+ subjectHash, // subjectHash
146
+ "ipfs://..." // uri
147
+ ], { value: parseEther("0.1") });
148
+ ```
149
+
150
+ ## Example: Creating a StakeModule
151
+
152
+ Let's walk through creating a complete `StakeModule` that stakes tokens:
153
+
154
+ ### Contract Implementation
155
+
156
+ ```solidity
157
+ // SPDX-License-Identifier: MIT
158
+ pragma solidity ^0.8.24;
159
+
160
+ import { ISkillModule } from "./ISkillModule.sol";
161
+ import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
162
+
163
+ /// @notice Staking module for token staking contracts.
164
+ contract StakeModule is ISkillModule {
165
+ event StakeExecuted(
166
+ address indexed agent,
167
+ address indexed target,
168
+ address indexed token,
169
+ uint256 amount,
170
+ bytes4 selector
171
+ );
172
+
173
+ function execute(address agentAccount, bytes calldata input) external payable returns (bytes memory output) {
174
+ // Decode: (target staking contract, token address, amount, calldata)
175
+ (address target, address token, uint256 amount, bytes memory data) = abi.decode(
176
+ input,
177
+ (address, address, uint256, bytes)
178
+ );
179
+
180
+ require(target != address(0), "target required");
181
+ require(token != address(0), "token required");
182
+ require(amount > 0, "amount required");
183
+
184
+ bytes4 selector;
185
+ assembly {
186
+ selector := mload(add(data, 32))
187
+ }
188
+
189
+ // Approve token spending (if needed)
190
+ IERC20(token).approve(target, amount);
191
+
192
+ // Forward call to staking contract
193
+ (bool ok, bytes memory ret) = target.call(data);
194
+ require(ok, "stake call failed");
195
+
196
+ emit StakeExecuted(agentAccount, target, token, amount, selector);
197
+ return ret;
198
+ }
199
+ }
200
+ ```
201
+
202
+ ### Deployment Script
203
+
204
+ ```javascript
205
+ // scripts/deploy-stake-module.js
206
+ import hre from "hardhat";
207
+
208
+ async function main() {
209
+ const { viem } = await hre.network.connect();
210
+
211
+ console.log("Deploying StakeModule...");
212
+ const stakeModule = await viem.deployContract("StakeModule");
213
+ console.log("StakeModule deployed to:", stakeModule.address);
214
+
215
+ // Get PolicyEngine address from deployment record
216
+ const deployment = JSON.parse(
217
+ fs.readFileSync("state/v2-deployments/apechain.json", "utf8")
218
+ );
219
+ const policy = await viem.getContractAt("PolicyEngine", deployment.policy);
220
+
221
+ // Allowlist module
222
+ await policy.write.setModuleAllowed([stakeModule.address, true]);
223
+ console.log("StakeModule allowlisted");
224
+
225
+ // Allowlist staking contract and selector
226
+ const stakingContract = "0x..."; // Your staking contract
227
+ const stakeSelector = "0xa694fc3a"; // stake(uint256)
228
+
229
+ await policy.write.setTargetAllowed([stakingContract, true]);
230
+ await policy.write.setSelectorAllowed([stakingContract, stakeSelector, true]);
231
+ console.log("Staking contract allowlisted");
232
+ }
233
+
234
+ main().catch(console.error);
235
+ ```
236
+
237
+ ### CLI Integration
238
+
239
+ Add a CLI command to execute staking:
240
+
241
+ ```javascript
242
+ // In src/cli.mjs
243
+ if (group === "stake" && sub === "execute") {
244
+ const stakingContract = args.target;
245
+ const token = args.token;
246
+ const amount = parseUnits(args.amount, 18);
247
+
248
+ if (!stakingContract || !token || !amount) {
249
+ fail("Required: --target --token --amount", command, args);
250
+ }
251
+
252
+ const input = encodeAbiParameters(
253
+ [{ type: "address" }, { type: "address" }, { type: "uint256" }, { type: "bytes" }],
254
+ [
255
+ stakingContract,
256
+ token,
257
+ amount,
258
+ encodeFunctionData({
259
+ abi: stakingAbi,
260
+ functionName: "stake",
261
+ args: [amount]
262
+ })
263
+ ]
264
+ );
265
+
266
+ const txHash = await agentAccount.write.executeSkill([
267
+ stakeModuleAddress,
268
+ input,
269
+ 0n, // no ETH value
270
+ traceIdHash,
271
+ subjectHash,
272
+ uri
273
+ ]);
274
+
275
+ return print({ ok: true, txHash }, asJson);
276
+ }
277
+ ```
278
+
279
+ ## Registration
280
+
281
+ ### PolicyEngine Allowlists
282
+
283
+ The `PolicyEngine` maintains three allowlists:
284
+
285
+ 1. **Module Allowlist**: Which modules can be executed
286
+ 2. **Target Allowlist**: Which contracts modules can call
287
+ 3. **Selector Allowlist**: Which functions can be called on targets
288
+
289
+ ```solidity
290
+ // From PolicyEngine.sol
291
+ mapping(address => bool) public allowedModules;
292
+ mapping(address => bool) public allowedTargets;
293
+ mapping(address => mapping(bytes4 => bool)) public allowedSelectors;
294
+ ```
295
+
296
+ ### Registration Functions
297
+
298
+ ```solidity
299
+ // Allowlist a module
300
+ function setModuleAllowed(address module, bool allowed) external onlyOwner;
301
+
302
+ // Allowlist a target contract
303
+ function setTargetAllowed(address target, bool allowed) external onlyOwner;
304
+
305
+ // Allowlist a function selector on a target
306
+ function setSelectorAllowed(address target, bytes4 selector, bool allowed) external onlyOwner;
307
+ ```
308
+
309
+ ### Example Registration
310
+
311
+ From `deploy-and-seed-v2-alpha.js`:
312
+
313
+ ```javascript
314
+ // Set max value per transaction
315
+ await policy.write.setMaxValuePerTx([parseEther("1")]);
316
+
317
+ // Allowlist modules
318
+ await policy.write.setModuleAllowed([swapModule.address, true]);
319
+ await policy.write.setModuleAllowed([bridgeModule.address, true]);
320
+ await policy.write.setModuleAllowed([nftBuyModule.address, true]);
321
+
322
+ // Allowlist targets (example: mock target for testing)
323
+ const mockTarget = await viem.deployContract("MockTarget");
324
+ await policy.write.setTargetAllowed([mockTarget.address, true]);
325
+
326
+ // Allowlist selectors
327
+ const mockSelector = "0x12345678";
328
+ await policy.write.setSelectorAllowed([mockTarget.address, mockSelector, true]);
329
+ ```
330
+
331
+ ## Receipts
332
+
333
+ Receipts are automatically recorded by `AgentAccount` after module execution:
334
+
335
+ ```solidity
336
+ // From AgentAccount.sol
337
+ function executeSkill(...) external payable returns (bytes memory output) {
338
+ // ... policy check ...
339
+
340
+ // Execute module
341
+ output = ISkillModule(module).execute{ value: value }(address(this), input);
342
+
343
+ // Compute content hash
344
+ bytes32 outputHash = keccak256(output);
345
+ bytes32 contentHash = keccak256(abi.encodePacked(
346
+ module,
347
+ keccak256(input),
348
+ value,
349
+ outputHash
350
+ ));
351
+
352
+ // Record receipt (best-effort, doesn't fail execution)
353
+ bool receiptOk = false;
354
+ if (address(receipts) != address(0)) {
355
+ try receipts.recordReceipt(traceIdHash, contentHash, subjectHash, uri) {
356
+ receiptOk = true;
357
+ } catch {
358
+ receiptOk = false;
359
+ }
360
+ }
361
+
362
+ emit SkillExecuted(traceIdHash, contentHash, module, target, selector, value, receiptOk);
363
+ }
364
+ ```
365
+
366
+ ### Receipt Structure
367
+
368
+ ```solidity
369
+ // From ReceiptRegistry.sol
370
+ struct Receipt {
371
+ bytes32 traceIdHash; // Execution trace identifier
372
+ bytes32 contentHash; // Hash of (module, input, value, output)
373
+ bytes32 subject; // Agent/skill identifier
374
+ string uri; // Metadata URI
375
+ uint64 recordedAt; // Timestamp
376
+ address recorder; // AgentAccount address
377
+ }
378
+ ```
379
+
380
+ ### Querying Receipts
381
+
382
+ ```javascript
383
+ // Check if receipt exists
384
+ const isRecorded = await receipts.read.isRecorded([traceIdHash]);
385
+
386
+ // Get receipt details
387
+ const receipt = await receipts.read.getReceipt([traceIdHash]);
388
+ console.log({
389
+ traceIdHash: receipt.traceIdHash,
390
+ contentHash: receipt.contentHash,
391
+ subject: receipt.subject,
392
+ uri: receipt.uri,
393
+ recordedAt: receipt.recordedAt,
394
+ recorder: receipt.recorder
395
+ });
396
+ ```
397
+
398
+ ## Module Patterns
399
+
400
+ ### Pattern 1: Simple Forwarder
401
+
402
+ Most modules are simple forwarders (like `SwapModule`, `BridgeModule`, `NftBuyModule`):
403
+
404
+ ```solidity
405
+ function execute(address agentAccount, bytes calldata input) external payable returns (bytes memory output) {
406
+ (address target, bytes memory data) = abi.decode(input, (address, bytes));
407
+ require(target != address(0), "target required");
408
+
409
+ bytes4 selector;
410
+ assembly { selector := mload(add(data, 32)) }
411
+
412
+ (bool ok, bytes memory ret) = target.call{ value: msg.value }(data);
413
+ require(ok, "call failed");
414
+
415
+ emit ModuleExecuted(agentAccount, target, selector, msg.value);
416
+ return ret;
417
+ }
418
+ ```
419
+
420
+ ### Pattern 2: Token Approver
421
+
422
+ Modules that need to approve tokens before calling:
423
+
424
+ ```solidity
425
+ function execute(address agentAccount, bytes calldata input) external payable returns (bytes memory output) {
426
+ (address target, address token, uint256 amount, bytes memory data) = abi.decode(
427
+ input,
428
+ (address, address, uint256, bytes)
429
+ );
430
+
431
+ // Approve token spending
432
+ IERC20(token).approve(target, amount);
433
+
434
+ // Forward call
435
+ (bool ok, bytes memory ret) = target.call(data);
436
+ require(ok, "call failed");
437
+
438
+ return ret;
439
+ }
440
+ ```
441
+
442
+ ### Pattern 3: Multi-Step Operations
443
+
444
+ Modules that perform multiple operations:
445
+
446
+ ```solidity
447
+ function execute(address agentAccount, bytes calldata input) external payable returns (bytes memory output) {
448
+ (address[] memory targets, bytes[] memory calldatas) = abi.decode(
449
+ input,
450
+ (address[], bytes[])
451
+ );
452
+
453
+ require(targets.length == calldatas.length, "length mismatch");
454
+
455
+ bytes[] memory outputs = new bytes[](targets.length);
456
+
457
+ for (uint256 i = 0; i < targets.length; i++) {
458
+ (bool ok, bytes memory ret) = targets[i].call(calldatas[i]);
459
+ require(ok, "step failed");
460
+ outputs[i] = ret;
461
+ }
462
+
463
+ return abi.encode(outputs);
464
+ }
465
+ ```
466
+
467
+ ## Best Practices
468
+
469
+ 1. **Always validate inputs**: Check for zero addresses and invalid values
470
+ 2. **Emit events**: Include `agentAccount`, `target`, `selector`, and `value` for indexing
471
+ 3. **Handle failures gracefully**: Use `require()` with clear error messages
472
+ 4. **Keep it simple**: Modules should be thin wrappers; complex logic belongs in target contracts
473
+ 5. **Document the input format**: Specify the `abi.decode` structure in comments
474
+ 6. **Test thoroughly**: Write tests for both success and failure cases
475
+
476
+ ## Testing
477
+
478
+ Example Hardhat test:
479
+
480
+ ```javascript
481
+ import { expect } from "chai";
482
+ import hre from "hardhat";
483
+
484
+ describe("StakeModule", function() {
485
+ let stakeModule, agentAccount, stakingContract, token;
486
+
487
+ beforeEach(async function() {
488
+ const { viem } = await hre.network.connect();
489
+
490
+ stakeModule = await viem.deployContract("StakeModule");
491
+ // ... deploy other contracts ...
492
+ });
493
+
494
+ it("should stake tokens", async function() {
495
+ const amount = parseEther("100");
496
+ const input = encodeAbiParameters(
497
+ [{ type: "address" }, { type: "address" }, { type: "uint256" }, { type: "bytes" }],
498
+ [stakingContract.address, token.address, amount, calldata]
499
+ );
500
+
501
+ await expect(
502
+ agentAccount.write.executeSkill([
503
+ stakeModule.address,
504
+ input,
505
+ 0n,
506
+ traceIdHash,
507
+ subjectHash,
508
+ uri
509
+ ])
510
+ ).to.emit(stakeModule, "StakeExecuted");
511
+ });
512
+ });
513
+ ```