@swarmclawai/swarmclaw 0.7.7 → 0.8.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 (281) hide show
  1. package/README.md +12 -14
  2. package/next.config.ts +13 -2
  3. package/package.json +4 -2
  4. package/src/app/api/agents/[id]/thread/route.ts +9 -0
  5. package/src/app/api/agents/route.ts +4 -0
  6. package/src/app/api/agents/thread-route.test.ts +133 -0
  7. package/src/app/api/approvals/route.test.ts +148 -0
  8. package/src/app/api/canvas/[sessionId]/route.ts +3 -1
  9. package/src/app/api/chatrooms/[id]/chat/route.ts +4 -2
  10. package/src/app/api/chats/[id]/devserver/route.ts +48 -7
  11. package/src/app/api/chats/[id]/messages/route.ts +42 -18
  12. package/src/app/api/chats/[id]/route.ts +1 -1
  13. package/src/app/api/chats/[id]/stop/route.ts +5 -4
  14. package/src/app/api/chats/route.ts +23 -2
  15. package/src/app/api/clawhub/install/route.ts +28 -8
  16. package/src/app/api/connectors/[id]/route.ts +46 -3
  17. package/src/app/api/connectors/route.ts +12 -8
  18. package/src/app/api/external-agents/route.test.ts +165 -0
  19. package/src/app/api/gateways/[id]/health/route.ts +27 -12
  20. package/src/app/api/gateways/[id]/route.ts +2 -0
  21. package/src/app/api/gateways/health-route.test.ts +135 -0
  22. package/src/app/api/gateways/route.ts +2 -0
  23. package/src/app/api/mcp-servers/route.test.ts +130 -0
  24. package/src/app/api/openclaw/deploy/route.ts +38 -5
  25. package/src/app/api/plugins/install/route.ts +46 -6
  26. package/src/app/api/plugins/marketplace/route.ts +48 -15
  27. package/src/app/api/preview-server/route.ts +26 -11
  28. package/src/app/api/projects/[id]/route.ts +6 -2
  29. package/src/app/api/projects/route.ts +4 -3
  30. package/src/app/api/schedules/[id]/run/route.ts +4 -0
  31. package/src/app/api/schedules/route.test.ts +86 -0
  32. package/src/app/api/schedules/route.ts +6 -1
  33. package/src/app/api/secrets/[id]/route.ts +1 -0
  34. package/src/app/api/secrets/route.ts +2 -1
  35. package/src/app/api/settings/route.ts +2 -0
  36. package/src/app/api/setup/check-provider/route.test.ts +19 -0
  37. package/src/app/api/setup/check-provider/route.ts +40 -10
  38. package/src/app/api/skills/[id]/route.ts +12 -0
  39. package/src/app/api/skills/import/route.ts +14 -12
  40. package/src/app/api/skills/route.ts +13 -1
  41. package/src/app/api/tasks/[id]/route.ts +10 -1
  42. package/src/app/api/tasks/import/github/route.test.ts +65 -0
  43. package/src/app/api/tasks/import/github/route.ts +337 -0
  44. package/src/app/api/wallets/[id]/approve/route.ts +17 -3
  45. package/src/app/api/wallets/[id]/route.ts +79 -33
  46. package/src/app/api/wallets/[id]/send/route.ts +19 -33
  47. package/src/app/api/wallets/route.ts +78 -61
  48. package/src/app/api/webhooks/[id]/route.ts +33 -6
  49. package/src/app/api/webhooks/route.test.ts +272 -0
  50. package/src/cli/index.js +1 -0
  51. package/src/cli/spec.js +1 -0
  52. package/src/components/agents/agent-card.tsx +9 -2
  53. package/src/components/agents/agent-chat-list.tsx +18 -2
  54. package/src/components/agents/agent-list.tsx +1 -0
  55. package/src/components/agents/agent-sheet.tsx +257 -38
  56. package/src/components/agents/inspector-panel.tsx +41 -0
  57. package/src/components/canvas/canvas-panel.tsx +236 -65
  58. package/src/components/chat/chat-area.tsx +36 -19
  59. package/src/components/chat/chat-card.tsx +36 -13
  60. package/src/components/chat/chat-header.tsx +48 -16
  61. package/src/components/chat/chat-list.tsx +28 -4
  62. package/src/components/chat/checkpoint-timeline.tsx +50 -34
  63. package/src/components/chat/delegation-banner.test.ts +14 -1
  64. package/src/components/chat/delegation-banner.tsx +1 -1
  65. package/src/components/chat/message-bubble.tsx +208 -145
  66. package/src/components/chat/message-list.tsx +48 -19
  67. package/src/components/chatrooms/chatroom-message.tsx +2 -2
  68. package/src/components/chatrooms/chatroom-sheet.tsx +16 -2
  69. package/src/components/connectors/connector-health.tsx +1 -1
  70. package/src/components/connectors/connector-list.tsx +7 -2
  71. package/src/components/connectors/connector-sheet.tsx +337 -148
  72. package/src/components/gateways/gateway-sheet.tsx +2 -2
  73. package/src/components/layout/app-layout.tsx +40 -23
  74. package/src/components/mcp-servers/mcp-server-list.tsx +26 -5
  75. package/src/components/mcp-servers/mcp-server-sheet.tsx +19 -2
  76. package/src/components/openclaw/openclaw-deploy-panel.tsx +269 -21
  77. package/src/components/plugins/plugin-list.tsx +45 -9
  78. package/src/components/plugins/plugin-sheet.tsx +55 -7
  79. package/src/components/projects/project-detail.tsx +217 -0
  80. package/src/components/projects/project-sheet.tsx +176 -4
  81. package/src/components/providers/provider-list.tsx +2 -1
  82. package/src/components/providers/provider-sheet.tsx +21 -2
  83. package/src/components/schedules/schedule-card.tsx +25 -1
  84. package/src/components/schedules/schedule-sheet.tsx +44 -2
  85. package/src/components/secrets/secret-sheet.tsx +21 -2
  86. package/src/components/shared/agent-switch-dialog.tsx +12 -1
  87. package/src/components/shared/bottom-sheet.tsx +13 -3
  88. package/src/components/shared/command-palette.tsx +8 -1
  89. package/src/components/shared/confirm-dialog.tsx +19 -4
  90. package/src/components/shared/connector-platform-icon.test.ts +28 -0
  91. package/src/components/shared/connector-platform-icon.tsx +39 -6
  92. package/src/components/shared/settings/plugin-manager.tsx +29 -6
  93. package/src/components/shared/settings/section-capability-policy.tsx +45 -3
  94. package/src/components/shared/settings/section-voice.tsx +11 -3
  95. package/src/components/skills/skill-list.tsx +25 -0
  96. package/src/components/skills/skill-sheet.tsx +84 -12
  97. package/src/components/tasks/approvals-panel.tsx +289 -34
  98. package/src/components/tasks/task-board.tsx +410 -25
  99. package/src/components/tasks/task-card.tsx +66 -8
  100. package/src/components/tasks/task-sheet.tsx +16 -4
  101. package/src/components/ui/dialog.tsx +2 -2
  102. package/src/components/wallets/wallet-approval-dialog.tsx +4 -2
  103. package/src/components/wallets/wallet-panel.tsx +435 -90
  104. package/src/components/wallets/wallet-section.tsx +198 -48
  105. package/src/components/webhooks/webhook-sheet.tsx +22 -2
  106. package/src/lib/approval-display.ts +20 -0
  107. package/src/lib/canvas-content.ts +198 -0
  108. package/src/lib/chat-artifact-summary.ts +165 -0
  109. package/src/lib/chat-display.test.ts +91 -0
  110. package/src/lib/chat-display.ts +58 -0
  111. package/src/lib/chat-streaming-state.test.ts +47 -1
  112. package/src/lib/chat-streaming-state.ts +42 -0
  113. package/src/lib/ollama-model.ts +10 -0
  114. package/src/lib/openclaw-endpoint.test.ts +8 -0
  115. package/src/lib/openclaw-endpoint.ts +6 -1
  116. package/src/lib/plugin-install-cors.ts +46 -0
  117. package/src/lib/plugin-sources.test.ts +43 -0
  118. package/src/lib/plugin-sources.ts +77 -0
  119. package/src/lib/providers/ollama.ts +16 -6
  120. package/src/lib/providers/openclaw.test.ts +54 -0
  121. package/src/lib/providers/openclaw.ts +127 -11
  122. package/src/lib/schedule-dedupe-advanced.test.ts +1335 -0
  123. package/src/lib/schedule-dedupe.test.ts +66 -1
  124. package/src/lib/schedule-dedupe.ts +169 -12
  125. package/src/lib/schedule-origin.test.ts +20 -0
  126. package/src/lib/schedule-origin.ts +15 -0
  127. package/src/lib/server/__fixtures__/fake-mcp-stdio-server.mjs +27 -0
  128. package/src/lib/server/agent-availability.ts +16 -0
  129. package/src/lib/server/agent-runtime-config.ts +12 -4
  130. package/src/lib/server/agent-thread-session.test.ts +51 -0
  131. package/src/lib/server/agent-thread-session.ts +7 -0
  132. package/src/lib/server/approval-match.ts +205 -0
  133. package/src/lib/server/approvals-auto-approve.test.ts +538 -1
  134. package/src/lib/server/approvals.ts +214 -1
  135. package/src/lib/server/assistant-control.test.ts +29 -0
  136. package/src/lib/server/assistant-control.ts +23 -0
  137. package/src/lib/server/build-llm.test.ts +79 -0
  138. package/src/lib/server/build-llm.ts +14 -4
  139. package/src/lib/server/canvas-content.test.ts +32 -0
  140. package/src/lib/server/canvas-content.ts +6 -0
  141. package/src/lib/server/capability-router.test.ts +33 -0
  142. package/src/lib/server/capability-router.ts +80 -19
  143. package/src/lib/server/chat-execution-advanced.test.ts +651 -0
  144. package/src/lib/server/chat-execution-disabled.test.ts +94 -0
  145. package/src/lib/server/chat-execution-tool-events.test.ts +157 -0
  146. package/src/lib/server/chat-execution.ts +378 -73
  147. package/src/lib/server/clawhub-client.test.ts +14 -8
  148. package/src/lib/server/connectors/manager-reconnect.test.ts +47 -0
  149. package/src/lib/server/connectors/manager.test.ts +1147 -0
  150. package/src/lib/server/connectors/manager.ts +461 -137
  151. package/src/lib/server/connectors/pairing.ts +26 -5
  152. package/src/lib/server/connectors/types.ts +2 -0
  153. package/src/lib/server/connectors/whatsapp.test.ts +134 -0
  154. package/src/lib/server/connectors/whatsapp.ts +271 -47
  155. package/src/lib/server/context-manager.ts +6 -1
  156. package/src/lib/server/daemon-state.ts +84 -47
  157. package/src/lib/server/data-dir.test.ts +37 -0
  158. package/src/lib/server/data-dir.ts +20 -1
  159. package/src/lib/server/delegation-jobs-advanced.test.ts +513 -0
  160. package/src/lib/server/devserver-launch.test.ts +60 -0
  161. package/src/lib/server/devserver-launch.ts +85 -0
  162. package/src/lib/server/elevenlabs.test.ts +247 -1
  163. package/src/lib/server/elevenlabs.ts +147 -43
  164. package/src/lib/server/ethereum.ts +590 -0
  165. package/src/lib/server/eval/agent-regression-advanced.test.ts +302 -0
  166. package/src/lib/server/eval/agent-regression.test.ts +18 -1
  167. package/src/lib/server/eval/agent-regression.ts +383 -11
  168. package/src/lib/server/evm-swap.ts +475 -0
  169. package/src/lib/server/execution-log.ts +1 -0
  170. package/src/lib/server/heartbeat-service-timer.test.ts +173 -0
  171. package/src/lib/server/heartbeat-service.ts +20 -11
  172. package/src/lib/server/heartbeat-wake.test.ts +112 -0
  173. package/src/lib/server/heartbeat-wake.ts +338 -57
  174. package/src/lib/server/main-agent-loop-advanced.test.ts +538 -0
  175. package/src/lib/server/main-agent-loop.test.ts +260 -0
  176. package/src/lib/server/main-agent-loop.ts +559 -14
  177. package/src/lib/server/mcp-client.test.ts +16 -0
  178. package/src/lib/server/mcp-client.ts +25 -0
  179. package/src/lib/server/memory-integration.test.ts +719 -0
  180. package/src/lib/server/memory-policy.test.ts +43 -0
  181. package/src/lib/server/memory-policy.ts +132 -0
  182. package/src/lib/server/memory-tiers.test.ts +60 -0
  183. package/src/lib/server/memory-tiers.ts +16 -0
  184. package/src/lib/server/ollama-runtime.ts +58 -0
  185. package/src/lib/server/openclaw-deploy.test.ts +109 -1
  186. package/src/lib/server/openclaw-deploy.ts +557 -81
  187. package/src/lib/server/openclaw-gateway.test.ts +131 -0
  188. package/src/lib/server/openclaw-gateway.ts +10 -4
  189. package/src/lib/server/openclaw-health.test.ts +35 -0
  190. package/src/lib/server/openclaw-health.ts +215 -47
  191. package/src/lib/server/orchestrator-lg.ts +3 -2
  192. package/src/lib/server/orchestrator.ts +2 -0
  193. package/src/lib/server/plugins-advanced.test.ts +351 -0
  194. package/src/lib/server/plugins.ts +211 -6
  195. package/src/lib/server/project-context.ts +162 -0
  196. package/src/lib/server/project-utils.ts +150 -0
  197. package/src/lib/server/queue-advanced.test.ts +528 -0
  198. package/src/lib/server/queue-followups.test.ts +409 -2
  199. package/src/lib/server/queue-reconcile.test.ts +128 -0
  200. package/src/lib/server/queue.ts +527 -68
  201. package/src/lib/server/scheduler.ts +29 -1
  202. package/src/lib/server/session-note.test.ts +36 -0
  203. package/src/lib/server/session-note.ts +42 -0
  204. package/src/lib/server/session-run-manager.ts +83 -4
  205. package/src/lib/server/session-tools/canvas.ts +14 -12
  206. package/src/lib/server/session-tools/connector-inputs.test.ts +37 -0
  207. package/src/lib/server/session-tools/connector.test.ts +138 -0
  208. package/src/lib/server/session-tools/connector.ts +366 -54
  209. package/src/lib/server/session-tools/context.ts +17 -3
  210. package/src/lib/server/session-tools/crud.ts +484 -84
  211. package/src/lib/server/session-tools/delegate-fallback.test.ts +103 -0
  212. package/src/lib/server/session-tools/delegate-resume.test.ts +50 -0
  213. package/src/lib/server/session-tools/delegate.ts +102 -10
  214. package/src/lib/server/session-tools/discovery-approvals.test.ts +142 -0
  215. package/src/lib/server/session-tools/discovery.ts +80 -12
  216. package/src/lib/server/session-tools/file-normalize.test.ts +36 -0
  217. package/src/lib/server/session-tools/file.ts +43 -4
  218. package/src/lib/server/session-tools/human-loop.ts +35 -5
  219. package/src/lib/server/session-tools/index.ts +44 -9
  220. package/src/lib/server/session-tools/manage-connectors.test.ts +139 -0
  221. package/src/lib/server/session-tools/manage-schedules-advanced.test.ts +564 -0
  222. package/src/lib/server/session-tools/manage-schedules.test.ts +283 -0
  223. package/src/lib/server/session-tools/manage-tasks-advanced.test.ts +852 -0
  224. package/src/lib/server/session-tools/manage-tasks.test.ts +114 -0
  225. package/src/lib/server/session-tools/memory.test.ts +93 -0
  226. package/src/lib/server/session-tools/memory.ts +554 -75
  227. package/src/lib/server/session-tools/normalize-tool-args.ts +1 -1
  228. package/src/lib/server/session-tools/platform-access.test.ts +58 -0
  229. package/src/lib/server/session-tools/platform.ts +60 -19
  230. package/src/lib/server/session-tools/plugin-creator.ts +57 -1
  231. package/src/lib/server/session-tools/primitive-tools.test.ts +6 -0
  232. package/src/lib/server/session-tools/schedule.ts +6 -1
  233. package/src/lib/server/session-tools/shell-normalize.test.ts +25 -1
  234. package/src/lib/server/session-tools/shell.ts +22 -3
  235. package/src/lib/server/session-tools/wallet-tool.test.ts +254 -0
  236. package/src/lib/server/session-tools/wallet.ts +1374 -139
  237. package/src/lib/server/session-tools/web-inputs.test.ts +178 -0
  238. package/src/lib/server/session-tools/web.ts +621 -70
  239. package/src/lib/server/skill-discovery.ts +128 -0
  240. package/src/lib/server/skill-eligibility.test.ts +84 -0
  241. package/src/lib/server/skill-eligibility.ts +95 -0
  242. package/src/lib/server/skill-prompt-budget.test.ts +102 -0
  243. package/src/lib/server/skill-prompt-budget.ts +125 -0
  244. package/src/lib/server/skills-normalize.test.ts +54 -0
  245. package/src/lib/server/skills-normalize.ts +372 -26
  246. package/src/lib/server/solana.ts +214 -29
  247. package/src/lib/server/storage.ts +65 -36
  248. package/src/lib/server/stream-agent-chat.test.ts +437 -2
  249. package/src/lib/server/stream-agent-chat.ts +957 -79
  250. package/src/lib/server/system-events.ts +1 -1
  251. package/src/lib/server/tool-aliases.ts +2 -0
  252. package/src/lib/server/tool-capability-policy-advanced.test.ts +502 -0
  253. package/src/lib/server/tool-capability-policy.test.ts +24 -0
  254. package/src/lib/server/tool-capability-policy.ts +29 -1
  255. package/src/lib/server/tool-loop-detection.test.ts +105 -0
  256. package/src/lib/server/tool-loop-detection.ts +260 -0
  257. package/src/lib/server/tool-planning.test.ts +44 -0
  258. package/src/lib/server/tool-planning.ts +271 -0
  259. package/src/lib/server/wallet-execution.test.ts +198 -0
  260. package/src/lib/server/wallet-portfolio.test.ts +98 -0
  261. package/src/lib/server/wallet-portfolio.ts +724 -0
  262. package/src/lib/server/wallet-service.test.ts +57 -0
  263. package/src/lib/server/wallet-service.ts +213 -0
  264. package/src/lib/server/watch-jobs-advanced.test.ts +594 -0
  265. package/src/lib/server/watch-jobs.ts +17 -2
  266. package/src/lib/server/workspace-context.ts +111 -0
  267. package/src/lib/skill-save-payload.test.ts +39 -0
  268. package/src/lib/skill-save-payload.ts +37 -0
  269. package/src/lib/tasks.ts +28 -0
  270. package/src/lib/tool-definitions.ts +2 -1
  271. package/src/lib/tool-event-summary.test.ts +30 -0
  272. package/src/lib/tool-event-summary.ts +37 -0
  273. package/src/lib/validation/schemas.ts +1 -0
  274. package/src/lib/wallet-transactions.test.ts +75 -0
  275. package/src/lib/wallet-transactions.ts +43 -0
  276. package/src/lib/wallet.test.ts +17 -0
  277. package/src/lib/wallet.ts +183 -0
  278. package/src/proxy.test.ts +31 -0
  279. package/src/proxy.ts +34 -2
  280. package/src/stores/use-chat-store.ts +15 -1
  281. package/src/types/index.ts +249 -14
@@ -0,0 +1,198 @@
1
+ import assert from 'node:assert/strict'
2
+ import fs from 'node:fs'
3
+ import os from 'node:os'
4
+ import path from 'node:path'
5
+ import { after, before, describe, it } from 'node:test'
6
+
7
+ import type { AgentWallet } from '@/types'
8
+
9
+ const originalEnv = {
10
+ DATA_DIR: process.env.DATA_DIR,
11
+ WORKSPACE_DIR: process.env.WORKSPACE_DIR,
12
+ SWARMCLAW_BUILD_MODE: process.env.SWARMCLAW_BUILD_MODE,
13
+ CREDENTIAL_SECRET: process.env.CREDENTIAL_SECRET,
14
+ }
15
+
16
+ let tempDir = ''
17
+ let encryptKey: typeof import('./storage').encryptKey
18
+ let callEthereumContract: typeof import('./ethereum').callEthereumContract
19
+ let encodeEthereumContractCall: typeof import('./ethereum').encodeEthereumContractCall
20
+ let prepareEvmSwapPlan: typeof import('./evm-swap').prepareEvmSwapPlan
21
+ let signEthereumMessage: typeof import('./ethereum').signEthereumMessage
22
+ let signEthereumTypedData: typeof import('./ethereum').signEthereumTypedData
23
+ let generateSolanaKeypair: typeof import('./solana').generateSolanaKeypair
24
+ let signSolanaMessage: typeof import('./solana').signSolanaMessage
25
+ let signSolanaTransaction: typeof import('./solana').signSolanaTransaction
26
+ let TransactionCtor: typeof import('@solana/web3.js').Transaction
27
+ let SystemProgramNs: typeof import('@solana/web3.js').SystemProgram
28
+ let PublicKeyCtor: typeof import('@solana/web3.js').PublicKey
29
+
30
+ before(async () => {
31
+ tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'swarmclaw-wallet-exec-'))
32
+ process.env.DATA_DIR = path.join(tempDir, 'data')
33
+ process.env.WORKSPACE_DIR = path.join(tempDir, 'workspace')
34
+ process.env.SWARMCLAW_BUILD_MODE = '1'
35
+ process.env.CREDENTIAL_SECRET = '11'.repeat(32)
36
+ fs.mkdirSync(process.env.DATA_DIR, { recursive: true })
37
+ fs.mkdirSync(process.env.WORKSPACE_DIR, { recursive: true })
38
+
39
+ ;({ encryptKey } = await import('./storage'))
40
+ ;({ callEthereumContract, encodeEthereumContractCall, signEthereumMessage, signEthereumTypedData } = await import('./ethereum'))
41
+ ;({ prepareEvmSwapPlan } = await import('./evm-swap'))
42
+ ;({ generateSolanaKeypair, signSolanaMessage, signSolanaTransaction } = await import('./solana'))
43
+ ;({ Transaction: TransactionCtor, SystemProgram: SystemProgramNs, PublicKey: PublicKeyCtor } = await import('@solana/web3.js'))
44
+ })
45
+
46
+ after(() => {
47
+ if (originalEnv.DATA_DIR === undefined) delete process.env.DATA_DIR
48
+ else process.env.DATA_DIR = originalEnv.DATA_DIR
49
+ if (originalEnv.WORKSPACE_DIR === undefined) delete process.env.WORKSPACE_DIR
50
+ else process.env.WORKSPACE_DIR = originalEnv.WORKSPACE_DIR
51
+ if (originalEnv.SWARMCLAW_BUILD_MODE === undefined) delete process.env.SWARMCLAW_BUILD_MODE
52
+ else process.env.SWARMCLAW_BUILD_MODE = originalEnv.SWARMCLAW_BUILD_MODE
53
+ if (originalEnv.CREDENTIAL_SECRET === undefined) delete process.env.CREDENTIAL_SECRET
54
+ else process.env.CREDENTIAL_SECRET = originalEnv.CREDENTIAL_SECRET
55
+ fs.rmSync(tempDir, { recursive: true, force: true })
56
+ })
57
+
58
+ describe('wallet execution helpers', () => {
59
+ it('encodes ERC-20 contract calldata and signs EVM payloads', async () => {
60
+ const privateKey = '0x59c6995e998f97a5a004497e5d4ab3d89165b0def05d6d33923995df83329538'
61
+ const encrypted = encryptKey(privateKey)
62
+
63
+ const encoded = encodeEthereumContractCall(
64
+ ['function approve(address spender,uint256 amount)'],
65
+ 'approve',
66
+ ['0x000000000000000000000000000000000000dEaD', '1000'],
67
+ )
68
+ assert.equal(encoded.data.startsWith('0x095ea7b3'), true)
69
+
70
+ const encodedFromNamedArgs = encodeEthereumContractCall(
71
+ ['function approve(address spender,uint256 amount)'],
72
+ 'approve',
73
+ { spender: '0x000000000000000000000000000000000000dEaD', amount: '1000' },
74
+ )
75
+ assert.equal(encodedFromNamedArgs.data, encoded.data)
76
+
77
+ const encodedTupleArg = encodeEthereumContractCall(
78
+ ['function quoteExactInputSingle((address tokenIn,address tokenOut,uint256 amountIn,uint24 fee,uint160 sqrtPriceLimitX96) params) returns (uint256 amountOut)'],
79
+ 'quoteExactInputSingle',
80
+ {
81
+ tokenIn: '0x0000000000000000000000000000000000000001',
82
+ tokenOut: '0x0000000000000000000000000000000000000002',
83
+ amountIn: '1000000',
84
+ fee: 500,
85
+ sqrtPriceLimitX96: '0',
86
+ },
87
+ )
88
+ assert.equal(encodedTupleArg.data.startsWith('0xc6a5026a'), true)
89
+
90
+ const signedMessage = await signEthereumMessage(encrypted, { message: 'hello world' })
91
+ assert.equal(signedMessage.address.length, 42)
92
+ assert.equal(signedMessage.signature.startsWith('0x'), true)
93
+
94
+ const signedTypedData = await signEthereumTypedData(encrypted, {
95
+ domain: {
96
+ name: 'SwarmClaw',
97
+ version: '1',
98
+ chainId: 1,
99
+ },
100
+ types: {
101
+ Login: [
102
+ { name: 'wallet', type: 'address' },
103
+ { name: 'nonce', type: 'uint256' },
104
+ ],
105
+ },
106
+ value: {
107
+ wallet: signedMessage.address,
108
+ nonce: '7',
109
+ },
110
+ })
111
+ assert.equal(signedTypedData.signature.startsWith('0x'), true)
112
+
113
+ const called = await callEthereumContract(encrypted, {
114
+ contractAddress: '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2',
115
+ abi: ['function name() view returns (string)'],
116
+ functionName: 'name',
117
+ }, {
118
+ network: 'ethereum',
119
+ rpcUrl: 'https://ethereum-rpc.publicnode.com',
120
+ })
121
+ assert.equal(called.decoded, 'Wrapped Ether')
122
+
123
+ const allowance = await callEthereumContract(encrypted, {
124
+ contractAddress: '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2',
125
+ abi: ['function allowance(address owner,address spender) view returns (uint256)'],
126
+ functionName: 'allowance',
127
+ args: {
128
+ owner: signedMessage.address,
129
+ spender: '0x000000000000000000000000000000000000dEaD',
130
+ },
131
+ }, {
132
+ network: 'ethereum',
133
+ rpcUrl: 'https://ethereum-rpc.publicnode.com',
134
+ })
135
+ assert.equal(typeof allowance.decoded, 'string')
136
+ })
137
+
138
+ it('builds a generic ParaSwap-backed swap plan for Arbitrum without a venue-specific adapter', async () => {
139
+ const privateKey = '0x59c6995e998f97a5a004497e5d4ab3d89165b0def05d6d33923995df83329538'
140
+ const encrypted = encryptKey(privateKey)
141
+ const walletAddress = (await signEthereumMessage(encrypted, { message: 'derive address' })).address
142
+ const wallet: AgentWallet = {
143
+ id: 'wallet_swap_plan',
144
+ agentId: 'agent_wallet',
145
+ chain: 'ethereum',
146
+ publicKey: walletAddress,
147
+ encryptedPrivateKey: encrypted,
148
+ spendingLimitAtomic: '1000000000000000000',
149
+ dailyLimitAtomic: '10000000000000000000',
150
+ requireApproval: true,
151
+ createdAt: Date.now(),
152
+ updatedAt: Date.now(),
153
+ }
154
+
155
+ const plan = await prepareEvmSwapPlan({
156
+ wallet,
157
+ network: 'arbitrum',
158
+ sellToken: 'USDC',
159
+ buyToken: 'ETH',
160
+ sellAmountDisplay: '1',
161
+ skipBalanceCheck: true,
162
+ })
163
+
164
+ assert.equal(plan.provider, 'paraswap')
165
+ assert.equal(plan.network.id, 'arbitrum')
166
+ assert.equal(plan.sellToken.symbol, 'USDC')
167
+ assert.equal(plan.buyToken.symbol, 'ETH')
168
+ assert.equal(plan.sellAmountAtomic, '1000000')
169
+ assert.equal(plan.approvalRequired, true)
170
+ assert.equal(typeof plan.spenderAddress, 'string')
171
+ assert.equal(typeof plan.swapTransaction.to, 'string')
172
+ assert.equal(String(plan.swapTransaction.data || '').startsWith('0x'), true)
173
+ })
174
+
175
+ it('signs Solana messages and legacy transactions offline', async () => {
176
+ const sender = generateSolanaKeypair()
177
+ const recipient = generateSolanaKeypair()
178
+
179
+ const signedMessage = await signSolanaMessage(sender.encryptedPrivateKey, { message: 'solana hello' })
180
+ assert.equal(signedMessage.publicKey, sender.publicKey)
181
+ assert.equal(signedMessage.signature.length > 40, true)
182
+
183
+ const tx = new TransactionCtor()
184
+ tx.feePayer = new PublicKeyCtor(sender.publicKey)
185
+ tx.recentBlockhash = generateSolanaKeypair().publicKey
186
+ tx.add(SystemProgramNs.transfer({
187
+ fromPubkey: new PublicKeyCtor(sender.publicKey),
188
+ toPubkey: new PublicKeyCtor(recipient.publicKey),
189
+ lamports: 1_234,
190
+ }))
191
+
192
+ const unsignedBase64 = Buffer.from(tx.serialize({ requireAllSignatures: false, verifySignatures: false })).toString('base64')
193
+ const signedTx = await signSolanaTransaction(sender.encryptedPrivateKey, unsignedBase64)
194
+ assert.equal(signedTx.publicKey, sender.publicKey)
195
+ assert.equal(signedTx.signatures.length > 0, true)
196
+ assert.equal(typeof signedTx.signedTransactionBase64, 'string')
197
+ })
198
+ })
@@ -0,0 +1,98 @@
1
+ import assert from 'node:assert/strict'
2
+ import { describe, it } from 'node:test'
3
+ import type { AgentWallet } from '@/types'
4
+
5
+ import {
6
+ buildLogDiscoveryRanges,
7
+ estimateDiscoveryStartBlock,
8
+ getKnownEvmTokenContracts,
9
+ parseMetaplexMetadataFields,
10
+ buildEmptyWalletPortfolio,
11
+ resolveWalletPortfolioWithTimeout,
12
+ } from './wallet-portfolio'
13
+
14
+ describe('wallet portfolio helpers', () => {
15
+ it('splits large log discovery requests into provider-safe chunks', () => {
16
+ assert.deepEqual(buildLogDiscoveryRanges(10, 10, 50_000), [{ fromBlock: 10, toBlock: 10 }])
17
+ assert.deepEqual(buildLogDiscoveryRanges(1, 120_000, 50_000), [
18
+ { fromBlock: 1, toBlock: 50_000 },
19
+ { fromBlock: 50_001, toBlock: 100_000 },
20
+ { fromBlock: 100_001, toBlock: 120_000 },
21
+ ])
22
+ })
23
+
24
+ it('always checks canonical USDC contracts on supported EVM networks', () => {
25
+ assert.equal(
26
+ getKnownEvmTokenContracts('arbitrum').map((address) => address.toLowerCase()).includes('0xaf88d065e77c8cc2239327c5edb3a432268e5831'),
27
+ true,
28
+ )
29
+ assert.equal(
30
+ getKnownEvmTokenContracts('base').map((address) => address.toLowerCase()).includes('0x833589fcd6edb6e08f4c7c32d4f71b54bda02913'),
31
+ true,
32
+ )
33
+ assert.equal(
34
+ getKnownEvmTokenContracts('ethereum').map((address) => address.toLowerCase()).includes('0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48'),
35
+ true,
36
+ )
37
+ })
38
+
39
+ it('scans from wallet age rather than capping discovery to a fixed recent window', () => {
40
+ const now = Date.UTC(2026, 2, 8)
41
+ const latestBlock = 10_000_000
42
+ const newerWalletStart = estimateDiscoveryStartBlock({
43
+ latestBlock,
44
+ walletCreatedAt: now - (7 * 24 * 60 * 60 * 1000),
45
+ avgBlockMs: 12_000,
46
+ maxDiscoveryBlocks: 5_000_000,
47
+ now,
48
+ })
49
+ const olderWalletStart = estimateDiscoveryStartBlock({
50
+ latestBlock,
51
+ walletCreatedAt: now - (30 * 24 * 60 * 60 * 1000),
52
+ avgBlockMs: 12_000,
53
+ maxDiscoveryBlocks: 5_000_000,
54
+ now,
55
+ })
56
+
57
+ assert.equal(olderWalletStart < newerWalletStart, true)
58
+ })
59
+
60
+ it('parses metaplex metadata name and symbol for arbitrary SPL mints', () => {
61
+ const data = Buffer.alloc(1 + 32 + 32 + 32 + 10)
62
+ Buffer.from('Example Token').copy(data, 1 + 32 + 32)
63
+ Buffer.from('EXMPL').copy(data, 1 + 32 + 32 + 32)
64
+
65
+ assert.deepEqual(parseMetaplexMetadataFields(data), {
66
+ name: 'Example Token',
67
+ symbol: 'EXMPL',
68
+ })
69
+ })
70
+
71
+ it('returns stale portfolio data when a live portfolio lookup times out', async () => {
72
+ const wallet: AgentWallet = {
73
+ id: 'wallet-timeout',
74
+ agentId: 'agent-timeout',
75
+ chain: 'ethereum',
76
+ publicKey: '0x0000000000000000000000000000000000000001',
77
+ encryptedPrivateKey: 'secret',
78
+ requireApproval: true,
79
+ spendingLimitAtomic: '1',
80
+ dailyLimitAtomic: '1',
81
+ createdAt: 1,
82
+ updatedAt: 1,
83
+ }
84
+ const stale = buildEmptyWalletPortfolio(wallet)
85
+ stale.balanceAtomic = '123'
86
+ stale.balanceFormatted = '0.000000000000000123'
87
+ stale.balanceDisplay = `${stale.balanceFormatted} ETH`
88
+
89
+ const result = await resolveWalletPortfolioWithTimeout({
90
+ load: () => new Promise<ReturnType<typeof buildEmptyWalletPortfolio>>(() => {}),
91
+ timeoutMs: 5,
92
+ stale,
93
+ label: 'wallet portfolio timeout test',
94
+ })
95
+
96
+ assert.equal(result.balanceAtomic, '123')
97
+ })
98
+ })