web3-tools-mcp 1.3.3 → 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. package/README.md +51 -4
  2. package/dist/anvil.d.ts +34 -0
  3. package/dist/anvil.d.ts.map +1 -0
  4. package/dist/anvil.js +254 -0
  5. package/dist/anvil.js.map +1 -0
  6. package/dist/client.d.ts +3 -0
  7. package/dist/client.d.ts.map +1 -1
  8. package/dist/client.js +13 -2
  9. package/dist/client.js.map +1 -1
  10. package/dist/index.js +25 -5
  11. package/dist/index.js.map +1 -1
  12. package/dist/preview.d.ts +55 -0
  13. package/dist/preview.d.ts.map +1 -0
  14. package/dist/preview.js +233 -0
  15. package/dist/preview.js.map +1 -0
  16. package/dist/tools/advanced.d.ts +67 -9
  17. package/dist/tools/advanced.d.ts.map +1 -1
  18. package/dist/tools/advanced.js +206 -67
  19. package/dist/tools/advanced.js.map +1 -1
  20. package/dist/tools/balance.d.ts +5 -5
  21. package/dist/tools/contract-info.d.ts +9 -9
  22. package/dist/tools/contract.d.ts +8 -8
  23. package/dist/tools/contract.d.ts.map +1 -1
  24. package/dist/tools/contract.js +3 -0
  25. package/dist/tools/contract.js.map +1 -1
  26. package/dist/tools/ens.d.ts +15 -15
  27. package/dist/tools/gas.d.ts +18 -18
  28. package/dist/tools/logs.d.ts +3 -3
  29. package/dist/tools/transactions.d.ts +9 -9
  30. package/dist/tools/transactions.d.ts.map +1 -1
  31. package/dist/tools/transactions.js +92 -113
  32. package/dist/tools/transactions.js.map +1 -1
  33. package/dist/utils.d.ts +13 -0
  34. package/dist/utils.d.ts.map +1 -1
  35. package/dist/utils.js +76 -0
  36. package/dist/utils.js.map +1 -1
  37. package/dist/wallet-client.d.ts +83 -0
  38. package/dist/wallet-client.d.ts.map +1 -0
  39. package/dist/wallet-client.js +357 -0
  40. package/dist/wallet-client.js.map +1 -0
  41. package/package.json +9 -8
  42. package/src/anvil.ts +323 -0
  43. package/src/client.ts +16 -2
  44. package/src/index.ts +26 -5
  45. package/src/preview.ts +320 -0
  46. package/src/tools/advanced.ts +239 -70
  47. package/src/tools/contract.ts +3 -0
  48. package/src/tools/transactions.ts +98 -125
  49. package/src/utils.ts +95 -0
  50. package/src/wallet-client.ts +380 -0
  51. package/dist/wallet-server.d.ts +0 -35
  52. package/dist/wallet-server.d.ts.map +0 -1
  53. package/dist/wallet-server.js +0 -232
  54. package/dist/wallet-server.js.map +0 -1
  55. package/public/wallet-app.js +0 -677
  56. package/public/wallet.html +0 -723
  57. package/src/wallet-server.ts +0 -283
@@ -0,0 +1,380 @@
1
+ import { WebSocket } from 'ws'
2
+ import { execFile } from 'node:child_process'
3
+ import { randomBytes } from 'node:crypto'
4
+ import { mkdirSync, readFileSync, writeFileSync } from 'node:fs'
5
+ import { homedir } from 'node:os'
6
+ import { join } from 'node:path'
7
+ import { WalletRelay, type TransactionRequest, type TransactionResponse } from 'web3-wallet-relay'
8
+
9
+ const REQUEST_TIMEOUT = 300_000
10
+ const SIGNER_WAIT_TIMEOUT = 30_000
11
+ const CONNECT_ATTEMPTS = 8
12
+ const MAX_CONNECT_BACKOFF = 15_000
13
+ const READY_TIMEOUT = 3_000
14
+ // A tab we opened moments ago is already frontmost; raising it again is noise.
15
+ const RECENT_OPEN_MS = 10_000
16
+
17
+ // Browser apps we can raise on macOS, matched against the wallet page's User-Agent. Edge
18
+ // and Opera also say "Chrome", so they are checked first; Chrome says "Safari" too.
19
+ const BROWSER_APPS: ReadonlyArray<[RegExp, string]> = [
20
+ [/Edg\//, 'Microsoft Edge'],
21
+ [/OPR\//, 'Opera'],
22
+ [/Firefox\//, 'Firefox'],
23
+ [/Chrome\//, 'Google Chrome'],
24
+ [/Safari\//, 'Safari']
25
+ ]
26
+
27
+ function browserApp(userAgent?: string): string | undefined {
28
+ if (!userAgent) return undefined
29
+ return BROWSER_APPS.find(([pattern]) => pattern.test(userAgent))?.[1]
30
+ }
31
+
32
+ // Every MCP process on this machine looks for a relay here before starting one, so all
33
+ // sessions end up sharing a single wallet page instead of each opening its own tab.
34
+ const LOCAL_PORTS = [3456, 3457, 3458, 3459, 3460]
35
+
36
+ /**
37
+ * Token shared by every local session, so a process can join a relay another one owns.
38
+ * Stored 0600 because holding it is enough to push transactions at the signer.
39
+ */
40
+ function localToken(): string {
41
+ const dir = join(process.env.XDG_CONFIG_HOME ?? join(homedir(), '.config'), 'web3-tools-mcp')
42
+ const file = join(dir, 'relay-token')
43
+
44
+ try {
45
+ const existing = readFileSync(file, 'utf8').trim()
46
+ if (existing) return existing
47
+ } catch {
48
+ // not created yet
49
+ }
50
+
51
+ const token = randomBytes(16).toString('hex')
52
+ try {
53
+ mkdirSync(dir, { recursive: true, mode: 0o700 })
54
+ writeFileSync(file, token, { mode: 0o600, flag: 'wx' })
55
+ return token
56
+ } catch {
57
+ // Another process created it between our read and write — theirs wins.
58
+ return readFileSync(file, 'utf8').trim()
59
+ }
60
+ }
61
+
62
+ /**
63
+ * Requester side of the wallet relay. Connects to a hosted relay when
64
+ * WALLET_SERVER_URL is set, otherwise starts one in-process and talks to that.
65
+ */
66
+ export class WalletClient {
67
+ private relay: WalletRelay | null = null
68
+ private ws: WebSocket | null = null
69
+ private connecting: Promise<void> | null = null
70
+ private pending = new Map<string, { resolve: (value: unknown) => void; reject: (error: Error) => void }>()
71
+ private signers = 0
72
+ private pages = 0
73
+ private signerAddress: string | undefined
74
+ private pairingUrl: string | undefined
75
+ private pageUrl: string | undefined
76
+ private pageAgent: string | undefined
77
+ private lastOpenedAt = 0
78
+ /** Instance field so tests can shorten the wait instead of sitting out the real one. */
79
+ private signerWaitTimeout = SIGNER_WAIT_TIMEOUT
80
+
81
+ private readonly remoteUrl = process.env.WALLET_SERVER_URL
82
+ private readonly remoteToken = process.env.WALLET_TOKEN
83
+
84
+ constructor(private relayOptions: { port?: number; token?: string } = {}) {}
85
+
86
+ get isRemote(): boolean {
87
+ return Boolean(this.remoteUrl)
88
+ }
89
+
90
+ async connect(): Promise<void> {
91
+ if (this.ws?.readyState === WebSocket.OPEN) return
92
+ if (this.connecting) return this.connecting
93
+
94
+ this.connecting = this.doConnect().finally(() => {
95
+ this.connecting = null
96
+ })
97
+ return this.connecting
98
+ }
99
+
100
+ private async doConnect(): Promise<void> {
101
+ if (this.remoteUrl) return this.connectRemote()
102
+ return this.connectLocal()
103
+ }
104
+
105
+ private async connectRemote(): Promise<void> {
106
+ if (!this.remoteToken) {
107
+ throw new Error('WALLET_SERVER_URL is set but WALLET_TOKEN is missing — both are required to use a hosted wallet relay')
108
+ }
109
+
110
+ const url = String(this.remoteUrl).replace(/^http/, 'ws')
111
+ let lastError: Error | undefined
112
+
113
+ // A hosted relay may be asleep (free tiers spin down) or restarting, and takes about a
114
+ // minute to come back.
115
+ for (let attempt = 0; attempt < CONNECT_ATTEMPTS; attempt++) {
116
+ try {
117
+ await this.openSocket(url, this.remoteToken)
118
+ this.pairingUrl = `${this.remoteUrl}#t=${this.remoteToken}`
119
+ return
120
+ } catch (error) {
121
+ lastError = error as Error
122
+ if (attempt === CONNECT_ATTEMPTS - 1) break
123
+ const delay = Math.min(1000 * 2 ** attempt, MAX_CONNECT_BACKOFF)
124
+ console.error(`[Wallet] Relay unreachable, retrying in ${delay / 1000}s`)
125
+ await new Promise((resolve) => setTimeout(resolve, delay))
126
+ }
127
+ }
128
+
129
+ throw lastError
130
+ }
131
+
132
+ /**
133
+ * Join the relay another local session already owns, or become the owner.
134
+ *
135
+ * ponytail: two sessions starting in the same instant can each end up owning a relay on a
136
+ * different port, splitting sessions across two wallet tabs. Rare enough to live with —
137
+ * the fix would be for the higher port to hand over when a lower one answers.
138
+ */
139
+ private async connectLocal(): Promise<void> {
140
+ const token = this.relayOptions.token ?? localToken()
141
+ const ports = this.relayOptions.port ? [this.relayOptions.port] : LOCAL_PORTS
142
+
143
+ for (const port of ports) {
144
+ try {
145
+ await this.openSocket(`ws://127.0.0.1:${port}`, token)
146
+ this.pairingUrl = `http://127.0.0.1:${port}/#t=${token}`
147
+ return
148
+ } catch {
149
+ // Nothing on this port, or something that isn't our relay — keep looking.
150
+ }
151
+ }
152
+
153
+ this.relay = new WalletRelay({ port: ports[0], token })
154
+ await this.relay.start()
155
+ await this.openSocket(`ws://127.0.0.1:${this.relay.getPort()}`, token)
156
+ this.pairingUrl = this.relay.getUrl()
157
+ }
158
+
159
+ /**
160
+ * Connect and complete the handshake. Resolves only once the relay acks with `ready`, so
161
+ * a port held by something that isn't our relay fails here instead of looking connected.
162
+ */
163
+ private openSocket(url: string, token: string): Promise<void> {
164
+ return new Promise<void>((resolve, reject) => {
165
+ const ws = new WebSocket(url)
166
+ let settled = false
167
+
168
+ const timer = setTimeout(() => fail('no handshake response'), READY_TIMEOUT)
169
+
170
+ function fail(reason: string) {
171
+ if (settled) return
172
+ settled = true
173
+ clearTimeout(timer)
174
+ ws.close()
175
+ reject(new Error(`Wallet relay connection failed: ${reason}`))
176
+ }
177
+
178
+ const onHandshake = (data: Buffer) => {
179
+ let message: { type?: string }
180
+ try {
181
+ message = JSON.parse(data.toString())
182
+ } catch {
183
+ return
184
+ }
185
+ if (message.type !== 'ready' || settled) return
186
+
187
+ settled = true
188
+ clearTimeout(timer)
189
+ ws.off('message', onHandshake)
190
+ this.ws = ws
191
+ resolve()
192
+ }
193
+
194
+ ws.on('message', onHandshake)
195
+ ws.on('message', (data: Buffer) => this.onMessage(data))
196
+ ws.on('open', () => ws.send(JSON.stringify({ token, role: 'requester' })))
197
+ ws.on('error', (error: Error) => {
198
+ fail(error.message)
199
+ ws.close()
200
+ })
201
+ ws.on('close', () => {
202
+ fail('closed during handshake')
203
+ if (this.ws !== ws) return
204
+ this.ws = null
205
+ this.signers = 0
206
+ this.pages = 0
207
+ this.signerAddress = undefined
208
+ this.pageUrl = undefined
209
+ this.pageAgent = undefined
210
+ })
211
+ })
212
+ }
213
+
214
+ private onMessage(data: Buffer) {
215
+ let message: TransactionResponse & {
216
+ type?: string
217
+ signers?: number
218
+ pages?: number
219
+ address?: string
220
+ pageUrl?: string
221
+ pageAgent?: string
222
+ }
223
+ try {
224
+ message = JSON.parse(data.toString())
225
+ } catch {
226
+ return
227
+ }
228
+
229
+ if (message.type === 'status') {
230
+ this.signers = message.signers ?? 0
231
+ this.pages = message.pages ?? 0
232
+ this.signerAddress = message.address
233
+ this.pageUrl = message.pageUrl
234
+ this.pageAgent = message.pageAgent
235
+ return
236
+ }
237
+ if (message.type === 'ready') return
238
+
239
+ const pending = this.pending.get(message.id)
240
+ if (!pending) return
241
+ this.pending.delete(message.id)
242
+
243
+ if (message.success) pending.resolve(message.result)
244
+ else pending.reject(new Error(message.error || 'Transaction failed'))
245
+ }
246
+
247
+ /** Wait for a wallet page to connect, opening the browser first when running locally. */
248
+ async waitForSigner(): Promise<void> {
249
+ await this.connect()
250
+ if (this.signers > 0) return
251
+
252
+ // Only ever open a tab when no wallet page is connected at all. An open page learns
253
+ // about the request over its own socket and raises itself (tab title + desktop
254
+ // notification); asking the OS to open a URL cannot reliably focus an existing tab —
255
+ // Chrome opens another one — and every attempt to do so left a stray tab behind.
256
+ if (!this.isRemote && this.pages === 0) this.openBrowser()
257
+
258
+ const deadline = Date.now() + this.signerWaitTimeout
259
+ while (this.signers === 0 && Date.now() < deadline) {
260
+ await new Promise((resolve) => setTimeout(resolve, 250))
261
+ }
262
+
263
+ if (this.signers === 0) {
264
+ throw new Error(`No wallet connected. Open ${this.getUrl()} and connect your wallet.`)
265
+ }
266
+ }
267
+
268
+ async request(request: TransactionRequest): Promise<unknown> {
269
+ await this.waitForSigner()
270
+ this.focusBrowser()
271
+
272
+ const ws = this.ws
273
+ if (!ws) throw new Error('Wallet relay disconnected')
274
+
275
+ return new Promise((resolve, reject) => {
276
+ this.pending.set(request.id, { resolve, reject })
277
+ ws.send(JSON.stringify(request))
278
+
279
+ setTimeout(() => {
280
+ if (this.pending.delete(request.id)) reject(new Error('Transaction request timed out'))
281
+ }, REQUEST_TIMEOUT)
282
+ })
283
+ }
284
+
285
+ isConnected(): boolean {
286
+ return this.signers > 0
287
+ }
288
+
289
+ getAddress(): string | undefined {
290
+ return this.signerAddress
291
+ }
292
+
293
+ /** URL of the tab currently holding the wallet, so a caller can point the user at it. */
294
+ getPageUrl(): string | undefined {
295
+ return this.pageUrl
296
+ }
297
+
298
+ /**
299
+ * Pairing URL of the relay actually in use — set once the handshake succeeds. Before that
300
+ * we still hand out a tokened URL: a link without one loads a page that cannot pair.
301
+ */
302
+ getUrl(): string {
303
+ if (this.pairingUrl) return this.pairingUrl
304
+ if (this.remoteUrl) return `${this.remoteUrl}#t=${this.remoteToken ?? ''}`
305
+ return `http://127.0.0.1:${LOCAL_PORTS[0]}/#t=${this.relayOptions.token ?? localToken()}`
306
+ }
307
+
308
+ openBrowser() {
309
+ if (this.isRemote) return
310
+ this.lastOpenedAt = Date.now()
311
+ this.open(this.getUrl(), (url) => console.error(`[Wallet] Could not open a browser — visit ${url}`))
312
+ }
313
+
314
+ /**
315
+ * Raise the already-open wallet tab. Deliberately drops the `#t=` fragment: the tab has
316
+ * stripped it from its own address bar, and asking for a URL it doesn't currently show
317
+ * makes the browser navigate — a reload would drop the request we are about to send.
318
+ */
319
+ /**
320
+ * Bring the browser holding the wallet page forward.
321
+ *
322
+ * Activates the application rather than opening its URL: measured on macOS, `open <url>`
323
+ * loads the page again in a NEW tab even when an open tab has exactly that URL, which is
324
+ * where every stray tab came from. Activating an app cannot create one. The page itself
325
+ * flags which tab wants attention, via its title and a desktop notification.
326
+ */
327
+ focusBrowser() {
328
+ if (this.isRemote || process.platform !== 'darwin') return
329
+ if (Date.now() - this.lastOpenedAt < RECENT_OPEN_MS) return
330
+
331
+ const app = browserApp(this.pageAgent)
332
+ if (app) this.activateApp(app)
333
+ }
334
+
335
+ /** Raise an app, but only one already running: launching a browser the user does not use
336
+ * would be worse than doing nothing (Brave and Arc both report themselves as Chrome). */
337
+ private activateApp(app: string) {
338
+ execFile('pgrep', ['-x', app], (notRunning) => {
339
+ if (notRunning) return
340
+ execFile('open', ['-a', app], () => {})
341
+ })
342
+ }
343
+
344
+ /**
345
+ * Hand a URL to the OS. Runs the opener directly rather than through a shell, and only
346
+ * for http(s): the page reports its own URL, so this value is not fully ours to trust.
347
+ */
348
+ private open(url: string, onError?: (url: string) => void) {
349
+ try {
350
+ const { protocol } = new URL(url)
351
+ if (protocol !== 'http:' && protocol !== 'https:') return
352
+ } catch {
353
+ return
354
+ }
355
+
356
+ const [command, args] =
357
+ process.platform === 'darwin'
358
+ ? ['open', [url]]
359
+ : process.platform === 'win32'
360
+ ? ['cmd', ['/c', 'start', '', url]]
361
+ : ['xdg-open', [url]]
362
+
363
+ execFile(command as string, args as string[], (error) => {
364
+ if (error) onError?.(url)
365
+ })
366
+ }
367
+
368
+ async stop(): Promise<void> {
369
+ this.ws?.close()
370
+ await this.relay?.stop()
371
+ this.relay = null
372
+ }
373
+ }
374
+
375
+ let client: WalletClient | null = null
376
+
377
+ export function getWalletClient(): WalletClient {
378
+ if (!client) client = new WalletClient()
379
+ return client
380
+ }
@@ -1,35 +0,0 @@
1
- export interface TransactionRequest {
2
- id: string;
3
- type: 'send_transaction' | 'sign_message' | 'sign_typed_data';
4
- chain: string;
5
- data: unknown;
6
- }
7
- export interface TransactionResponse {
8
- id: string;
9
- success: boolean;
10
- result?: unknown;
11
- error?: string;
12
- }
13
- export declare class WalletServer {
14
- private app;
15
- private httpServer;
16
- private wss;
17
- private clients;
18
- private pendingRequests;
19
- private port;
20
- private isStarted;
21
- constructor(port?: number);
22
- private setupExpress;
23
- private setupWebSocket;
24
- start(): Promise<void>;
25
- private tryListen;
26
- private startWithRetry;
27
- stop(): Promise<void>;
28
- openBrowser(): void;
29
- sendTransaction(request: TransactionRequest): Promise<unknown>;
30
- isConnected(): boolean;
31
- getPort(): number;
32
- }
33
- export declare function getWalletServer(): WalletServer;
34
- export declare function startWalletServer(): Promise<WalletServer>;
35
- //# sourceMappingURL=wallet-server.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"wallet-server.d.ts","sourceRoot":"","sources":["../src/wallet-server.ts"],"names":[],"mappings":"AAWA,MAAM,WAAW,kBAAkB;IACjC,EAAE,EAAE,MAAM,CAAA;IACV,IAAI,EAAE,kBAAkB,GAAG,cAAc,GAAG,iBAAiB,CAAA;IAC7D,KAAK,EAAE,MAAM,CAAA;IACb,IAAI,EAAE,OAAO,CAAA;CACd;AAED,MAAM,WAAW,mBAAmB;IAClC,EAAE,EAAE,MAAM,CAAA;IACV,OAAO,EAAE,OAAO,CAAA;IAChB,MAAM,CAAC,EAAE,OAAO,CAAA;IAChB,KAAK,CAAC,EAAE,MAAM,CAAA;CACf;AAED,qBAAa,YAAY;IACvB,OAAO,CAAC,GAAG,CAAqB;IAChC,OAAO,CAAC,UAAU,CAAiC;IACnD,OAAO,CAAC,GAAG,CAAiB;IAC5B,OAAO,CAAC,OAAO,CAA4B;IAC3C,OAAO,CAAC,eAAe,CAGT;IACd,OAAO,CAAC,IAAI,CAAQ;IACpB,OAAO,CAAC,SAAS,CAAQ;gBAEb,IAAI,SAAO;IAUvB,OAAO,CAAC,YAAY;IAkBpB,OAAO,CAAC,cAAc;IAqChB,KAAK;IAQX,OAAO,CAAC,SAAS;YA6BH,cAAc;IAgCtB,IAAI;IAaV,WAAW;IAuBL,eAAe,CAAC,OAAO,EAAE,kBAAkB,GAAG,OAAO,CAAC,OAAO,CAAC;IAoDpE,WAAW,IAAI,OAAO;IAItB,OAAO,IAAI,MAAM;CAGlB;AAKD,wBAAgB,eAAe,IAAI,YAAY,CAK9C;AAED,wBAAsB,iBAAiB,IAAI,OAAO,CAAC,YAAY,CAAC,CAI/D"}
@@ -1,232 +0,0 @@
1
- import express from 'express';
2
- import cors from 'cors';
3
- import { WebSocketServer, WebSocket } from 'ws';
4
- import { createServer } from 'http';
5
- import { fileURLToPath } from 'url';
6
- import { dirname, join } from 'path';
7
- import { exec } from 'child_process';
8
- const __filename = fileURLToPath(import.meta.url);
9
- const __dirname = dirname(__filename);
10
- export class WalletServer {
11
- app;
12
- httpServer;
13
- wss;
14
- clients = new Set();
15
- pendingRequests = new Map();
16
- port;
17
- isStarted = false;
18
- constructor(port = 3456) {
19
- this.port = port;
20
- this.app = express();
21
- this.httpServer = createServer(this.app);
22
- this.wss = new WebSocketServer({ server: this.httpServer });
23
- this.setupExpress();
24
- this.setupWebSocket();
25
- }
26
- setupExpress() {
27
- this.app.use(cors());
28
- this.app.use(express.json());
29
- this.app.use(express.static(join(__dirname, '..', 'public')));
30
- this.app.get('/', (_req, res) => {
31
- res.sendFile(join(__dirname, '..', 'public', 'wallet.html'));
32
- });
33
- this.app.get('/health', (_req, res) => {
34
- res.json({
35
- status: 'ok',
36
- clients: this.clients.size,
37
- pendingRequests: this.pendingRequests.size
38
- });
39
- });
40
- }
41
- setupWebSocket() {
42
- this.wss.on('connection', (ws) => {
43
- console.error('[Wallet Server] Client connected');
44
- this.clients.add(ws);
45
- ws.on('message', (data) => {
46
- try {
47
- const response = JSON.parse(data.toString());
48
- console.error('[Wallet Server] Received response:', response.id);
49
- console.error('[Wallet Server] Response data:', JSON.stringify(response));
50
- const pending = this.pendingRequests.get(response.id);
51
- if (pending) {
52
- if (response.success) {
53
- pending.resolve(response.result);
54
- }
55
- else {
56
- pending.reject(new Error(response.error || 'Transaction failed'));
57
- }
58
- this.pendingRequests.delete(response.id);
59
- }
60
- }
61
- catch (error) {
62
- console.error('[Wallet Server] Error parsing message:', error);
63
- }
64
- });
65
- ws.on('close', () => {
66
- console.error('[Wallet Server] Client disconnected');
67
- this.clients.delete(ws);
68
- });
69
- ws.on('error', (error) => {
70
- console.error('[Wallet Server] WebSocket error:', error);
71
- this.clients.delete(ws);
72
- });
73
- });
74
- }
75
- async start() {
76
- if (this.isStarted) {
77
- return;
78
- }
79
- return this.startWithRetry();
80
- }
81
- tryListen(port) {
82
- return new Promise((resolve, reject) => {
83
- // Create a fresh server for this attempt
84
- const tempServer = createServer(this.app);
85
- const cleanup = () => {
86
- tempServer.removeAllListeners();
87
- };
88
- const onError = (error) => {
89
- cleanup();
90
- reject(error);
91
- };
92
- const onListening = () => {
93
- cleanup();
94
- // Success! Replace our server instance with this working one
95
- this.httpServer = tempServer;
96
- this.wss = new WebSocketServer({ server: this.httpServer });
97
- this.setupWebSocket();
98
- resolve();
99
- };
100
- tempServer.once('error', onError);
101
- tempServer.once('listening', onListening);
102
- tempServer.listen(port);
103
- });
104
- }
105
- async startWithRetry(maxAttempts = 10) {
106
- const originalPort = this.port;
107
- for (let attempt = 0; attempt < maxAttempts; attempt++) {
108
- try {
109
- await this.tryListen(this.port);
110
- // Success - server started
111
- this.isStarted = true;
112
- if (this.port !== originalPort) {
113
- console.error(`[Wallet Server] Port ${originalPort} was in use, using port ${this.port} instead`);
114
- }
115
- console.error(`[Wallet Server] Running on http://localhost:${this.port}`);
116
- return;
117
- }
118
- catch (error) {
119
- const errnoError = error;
120
- if (errnoError.code === 'EADDRINUSE') {
121
- console.error(`[Wallet Server] Port ${this.port} in use, trying port ${this.port + 1}...`);
122
- this.port++;
123
- }
124
- else {
125
- throw error;
126
- }
127
- }
128
- }
129
- // If we exhausted all attempts
130
- throw new Error(`Failed to start wallet server: Ports ${originalPort}-${originalPort + maxAttempts - 1} are all in use. ` +
131
- `Please free up a port or specify a different starting port.`);
132
- }
133
- async stop() {
134
- return new Promise((resolve) => {
135
- this.clients.forEach(client => client.close());
136
- this.wss.close(() => {
137
- this.httpServer.close(() => {
138
- this.isStarted = false;
139
- console.error('[Wallet Server] Stopped');
140
- resolve();
141
- });
142
- });
143
- });
144
- }
145
- openBrowser() {
146
- const url = `http://localhost:${this.port}`;
147
- const platform = process.platform;
148
- let command;
149
- if (platform === 'darwin') {
150
- command = `open "${url}"`;
151
- }
152
- else if (platform === 'win32') {
153
- command = `start "${url}"`;
154
- }
155
- else {
156
- command = `xdg-open "${url}"`;
157
- }
158
- exec(command, (error) => {
159
- if (error) {
160
- console.error('[Wallet Server] Failed to open browser:', error);
161
- console.error(`[Wallet Server] Please open manually: ${url}`);
162
- }
163
- else {
164
- console.error(`[Wallet Server] Opened browser at ${url}`);
165
- }
166
- });
167
- }
168
- async sendTransaction(request) {
169
- if (!this.isStarted) {
170
- await this.start();
171
- }
172
- // Always open/focus browser for transaction requests
173
- this.openBrowser();
174
- // Open browser if no clients connected
175
- if (this.clients.size === 0) {
176
- // Wait for client to connect (max 30 seconds)
177
- const timeout = 30000;
178
- const startTime = Date.now();
179
- while (this.clients.size === 0 && Date.now() - startTime < timeout) {
180
- await new Promise(resolve => setTimeout(resolve, 500));
181
- }
182
- if (this.clients.size === 0) {
183
- throw new Error('No wallet connected. Please open the browser and connect your wallet.');
184
- }
185
- }
186
- return new Promise((resolve, reject) => {
187
- this.pendingRequests.set(request.id, { resolve, reject });
188
- // Send to first available connected client only
189
- const message = JSON.stringify(request);
190
- let sent = false;
191
- for (const client of this.clients) {
192
- if (client.readyState === WebSocket.OPEN) {
193
- client.send(message);
194
- sent = true;
195
- break;
196
- }
197
- }
198
- if (!sent) {
199
- this.pendingRequests.delete(request.id);
200
- reject(new Error('No active wallet connection'));
201
- return;
202
- }
203
- // Timeout after 5 minutes
204
- setTimeout(() => {
205
- if (this.pendingRequests.has(request.id)) {
206
- this.pendingRequests.delete(request.id);
207
- reject(new Error('Transaction request timed out'));
208
- }
209
- }, 300000);
210
- });
211
- }
212
- isConnected() {
213
- return this.clients.size > 0;
214
- }
215
- getPort() {
216
- return this.port;
217
- }
218
- }
219
- // Singleton instance
220
- let walletServer = null;
221
- export function getWalletServer() {
222
- if (!walletServer) {
223
- walletServer = new WalletServer();
224
- }
225
- return walletServer;
226
- }
227
- export async function startWalletServer() {
228
- const server = getWalletServer();
229
- await server.start();
230
- return server;
231
- }
232
- //# sourceMappingURL=wallet-server.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"wallet-server.js","sourceRoot":"","sources":["../src/wallet-server.ts"],"names":[],"mappings":"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"}