@swarmclawai/swarmclaw 1.2.6 → 1.2.9

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 (269) hide show
  1. package/README.md +54 -23
  2. package/next.config.ts +1 -0
  3. package/package.json +4 -3
  4. package/scripts/easy-setup.mjs +1 -1
  5. package/scripts/postinstall.mjs +1 -1
  6. package/skills/swarmclaw.md +115 -0
  7. package/skills/tools/browser.md +131 -0
  8. package/skills/tools/execute.md +98 -0
  9. package/skills/tools/files.md +98 -0
  10. package/skills/tools/memory.md +104 -0
  11. package/skills/tools/platform.md +144 -0
  12. package/skills/tools/skills.md +83 -0
  13. package/src/app/agents/[id]/page.tsx +1 -18
  14. package/src/app/api/agents/thread-route.test.ts +0 -1
  15. package/src/app/api/approvals/route.test.ts +6 -22
  16. package/src/app/api/chats/[id]/messages/route.ts +23 -19
  17. package/src/app/api/chats/messages-route.test.ts +105 -51
  18. package/src/app/api/connectors/route.ts +2 -2
  19. package/src/app/api/mcp-servers/[id]/test/route.ts +3 -2
  20. package/src/app/api/openclaw/deploy/route.ts +2 -0
  21. package/src/app/api/portability/export/route.ts +8 -0
  22. package/src/app/api/portability/import/route.test.ts +80 -0
  23. package/src/app/api/portability/import/route.ts +28 -0
  24. package/src/app/api/settings/route.ts +0 -2
  25. package/src/app/api/setup/doctor/route.ts +4 -4
  26. package/src/app/api/wallets/[id]/route.ts +15 -157
  27. package/src/app/api/wallets/generate/route.ts +22 -0
  28. package/src/app/api/wallets/route.test.ts +147 -0
  29. package/src/app/api/wallets/route.ts +13 -95
  30. package/src/app/autonomy/page.tsx +2 -57
  31. package/src/app/protocols/page.tsx +2 -21
  32. package/src/app/settings/page.tsx +0 -9
  33. package/src/app/wallets/page.tsx +105 -5
  34. package/src/cli/index.js +21 -33
  35. package/src/cli/spec.js +19 -30
  36. package/src/components/agents/agent-chat-list.tsx +23 -1
  37. package/src/components/agents/agent-sheet.tsx +2 -40
  38. package/src/components/agents/inspector-panel.tsx +165 -131
  39. package/src/components/chat/chat-area.tsx +38 -9
  40. package/src/components/chat/chat-card.tsx +0 -31
  41. package/src/components/chat/message-bubble.tsx +1 -108
  42. package/src/components/chat/message-list.tsx +33 -19
  43. package/src/components/connectors/connector-sheet.tsx +25 -1
  44. package/src/components/gateways/gateway-sheet.tsx +5 -2
  45. package/src/components/layout/sidebar-rail.tsx +6 -10
  46. package/src/components/projects/project-detail.tsx +3 -35
  47. package/src/components/projects/tabs/overview-tab.tsx +3 -59
  48. package/src/components/projects/tabs/work-tab.tsx +7 -77
  49. package/src/components/protocols/structured-session-launcher.tsx +1 -22
  50. package/src/components/shared/connector-platform-icon.tsx +1 -0
  51. package/src/components/tasks/task-card.tsx +4 -34
  52. package/src/components/tasks/task-sheet.tsx +6 -36
  53. package/src/components/wallets/wallet-list.tsx +150 -0
  54. package/src/lib/agent-execute-defaults.test.ts +24 -0
  55. package/src/lib/agent-execute-defaults.ts +62 -0
  56. package/src/lib/app/navigation.test.ts +0 -13
  57. package/src/lib/app/navigation.ts +2 -7
  58. package/src/lib/app/view-constants.ts +14 -19
  59. package/src/lib/chat/queued-message-queue.test.ts +134 -1
  60. package/src/lib/chat/queued-message-queue.ts +77 -2
  61. package/src/lib/server/agents/agent-service.ts +5 -0
  62. package/src/lib/server/agents/agent-thread-session.ts +0 -1
  63. package/src/lib/server/agents/delegation-advisory.test.ts +0 -1
  64. package/src/lib/server/agents/delegation-jobs.test.ts +0 -69
  65. package/src/lib/server/agents/delegation-jobs.ts +0 -25
  66. package/src/lib/server/agents/main-agent-loop.ts +1 -49
  67. package/src/lib/server/agents/subagent-runtime.ts +0 -1
  68. package/src/lib/server/approval-match.ts +0 -85
  69. package/src/lib/server/approvals.test.ts +6 -6
  70. package/src/lib/server/approvals.ts +0 -6
  71. package/src/lib/server/autonomy/supervisor-reflection.test.ts +0 -1
  72. package/src/lib/server/builtin-extensions.ts +1 -2
  73. package/src/lib/server/capability-router.test.ts +0 -2
  74. package/src/lib/server/chat-execution/chat-execution-advanced.test.ts +1 -1
  75. package/src/lib/server/chat-execution/chat-execution-tool-events.test.ts +15 -14
  76. package/src/lib/server/chat-execution/chat-execution-types.ts +0 -2
  77. package/src/lib/server/chat-execution/chat-execution-utils.ts +2 -4
  78. package/src/lib/server/chat-execution/chat-streaming-utils.ts +2 -30
  79. package/src/lib/server/chat-execution/chat-turn-finalization.ts +1 -36
  80. package/src/lib/server/chat-execution/chat-turn-preparation.ts +81 -64
  81. package/src/lib/server/chat-execution/chat-turn-stream-execution.ts +4 -0
  82. package/src/lib/server/chat-execution/continuation-evaluator.ts +8 -0
  83. package/src/lib/server/chat-execution/iteration-event-handler.ts +0 -24
  84. package/src/lib/server/chat-execution/memory-mutation-tools.ts +1 -1
  85. package/src/lib/server/chat-execution/message-classifier.test.ts +0 -45
  86. package/src/lib/server/chat-execution/message-classifier.ts +11 -16
  87. package/src/lib/server/chat-execution/prompt-builder.test.ts +27 -0
  88. package/src/lib/server/chat-execution/prompt-builder.ts +14 -31
  89. package/src/lib/server/chat-execution/prompt-mode.test.ts +24 -0
  90. package/src/lib/server/chat-execution/prompt-mode.ts +5 -1
  91. package/src/lib/server/chat-execution/prompt-sections.ts +0 -1
  92. package/src/lib/server/chat-execution/situational-awareness.test.ts +2 -73
  93. package/src/lib/server/chat-execution/situational-awareness.ts +4 -38
  94. package/src/lib/server/chat-execution/stream-agent-chat.test.ts +13 -126
  95. package/src/lib/server/chat-execution/stream-agent-chat.ts +46 -21
  96. package/src/lib/server/chat-execution/stream-continuation.test.ts +4 -52
  97. package/src/lib/server/chat-execution/stream-continuation.ts +6 -48
  98. package/src/lib/server/chatrooms/chatroom-routing.test.ts +4 -0
  99. package/src/lib/server/chatrooms/session-mailbox.ts +0 -10
  100. package/src/lib/server/chats/chat-session-service.ts +3 -5
  101. package/src/lib/server/connectors/connector-inbound.ts +0 -1
  102. package/src/lib/server/connectors/connector-lifecycle.ts +19 -3
  103. package/src/lib/server/connectors/connector-service.ts +39 -9
  104. package/src/lib/server/connectors/discord.ts +2 -2
  105. package/src/lib/server/connectors/matrix.ts +3 -2
  106. package/src/lib/server/connectors/signal.ts +5 -4
  107. package/src/lib/server/connectors/slack.ts +10 -9
  108. package/src/lib/server/connectors/swarmdock-bidding.ts +74 -0
  109. package/src/lib/server/connectors/swarmdock-payloads.test.ts +85 -0
  110. package/src/lib/server/connectors/swarmdock-secret.test.ts +128 -0
  111. package/src/lib/server/connectors/swarmdock-secret.ts +152 -0
  112. package/src/lib/server/connectors/swarmdock-tasks.ts +119 -0
  113. package/src/lib/server/connectors/swarmdock.ts +255 -0
  114. package/src/lib/server/connectors/teams.ts +3 -2
  115. package/src/lib/server/connectors/telegram.ts +4 -4
  116. package/src/lib/server/connectors/whatsapp.ts +2 -2
  117. package/src/lib/server/daemon/controller.ts +7 -0
  118. package/src/lib/server/execution-brief.test.ts +2 -25
  119. package/src/lib/server/execution-brief.ts +12 -35
  120. package/src/lib/server/execution-engine/task-attempt.ts +0 -1
  121. package/src/lib/server/gateways/gateway-profile-service.ts +19 -1
  122. package/src/lib/server/messages/message-repository.test.ts +70 -0
  123. package/src/lib/server/messages/message-repository.ts +11 -6
  124. package/src/lib/server/openclaw/deploy.ts +32 -2
  125. package/src/lib/server/persistence/storage-context.ts +0 -5
  126. package/src/lib/server/plugins-advanced.test.ts +1 -2
  127. package/src/lib/server/portability/export.ts +109 -0
  128. package/src/lib/server/portability/import.ts +159 -0
  129. package/src/lib/server/protocols/protocol-normalization.ts +0 -4
  130. package/src/lib/server/protocols/protocol-queries.ts +0 -6
  131. package/src/lib/server/protocols/protocol-run-lifecycle.ts +4 -32
  132. package/src/lib/server/protocols/protocol-service.ts +0 -1
  133. package/src/lib/server/protocols/protocol-step-helpers.ts +0 -4
  134. package/src/lib/server/protocols/protocol-step-processors.ts +0 -6
  135. package/src/lib/server/protocols/protocol-swarm.ts +0 -2
  136. package/src/lib/server/protocols/protocol-types.ts +0 -2
  137. package/src/lib/server/provider-health.ts +1 -10
  138. package/src/lib/server/runtime/daemon-state/core.ts +0 -9
  139. package/src/lib/server/runtime/daemon-state.test.ts +0 -35
  140. package/src/lib/server/runtime/heartbeat-service.ts +3 -23
  141. package/src/lib/server/runtime/process-manager.ts +13 -9
  142. package/src/lib/server/runtime/queue/core.ts +11 -33
  143. package/src/lib/server/runtime/runtime-storage-write-paths.test.ts +6 -6
  144. package/src/lib/server/runtime/scheduler.ts +0 -13
  145. package/src/lib/server/runtime/session-run-manager/drain.ts +0 -24
  146. package/src/lib/server/runtime/session-run-manager/enqueue.ts +0 -1
  147. package/src/lib/server/runtime/session-run-manager/queries.ts +15 -1
  148. package/src/lib/server/runtime/session-run-manager/recovery.ts +0 -1
  149. package/src/lib/server/runtime/session-run-manager.test.ts +58 -28
  150. package/src/lib/server/sandbox/session-runtime.test.ts +18 -1
  151. package/src/lib/server/sandbox/session-runtime.ts +40 -28
  152. package/src/lib/server/session-tools/autonomy-tools.test.ts +7 -9
  153. package/src/lib/server/session-tools/context.ts +1 -1
  154. package/src/lib/server/session-tools/credential-env.ts +109 -0
  155. package/src/lib/server/session-tools/crud.ts +3 -17
  156. package/src/lib/server/session-tools/delegate.ts +0 -4
  157. package/src/lib/server/session-tools/edit_file.ts +3 -2
  158. package/src/lib/server/session-tools/execute.test.ts +58 -0
  159. package/src/lib/server/session-tools/execute.ts +334 -0
  160. package/src/lib/server/session-tools/files-tool.ts +635 -0
  161. package/src/lib/server/session-tools/index.ts +14 -8
  162. package/src/lib/server/session-tools/memory-tool.ts +242 -0
  163. package/src/lib/server/session-tools/memory.ts +1 -1
  164. package/src/lib/server/session-tools/openclaw-nodes.ts +3 -2
  165. package/src/lib/server/session-tools/openclaw-workspace.ts +3 -2
  166. package/src/lib/server/session-tools/platform-tool.ts +617 -0
  167. package/src/lib/server/session-tools/session-info.ts +3 -2
  168. package/src/lib/server/session-tools/session-tools-wiring.test.ts +3 -4
  169. package/src/lib/server/session-tools/shell.ts +7 -122
  170. package/src/lib/server/session-tools/skills-tool.ts +396 -0
  171. package/src/lib/server/session-tools/team-context.ts +0 -3
  172. package/src/lib/server/session-tools/web.ts +2 -2
  173. package/src/lib/server/storage-normalization.ts +10 -0
  174. package/src/lib/server/storage.ts +18 -45
  175. package/src/lib/server/tasks/task-checkout.ts +59 -0
  176. package/src/lib/server/tasks/task-lifecycle.ts +2 -0
  177. package/src/lib/server/tasks/task-route-service.ts +4 -26
  178. package/src/lib/server/tasks/task-service.ts +0 -7
  179. package/src/lib/server/tool-aliases.ts +2 -2
  180. package/src/lib/server/tool-capability-policy-advanced.test.ts +13 -6
  181. package/src/lib/server/tool-capability-policy.test.ts +2 -1
  182. package/src/lib/server/tool-capability-policy.ts +60 -35
  183. package/src/lib/server/tool-planning.ts +11 -12
  184. package/src/lib/server/universal-tool-access.ts +0 -1
  185. package/src/lib/server/wallets/wallet-crypto.ts +33 -0
  186. package/src/lib/server/wallets/wallet-repository.ts +24 -0
  187. package/src/lib/server/wallets/wallet-service.ts +119 -0
  188. package/src/lib/server/working-state/extraction.ts +8 -42
  189. package/src/lib/server/working-state/normalization.ts +10 -103
  190. package/src/lib/server/working-state/service.ts +12 -21
  191. package/src/lib/setup-defaults.ts +5 -0
  192. package/src/lib/strip-internal-metadata.test.ts +1 -1
  193. package/src/lib/strip-internal-metadata.ts +1 -1
  194. package/src/lib/tool-definitions.ts +1 -1
  195. package/src/lib/validation/schemas.test.ts +16 -0
  196. package/src/lib/validation/schemas.ts +49 -2
  197. package/src/stores/slices/data-slice.ts +5 -1
  198. package/src/stores/slices/ui-slice.ts +0 -4
  199. package/src/stores/use-chat-store.test.ts +231 -0
  200. package/src/stores/use-chat-store.ts +62 -13
  201. package/src/types/agent.ts +264 -0
  202. package/src/types/app-settings.ts +173 -0
  203. package/src/types/approval.ts +25 -0
  204. package/src/types/connector.ts +188 -0
  205. package/src/types/extension.ts +386 -0
  206. package/src/types/index.ts +16 -3555
  207. package/src/types/message.ts +56 -0
  208. package/src/types/misc.ts +737 -0
  209. package/src/types/protocol.ts +420 -0
  210. package/src/types/provider.ts +52 -0
  211. package/src/types/run.ts +180 -0
  212. package/src/types/schedule.ts +59 -0
  213. package/src/types/session.ts +215 -0
  214. package/src/types/skill.ts +157 -0
  215. package/src/types/swarmdock.ts +29 -0
  216. package/src/types/task.ts +144 -0
  217. package/src/types/working-state.ts +204 -0
  218. package/src/views/settings/section-heartbeat.tsx +2 -2
  219. package/src/views/settings/section-runtime-loop.tsx +0 -14
  220. package/src/app/api/canvas/[sessionId]/route.ts +0 -35
  221. package/src/app/api/missions/[id]/actions/route.ts +0 -31
  222. package/src/app/api/missions/[id]/events/route.ts +0 -14
  223. package/src/app/api/missions/[id]/route.ts +0 -10
  224. package/src/app/api/missions/route.test.ts +0 -244
  225. package/src/app/api/missions/route.ts +0 -57
  226. package/src/app/api/wallets/[id]/approve/route.ts +0 -79
  227. package/src/app/api/wallets/[id]/balance-history/route.ts +0 -18
  228. package/src/app/api/wallets/[id]/send/route.ts +0 -113
  229. package/src/app/api/wallets/[id]/transactions/route.ts +0 -18
  230. package/src/app/missions/[id]/page.tsx +0 -3
  231. package/src/app/missions/page.tsx +0 -685
  232. package/src/components/canvas/canvas-panel.tsx +0 -267
  233. package/src/components/wallets/wallet-approval-dialog.tsx +0 -107
  234. package/src/components/wallets/wallet-panel.tsx +0 -1010
  235. package/src/components/wallets/wallet-section.tsx +0 -260
  236. package/src/features/missions/queries.ts +0 -23
  237. package/src/lib/canvas-content.test.ts +0 -360
  238. package/src/lib/canvas-content.ts +0 -198
  239. package/src/lib/server/canvas-content.test.ts +0 -32
  240. package/src/lib/server/canvas-content.ts +0 -6
  241. package/src/lib/server/ethereum.ts +0 -591
  242. package/src/lib/server/evm-swap.ts +0 -476
  243. package/src/lib/server/missions/mission-intent.test.ts +0 -63
  244. package/src/lib/server/missions/mission-intent.ts +0 -569
  245. package/src/lib/server/missions/mission-repository.ts +0 -74
  246. package/src/lib/server/missions/mission-service/actions.ts +0 -6
  247. package/src/lib/server/missions/mission-service/bindings.ts +0 -9
  248. package/src/lib/server/missions/mission-service/context.ts +0 -4
  249. package/src/lib/server/missions/mission-service/core.ts +0 -2271
  250. package/src/lib/server/missions/mission-service/queries.ts +0 -12
  251. package/src/lib/server/missions/mission-service/recovery.ts +0 -5
  252. package/src/lib/server/missions/mission-service/ticks.ts +0 -9
  253. package/src/lib/server/missions/mission-service.test.ts +0 -888
  254. package/src/lib/server/missions/mission-service.ts +0 -6
  255. package/src/lib/server/session-tools/canvas.ts +0 -105
  256. package/src/lib/server/session-tools/sandbox.ts +0 -281
  257. package/src/lib/server/session-tools/wallet-tool.test.ts +0 -150
  258. package/src/lib/server/session-tools/wallet.ts +0 -1287
  259. package/src/lib/server/solana.ts +0 -327
  260. package/src/lib/server/wallet/wallet-execution.test.ts +0 -198
  261. package/src/lib/server/wallet/wallet-portfolio.test.ts +0 -98
  262. package/src/lib/server/wallet/wallet-portfolio.ts +0 -772
  263. package/src/lib/server/wallet/wallet-service.test.ts +0 -81
  264. package/src/lib/server/wallet/wallet-service.ts +0 -225
  265. package/src/lib/wallet/wallet-transactions.test.ts +0 -75
  266. package/src/lib/wallet/wallet-transactions.ts +0 -43
  267. package/src/lib/wallet/wallet.test.ts +0 -333
  268. package/src/lib/wallet/wallet.ts +0 -183
  269. package/src/views/settings/section-wallets.tsx +0 -35
@@ -1,327 +0,0 @@
1
- import {
2
- Connection,
3
- Keypair,
4
- LAMPORTS_PER_SOL,
5
- PublicKey,
6
- SystemProgram,
7
- Transaction,
8
- VersionedTransaction,
9
- sendAndConfirmTransaction,
10
- } from '@solana/web3.js'
11
- import bs58 from 'bs58'
12
- import nacl from 'tweetnacl'
13
-
14
- import { decryptKey, encryptKey } from './storage'
15
-
16
- export type SolanaCluster = 'mainnet-beta' | 'devnet' | 'testnet'
17
-
18
- export interface SolanaExecutionOptions {
19
- cluster?: SolanaCluster | string | null
20
- rpcUrl?: string | null
21
- }
22
-
23
- export interface SolanaMessageInput {
24
- message?: string | null
25
- messageHex?: string | null
26
- messageBase64?: string | null
27
- }
28
-
29
- const DEFAULT_RPC_URL = process.env.SOLANA_RPC_URL || 'https://api.mainnet-beta.solana.com'
30
-
31
- function getClusterRpcUrl(cluster: SolanaCluster): string {
32
- if (cluster === 'devnet') return process.env.SOLANA_DEVNET_RPC_URL || 'https://api.devnet.solana.com'
33
- if (cluster === 'testnet') return process.env.SOLANA_TESTNET_RPC_URL || 'https://api.testnet.solana.com'
34
- return process.env.SOLANA_RPC_URL || 'https://api.mainnet-beta.solana.com'
35
- }
36
-
37
- function normalizeHexMessage(value: string): Uint8Array {
38
- const trimmed = value.trim()
39
- if (!/^0x[0-9a-fA-F]*$/.test(trimmed)) {
40
- throw new Error('messageHex must be a 0x-prefixed hex string')
41
- }
42
- return Uint8Array.from(Buffer.from(trimmed.slice(2), 'hex'))
43
- }
44
-
45
- function normalizeMessageBytes(input: SolanaMessageInput): Uint8Array {
46
- if (typeof input.messageHex === 'string' && input.messageHex.trim()) return normalizeHexMessage(input.messageHex)
47
- if (typeof input.messageBase64 === 'string' && input.messageBase64.trim()) {
48
- return Uint8Array.from(Buffer.from(input.messageBase64.trim(), 'base64'))
49
- }
50
- if (typeof input.message === 'string') return new TextEncoder().encode(input.message)
51
- throw new Error('message, messageHex, or messageBase64 is required')
52
- }
53
-
54
- function deserializeTransactionBase64(value: string): Transaction | VersionedTransaction {
55
- const bytes = Buffer.from(value, 'base64')
56
- try {
57
- return VersionedTransaction.deserialize(bytes)
58
- } catch {
59
- return Transaction.from(bytes)
60
- }
61
- }
62
-
63
- function serializeTransactionBase64(transaction: Transaction | VersionedTransaction): string {
64
- return Buffer.from(transaction.serialize()).toString('base64')
65
- }
66
-
67
- function collectTransactionSignatures(transaction: Transaction | VersionedTransaction): string[] {
68
- if (transaction instanceof VersionedTransaction) {
69
- return transaction.signatures
70
- .map((signature) => bs58.encode(signature))
71
- .filter(Boolean)
72
- }
73
- return transaction.signatures
74
- .map((entry) => (entry.signature ? bs58.encode(entry.signature) : ''))
75
- .filter(Boolean)
76
- }
77
-
78
- function signTransactionWithWallet(
79
- encryptedPrivateKey: string,
80
- transaction: Transaction | VersionedTransaction,
81
- ): { transaction: Transaction | VersionedTransaction; publicKey: string } {
82
- const keypair = getKeypairFromEncrypted(encryptedPrivateKey)
83
- if (transaction instanceof VersionedTransaction) {
84
- transaction.sign([keypair])
85
- } else {
86
- transaction.sign(keypair)
87
- }
88
- return {
89
- transaction,
90
- publicKey: keypair.publicKey.toBase58(),
91
- }
92
- }
93
-
94
- export function generateSolanaKeypair(): { publicKey: string; encryptedPrivateKey: string } {
95
- const keypair = Keypair.generate()
96
- const secretKeyBase58 = bs58.encode(keypair.secretKey)
97
- return {
98
- publicKey: keypair.publicKey.toBase58(),
99
- encryptedPrivateKey: encryptKey(secretKeyBase58),
100
- }
101
- }
102
-
103
- export function getKeypairFromEncrypted(encryptedPrivateKey: string): Keypair {
104
- const secretKeyBase58 = decryptKey(encryptedPrivateKey)
105
- const secretKey = bs58.decode(secretKeyBase58)
106
- return Keypair.fromSecretKey(secretKey)
107
- }
108
-
109
- export function normalizeSolanaCluster(value: unknown, fallback: SolanaCluster = 'mainnet-beta'): SolanaCluster {
110
- const normalized = String(value ?? '').trim().toLowerCase()
111
- if (!normalized) return fallback
112
- if (normalized === 'mainnet' || normalized === 'mainnet-beta' || normalized === 'solana') return 'mainnet-beta'
113
- if (normalized === 'devnet') return 'devnet'
114
- if (normalized === 'testnet') return 'testnet'
115
- throw new Error(`Unsupported Solana cluster: ${String(value)}`)
116
- }
117
-
118
- export function getSolanaClusterLabel(value?: unknown): string {
119
- const cluster = normalizeSolanaCluster(value)
120
- if (cluster === 'devnet') return 'Solana Devnet'
121
- if (cluster === 'testnet') return 'Solana Testnet'
122
- return 'Solana Mainnet'
123
- }
124
-
125
- export function getSolanaExplorerUrl(cluster: SolanaCluster | string | null | undefined, kind: 'address' | 'transaction', value: string): string {
126
- const normalized = normalizeSolanaCluster(cluster)
127
- const prefix = kind === 'address' ? 'address' : 'tx'
128
- const clusterSuffix = normalized === 'mainnet-beta' ? '' : `?cluster=${normalized}`
129
- return `https://explorer.solana.com/${prefix}/${value}${clusterSuffix}`
130
- }
131
-
132
- const CONNECTION_CACHE_MAX = 50
133
- const connectionCache = new Map<string, Connection>()
134
-
135
- function getCachedConnection(url: string): Connection {
136
- let conn = connectionCache.get(url)
137
- if (!conn) {
138
- // FIFO eviction if cache exceeds cap
139
- if (connectionCache.size >= CONNECTION_CACHE_MAX) {
140
- const firstKey = connectionCache.keys().next().value
141
- if (firstKey !== undefined) connectionCache.delete(firstKey)
142
- }
143
- conn = new Connection(url, {
144
- commitment: 'confirmed',
145
- disableRetryOnRateLimit: true,
146
- })
147
- connectionCache.set(url, conn)
148
- }
149
- return conn
150
- }
151
-
152
- export function getConnection(rpcUrl?: string): Connection {
153
- return getCachedConnection(rpcUrl || DEFAULT_RPC_URL)
154
- }
155
-
156
- export function getConnectionForCluster(cluster?: SolanaCluster | string | null, rpcUrl?: string | null): Connection {
157
- return getCachedConnection(rpcUrl || getClusterRpcUrl(normalizeSolanaCluster(cluster)))
158
- }
159
-
160
- export async function getBalance(publicKey: string, rpcUrl?: string): Promise<number> {
161
- const connection = getConnection(rpcUrl)
162
- const pk = new PublicKey(publicKey)
163
- return connection.getBalance(pk)
164
- }
165
-
166
- export async function sendSol(
167
- encryptedPrivateKey: string,
168
- toAddress: string,
169
- lamports: number,
170
- rpcUrl?: string,
171
- ): Promise<{ signature: string; fee: number }> {
172
- const connection = getConnection(rpcUrl)
173
- const fromKeypair = getKeypairFromEncrypted(encryptedPrivateKey)
174
- const toPublicKey = new PublicKey(toAddress)
175
-
176
- const transaction = new Transaction().add(
177
- SystemProgram.transfer({
178
- fromPubkey: fromKeypair.publicKey,
179
- toPubkey: toPublicKey,
180
- lamports,
181
- }),
182
- )
183
-
184
- const signature = await sendAndConfirmTransaction(connection, transaction, [fromKeypair])
185
-
186
- let fee = 5000
187
- try {
188
- const txInfo = await connection.getTransaction(signature, { commitment: 'confirmed' })
189
- if (txInfo?.meta?.fee) fee = txInfo.meta.fee
190
- } catch {
191
- // keep default
192
- }
193
-
194
- return { signature, fee }
195
- }
196
-
197
- export async function signSolanaMessage(
198
- encryptedPrivateKey: string,
199
- input: SolanaMessageInput,
200
- ): Promise<{ signature: string; publicKey: string }> {
201
- const keypair = getKeypairFromEncrypted(encryptedPrivateKey)
202
- const signature = nacl.sign.detached(normalizeMessageBytes(input), keypair.secretKey)
203
- return {
204
- signature: bs58.encode(signature),
205
- publicKey: keypair.publicKey.toBase58(),
206
- }
207
- }
208
-
209
- export async function signSolanaTransaction(
210
- encryptedPrivateKey: string,
211
- transactionBase64: string,
212
- ): Promise<{
213
- signedTransactionBase64: string
214
- signatures: string[]
215
- publicKey: string
216
- versioned: boolean
217
- }> {
218
- const unsignedTx = deserializeTransactionBase64(transactionBase64)
219
- const { transaction, publicKey } = signTransactionWithWallet(encryptedPrivateKey, unsignedTx)
220
- return {
221
- signedTransactionBase64: serializeTransactionBase64(transaction),
222
- signatures: collectTransactionSignatures(transaction),
223
- publicKey,
224
- versioned: transaction instanceof VersionedTransaction,
225
- }
226
- }
227
-
228
- export async function simulateSolanaTransaction(
229
- encryptedPrivateKey: string,
230
- transactionBase64: string,
231
- options?: SolanaExecutionOptions,
232
- ): Promise<{
233
- signatures: string[]
234
- publicKey: string
235
- logs: string[]
236
- unitsConsumed?: number
237
- err?: unknown
238
- versioned: boolean
239
- }> {
240
- const unsignedTx = deserializeTransactionBase64(transactionBase64)
241
- const { transaction, publicKey } = signTransactionWithWallet(encryptedPrivateKey, unsignedTx)
242
- const connection = getConnectionForCluster(options?.cluster, options?.rpcUrl)
243
- const simulation = transaction instanceof VersionedTransaction
244
- ? await connection.simulateTransaction(transaction)
245
- : await connection.simulateTransaction(transaction)
246
- return {
247
- signatures: collectTransactionSignatures(transaction),
248
- publicKey,
249
- logs: simulation.value.logs || [],
250
- unitsConsumed: simulation.value.unitsConsumed ?? undefined,
251
- err: simulation.value.err ?? undefined,
252
- versioned: transaction instanceof VersionedTransaction,
253
- }
254
- }
255
-
256
- export async function sendSolanaTransaction(
257
- encryptedPrivateKey: string,
258
- input: {
259
- transactionBase64?: string | null
260
- signedTransactionBase64?: string | null
261
- waitForConfirmation?: boolean
262
- },
263
- options?: SolanaExecutionOptions,
264
- ): Promise<{
265
- signature: string
266
- publicKey: string
267
- explorerUrl: string
268
- versioned: boolean
269
- }> {
270
- const connection = getConnectionForCluster(options?.cluster, options?.rpcUrl)
271
- const keypair = getKeypairFromEncrypted(encryptedPrivateKey)
272
- const waitForConfirmation = input.waitForConfirmation !== false
273
-
274
- let transaction: Transaction | VersionedTransaction
275
- if (typeof input.signedTransactionBase64 === 'string' && input.signedTransactionBase64.trim()) {
276
- transaction = deserializeTransactionBase64(input.signedTransactionBase64.trim())
277
- } else if (typeof input.transactionBase64 === 'string' && input.transactionBase64.trim()) {
278
- transaction = signTransactionWithWallet(encryptedPrivateKey, deserializeTransactionBase64(input.transactionBase64.trim())).transaction
279
- } else {
280
- throw new Error('transactionBase64 or signedTransactionBase64 is required')
281
- }
282
-
283
- const raw = transaction.serialize()
284
- const signature = await connection.sendRawTransaction(raw)
285
- if (waitForConfirmation) {
286
- await connection.confirmTransaction(signature, 'confirmed')
287
- }
288
-
289
- return {
290
- signature,
291
- publicKey: keypair.publicKey.toBase58(),
292
- explorerUrl: getSolanaExplorerUrl(options?.cluster, 'transaction', signature),
293
- versioned: transaction instanceof VersionedTransaction,
294
- }
295
- }
296
-
297
- export async function getRecentTransactions(
298
- publicKey: string,
299
- limit = 20,
300
- rpcUrl?: string,
301
- ): Promise<Array<{ signature: string; blockTime: number | null; err: unknown }>> {
302
- const connection = getConnection(rpcUrl)
303
- const pk = new PublicKey(publicKey)
304
- const signatures = await connection.getSignaturesForAddress(pk, { limit })
305
- return signatures.map((s) => ({
306
- signature: s.signature,
307
- blockTime: s.blockTime ?? null,
308
- err: s.err,
309
- }))
310
- }
311
-
312
- export function isValidSolanaAddress(address: string): boolean {
313
- try {
314
- new PublicKey(address)
315
- return true
316
- } catch {
317
- return false
318
- }
319
- }
320
-
321
- export function lamportsToSol(lamports: number): number {
322
- return lamports / LAMPORTS_PER_SOL
323
- }
324
-
325
- export function solToLamports(sol: number): number {
326
- return Math.round(sol * LAMPORTS_PER_SOL)
327
- }
@@ -1,198 +0,0 @@
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('@/lib/server/storage').encryptKey
18
- let callEthereumContract: typeof import('@/lib/server/ethereum').callEthereumContract
19
- let encodeEthereumContractCall: typeof import('@/lib/server/ethereum').encodeEthereumContractCall
20
- let prepareEvmSwapPlan: typeof import('@/lib/server/evm-swap').prepareEvmSwapPlan
21
- let signEthereumMessage: typeof import('@/lib/server/ethereum').signEthereumMessage
22
- let signEthereumTypedData: typeof import('@/lib/server/ethereum').signEthereumTypedData
23
- let generateSolanaKeypair: typeof import('@/lib/server/solana').generateSolanaKeypair
24
- let signSolanaMessage: typeof import('@/lib/server/solana').signSolanaMessage
25
- let signSolanaTransaction: typeof import('@/lib/server/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('@/lib/server/storage'))
40
- ;({ callEthereumContract, encodeEthereumContractCall, signEthereumMessage, signEthereumTypedData } = await import('@/lib/server/ethereum'))
41
- ;({ prepareEvmSwapPlan } = await import('@/lib/server/evm-swap'))
42
- ;({ generateSolanaKeypair, signSolanaMessage, signSolanaTransaction } = await import('@/lib/server/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
- })
@@ -1,98 +0,0 @@
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 '@/lib/server/wallet/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
- })