@swarmclawai/swarmclaw 1.2.8 → 1.3.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 (214) hide show
  1. package/README.md +39 -6
  2. package/package.json +2 -2
  3. package/src/app/agents/[id]/page.tsx +1 -18
  4. package/src/app/api/activity/route.ts +9 -23
  5. package/src/app/api/agents/route.ts +17 -1
  6. package/src/app/api/agents/thread-route.test.ts +0 -1
  7. package/src/app/api/approvals/route.test.ts +6 -22
  8. package/src/app/api/approvals/route.ts +13 -5
  9. package/src/app/api/connectors/route.ts +2 -2
  10. package/src/app/api/credentials/[id]/route.ts +2 -0
  11. package/src/app/api/credentials/route.ts +4 -1
  12. package/src/app/api/goals/[id]/route.ts +28 -0
  13. package/src/app/api/goals/route.ts +33 -0
  14. package/src/app/api/portability/export/route.ts +8 -0
  15. package/src/app/api/portability/import/route.test.ts +80 -0
  16. package/src/app/api/portability/import/route.ts +28 -0
  17. package/src/app/api/protocols/templates/[id]/route.ts +2 -1
  18. package/src/app/api/protocols/templates/route.ts +2 -1
  19. package/src/app/api/settings/route.ts +13 -2
  20. package/src/app/api/wallets/[id]/route.ts +15 -157
  21. package/src/app/api/wallets/generate/route.ts +22 -0
  22. package/src/app/api/wallets/route.test.ts +147 -0
  23. package/src/app/api/wallets/route.ts +13 -95
  24. package/src/app/autonomy/page.tsx +2 -57
  25. package/src/app/home/page.tsx +3 -0
  26. package/src/app/protocols/page.tsx +2 -21
  27. package/src/app/settings/page.tsx +0 -9
  28. package/src/app/wallets/page.tsx +105 -5
  29. package/src/cli/index.js +32 -33
  30. package/src/cli/spec.js +26 -27
  31. package/src/components/agents/agent-sheet.tsx +2 -40
  32. package/src/components/agents/inspector-panel.tsx +0 -83
  33. package/src/components/chat/chat-card.tsx +0 -31
  34. package/src/components/chat/message-bubble.tsx +1 -108
  35. package/src/components/connectors/connector-sheet.tsx +25 -1
  36. package/src/components/layout/sidebar-rail.tsx +6 -10
  37. package/src/components/projects/project-detail.tsx +3 -35
  38. package/src/components/projects/tabs/overview-tab.tsx +3 -59
  39. package/src/components/projects/tabs/work-tab.tsx +7 -77
  40. package/src/components/protocols/structured-session-launcher.tsx +1 -22
  41. package/src/components/shared/connector-platform-icon.tsx +1 -0
  42. package/src/components/tasks/task-card.tsx +4 -34
  43. package/src/components/tasks/task-sheet.tsx +6 -36
  44. package/src/components/wallets/wallet-list.tsx +150 -0
  45. package/src/lib/app/navigation.test.ts +0 -13
  46. package/src/lib/app/navigation.ts +2 -7
  47. package/src/lib/app/view-constants.ts +14 -19
  48. package/src/lib/server/activity/activity-log.ts +16 -1
  49. package/src/lib/server/agents/agent-service.ts +24 -11
  50. package/src/lib/server/agents/agent-thread-session.ts +0 -1
  51. package/src/lib/server/agents/delegation-advisory.test.ts +0 -1
  52. package/src/lib/server/agents/delegation-jobs.test.ts +0 -69
  53. package/src/lib/server/agents/delegation-jobs.ts +0 -25
  54. package/src/lib/server/agents/main-agent-loop.ts +1 -49
  55. package/src/lib/server/agents/subagent-runtime.ts +0 -1
  56. package/src/lib/server/approval-match.ts +14 -85
  57. package/src/lib/server/approvals/approval-hooks.ts +81 -0
  58. package/src/lib/server/approvals.test.ts +6 -6
  59. package/src/lib/server/approvals.ts +11 -6
  60. package/src/lib/server/autonomy/supervisor-reflection.test.ts +0 -1
  61. package/src/lib/server/builtin-extensions.ts +0 -2
  62. package/src/lib/server/capability-router.test.ts +0 -2
  63. package/src/lib/server/chat-execution/chat-execution-tool-events.test.ts +14 -14
  64. package/src/lib/server/chat-execution/chat-execution-types.ts +0 -2
  65. package/src/lib/server/chat-execution/chat-execution-utils.ts +0 -2
  66. package/src/lib/server/chat-execution/chat-streaming-utils.ts +2 -30
  67. package/src/lib/server/chat-execution/chat-turn-finalization.ts +1 -36
  68. package/src/lib/server/chat-execution/chat-turn-preparation.ts +2 -22
  69. package/src/lib/server/chat-execution/iteration-event-handler.ts +0 -24
  70. package/src/lib/server/chat-execution/message-classifier.test.ts +0 -45
  71. package/src/lib/server/chat-execution/message-classifier.ts +1 -16
  72. package/src/lib/server/chat-execution/prompt-builder.test.ts +0 -1
  73. package/src/lib/server/chat-execution/prompt-builder.ts +0 -30
  74. package/src/lib/server/chat-execution/prompt-sections.ts +0 -1
  75. package/src/lib/server/chat-execution/situational-awareness.test.ts +2 -73
  76. package/src/lib/server/chat-execution/situational-awareness.ts +4 -38
  77. package/src/lib/server/chat-execution/stream-agent-chat.test.ts +8 -123
  78. package/src/lib/server/chat-execution/stream-agent-chat.ts +1 -5
  79. package/src/lib/server/chat-execution/stream-continuation.test.ts +4 -52
  80. package/src/lib/server/chat-execution/stream-continuation.ts +6 -48
  81. package/src/lib/server/chatrooms/session-mailbox.ts +0 -10
  82. package/src/lib/server/chats/chat-session-service.ts +3 -5
  83. package/src/lib/server/connectors/connector-inbound.ts +0 -1
  84. package/src/lib/server/connectors/connector-lifecycle.ts +19 -3
  85. package/src/lib/server/connectors/connector-service.ts +39 -9
  86. package/src/lib/server/connectors/swarmdock-bidding.ts +74 -0
  87. package/src/lib/server/connectors/swarmdock-payloads.test.ts +85 -0
  88. package/src/lib/server/connectors/swarmdock-secret.test.ts +128 -0
  89. package/src/lib/server/connectors/swarmdock-secret.ts +152 -0
  90. package/src/lib/server/connectors/swarmdock-tasks.ts +127 -0
  91. package/src/lib/server/connectors/swarmdock.ts +285 -0
  92. package/src/lib/server/execution-brief.test.ts +2 -25
  93. package/src/lib/server/execution-brief.ts +30 -35
  94. package/src/lib/server/execution-engine/task-attempt.ts +0 -1
  95. package/src/lib/server/goals/goal-repository.ts +19 -0
  96. package/src/lib/server/goals/goal-service.ts +143 -0
  97. package/src/lib/server/persistence/storage-context.ts +0 -5
  98. package/src/lib/server/portability/export.ts +109 -0
  99. package/src/lib/server/portability/import.ts +159 -0
  100. package/src/lib/server/protocols/protocol-normalization.ts +0 -4
  101. package/src/lib/server/protocols/protocol-queries.ts +0 -6
  102. package/src/lib/server/protocols/protocol-run-lifecycle.ts +4 -32
  103. package/src/lib/server/protocols/protocol-service.ts +0 -1
  104. package/src/lib/server/protocols/protocol-step-helpers.ts +0 -4
  105. package/src/lib/server/protocols/protocol-step-processors.ts +0 -6
  106. package/src/lib/server/protocols/protocol-swarm.ts +0 -2
  107. package/src/lib/server/protocols/protocol-types.ts +0 -2
  108. package/src/lib/server/provider-health.ts +0 -9
  109. package/src/lib/server/runtime/daemon-state/core.ts +0 -9
  110. package/src/lib/server/runtime/daemon-state.test.ts +0 -35
  111. package/src/lib/server/runtime/heartbeat-service.ts +3 -23
  112. package/src/lib/server/runtime/queue/core.ts +11 -33
  113. package/src/lib/server/runtime/runtime-storage-write-paths.test.ts +6 -6
  114. package/src/lib/server/runtime/scheduler.ts +0 -13
  115. package/src/lib/server/runtime/session-run-manager/drain.ts +0 -24
  116. package/src/lib/server/runtime/session-run-manager/enqueue.ts +0 -1
  117. package/src/lib/server/runtime/session-run-manager/queries.ts +0 -1
  118. package/src/lib/server/runtime/session-run-manager/recovery.ts +0 -1
  119. package/src/lib/server/runtime/session-run-manager.test.ts +0 -28
  120. package/src/lib/server/session-tools/crud.ts +0 -14
  121. package/src/lib/server/session-tools/delegate.ts +0 -4
  122. package/src/lib/server/session-tools/index.ts +0 -4
  123. package/src/lib/server/session-tools/team-context.ts +0 -3
  124. package/src/lib/server/storage-normalization.ts +13 -0
  125. package/src/lib/server/storage.ts +75 -45
  126. package/src/lib/server/tasks/task-checkout.ts +59 -0
  127. package/src/lib/server/tasks/task-lifecycle.ts +2 -0
  128. package/src/lib/server/tasks/task-route-service.ts +4 -26
  129. package/src/lib/server/tasks/task-service.ts +0 -7
  130. package/src/lib/server/tool-aliases.ts +0 -1
  131. package/src/lib/server/tool-capability-policy-advanced.test.ts +4 -4
  132. package/src/lib/server/tool-capability-policy.ts +0 -2
  133. package/src/lib/server/tool-planning.ts +0 -12
  134. package/src/lib/server/universal-tool-access.ts +0 -1
  135. package/src/lib/server/usage/cost-rollup.ts +124 -0
  136. package/src/lib/server/usage/usage-repository.ts +6 -0
  137. package/src/lib/server/wallets/wallet-crypto.ts +33 -0
  138. package/src/lib/server/wallets/wallet-repository.ts +24 -0
  139. package/src/lib/server/wallets/wallet-service.ts +119 -0
  140. package/src/lib/server/working-state/extraction.ts +8 -42
  141. package/src/lib/server/working-state/normalization.ts +10 -103
  142. package/src/lib/server/working-state/service.ts +12 -21
  143. package/src/lib/strip-internal-metadata.test.ts +1 -1
  144. package/src/lib/strip-internal-metadata.ts +1 -1
  145. package/src/lib/tool-definitions.ts +0 -1
  146. package/src/lib/validation/schemas.ts +36 -32
  147. package/src/lib/validation/server-schemas.ts +35 -0
  148. package/src/stores/slices/data-slice.ts +5 -1
  149. package/src/stores/slices/ui-slice.ts +0 -4
  150. package/src/types/agent.ts +10 -84
  151. package/src/types/app-settings.ts +6 -2
  152. package/src/types/approval.ts +3 -2
  153. package/src/types/connector.ts +1 -0
  154. package/src/types/goal.ts +30 -0
  155. package/src/types/index.ts +2 -1
  156. package/src/types/message.ts +0 -1
  157. package/src/types/misc.ts +2 -4
  158. package/src/types/protocol.ts +0 -2
  159. package/src/types/run.ts +0 -3
  160. package/src/types/session.ts +1 -51
  161. package/src/types/swarmdock.ts +29 -0
  162. package/src/types/task.ts +9 -3
  163. package/src/types/working-state.ts +2 -9
  164. package/src/views/settings/section-runtime-loop.tsx +0 -14
  165. package/src/app/api/canvas/[sessionId]/route.ts +0 -35
  166. package/src/app/api/missions/[id]/actions/route.ts +0 -31
  167. package/src/app/api/missions/[id]/events/route.ts +0 -14
  168. package/src/app/api/missions/[id]/route.ts +0 -10
  169. package/src/app/api/missions/route.test.ts +0 -244
  170. package/src/app/api/missions/route.ts +0 -57
  171. package/src/app/api/wallets/[id]/approve/route.ts +0 -79
  172. package/src/app/api/wallets/[id]/balance-history/route.ts +0 -18
  173. package/src/app/api/wallets/[id]/send/route.ts +0 -113
  174. package/src/app/api/wallets/[id]/transactions/route.ts +0 -18
  175. package/src/app/missions/[id]/page.tsx +0 -3
  176. package/src/app/missions/page.tsx +0 -685
  177. package/src/components/canvas/canvas-panel.tsx +0 -267
  178. package/src/components/wallets/wallet-approval-dialog.tsx +0 -107
  179. package/src/components/wallets/wallet-panel.tsx +0 -1010
  180. package/src/components/wallets/wallet-section.tsx +0 -260
  181. package/src/features/missions/queries.ts +0 -23
  182. package/src/lib/canvas-content.test.ts +0 -360
  183. package/src/lib/canvas-content.ts +0 -198
  184. package/src/lib/server/canvas-content.test.ts +0 -32
  185. package/src/lib/server/canvas-content.ts +0 -6
  186. package/src/lib/server/ethereum.ts +0 -591
  187. package/src/lib/server/evm-swap.ts +0 -476
  188. package/src/lib/server/missions/mission-intent.test.ts +0 -63
  189. package/src/lib/server/missions/mission-intent.ts +0 -569
  190. package/src/lib/server/missions/mission-repository.ts +0 -74
  191. package/src/lib/server/missions/mission-service/actions.ts +0 -6
  192. package/src/lib/server/missions/mission-service/bindings.ts +0 -9
  193. package/src/lib/server/missions/mission-service/context.ts +0 -4
  194. package/src/lib/server/missions/mission-service/core.ts +0 -2271
  195. package/src/lib/server/missions/mission-service/queries.ts +0 -12
  196. package/src/lib/server/missions/mission-service/recovery.ts +0 -5
  197. package/src/lib/server/missions/mission-service/ticks.ts +0 -9
  198. package/src/lib/server/missions/mission-service.test.ts +0 -888
  199. package/src/lib/server/missions/mission-service.ts +0 -6
  200. package/src/lib/server/session-tools/canvas.ts +0 -105
  201. package/src/lib/server/session-tools/wallet-tool.test.ts +0 -150
  202. package/src/lib/server/session-tools/wallet.ts +0 -1287
  203. package/src/lib/server/solana.ts +0 -327
  204. package/src/lib/server/wallet/wallet-execution.test.ts +0 -198
  205. package/src/lib/server/wallet/wallet-portfolio.test.ts +0 -98
  206. package/src/lib/server/wallet/wallet-portfolio.ts +0 -772
  207. package/src/lib/server/wallet/wallet-service.test.ts +0 -81
  208. package/src/lib/server/wallet/wallet-service.ts +0 -225
  209. package/src/lib/wallet/wallet-transactions.test.ts +0 -75
  210. package/src/lib/wallet/wallet-transactions.ts +0 -43
  211. package/src/lib/wallet/wallet.test.ts +0 -333
  212. package/src/lib/wallet/wallet.ts +0 -183
  213. package/src/types/mission.ts +0 -185
  214. package/src/views/settings/section-wallets.tsx +0 -35
@@ -1,81 +0,0 @@
1
- import assert from 'node:assert/strict'
2
- import { describe, it } from 'node:test'
3
-
4
- import type { AgentWallet, WalletTransaction } from '@/types'
5
-
6
- import {
7
- validateWalletSendLimits,
8
- walletApprovalsGloballyEnabled,
9
- walletRequiresApproval,
10
- } from '@/lib/server/wallet/wallet-service'
11
-
12
- function buildWallet(overrides: Partial<AgentWallet> = {}): AgentWallet {
13
- return {
14
- id: 'wallet-1',
15
- agentId: 'agent-1',
16
- chain: 'ethereum',
17
- publicKey: '0x0000000000000000000000000000000000000001',
18
- encryptedPrivateKey: 'secret',
19
- requireApproval: true,
20
- spendingLimitAtomic: '1000000000000000000',
21
- dailyLimitAtomic: '1500000000000000000',
22
- createdAt: 1,
23
- updatedAt: 1,
24
- ...overrides,
25
- }
26
- }
27
-
28
- function buildTransaction(overrides: Partial<WalletTransaction> = {}): WalletTransaction {
29
- return {
30
- id: 'tx-1',
31
- walletId: 'wallet-1',
32
- agentId: 'agent-1',
33
- chain: 'ethereum',
34
- type: 'send',
35
- signature: '0xhash',
36
- fromAddress: '0xfrom',
37
- toAddress: '0xto',
38
- amountAtomic: '1000000000000000000',
39
- status: 'confirmed',
40
- timestamp: Date.now(),
41
- ...overrides,
42
- }
43
- }
44
-
45
- describe('validateWalletSendLimits', () => {
46
- it('blocks approvals that would exceed the current daily limit', () => {
47
- const wallet = buildWallet()
48
- const transactions = [
49
- buildTransaction({ id: 'existing', amountAtomic: '1000000000000000000' }),
50
- ]
51
-
52
- const error = validateWalletSendLimits({
53
- wallet,
54
- amountAtomic: '600000000000000000',
55
- transactions,
56
- now: Date.now(),
57
- })
58
-
59
- assert.match(error || '', /Daily limit exceeded/)
60
- })
61
- })
62
-
63
- describe('wallet approval helpers', () => {
64
- it('treats missing app setting as globally enabled', () => {
65
- assert.equal(walletApprovalsGloballyEnabled(undefined), true)
66
- assert.equal(walletApprovalsGloballyEnabled({}), true)
67
- })
68
-
69
- it('lets the global setting disable approval even for approval-required wallets', () => {
70
- const wallet = buildWallet({ requireApproval: true })
71
-
72
- assert.equal(walletRequiresApproval(wallet, { walletApprovalsEnabled: true }), true)
73
- assert.equal(walletRequiresApproval(wallet, { walletApprovalsEnabled: false }), false)
74
- })
75
-
76
- it('still respects the per-wallet toggle when global approvals remain enabled', () => {
77
- const wallet = buildWallet({ requireApproval: false })
78
-
79
- assert.equal(walletRequiresApproval(wallet, { walletApprovalsEnabled: true }), false)
80
- })
81
- })
@@ -1,225 +0,0 @@
1
- import { genId } from '@/lib/id'
2
- import {
3
- formatWalletAmount,
4
- getWalletAssetSymbol,
5
- getWalletAtomicAmount,
6
- getWalletChainOrDefault,
7
- getWalletDefaultLimitAtomic,
8
- getWalletLimitAtomic,
9
- normalizeAtomicString,
10
- } from '@/lib/wallet/wallet'
11
- import type { Agent, AgentWallet, WalletChain, WalletTransaction } from '@/types'
12
- import { dedup } from '@/lib/shared-utils'
13
- import { loadAgent, loadAgents, loadWalletTransactions, loadWallets, upsertAgent, upsertWallet } from '@/lib/server/storage'
14
- import { generateEthereumWallet, isValidEthereumAddress, sendEth } from '@/lib/server/ethereum'
15
- import { generateSolanaKeypair, isValidSolanaAddress, sendSol } from '@/lib/server/solana'
16
- import { notify } from '@/lib/server/ws-hub'
17
- import { clearWalletPortfolioCache, getWalletPortfolio, type GetWalletPortfolioOptions, type WalletPortfolio } from '@/lib/server/wallet/wallet-portfolio'
18
-
19
- function generateWalletCredentials(chain: WalletChain): { publicKey: string; encryptedPrivateKey: string } {
20
- if (chain === 'ethereum') return generateEthereumWallet()
21
- return generateSolanaKeypair()
22
- }
23
-
24
- export function stripWalletPrivateKey<T extends Record<string, unknown>>(wallet: T): Omit<T, 'encryptedPrivateKey'> {
25
- return Object.fromEntries(Object.entries(wallet).filter(([key]) => key !== 'encryptedPrivateKey')) as Omit<T, 'encryptedPrivateKey'>
26
- }
27
-
28
- export function getAgentWalletIds(agent: Pick<Agent, 'walletIds' | 'walletId'> | null | undefined): string[] {
29
- const ids = Array.isArray(agent?.walletIds)
30
- ? agent.walletIds.filter((value): value is string => typeof value === 'string' && value.trim().length > 0)
31
- : []
32
- const legacy = typeof agent?.walletId === 'string' && agent.walletId.trim()
33
- ? [agent.walletId.trim()]
34
- : []
35
- return dedup([...ids, ...legacy])
36
- }
37
-
38
- export function getAgentActiveWalletId(
39
- agent: Pick<Agent, 'walletIds' | 'walletId' | 'activeWalletId'> | null | undefined,
40
- walletIds = getAgentWalletIds(agent),
41
- ): string | null {
42
- if (typeof agent?.activeWalletId === 'string' && walletIds.includes(agent.activeWalletId)) return agent.activeWalletId
43
- if (typeof agent?.walletId === 'string' && walletIds.includes(agent.walletId)) return agent.walletId
44
- return walletIds[0] || null
45
- }
46
-
47
- function syncAgentWalletPointers(agent: Agent, walletIds: string[], activeWalletId?: string | null): Agent {
48
- const normalizedIds = dedup(walletIds.filter(Boolean))
49
- const normalizedActive = activeWalletId && normalizedIds.includes(activeWalletId)
50
- ? activeWalletId
51
- : normalizedIds[0] || null
52
- agent.walletIds = normalizedIds
53
- agent.activeWalletId = normalizedActive
54
- agent.walletId = normalizedActive
55
- return agent
56
- }
57
-
58
- export function linkWalletToAgent(agent: Agent, walletId: string, makeActive = false): Agent {
59
- const walletIds = getAgentWalletIds(agent)
60
- if (!walletIds.includes(walletId)) walletIds.push(walletId)
61
- const activeWalletId = makeActive ? walletId : getAgentActiveWalletId(agent, walletIds)
62
- return syncAgentWalletPointers(agent, walletIds, activeWalletId)
63
- }
64
-
65
- export function unlinkWalletFromAgent(agent: Agent, walletId: string): Agent {
66
- const walletIds = getAgentWalletIds(agent).filter((id) => id !== walletId)
67
- const activeWalletId = getAgentActiveWalletId(agent, walletIds)
68
- return syncAgentWalletPointers(agent, walletIds, activeWalletId)
69
- }
70
-
71
- export function setAgentActiveWallet(agent: Agent, walletId: string | null): Agent {
72
- const walletIds = getAgentWalletIds(agent)
73
- const activeWalletId = walletId && walletIds.includes(walletId) ? walletId : walletIds[0] || null
74
- return syncAgentWalletPointers(agent, walletIds, activeWalletId)
75
- }
76
-
77
- export function getWalletsByAgentId(agentId: string): AgentWallet[] {
78
- const wallets = loadWallets() as Record<string, AgentWallet>
79
- return Object.values(wallets)
80
- .filter((wallet) => wallet.agentId === agentId)
81
- .sort((a, b) => a.createdAt - b.createdAt)
82
- }
83
-
84
- export function getWalletByAgentId(agentId: string, chain?: WalletChain | null): AgentWallet | null {
85
- const wallets = getWalletsByAgentId(agentId)
86
- if (chain) return wallets.find((wallet) => wallet.chain === chain) ?? null
87
-
88
- const agents = loadAgents()
89
- const agent = agents[agentId]
90
- const activeWalletId = getAgentActiveWalletId(agent)
91
- return wallets.find((wallet) => wallet.id === activeWalletId) ?? wallets[0] ?? null
92
- }
93
-
94
- export function walletApprovalsGloballyEnabled(
95
- settings?: { walletApprovalsEnabled?: boolean | null } | null,
96
- ): boolean {
97
- return settings?.walletApprovalsEnabled !== false
98
- }
99
-
100
- export function walletRequiresApproval(
101
- wallet: Pick<AgentWallet, 'requireApproval'>,
102
- settings?: { walletApprovalsEnabled?: boolean | null } | null,
103
- ): boolean {
104
- return walletApprovalsGloballyEnabled(settings) && wallet.requireApproval !== false
105
- }
106
-
107
- export function createAgentWallet(input: {
108
- agentId: string
109
- chain?: WalletChain | string | null
110
- provider?: WalletChain | string | null
111
- label?: string
112
- requireApproval?: boolean
113
- spendingLimitAtomic?: string | number | null
114
- dailyLimitAtomic?: string | number | null
115
- }): AgentWallet {
116
- const agentId = String(input.agentId || '').trim()
117
- if (!agentId) throw new Error('agentId is required')
118
-
119
- const agent = loadAgent(agentId)
120
- if (!agent) throw new Error('Agent not found')
121
-
122
- const chain = getWalletChainOrDefault(input.chain ?? input.provider, 'solana')
123
- const existing = getWalletByAgentId(agentId, chain)
124
- if (existing) throw new Error(`Agent already has a ${chain} wallet`)
125
- const { publicKey, encryptedPrivateKey } = generateWalletCredentials(chain)
126
- const id = genId()
127
- const now = Date.now()
128
- const wallet: AgentWallet = {
129
- id,
130
- agentId,
131
- chain,
132
- publicKey,
133
- encryptedPrivateKey,
134
- label: typeof input.label === 'string' && input.label.trim() ? input.label.trim() : undefined,
135
- spendingLimitAtomic: normalizeAtomicString(input.spendingLimitAtomic, getWalletDefaultLimitAtomic(chain, 'perTx')),
136
- dailyLimitAtomic: normalizeAtomicString(input.dailyLimitAtomic, getWalletDefaultLimitAtomic(chain, 'daily')),
137
- requireApproval: input.requireApproval !== false,
138
- createdAt: now,
139
- updatedAt: now,
140
- }
141
-
142
- upsertWallet(id, wallet)
143
- clearWalletPortfolioCache(id)
144
-
145
- linkWalletToAgent(agent as any, id, getAgentActiveWalletId(agent as any) == null)
146
- agent.updatedAt = now
147
- upsertAgent(agentId, agent)
148
-
149
- notify('wallets')
150
- notify('agents')
151
-
152
- return wallet
153
- }
154
-
155
- export async function getWalletBalanceAtomic(wallet: AgentWallet): Promise<string> {
156
- return (await getWalletPortfolio(wallet)).balanceAtomic
157
- }
158
-
159
- export async function getWalletPortfolioSnapshot(
160
- wallet: AgentWallet,
161
- options?: GetWalletPortfolioOptions,
162
- ): Promise<WalletPortfolio> {
163
- return getWalletPortfolio(wallet, options)
164
- }
165
-
166
- export function validateWalletSendLimits(params: {
167
- wallet: AgentWallet
168
- amountAtomic: string
169
- transactions?: WalletTransaction[]
170
- now?: number
171
- excludeTransactionId?: string
172
- }): string | null {
173
- const { wallet } = params
174
- const amountAtomic = normalizeAtomicString(params.amountAtomic, '0')
175
- const assetSymbol = getWalletAssetSymbol(wallet.chain)
176
-
177
- if (BigInt(amountAtomic) <= BigInt(0)) {
178
- return 'Amount must be positive'
179
- }
180
-
181
- const perTxLimitAtomic = getWalletLimitAtomic(wallet, 'perTx')
182
- if (BigInt(amountAtomic) > BigInt(perTxLimitAtomic)) {
183
- return `Amount ${formatWalletAmount(wallet.chain, amountAtomic, { maxFractionDigits: 6 })} ${assetSymbol} exceeds per-transaction limit of ${formatWalletAmount(wallet.chain, perTxLimitAtomic, { maxFractionDigits: 6 })} ${assetSymbol}`
184
- }
185
-
186
- const dailyLimitAtomic = getWalletLimitAtomic(wallet, 'daily')
187
- const oneDayAgo = (params.now ?? Date.now()) - 24 * 60 * 60 * 1000
188
- const transactions = params.transactions
189
- ?? Object.values(loadWalletTransactions() as Record<string, WalletTransaction>)
190
- const dailySpentAtomic = transactions
191
- .filter((tx) => tx.walletId === wallet.id)
192
- .filter((tx) => tx.id !== params.excludeTransactionId)
193
- .filter((tx) => tx.type === 'send' && tx.status === 'confirmed' && tx.timestamp > oneDayAgo)
194
- .reduce((sum, tx) => sum + BigInt(getWalletAtomicAmount(tx)), BigInt(0))
195
-
196
- if (dailySpentAtomic + BigInt(amountAtomic) > BigInt(dailyLimitAtomic)) {
197
- return `Daily limit exceeded. Spent ${formatWalletAmount(wallet.chain, dailySpentAtomic.toString(), { maxFractionDigits: 6 })} ${assetSymbol} in the last 24h, limit is ${formatWalletAmount(wallet.chain, dailyLimitAtomic, { maxFractionDigits: 6 })} ${assetSymbol}`
198
- }
199
-
200
- return null
201
- }
202
-
203
- export function isValidWalletAddress(chain: WalletChain, address: string): boolean {
204
- if (chain === 'ethereum') return isValidEthereumAddress(address)
205
- return isValidSolanaAddress(address)
206
- }
207
-
208
- export async function sendWalletNativeAsset(
209
- wallet: AgentWallet,
210
- toAddress: string,
211
- amountAtomic: string,
212
- ): Promise<{ signature: string; feeAtomic?: string }> {
213
- if (wallet.chain === 'ethereum') {
214
- const result = await sendEth(wallet.encryptedPrivateKey, toAddress, amountAtomic)
215
- clearWalletPortfolioCache(wallet.id)
216
- return { signature: result.signature, feeAtomic: result.fee }
217
- }
218
-
219
- const result = await sendSol(wallet.encryptedPrivateKey, toAddress, Number.parseInt(amountAtomic, 10))
220
- clearWalletPortfolioCache(wallet.id)
221
- return {
222
- signature: result.signature,
223
- feeAtomic: String(result.fee),
224
- }
225
- }
@@ -1,75 +0,0 @@
1
- import assert from 'node:assert/strict'
2
- import { describe, it } from 'node:test'
3
-
4
- import type { WalletTransaction } from '@/types'
5
-
6
- import {
7
- filterWalletTransactions,
8
- getWalletTransactionStatusGroup,
9
- matchesWalletTransactionFilter,
10
- matchesWalletTransactionQuery,
11
- } from '@/lib/wallet/wallet-transactions'
12
-
13
- function buildTransaction(overrides: Partial<WalletTransaction> = {}): WalletTransaction {
14
- return {
15
- id: 'tx-1',
16
- walletId: 'wallet-1',
17
- agentId: 'agent-1',
18
- chain: 'ethereum',
19
- type: 'swap',
20
- signature: '0xabc123',
21
- fromAddress: '0xfrom000000000000000000000000000000000001',
22
- toAddress: '0xto0000000000000000000000000000000000002',
23
- amountAtomic: '1000000',
24
- status: 'confirmed',
25
- memo: 'Swapped 1 USDC to ETH',
26
- timestamp: 1,
27
- ...overrides,
28
- }
29
- }
30
-
31
- describe('wallet transaction filters', () => {
32
- it('groups pending and pending_approval together', () => {
33
- assert.equal(getWalletTransactionStatusGroup('pending'), 'pending')
34
- assert.equal(getWalletTransactionStatusGroup('pending_approval'), 'pending')
35
- assert.equal(getWalletTransactionStatusGroup('confirmed'), 'confirmed')
36
- assert.equal(getWalletTransactionStatusGroup('failed'), 'failed')
37
- assert.equal(getWalletTransactionStatusGroup('denied'), 'failed')
38
- })
39
-
40
- it('matches filters by type and status group', () => {
41
- assert.equal(matchesWalletTransactionFilter(buildTransaction({ type: 'swap' }), 'swap'), true)
42
- assert.equal(matchesWalletTransactionFilter(buildTransaction({ type: 'send' }), 'send'), true)
43
- assert.equal(matchesWalletTransactionFilter(buildTransaction({ status: 'pending_approval' }), 'pending'), true)
44
- assert.equal(matchesWalletTransactionFilter(buildTransaction({ status: 'denied' }), 'failed'), true)
45
- })
46
-
47
- it('matches search queries against signature, memo, and addresses', () => {
48
- const tx = buildTransaction()
49
- assert.equal(matchesWalletTransactionQuery(tx, 'usdc'), true)
50
- assert.equal(matchesWalletTransactionQuery(tx, '0xabc123'), true)
51
- assert.equal(matchesWalletTransactionQuery(tx, '0xfrom0000'), true)
52
- assert.equal(matchesWalletTransactionQuery(tx, 'missing-text'), false)
53
- })
54
-
55
- it('filters transactions by combined status/type and search query', () => {
56
- const transactions = [
57
- buildTransaction({ id: 'tx-confirmed', status: 'confirmed', memo: 'Swap USDC to ETH' }),
58
- buildTransaction({ id: 'tx-pending', status: 'pending_approval', memo: 'Awaiting approval' }),
59
- buildTransaction({ id: 'tx-send', type: 'send', status: 'confirmed', memo: 'Send ETH to treasury' }),
60
- ]
61
-
62
- assert.deepEqual(
63
- filterWalletTransactions(transactions, { filter: 'pending' }).map((tx) => tx.id),
64
- ['tx-pending'],
65
- )
66
- assert.deepEqual(
67
- filterWalletTransactions(transactions, { filter: 'send', query: 'treasury' }).map((tx) => tx.id),
68
- ['tx-send'],
69
- )
70
- assert.deepEqual(
71
- filterWalletTransactions(transactions, { filter: 'confirmed', query: 'usdc' }).map((tx) => tx.id),
72
- ['tx-confirmed'],
73
- )
74
- })
75
- })
@@ -1,43 +0,0 @@
1
- import type { WalletTransaction, WalletTransactionStatus } from '@/types'
2
-
3
- export type WalletTransactionFilter = 'all' | 'confirmed' | 'pending' | 'failed' | 'send' | 'receive' | 'swap'
4
-
5
- export function getWalletTransactionStatusGroup(status: WalletTransactionStatus): 'confirmed' | 'pending' | 'failed' {
6
- if (status === 'confirmed') return 'confirmed'
7
- if (status === 'pending' || status === 'pending_approval') return 'pending'
8
- return 'failed'
9
- }
10
-
11
- export function matchesWalletTransactionFilter(tx: WalletTransaction, filter: WalletTransactionFilter): boolean {
12
- if (filter === 'all') return true
13
- if (filter === 'send' || filter === 'receive' || filter === 'swap') return tx.type === filter
14
- return getWalletTransactionStatusGroup(tx.status) === filter
15
- }
16
-
17
- export function matchesWalletTransactionQuery(tx: WalletTransaction, query: string): boolean {
18
- const normalized = query.trim().toLowerCase()
19
- if (!normalized) return true
20
- const haystack = [
21
- tx.id,
22
- tx.signature,
23
- tx.memo || '',
24
- tx.fromAddress,
25
- tx.toAddress,
26
- tx.status,
27
- tx.type,
28
- tx.tokenMint || '',
29
- tx.approvedBy || '',
30
- ]
31
- .join(' ')
32
- .toLowerCase()
33
- return haystack.includes(normalized)
34
- }
35
-
36
- export function filterWalletTransactions(
37
- transactions: WalletTransaction[],
38
- options?: { filter?: WalletTransactionFilter; query?: string },
39
- ): WalletTransaction[] {
40
- const filter = options?.filter || 'all'
41
- const query = options?.query || ''
42
- return transactions.filter((tx) => matchesWalletTransactionFilter(tx, filter) && matchesWalletTransactionQuery(tx, query))
43
- }