miladyai 2.0.0-alpha.27

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 (241) hide show
  1. package/dist/_virtual/_rolldown/runtime.js +7 -0
  2. package/dist/actions/emote.js +64 -0
  3. package/dist/actions/restart.js +81 -0
  4. package/dist/actions/send-message.js +152 -0
  5. package/dist/agent-admin-routes.js +82 -0
  6. package/dist/agent-lifecycle-routes.js +79 -0
  7. package/dist/agent-transfer-routes.js +102 -0
  8. package/dist/api/agent-admin-routes.js +82 -0
  9. package/dist/api/agent-lifecycle-routes.js +79 -0
  10. package/dist/api/agent-transfer-routes.js +102 -0
  11. package/dist/api/apps-hyperscape-routes.js +58 -0
  12. package/dist/api/apps-routes.js +114 -0
  13. package/dist/api/auth-routes.js +56 -0
  14. package/dist/api/autonomy-routes.js +44 -0
  15. package/dist/api/bug-report-routes.js +111 -0
  16. package/dist/api/character-routes.js +195 -0
  17. package/dist/api/cloud-routes.js +330 -0
  18. package/dist/api/cloud-status-routes.js +155 -0
  19. package/dist/api/compat-utils.js +111 -0
  20. package/dist/api/database.js +735 -0
  21. package/dist/api/diagnostics-routes.js +205 -0
  22. package/dist/api/drop-service.js +134 -0
  23. package/dist/api/early-logs.js +86 -0
  24. package/dist/api/http-helpers.js +131 -0
  25. package/dist/api/knowledge-routes.js +534 -0
  26. package/dist/api/memory-bounds.js +71 -0
  27. package/dist/api/models-routes.js +28 -0
  28. package/dist/api/og-tracker.js +36 -0
  29. package/dist/api/permissions-routes.js +109 -0
  30. package/dist/api/plugin-validation.js +198 -0
  31. package/dist/api/provider-switch-config.js +41 -0
  32. package/dist/api/registry-routes.js +86 -0
  33. package/dist/api/registry-service.js +164 -0
  34. package/dist/api/sandbox-routes.js +1112 -0
  35. package/dist/api/server.js +7949 -0
  36. package/dist/api/subscription-routes.js +172 -0
  37. package/dist/api/terminal-run-limits.js +24 -0
  38. package/dist/api/training-routes.js +158 -0
  39. package/dist/api/trajectory-routes.js +300 -0
  40. package/dist/api/trigger-routes.js +246 -0
  41. package/dist/api/twitter-verify.js +134 -0
  42. package/dist/api/tx-service.js +108 -0
  43. package/dist/api/wallet-routes.js +266 -0
  44. package/dist/api/wallet.js +568 -0
  45. package/dist/api/whatsapp-routes.js +182 -0
  46. package/dist/api/zip-utils.js +109 -0
  47. package/dist/apps-hyperscape-routes.js +58 -0
  48. package/dist/apps-routes.js +114 -0
  49. package/dist/ascii.js +20 -0
  50. package/dist/auth/anthropic.js +44 -0
  51. package/dist/auth/apply-stealth.js +41 -0
  52. package/dist/auth/claude-code-stealth.js +78 -0
  53. package/dist/auth/credentials.js +156 -0
  54. package/dist/auth/index.js +5 -0
  55. package/dist/auth/openai-codex.js +66 -0
  56. package/dist/auth/types.js +9 -0
  57. package/dist/auth-routes.js +56 -0
  58. package/dist/autonomy-routes.js +44 -0
  59. package/dist/bug-report-routes.js +111 -0
  60. package/dist/build-info.json +6 -0
  61. package/dist/character-routes.js +195 -0
  62. package/dist/cli/argv.js +63 -0
  63. package/dist/cli/banner.js +34 -0
  64. package/dist/cli/cli-name.js +21 -0
  65. package/dist/cli/cli-utils.js +16 -0
  66. package/dist/cli/git-commit.js +78 -0
  67. package/dist/cli/parse-duration.js +15 -0
  68. package/dist/cli/plugins-cli.js +590 -0
  69. package/dist/cli/profile-utils.js +9 -0
  70. package/dist/cli/profile.js +95 -0
  71. package/dist/cli/program/build-program.js +17 -0
  72. package/dist/cli/program/command-registry.js +23 -0
  73. package/dist/cli/program/help.js +47 -0
  74. package/dist/cli/program/preaction.js +33 -0
  75. package/dist/cli/program/register.config.js +106 -0
  76. package/dist/cli/program/register.configure.js +20 -0
  77. package/dist/cli/program/register.dashboard.js +124 -0
  78. package/dist/cli/program/register.models.js +23 -0
  79. package/dist/cli/program/register.setup.js +36 -0
  80. package/dist/cli/program/register.start.js +22 -0
  81. package/dist/cli/program/register.subclis.js +70 -0
  82. package/dist/cli/program/register.tui.js +163 -0
  83. package/dist/cli/program/register.update.js +154 -0
  84. package/dist/cli/program.js +3 -0
  85. package/dist/cli/run-main.js +37 -0
  86. package/dist/cli/version.js +7 -0
  87. package/dist/cloud/validate-url.js +93 -0
  88. package/dist/cloud-routes.js +330 -0
  89. package/dist/cloud-status-routes.js +155 -0
  90. package/dist/compat-utils.js +111 -0
  91. package/dist/config/config.js +69 -0
  92. package/dist/config/env-vars.js +19 -0
  93. package/dist/config/includes.js +121 -0
  94. package/dist/config/object-utils.js +7 -0
  95. package/dist/config/paths.js +38 -0
  96. package/dist/config/plugin-auto-enable.js +231 -0
  97. package/dist/config/schema.js +864 -0
  98. package/dist/config/telegram-custom-commands.js +76 -0
  99. package/dist/config/zod-schema.agent-runtime.js +519 -0
  100. package/dist/config/zod-schema.core.js +538 -0
  101. package/dist/config/zod-schema.hooks.js +103 -0
  102. package/dist/config/zod-schema.js +488 -0
  103. package/dist/config/zod-schema.providers-core.js +785 -0
  104. package/dist/config/zod-schema.session.js +73 -0
  105. package/dist/core-plugins.js +37 -0
  106. package/dist/custom-actions.js +250 -0
  107. package/dist/database.js +735 -0
  108. package/dist/diagnostics/integration-observability.js +57 -0
  109. package/dist/diagnostics-routes.js +205 -0
  110. package/dist/drop-service.js +134 -0
  111. package/dist/early-logs.js +24 -0
  112. package/dist/eliza.js +2061 -0
  113. package/dist/emotes/catalog.js +271 -0
  114. package/dist/entry.js +40 -0
  115. package/dist/hooks/discovery.js +167 -0
  116. package/dist/hooks/eligibility.js +64 -0
  117. package/dist/hooks/index.js +4 -0
  118. package/dist/hooks/loader.js +147 -0
  119. package/dist/hooks/registry.js +55 -0
  120. package/dist/http-helpers.js +131 -0
  121. package/dist/index.js +49 -0
  122. package/dist/knowledge-routes.js +534 -0
  123. package/dist/memory-bounds.js +71 -0
  124. package/dist/milady-plugin.js +90 -0
  125. package/dist/models-routes.js +28 -0
  126. package/dist/onboarding-names.js +78 -0
  127. package/dist/onboarding-presets.js +922 -0
  128. package/dist/package.json +1 -0
  129. package/dist/permissions-routes.js +109 -0
  130. package/dist/plugin-validation.js +107 -0
  131. package/dist/plugins/whatsapp/actions.js +91 -0
  132. package/dist/plugins/whatsapp/index.js +16 -0
  133. package/dist/plugins/whatsapp/service.js +270 -0
  134. package/dist/provider-switch-config.js +41 -0
  135. package/dist/providers/admin-trust.js +46 -0
  136. package/dist/providers/autonomous-state.js +101 -0
  137. package/dist/providers/session-bridge.js +86 -0
  138. package/dist/providers/session-utils.js +36 -0
  139. package/dist/providers/simple-mode.js +50 -0
  140. package/dist/providers/ui-catalog.js +15 -0
  141. package/dist/providers/workspace-provider.js +93 -0
  142. package/dist/providers/workspace.js +348 -0
  143. package/dist/registry-routes.js +86 -0
  144. package/dist/registry-service.js +164 -0
  145. package/dist/restart.js +40 -0
  146. package/dist/runtime/core-plugins.js +37 -0
  147. package/dist/runtime/custom-actions.js +250 -0
  148. package/dist/runtime/eliza.js +2061 -0
  149. package/dist/runtime/embedding-manager-support.js +185 -0
  150. package/dist/runtime/embedding-manager.js +193 -0
  151. package/dist/runtime/embedding-presets.js +54 -0
  152. package/dist/runtime/embedding-state.js +8 -0
  153. package/dist/runtime/milady-plugin.js +90 -0
  154. package/dist/runtime/onboarding-names.js +78 -0
  155. package/dist/runtime/restart.js +40 -0
  156. package/dist/runtime/version.js +7 -0
  157. package/dist/sandbox-routes.js +1112 -0
  158. package/dist/security/audit-log.js +149 -0
  159. package/dist/security/network-policy.js +70 -0
  160. package/dist/server.js +7949 -0
  161. package/dist/services/agent-export.js +559 -0
  162. package/dist/services/app-manager.js +389 -0
  163. package/dist/services/browser-capture.js +86 -0
  164. package/dist/services/fallback-training-service.js +128 -0
  165. package/dist/services/mcp-marketplace.js +134 -0
  166. package/dist/services/plugin-installer.js +396 -0
  167. package/dist/services/plugin-manager-types.js +15 -0
  168. package/dist/services/registry-client-app-meta.js +144 -0
  169. package/dist/services/registry-client-endpoints.js +166 -0
  170. package/dist/services/registry-client-local.js +271 -0
  171. package/dist/services/registry-client-network.js +93 -0
  172. package/dist/services/registry-client-queries.js +70 -0
  173. package/dist/services/registry-client.js +157 -0
  174. package/dist/services/sandbox-engine.js +511 -0
  175. package/dist/services/sandbox-manager.js +297 -0
  176. package/dist/services/self-updater.js +175 -0
  177. package/dist/services/skill-catalog-client.js +119 -0
  178. package/dist/services/skill-marketplace.js +521 -0
  179. package/dist/services/stream-manager.js +236 -0
  180. package/dist/services/update-checker.js +121 -0
  181. package/dist/services/update-notifier.js +29 -0
  182. package/dist/services/version-compat.js +78 -0
  183. package/dist/services/whatsapp-pairing.js +196 -0
  184. package/dist/shared/ui-catalog-prompt.js +728 -0
  185. package/dist/subscription-routes.js +172 -0
  186. package/dist/terminal/links.js +19 -0
  187. package/dist/terminal/palette.js +14 -0
  188. package/dist/terminal/theme.js +25 -0
  189. package/dist/terminal-run-limits.js +24 -0
  190. package/dist/training-routes.js +158 -0
  191. package/dist/trajectory-routes.js +300 -0
  192. package/dist/trigger-routes.js +246 -0
  193. package/dist/triggers/action.js +218 -0
  194. package/dist/triggers/runtime.js +281 -0
  195. package/dist/triggers/scheduling.js +295 -0
  196. package/dist/triggers/types.js +5 -0
  197. package/dist/tui/components/assistant-message.js +76 -0
  198. package/dist/tui/components/chat-editor.js +34 -0
  199. package/dist/tui/components/embeddings-overlay.js +46 -0
  200. package/dist/tui/components/footer.js +60 -0
  201. package/dist/tui/components/index.js +15 -0
  202. package/dist/tui/components/modal-frame.js +45 -0
  203. package/dist/tui/components/modal-style.js +15 -0
  204. package/dist/tui/components/model-selector.js +70 -0
  205. package/dist/tui/components/pinned-chat-layout.js +46 -0
  206. package/dist/tui/components/plugins-endpoints-tab.js +196 -0
  207. package/dist/tui/components/plugins-installed-tab-view.js +69 -0
  208. package/dist/tui/components/plugins-installed-tab.js +319 -0
  209. package/dist/tui/components/plugins-overlay-catalog.js +81 -0
  210. package/dist/tui/components/plugins-overlay-data-api.js +21 -0
  211. package/dist/tui/components/plugins-overlay-data-shared.js +20 -0
  212. package/dist/tui/components/plugins-overlay-data.js +323 -0
  213. package/dist/tui/components/plugins-overlay.js +117 -0
  214. package/dist/tui/components/plugins-store-tab.js +148 -0
  215. package/dist/tui/components/settings-overlay.js +61 -0
  216. package/dist/tui/components/status-bar.js +64 -0
  217. package/dist/tui/components/tool-execution.js +68 -0
  218. package/dist/tui/components/user-message.js +22 -0
  219. package/dist/tui/eliza-tui-bridge.js +606 -0
  220. package/dist/tui/index.js +370 -0
  221. package/dist/tui/modal-presets.js +33 -0
  222. package/dist/tui/model-spec.js +46 -0
  223. package/dist/tui/sse-parser.js +78 -0
  224. package/dist/tui/theme.js +110 -0
  225. package/dist/tui/titlebar-spinner.js +62 -0
  226. package/dist/tui/tui-app.js +311 -0
  227. package/dist/tui/ws-client.js +215 -0
  228. package/dist/twitter-verify.js +134 -0
  229. package/dist/tx-service.js +108 -0
  230. package/dist/utils/exec-safety.js +17 -0
  231. package/dist/utils/globals.js +20 -0
  232. package/dist/utils/milady-root.js +61 -0
  233. package/dist/utils/number-parsing.js +37 -0
  234. package/dist/version-resolver.js +37 -0
  235. package/dist/version.js +7 -0
  236. package/dist/wallet-routes.js +266 -0
  237. package/dist/wallet.js +568 -0
  238. package/dist/whatsapp-routes.js +182 -0
  239. package/dist/zip-utils.js +109 -0
  240. package/milady.mjs +14 -0
  241. package/package.json +111 -0
package/dist/wallet.js ADDED
@@ -0,0 +1,568 @@
1
+ import crypto from "node:crypto";
2
+ import { logger } from "@elizaos/core";
3
+ import { secp256k1 } from "@noble/curves/secp256k1.js";
4
+
5
+ //#region src/api/wallet.ts
6
+ /**
7
+ * Wallet key generation, address derivation, and balance/NFT fetching.
8
+ * Uses Node crypto primitives (no viem/@solana/web3.js dependency).
9
+ * Balance data from Alchemy (EVM) and Helius (Solana) REST APIs.
10
+ */
11
+ const FETCH_TIMEOUT_MS = 15e3;
12
+ const DEFAULT_EVM_CHAINS = [
13
+ {
14
+ name: "Ethereum",
15
+ subdomain: "eth-mainnet",
16
+ chainId: 1,
17
+ nativeSymbol: "ETH"
18
+ },
19
+ {
20
+ name: "Base",
21
+ subdomain: "base-mainnet",
22
+ chainId: 8453,
23
+ nativeSymbol: "ETH"
24
+ },
25
+ {
26
+ name: "Arbitrum",
27
+ subdomain: "arb-mainnet",
28
+ chainId: 42161,
29
+ nativeSymbol: "ETH"
30
+ },
31
+ {
32
+ name: "Optimism",
33
+ subdomain: "opt-mainnet",
34
+ chainId: 10,
35
+ nativeSymbol: "ETH"
36
+ },
37
+ {
38
+ name: "Polygon",
39
+ subdomain: "polygon-mainnet",
40
+ chainId: 137,
41
+ nativeSymbol: "POL"
42
+ }
43
+ ];
44
+ function generateEvmPrivateKey() {
45
+ return `0x${crypto.randomBytes(32).toString("hex")}`;
46
+ }
47
+ function deriveEvmAddress(privateKeyHex) {
48
+ const cleaned = privateKeyHex.startsWith("0x") ? privateKeyHex.slice(2) : privateKeyHex;
49
+ return toChecksumAddress(`0x${keccak256(secp256k1.getPublicKey(Buffer.from(cleaned, "hex"), false).subarray(1)).subarray(12).toString("hex")}`);
50
+ }
51
+ const RC = [
52
+ 1n,
53
+ 32898n,
54
+ 9223372036854808714n,
55
+ 9223372039002292224n,
56
+ 32907n,
57
+ 2147483649n,
58
+ 9223372039002292353n,
59
+ 9223372036854808585n,
60
+ 138n,
61
+ 136n,
62
+ 2147516425n,
63
+ 2147483658n,
64
+ 2147516555n,
65
+ 9223372036854775947n,
66
+ 9223372036854808713n,
67
+ 9223372036854808579n,
68
+ 9223372036854808578n,
69
+ 9223372036854775936n,
70
+ 32778n,
71
+ 9223372039002259466n,
72
+ 9223372039002292353n,
73
+ 9223372036854808704n,
74
+ 2147483649n,
75
+ 9223372039002292232n
76
+ ];
77
+ const ROT = [
78
+ [
79
+ 0,
80
+ 36,
81
+ 3,
82
+ 41,
83
+ 18
84
+ ],
85
+ [
86
+ 1,
87
+ 44,
88
+ 10,
89
+ 45,
90
+ 2
91
+ ],
92
+ [
93
+ 62,
94
+ 6,
95
+ 43,
96
+ 15,
97
+ 61
98
+ ],
99
+ [
100
+ 28,
101
+ 55,
102
+ 25,
103
+ 21,
104
+ 56
105
+ ],
106
+ [
107
+ 27,
108
+ 20,
109
+ 39,
110
+ 8,
111
+ 14
112
+ ]
113
+ ];
114
+ function keccak256(data) {
115
+ const rate = 136;
116
+ const state = Array.from({ length: 5 }, () => Array.from({ length: 5 }, () => 0n));
117
+ const q = rate - data.length % rate;
118
+ const padded = Buffer.alloc(data.length + q);
119
+ padded.set(data);
120
+ padded[data.length] = 1;
121
+ padded[padded.length - 1] |= 128;
122
+ for (let off = 0; off < padded.length; off += rate) {
123
+ for (let i = 0; i < rate / 8; i++) {
124
+ let w = 0n;
125
+ for (let b = 0; b < 8; b++) w |= BigInt(padded[off + i * 8 + b]) << BigInt(b * 8);
126
+ state[i % 5][Math.floor(i / 5)] ^= w;
127
+ }
128
+ keccakF1600(state);
129
+ }
130
+ const out = Buffer.alloc(32);
131
+ for (let i = 0; i < 4; i++) {
132
+ const v = state[i % 5][Math.floor(i / 5)];
133
+ for (let b = 0; b < 8; b++) out[i * 8 + b] = Number(v >> BigInt(b * 8) & 255n);
134
+ }
135
+ return out;
136
+ }
137
+ function keccakF1600(state) {
138
+ const M = (1n << 64n) - 1n;
139
+ const rot = (v, s) => s === 0 ? v : (v << BigInt(s) | v >> BigInt(64 - s)) & M;
140
+ for (let round = 0; round < 24; round++) {
141
+ const c = [];
142
+ for (let x = 0; x < 5; x++) c[x] = state[x][0] ^ state[x][1] ^ state[x][2] ^ state[x][3] ^ state[x][4];
143
+ for (let x = 0; x < 5; x++) {
144
+ const d = c[(x + 4) % 5] ^ rot(c[(x + 1) % 5], 1);
145
+ for (let y = 0; y < 5; y++) state[x][y] = (state[x][y] ^ d) & M;
146
+ }
147
+ const b = Array.from({ length: 5 }, () => Array.from({ length: 5 }, () => 0n));
148
+ for (let x = 0; x < 5; x++) for (let y = 0; y < 5; y++) b[y][(2 * x + 3 * y) % 5] = rot(state[x][y], ROT[x][y]);
149
+ for (let x = 0; x < 5; x++) for (let y = 0; y < 5; y++) state[x][y] = (b[x][y] ^ ~b[(x + 1) % 5][y] & M & b[(x + 2) % 5][y]) & M;
150
+ state[0][0] = (state[0][0] ^ RC[round]) & M;
151
+ }
152
+ }
153
+ function toChecksumAddress(address) {
154
+ const addr = address.toLowerCase().replace("0x", "");
155
+ const hash = keccak256(Buffer.from(addr, "utf8")).toString("hex");
156
+ let out = "0x";
157
+ for (let i = 0; i < 40; i++) out += Number.parseInt(hash[i], 16) >= 8 ? addr[i].toUpperCase() : addr[i];
158
+ return out;
159
+ }
160
+ function generateSolanaKeypair() {
161
+ const { privateKey, publicKey } = crypto.generateKeyPairSync("ed25519");
162
+ const privBytes = privateKey.export({
163
+ type: "pkcs8",
164
+ format: "der"
165
+ });
166
+ const pubBytes = publicKey.export({
167
+ type: "spki",
168
+ format: "der"
169
+ });
170
+ const seed = privBytes.subarray(16, 48);
171
+ const pubRaw = pubBytes.subarray(12, 44);
172
+ return {
173
+ privateKey: base58Encode(Buffer.concat([seed, pubRaw])),
174
+ publicKey: base58Encode(pubRaw)
175
+ };
176
+ }
177
+ function deriveSolanaAddress(privateKeyBase58) {
178
+ const secretBytes = base58Decode(privateKeyBase58);
179
+ if (secretBytes.length === 64) return base58Encode(secretBytes.subarray(32));
180
+ if (secretBytes.length === 32) {
181
+ const keyObj = crypto.createPrivateKey({
182
+ key: Buffer.concat([Buffer.from("302e020100300506032b657004220420", "hex"), secretBytes]),
183
+ format: "der",
184
+ type: "pkcs8"
185
+ });
186
+ return base58Encode(crypto.createPublicKey(keyObj).export({
187
+ type: "spki",
188
+ format: "der"
189
+ }).subarray(12, 44));
190
+ }
191
+ throw new Error(`Invalid Solana secret key length: ${secretBytes.length}`);
192
+ }
193
+ const B58 = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
194
+ function base58Encode(data) {
195
+ let num = BigInt(`0x${Buffer.from(data).toString("hex")}`);
196
+ const chars = [];
197
+ while (num > 0n) {
198
+ chars.unshift(B58[Number(num % 58n)]);
199
+ num /= 58n;
200
+ }
201
+ for (const byte of data) if (byte === 0) chars.unshift("1");
202
+ else break;
203
+ return chars.join("") || "1";
204
+ }
205
+ function base58Decode(str) {
206
+ if (str.length === 0) return Buffer.alloc(0);
207
+ let num = 0n;
208
+ for (const c of str) {
209
+ const i = B58.indexOf(c);
210
+ if (i === -1) throw new Error(`Invalid base58: ${c}`);
211
+ num = num * 58n + BigInt(i);
212
+ }
213
+ const hex = num.toString(16).padStart(2, "0");
214
+ const bytes = Buffer.from(hex.length % 2 ? `0${hex}` : hex, "hex");
215
+ let zeros = 0;
216
+ for (const c of str) if (c === "1") zeros++;
217
+ else break;
218
+ return zeros > 0 ? Buffer.concat([Buffer.alloc(zeros), bytes]) : bytes;
219
+ }
220
+ const HEX_RE = /^[0-9a-fA-F]+$/;
221
+ function validateEvmPrivateKey(key) {
222
+ const cleaned = key.startsWith("0x") ? key.slice(2) : key;
223
+ if (cleaned.length !== 64) return {
224
+ valid: false,
225
+ chain: "evm",
226
+ address: null,
227
+ error: "Must be 64 hex characters"
228
+ };
229
+ if (!HEX_RE.test(cleaned)) return {
230
+ valid: false,
231
+ chain: "evm",
232
+ address: null,
233
+ error: "Invalid hex characters"
234
+ };
235
+ try {
236
+ return {
237
+ valid: true,
238
+ chain: "evm",
239
+ address: deriveEvmAddress(key),
240
+ error: null
241
+ };
242
+ } catch (err) {
243
+ return {
244
+ valid: false,
245
+ chain: "evm",
246
+ address: null,
247
+ error: `Derivation failed: ${err instanceof Error ? err.message : err}`
248
+ };
249
+ }
250
+ }
251
+ function validateSolanaPrivateKey(key) {
252
+ try {
253
+ const bytes = base58Decode(key);
254
+ if (bytes.length !== 64 && bytes.length !== 32) return {
255
+ valid: false,
256
+ chain: "solana",
257
+ address: null,
258
+ error: `Must be 32 or 64 bytes, got ${bytes.length}`
259
+ };
260
+ return {
261
+ valid: true,
262
+ chain: "solana",
263
+ address: deriveSolanaAddress(key),
264
+ error: null
265
+ };
266
+ } catch (err) {
267
+ return {
268
+ valid: false,
269
+ chain: "solana",
270
+ address: null,
271
+ error: `Invalid key: ${err instanceof Error ? err.message : err}`
272
+ };
273
+ }
274
+ }
275
+ /** Auto-detect chain from key format and validate. */
276
+ function validatePrivateKey(key) {
277
+ const trimmed = key.trim();
278
+ if (trimmed.startsWith("0x") || trimmed.length === 64 && HEX_RE.test(trimmed)) return validateEvmPrivateKey(trimmed);
279
+ return validateSolanaPrivateKey(trimmed);
280
+ }
281
+ function generateWalletKeys() {
282
+ const evmPrivateKey = generateEvmPrivateKey();
283
+ const solana = generateSolanaKeypair();
284
+ return {
285
+ evmPrivateKey,
286
+ evmAddress: deriveEvmAddress(evmPrivateKey),
287
+ solanaPrivateKey: solana.privateKey,
288
+ solanaAddress: solana.publicKey
289
+ };
290
+ }
291
+ function generateWalletForChain(chain) {
292
+ if (chain === "evm") {
293
+ const pk = generateEvmPrivateKey();
294
+ return {
295
+ chain,
296
+ address: deriveEvmAddress(pk),
297
+ privateKey: pk
298
+ };
299
+ }
300
+ const sol = generateSolanaKeypair();
301
+ return {
302
+ chain: "solana",
303
+ address: sol.publicKey,
304
+ privateKey: sol.privateKey
305
+ };
306
+ }
307
+ /** Validate key, store in process.env. Caller persists to config if needed. */
308
+ function importWallet(chain, privateKey) {
309
+ const trimmed = privateKey.trim();
310
+ if (chain === "evm") {
311
+ const v = validateEvmPrivateKey(trimmed);
312
+ if (!v.valid) return {
313
+ success: false,
314
+ chain,
315
+ address: null,
316
+ error: v.error
317
+ };
318
+ process.env.EVM_PRIVATE_KEY = trimmed.startsWith("0x") ? trimmed : `0x${trimmed}`;
319
+ logger.info(`[wallet] Imported EVM wallet: ${v.address}`);
320
+ return {
321
+ success: true,
322
+ chain,
323
+ address: v.address,
324
+ error: null
325
+ };
326
+ }
327
+ const v = validateSolanaPrivateKey(trimmed);
328
+ if (!v.valid) return {
329
+ success: false,
330
+ chain,
331
+ address: null,
332
+ error: v.error
333
+ };
334
+ process.env.SOLANA_PRIVATE_KEY = trimmed;
335
+ logger.info(`[wallet] Imported Solana wallet: ${v.address}`);
336
+ return {
337
+ success: true,
338
+ chain,
339
+ address: v.address,
340
+ error: null
341
+ };
342
+ }
343
+ /** Derive addresses from env keys. Works without a running runtime. */
344
+ function getWalletAddresses() {
345
+ let evmAddress = null;
346
+ let solanaAddress = null;
347
+ const evmKey = process.env.EVM_PRIVATE_KEY;
348
+ if (evmKey) try {
349
+ evmAddress = deriveEvmAddress(evmKey);
350
+ } catch (e) {
351
+ logger.warn(`Bad EVM key: ${e}`);
352
+ }
353
+ const solKey = process.env.SOLANA_PRIVATE_KEY;
354
+ if (solKey) try {
355
+ solanaAddress = deriveSolanaAddress(solKey);
356
+ } catch (e) {
357
+ logger.warn(`Bad SOL key: ${e}`);
358
+ }
359
+ return {
360
+ evmAddress,
361
+ solanaAddress
362
+ };
363
+ }
364
+ /** Parse JSON from a fetch response. If the body isn't JSON, throw with the raw text. */
365
+ async function jsonOrThrow(res) {
366
+ const text = await res.text();
367
+ if (!res.ok) throw new Error(text.slice(0, 200) || `HTTP ${res.status}`);
368
+ try {
369
+ return JSON.parse(text);
370
+ } catch {
371
+ throw new Error(text.slice(0, 200) || "Invalid JSON");
372
+ }
373
+ }
374
+ async function fetchEvmBalances(address, alchemyKey) {
375
+ return Promise.all(DEFAULT_EVM_CHAINS.map(async (chain) => {
376
+ const fail = (msg) => ({
377
+ chain: chain.name,
378
+ chainId: chain.chainId,
379
+ nativeBalance: "0",
380
+ nativeSymbol: chain.nativeSymbol,
381
+ nativeValueUsd: "0",
382
+ tokens: [],
383
+ error: msg
384
+ });
385
+ try {
386
+ const url = `https://${chain.subdomain}.g.alchemy.com/v2/${alchemyKey}`;
387
+ const rpc = (body) => ({
388
+ method: "POST",
389
+ headers: { "Content-Type": "application/json" },
390
+ signal: AbortSignal.timeout(FETCH_TIMEOUT_MS),
391
+ body
392
+ });
393
+ const nativeData = await jsonOrThrow(await fetch(url, rpc(JSON.stringify({
394
+ jsonrpc: "2.0",
395
+ id: 1,
396
+ method: "eth_getBalance",
397
+ params: [address, "latest"]
398
+ }))));
399
+ const nativeBalance = formatWei(nativeData.result ? BigInt(nativeData.result) : 0n, 18);
400
+ const nonZero = ((await jsonOrThrow(await fetch(url, rpc(JSON.stringify({
401
+ jsonrpc: "2.0",
402
+ id: 2,
403
+ method: "alchemy_getTokenBalances",
404
+ params: [address, "DEFAULT_TOKENS"]
405
+ }))))).result?.tokenBalances ?? []).filter((t) => t.tokenBalance && t.tokenBalance !== "0x0" && t.tokenBalance !== "0x");
406
+ const tokens = (await Promise.allSettled(nonZero.slice(0, 50).map(async (tok) => {
407
+ const meta = (await jsonOrThrow(await fetch(url, rpc(JSON.stringify({
408
+ jsonrpc: "2.0",
409
+ id: 3,
410
+ method: "alchemy_getTokenMetadata",
411
+ params: [tok.contractAddress]
412
+ }))))).result;
413
+ const decimals = meta?.decimals ?? 18;
414
+ return {
415
+ symbol: meta?.symbol ?? "???",
416
+ name: meta?.name ?? "Unknown Token",
417
+ contractAddress: tok.contractAddress,
418
+ balance: formatWei(BigInt(tok.tokenBalance), decimals),
419
+ decimals,
420
+ valueUsd: "0",
421
+ logoUrl: meta?.logo ?? ""
422
+ };
423
+ }))).filter((r) => r.status === "fulfilled").map((r) => r.value);
424
+ return {
425
+ chain: chain.name,
426
+ chainId: chain.chainId,
427
+ nativeBalance,
428
+ nativeSymbol: chain.nativeSymbol,
429
+ nativeValueUsd: "0",
430
+ tokens,
431
+ error: null
432
+ };
433
+ } catch (err) {
434
+ const msg = err instanceof Error ? err.message : String(err);
435
+ logger.warn(`EVM balance fetch failed for ${chain.name}: ${msg}`);
436
+ return fail(msg);
437
+ }
438
+ }));
439
+ }
440
+ async function fetchEvmNfts(address, alchemyKey) {
441
+ return Promise.all(DEFAULT_EVM_CHAINS.map(async (chain) => {
442
+ try {
443
+ const data = await jsonOrThrow(await fetch(`https://${chain.subdomain}.g.alchemy.com/nft/v3/${alchemyKey}/getNFTsForOwner?owner=${address}&withMetadata=true&pageSize=50`, { signal: AbortSignal.timeout(FETCH_TIMEOUT_MS) }));
444
+ return {
445
+ chain: chain.name,
446
+ nfts: (data.ownedNfts ?? []).map((nft) => ({
447
+ contractAddress: nft.contract?.address ?? "",
448
+ tokenId: nft.tokenId ?? "",
449
+ name: nft.name ?? "Untitled",
450
+ description: (nft.description ?? "").slice(0, 200),
451
+ imageUrl: nft.image?.cachedUrl ?? nft.image?.thumbnailUrl ?? nft.image?.originalUrl ?? "",
452
+ collectionName: nft.contract?.openSeaMetadata?.collectionName ?? nft.contract?.name ?? "",
453
+ tokenType: nft.tokenType ?? "ERC721"
454
+ }))
455
+ };
456
+ } catch (err) {
457
+ logger.warn(`EVM NFT fetch failed for ${chain.name}: ${err}`);
458
+ return {
459
+ chain: chain.name,
460
+ nfts: []
461
+ };
462
+ }
463
+ }));
464
+ }
465
+ async function fetchSolanaBalances(address, heliusKey) {
466
+ const url = `https://mainnet.helius-rpc.com/?api-key=${heliusKey}`;
467
+ const rpc = (body) => ({
468
+ method: "POST",
469
+ headers: { "Content-Type": "application/json" },
470
+ signal: AbortSignal.timeout(FETCH_TIMEOUT_MS),
471
+ body
472
+ });
473
+ let solBalance = "0";
474
+ try {
475
+ const data = await jsonOrThrow(await fetch(url, rpc(JSON.stringify({
476
+ jsonrpc: "2.0",
477
+ id: 1,
478
+ method: "getBalance",
479
+ params: [address]
480
+ }))));
481
+ if (data.error?.message) throw new Error(data.error.message);
482
+ solBalance = ((data.result?.value ?? 0) / 1e9).toFixed(9);
483
+ } catch (err) {
484
+ logger.warn(`SOL balance fetch failed: ${err instanceof Error ? err.message : err}`);
485
+ }
486
+ const tokens = [];
487
+ try {
488
+ const data = await jsonOrThrow(await fetch(url, rpc(JSON.stringify({
489
+ jsonrpc: "2.0",
490
+ id: 2,
491
+ method: "getAssetsByOwner",
492
+ params: {
493
+ ownerAddress: address,
494
+ displayOptions: {
495
+ showFungible: true,
496
+ showNativeBalance: true
497
+ },
498
+ page: 1,
499
+ limit: 100
500
+ }
501
+ }))));
502
+ if (data.error?.message) throw new Error(data.error.message);
503
+ for (const item of data.result?.items ?? []) {
504
+ if (item.interface !== "FungibleToken" && item.interface !== "FungibleAsset") continue;
505
+ const dec = item.token_info?.decimals ?? 0;
506
+ const raw = item.token_info?.balance ?? 0;
507
+ tokens.push({
508
+ symbol: item.token_info?.symbol ?? item.content?.metadata?.symbol ?? "???",
509
+ name: item.content?.metadata?.name ?? "Unknown",
510
+ mint: item.id,
511
+ balance: dec > 0 ? (raw / 10 ** dec).toString() : raw.toString(),
512
+ decimals: dec,
513
+ valueUsd: item.token_info?.price_info?.total_price?.toFixed(2) ?? "0",
514
+ logoUrl: item.content?.links?.image ?? ""
515
+ });
516
+ }
517
+ } catch (err) {
518
+ logger.warn(`Solana token fetch failed: ${err instanceof Error ? err.message : err}`);
519
+ }
520
+ return {
521
+ solBalance,
522
+ solValueUsd: "0",
523
+ tokens
524
+ };
525
+ }
526
+ async function fetchSolanaNfts(address, heliusKey) {
527
+ const url = `https://mainnet.helius-rpc.com/?api-key=${heliusKey}`;
528
+ try {
529
+ const data = await jsonOrThrow(await fetch(url, {
530
+ method: "POST",
531
+ headers: { "Content-Type": "application/json" },
532
+ signal: AbortSignal.timeout(FETCH_TIMEOUT_MS),
533
+ body: JSON.stringify({
534
+ jsonrpc: "2.0",
535
+ id: 1,
536
+ method: "getAssetsByOwner",
537
+ params: {
538
+ ownerAddress: address,
539
+ displayOptions: { showFungible: false },
540
+ page: 1,
541
+ limit: 100
542
+ }
543
+ })
544
+ }));
545
+ if (data.error?.message) throw new Error(data.error.message);
546
+ return (data.result?.items ?? []).filter((i) => i.interface === "V1_NFT" || i.interface === "ProgrammableNFT" || i.interface === "V2_NFT").map((i) => ({
547
+ mint: i.id,
548
+ name: i.content?.metadata?.name ?? "Untitled",
549
+ description: (i.content?.metadata?.description ?? "").slice(0, 200),
550
+ imageUrl: i.content?.links?.image ?? "",
551
+ collectionName: i.grouping?.find((g) => g.group_key === "collection")?.collection_metadata?.name ?? ""
552
+ }));
553
+ } catch (err) {
554
+ logger.warn(`Solana NFT fetch failed: ${err}`);
555
+ return [];
556
+ }
557
+ }
558
+ function formatWei(wei, decimals) {
559
+ if (wei <= 0n || decimals <= 0) return wei <= 0n ? "0" : wei.toString();
560
+ const divisor = 10n ** BigInt(decimals);
561
+ const whole = wei / divisor;
562
+ const rem = wei % divisor;
563
+ if (rem === 0n) return whole.toString();
564
+ return `${whole}.${rem.toString().padStart(decimals, "0").replace(/0+$/, "")}`;
565
+ }
566
+
567
+ //#endregion
568
+ export { deriveEvmAddress, deriveSolanaAddress, fetchEvmBalances, fetchEvmNfts, fetchSolanaBalances, fetchSolanaNfts, generateWalletForChain, generateWalletKeys, getWalletAddresses, importWallet, validatePrivateKey };