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.
- package/README.md +51 -4
- package/dist/anvil.d.ts +34 -0
- package/dist/anvil.d.ts.map +1 -0
- package/dist/anvil.js +254 -0
- package/dist/anvil.js.map +1 -0
- package/dist/client.d.ts +3 -0
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +13 -2
- package/dist/client.js.map +1 -1
- package/dist/index.js +25 -5
- package/dist/index.js.map +1 -1
- package/dist/preview.d.ts +55 -0
- package/dist/preview.d.ts.map +1 -0
- package/dist/preview.js +233 -0
- package/dist/preview.js.map +1 -0
- package/dist/tools/advanced.d.ts +67 -9
- package/dist/tools/advanced.d.ts.map +1 -1
- package/dist/tools/advanced.js +206 -67
- package/dist/tools/advanced.js.map +1 -1
- package/dist/tools/balance.d.ts +5 -5
- package/dist/tools/contract-info.d.ts +9 -9
- package/dist/tools/contract.d.ts +8 -8
- package/dist/tools/contract.d.ts.map +1 -1
- package/dist/tools/contract.js +3 -0
- package/dist/tools/contract.js.map +1 -1
- package/dist/tools/ens.d.ts +15 -15
- package/dist/tools/gas.d.ts +18 -18
- package/dist/tools/logs.d.ts +3 -3
- package/dist/tools/transactions.d.ts +9 -9
- package/dist/tools/transactions.d.ts.map +1 -1
- package/dist/tools/transactions.js +92 -113
- package/dist/tools/transactions.js.map +1 -1
- package/dist/utils.d.ts +13 -0
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +76 -0
- package/dist/utils.js.map +1 -1
- package/dist/wallet-client.d.ts +83 -0
- package/dist/wallet-client.d.ts.map +1 -0
- package/dist/wallet-client.js +357 -0
- package/dist/wallet-client.js.map +1 -0
- package/package.json +9 -8
- package/src/anvil.ts +323 -0
- package/src/client.ts +16 -2
- package/src/index.ts +26 -5
- package/src/preview.ts +320 -0
- package/src/tools/advanced.ts +239 -70
- package/src/tools/contract.ts +3 -0
- package/src/tools/transactions.ts +98 -125
- package/src/utils.ts +95 -0
- package/src/wallet-client.ts +380 -0
- package/dist/wallet-server.d.ts +0 -35
- package/dist/wallet-server.d.ts.map +0 -1
- package/dist/wallet-server.js +0 -232
- package/dist/wallet-server.js.map +0 -1
- package/public/wallet-app.js +0 -677
- package/public/wallet.html +0 -723
- 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
|
+
}
|
package/dist/wallet-server.d.ts
DELETED
|
@@ -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"}
|
package/dist/wallet-server.js
DELETED
|
@@ -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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"}
|