browser-evm-signer 0.1.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.
@@ -0,0 +1,341 @@
1
+ import { createServer } from "node:http";
2
+ import { statSync } from "node:fs";
3
+ import { readFile } from "node:fs/promises";
4
+ import { getPort } from "./config.js";
5
+ import { pendingStore as defaultPendingStore } from "./pending-store.js";
6
+ // Store test results for e2e browser testing
7
+ const testResults = new Map();
8
+ /**
9
+ * Get the path to the bundled web UI
10
+ */
11
+ function getWebDistPath() {
12
+ const scriptDir = new URL(".", globalThis[Symbol.for("import-meta-ponyfill-esmodule")](import.meta).url).pathname;
13
+ // Try candidate paths in order. Dev build (web/dist) must come before web/
14
+ // because the web/ source directory also exists and would match statSync.
15
+ const candidates = [
16
+ `${scriptDir}../web/dist`, // dev: src/ → web/dist (vite build output)
17
+ `${scriptDir}../dist/web`, // esbuild: dist/ → dist/web
18
+ `${scriptDir}../web`, // dnt: esm/ → package root → web/ (contains built assets)
19
+ ];
20
+ for (const candidate of candidates) {
21
+ try {
22
+ statSync(candidate);
23
+ return candidate;
24
+ }
25
+ catch {
26
+ // try next
27
+ }
28
+ }
29
+ // Fallback — will fail later with a clear error from serveStaticFile
30
+ return candidates[0];
31
+ }
32
+ /**
33
+ * Serve static files from the web dist directory
34
+ */
35
+ async function serveStaticFile(path, webDistPath) {
36
+ // Default to index.html for SPA routing
37
+ let filePath = path === "/" || path === "" ? "/index.html" : path;
38
+ // Remove leading slash and sanitize
39
+ filePath = filePath.replace(/^\/+/, "").replace(/\.\./g, "");
40
+ const fullPath = `${webDistPath}/${filePath}`;
41
+ try {
42
+ const file = new Uint8Array(await readFile(fullPath));
43
+ const contentType = getContentType(filePath);
44
+ return new Response(file, {
45
+ headers: {
46
+ "Content-Type": contentType,
47
+ "Cache-Control": "no-cache",
48
+ },
49
+ });
50
+ }
51
+ catch {
52
+ // For SPA, serve index.html for any unknown path
53
+ if (!filePath.includes(".")) {
54
+ try {
55
+ const indexHtml = new Uint8Array(await readFile(`${webDistPath}/index.html`));
56
+ return new Response(indexHtml, {
57
+ headers: {
58
+ "Content-Type": "text/html",
59
+ "Cache-Control": "no-cache",
60
+ },
61
+ });
62
+ }
63
+ catch {
64
+ return new Response("Web UI not found. Run 'deno task build:web' first.", {
65
+ status: 404,
66
+ });
67
+ }
68
+ }
69
+ return new Response("Not found", { status: 404 });
70
+ }
71
+ }
72
+ /**
73
+ * Get content type from file extension
74
+ */
75
+ function getContentType(path) {
76
+ const ext = path.split(".").pop()?.toLowerCase();
77
+ const types = {
78
+ html: "text/html",
79
+ js: "application/javascript",
80
+ mjs: "application/javascript",
81
+ css: "text/css",
82
+ json: "application/json",
83
+ png: "image/png",
84
+ jpg: "image/jpeg",
85
+ jpeg: "image/jpeg",
86
+ svg: "image/svg+xml",
87
+ ico: "image/x-icon",
88
+ woff: "font/woff",
89
+ woff2: "font/woff2",
90
+ ttf: "font/ttf",
91
+ };
92
+ return types[ext || ""] || "application/octet-stream";
93
+ }
94
+ /**
95
+ * Handle API requests
96
+ */
97
+ function handleApiRequest(pathname, method, body, store) {
98
+ const corsHeaders = {
99
+ "Access-Control-Allow-Origin": "*",
100
+ "Access-Control-Allow-Methods": "GET, POST, OPTIONS",
101
+ "Access-Control-Allow-Headers": "Content-Type",
102
+ };
103
+ // Handle CORS preflight
104
+ if (method === "OPTIONS") {
105
+ return new Response(null, { status: 204, headers: corsHeaders });
106
+ }
107
+ // GET /api/pending/:id - Get pending request details
108
+ const pendingMatch = pathname.match(/^\/api\/pending\/([a-f0-9-]+)$/);
109
+ if (pendingMatch && method === "GET") {
110
+ const id = pendingMatch[1];
111
+ const request = store.get(id);
112
+ if (!request) {
113
+ return new Response(JSON.stringify({ error: "Request not found" }), {
114
+ status: 404,
115
+ headers: { ...corsHeaders, "Content-Type": "application/json" },
116
+ });
117
+ }
118
+ const response = { request };
119
+ return new Response(JSON.stringify(response), {
120
+ headers: { ...corsHeaders, "Content-Type": "application/json" },
121
+ });
122
+ }
123
+ // POST /api/complete/:id - Complete a pending request
124
+ const completeMatch = pathname.match(/^\/api\/complete\/([a-f0-9-]+)$/);
125
+ if (completeMatch && method === "POST") {
126
+ const id = completeMatch[1];
127
+ if (!store.has(id)) {
128
+ return new Response(JSON.stringify({ error: "Request not found" }), {
129
+ status: 404,
130
+ headers: { ...corsHeaders, "Content-Type": "application/json" },
131
+ });
132
+ }
133
+ const data = body;
134
+ if (typeof data.success !== "boolean") {
135
+ return new Response(JSON.stringify({ error: "Invalid request body" }), {
136
+ status: 400,
137
+ headers: { ...corsHeaders, "Content-Type": "application/json" },
138
+ });
139
+ }
140
+ const result = data.success
141
+ ? { success: true, result: data.result || "" }
142
+ : { success: false, error: data.error || "Unknown error" };
143
+ const completed = store.complete(id, result);
144
+ if (!completed) {
145
+ return new Response(JSON.stringify({ error: "Failed to complete request" }), {
146
+ status: 500,
147
+ headers: { ...corsHeaders, "Content-Type": "application/json" },
148
+ });
149
+ }
150
+ return new Response(JSON.stringify({ ok: true }), {
151
+ headers: { ...corsHeaders, "Content-Type": "application/json" },
152
+ });
153
+ }
154
+ // GET /api/health - Health check
155
+ if (pathname === "/api/health" && method === "GET") {
156
+ return new Response(JSON.stringify({
157
+ status: "ok",
158
+ pendingRequests: store.size,
159
+ }), {
160
+ headers: { ...corsHeaders, "Content-Type": "application/json" },
161
+ });
162
+ }
163
+ // === Test endpoints (for e2e browser testing) ===
164
+ // POST /api/test/create-request - Create a pending request for testing
165
+ if (pathname === "/api/test/create-request" && method === "POST") {
166
+ const data = body;
167
+ const type = data.type;
168
+ let id;
169
+ let promise;
170
+ switch (type) {
171
+ case "connect": {
172
+ const result = store.createConnectRequest({
173
+ chainId: data.chainId,
174
+ address: data.address,
175
+ });
176
+ id = result.id;
177
+ promise = result.promise;
178
+ break;
179
+ }
180
+ case "send_transaction": {
181
+ const result = store.createSendTransactionRequest({
182
+ to: data.to,
183
+ value: data.value,
184
+ data: data.data,
185
+ chainId: data.chainId,
186
+ gasLimit: data.gasLimit,
187
+ maxFeePerGas: data.maxFeePerGas,
188
+ maxPriorityFeePerGas: data.maxPriorityFeePerGas,
189
+ });
190
+ id = result.id;
191
+ promise = result.promise;
192
+ break;
193
+ }
194
+ case "sign_message": {
195
+ const result = store.createSignMessageRequest({
196
+ message: data.message,
197
+ address: data.address,
198
+ chainId: data.chainId,
199
+ });
200
+ id = result.id;
201
+ promise = result.promise;
202
+ break;
203
+ }
204
+ case "sign_typed_data": {
205
+ const result = store.createSignTypedDataRequest({
206
+ domain: data.domain,
207
+ types: data.types,
208
+ primaryType: data.primaryType,
209
+ message: data.message,
210
+ address: data.address,
211
+ chainId: data.chainId,
212
+ });
213
+ id = result.id;
214
+ promise = result.promise;
215
+ break;
216
+ }
217
+ default:
218
+ return new Response(JSON.stringify({ error: "Invalid request type" }), {
219
+ status: 400,
220
+ headers: { ...corsHeaders, "Content-Type": "application/json" },
221
+ });
222
+ }
223
+ // Store the promise result for later retrieval
224
+ promise.then((result) => {
225
+ testResults.set(id, result);
226
+ }).catch((err) => {
227
+ testResults.set(id, { success: false, error: err.message });
228
+ });
229
+ return new Response(JSON.stringify({ id }), {
230
+ headers: { ...corsHeaders, "Content-Type": "application/json" },
231
+ });
232
+ }
233
+ // GET /api/test/result/:id - Get test result
234
+ const testResultMatch = pathname.match(/^\/api\/test\/result\/([a-f0-9-]+)$/);
235
+ if (testResultMatch && method === "GET") {
236
+ const id = testResultMatch[1];
237
+ const result = testResults.get(id);
238
+ if (result === undefined) {
239
+ // Check if request is still pending
240
+ if (store.has(id)) {
241
+ return new Response(JSON.stringify({ pending: true }), {
242
+ headers: { ...corsHeaders, "Content-Type": "application/json" },
243
+ });
244
+ }
245
+ return new Response(JSON.stringify({ error: "Result not found" }), {
246
+ status: 404,
247
+ headers: { ...corsHeaders, "Content-Type": "application/json" },
248
+ });
249
+ }
250
+ return new Response(JSON.stringify(result), {
251
+ headers: { ...corsHeaders, "Content-Type": "application/json" },
252
+ });
253
+ }
254
+ return new Response(JSON.stringify({ error: "Not found" }), {
255
+ status: 404,
256
+ headers: { ...corsHeaders, "Content-Type": "application/json" },
257
+ });
258
+ }
259
+ /**
260
+ * Read the full request body from an IncomingMessage as a string.
261
+ */
262
+ function readBody(req) {
263
+ return new Promise((resolve, reject) => {
264
+ let body = "";
265
+ req.setEncoding("utf8");
266
+ req.on("data", (chunk) => (body += chunk));
267
+ req.on("end", () => resolve(body));
268
+ req.on("error", reject);
269
+ });
270
+ }
271
+ /**
272
+ * Write a web-standard Response to a Node ServerResponse.
273
+ */
274
+ async function writeResponse(res, response) {
275
+ res.writeHead(response.status, Object.fromEntries(response.headers.entries()));
276
+ if (response.body) {
277
+ const bytes = new Uint8Array(await response.arrayBuffer());
278
+ res.end(bytes);
279
+ }
280
+ else {
281
+ res.end();
282
+ }
283
+ }
284
+ /**
285
+ * Create a node:http request handler using the existing Response-based logic.
286
+ */
287
+ function makeHandler(webDistPath, store) {
288
+ return async (req, res) => {
289
+ const url = new URL(req.url, `http://${req.headers.host || "127.0.0.1"}`);
290
+ const pathname = url.pathname;
291
+ const method = req.method || "GET";
292
+ let response;
293
+ if (pathname.startsWith("/api/")) {
294
+ let body = null;
295
+ if (method === "POST") {
296
+ try {
297
+ const raw = await readBody(req);
298
+ body = JSON.parse(raw);
299
+ }
300
+ catch {
301
+ response = new Response(JSON.stringify({ error: "Invalid JSON" }), {
302
+ status: 400,
303
+ headers: { "Content-Type": "application/json" },
304
+ });
305
+ await writeResponse(res, response);
306
+ return;
307
+ }
308
+ }
309
+ response = handleApiRequest(pathname, method, body, store);
310
+ }
311
+ else {
312
+ response = await serveStaticFile(pathname, webDistPath);
313
+ }
314
+ await writeResponse(res, response);
315
+ };
316
+ }
317
+ /**
318
+ * Create an HTTP server bound to a PendingStore.
319
+ * Returns the port and a stop function.
320
+ */
321
+ export async function createHttpServer(store, port) {
322
+ const targetPort = port ?? getPort();
323
+ const webDistPath = getWebDistPath();
324
+ const srv = createServer(makeHandler(webDistPath, store));
325
+ await new Promise((resolve) => {
326
+ srv.listen(targetPort, "127.0.0.1", () => resolve());
327
+ });
328
+ const actualPort = srv.address().port;
329
+ return {
330
+ port: actualPort,
331
+ stop: () => new Promise((resolve, reject) => {
332
+ srv.close((err) => (err ? reject(err) : resolve()));
333
+ }),
334
+ };
335
+ }
336
+ /**
337
+ * Start a test server on a random port using the default PendingStore.
338
+ */
339
+ export function startTestServer() {
340
+ return createHttpServer(defaultPendingStore, 0);
341
+ }
package/esm/mod.d.ts ADDED
@@ -0,0 +1,11 @@
1
+ import "./_dnt.polyfills.js";
2
+ export { type BalanceResult, type ConnectResult, type SendTransactionParams, type SignMessageParams, type SignResult, type SignTypedDataParams, type TransactionResult, WalletSigner, type WalletSignerOptions, } from "./wallet-signer.js";
3
+ export { CHAINS, getChainConfig, getDefaultChainId, getPort, getRpcUrl } from "./config.js";
4
+ export type { ChainConfig, TypedDataDomain, TypedDataField } from "./types.js";
5
+ export { walletSignerTransport, type WalletSignerTransportOptions } from "./transport.js";
6
+ export { connectWalletViem, type ConnectWalletViemOptions, type ViemBrowserAccount } from "./viem-account.js";
7
+ export { PendingStore, pendingStore } from "./pending-store.js";
8
+ export { createHttpServer, startTestServer } from "./http-server.js";
9
+ export { buildConnectUrl, buildSignUrl, openBrowser } from "./browser.js";
10
+ export { VERSION } from "./version.js";
11
+ //# sourceMappingURL=mod.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mod.d.ts","sourceRoot":"","sources":["../src/mod.ts"],"names":[],"mappings":"AAAA,OAAO,qBAAqB,CAAC;AAC7B,OAAO,EACL,KAAK,aAAa,EAClB,KAAK,aAAa,EAClB,KAAK,qBAAqB,EAC1B,KAAK,iBAAiB,EACtB,KAAK,UAAU,EACf,KAAK,mBAAmB,EACxB,KAAK,iBAAiB,EACtB,YAAY,EACZ,KAAK,mBAAmB,GACzB,MAAM,oBAAoB,CAAC;AAE5B,OAAO,EAAE,MAAM,EAAE,cAAc,EAAE,iBAAiB,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAC5F,YAAY,EAAE,WAAW,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAE/E,OAAO,EAAE,qBAAqB,EAAE,KAAK,4BAA4B,EAAE,MAAM,gBAAgB,CAAC;AAC1F,OAAO,EAAE,iBAAiB,EAAE,KAAK,wBAAwB,EAAE,KAAK,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AAE9G,OAAO,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAChE,OAAO,EAAE,gBAAgB,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AACrE,OAAO,EAAE,eAAe,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAC1E,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC"}
package/esm/mod.js ADDED
@@ -0,0 +1,9 @@
1
+ import "./_dnt.polyfills.js";
2
+ export { WalletSigner, } from "./wallet-signer.js";
3
+ export { CHAINS, getChainConfig, getDefaultChainId, getPort, getRpcUrl } from "./config.js";
4
+ export { walletSignerTransport } from "./transport.js";
5
+ export { connectWalletViem } from "./viem-account.js";
6
+ export { PendingStore, pendingStore } from "./pending-store.js";
7
+ export { createHttpServer, startTestServer } from "./http-server.js";
8
+ export { buildConnectUrl, buildSignUrl, openBrowser } from "./browser.js";
9
+ export { VERSION } from "./version.js";
@@ -0,0 +1,3 @@
1
+ {
2
+ "type": "module"
3
+ }
@@ -0,0 +1,89 @@
1
+ import type { PendingRequest, RequestResult, SignTypedDataRequest } from "./types.js";
2
+ /**
3
+ * Store for pending signing requests.
4
+ * Each request creates a Promise that resolves when the browser completes the signing.
5
+ */
6
+ export declare class PendingStore {
7
+ private pending;
8
+ private timeouts;
9
+ /**
10
+ * Create a new connect wallet request
11
+ */
12
+ createConnectRequest(params?: {
13
+ chainId?: number;
14
+ address?: string;
15
+ }): {
16
+ id: string;
17
+ promise: Promise<RequestResult>;
18
+ };
19
+ /**
20
+ * Create a new send transaction request
21
+ */
22
+ createSendTransactionRequest(params: {
23
+ to: string;
24
+ value?: string;
25
+ data?: string;
26
+ chainId?: number;
27
+ gasLimit?: string;
28
+ maxFeePerGas?: string;
29
+ maxPriorityFeePerGas?: string;
30
+ }): {
31
+ id: string;
32
+ promise: Promise<RequestResult>;
33
+ };
34
+ /**
35
+ * Create a new sign message request
36
+ */
37
+ createSignMessageRequest(params: {
38
+ message: string;
39
+ address?: string;
40
+ chainId?: number;
41
+ }): {
42
+ id: string;
43
+ promise: Promise<RequestResult>;
44
+ };
45
+ /**
46
+ * Create a new sign typed data request
47
+ */
48
+ createSignTypedDataRequest(params: {
49
+ domain: SignTypedDataRequest["domain"];
50
+ types: SignTypedDataRequest["types"];
51
+ primaryType: string;
52
+ message: Record<string, unknown>;
53
+ address?: string;
54
+ chainId?: number;
55
+ }): {
56
+ id: string;
57
+ promise: Promise<RequestResult>;
58
+ };
59
+ /**
60
+ * Create a pending request and return a Promise that resolves when completed
61
+ */
62
+ private create;
63
+ /**
64
+ * Get a pending request by ID
65
+ */
66
+ get(id: string): PendingRequest | undefined;
67
+ /**
68
+ * Complete a pending request with a result
69
+ */
70
+ complete(id: string, result: RequestResult): boolean;
71
+ /**
72
+ * Cancel a pending request
73
+ */
74
+ cancel(id: string, reason?: string): boolean;
75
+ /**
76
+ * Check if a request is pending
77
+ */
78
+ has(id: string): boolean;
79
+ /**
80
+ * Get all pending request IDs
81
+ */
82
+ getPendingIds(): string[];
83
+ /**
84
+ * Get count of pending requests
85
+ */
86
+ get size(): number;
87
+ }
88
+ export declare const pendingStore: PendingStore;
89
+ //# sourceMappingURL=pending-store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pending-store.d.ts","sourceRoot":"","sources":["../src/pending-store.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAGV,cAAc,EACd,aAAa,EAGb,oBAAoB,EACrB,MAAM,YAAY,CAAC;AAUpB;;;GAGG;AACH,qBAAa,YAAY;IACvB,OAAO,CAAC,OAAO,CAAwC;IACvD,OAAO,CAAC,QAAQ,CAAkC;IAElD;;OAEG;IACH,oBAAoB,CAAC,MAAM,CAAC,EAAE;QAAE,OAAO,CAAC,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,OAAO,CAAC,aAAa,CAAC,CAAA;KAAE;IAWtH;;OAEG;IACH,4BAA4B,CAAC,MAAM,EAAE;QACnC,EAAE,EAAE,MAAM,CAAC;QACX,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,YAAY,CAAC,EAAE,MAAM,CAAC;QACtB,oBAAoB,CAAC,EAAE,MAAM,CAAC;KAC/B,GAAG;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,OAAO,CAAC,aAAa,CAAC,CAAA;KAAE;IAUnD;;OAEG;IACH,wBAAwB,CAAC,MAAM,EAAE;QAC/B,OAAO,EAAE,MAAM,CAAC;QAChB,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,OAAO,CAAC,EAAE,MAAM,CAAC;KAClB,GAAG;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,OAAO,CAAC,aAAa,CAAC,CAAA;KAAE;IAUnD;;OAEG;IACH,0BAA0B,CAAC,MAAM,EAAE;QACjC,MAAM,EAAE,oBAAoB,CAAC,QAAQ,CAAC,CAAC;QACvC,KAAK,EAAE,oBAAoB,CAAC,OAAO,CAAC,CAAC;QACrC,WAAW,EAAE,MAAM,CAAC;QACpB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;QACjC,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,OAAO,CAAC,EAAE,MAAM,CAAC;KAClB,GAAG;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,OAAO,CAAC,aAAa,CAAC,CAAA;KAAE;IAUnD;;OAEG;IACH,OAAO,CAAC,MAAM;IAwBd;;OAEG;IACH,GAAG,CAAC,EAAE,EAAE,MAAM,GAAG,cAAc,GAAG,SAAS;IAI3C;;OAEG;IACH,QAAQ,CAAC,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,aAAa,GAAG,OAAO;IAmBpD;;OAEG;IACH,MAAM,CAAC,EAAE,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO;IAmB5C;;OAEG;IACH,GAAG,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO;IAIxB;;OAEG;IACH,aAAa,IAAI,MAAM,EAAE;IAIzB;;OAEG;IACH,IAAI,IAAI,IAAI,MAAM,CAEjB;CACF;AAGD,eAAO,MAAM,YAAY,cAAqB,CAAC"}
@@ -0,0 +1,162 @@
1
+ // Generates a unique request ID
2
+ function generateId() {
3
+ return crypto.randomUUID();
4
+ }
5
+ // Timeout for pending requests (5 minutes)
6
+ const REQUEST_TIMEOUT_MS = 5 * 60 * 1000;
7
+ /**
8
+ * Store for pending signing requests.
9
+ * Each request creates a Promise that resolves when the browser completes the signing.
10
+ */
11
+ export class PendingStore {
12
+ constructor() {
13
+ Object.defineProperty(this, "pending", {
14
+ enumerable: true,
15
+ configurable: true,
16
+ writable: true,
17
+ value: new Map()
18
+ });
19
+ Object.defineProperty(this, "timeouts", {
20
+ enumerable: true,
21
+ configurable: true,
22
+ writable: true,
23
+ value: new Map()
24
+ });
25
+ }
26
+ /**
27
+ * Create a new connect wallet request
28
+ */
29
+ createConnectRequest(params) {
30
+ const request = {
31
+ id: generateId(),
32
+ type: "connect",
33
+ chainId: params?.chainId,
34
+ address: params?.address,
35
+ createdAt: Date.now(),
36
+ };
37
+ return this.create(request);
38
+ }
39
+ /**
40
+ * Create a new send transaction request
41
+ */
42
+ createSendTransactionRequest(params) {
43
+ const request = {
44
+ id: generateId(),
45
+ type: "send_transaction",
46
+ createdAt: Date.now(),
47
+ ...params,
48
+ };
49
+ return this.create(request);
50
+ }
51
+ /**
52
+ * Create a new sign message request
53
+ */
54
+ createSignMessageRequest(params) {
55
+ const request = {
56
+ id: generateId(),
57
+ type: "sign_message",
58
+ createdAt: Date.now(),
59
+ ...params,
60
+ };
61
+ return this.create(request);
62
+ }
63
+ /**
64
+ * Create a new sign typed data request
65
+ */
66
+ createSignTypedDataRequest(params) {
67
+ const request = {
68
+ id: generateId(),
69
+ type: "sign_typed_data",
70
+ createdAt: Date.now(),
71
+ ...params,
72
+ };
73
+ return this.create(request);
74
+ }
75
+ /**
76
+ * Create a pending request and return a Promise that resolves when completed
77
+ */
78
+ create(request) {
79
+ const promise = new Promise((resolve, reject) => {
80
+ const entry = {
81
+ request,
82
+ resolve,
83
+ reject,
84
+ };
85
+ this.pending.set(request.id, entry);
86
+ // Set timeout to auto-reject
87
+ const timeoutId = setTimeout(() => {
88
+ if (this.pending.has(request.id)) {
89
+ this.pending.delete(request.id);
90
+ this.timeouts.delete(request.id);
91
+ reject(new Error("Request timed out after 5 minutes"));
92
+ }
93
+ }, REQUEST_TIMEOUT_MS);
94
+ this.timeouts.set(request.id, timeoutId);
95
+ });
96
+ return { id: request.id, promise };
97
+ }
98
+ /**
99
+ * Get a pending request by ID
100
+ */
101
+ get(id) {
102
+ return this.pending.get(id)?.request;
103
+ }
104
+ /**
105
+ * Complete a pending request with a result
106
+ */
107
+ complete(id, result) {
108
+ const entry = this.pending.get(id);
109
+ if (!entry) {
110
+ return false;
111
+ }
112
+ // Clear timeout
113
+ const timeoutId = this.timeouts.get(id);
114
+ if (timeoutId) {
115
+ clearTimeout(timeoutId);
116
+ this.timeouts.delete(id);
117
+ }
118
+ // Resolve the promise
119
+ entry.resolve(result);
120
+ this.pending.delete(id);
121
+ return true;
122
+ }
123
+ /**
124
+ * Cancel a pending request
125
+ */
126
+ cancel(id, reason) {
127
+ const entry = this.pending.get(id);
128
+ if (!entry) {
129
+ return false;
130
+ }
131
+ // Clear timeout
132
+ const timeoutId = this.timeouts.get(id);
133
+ if (timeoutId) {
134
+ clearTimeout(timeoutId);
135
+ this.timeouts.delete(id);
136
+ }
137
+ // Reject the promise
138
+ entry.reject(new Error(reason || "Request cancelled"));
139
+ this.pending.delete(id);
140
+ return true;
141
+ }
142
+ /**
143
+ * Check if a request is pending
144
+ */
145
+ has(id) {
146
+ return this.pending.has(id);
147
+ }
148
+ /**
149
+ * Get all pending request IDs
150
+ */
151
+ getPendingIds() {
152
+ return Array.from(this.pending.keys());
153
+ }
154
+ /**
155
+ * Get count of pending requests
156
+ */
157
+ get size() {
158
+ return this.pending.size;
159
+ }
160
+ }
161
+ // Singleton instance
162
+ export const pendingStore = new PendingStore();
@@ -0,0 +1,9 @@
1
+ import type { CustomTransport } from "viem";
2
+ import type { WalletSigner } from "./wallet-signer.js";
3
+ export interface WalletSignerTransportOptions {
4
+ /** JSON-RPC endpoint for chain-reading calls. Defaults to built-in RPC for the signer's default chain. */
5
+ rpcUrl?: string;
6
+ }
7
+ /** Create a viem custom transport that routes wallet methods through WalletSigner. */
8
+ export declare function walletSignerTransport(signer: WalletSigner, options?: WalletSignerTransportOptions): CustomTransport;
9
+ //# sourceMappingURL=transport.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"transport.d.ts","sourceRoot":"","sources":["../src/transport.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,MAAM,CAAC;AAE5C,OAAO,KAAK,EAAyB,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAG9E,MAAM,WAAW,4BAA4B;IAC3C,0GAA0G;IAC1G,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,sFAAsF;AACtF,wBAAgB,qBAAqB,CACnC,MAAM,EAAE,YAAY,EACpB,OAAO,CAAC,EAAE,4BAA4B,GACrC,eAAe,CAyDjB"}