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.
- package/esm/_dnt.polyfills.d.ts +101 -0
- package/esm/_dnt.polyfills.d.ts.map +1 -0
- package/esm/_dnt.polyfills.js +127 -0
- package/esm/browser.d.ts +13 -0
- package/esm/browser.d.ts.map +1 -0
- package/esm/browser.js +28 -0
- package/esm/config.d.ts +8 -0
- package/esm/config.d.ts.map +1 -0
- package/esm/config.js +100 -0
- package/esm/http-server.d.ts +17 -0
- package/esm/http-server.d.ts.map +1 -0
- package/esm/http-server.js +341 -0
- package/esm/mod.d.ts +11 -0
- package/esm/mod.d.ts.map +1 -0
- package/esm/mod.js +9 -0
- package/esm/package.json +3 -0
- package/esm/pending-store.d.ts +89 -0
- package/esm/pending-store.d.ts.map +1 -0
- package/esm/pending-store.js +162 -0
- package/esm/transport.d.ts +9 -0
- package/esm/transport.d.ts.map +1 -0
- package/esm/transport.js +60 -0
- package/esm/types.d.ts +79 -0
- package/esm/types.d.ts.map +1 -0
- package/esm/types.js +1 -0
- package/esm/version.d.ts +2 -0
- package/esm/version.d.ts.map +1 -0
- package/esm/version.js +14 -0
- package/esm/viem-account.d.ts +27 -0
- package/esm/viem-account.d.ts.map +1 -0
- package/esm/viem-account.js +36 -0
- package/esm/wallet-signer.d.ts +106 -0
- package/esm/wallet-signer.d.ts.map +1 -0
- package/esm/wallet-signer.js +178 -0
- package/package.json +46 -0
- package/web/assets/index-C17Xxzpm.js +46 -0
- package/web/assets/index-C5BDQo8n.css +1 -0
- package/web/assets/secp256k1-B4KiQCBs.js +1 -0
- package/web/favicon.svg +10 -0
- package/web/index.html +27 -0
|
@@ -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
|
package/esm/mod.d.ts.map
ADDED
|
@@ -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";
|
package/esm/package.json
ADDED
|
@@ -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"}
|