@swarmclawai/swarmclaw 1.2.8 → 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 (195) hide show
  1. package/README.md +30 -6
  2. package/package.json +2 -2
  3. package/src/app/agents/[id]/page.tsx +1 -18
  4. package/src/app/api/agents/thread-route.test.ts +0 -1
  5. package/src/app/api/approvals/route.test.ts +6 -22
  6. package/src/app/api/connectors/route.ts +2 -2
  7. package/src/app/api/portability/export/route.ts +8 -0
  8. package/src/app/api/portability/import/route.test.ts +80 -0
  9. package/src/app/api/portability/import/route.ts +28 -0
  10. package/src/app/api/settings/route.ts +0 -2
  11. package/src/app/api/wallets/[id]/route.ts +15 -157
  12. package/src/app/api/wallets/generate/route.ts +22 -0
  13. package/src/app/api/wallets/route.test.ts +147 -0
  14. package/src/app/api/wallets/route.ts +13 -95
  15. package/src/app/autonomy/page.tsx +2 -57
  16. package/src/app/protocols/page.tsx +2 -21
  17. package/src/app/settings/page.tsx +0 -9
  18. package/src/app/wallets/page.tsx +105 -5
  19. package/src/cli/index.js +21 -33
  20. package/src/cli/spec.js +19 -30
  21. package/src/components/agents/agent-sheet.tsx +2 -40
  22. package/src/components/agents/inspector-panel.tsx +0 -83
  23. package/src/components/chat/chat-card.tsx +0 -31
  24. package/src/components/chat/message-bubble.tsx +1 -108
  25. package/src/components/connectors/connector-sheet.tsx +25 -1
  26. package/src/components/layout/sidebar-rail.tsx +6 -10
  27. package/src/components/projects/project-detail.tsx +3 -35
  28. package/src/components/projects/tabs/overview-tab.tsx +3 -59
  29. package/src/components/projects/tabs/work-tab.tsx +7 -77
  30. package/src/components/protocols/structured-session-launcher.tsx +1 -22
  31. package/src/components/shared/connector-platform-icon.tsx +1 -0
  32. package/src/components/tasks/task-card.tsx +4 -34
  33. package/src/components/tasks/task-sheet.tsx +6 -36
  34. package/src/components/wallets/wallet-list.tsx +150 -0
  35. package/src/lib/app/navigation.test.ts +0 -13
  36. package/src/lib/app/navigation.ts +2 -7
  37. package/src/lib/app/view-constants.ts +14 -19
  38. package/src/lib/server/agents/agent-thread-session.ts +0 -1
  39. package/src/lib/server/agents/delegation-advisory.test.ts +0 -1
  40. package/src/lib/server/agents/delegation-jobs.test.ts +0 -69
  41. package/src/lib/server/agents/delegation-jobs.ts +0 -25
  42. package/src/lib/server/agents/main-agent-loop.ts +1 -49
  43. package/src/lib/server/agents/subagent-runtime.ts +0 -1
  44. package/src/lib/server/approval-match.ts +0 -85
  45. package/src/lib/server/approvals.test.ts +6 -6
  46. package/src/lib/server/approvals.ts +0 -6
  47. package/src/lib/server/autonomy/supervisor-reflection.test.ts +0 -1
  48. package/src/lib/server/builtin-extensions.ts +0 -2
  49. package/src/lib/server/capability-router.test.ts +0 -2
  50. package/src/lib/server/chat-execution/chat-execution-tool-events.test.ts +14 -14
  51. package/src/lib/server/chat-execution/chat-execution-types.ts +0 -2
  52. package/src/lib/server/chat-execution/chat-execution-utils.ts +0 -2
  53. package/src/lib/server/chat-execution/chat-streaming-utils.ts +2 -30
  54. package/src/lib/server/chat-execution/chat-turn-finalization.ts +1 -36
  55. package/src/lib/server/chat-execution/chat-turn-preparation.ts +2 -22
  56. package/src/lib/server/chat-execution/iteration-event-handler.ts +0 -24
  57. package/src/lib/server/chat-execution/message-classifier.test.ts +0 -45
  58. package/src/lib/server/chat-execution/message-classifier.ts +1 -16
  59. package/src/lib/server/chat-execution/prompt-builder.test.ts +0 -1
  60. package/src/lib/server/chat-execution/prompt-builder.ts +0 -30
  61. package/src/lib/server/chat-execution/prompt-sections.ts +0 -1
  62. package/src/lib/server/chat-execution/situational-awareness.test.ts +2 -73
  63. package/src/lib/server/chat-execution/situational-awareness.ts +4 -38
  64. package/src/lib/server/chat-execution/stream-agent-chat.test.ts +8 -123
  65. package/src/lib/server/chat-execution/stream-agent-chat.ts +1 -5
  66. package/src/lib/server/chat-execution/stream-continuation.test.ts +4 -52
  67. package/src/lib/server/chat-execution/stream-continuation.ts +6 -48
  68. package/src/lib/server/chatrooms/session-mailbox.ts +0 -10
  69. package/src/lib/server/chats/chat-session-service.ts +3 -5
  70. package/src/lib/server/connectors/connector-inbound.ts +0 -1
  71. package/src/lib/server/connectors/connector-lifecycle.ts +19 -3
  72. package/src/lib/server/connectors/connector-service.ts +39 -9
  73. package/src/lib/server/connectors/swarmdock-bidding.ts +74 -0
  74. package/src/lib/server/connectors/swarmdock-payloads.test.ts +85 -0
  75. package/src/lib/server/connectors/swarmdock-secret.test.ts +128 -0
  76. package/src/lib/server/connectors/swarmdock-secret.ts +152 -0
  77. package/src/lib/server/connectors/swarmdock-tasks.ts +119 -0
  78. package/src/lib/server/connectors/swarmdock.ts +255 -0
  79. package/src/lib/server/execution-brief.test.ts +2 -25
  80. package/src/lib/server/execution-brief.ts +12 -35
  81. package/src/lib/server/execution-engine/task-attempt.ts +0 -1
  82. package/src/lib/server/persistence/storage-context.ts +0 -5
  83. package/src/lib/server/portability/export.ts +109 -0
  84. package/src/lib/server/portability/import.ts +159 -0
  85. package/src/lib/server/protocols/protocol-normalization.ts +0 -4
  86. package/src/lib/server/protocols/protocol-queries.ts +0 -6
  87. package/src/lib/server/protocols/protocol-run-lifecycle.ts +4 -32
  88. package/src/lib/server/protocols/protocol-service.ts +0 -1
  89. package/src/lib/server/protocols/protocol-step-helpers.ts +0 -4
  90. package/src/lib/server/protocols/protocol-step-processors.ts +0 -6
  91. package/src/lib/server/protocols/protocol-swarm.ts +0 -2
  92. package/src/lib/server/protocols/protocol-types.ts +0 -2
  93. package/src/lib/server/provider-health.ts +0 -9
  94. package/src/lib/server/runtime/daemon-state/core.ts +0 -9
  95. package/src/lib/server/runtime/daemon-state.test.ts +0 -35
  96. package/src/lib/server/runtime/heartbeat-service.ts +3 -23
  97. package/src/lib/server/runtime/queue/core.ts +11 -33
  98. package/src/lib/server/runtime/runtime-storage-write-paths.test.ts +6 -6
  99. package/src/lib/server/runtime/scheduler.ts +0 -13
  100. package/src/lib/server/runtime/session-run-manager/drain.ts +0 -24
  101. package/src/lib/server/runtime/session-run-manager/enqueue.ts +0 -1
  102. package/src/lib/server/runtime/session-run-manager/queries.ts +0 -1
  103. package/src/lib/server/runtime/session-run-manager/recovery.ts +0 -1
  104. package/src/lib/server/runtime/session-run-manager.test.ts +0 -28
  105. package/src/lib/server/session-tools/crud.ts +0 -14
  106. package/src/lib/server/session-tools/delegate.ts +0 -4
  107. package/src/lib/server/session-tools/index.ts +0 -4
  108. package/src/lib/server/session-tools/team-context.ts +0 -3
  109. package/src/lib/server/storage-normalization.ts +8 -0
  110. package/src/lib/server/storage.ts +18 -45
  111. package/src/lib/server/tasks/task-checkout.ts +59 -0
  112. package/src/lib/server/tasks/task-lifecycle.ts +2 -0
  113. package/src/lib/server/tasks/task-route-service.ts +4 -26
  114. package/src/lib/server/tasks/task-service.ts +0 -7
  115. package/src/lib/server/tool-aliases.ts +0 -1
  116. package/src/lib/server/tool-capability-policy-advanced.test.ts +4 -4
  117. package/src/lib/server/tool-capability-policy.ts +0 -2
  118. package/src/lib/server/tool-planning.ts +0 -12
  119. package/src/lib/server/universal-tool-access.ts +0 -1
  120. package/src/lib/server/wallets/wallet-crypto.ts +33 -0
  121. package/src/lib/server/wallets/wallet-repository.ts +24 -0
  122. package/src/lib/server/wallets/wallet-service.ts +119 -0
  123. package/src/lib/server/working-state/extraction.ts +8 -42
  124. package/src/lib/server/working-state/normalization.ts +10 -103
  125. package/src/lib/server/working-state/service.ts +12 -21
  126. package/src/lib/strip-internal-metadata.test.ts +1 -1
  127. package/src/lib/strip-internal-metadata.ts +1 -1
  128. package/src/lib/tool-definitions.ts +0 -1
  129. package/src/lib/validation/schemas.ts +33 -2
  130. package/src/stores/slices/data-slice.ts +5 -1
  131. package/src/stores/slices/ui-slice.ts +0 -4
  132. package/src/types/agent.ts +0 -84
  133. package/src/types/app-settings.ts +0 -2
  134. package/src/types/approval.ts +0 -2
  135. package/src/types/connector.ts +1 -0
  136. package/src/types/index.ts +1 -1
  137. package/src/types/message.ts +0 -1
  138. package/src/types/misc.ts +0 -2
  139. package/src/types/protocol.ts +0 -2
  140. package/src/types/run.ts +0 -3
  141. package/src/types/session.ts +1 -51
  142. package/src/types/swarmdock.ts +29 -0
  143. package/src/types/task.ts +7 -3
  144. package/src/types/working-state.ts +2 -9
  145. package/src/views/settings/section-runtime-loop.tsx +0 -14
  146. package/src/app/api/canvas/[sessionId]/route.ts +0 -35
  147. package/src/app/api/missions/[id]/actions/route.ts +0 -31
  148. package/src/app/api/missions/[id]/events/route.ts +0 -14
  149. package/src/app/api/missions/[id]/route.ts +0 -10
  150. package/src/app/api/missions/route.test.ts +0 -244
  151. package/src/app/api/missions/route.ts +0 -57
  152. package/src/app/api/wallets/[id]/approve/route.ts +0 -79
  153. package/src/app/api/wallets/[id]/balance-history/route.ts +0 -18
  154. package/src/app/api/wallets/[id]/send/route.ts +0 -113
  155. package/src/app/api/wallets/[id]/transactions/route.ts +0 -18
  156. package/src/app/missions/[id]/page.tsx +0 -3
  157. package/src/app/missions/page.tsx +0 -685
  158. package/src/components/canvas/canvas-panel.tsx +0 -267
  159. package/src/components/wallets/wallet-approval-dialog.tsx +0 -107
  160. package/src/components/wallets/wallet-panel.tsx +0 -1010
  161. package/src/components/wallets/wallet-section.tsx +0 -260
  162. package/src/features/missions/queries.ts +0 -23
  163. package/src/lib/canvas-content.test.ts +0 -360
  164. package/src/lib/canvas-content.ts +0 -198
  165. package/src/lib/server/canvas-content.test.ts +0 -32
  166. package/src/lib/server/canvas-content.ts +0 -6
  167. package/src/lib/server/ethereum.ts +0 -591
  168. package/src/lib/server/evm-swap.ts +0 -476
  169. package/src/lib/server/missions/mission-intent.test.ts +0 -63
  170. package/src/lib/server/missions/mission-intent.ts +0 -569
  171. package/src/lib/server/missions/mission-repository.ts +0 -74
  172. package/src/lib/server/missions/mission-service/actions.ts +0 -6
  173. package/src/lib/server/missions/mission-service/bindings.ts +0 -9
  174. package/src/lib/server/missions/mission-service/context.ts +0 -4
  175. package/src/lib/server/missions/mission-service/core.ts +0 -2271
  176. package/src/lib/server/missions/mission-service/queries.ts +0 -12
  177. package/src/lib/server/missions/mission-service/recovery.ts +0 -5
  178. package/src/lib/server/missions/mission-service/ticks.ts +0 -9
  179. package/src/lib/server/missions/mission-service.test.ts +0 -888
  180. package/src/lib/server/missions/mission-service.ts +0 -6
  181. package/src/lib/server/session-tools/canvas.ts +0 -105
  182. package/src/lib/server/session-tools/wallet-tool.test.ts +0 -150
  183. package/src/lib/server/session-tools/wallet.ts +0 -1287
  184. package/src/lib/server/solana.ts +0 -327
  185. package/src/lib/server/wallet/wallet-execution.test.ts +0 -198
  186. package/src/lib/server/wallet/wallet-portfolio.test.ts +0 -98
  187. package/src/lib/server/wallet/wallet-portfolio.ts +0 -772
  188. package/src/lib/server/wallet/wallet-service.test.ts +0 -81
  189. package/src/lib/server/wallet/wallet-service.ts +0 -225
  190. package/src/lib/wallet/wallet-transactions.test.ts +0 -75
  191. package/src/lib/wallet/wallet-transactions.ts +0 -43
  192. package/src/lib/wallet/wallet.test.ts +0 -333
  193. package/src/lib/wallet/wallet.ts +0 -183
  194. package/src/types/mission.ts +0 -185
  195. 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
- })