@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,1287 +0,0 @@
1
- import { z } from 'zod'
2
- import { tool, type StructuredToolInterface } from '@langchain/core/tools'
3
- import crypto from 'node:crypto'
4
-
5
- import type { ApprovalCategory, ApprovalRequest, Extension, ExtensionHooks, WalletTransaction } from '@/types'
6
- import { genId } from '@/lib/id'
7
- import {
8
- formatWalletAmount,
9
- getWalletAssetSymbol,
10
- getWalletAtomicAmount,
11
- getWalletChainOrDefault,
12
- getWalletExplorerUrl,
13
- getWalletLimitAtomic,
14
- parseDisplayAmountToAtomic,
15
- } from '@/lib/wallet/wallet'
16
-
17
- import type { ToolBuildContext } from './context'
18
- import { normalizeToolInputArgs } from './normalize-tool-args'
19
- import type { SolanaCluster } from '../solana'
20
- import { isLikelyRetryableSwapError, prepareEvmSwapPlan } from '../evm-swap'
21
- import {
22
- callEthereumContract,
23
- encodeEthereumContractCall,
24
- getEvmNetworkConfig,
25
- sendEthereumTransaction,
26
- signEthereumMessage,
27
- signEthereumTransaction,
28
- signEthereumTypedData,
29
- simulateEthereumTransaction,
30
- } from '../ethereum'
31
- import { registerNativeCapability } from '../native-capabilities'
32
- import { loadAgents, loadWalletTransactions, upsertWalletTransaction } from '../storage'
33
- import {
34
- getSolanaClusterLabel,
35
- normalizeSolanaCluster,
36
- sendSolanaTransaction,
37
- signSolanaMessage,
38
- signSolanaTransaction,
39
- simulateSolanaTransaction,
40
- } from '../solana'
41
- import { TOOL_CAPABILITY } from '../tool-planning'
42
- import { log } from '@/lib/server/logger'
43
-
44
- const TAG = 'wallet'
45
- import { clearWalletPortfolioCache } from '@/lib/server/wallet/wallet-portfolio'
46
- import {
47
- createAgentWallet,
48
- getAgentActiveWalletId,
49
- getWalletByAgentId,
50
- getWalletPortfolioSnapshot,
51
- getWalletsByAgentId,
52
- isValidWalletAddress,
53
- } from '@/lib/server/wallet/wallet-service'
54
- import { errorMessage } from '@/lib/shared-utils'
55
-
56
- const WALLET_TOOL_ACTIONS = [
57
- 'setup',
58
- 'balance',
59
- 'address',
60
- 'send',
61
- 'transactions',
62
- 'call_contract',
63
- 'sign_message',
64
- 'sign_typed_data',
65
- 'encode_contract_call',
66
- 'quote_swap',
67
- 'simulate_transaction',
68
- 'sign_transaction',
69
- 'swap',
70
- 'send_transaction',
71
- ] as const
72
-
73
- type WalletToolAction = (typeof WALLET_TOOL_ACTIONS)[number]
74
-
75
- function trimString(value: unknown): string {
76
- return typeof value === 'string' ? value.trim() : ''
77
- }
78
-
79
- function parseJsonValue<T>(value: unknown, label: string): T | undefined {
80
- if (value === undefined || value === null || value === '') return undefined
81
- if (typeof value === 'string') {
82
- const trimmed = value.trim()
83
- if (!trimmed) return undefined
84
- try {
85
- return JSON.parse(trimmed) as T
86
- } catch (err: unknown) {
87
- throw new Error(`${label} must be valid JSON: ${errorMessage(err)}`)
88
- }
89
- }
90
- return value as T
91
- }
92
-
93
- function parseRecordValue(value: unknown, label: string): Record<string, unknown> | undefined {
94
- const parsed = parseJsonValue<Record<string, unknown>>(value, label)
95
- if (parsed === undefined) return undefined
96
- if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
97
- throw new Error(`${label} must be an object`)
98
- }
99
- return parsed
100
- }
101
-
102
- function parseArrayValue(value: unknown, label: string): unknown[] | undefined {
103
- const parsed = parseJsonValue<unknown[]>(value, label)
104
- if (parsed === undefined) return undefined
105
- if (!Array.isArray(parsed)) throw new Error(`${label} must be an array`)
106
- return parsed
107
- }
108
-
109
- function parseFunctionArgsValue(value: unknown, label: string): unknown[] | Record<string, unknown> | undefined {
110
- const parsed = parseJsonValue<unknown>(value, label)
111
- if (parsed === undefined) return undefined
112
- if (Array.isArray(parsed)) return parsed
113
- if (parsed && typeof parsed === 'object') return parsed as Record<string, unknown>
114
- throw new Error(`${label} must be a JSON array or object`)
115
- }
116
-
117
- function pickFirstDefined(record: Record<string, unknown>, keys: string[]): unknown {
118
- for (const key of keys) {
119
- if (record[key] !== undefined && record[key] !== null && record[key] !== '') return record[key]
120
- }
121
- return undefined
122
- }
123
-
124
- function describeWalletAssetIdentity(asset: {
125
- isNative?: boolean
126
- contractAddress?: string
127
- tokenMint?: string
128
- }): string {
129
- if (asset.isNative) return ''
130
- if (asset.contractAddress) return ` contract \`${asset.contractAddress}\``
131
- if (asset.tokenMint) return ` mint \`${asset.tokenMint}\``
132
- return ''
133
- }
134
-
135
- function hashApprovalPayload(value: string): string {
136
- return crypto.createHash('sha256').update(value).digest('hex')
137
- }
138
-
139
- function isPlainRecord(value: unknown): value is Record<string, unknown> {
140
- return !!value && typeof value === 'object' && !Array.isArray(value)
141
- }
142
-
143
- async function requestWalletApproval(params: {
144
- wallet: { requireApproval: boolean; chain: 'ethereum' | 'solana' }
145
- approved: unknown
146
- approvalId: unknown
147
- category: ApprovalCategory
148
- action: string
149
- title: string
150
- description: string
151
- summary: string
152
- data: Record<string, unknown>
153
- context: { agentId?: string | null; sessionId?: string | null }
154
- }): Promise<string | null> {
155
- void params
156
- return null
157
- }
158
-
159
- function buildEthereumTransaction(normalized: Record<string, unknown>): {
160
- transaction: Record<string, unknown>
161
- summaryParts: string[]
162
- } {
163
- const explicitTx = parseRecordValue(normalized.transaction ?? normalized.transactionJson, 'transaction') || {}
164
- const transaction: Record<string, unknown> = { ...explicitTx }
165
- const summaryParts: string[] = []
166
-
167
- const contractAddress = trimString(normalized.contractAddress)
168
- const toAddress = trimString(normalized.toAddress ?? normalized.to)
169
- if (!transaction.to && (contractAddress || toAddress)) {
170
- transaction.to = contractAddress || toAddress
171
- }
172
-
173
- const data = trimString(normalized.data ?? normalized.calldata)
174
- if (data) transaction.data = data
175
-
176
- const valueAtomic = normalized.valueAtomic ?? normalized.valueWei
177
- if (valueAtomic !== undefined && valueAtomic !== null && valueAtomic !== '') {
178
- transaction.value = typeof valueAtomic === 'string' ? valueAtomic.trim() : valueAtomic
179
- }
180
-
181
- const abi = normalized.abi
182
- const functionName = trimString(normalized.functionName)
183
- if (abi !== undefined && functionName) {
184
- const args = parseFunctionArgsValue(normalized.args ?? normalized.functionArgs, 'args') || []
185
- const encoded = encodeEthereumContractCall(abi, functionName, args)
186
- transaction.data = encoded.data
187
- summaryParts.push(`contract call ${functionName}`)
188
- if (contractAddress) summaryParts.push(`contract ${contractAddress}`)
189
- }
190
-
191
- if (trimString(String(transaction.to || ''))) summaryParts.push(`to ${String(transaction.to)}`)
192
- if (typeof transaction.value === 'string' && transaction.value.trim()) {
193
- summaryParts.push(`value ${transaction.value.trim()} wei`)
194
- }
195
- if (typeof transaction.data === 'string' && transaction.data.trim()) {
196
- summaryParts.push(`data ${String(transaction.data).slice(0, 18)}...`)
197
- }
198
-
199
- return { transaction, summaryParts }
200
- }
201
-
202
- function buildSolanaTransactionSummary(normalized: Record<string, unknown>, cluster: SolanaCluster): string {
203
- const explicitTx = trimString(normalized.transactionBase64)
204
- const signedTx = trimString(normalized.signedTransactionBase64)
205
- const parts = [`cluster ${getSolanaClusterLabel(cluster)}`]
206
- if (signedTx) parts.push('signed transaction')
207
- else if (explicitTx) parts.push('unsigned transaction')
208
- return parts.join(', ')
209
- }
210
-
211
- function buildWalletApprovalResumeInput(approval: ApprovalRequest): Record<string, unknown> | null {
212
- const action = trimString(approval.data.action)
213
- const chain = trimString(approval.data.chain)
214
- const network = trimString(approval.data.network)
215
- if (!action || !chain) return null
216
-
217
- if (approval.category === 'wallet_transfer') {
218
- const toAddress = trimString(approval.data.toAddress)
219
- const amount = trimString(approval.data.amount)
220
- const memo = trimString(approval.data.memo)
221
- if (!toAddress || !amount) return null
222
- return {
223
- action: 'send',
224
- chain,
225
- toAddress,
226
- amount,
227
- ...(memo ? { memo } : {}),
228
- }
229
- }
230
-
231
- if (approval.category !== 'wallet_action') return null
232
-
233
- switch (action) {
234
- case 'send_transaction':
235
- case 'sign_transaction': {
236
- const transaction = isPlainRecord(approval.data.transaction) ? approval.data.transaction : null
237
- const signedTransaction = trimString(approval.data.signedTransaction)
238
- if (!transaction && !signedTransaction) return null
239
- return {
240
- action,
241
- chain,
242
- ...(network ? { network } : {}),
243
- ...(transaction ? { transaction } : {}),
244
- ...(signedTransaction ? { signedTransaction } : {}),
245
- }
246
- }
247
- case 'sign_typed_data': {
248
- const domain = isPlainRecord(approval.data.domain) ? approval.data.domain : null
249
- const types = isPlainRecord(approval.data.types) ? approval.data.types : null
250
- const value = isPlainRecord(approval.data.value) ? approval.data.value : null
251
- if (!domain || !types || !value) return null
252
- return {
253
- action,
254
- chain,
255
- ...(network ? { network } : {}),
256
- domain,
257
- types,
258
- value,
259
- }
260
- }
261
- case 'swap': {
262
- const sellToken = trimString(approval.data.sellToken)
263
- const buyToken = trimString(approval.data.buyToken)
264
- const amountAtomic = trimString(approval.data.amountAtomic)
265
- const recipient = trimString(approval.data.recipient)
266
- const slippageBps = trimString(approval.data.slippageBps)
267
- if (!sellToken || !buyToken || !amountAtomic) return null
268
- return {
269
- action,
270
- chain,
271
- ...(network ? { network } : {}),
272
- sellToken,
273
- buyToken,
274
- sellAmountAtomic: amountAtomic,
275
- ...(recipient ? { recipient } : {}),
276
- ...(slippageBps ? { slippageBps } : {}),
277
- }
278
- }
279
- default:
280
- return null
281
- }
282
- }
283
-
284
- async function executeWalletAction(args: unknown, context: { agentId?: string | null; sessionId?: string | null }) {
285
- const normalized = normalizeToolInputArgs((args ?? {}) as Record<string, unknown>)
286
- const action = trimString(normalized.action) as WalletToolAction | ''
287
- const requestedChainExplicit = normalized.chain !== undefined || normalized.provider !== undefined
288
- const toAddress = trimString(normalized.toAddress ?? normalized.to)
289
- const amount = normalized.amount as string | number | undefined
290
- const amountLegacy = normalized.amountSol as number | undefined
291
- const memo = trimString(normalized.memo)
292
- const limit = typeof normalized.limit === 'number' ? normalized.limit : undefined
293
- const label = trimString(normalized.label) || undefined
294
- const agentId = context.agentId
295
-
296
- if (!agentId) return JSON.stringify({ error: 'No agent ID in context' })
297
-
298
- let requestedChain: 'ethereum' | 'solana'
299
- try {
300
- requestedChain = getWalletChainOrDefault(normalized.chain ?? normalized.provider, 'solana')
301
- } catch (err: unknown) {
302
- return JSON.stringify({ error: errorMessage(err) })
303
- }
304
-
305
- const wallets = getWalletsByAgentId(agentId)
306
- const defaultWallet = getWalletByAgentId(agentId)
307
- const requestedWallet = getWalletByAgentId(agentId, requestedChain)
308
-
309
- if (wallets.length === 0) {
310
- if (action === 'setup') {
311
- try {
312
- const created = createAgentWallet({ agentId, chain: requestedChain, label })
313
- return JSON.stringify({
314
- status: 'wallet_created',
315
- chain: created.chain,
316
- address: created.publicKey,
317
- symbol: getWalletAssetSymbol(created.chain),
318
- message: `Created a ${created.chain} wallet for this agent.`,
319
- actions: [
320
- { id: 'view-wallet', label: 'Open Wallets', href: '/wallets' },
321
- { id: 'view-explorer', label: 'View Address', href: getWalletExplorerUrl(created.chain, 'address', created.publicKey) },
322
- ],
323
- })
324
- } catch (err: unknown) {
325
- return JSON.stringify({ error: errorMessage(err) })
326
- }
327
- }
328
-
329
- return JSON.stringify({
330
- status: 'wallet_not_linked',
331
- message: 'No wallet linked to this agent yet.',
332
- setup: {
333
- tool: 'wallet_tool',
334
- action: 'setup',
335
- body: { chain: requestedChain },
336
- },
337
- })
338
- }
339
-
340
- if (action === 'setup' && requestedChainExplicit && !requestedWallet) {
341
- try {
342
- const created = createAgentWallet({ agentId, chain: requestedChain, label })
343
- return JSON.stringify({
344
- status: 'wallet_created',
345
- chain: created.chain,
346
- address: created.publicKey,
347
- symbol: getWalletAssetSymbol(created.chain),
348
- message: `Created a ${created.chain} wallet for this agent.`,
349
- actions: [
350
- { id: 'view-wallet', label: 'Open Wallets', href: '/wallets' },
351
- { id: 'view-explorer', label: 'View Address', href: getWalletExplorerUrl(created.chain, 'address', created.publicKey) },
352
- ],
353
- })
354
- } catch (err: unknown) {
355
- return JSON.stringify({ error: errorMessage(err) })
356
- }
357
- }
358
-
359
- const wallet = requestedChainExplicit ? requestedWallet : defaultWallet
360
- if (!wallet) {
361
- return JSON.stringify({
362
- status: 'wallet_not_linked',
363
- message: requestedChainExplicit
364
- ? `No ${requestedChain} wallet linked to this agent yet.`
365
- : 'No wallet linked to this agent yet.',
366
- setup: {
367
- tool: 'wallet_tool',
368
- action: 'setup',
369
- body: { chain: requestedChain },
370
- },
371
- })
372
- }
373
-
374
- try {
375
- switch (action) {
376
- case 'setup': {
377
- const activeWalletId = getAgentActiveWalletId(loadAgents()[agentId])
378
- return JSON.stringify({
379
- status: 'wallet_ready',
380
- chain: wallet.chain,
381
- address: wallet.publicKey,
382
- symbol: getWalletAssetSymbol(wallet.chain),
383
- isActive: activeWalletId === wallet.id,
384
- message: requestedChainExplicit
385
- ? `This agent already has a ${wallet.chain} wallet ready.`
386
- : `This agent has ${wallets.length} wallet${wallets.length === 1 ? '' : 's'} linked. The default wallet is ${wallet.chain}.`,
387
- })
388
- }
389
- case 'balance': {
390
- const portfolio = await getWalletPortfolioSnapshot(wallet)
391
- const assetLines = portfolio.assets
392
- .filter((asset) => BigInt(asset.balanceAtomic) > BigInt(0))
393
- .slice(0, 8)
394
- .map((asset) => `- \`${asset.balanceDisplay}\` on \`${asset.networkLabel}\`${asset.isNative ? '' : ` via \`${asset.symbol}\``}${describeWalletAssetIdentity(asset)}`)
395
- .join('\n')
396
- return JSON.stringify({
397
- kind: 'extension-ui',
398
- text: `### Wallet Balance\n\n**Chain:** \`${wallet.chain}\`\n**Address:** \`${wallet.publicKey}\`\n**Primary Balance:** \`${portfolio.balanceDisplay}\`\n**Assets Detected:** \`${portfolio.summary.nonZeroAssets}\`\n${assetLines ? `\n${assetLines}` : '\nNo funded assets detected yet.'}`,
399
- actions: [
400
- { id: 'view-wallet', label: 'View Address', href: getWalletExplorerUrl(wallet.chain, 'address', wallet.publicKey) },
401
- ],
402
- })
403
- }
404
- case 'address':
405
- return JSON.stringify({
406
- address: wallet.publicKey,
407
- chain: wallet.chain,
408
- symbol: getWalletAssetSymbol(wallet.chain),
409
- explorerUrl: getWalletExplorerUrl(wallet.chain, 'address', wallet.publicKey),
410
- })
411
- case 'send': {
412
- const symbol = getWalletAssetSymbol(wallet.chain)
413
- const displayAmount = amount ?? amountLegacy
414
- if (!toAddress) return JSON.stringify({ error: 'toAddress is required for send' })
415
- if (displayAmount === undefined || displayAmount === null || String(displayAmount).trim() === '') {
416
- return JSON.stringify({ error: 'amount must be positive' })
417
- }
418
- if (!isValidWalletAddress(wallet.chain, toAddress)) return JSON.stringify({ error: `Invalid ${wallet.chain} address` })
419
-
420
- let amountAtomic = '0'
421
- let formattedAmount = ''
422
- try {
423
- amountAtomic = parseDisplayAmountToAtomic(displayAmount, wallet.chain === 'ethereum' ? 18 : 9)
424
- if (BigInt(amountAtomic) <= BigInt(0)) return JSON.stringify({ error: 'amount must be positive' })
425
- formattedAmount = formatWalletAmount(wallet.chain, amountAtomic, { minFractionDigits: 4, maxFractionDigits: 6 })
426
- } catch (err: unknown) {
427
- return JSON.stringify({ error: errorMessage(err) })
428
- }
429
-
430
- const perTxLimitAtomic = getWalletLimitAtomic(wallet, 'perTx')
431
- if (BigInt(amountAtomic) > BigInt(perTxLimitAtomic)) {
432
- return JSON.stringify({
433
- error: `Amount ${formattedAmount} ${symbol} exceeds limit of ${formatWalletAmount(wallet.chain, perTxLimitAtomic, { maxFractionDigits: 6 })} ${symbol}`,
434
- })
435
- }
436
-
437
- const approvalResponse = await requestWalletApproval({
438
- wallet,
439
- approved: normalized.approved,
440
- approvalId: normalized.approvalId,
441
- category: 'wallet_transfer',
442
- action,
443
- title: `Send ${formattedAmount} ${symbol}`,
444
- description: `Transfer to ${toAddress}. Memo: ${memo || 'none'}`,
445
- summary: `transfer ${formattedAmount} ${symbol} to ${toAddress}`,
446
- data: {
447
- toAddress,
448
- amount: formattedAmount,
449
- amountDisplay: `${formattedAmount} ${symbol}`,
450
- amountAtomic,
451
- assetSymbol: symbol,
452
- chain: wallet.chain,
453
- memo,
454
- },
455
- context,
456
- })
457
- if (approvalResponse) return approvalResponse
458
-
459
- const baseUrl = process.env.NEXTAUTH_URL || `http://localhost:${process.env.PORT || 3456}`
460
- const res = await fetch(`${baseUrl}/api/wallets/${wallet.id}/send`, {
461
- method: 'POST',
462
- headers: { 'Content-Type': 'application/json', 'X-Access-Key': process.env.ACCESS_KEY || '' },
463
- body: JSON.stringify({ toAddress, amountAtomic, memo }),
464
- })
465
- const data = await res.json()
466
-
467
- if (data.signature) {
468
- return JSON.stringify({
469
- kind: 'extension-ui',
470
- text: `### Transaction Sent!\n\n**Amount:** \`${formattedAmount} ${symbol}\`\n**To:** \`${toAddress}\`\n**Tx:** \`${data.signature.slice(0, 10)}...\``,
471
- actions: [
472
- { id: 'view-tx', label: 'View Transaction', href: getWalletExplorerUrl(wallet.chain, 'transaction', data.signature) },
473
- ],
474
- })
475
- }
476
- return JSON.stringify(data)
477
- }
478
- case 'transactions': {
479
- const allTxs = loadWalletTransactions() as Record<string, WalletTransaction>
480
- const walletTxs = Object.values(allTxs)
481
- .filter((tx) => tx.walletId === wallet.id)
482
- .sort((a, b) => b.timestamp - a.timestamp)
483
- .slice(0, limit ?? 5)
484
-
485
- const symbol = getWalletAssetSymbol(wallet.chain)
486
- const txLines = walletTxs
487
- .map((tx) => `- **${tx.type.toUpperCase()}**: ${formatWalletAmount(tx.chain, getWalletAtomicAmount(tx), { minFractionDigits: 4, maxFractionDigits: 6 })} ${symbol} (${tx.status})`)
488
- .join('\n')
489
-
490
- return JSON.stringify({
491
- kind: 'extension-ui',
492
- text: `### Recent Transactions\n\n${txLines || 'No recent transactions found.'}`,
493
- actions: [
494
- { id: 'view-history', label: 'View Address', href: getWalletExplorerUrl(wallet.chain, 'address', wallet.publicKey) },
495
- ],
496
- })
497
- }
498
- case 'sign_message': {
499
- const network = wallet.chain === 'ethereum'
500
- ? getEvmNetworkConfig(normalized.network).label
501
- : getSolanaClusterLabel(normalized.network)
502
- const summary = `sign message on ${network}`
503
- const approvalResponse = await requestWalletApproval({
504
- wallet,
505
- approved: normalized.approved,
506
- approvalId: normalized.approvalId,
507
- category: 'wallet_action',
508
- action,
509
- title: `Wallet action: sign message`,
510
- description: `Sign a message with the ${wallet.chain} wallet on ${network}.`,
511
- summary,
512
- data: {
513
- network: wallet.chain === 'ethereum'
514
- ? getEvmNetworkConfig(normalized.network).id
515
- : normalizeSolanaCluster(normalized.network),
516
- messageDigest: hashApprovalPayload(JSON.stringify({
517
- message: typeof normalized.message === 'string' ? normalized.message : null,
518
- messageHex: typeof normalized.messageHex === 'string' ? normalized.messageHex : null,
519
- messageBase64: typeof normalized.messageBase64 === 'string' ? normalized.messageBase64 : null,
520
- })),
521
- },
522
- context,
523
- })
524
- if (approvalResponse) return approvalResponse
525
-
526
- if (wallet.chain === 'ethereum') {
527
- const result = await signEthereumMessage(wallet.encryptedPrivateKey, {
528
- message: typeof normalized.message === 'string' ? normalized.message : null,
529
- messageHex: typeof normalized.messageHex === 'string' ? normalized.messageHex : null,
530
- messageBase64: typeof normalized.messageBase64 === 'string' ? normalized.messageBase64 : null,
531
- })
532
- return JSON.stringify({
533
- status: 'signed',
534
- action,
535
- chain: wallet.chain,
536
- network: getEvmNetworkConfig(normalized.network).id,
537
- address: result.address,
538
- signature: result.signature,
539
- })
540
- }
541
-
542
- const result = await signSolanaMessage(wallet.encryptedPrivateKey, {
543
- message: typeof normalized.message === 'string' ? normalized.message : null,
544
- messageHex: typeof normalized.messageHex === 'string' ? normalized.messageHex : null,
545
- messageBase64: typeof normalized.messageBase64 === 'string' ? normalized.messageBase64 : null,
546
- })
547
- return JSON.stringify({
548
- status: 'signed',
549
- action,
550
- chain: wallet.chain,
551
- network: normalizeSolanaCluster(normalized.network),
552
- address: result.publicKey,
553
- signature: result.signature,
554
- })
555
- }
556
- case 'sign_typed_data': {
557
- if (wallet.chain !== 'ethereum') {
558
- return JSON.stringify({ error: 'sign_typed_data is only supported for Ethereum-compatible wallets' })
559
- }
560
-
561
- const typedData = parseRecordValue(normalized.typedData, 'typedData')
562
- const domain = parseRecordValue(normalized.domain, 'domain') || parseRecordValue(typedData?.domain, 'typedData.domain')
563
- const types = parseRecordValue(normalized.types, 'types') || parseRecordValue(typedData?.types, 'typedData.types')
564
- const value = parseRecordValue(normalized.value, 'value')
565
- || parseRecordValue(normalized.messageValue, 'messageValue')
566
- || parseRecordValue(typedData?.message, 'typedData.message')
567
- if (!domain || !types || !value) {
568
- return JSON.stringify({ error: 'domain, types, and value are required for sign_typed_data' })
569
- }
570
-
571
- const network = getEvmNetworkConfig(normalized.network).id
572
- const approvalResponse = await requestWalletApproval({
573
- wallet,
574
- approved: normalized.approved,
575
- approvalId: normalized.approvalId,
576
- category: 'wallet_action',
577
- action,
578
- title: 'Wallet action: sign typed data',
579
- description: `Sign typed data with the Ethereum wallet on ${getEvmNetworkConfig(network).label}.`,
580
- summary: `sign typed data on ${getEvmNetworkConfig(network).label}`,
581
- data: { network, domain, types, value },
582
- context,
583
- })
584
- if (approvalResponse) return approvalResponse
585
-
586
- const result = await signEthereumTypedData(wallet.encryptedPrivateKey, { domain, types, value })
587
- return JSON.stringify({
588
- status: 'signed',
589
- action,
590
- chain: wallet.chain,
591
- network,
592
- address: result.address,
593
- signature: result.signature,
594
- })
595
- }
596
- case 'call_contract': {
597
- if (wallet.chain !== 'ethereum') {
598
- return JSON.stringify({ error: 'call_contract is only supported for Ethereum-compatible wallets' })
599
- }
600
- const abi = normalized.abi
601
- const functionName = trimString(normalized.functionName)
602
- const contractAddress = trimString(normalized.contractAddress)
603
- if (!abi || !functionName || !contractAddress) {
604
- return JSON.stringify({ error: 'contractAddress, abi, and functionName are required for call_contract' })
605
- }
606
- const args = parseFunctionArgsValue(normalized.args ?? normalized.functionArgs, 'args') || []
607
- const result = await callEthereumContract(
608
- wallet.encryptedPrivateKey,
609
- {
610
- contractAddress,
611
- abi,
612
- functionName,
613
- args,
614
- },
615
- {
616
- network: trimString(normalized.network) || undefined,
617
- rpcUrl: trimString(normalized.rpcUrl) || undefined,
618
- },
619
- )
620
- return JSON.stringify({
621
- status: 'called',
622
- action,
623
- chain: wallet.chain,
624
- network: result.network.id,
625
- address: result.address,
626
- contractAddress,
627
- functionName,
628
- fragment: result.fragment,
629
- data: result.data,
630
- rawResult: result.rawResult,
631
- decoded: result.decoded,
632
- namedOutputs: result.namedOutputs,
633
- })
634
- }
635
- case 'encode_contract_call': {
636
- if (wallet.chain !== 'ethereum') {
637
- return JSON.stringify({ error: 'encode_contract_call is only supported for Ethereum-compatible wallets' })
638
- }
639
- const abi = normalized.abi
640
- const functionName = trimString(normalized.functionName)
641
- if (!abi || !functionName) return JSON.stringify({ error: 'abi and functionName are required for encode_contract_call' })
642
- const args = parseFunctionArgsValue(normalized.args ?? normalized.functionArgs, 'args') || []
643
- const encoded = encodeEthereumContractCall(abi, functionName, args)
644
- return JSON.stringify({
645
- status: 'encoded',
646
- action,
647
- chain: wallet.chain,
648
- functionName,
649
- fragment: encoded.fragment,
650
- data: encoded.data,
651
- })
652
- }
653
- case 'quote_swap': {
654
- if (wallet.chain !== 'ethereum') {
655
- return JSON.stringify({ error: 'quote_swap is only supported for Ethereum-compatible wallets' })
656
- }
657
- const network = getEvmNetworkConfig(normalized.network).id
658
- const sellToken = trimString(pickFirstDefined(normalized, ['sellToken', 'fromToken', 'inputToken', 'srcToken', 'tokenIn']))
659
- const buyToken = trimString(pickFirstDefined(normalized, ['buyToken', 'toToken', 'outputToken', 'destToken', 'tokenOut']))
660
- const sellAmountAtomic = pickFirstDefined(normalized, ['sellAmountAtomic', 'amountAtomic', 'srcAmountAtomic'])
661
- const sellAmountDisplay = pickFirstDefined(normalized, ['sellAmount', 'amount', 'sellAmountDisplay', 'srcAmount'])
662
- const recipient = pickFirstDefined(normalized, ['recipient', 'receiver', 'destReceiver'])
663
- const plan = await prepareEvmSwapPlan({
664
- wallet,
665
- network,
666
- sellToken,
667
- buyToken,
668
- sellAmountAtomic,
669
- sellAmountDisplay,
670
- slippageBps: pickFirstDefined(normalized, ['slippageBps', 'slippage', 'slippagePercent']),
671
- recipient,
672
- rpcUrl: trimString(normalized.rpcUrl) || undefined,
673
- })
674
- return JSON.stringify({
675
- status: 'quoted',
676
- action,
677
- chain: wallet.chain,
678
- network,
679
- provider: plan.provider,
680
- routeSummary: plan.routeSummary,
681
- sellToken: plan.sellToken,
682
- buyToken: plan.buyToken,
683
- sellAmountAtomic: plan.sellAmountAtomic,
684
- sellAmountDisplay: plan.sellAmountDisplay,
685
- estimatedBuyAmountAtomic: plan.buyAmountAtomic,
686
- estimatedBuyAmountDisplay: plan.buyAmountDisplay,
687
- slippageBps: plan.slippageBps,
688
- spenderAddress: plan.spenderAddress,
689
- approvalRequired: plan.approvalRequired,
690
- approvalTransaction: plan.approvalTransaction,
691
- swapTransaction: plan.swapTransaction,
692
- recipient: plan.recipient,
693
- })
694
- }
695
- case 'simulate_transaction': {
696
- if (wallet.chain === 'ethereum') {
697
- const network = getEvmNetworkConfig(normalized.network).id
698
- const { transaction, summaryParts } = buildEthereumTransaction(normalized)
699
- if (!transaction.to && !transaction.data) {
700
- return JSON.stringify({ error: 'transaction.to or contract calldata is required for simulate_transaction' })
701
- }
702
- const result = await simulateEthereumTransaction(wallet.encryptedPrivateKey, transaction, {
703
- network,
704
- rpcUrl: trimString(normalized.rpcUrl) || undefined,
705
- })
706
- return JSON.stringify({
707
- status: 'simulated',
708
- action,
709
- chain: wallet.chain,
710
- network: result.network.id,
711
- address: result.address,
712
- estimateGas: result.estimateGas,
713
- callResult: result.callResult,
714
- callError: result.callError,
715
- })
716
- }
717
-
718
- const cluster = normalizeSolanaCluster(normalized.network)
719
- const transactionBase64 = trimString(normalized.transactionBase64)
720
- if (!transactionBase64) return JSON.stringify({ error: 'transactionBase64 is required for Solana simulate_transaction' })
721
- const result = await simulateSolanaTransaction(wallet.encryptedPrivateKey, transactionBase64, {
722
- cluster,
723
- rpcUrl: trimString(normalized.rpcUrl) || undefined,
724
- })
725
- return JSON.stringify({
726
- status: 'simulated',
727
- action,
728
- chain: wallet.chain,
729
- network: cluster,
730
- address: result.publicKey,
731
- signatures: result.signatures,
732
- logs: result.logs,
733
- unitsConsumed: result.unitsConsumed,
734
- err: result.err,
735
- versioned: result.versioned,
736
- })
737
- }
738
- case 'sign_transaction': {
739
- if (wallet.chain === 'ethereum') {
740
- const network = getEvmNetworkConfig(normalized.network).id
741
- const { transaction, summaryParts } = buildEthereumTransaction(normalized)
742
- if (!transaction.to && !transaction.data) {
743
- return JSON.stringify({ error: 'transaction.to or contract calldata is required for sign_transaction' })
744
- }
745
- const approvalResponse = await requestWalletApproval({
746
- wallet,
747
- approved: normalized.approved,
748
- approvalId: normalized.approvalId,
749
- category: 'wallet_action',
750
- action,
751
- title: 'Wallet action: sign transaction',
752
- description: `Sign an Ethereum transaction on ${getEvmNetworkConfig(network).label}.`,
753
- summary: summaryParts.join(', ') || `sign transaction on ${getEvmNetworkConfig(network).label}`,
754
- data: { network, transaction },
755
- context,
756
- })
757
- if (approvalResponse) return approvalResponse
758
-
759
- const result = await signEthereumTransaction(wallet.encryptedPrivateKey, transaction, {
760
- network,
761
- rpcUrl: trimString(normalized.rpcUrl) || undefined,
762
- })
763
- return JSON.stringify({
764
- status: 'signed',
765
- action,
766
- chain: wallet.chain,
767
- network: result.network.id,
768
- address: result.address,
769
- signedTransaction: result.signedTransaction,
770
- transactionHash: result.transactionHash,
771
- })
772
- }
773
-
774
- const transactionBase64 = trimString(normalized.transactionBase64)
775
- if (!transactionBase64) return JSON.stringify({ error: 'transactionBase64 is required for Solana sign_transaction' })
776
- const cluster = normalizeSolanaCluster(normalized.network)
777
- const approvalResponse = await requestWalletApproval({
778
- wallet,
779
- approved: normalized.approved,
780
- approvalId: normalized.approvalId,
781
- category: 'wallet_action',
782
- action,
783
- title: 'Wallet action: sign transaction',
784
- description: `Sign a Solana transaction on ${getSolanaClusterLabel(cluster)}.`,
785
- summary: buildSolanaTransactionSummary(normalized, cluster),
786
- data: {
787
- network: cluster,
788
- transactionFingerprint: hashApprovalPayload(transactionBase64),
789
- },
790
- context,
791
- })
792
- if (approvalResponse) return approvalResponse
793
-
794
- const result = await signSolanaTransaction(wallet.encryptedPrivateKey, transactionBase64)
795
- return JSON.stringify({
796
- status: 'signed',
797
- action,
798
- chain: wallet.chain,
799
- network: cluster,
800
- address: result.publicKey,
801
- signatures: result.signatures,
802
- signedTransactionBase64: result.signedTransactionBase64,
803
- versioned: result.versioned,
804
- })
805
- }
806
- case 'swap': {
807
- if (wallet.chain !== 'ethereum') {
808
- return JSON.stringify({ error: 'swap is only supported for Ethereum-compatible wallets' })
809
- }
810
- const network = getEvmNetworkConfig(normalized.network).id
811
- const sellToken = trimString(pickFirstDefined(normalized, ['sellToken', 'fromToken', 'inputToken', 'srcToken', 'tokenIn']))
812
- const buyToken = trimString(pickFirstDefined(normalized, ['buyToken', 'toToken', 'outputToken', 'destToken', 'tokenOut']))
813
- const sellAmountAtomic = pickFirstDefined(normalized, ['sellAmountAtomic', 'amountAtomic', 'srcAmountAtomic'])
814
- const sellAmountDisplay = pickFirstDefined(normalized, ['sellAmount', 'amount', 'sellAmountDisplay', 'srcAmount'])
815
- const recipient = pickFirstDefined(normalized, ['recipient', 'receiver', 'destReceiver'])
816
- const slippageBps = pickFirstDefined(normalized, ['slippageBps', 'slippage', 'slippagePercent'])
817
- const waitForReceipt = normalized.waitForReceipt !== false
818
- const buildPlan = () => prepareEvmSwapPlan({
819
- wallet,
820
- network,
821
- sellToken,
822
- buyToken,
823
- sellAmountAtomic,
824
- sellAmountDisplay,
825
- slippageBps,
826
- recipient,
827
- rpcUrl: trimString(normalized.rpcUrl) || undefined,
828
- })
829
- let plan = await buildPlan()
830
- const title = `Wallet action: swap ${plan.sellAmountDisplay} to ${plan.buyToken.symbol}`
831
- const description = plan.approvalRequired
832
- ? `Execute a swap on ${plan.network.label}. This will send an exact token approval transaction to ${plan.spenderAddress} and then broadcast the swap transaction.`
833
- : `Execute a swap on ${plan.network.label} and broadcast the resulting transaction.`
834
- const summary = `swap ${plan.sellAmountDisplay} for about ${plan.buyAmountDisplay} on ${plan.network.label} via ${plan.provider}${plan.routeSummary ? ` (${plan.routeSummary})` : ''}`
835
- const approvalResponse = await requestWalletApproval({
836
- wallet,
837
- approved: normalized.approved,
838
- approvalId: normalized.approvalId,
839
- category: 'wallet_action',
840
- action,
841
- title,
842
- description,
843
- summary,
844
- data: {
845
- network,
846
- sellToken: plan.sellToken.address,
847
- buyToken: plan.buyToken.address,
848
- recipient: plan.recipient,
849
- amountAtomic: plan.sellAmountAtomic,
850
- amountDisplay: plan.sellAmountDisplay,
851
- estimatedBuyAmountAtomic: plan.buyAmountAtomic,
852
- estimatedBuyAmountDisplay: plan.buyAmountDisplay,
853
- slippageBps: String(plan.slippageBps),
854
- routeProvider: plan.provider,
855
- routeSummary: plan.routeSummary,
856
- spenderAddress: plan.spenderAddress || '',
857
- },
858
- context,
859
- })
860
- if (approvalResponse) return approvalResponse
861
-
862
- let approvalBroadcast: Awaited<ReturnType<typeof sendEthereumTransaction>> | null = null
863
- if (plan.approvalRequired && plan.approvalTransaction) {
864
- approvalBroadcast = await sendEthereumTransaction(
865
- wallet.encryptedPrivateKey,
866
- {
867
- transaction: plan.approvalTransaction,
868
- waitForReceipt: true,
869
- },
870
- {
871
- network,
872
- rpcUrl: trimString(normalized.rpcUrl) || undefined,
873
- },
874
- )
875
- clearWalletPortfolioCache(wallet.id)
876
- }
877
-
878
- const sendSwap = async (preparedPlan: typeof plan) => sendEthereumTransaction(
879
- wallet.encryptedPrivateKey,
880
- {
881
- transaction: preparedPlan.swapTransaction,
882
- waitForReceipt,
883
- },
884
- {
885
- network,
886
- rpcUrl: trimString(normalized.rpcUrl) || undefined,
887
- },
888
- )
889
-
890
- let swapResult: Awaited<ReturnType<typeof sendEthereumTransaction>>
891
- try {
892
- swapResult = await sendSwap(plan)
893
- } catch (err: unknown) {
894
- if (!isLikelyRetryableSwapError(err)) throw err
895
- plan = await buildPlan()
896
- swapResult = await sendSwap(plan)
897
- }
898
-
899
- clearWalletPortfolioCache(wallet.id)
900
- const txId = genId()
901
- const now = Date.now()
902
- const approvedBy = wallet.requireApproval ? (trimString(normalized.approvalId) ? 'user' : 'auto') : undefined
903
- const txRecord: WalletTransaction = {
904
- id: txId,
905
- walletId: wallet.id,
906
- agentId,
907
- chain: wallet.chain,
908
- type: 'swap',
909
- signature: swapResult.transactionHash,
910
- fromAddress: wallet.publicKey,
911
- toAddress: plan.recipient,
912
- amountAtomic: plan.sellAmountAtomic,
913
- feeAtomic: typeof swapResult.receipt?.fee === 'string' ? swapResult.receipt.fee : undefined,
914
- status: swapResult.receipt ? 'confirmed' : 'pending',
915
- memo: `Swapped ${plan.sellAmountDisplay} to approximately ${plan.buyAmountDisplay} on ${plan.network.label}`,
916
- approvedBy,
917
- tokenMint: plan.sellToken.isNative ? undefined : plan.sellToken.address,
918
- timestamp: now,
919
- }
920
- upsertWalletTransaction(txId, txRecord)
921
-
922
- return JSON.stringify({
923
- status: swapResult.receipt ? 'confirmed' : 'broadcast',
924
- action,
925
- chain: wallet.chain,
926
- network,
927
- provider: plan.provider,
928
- routeSummary: plan.routeSummary,
929
- sellToken: plan.sellToken,
930
- buyToken: plan.buyToken,
931
- sellAmountAtomic: plan.sellAmountAtomic,
932
- sellAmountDisplay: plan.sellAmountDisplay,
933
- estimatedBuyAmountAtomic: plan.buyAmountAtomic,
934
- estimatedBuyAmountDisplay: plan.buyAmountDisplay,
935
- approvalTransactionHash: approvalBroadcast?.transactionHash || undefined,
936
- transactionHash: swapResult.transactionHash,
937
- explorerUrl: swapResult.explorerUrl,
938
- receipt: swapResult.receipt,
939
- recipient: plan.recipient,
940
- })
941
- }
942
- case 'send_transaction': {
943
- if (wallet.chain === 'ethereum') {
944
- const network = getEvmNetworkConfig(normalized.network).id
945
- const signedTransaction = trimString(normalized.signedTransaction)
946
- const { transaction, summaryParts } = buildEthereumTransaction(normalized)
947
- if (!signedTransaction && !transaction.to && !transaction.data) {
948
- return JSON.stringify({ error: 'signedTransaction, transaction.to, or contract calldata is required for send_transaction' })
949
- }
950
-
951
- const approvalResponse = await requestWalletApproval({
952
- wallet,
953
- approved: normalized.approved,
954
- approvalId: normalized.approvalId,
955
- category: 'wallet_action',
956
- action,
957
- title: 'Wallet action: send transaction',
958
- description: `Broadcast an Ethereum transaction on ${getEvmNetworkConfig(network).label}.`,
959
- summary: summaryParts.join(', ') || `broadcast transaction on ${getEvmNetworkConfig(network).label}`,
960
- data: {
961
- network,
962
- transaction,
963
- signedTransactionFingerprint: signedTransaction ? hashApprovalPayload(signedTransaction) : '',
964
- },
965
- context,
966
- })
967
- if (approvalResponse) return approvalResponse
968
-
969
- const result = await sendEthereumTransaction(
970
- wallet.encryptedPrivateKey,
971
- {
972
- transaction: signedTransaction ? undefined : transaction,
973
- signedTransaction: signedTransaction || undefined,
974
- waitForReceipt: normalized.waitForReceipt === true,
975
- },
976
- {
977
- network,
978
- rpcUrl: trimString(normalized.rpcUrl) || undefined,
979
- },
980
- )
981
- clearWalletPortfolioCache(wallet.id)
982
- return JSON.stringify({
983
- status: 'broadcast',
984
- action,
985
- chain: wallet.chain,
986
- network: result.network.id,
987
- address: result.address,
988
- transactionHash: result.transactionHash,
989
- explorerUrl: result.explorerUrl,
990
- receipt: result.receipt,
991
- })
992
- }
993
-
994
- const cluster = normalizeSolanaCluster(normalized.network)
995
- const transactionBase64 = trimString(normalized.transactionBase64)
996
- const signedTransactionBase64 = trimString(normalized.signedTransactionBase64)
997
- if (!transactionBase64 && !signedTransactionBase64) {
998
- return JSON.stringify({ error: 'transactionBase64 or signedTransactionBase64 is required for Solana send_transaction' })
999
- }
1000
- const approvalResponse = await requestWalletApproval({
1001
- wallet,
1002
- approved: normalized.approved,
1003
- approvalId: normalized.approvalId,
1004
- category: 'wallet_action',
1005
- action,
1006
- title: 'Wallet action: send transaction',
1007
- description: `Broadcast a Solana transaction on ${getSolanaClusterLabel(cluster)}.`,
1008
- summary: buildSolanaTransactionSummary(normalized, cluster),
1009
- data: {
1010
- network: cluster,
1011
- transactionFingerprint: transactionBase64 ? hashApprovalPayload(transactionBase64) : '',
1012
- signedTransactionFingerprint: signedTransactionBase64 ? hashApprovalPayload(signedTransactionBase64) : '',
1013
- },
1014
- context,
1015
- })
1016
- if (approvalResponse) return approvalResponse
1017
-
1018
- const result = await sendSolanaTransaction(
1019
- wallet.encryptedPrivateKey,
1020
- {
1021
- transactionBase64: transactionBase64 || undefined,
1022
- signedTransactionBase64: signedTransactionBase64 || undefined,
1023
- waitForConfirmation: normalized.waitForConfirmation !== false,
1024
- },
1025
- {
1026
- cluster,
1027
- rpcUrl: trimString(normalized.rpcUrl) || undefined,
1028
- },
1029
- )
1030
- clearWalletPortfolioCache(wallet.id)
1031
- return JSON.stringify({
1032
- status: 'broadcast',
1033
- action,
1034
- chain: wallet.chain,
1035
- network: cluster,
1036
- address: result.publicKey,
1037
- signature: result.signature,
1038
- explorerUrl: result.explorerUrl,
1039
- versioned: result.versioned,
1040
- })
1041
- }
1042
- default:
1043
- return JSON.stringify({ error: `Unknown action: ${action}` })
1044
- }
1045
- } catch (err: unknown) {
1046
- const msg = errorMessage(err)
1047
- if (msg.includes('429') || msg.toLowerCase().includes('too many requests')) {
1048
- log.warn(TAG, 'Solana RPC rate-limited. Consider using a dedicated RPC endpoint (SOLANA_RPC_URL env var).')
1049
- }
1050
- return JSON.stringify({ error: msg })
1051
- }
1052
- }
1053
-
1054
- const WalletExtension: Extension = {
1055
- name: 'Core Wallet',
1056
- description: 'Manage an agent wallet, inspect assets, sign payloads, and execute generic onchain actions without venue-specific adapters.',
1057
- hooks: {
1058
- getAgentContext: async (ctx) => {
1059
- const agentId = ctx.session.agentId
1060
- if (!agentId) return null
1061
- const wallets = getWalletsByAgentId(agentId)
1062
- if (wallets.length === 0) return null
1063
-
1064
- const agent = loadAgents()[agentId]
1065
- const activeWalletId = getAgentActiveWalletId(agent)
1066
- const onlyWallet = wallets[0]
1067
- const lines = [
1068
- wallets.length === 1 ? '## Your Wallet' : '## Your Wallets',
1069
- wallets.length === 1
1070
- ? `You own a ${onlyWallet?.chain || 'wallet'} wallet. Speak about it in the first person ("my wallet", "my balance"). You can inspect assets, sign messages, sign transactions, and submit generic onchain actions directly.`
1071
- : `You own ${wallets.length} wallets across multiple chains. Speak about them in the first person ("my wallet", "my Ethereum wallet", "my Solana wallet"). If you need a specific wallet, use the \`chain\` parameter on \`wallet_tool\` actions.`,
1072
- ]
1073
-
1074
- for (const entry of wallets) {
1075
- let portfolio = {
1076
- balanceAtomic: '0',
1077
- balanceDisplay: `0.0000 ${getWalletAssetSymbol(entry.chain)}`,
1078
- assets: [] as Array<{ balanceAtomic: string; balanceDisplay?: string; networkLabel: string; symbol: string; isNative?: boolean }>,
1079
- summary: { totalAssets: 0, nonZeroAssets: 0, tokenAssets: 0, networkCount: 0 },
1080
- }
1081
- try {
1082
- portfolio = await getWalletPortfolioSnapshot(entry)
1083
- } catch {
1084
- // best-effort context only
1085
- }
1086
- const symbol = getWalletAssetSymbol(entry.chain)
1087
- const perTxLimit = formatWalletAmount(entry.chain, getWalletLimitAtomic(entry, 'perTx'), { maxFractionDigits: 6 })
1088
- const dailyLimit = formatWalletAmount(entry.chain, getWalletLimitAtomic(entry, 'daily'), { maxFractionDigits: 6 })
1089
- const tokenPreview = portfolio.assets
1090
- .filter((asset) => BigInt(asset.balanceAtomic) > BigInt(0) && asset.isNative !== true)
1091
- .slice(0, 2)
1092
- .map((asset) => `${asset.balanceDisplay || `${asset.symbol} on ${asset.networkLabel}`}${describeWalletAssetIdentity(asset)}`)
1093
- .join(', ')
1094
- lines.push(
1095
- `- ${entry.chain === 'ethereum' ? 'Ethereum' : 'Solana'}${entry.id === activeWalletId ? ' (default)' : ''}: ${portfolio.balanceDisplay} at ${entry.publicKey}`,
1096
- tokenPreview ? ` Tokens: ${tokenPreview}` : ` Assets detected: ${portfolio.summary.nonZeroAssets}`,
1097
- ` Limits: ${perTxLimit} ${symbol}/tx, ${dailyLimit} ${symbol}/day${entry.requireApproval ? ', approval required' : ', auto-execution enabled'}`,
1098
- )
1099
- }
1100
-
1101
- lines.push('Use `wallet_tool` to inspect balances before external-service work. For API-native integrations, pair `wallet_tool` with `http_request`: fetch the docs or API payload first, then sign or broadcast only the specific request you can justify.')
1102
- lines.push('For standard EVM token swaps on supported networks, prefer `wallet_tool` action `swap` or `quote_swap` instead of manually assembling router calldata from public APIs.')
1103
- lines.push('When public quote or aggregator APIs are inconsistent, use read-only onchain primitives instead of endless venue-shopping. `wallet_tool` action `call_contract` can query allowances, quotes, and protocol state directly on EVM networks.')
1104
- lines.push('Treat contract addresses, token mints, router addresses, and spender addresses returned by wallet or HTTP tools as authoritative inputs. Do not invent replacements unless a later tool result proves the earlier value is wrong.')
1105
- lines.push('For EVM work, set `network` to `ethereum`, `arbitrum`, or `base`. For Solana work, set `network` to `mainnet-beta`, `devnet`, or `testnet`.')
1106
- return lines.join('\n')
1107
- },
1108
- getCapabilityDescription: () => 'I can use my wallets through `wallet_tool` for setup, balance checks, address inspection, native transfers, read-only contract calls, message signing, typed-data signing, calldata encoding, generic EVM token swap quotes/execution, transaction simulation, and raw transaction broadcast.',
1109
- getOperatingGuidance: () => [
1110
- 'Use `wallet_tool` to inspect balances and select the right wallet before exploring an external service or trading venue.',
1111
- 'For a standard EVM DEX trade on Ethereum, Arbitrum, or Base, prefer `wallet_tool` action `swap` before trying to invent router calldata yourself.',
1112
- 'Use `wallet_tool` action `quote_swap` when you need a read-only preview of the spender, allowance requirement, route, and executable swap transaction.',
1113
- 'Pair `wallet_tool` with `http_request` for API-native exchange and dApp workflows: discover the docs or payload first, then sign only the exact message or transaction required.',
1114
- 'Use the browser only for rendered UI or interactive page steps after the required wallet action is already understood.',
1115
- 'If multiple public APIs fail, switch to direct read-only contract calls with `wallet_tool` action `call_contract` instead of continuing to shop for venues.',
1116
- 'Treat token addresses, token mints, router addresses, spender addresses, and network identifiers returned by tools as authoritative unless newer tool evidence proves they changed.',
1117
- 'When the next step is a signature or transaction, call the real `wallet_tool` action instead of describing the action only in prose.',
1118
- 'Pass `chain: "ethereum"` or `chain: "solana"` explicitly whenever the task depends on a specific wallet.',
1119
- 'For EVM actions, also pass `network: "ethereum"`, `"arbitrum"`, or `"base"` when the venue or asset lives on a specific network.',
1120
- 'Treat `wallet_tool` as a server-side wallet capability. It does not inject a browser wallet extension or click wallet-connect/signature prompts inside third-party UIs.',
1121
- ],
1122
- getApprovalGuidance: ({ approval, phase, approved }) => {
1123
- const category = approval.category
1124
- if (category !== 'wallet_action' && category !== 'wallet_transfer') return null
1125
-
1126
- const resumeInput = buildWalletApprovalResumeInput(approval)
1127
- if (phase === 'request') {
1128
- return [
1129
- 'When this approval is granted, continue by calling `wallet_tool` for the exact approved action. Do not ask for approval again in prose.',
1130
- 'Do not change the approved amount, route, spender, destination, contract, or network unless a later tool result proves the approved action cannot execute as approved.',
1131
- ]
1132
- }
1133
-
1134
- if (phase === 'connector_reminder') {
1135
- return 'Approving this lets the agent resume the exact blocked wallet action automatically.'
1136
- }
1137
-
1138
- if (approved !== true) {
1139
- return 'Do not retry the rejected wallet action. Inspect state again and request a fresh approval only for a materially different exact action justified by tool evidence.'
1140
- }
1141
-
1142
- const lines = [
1143
- 'Resume immediately with `wallet_tool` using the exact approved action and this approvalId.',
1144
- 'Do not re-quote, re-route, or browse for alternatives before attempting the approved wallet action once.',
1145
- ]
1146
- if (resumeInput) {
1147
- lines.push(`Exact tool input: ${JSON.stringify({ ...resumeInput, approvalId: approval.id })}`)
1148
- } else {
1149
- lines.push('Use the `Approved payload` fields above as the exact wallet action inputs and add the approvalId.')
1150
- }
1151
- return lines
1152
- },
1153
- } as ExtensionHooks,
1154
- ui: {
1155
- sidebarItems: [
1156
- {
1157
- id: 'wallet-dashboard',
1158
- label: 'Wallet',
1159
- href: '/wallets',
1160
- position: 'top',
1161
- },
1162
- ],
1163
- headerWidgets: [
1164
- {
1165
- id: 'wallet-status',
1166
- label: 'Wallet',
1167
- },
1168
- ],
1169
- },
1170
- tools: [
1171
- {
1172
- name: 'wallet_tool',
1173
- description: 'Manage your own crypto wallet, including setup, balances, read-only contract calls, signatures, generic EVM swap execution, transaction simulation, and generic onchain execution.',
1174
- planning: {
1175
- capabilities: [TOOL_CAPABILITY.walletInspect, TOOL_CAPABILITY.walletExecute],
1176
- disciplineGuidance: [
1177
- 'For `wallet_tool`, inspect balances or addresses before attempting an exchange, dApp, or onchain workflow.',
1178
- 'Pass `{"chain":"ethereum"}` or `{"chain":"solana"}` explicitly whenever the task depends on a specific wallet.',
1179
- 'For a standard EVM token trade on Ethereum, Arbitrum, or Base, prefer `{"action":"swap",...}` or `{"action":"quote_swap",...}` instead of manually assembling router calldata.',
1180
- 'For API-native workflows, fetch the docs or request payload first, then use `wallet_tool` only for the exact signature, simulation, or broadcast step.',
1181
- 'If quote or assembly APIs keep failing, stop venue-shopping and use `wallet_tool` action `call_contract` for direct read-only onchain state or quote reads.',
1182
- 'Treat `wallet_tool` as a server-side wallet capability. It does not inject a browser wallet extension or complete third-party wallet-connect prompts for you.',
1183
- ],
1184
- },
1185
- parameters: {
1186
- type: 'object',
1187
- properties: {
1188
- action: { type: 'string', enum: [...WALLET_TOOL_ACTIONS] },
1189
- chain: { type: 'string', enum: ['solana', 'ethereum'], description: 'Selects or creates the wallet on this chain.' },
1190
- provider: { type: 'string', description: 'Alias for chain or wallet ecosystem, for example "ethereum" or "evm".' },
1191
- network: { type: 'string', description: 'Execution network or cluster. EVM: ethereum/arbitrum/base. Solana: mainnet-beta/devnet/testnet.' },
1192
- rpcUrl: { type: 'string', description: 'Optional RPC override for the selected network.' },
1193
- label: { type: 'string' },
1194
- toAddress: { type: 'string' },
1195
- contractAddress: { type: 'string' },
1196
- amount: { type: 'string', description: 'Native asset amount in display units, such as SOL or ETH.' },
1197
- amountSol: { type: 'number' },
1198
- memo: { type: 'string' },
1199
- limit: { type: 'number' },
1200
- message: { type: 'string' },
1201
- messageHex: { type: 'string' },
1202
- messageBase64: { type: 'string' },
1203
- abi: { type: 'string', description: 'ABI JSON array or a single ABI fragment string.' },
1204
- functionName: { type: 'string' },
1205
- args: { type: 'string', description: 'JSON array of function arguments, or a JSON object keyed by ABI input names.' },
1206
- sellToken: { type: 'string', description: 'For EVM swaps: token to sell, as a symbol like USDC/ETH or a token contract address.' },
1207
- buyToken: { type: 'string', description: 'For EVM swaps: token to buy, as a symbol like ETH/WETH/USDC or a token contract address.' },
1208
- sellAmount: { type: 'string', description: 'For EVM swaps: amount to sell in display units.' },
1209
- sellAmountAtomic: { type: 'string', description: 'For EVM swaps: amount to sell in atomic units.' },
1210
- recipient: { type: 'string', description: 'Optional swap recipient. Defaults to the wallet address.' },
1211
- slippageBps: { type: 'string', description: 'Optional max slippage in basis points. Also accepts simple percentage-like inputs such as 1 for 1%.' },
1212
- data: { type: 'string' },
1213
- valueAtomic: { type: 'string', description: 'Native value in atomic units such as wei or lamports.' },
1214
- typedData: { type: 'string', description: 'Typed-data JSON object.' },
1215
- domain: { type: 'string', description: 'Typed-data domain JSON object.' },
1216
- types: { type: 'string', description: 'Typed-data types JSON object.' },
1217
- value: { type: 'string', description: 'Typed-data value JSON object.' },
1218
- transaction: { type: 'string', description: 'Transaction JSON object.' },
1219
- transactionBase64: { type: 'string' },
1220
- signedTransaction: { type: 'string' },
1221
- signedTransactionBase64: { type: 'string' },
1222
- waitForReceipt: { type: 'boolean' },
1223
- waitForConfirmation: { type: 'boolean' },
1224
- approvalId: { type: 'string', description: 'The approval request id that was manually approved by the user for this exact wallet action.' },
1225
- approved: { type: 'boolean', description: 'Set to true only after the user has manually approved the requested wallet action.' },
1226
- },
1227
- required: ['action'],
1228
- },
1229
- execute: async (args, context) => executeWalletAction(args, { agentId: context.session.agentId, sessionId: context.session.id }),
1230
- },
1231
- ],
1232
- }
1233
-
1234
- registerNativeCapability('wallet', WalletExtension)
1235
-
1236
- export function buildWalletTools(bctx: ToolBuildContext): StructuredToolInterface[] {
1237
- if (!bctx.hasExtension('wallet')) return []
1238
- return [
1239
- tool(
1240
- async (args) => executeWalletAction(args, { agentId: bctx.ctx?.agentId, sessionId: bctx.ctx?.sessionId }),
1241
- {
1242
- name: 'wallet_tool',
1243
- description: WalletExtension.tools![0].description,
1244
- schema: z.object({
1245
- action: z.enum(WALLET_TOOL_ACTIONS),
1246
- chain: z.enum(['solana', 'ethereum']).optional().describe('Choose a specific wallet chain when the agent has multiple wallets.'),
1247
- provider: z.string().optional(),
1248
- network: z.string().optional(),
1249
- rpcUrl: z.string().optional(),
1250
- label: z.string().optional(),
1251
- toAddress: z.string().optional(),
1252
- contractAddress: z.string().optional(),
1253
- amount: z.string().optional(),
1254
- amountSol: z.number().optional(),
1255
- memo: z.string().optional(),
1256
- limit: z.number().optional(),
1257
- message: z.string().optional(),
1258
- messageHex: z.string().optional(),
1259
- messageBase64: z.string().optional(),
1260
- abi: z.any().optional(),
1261
- functionName: z.string().optional(),
1262
- args: z.any().optional(),
1263
- sellToken: z.string().optional(),
1264
- buyToken: z.string().optional(),
1265
- sellAmount: z.string().optional(),
1266
- sellAmountAtomic: z.union([z.string(), z.number()]).optional(),
1267
- recipient: z.string().optional(),
1268
- slippageBps: z.union([z.string(), z.number()]).optional(),
1269
- data: z.string().optional(),
1270
- valueAtomic: z.union([z.string(), z.number()]).optional(),
1271
- typedData: z.any().optional(),
1272
- domain: z.any().optional(),
1273
- types: z.any().optional(),
1274
- value: z.any().optional(),
1275
- transaction: z.any().optional(),
1276
- transactionBase64: z.string().optional(),
1277
- signedTransaction: z.string().optional(),
1278
- signedTransactionBase64: z.string().optional(),
1279
- waitForReceipt: z.boolean().optional(),
1280
- waitForConfirmation: z.boolean().optional(),
1281
- approvalId: z.string().optional(),
1282
- approved: z.boolean().optional(),
1283
- }),
1284
- },
1285
- ),
1286
- ]
1287
- }