@volr/sdk-core 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/dist/index.cjs +1342 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +1338 -0
- package/dist/index.d.ts +1338 -0
- package/dist/index.js +1288 -0
- package/dist/index.js.map +1 -0
- package/package.json +69 -0
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,1342 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var hkdf = require('@noble/hashes/hkdf');
|
|
4
|
+
var sha256 = require('@noble/hashes/sha256');
|
|
5
|
+
var bip32 = require('@scure/bip32');
|
|
6
|
+
var secp256k1 = require('@noble/curves/secp256k1');
|
|
7
|
+
var sha3 = require('@noble/hashes/sha3');
|
|
8
|
+
var ethers = require('ethers');
|
|
9
|
+
var axios = require('axios');
|
|
10
|
+
|
|
11
|
+
function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
|
|
12
|
+
|
|
13
|
+
var axios__default = /*#__PURE__*/_interopDefault(axios);
|
|
14
|
+
|
|
15
|
+
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
16
|
+
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
17
|
+
}) : x)(function(x) {
|
|
18
|
+
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
19
|
+
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
// src/core/errors.ts
|
|
23
|
+
var VolrError = class _VolrError extends Error {
|
|
24
|
+
constructor(code, message, cause) {
|
|
25
|
+
super(message);
|
|
26
|
+
this.code = code;
|
|
27
|
+
this.cause = cause;
|
|
28
|
+
this.name = "VolrError";
|
|
29
|
+
Object.setPrototypeOf(this, _VolrError.prototype);
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
var ERR_INVALID_PARAM = "ERR_INVALID_PARAM";
|
|
33
|
+
var ERR_CRYPTO_FAIL = "ERR_CRYPTO_FAIL";
|
|
34
|
+
var ERR_AAD_MISMATCH = "ERR_AAD_MISMATCH";
|
|
35
|
+
var ERR_NONCE_SIZE = "ERR_NONCE_SIZE";
|
|
36
|
+
var ERR_LOW_ENTROPY = "ERR_LOW_ENTROPY";
|
|
37
|
+
var ERR_CHAIN_MISMATCH = "ERR_CHAIN_MISMATCH";
|
|
38
|
+
|
|
39
|
+
// src/core/crypto/hkdf.ts
|
|
40
|
+
function hkdfSha256(ikm, salt, info, len = 32) {
|
|
41
|
+
if (!info || info.length === 0) {
|
|
42
|
+
throw new Error(`${ERR_INVALID_PARAM}: info cannot be empty`);
|
|
43
|
+
}
|
|
44
|
+
if (len < 1 || len > 255 * 32) {
|
|
45
|
+
throw new Error(`${ERR_INVALID_PARAM}: len must be between 1 and ${255 * 32}`);
|
|
46
|
+
}
|
|
47
|
+
try {
|
|
48
|
+
const derived = hkdf.hkdf(sha256.sha256, ikm, salt, info, len);
|
|
49
|
+
return derived;
|
|
50
|
+
} catch (error) {
|
|
51
|
+
throw new Error(`HKDF derivation failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// src/core/crypto/rng.ts
|
|
56
|
+
function getRandomBytes(len) {
|
|
57
|
+
if (len < 0 || len > 65536) {
|
|
58
|
+
throw new Error(`Invalid length: ${len}`);
|
|
59
|
+
}
|
|
60
|
+
const arr = new Uint8Array(len);
|
|
61
|
+
if (typeof crypto !== "undefined" && crypto.getRandomValues) {
|
|
62
|
+
crypto.getRandomValues(arr);
|
|
63
|
+
return arr;
|
|
64
|
+
}
|
|
65
|
+
if (typeof __require !== "undefined") {
|
|
66
|
+
try {
|
|
67
|
+
const nodeCrypto = __require("crypto");
|
|
68
|
+
if (nodeCrypto.webcrypto && nodeCrypto.webcrypto.getRandomValues) {
|
|
69
|
+
nodeCrypto.webcrypto.getRandomValues(arr);
|
|
70
|
+
return arr;
|
|
71
|
+
}
|
|
72
|
+
const randomBytes = nodeCrypto.randomBytes(len);
|
|
73
|
+
arr.set(randomBytes);
|
|
74
|
+
return arr;
|
|
75
|
+
} catch {
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
throw new Error("No secure random source available");
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// src/core/crypto/aes-gcm.ts
|
|
82
|
+
function getSubtleCrypto() {
|
|
83
|
+
if (typeof crypto !== "undefined" && crypto.subtle) {
|
|
84
|
+
return crypto.subtle;
|
|
85
|
+
}
|
|
86
|
+
if (typeof __require !== "undefined") {
|
|
87
|
+
try {
|
|
88
|
+
const nodeCrypto = __require("crypto");
|
|
89
|
+
if (nodeCrypto.webcrypto && nodeCrypto.webcrypto.subtle) {
|
|
90
|
+
return nodeCrypto.webcrypto.subtle;
|
|
91
|
+
}
|
|
92
|
+
} catch {
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
throw new Error(`${ERR_CRYPTO_FAIL}: WebCrypto API not available`);
|
|
96
|
+
}
|
|
97
|
+
async function aesGcmEncrypt(plain, params) {
|
|
98
|
+
const { key, nonce: providedNonce, aad } = params;
|
|
99
|
+
if (key.length !== 32) {
|
|
100
|
+
throw new Error(`${ERR_INVALID_PARAM}: key must be 32 bytes, got ${key.length}`);
|
|
101
|
+
}
|
|
102
|
+
const nonce = providedNonce || getRandomBytes(12);
|
|
103
|
+
if (nonce.length !== 12) {
|
|
104
|
+
throw new Error(`${ERR_NONCE_SIZE}: nonce must be 12 bytes, got ${nonce.length}`);
|
|
105
|
+
}
|
|
106
|
+
const subtle = getSubtleCrypto();
|
|
107
|
+
try {
|
|
108
|
+
const keyMaterial = await subtle.importKey(
|
|
109
|
+
"raw",
|
|
110
|
+
key,
|
|
111
|
+
{ name: "AES-GCM" },
|
|
112
|
+
false,
|
|
113
|
+
["encrypt"]
|
|
114
|
+
);
|
|
115
|
+
const encrypted = await subtle.encrypt(
|
|
116
|
+
{
|
|
117
|
+
name: "AES-GCM",
|
|
118
|
+
iv: nonce,
|
|
119
|
+
additionalData: aad,
|
|
120
|
+
tagLength: 128
|
|
121
|
+
// 16 bytes authentication tag
|
|
122
|
+
},
|
|
123
|
+
keyMaterial,
|
|
124
|
+
plain
|
|
125
|
+
);
|
|
126
|
+
return {
|
|
127
|
+
cipher: new Uint8Array(encrypted),
|
|
128
|
+
nonce
|
|
129
|
+
};
|
|
130
|
+
} catch (error) {
|
|
131
|
+
throw new Error(
|
|
132
|
+
`${ERR_CRYPTO_FAIL}: Encryption failed: ${error instanceof Error ? error.message : String(error)}`
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
async function aesGcmDecrypt(cipher, params) {
|
|
137
|
+
const { key, nonce, aad } = params;
|
|
138
|
+
if (key.length !== 32) {
|
|
139
|
+
throw new Error(`${ERR_INVALID_PARAM}: key must be 32 bytes, got ${key.length}`);
|
|
140
|
+
}
|
|
141
|
+
if (nonce.length !== 12) {
|
|
142
|
+
throw new Error(`${ERR_NONCE_SIZE}: nonce must be 12 bytes, got ${nonce.length}`);
|
|
143
|
+
}
|
|
144
|
+
const subtle = getSubtleCrypto();
|
|
145
|
+
try {
|
|
146
|
+
const keyMaterial = await subtle.importKey(
|
|
147
|
+
"raw",
|
|
148
|
+
key,
|
|
149
|
+
{ name: "AES-GCM" },
|
|
150
|
+
false,
|
|
151
|
+
["decrypt"]
|
|
152
|
+
);
|
|
153
|
+
const decrypted = await subtle.decrypt(
|
|
154
|
+
{
|
|
155
|
+
name: "AES-GCM",
|
|
156
|
+
iv: nonce,
|
|
157
|
+
additionalData: aad,
|
|
158
|
+
tagLength: 128
|
|
159
|
+
},
|
|
160
|
+
keyMaterial,
|
|
161
|
+
cipher
|
|
162
|
+
);
|
|
163
|
+
return new Uint8Array(decrypted);
|
|
164
|
+
} catch (error) {
|
|
165
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
166
|
+
if (message.includes("decryption") || message.includes("authentication")) {
|
|
167
|
+
throw new Error(`${ERR_AAD_MISMATCH}: Decryption failed - wrong key or AAD`);
|
|
168
|
+
}
|
|
169
|
+
throw new Error(`${ERR_CRYPTO_FAIL}: Decryption failed: ${message}`);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// src/core/crypto/zeroize.ts
|
|
174
|
+
function zeroize(buf) {
|
|
175
|
+
let view;
|
|
176
|
+
if (buf instanceof ArrayBuffer) {
|
|
177
|
+
view = new Uint8Array(buf);
|
|
178
|
+
} else {
|
|
179
|
+
view = buf;
|
|
180
|
+
}
|
|
181
|
+
view.fill(0);
|
|
182
|
+
view.length;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// src/master-key/encryption.ts
|
|
186
|
+
async function sealMasterSeed(masterSeed, wrapKey, aad) {
|
|
187
|
+
return aesGcmEncrypt(masterSeed, { key: wrapKey, aad });
|
|
188
|
+
}
|
|
189
|
+
async function unsealMasterSeed(cipher, wrapKey, aad, nonce) {
|
|
190
|
+
const decrypted = await aesGcmDecrypt(cipher, { key: wrapKey, aad, nonce });
|
|
191
|
+
return decrypted;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// src/master-key/provider.ts
|
|
195
|
+
function createMasterKeyProvider() {
|
|
196
|
+
return {
|
|
197
|
+
async unsealFromBlob(blob, wrapKey) {
|
|
198
|
+
const masterSeed = await unsealMasterSeed(
|
|
199
|
+
blob.cipher,
|
|
200
|
+
wrapKey,
|
|
201
|
+
blob.aad,
|
|
202
|
+
blob.nonce
|
|
203
|
+
);
|
|
204
|
+
return {
|
|
205
|
+
bytes: masterSeed,
|
|
206
|
+
destroy() {
|
|
207
|
+
zeroize(masterSeed);
|
|
208
|
+
}
|
|
209
|
+
};
|
|
210
|
+
},
|
|
211
|
+
async generate() {
|
|
212
|
+
const masterSeed = getRandomBytes(32);
|
|
213
|
+
return {
|
|
214
|
+
bytes: masterSeed,
|
|
215
|
+
destroy() {
|
|
216
|
+
zeroize(masterSeed);
|
|
217
|
+
}
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
function deriveWrapKey(input) {
|
|
223
|
+
const { origin, projectId, salt } = input;
|
|
224
|
+
const rpId = typeof window !== "undefined" && origin ? new URL(origin).hostname : origin;
|
|
225
|
+
const prfInput = new TextEncoder().encode(`${rpId}|${projectId}`);
|
|
226
|
+
const prfOutput = sha256.sha256(prfInput);
|
|
227
|
+
const defaultSalt = salt || sha256.sha256(
|
|
228
|
+
new TextEncoder().encode(`volr/salt|${rpId}|${projectId}`)
|
|
229
|
+
);
|
|
230
|
+
const info = `volr/wrap-key/v1|${rpId}|${projectId}`;
|
|
231
|
+
const wrapKey = hkdfSha256(prfOutput, defaultSalt, info, 32);
|
|
232
|
+
return wrapKey;
|
|
233
|
+
}
|
|
234
|
+
var DEFAULT_EVM_PATH = "m/60'/0'/0'/0/0";
|
|
235
|
+
function deriveEvmKey(args) {
|
|
236
|
+
const { masterSeed, path = DEFAULT_EVM_PATH } = args;
|
|
237
|
+
const hdKey = bip32.HDKey.fromMasterSeed(masterSeed);
|
|
238
|
+
const derived = hdKey.derive(path);
|
|
239
|
+
if (!derived.privateKey) {
|
|
240
|
+
throw new Error("Failed to derive private key");
|
|
241
|
+
}
|
|
242
|
+
const privateKey = new Uint8Array(derived.privateKey);
|
|
243
|
+
const publicKey = derived.publicKey;
|
|
244
|
+
if (!publicKey || publicKey.length !== 33) {
|
|
245
|
+
throw new Error("Invalid public key length");
|
|
246
|
+
}
|
|
247
|
+
const pubKeyPoint = secp256k1.secp256k1.ProjectivePoint.fromHex(publicKey);
|
|
248
|
+
const uncompressedPubKey = pubKeyPoint.toRawBytes(false);
|
|
249
|
+
const pubKeyWithoutPrefix = uncompressedPubKey.slice(1);
|
|
250
|
+
const hash = sha3.keccak_256(pubKeyWithoutPrefix);
|
|
251
|
+
const addressBytes = hash.slice(12);
|
|
252
|
+
const address = "0x" + Array.from(addressBytes).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
253
|
+
return {
|
|
254
|
+
privateKey,
|
|
255
|
+
publicKey: uncompressedPubKey,
|
|
256
|
+
address,
|
|
257
|
+
path
|
|
258
|
+
};
|
|
259
|
+
}
|
|
260
|
+
function toChecksumAddress(addr) {
|
|
261
|
+
if (!addr.startsWith("0x")) {
|
|
262
|
+
throw new Error(`${ERR_INVALID_PARAM}: Address must start with 0x`);
|
|
263
|
+
}
|
|
264
|
+
const address = addr.slice(2).toLowerCase();
|
|
265
|
+
if (address.length !== 40) {
|
|
266
|
+
throw new Error(`${ERR_INVALID_PARAM}: Address must be 40 hex characters (20 bytes)`);
|
|
267
|
+
}
|
|
268
|
+
if (!/^[0-9a-f]{40}$/.test(address)) {
|
|
269
|
+
throw new Error(`${ERR_INVALID_PARAM}: Address contains invalid hex characters`);
|
|
270
|
+
}
|
|
271
|
+
const addressBytes = new TextEncoder().encode(address);
|
|
272
|
+
const hash = sha3.keccak_256(addressBytes);
|
|
273
|
+
let checksummed = "0x";
|
|
274
|
+
for (let i = 0; i < address.length; i++) {
|
|
275
|
+
const char = address[i];
|
|
276
|
+
const hashByte = Math.floor(i / 2);
|
|
277
|
+
const hashNibble = i % 2 === 0 ? hash[hashByte] >> 4 : hash[hashByte] & 15;
|
|
278
|
+
checksummed += hashNibble >= 8 ? char.toUpperCase() : char;
|
|
279
|
+
}
|
|
280
|
+
return checksummed;
|
|
281
|
+
}
|
|
282
|
+
function evmSign(privKey, msgHash32) {
|
|
283
|
+
if (privKey.length !== 32) {
|
|
284
|
+
throw new Error(`${ERR_INVALID_PARAM}: Private key must be 32 bytes, got ${privKey.length}`);
|
|
285
|
+
}
|
|
286
|
+
if (msgHash32.length !== 32) {
|
|
287
|
+
throw new Error(`${ERR_INVALID_PARAM}: Message hash must be 32 bytes, got ${msgHash32.length}`);
|
|
288
|
+
}
|
|
289
|
+
const signature = secp256k1.secp256k1.sign(msgHash32, privKey, {
|
|
290
|
+
lowS: true
|
|
291
|
+
// Enforce low-S canonicalization
|
|
292
|
+
});
|
|
293
|
+
const r = signature.r;
|
|
294
|
+
const s = signature.s;
|
|
295
|
+
const rBytes = new Uint8Array(32);
|
|
296
|
+
const sBytes = new Uint8Array(32);
|
|
297
|
+
const rHex = r.toString(16).padStart(64, "0");
|
|
298
|
+
const sHex = s.toString(16).padStart(64, "0");
|
|
299
|
+
for (let i = 0; i < 32; i++) {
|
|
300
|
+
rBytes[i] = parseInt(rHex.slice(i * 2, i * 2 + 2), 16);
|
|
301
|
+
sBytes[i] = parseInt(sHex.slice(i * 2, i * 2 + 2), 16);
|
|
302
|
+
}
|
|
303
|
+
const yParity = signature.recovery % 2;
|
|
304
|
+
return {
|
|
305
|
+
r: rBytes,
|
|
306
|
+
s: sBytes,
|
|
307
|
+
yParity
|
|
308
|
+
};
|
|
309
|
+
}
|
|
310
|
+
var Secp256k1SoftwareSigner = class {
|
|
311
|
+
constructor(privateKey) {
|
|
312
|
+
this.privateKey = privateKey;
|
|
313
|
+
if (privateKey.length !== 32) {
|
|
314
|
+
throw new Error(`${ERR_INVALID_PARAM}: Private key must be 32 bytes, got ${privateKey.length}`);
|
|
315
|
+
}
|
|
316
|
+
if (privateKey.every((b) => b === 0)) {
|
|
317
|
+
throw new Error(`${ERR_INVALID_PARAM}: Private key cannot be zero`);
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
/**
|
|
321
|
+
* Get uncompressed public key (65 bytes)
|
|
322
|
+
*
|
|
323
|
+
* @returns Uncompressed public key with 0x04 prefix
|
|
324
|
+
*/
|
|
325
|
+
async getPublicKey() {
|
|
326
|
+
const pubKeyPoint = secp256k1.secp256k1.getPublicKey(this.privateKey, false);
|
|
327
|
+
const pubKey = new Uint8Array(65);
|
|
328
|
+
pubKey[0] = 4;
|
|
329
|
+
pubKey.set(pubKeyPoint.slice(1), 1);
|
|
330
|
+
return pubKey;
|
|
331
|
+
}
|
|
332
|
+
/**
|
|
333
|
+
* Sign message hash with deterministic, low-S canonicalization
|
|
334
|
+
*
|
|
335
|
+
* @param msgHash32 - 32-byte message hash
|
|
336
|
+
* @returns Signature with r, s, and yParity
|
|
337
|
+
* @throws Error if message hash is invalid
|
|
338
|
+
*/
|
|
339
|
+
async signMessage(msgHash32) {
|
|
340
|
+
if (msgHash32.length !== 32) {
|
|
341
|
+
throw new Error(`${ERR_INVALID_PARAM}: Message hash must be 32 bytes, got ${msgHash32.length}`);
|
|
342
|
+
}
|
|
343
|
+
const signature = secp256k1.secp256k1.sign(msgHash32, this.privateKey, {
|
|
344
|
+
lowS: true
|
|
345
|
+
// Enforce low-S canonicalization
|
|
346
|
+
});
|
|
347
|
+
const r = signature.r;
|
|
348
|
+
const s = signature.s;
|
|
349
|
+
const rBytes = new Uint8Array(32);
|
|
350
|
+
const sBytes = new Uint8Array(32);
|
|
351
|
+
const rHex = r.toString(16).padStart(64, "0");
|
|
352
|
+
const sHex = s.toString(16).padStart(64, "0");
|
|
353
|
+
for (let i = 0; i < 32; i++) {
|
|
354
|
+
rBytes[i] = parseInt(rHex.slice(i * 2, i * 2 + 2), 16);
|
|
355
|
+
sBytes[i] = parseInt(sHex.slice(i * 2, i * 2 + 2), 16);
|
|
356
|
+
}
|
|
357
|
+
const yParity = signature.recovery % 2;
|
|
358
|
+
return {
|
|
359
|
+
r: rBytes,
|
|
360
|
+
s: sBytes,
|
|
361
|
+
yParity
|
|
362
|
+
};
|
|
363
|
+
}
|
|
364
|
+
/**
|
|
365
|
+
* Sign raw hash without EIP-191 prefix (for EIP-7702)
|
|
366
|
+
*
|
|
367
|
+
* @param digest - 32-byte digest to sign
|
|
368
|
+
* @returns Signature with r, s, and yParity
|
|
369
|
+
* @throws Error if digest is invalid
|
|
370
|
+
*/
|
|
371
|
+
async signRawHash(digest) {
|
|
372
|
+
return this.signMessage(digest);
|
|
373
|
+
}
|
|
374
|
+
/**
|
|
375
|
+
* Sign EIP-712 typed data
|
|
376
|
+
*/
|
|
377
|
+
async signTyped(data) {
|
|
378
|
+
const hash = ethers.TypedDataEncoder.hash(data.domain, data.types, data.message);
|
|
379
|
+
const msgHash = new Uint8Array(Buffer.from(hash.slice(2), "hex"));
|
|
380
|
+
const sig = await this.signMessage(msgHash);
|
|
381
|
+
const rHex = Buffer.from(sig.r).toString("hex");
|
|
382
|
+
const sHex = Buffer.from(sig.s).toString("hex");
|
|
383
|
+
const v = (sig.yParity + 27).toString(16).padStart(2, "0");
|
|
384
|
+
return `0x${rHex}${sHex}${v}`;
|
|
385
|
+
}
|
|
386
|
+
};
|
|
387
|
+
function evmVerify(pubKeyUncompressed, msgHash32, sig) {
|
|
388
|
+
if (pubKeyUncompressed.length !== 65 || pubKeyUncompressed[0] !== 4) {
|
|
389
|
+
throw new Error(`${ERR_INVALID_PARAM}: Public key must be 65 bytes uncompressed (0x04 prefix)`);
|
|
390
|
+
}
|
|
391
|
+
if (msgHash32.length !== 32) {
|
|
392
|
+
throw new Error(`${ERR_INVALID_PARAM}: Message hash must be 32 bytes, got ${msgHash32.length}`);
|
|
393
|
+
}
|
|
394
|
+
if (sig.r.length !== 32 || sig.s.length !== 32) {
|
|
395
|
+
throw new Error(`${ERR_INVALID_PARAM}: Signature r and s must be 32 bytes each`);
|
|
396
|
+
}
|
|
397
|
+
const sBigInt = BigInt("0x" + Array.from(sig.s).map((b) => b.toString(16).padStart(2, "0")).join(""));
|
|
398
|
+
const n = secp256k1.secp256k1.CURVE.n;
|
|
399
|
+
const nHalf = n / 2n;
|
|
400
|
+
if (sBigInt > nHalf) {
|
|
401
|
+
return false;
|
|
402
|
+
}
|
|
403
|
+
const x = pubKeyUncompressed.slice(1, 33);
|
|
404
|
+
const y = pubKeyUncompressed.slice(33, 65);
|
|
405
|
+
const pubKeyPoint = secp256k1.secp256k1.ProjectivePoint.fromAffine({
|
|
406
|
+
x: BigInt("0x" + Array.from(x).map((b) => b.toString(16).padStart(2, "0")).join("")),
|
|
407
|
+
y: BigInt("0x" + Array.from(y).map((b) => b.toString(16).padStart(2, "0")).join(""))
|
|
408
|
+
});
|
|
409
|
+
const compressedPubKey = pubKeyPoint.toRawBytes(true);
|
|
410
|
+
try {
|
|
411
|
+
const compactSig = new Uint8Array([...sig.r, ...sig.s]);
|
|
412
|
+
return secp256k1.secp256k1.verify(compactSig, msgHash32, compressedPubKey);
|
|
413
|
+
} catch {
|
|
414
|
+
return false;
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
var PasskeyP256Signer = class {
|
|
418
|
+
constructor(provider) {
|
|
419
|
+
this.provider = provider;
|
|
420
|
+
}
|
|
421
|
+
/**
|
|
422
|
+
* Get uncompressed P-256 public key (65 bytes)
|
|
423
|
+
*
|
|
424
|
+
* @returns Uncompressed public key with 0x04 prefix
|
|
425
|
+
*/
|
|
426
|
+
async getPublicKey() {
|
|
427
|
+
const { x, y } = await this.provider.getPublicKey();
|
|
428
|
+
if (x.length !== 32 || y.length !== 32) {
|
|
429
|
+
throw new Error(`${ERR_INVALID_PARAM}: Public key coordinates must be 32 bytes each`);
|
|
430
|
+
}
|
|
431
|
+
const pubKey = new Uint8Array(65);
|
|
432
|
+
pubKey[0] = 4;
|
|
433
|
+
pubKey.set(x, 1);
|
|
434
|
+
pubKey.set(y, 33);
|
|
435
|
+
return pubKey;
|
|
436
|
+
}
|
|
437
|
+
/**
|
|
438
|
+
* Sign message hash using P-256 passkey
|
|
439
|
+
*
|
|
440
|
+
* @param msgHash32 - 32-byte message hash
|
|
441
|
+
* @returns Signature with r, s, and yParity
|
|
442
|
+
* @throws Error if message hash is invalid
|
|
443
|
+
*/
|
|
444
|
+
async signMessage(msgHash32) {
|
|
445
|
+
if (msgHash32.length !== 32) {
|
|
446
|
+
throw new Error(`${ERR_INVALID_PARAM}: Message hash must be 32 bytes, got ${msgHash32.length}`);
|
|
447
|
+
}
|
|
448
|
+
const { r, s } = await this.provider.signP256(msgHash32);
|
|
449
|
+
if (r.length !== 32 || s.length !== 32) {
|
|
450
|
+
throw new Error(`${ERR_INVALID_PARAM}: Signature r and s must be 32 bytes each`);
|
|
451
|
+
}
|
|
452
|
+
const { y } = await this.provider.getPublicKey();
|
|
453
|
+
const yParity = y[y.length - 1] & 1;
|
|
454
|
+
return {
|
|
455
|
+
r,
|
|
456
|
+
s,
|
|
457
|
+
yParity
|
|
458
|
+
};
|
|
459
|
+
}
|
|
460
|
+
/**
|
|
461
|
+
* Sign raw hash without EIP-191 prefix (for EIP-7702)
|
|
462
|
+
*
|
|
463
|
+
* @param _digest - 32-byte digest to sign
|
|
464
|
+
* @returns Signature with r, s, and yParity
|
|
465
|
+
* @throws Error - P-256 cannot be used for EIP-7702
|
|
466
|
+
*/
|
|
467
|
+
async signRawHash(_digest) {
|
|
468
|
+
throw new Error("EIP-7702 authorization requires secp256k1, not P-256. Use Secp256k1SoftwareSigner for EIP-7702.");
|
|
469
|
+
}
|
|
470
|
+
/**
|
|
471
|
+
* Sign EIP-712 typed data
|
|
472
|
+
*/
|
|
473
|
+
async signTyped(data) {
|
|
474
|
+
const hash = ethers.TypedDataEncoder.hash(data.domain, data.types, data.message);
|
|
475
|
+
const msgHash = new Uint8Array(Buffer.from(hash.slice(2), "hex"));
|
|
476
|
+
const sig = await this.signMessage(msgHash);
|
|
477
|
+
const rHex = Buffer.from(sig.r).toString("hex");
|
|
478
|
+
const sHex = Buffer.from(sig.s).toString("hex");
|
|
479
|
+
const v = sig.yParity.toString(16).padStart(2, "0");
|
|
480
|
+
return `0x${rHex}${sHex}${v}`;
|
|
481
|
+
}
|
|
482
|
+
};
|
|
483
|
+
|
|
484
|
+
// src/wallet/signers/external-wallet-errors.ts
|
|
485
|
+
var EIP1193ErrorCode = /* @__PURE__ */ ((EIP1193ErrorCode2) => {
|
|
486
|
+
EIP1193ErrorCode2[EIP1193ErrorCode2["USER_REJECTED"] = 4001] = "USER_REJECTED";
|
|
487
|
+
EIP1193ErrorCode2[EIP1193ErrorCode2["UNAUTHORIZED"] = 4100] = "UNAUTHORIZED";
|
|
488
|
+
EIP1193ErrorCode2[EIP1193ErrorCode2["UNSUPPORTED_METHOD"] = 4200] = "UNSUPPORTED_METHOD";
|
|
489
|
+
EIP1193ErrorCode2[EIP1193ErrorCode2["DISCONNECTED"] = 4900] = "DISCONNECTED";
|
|
490
|
+
EIP1193ErrorCode2[EIP1193ErrorCode2["CHAIN_DISCONNECTED"] = 4901] = "CHAIN_DISCONNECTED";
|
|
491
|
+
EIP1193ErrorCode2[EIP1193ErrorCode2["UNRECOGNIZED_CHAIN"] = 4902] = "UNRECOGNIZED_CHAIN";
|
|
492
|
+
EIP1193ErrorCode2[EIP1193ErrorCode2["RESOURCE_UNAVAILABLE"] = -32002] = "RESOURCE_UNAVAILABLE";
|
|
493
|
+
EIP1193ErrorCode2[EIP1193ErrorCode2["RESOURCE_NOT_FOUND"] = -32001] = "RESOURCE_NOT_FOUND";
|
|
494
|
+
EIP1193ErrorCode2[EIP1193ErrorCode2["INVALID_INPUT"] = -32e3] = "INVALID_INPUT";
|
|
495
|
+
EIP1193ErrorCode2[EIP1193ErrorCode2["INTERNAL_ERROR"] = -32603] = "INTERNAL_ERROR";
|
|
496
|
+
EIP1193ErrorCode2[EIP1193ErrorCode2["INVALID_REQUEST"] = -32600] = "INVALID_REQUEST";
|
|
497
|
+
EIP1193ErrorCode2[EIP1193ErrorCode2["METHOD_NOT_FOUND"] = -32601] = "METHOD_NOT_FOUND";
|
|
498
|
+
EIP1193ErrorCode2[EIP1193ErrorCode2["INVALID_PARAMS"] = -32602] = "INVALID_PARAMS";
|
|
499
|
+
EIP1193ErrorCode2[EIP1193ErrorCode2["PARSE_ERROR"] = -32700] = "PARSE_ERROR";
|
|
500
|
+
return EIP1193ErrorCode2;
|
|
501
|
+
})(EIP1193ErrorCode || {});
|
|
502
|
+
var WalletError = class extends Error {
|
|
503
|
+
constructor(message, code, originalError) {
|
|
504
|
+
super(message);
|
|
505
|
+
this.code = code;
|
|
506
|
+
this.originalError = originalError;
|
|
507
|
+
this.name = "WalletError";
|
|
508
|
+
}
|
|
509
|
+
};
|
|
510
|
+
var UserRejectedError = class extends WalletError {
|
|
511
|
+
constructor(message = "User rejected the request", originalError) {
|
|
512
|
+
super(message, 4001 /* USER_REJECTED */, originalError);
|
|
513
|
+
this.name = "UserRejectedError";
|
|
514
|
+
}
|
|
515
|
+
};
|
|
516
|
+
var UnauthorizedError = class extends WalletError {
|
|
517
|
+
constructor(message = "Unauthorized to perform this action", originalError) {
|
|
518
|
+
super(message, 4100 /* UNAUTHORIZED */, originalError);
|
|
519
|
+
this.name = "UnauthorizedError";
|
|
520
|
+
}
|
|
521
|
+
};
|
|
522
|
+
var UnsupportedMethodError = class extends WalletError {
|
|
523
|
+
constructor(message = "Method not supported by wallet", originalError) {
|
|
524
|
+
super(message, 4200 /* UNSUPPORTED_METHOD */, originalError);
|
|
525
|
+
this.name = "UnsupportedMethodError";
|
|
526
|
+
}
|
|
527
|
+
};
|
|
528
|
+
var WrongNetworkError = class extends WalletError {
|
|
529
|
+
constructor(message = "Wrong network", expectedChainId, actualChainId, originalError) {
|
|
530
|
+
super(message, 4902 /* UNRECOGNIZED_CHAIN */, originalError);
|
|
531
|
+
this.expectedChainId = expectedChainId;
|
|
532
|
+
this.actualChainId = actualChainId;
|
|
533
|
+
this.name = "WrongNetworkError";
|
|
534
|
+
}
|
|
535
|
+
};
|
|
536
|
+
var ChainNotAddedError = class extends WalletError {
|
|
537
|
+
constructor(message = "Chain not added to wallet", originalError) {
|
|
538
|
+
super(message, 4902 /* UNRECOGNIZED_CHAIN */, originalError);
|
|
539
|
+
this.name = "ChainNotAddedError";
|
|
540
|
+
}
|
|
541
|
+
};
|
|
542
|
+
var RequestPendingError = class extends WalletError {
|
|
543
|
+
constructor(message = "Request already pending in wallet", originalError) {
|
|
544
|
+
super(message, -32002 /* RESOURCE_UNAVAILABLE */, originalError);
|
|
545
|
+
this.name = "RequestPendingError";
|
|
546
|
+
}
|
|
547
|
+
};
|
|
548
|
+
function normalizeWalletError(error) {
|
|
549
|
+
if (error instanceof WalletError) {
|
|
550
|
+
return error;
|
|
551
|
+
}
|
|
552
|
+
if (typeof error === "object" && error !== null) {
|
|
553
|
+
const err = error;
|
|
554
|
+
const code = err.code;
|
|
555
|
+
const message2 = err.message || "Unknown wallet error";
|
|
556
|
+
switch (code) {
|
|
557
|
+
case 4001 /* USER_REJECTED */:
|
|
558
|
+
return new UserRejectedError(message2, error);
|
|
559
|
+
case 4100 /* UNAUTHORIZED */:
|
|
560
|
+
return new UnauthorizedError(message2, error);
|
|
561
|
+
case 4200 /* UNSUPPORTED_METHOD */:
|
|
562
|
+
return new UnsupportedMethodError(message2, error);
|
|
563
|
+
case 4902 /* UNRECOGNIZED_CHAIN */:
|
|
564
|
+
return new ChainNotAddedError(message2, error);
|
|
565
|
+
case -32002 /* RESOURCE_UNAVAILABLE */:
|
|
566
|
+
return new RequestPendingError(message2, error);
|
|
567
|
+
default:
|
|
568
|
+
return new WalletError(message2, code || -1, error);
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
572
|
+
return new WalletError(message, -1, error);
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
// src/wallet/signers/external-wallet.ts
|
|
576
|
+
var ExternalWalletSigner = class {
|
|
577
|
+
constructor(provider, expectedChainId, address) {
|
|
578
|
+
this.provider = provider;
|
|
579
|
+
this.expectedChainId = expectedChainId;
|
|
580
|
+
this.address = address;
|
|
581
|
+
}
|
|
582
|
+
/**
|
|
583
|
+
* Verify that the wallet is on the correct network
|
|
584
|
+
* @throws WrongNetworkError if network mismatch
|
|
585
|
+
*/
|
|
586
|
+
async verifyNetwork() {
|
|
587
|
+
try {
|
|
588
|
+
const chainIdHex = await this.provider.request({
|
|
589
|
+
method: "eth_chainId"
|
|
590
|
+
});
|
|
591
|
+
const actualChainId = parseInt(chainIdHex, 16);
|
|
592
|
+
if (actualChainId !== this.expectedChainId) {
|
|
593
|
+
throw new WrongNetworkError(
|
|
594
|
+
`Wrong network. Expected: ${this.expectedChainId}, Got: ${actualChainId}`,
|
|
595
|
+
this.expectedChainId,
|
|
596
|
+
actualChainId
|
|
597
|
+
);
|
|
598
|
+
}
|
|
599
|
+
} catch (error) {
|
|
600
|
+
if (error instanceof WrongNetworkError) {
|
|
601
|
+
throw error;
|
|
602
|
+
}
|
|
603
|
+
throw normalizeWalletError(error);
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
/**
|
|
607
|
+
* Get the wallet address
|
|
608
|
+
*/
|
|
609
|
+
async getAddress() {
|
|
610
|
+
return this.address;
|
|
611
|
+
}
|
|
612
|
+
/**
|
|
613
|
+
* Get public key - not directly supported by EIP-1193
|
|
614
|
+
* Returns a placeholder as external wallets don't expose raw public keys
|
|
615
|
+
*
|
|
616
|
+
* For external wallets, we rely on address-based verification instead
|
|
617
|
+
*/
|
|
618
|
+
async getPublicKey() {
|
|
619
|
+
return new Uint8Array(65);
|
|
620
|
+
}
|
|
621
|
+
/**
|
|
622
|
+
* Sign a message hash using personal_sign
|
|
623
|
+
*
|
|
624
|
+
* Note: personal_sign automatically adds EIP-191 prefix
|
|
625
|
+
*
|
|
626
|
+
* @param msgHash32 - 32-byte message hash
|
|
627
|
+
* @returns Signature components (r, s, yParity)
|
|
628
|
+
*/
|
|
629
|
+
async signMessage(msgHash32) {
|
|
630
|
+
try {
|
|
631
|
+
await this.verifyNetwork();
|
|
632
|
+
const msgHashHex = "0x" + Array.from(msgHash32).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
633
|
+
const signature = await this.provider.request({
|
|
634
|
+
method: "personal_sign",
|
|
635
|
+
params: [msgHashHex, this.address]
|
|
636
|
+
});
|
|
637
|
+
const sig = signature.startsWith("0x") ? signature.slice(2) : signature;
|
|
638
|
+
if (sig.length !== 130) {
|
|
639
|
+
throw new Error(`Invalid signature length: ${sig.length}`);
|
|
640
|
+
}
|
|
641
|
+
const r = new Uint8Array(32);
|
|
642
|
+
const s = new Uint8Array(32);
|
|
643
|
+
for (let i = 0; i < 32; i++) {
|
|
644
|
+
r[i] = parseInt(sig.slice(i * 2, i * 2 + 2), 16);
|
|
645
|
+
s[i] = parseInt(sig.slice(64 + i * 2, 64 + i * 2 + 2), 16);
|
|
646
|
+
}
|
|
647
|
+
const v = parseInt(sig.slice(128, 130), 16);
|
|
648
|
+
const yParity = v === 27 ? 0 : 1;
|
|
649
|
+
return { r, s, yParity };
|
|
650
|
+
} catch (error) {
|
|
651
|
+
throw normalizeWalletError(error);
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
/**
|
|
655
|
+
* Sign raw hash for EIP-7702 authorization
|
|
656
|
+
*
|
|
657
|
+
* External wallets don't support raw hash signing directly (eth_sign is deprecated/disabled).
|
|
658
|
+
* Instead, we use personal_sign which adds EIP-191 prefix, then we need to account for this
|
|
659
|
+
* when verifying the signature on-chain.
|
|
660
|
+
*
|
|
661
|
+
* ⚠️ IMPORTANT: This creates a signature over the EIP-191 prefixed message, NOT the raw digest.
|
|
662
|
+
* The backend/contract must verify accordingly.
|
|
663
|
+
*
|
|
664
|
+
* Alternative approach: Use EIP-712 typed data for authorization (more wallet-friendly)
|
|
665
|
+
*
|
|
666
|
+
* @param digest - 32-byte digest to sign
|
|
667
|
+
* @returns Signature components (r, s, yParity)
|
|
668
|
+
* @throws WalletError if signing fails
|
|
669
|
+
*/
|
|
670
|
+
async signRawHash(digest) {
|
|
671
|
+
try {
|
|
672
|
+
await this.verifyNetwork();
|
|
673
|
+
const digestHex = "0x" + Array.from(digest).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
674
|
+
let signature;
|
|
675
|
+
try {
|
|
676
|
+
signature = await this.provider.request({
|
|
677
|
+
method: "eth_sign",
|
|
678
|
+
params: [this.address, digestHex]
|
|
679
|
+
});
|
|
680
|
+
} catch (ethSignError) {
|
|
681
|
+
if (ethSignError?.code === -32004 || ethSignError?.code === 4200) {
|
|
682
|
+
throw new Error(
|
|
683
|
+
'eth_sign is required for EIP-7702 authorization but is disabled in your wallet. \n\nTo enable eth_sign in MetaMask:\n1. Go to Settings \u2192 Advanced\n2. Enable "Eth_sign requests"\n\nNote: eth_sign can be dangerous if used carelessly. Only enable it for trusted dApps.\n\nOriginal error: ' + (ethSignError?.message || "eth_sign not supported")
|
|
684
|
+
);
|
|
685
|
+
} else {
|
|
686
|
+
throw ethSignError;
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
const sig = signature.startsWith("0x") ? signature.slice(2) : signature;
|
|
690
|
+
if (sig.length !== 130) {
|
|
691
|
+
throw new Error(`Invalid signature length: ${sig.length}`);
|
|
692
|
+
}
|
|
693
|
+
const r = new Uint8Array(32);
|
|
694
|
+
const s = new Uint8Array(32);
|
|
695
|
+
for (let i = 0; i < 32; i++) {
|
|
696
|
+
r[i] = parseInt(sig.slice(i * 2, i * 2 + 2), 16);
|
|
697
|
+
s[i] = parseInt(sig.slice(64 + i * 2, 64 + i * 2 + 2), 16);
|
|
698
|
+
}
|
|
699
|
+
const v = parseInt(sig.slice(128, 130), 16);
|
|
700
|
+
const yParity = v === 27 || v === 0 ? 0 : 1;
|
|
701
|
+
return { r, s, yParity };
|
|
702
|
+
} catch (error) {
|
|
703
|
+
throw normalizeWalletError(error);
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
/**
|
|
707
|
+
* Sign typed data (EIP-712)
|
|
708
|
+
*
|
|
709
|
+
* @param typed - Typed data structure with domain, types, and message
|
|
710
|
+
* @returns Signature hex string
|
|
711
|
+
*/
|
|
712
|
+
async signTyped(typed) {
|
|
713
|
+
try {
|
|
714
|
+
await this.verifyNetwork();
|
|
715
|
+
if (typeof typed !== "object" || typed === null) {
|
|
716
|
+
throw new Error("Invalid typed data: must be an object");
|
|
717
|
+
}
|
|
718
|
+
const input = typed;
|
|
719
|
+
if (!input.domain || !input.types || !input.message) {
|
|
720
|
+
throw new Error("Invalid typed data: missing domain, types, or message");
|
|
721
|
+
}
|
|
722
|
+
const signature = await this.provider.request({
|
|
723
|
+
method: "eth_signTypedData_v4",
|
|
724
|
+
params: [this.address, JSON.stringify(input)]
|
|
725
|
+
});
|
|
726
|
+
return signature;
|
|
727
|
+
} catch (error) {
|
|
728
|
+
throw normalizeWalletError(error);
|
|
729
|
+
}
|
|
730
|
+
}
|
|
731
|
+
/**
|
|
732
|
+
* Switch to the expected network
|
|
733
|
+
*
|
|
734
|
+
* @returns true if switch successful, false if user rejected
|
|
735
|
+
* @throws Error if switch fails for other reasons
|
|
736
|
+
*/
|
|
737
|
+
async switchNetwork() {
|
|
738
|
+
try {
|
|
739
|
+
const chainIdHex = "0x" + this.expectedChainId.toString(16);
|
|
740
|
+
await this.provider.request({
|
|
741
|
+
method: "wallet_switchEthereumChain",
|
|
742
|
+
params: [{ chainId: chainIdHex }]
|
|
743
|
+
});
|
|
744
|
+
return true;
|
|
745
|
+
} catch (error) {
|
|
746
|
+
const normalized = normalizeWalletError(error);
|
|
747
|
+
if (normalized.code === 4902) {
|
|
748
|
+
return false;
|
|
749
|
+
}
|
|
750
|
+
if (normalized.code === 4001) {
|
|
751
|
+
return false;
|
|
752
|
+
}
|
|
753
|
+
throw normalized;
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
/**
|
|
757
|
+
* Add a new network to the wallet
|
|
758
|
+
*
|
|
759
|
+
* @param chainConfig - Network configuration
|
|
760
|
+
* @returns true if add successful, false if user rejected
|
|
761
|
+
*/
|
|
762
|
+
async addNetwork(chainConfig) {
|
|
763
|
+
try {
|
|
764
|
+
await this.provider.request({
|
|
765
|
+
method: "wallet_addEthereumChain",
|
|
766
|
+
params: [chainConfig]
|
|
767
|
+
});
|
|
768
|
+
return true;
|
|
769
|
+
} catch (error) {
|
|
770
|
+
const normalized = normalizeWalletError(error);
|
|
771
|
+
if (normalized.code === 4001) {
|
|
772
|
+
return false;
|
|
773
|
+
}
|
|
774
|
+
throw normalized;
|
|
775
|
+
}
|
|
776
|
+
}
|
|
777
|
+
};
|
|
778
|
+
function blobToBase64(blob) {
|
|
779
|
+
return new Promise((resolve, reject) => {
|
|
780
|
+
if (blob instanceof Uint8Array || blob instanceof ArrayBuffer) {
|
|
781
|
+
const bytes = blob instanceof Uint8Array ? blob : new Uint8Array(blob);
|
|
782
|
+
const binary = String.fromCharCode(...bytes);
|
|
783
|
+
resolve(btoa(binary));
|
|
784
|
+
} else if (blob instanceof Blob) {
|
|
785
|
+
const reader = new FileReader();
|
|
786
|
+
reader.onloadend = () => {
|
|
787
|
+
const base64 = reader.result.split(",")[1] || reader.result;
|
|
788
|
+
resolve(base64);
|
|
789
|
+
};
|
|
790
|
+
reader.onerror = reject;
|
|
791
|
+
reader.readAsDataURL(blob);
|
|
792
|
+
} else {
|
|
793
|
+
reject(new Error("Unsupported blob type"));
|
|
794
|
+
}
|
|
795
|
+
});
|
|
796
|
+
}
|
|
797
|
+
async function uploadBlob(options) {
|
|
798
|
+
const {
|
|
799
|
+
baseUrl,
|
|
800
|
+
apiKey,
|
|
801
|
+
accessToken,
|
|
802
|
+
blob,
|
|
803
|
+
axiosInstance
|
|
804
|
+
} = options;
|
|
805
|
+
if (!apiKey) {
|
|
806
|
+
throw new Error("apiKey is required");
|
|
807
|
+
}
|
|
808
|
+
if (!accessToken) {
|
|
809
|
+
throw new Error("accessToken is required");
|
|
810
|
+
}
|
|
811
|
+
const blobB64 = await blobToBase64(blob);
|
|
812
|
+
let axiosClient;
|
|
813
|
+
if (axiosInstance) {
|
|
814
|
+
axiosClient = axiosInstance;
|
|
815
|
+
} else {
|
|
816
|
+
axiosClient = axios__default.default.create({
|
|
817
|
+
baseURL: baseUrl.replace(/\/+$/, ""),
|
|
818
|
+
withCredentials: true,
|
|
819
|
+
headers: {
|
|
820
|
+
"Content-Type": "application/json",
|
|
821
|
+
"X-API-Key": String(apiKey),
|
|
822
|
+
Authorization: `Bearer ${accessToken}`
|
|
823
|
+
}
|
|
824
|
+
});
|
|
825
|
+
}
|
|
826
|
+
const response = await axiosClient.post("/blob/upload", {
|
|
827
|
+
blobB64
|
|
828
|
+
});
|
|
829
|
+
if (!response.data?.ok || !response.data?.data?.key) {
|
|
830
|
+
const errorMessage = response.data?.error?.message || "Failed to upload blob";
|
|
831
|
+
throw new Error(errorMessage);
|
|
832
|
+
}
|
|
833
|
+
return { key: response.data.data.key };
|
|
834
|
+
}
|
|
835
|
+
function stripTrailingSlash(u) {
|
|
836
|
+
return u.endsWith("/") ? u.slice(0, -1) : u;
|
|
837
|
+
}
|
|
838
|
+
async function uploadBlobViaPresign(options) {
|
|
839
|
+
const {
|
|
840
|
+
baseUrl,
|
|
841
|
+
apiKey,
|
|
842
|
+
accessToken,
|
|
843
|
+
blob,
|
|
844
|
+
contentType = "application/octet-stream",
|
|
845
|
+
fetchFn
|
|
846
|
+
} = options;
|
|
847
|
+
const f = fetchFn ?? globalThis.fetch;
|
|
848
|
+
if (typeof f !== "function") {
|
|
849
|
+
throw new Error("Global fetch is not available; provide options.fetchFn");
|
|
850
|
+
}
|
|
851
|
+
const base = stripTrailingSlash(baseUrl);
|
|
852
|
+
const presignRes = await f(`${base}/blob/presign`, {
|
|
853
|
+
method: "POST",
|
|
854
|
+
headers: {
|
|
855
|
+
"Content-Type": "application/json",
|
|
856
|
+
...apiKey ? { "X-API-Key": String(apiKey) } : {},
|
|
857
|
+
...accessToken ? { Authorization: `Bearer ${accessToken}` } : {}
|
|
858
|
+
},
|
|
859
|
+
// backend expects { op: 'put', contentType }
|
|
860
|
+
body: JSON.stringify({ op: "put", contentType }),
|
|
861
|
+
// include cookies if backend also uses cookie-based sessions
|
|
862
|
+
credentials: "include"
|
|
863
|
+
});
|
|
864
|
+
const presignJson = await presignRes.json();
|
|
865
|
+
if (!presignRes.ok || presignJson && presignJson.ok === false) {
|
|
866
|
+
const msg = presignJson && presignJson.error && presignJson.error.message || `presign failed with status ${presignRes.status}`;
|
|
867
|
+
throw new Error(typeof msg === "string" ? msg : "Presign failed");
|
|
868
|
+
}
|
|
869
|
+
const url = presignJson?.data?.url ?? presignJson?.url;
|
|
870
|
+
const s3Key = presignJson?.data?.proposedKey ?? presignJson?.s3Key;
|
|
871
|
+
if (!url || !s3Key) {
|
|
872
|
+
throw new Error("Invalid presign response: missing url or s3Key");
|
|
873
|
+
}
|
|
874
|
+
const putHeaders = {
|
|
875
|
+
"Content-Type": contentType,
|
|
876
|
+
"x-amz-server-side-encryption": "AES256"
|
|
877
|
+
};
|
|
878
|
+
const body = typeof Blob !== "undefined" && blob instanceof Blob ? blob : blob;
|
|
879
|
+
const putRes = await f(url, {
|
|
880
|
+
method: "PUT",
|
|
881
|
+
headers: putHeaders,
|
|
882
|
+
body,
|
|
883
|
+
// allow browser CORS preflight to succeed
|
|
884
|
+
// (ignored in Node >= 18 where fetch is not CORS-restricted)
|
|
885
|
+
mode: "cors"
|
|
886
|
+
});
|
|
887
|
+
if (!putRes.ok) {
|
|
888
|
+
const text = await putRes.text().catch(() => "");
|
|
889
|
+
throw new Error(`S3 upload failed (${putRes.status}): ${text || putRes.statusText}`);
|
|
890
|
+
}
|
|
891
|
+
return { s3Key, url };
|
|
892
|
+
}
|
|
893
|
+
|
|
894
|
+
// src/chains/evm/constants.ts
|
|
895
|
+
var ZERO_HASH = "0x0000000000000000000000000000000000000000000000000000000000000000";
|
|
896
|
+
var ZERO_ADDRESS = "0x0000000000000000000000000000000000000000";
|
|
897
|
+
function computeCallsHash(calls) {
|
|
898
|
+
const coder = ethers.AbiCoder.defaultAbiCoder();
|
|
899
|
+
const tupleType = "tuple(address,uint256,bytes,uint256)[]";
|
|
900
|
+
const values = calls.map((c) => [c.target, c.value, c.data, c.gasLimit]);
|
|
901
|
+
const encoded = coder.encode([tupleType], [values]);
|
|
902
|
+
return ethers.keccak256(encoded);
|
|
903
|
+
}
|
|
904
|
+
|
|
905
|
+
// src/chains/evm/eip712/session.ts
|
|
906
|
+
var DOMAIN = (chainId, verifyingContract) => ({
|
|
907
|
+
name: "volr",
|
|
908
|
+
version: "1",
|
|
909
|
+
chainId,
|
|
910
|
+
verifyingContract
|
|
911
|
+
});
|
|
912
|
+
var EIP712_DOMAIN = DOMAIN;
|
|
913
|
+
var TYPES = {
|
|
914
|
+
Call: [
|
|
915
|
+
{ name: "target", type: "address" },
|
|
916
|
+
{ name: "data", type: "bytes" },
|
|
917
|
+
{ name: "value", type: "uint256" },
|
|
918
|
+
{ name: "gasLimit", type: "uint256" }
|
|
919
|
+
],
|
|
920
|
+
SessionAuth: [
|
|
921
|
+
{ name: "chainId", type: "uint256" },
|
|
922
|
+
{ name: "sessionKey", type: "address" },
|
|
923
|
+
{ name: "sessionId", type: "uint64" },
|
|
924
|
+
{ name: "nonce", type: "uint64" },
|
|
925
|
+
{ name: "expiresAt", type: "uint64" },
|
|
926
|
+
{ name: "policyId", type: "bytes32" },
|
|
927
|
+
{ name: "policySnapshotHash", type: "bytes32" },
|
|
928
|
+
{ name: "gasLimitMax", type: "uint256" },
|
|
929
|
+
{ name: "maxFeePerGas", type: "uint256" },
|
|
930
|
+
{ name: "maxPriorityFeePerGas", type: "uint256" },
|
|
931
|
+
{ name: "totalGasCap", type: "uint256" }
|
|
932
|
+
],
|
|
933
|
+
SignedBatch: [
|
|
934
|
+
{ name: "auth", type: "SessionAuth" },
|
|
935
|
+
{ name: "calls", type: "Call[]" },
|
|
936
|
+
{ name: "revertOnFail", type: "bool" },
|
|
937
|
+
{ name: "callsHash", type: "bytes32" }
|
|
938
|
+
]
|
|
939
|
+
};
|
|
940
|
+
function buildSignedBatchMessage(input) {
|
|
941
|
+
const callsHash = computeCallsHash(input.calls);
|
|
942
|
+
const domain = DOMAIN(Number(input.auth.chainId), input.invokerAddress);
|
|
943
|
+
const ensureHex = (val) => val.startsWith("0x") ? val : `0x${val}`;
|
|
944
|
+
const eip712Auth = {
|
|
945
|
+
chainId: BigInt(input.auth.chainId),
|
|
946
|
+
sessionKey: ensureHex(input.auth.sessionKey),
|
|
947
|
+
sessionId: input.auth.sessionId,
|
|
948
|
+
nonce: input.auth.nonce,
|
|
949
|
+
expiresAt: BigInt(input.auth.expiresAt),
|
|
950
|
+
policyId: ensureHex(input.auth.policyId),
|
|
951
|
+
policySnapshotHash: ensureHex(input.auth.policySnapshotHash),
|
|
952
|
+
gasLimitMax: input.auth.gasLimitMax,
|
|
953
|
+
maxFeePerGas: input.auth.maxFeePerGas,
|
|
954
|
+
maxPriorityFeePerGas: input.auth.maxPriorityFeePerGas,
|
|
955
|
+
totalGasCap: input.auth.totalGasCap
|
|
956
|
+
};
|
|
957
|
+
const eip712Calls = input.calls.map((call) => ({
|
|
958
|
+
target: ensureHex(call.target),
|
|
959
|
+
data: ensureHex(call.data),
|
|
960
|
+
value: call.value,
|
|
961
|
+
gasLimit: call.gasLimit
|
|
962
|
+
}));
|
|
963
|
+
return {
|
|
964
|
+
domain,
|
|
965
|
+
primaryType: "SignedBatch",
|
|
966
|
+
message: {
|
|
967
|
+
auth: eip712Auth,
|
|
968
|
+
calls: eip712Calls,
|
|
969
|
+
revertOnFail: input.revertOnFail ?? true,
|
|
970
|
+
callsHash
|
|
971
|
+
},
|
|
972
|
+
callsHash
|
|
973
|
+
};
|
|
974
|
+
}
|
|
975
|
+
async function signSession(input) {
|
|
976
|
+
if (input.from.toLowerCase() !== input.auth.sessionKey.toLowerCase()) {
|
|
977
|
+
throw new Error(
|
|
978
|
+
`from address (${input.from}) does not match sessionKey (${input.auth.sessionKey})`
|
|
979
|
+
);
|
|
980
|
+
}
|
|
981
|
+
const message = buildSignedBatchMessage({
|
|
982
|
+
auth: input.auth,
|
|
983
|
+
calls: input.calls,
|
|
984
|
+
invokerAddress: input.invokerAddress
|
|
985
|
+
});
|
|
986
|
+
if (!input.signer.signTyped) {
|
|
987
|
+
throw new Error("Signer must support signTyped for EIP-712 signing");
|
|
988
|
+
}
|
|
989
|
+
const typedData = {
|
|
990
|
+
domain: message.domain,
|
|
991
|
+
types: TYPES,
|
|
992
|
+
primaryType: message.primaryType,
|
|
993
|
+
message: message.message
|
|
994
|
+
};
|
|
995
|
+
const sessionSig = await input.signer.signTyped(typedData);
|
|
996
|
+
return {
|
|
997
|
+
sessionSig,
|
|
998
|
+
callsHash: message.callsHash
|
|
999
|
+
};
|
|
1000
|
+
}
|
|
1001
|
+
async function signAuthorization(input) {
|
|
1002
|
+
const { signer, chainId, address, nonce } = input;
|
|
1003
|
+
if (chainId === 0) {
|
|
1004
|
+
throw new Error(`${ERR_INVALID_PARAM}: chainId cannot be 0 (use specific chain ID)`);
|
|
1005
|
+
}
|
|
1006
|
+
if (nonce < 0n) {
|
|
1007
|
+
throw new Error(`${ERR_INVALID_PARAM}: nonce must be >= 0`);
|
|
1008
|
+
}
|
|
1009
|
+
if (!address.startsWith("0x") || address.length !== 42) {
|
|
1010
|
+
throw new Error(`${ERR_INVALID_PARAM}: address must be valid Ethereum address`);
|
|
1011
|
+
}
|
|
1012
|
+
const digest = ethers.hashAuthorization({
|
|
1013
|
+
chainId: BigInt(chainId),
|
|
1014
|
+
address: address.toLowerCase(),
|
|
1015
|
+
nonce
|
|
1016
|
+
});
|
|
1017
|
+
const digestBytes = ethers.getBytes(digest);
|
|
1018
|
+
let sig = await signer.signRawHash(digestBytes);
|
|
1019
|
+
const sBigInt = BigInt("0x" + Array.from(sig.s).map((b) => b.toString(16).padStart(2, "0")).join(""));
|
|
1020
|
+
const SECP256K1_N = secp256k1.secp256k1.CURVE.n;
|
|
1021
|
+
const nHalf = SECP256K1_N / 2n;
|
|
1022
|
+
let finalS = sig.s;
|
|
1023
|
+
let finalYParity = sig.yParity;
|
|
1024
|
+
if (sBigInt > nHalf) {
|
|
1025
|
+
const normalizedS = SECP256K1_N - sBigInt;
|
|
1026
|
+
const normalizedSHex = normalizedS.toString(16).padStart(64, "0");
|
|
1027
|
+
finalS = new Uint8Array(32);
|
|
1028
|
+
for (let i = 0; i < 32; i++) {
|
|
1029
|
+
finalS[i] = parseInt(normalizedSHex.slice(i * 2, i * 2 + 2), 16);
|
|
1030
|
+
}
|
|
1031
|
+
finalYParity = finalYParity === 0 ? 1 : 0;
|
|
1032
|
+
}
|
|
1033
|
+
const rHex = Array.from(sig.r).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
1034
|
+
const sHex = Array.from(finalS).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
1035
|
+
return {
|
|
1036
|
+
chainId,
|
|
1037
|
+
address: address.toLowerCase(),
|
|
1038
|
+
nonce,
|
|
1039
|
+
yParity: finalYParity,
|
|
1040
|
+
r: `0x${rHex}`,
|
|
1041
|
+
s: `0x${sHex}`
|
|
1042
|
+
};
|
|
1043
|
+
}
|
|
1044
|
+
|
|
1045
|
+
// src/chains/evm/nonce.ts
|
|
1046
|
+
async function getAuthNonce(client, from, mode) {
|
|
1047
|
+
if (!client.getTransactionCount) {
|
|
1048
|
+
throw new Error("RPC client must implement getTransactionCount method. Use ExtendedRPCClient.");
|
|
1049
|
+
}
|
|
1050
|
+
const nonce = await client.getTransactionCount(from, "latest");
|
|
1051
|
+
if (mode === "self") {
|
|
1052
|
+
return nonce + 1n;
|
|
1053
|
+
} else {
|
|
1054
|
+
return nonce;
|
|
1055
|
+
}
|
|
1056
|
+
}
|
|
1057
|
+
function createPasskeyProvider(passkey, options) {
|
|
1058
|
+
let unwrappedKeypair = null;
|
|
1059
|
+
const ensureSession = async (opts) => {
|
|
1060
|
+
if (opts?.force && unwrappedKeypair) {
|
|
1061
|
+
console.log("[PasskeyProvider] force=true: zeroizing existing session");
|
|
1062
|
+
zeroize(unwrappedKeypair.privateKey);
|
|
1063
|
+
zeroize(unwrappedKeypair.publicKey);
|
|
1064
|
+
unwrappedKeypair = null;
|
|
1065
|
+
}
|
|
1066
|
+
if (unwrappedKeypair) {
|
|
1067
|
+
return;
|
|
1068
|
+
}
|
|
1069
|
+
if (opts?.interactive || opts?.force) {
|
|
1070
|
+
console.log("[PasskeyProvider] interactive mode: triggering WebAuthn prompt");
|
|
1071
|
+
const prfSalt = deriveWrapKey(options.prfInput);
|
|
1072
|
+
const { prfOutput } = await passkey.authenticate({
|
|
1073
|
+
salt: prfSalt,
|
|
1074
|
+
credentialId: options.prfInput.credentialId
|
|
1075
|
+
});
|
|
1076
|
+
const wrapKey = prfOutput;
|
|
1077
|
+
const aad = options.aad || new TextEncoder().encode("volr/master-seed/v1");
|
|
1078
|
+
const masterSeed = await unsealMasterSeed(
|
|
1079
|
+
options.encryptedBlob.cipher,
|
|
1080
|
+
wrapKey,
|
|
1081
|
+
aad,
|
|
1082
|
+
options.encryptedBlob.nonce
|
|
1083
|
+
);
|
|
1084
|
+
const keypair = deriveEvmKey({ masterSeed });
|
|
1085
|
+
unwrappedKeypair = {
|
|
1086
|
+
privateKey: keypair.privateKey,
|
|
1087
|
+
publicKey: keypair.publicKey,
|
|
1088
|
+
address: keypair.address
|
|
1089
|
+
};
|
|
1090
|
+
zeroize(masterSeed);
|
|
1091
|
+
zeroize(wrapKey);
|
|
1092
|
+
zeroize(prfSalt);
|
|
1093
|
+
} else {
|
|
1094
|
+
console.warn("[PasskeyProvider] Non-interactive mode: using deterministic wrap key (insecure for TTL=0)");
|
|
1095
|
+
const wrapKey = deriveWrapKey(options.prfInput);
|
|
1096
|
+
const aad = options.aad || new TextEncoder().encode("volr/master-seed/v1");
|
|
1097
|
+
const masterSeed = await unsealMasterSeed(
|
|
1098
|
+
options.encryptedBlob.cipher,
|
|
1099
|
+
wrapKey,
|
|
1100
|
+
aad,
|
|
1101
|
+
options.encryptedBlob.nonce
|
|
1102
|
+
);
|
|
1103
|
+
const keypair = deriveEvmKey({ masterSeed });
|
|
1104
|
+
unwrappedKeypair = {
|
|
1105
|
+
privateKey: keypair.privateKey,
|
|
1106
|
+
publicKey: keypair.publicKey,
|
|
1107
|
+
address: keypair.address
|
|
1108
|
+
};
|
|
1109
|
+
zeroize(masterSeed);
|
|
1110
|
+
zeroize(wrapKey);
|
|
1111
|
+
}
|
|
1112
|
+
};
|
|
1113
|
+
const getAddress = async () => {
|
|
1114
|
+
await ensureSession();
|
|
1115
|
+
if (!unwrappedKeypair) {
|
|
1116
|
+
throw new Error(`${ERR_INVALID_PARAM}: Session not established`);
|
|
1117
|
+
}
|
|
1118
|
+
return toChecksumAddress(unwrappedKeypair.address);
|
|
1119
|
+
};
|
|
1120
|
+
const signMessage = async (hash32) => {
|
|
1121
|
+
if (hash32.length !== 32) {
|
|
1122
|
+
throw new Error(`${ERR_INVALID_PARAM}: Message hash must be 32 bytes, got ${hash32.length}`);
|
|
1123
|
+
}
|
|
1124
|
+
await ensureSession();
|
|
1125
|
+
if (!unwrappedKeypair) {
|
|
1126
|
+
throw new Error(`${ERR_INVALID_PARAM}: Session not established`);
|
|
1127
|
+
}
|
|
1128
|
+
const sig = evmSign(unwrappedKeypair.privateKey, hash32);
|
|
1129
|
+
return {
|
|
1130
|
+
r: sig.r,
|
|
1131
|
+
s: sig.s,
|
|
1132
|
+
yParity: sig.yParity
|
|
1133
|
+
};
|
|
1134
|
+
};
|
|
1135
|
+
const signTypedData = async (input) => {
|
|
1136
|
+
await ensureSession();
|
|
1137
|
+
if (!unwrappedKeypair) {
|
|
1138
|
+
throw new Error(`${ERR_INVALID_PARAM}: Session not established`);
|
|
1139
|
+
}
|
|
1140
|
+
console.log("[PasskeyProvider] signTypedData input:", JSON.stringify(
|
|
1141
|
+
input,
|
|
1142
|
+
(_key, value) => typeof value === "bigint" ? value.toString() : value
|
|
1143
|
+
));
|
|
1144
|
+
const hash = ethers.TypedDataEncoder.hash(input.domain, input.types, input.message);
|
|
1145
|
+
const msgHash = hash;
|
|
1146
|
+
console.log("[PasskeyProvider] msgHash:", msgHash);
|
|
1147
|
+
const msgHashBytes = new Uint8Array(32);
|
|
1148
|
+
const hex = msgHash.startsWith("0x") ? msgHash.slice(2) : msgHash;
|
|
1149
|
+
for (let i = 0; i < 32; i++) {
|
|
1150
|
+
msgHashBytes[i] = parseInt(hex.slice(i * 2, i * 2 + 2), 16);
|
|
1151
|
+
}
|
|
1152
|
+
const sig = await signMessage(msgHashBytes);
|
|
1153
|
+
const v = sig.yParity + 27;
|
|
1154
|
+
const rHex = Array.from(sig.r).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
1155
|
+
const sHex = Array.from(sig.s).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
1156
|
+
const vHex = v.toString(16).padStart(2, "0");
|
|
1157
|
+
return `0x${rHex}${sHex}${vHex}`;
|
|
1158
|
+
};
|
|
1159
|
+
const getPublicKey = async () => {
|
|
1160
|
+
await ensureSession();
|
|
1161
|
+
if (!unwrappedKeypair) {
|
|
1162
|
+
throw new Error(`${ERR_INVALID_PARAM}: Session not established`);
|
|
1163
|
+
}
|
|
1164
|
+
return new Uint8Array(unwrappedKeypair.publicKey);
|
|
1165
|
+
};
|
|
1166
|
+
const lock = async () => {
|
|
1167
|
+
if (unwrappedKeypair) {
|
|
1168
|
+
console.log("[PasskeyProvider] lock(): zeroizing sensitive data from memory");
|
|
1169
|
+
zeroize(unwrappedKeypair.privateKey);
|
|
1170
|
+
zeroize(unwrappedKeypair.publicKey);
|
|
1171
|
+
unwrappedKeypair = null;
|
|
1172
|
+
}
|
|
1173
|
+
};
|
|
1174
|
+
return {
|
|
1175
|
+
keyStorageType: "passkey",
|
|
1176
|
+
ensureSession,
|
|
1177
|
+
getAddress,
|
|
1178
|
+
signMessage,
|
|
1179
|
+
signTypedData,
|
|
1180
|
+
getPublicKey,
|
|
1181
|
+
lock,
|
|
1182
|
+
capabilities: {
|
|
1183
|
+
secp256k1: true,
|
|
1184
|
+
p256: false
|
|
1185
|
+
// Passkey provider uses secp256k1 after unwrapping
|
|
1186
|
+
}
|
|
1187
|
+
};
|
|
1188
|
+
}
|
|
1189
|
+
|
|
1190
|
+
// src/chains/evm/providers/mpcProvider.ts
|
|
1191
|
+
function createMpcProvider(transport) {
|
|
1192
|
+
return {
|
|
1193
|
+
keyStorageType: "mpc",
|
|
1194
|
+
ensureSession: () => transport.ensureSession(),
|
|
1195
|
+
getAddress: () => transport.getAddress(),
|
|
1196
|
+
signMessage: (hash32) => transport.signMessage(hash32),
|
|
1197
|
+
signTypedData: (input) => transport.signTypedData(input),
|
|
1198
|
+
getPublicKey: transport.getPublicKey ? () => transport.getPublicKey() : void 0,
|
|
1199
|
+
capabilities: {
|
|
1200
|
+
secp256k1: true,
|
|
1201
|
+
p256: false
|
|
1202
|
+
// MPC uses secp256k1 via backend
|
|
1203
|
+
}
|
|
1204
|
+
};
|
|
1205
|
+
}
|
|
1206
|
+
|
|
1207
|
+
// src/chains/evm/signers/selectSigner.ts
|
|
1208
|
+
async function selectSigner(ctx) {
|
|
1209
|
+
const { provider } = ctx;
|
|
1210
|
+
if (provider.getPublicKey) {
|
|
1211
|
+
return new ProviderBackedSigner(provider);
|
|
1212
|
+
}
|
|
1213
|
+
throw new Error(
|
|
1214
|
+
`${ERR_INVALID_PARAM}: Provider must expose getPublicKey() for signer creation. Use provider.signMessage() directly or implement getPublicKey() in provider.`
|
|
1215
|
+
);
|
|
1216
|
+
}
|
|
1217
|
+
var ProviderBackedSigner = class {
|
|
1218
|
+
constructor(provider) {
|
|
1219
|
+
this.provider = provider;
|
|
1220
|
+
if (!provider.getPublicKey) {
|
|
1221
|
+
throw new Error(`${ERR_INVALID_PARAM}: Provider must implement getPublicKey()`);
|
|
1222
|
+
}
|
|
1223
|
+
}
|
|
1224
|
+
async getPublicKey() {
|
|
1225
|
+
return this.provider.getPublicKey();
|
|
1226
|
+
}
|
|
1227
|
+
async signMessage(msgHash32) {
|
|
1228
|
+
return this.provider.signMessage(msgHash32);
|
|
1229
|
+
}
|
|
1230
|
+
async signRawHash(digest) {
|
|
1231
|
+
return this.provider.signMessage(digest);
|
|
1232
|
+
}
|
|
1233
|
+
async signTyped(typed) {
|
|
1234
|
+
if (typeof typed === "object" && typed !== null) {
|
|
1235
|
+
const input = typed;
|
|
1236
|
+
return this.provider.signTypedData(input);
|
|
1237
|
+
}
|
|
1238
|
+
throw new Error(`${ERR_INVALID_PARAM}: Invalid typed data input`);
|
|
1239
|
+
}
|
|
1240
|
+
};
|
|
1241
|
+
|
|
1242
|
+
// src/wallet/signer.ts
|
|
1243
|
+
var ProviderBackedSigner2 = class {
|
|
1244
|
+
constructor(provider) {
|
|
1245
|
+
this.provider = provider;
|
|
1246
|
+
if (!provider.getPublicKey) {
|
|
1247
|
+
throw new Error(`${ERR_INVALID_PARAM}: Provider must implement getPublicKey()`);
|
|
1248
|
+
}
|
|
1249
|
+
}
|
|
1250
|
+
async getPublicKey() {
|
|
1251
|
+
return this.provider.getPublicKey();
|
|
1252
|
+
}
|
|
1253
|
+
async signMessage(msgHash32) {
|
|
1254
|
+
return this.provider.signMessage(msgHash32);
|
|
1255
|
+
}
|
|
1256
|
+
async signRawHash(_digest) {
|
|
1257
|
+
throw new Error("EIP-7702 raw hash signing not supported by wallet provider. Use Secp256k1SoftwareSigner for EIP-7702.");
|
|
1258
|
+
}
|
|
1259
|
+
async signTyped(typed) {
|
|
1260
|
+
if (typeof typed === "object" && typed !== null) {
|
|
1261
|
+
const input = typed;
|
|
1262
|
+
return this.provider.signTypedData(input);
|
|
1263
|
+
}
|
|
1264
|
+
throw new Error(`${ERR_INVALID_PARAM}: Invalid typed data input`);
|
|
1265
|
+
}
|
|
1266
|
+
};
|
|
1267
|
+
async function selectSigner2(ctx) {
|
|
1268
|
+
const { provider, secpKey } = ctx;
|
|
1269
|
+
if (provider) {
|
|
1270
|
+
if (!provider.getPublicKey) {
|
|
1271
|
+
throw new Error(
|
|
1272
|
+
`${ERR_INVALID_PARAM}: Provider must implement getPublicKey() for signer creation. Use provider.signMessage() directly or implement getPublicKey() in provider.`
|
|
1273
|
+
);
|
|
1274
|
+
}
|
|
1275
|
+
return {
|
|
1276
|
+
kind: "secp256k1",
|
|
1277
|
+
// Provider signs with secp256k1
|
|
1278
|
+
signer: new ProviderBackedSigner2(provider)
|
|
1279
|
+
};
|
|
1280
|
+
}
|
|
1281
|
+
if (secpKey) {
|
|
1282
|
+
return {
|
|
1283
|
+
kind: "secp256k1",
|
|
1284
|
+
signer: new Secp256k1SoftwareSigner(secpKey)
|
|
1285
|
+
};
|
|
1286
|
+
}
|
|
1287
|
+
throw new Error(
|
|
1288
|
+
`${ERR_INVALID_PARAM}: No signer input available. Provide either provider, passkey (for P-256), or secpKey (for secp256k1).`
|
|
1289
|
+
);
|
|
1290
|
+
}
|
|
1291
|
+
|
|
1292
|
+
exports.ChainNotAddedError = ChainNotAddedError;
|
|
1293
|
+
exports.DEFAULT_EVM_PATH = DEFAULT_EVM_PATH;
|
|
1294
|
+
exports.DOMAIN = DOMAIN;
|
|
1295
|
+
exports.EIP1193ErrorCode = EIP1193ErrorCode;
|
|
1296
|
+
exports.EIP712_DOMAIN = EIP712_DOMAIN;
|
|
1297
|
+
exports.ERR_AAD_MISMATCH = ERR_AAD_MISMATCH;
|
|
1298
|
+
exports.ERR_CHAIN_MISMATCH = ERR_CHAIN_MISMATCH;
|
|
1299
|
+
exports.ERR_CRYPTO_FAIL = ERR_CRYPTO_FAIL;
|
|
1300
|
+
exports.ERR_INVALID_PARAM = ERR_INVALID_PARAM;
|
|
1301
|
+
exports.ERR_LOW_ENTROPY = ERR_LOW_ENTROPY;
|
|
1302
|
+
exports.ERR_NONCE_SIZE = ERR_NONCE_SIZE;
|
|
1303
|
+
exports.ExternalWalletSigner = ExternalWalletSigner;
|
|
1304
|
+
exports.PasskeyP256Signer = PasskeyP256Signer;
|
|
1305
|
+
exports.RequestPendingError = RequestPendingError;
|
|
1306
|
+
exports.Secp256k1SoftwareSigner = Secp256k1SoftwareSigner;
|
|
1307
|
+
exports.TYPES = TYPES;
|
|
1308
|
+
exports.UnauthorizedError = UnauthorizedError;
|
|
1309
|
+
exports.UnsupportedMethodError = UnsupportedMethodError;
|
|
1310
|
+
exports.UserRejectedError = UserRejectedError;
|
|
1311
|
+
exports.VolrError = VolrError;
|
|
1312
|
+
exports.WalletError = WalletError;
|
|
1313
|
+
exports.WrongNetworkError = WrongNetworkError;
|
|
1314
|
+
exports.ZERO_ADDRESS = ZERO_ADDRESS;
|
|
1315
|
+
exports.ZERO_HASH = ZERO_HASH;
|
|
1316
|
+
exports.aesGcmDecrypt = aesGcmDecrypt;
|
|
1317
|
+
exports.aesGcmEncrypt = aesGcmEncrypt;
|
|
1318
|
+
exports.buildSignedBatchMessage = buildSignedBatchMessage;
|
|
1319
|
+
exports.computeCallsHash = computeCallsHash;
|
|
1320
|
+
exports.createMasterKeyProvider = createMasterKeyProvider;
|
|
1321
|
+
exports.createMpcProvider = createMpcProvider;
|
|
1322
|
+
exports.createPasskeyProvider = createPasskeyProvider;
|
|
1323
|
+
exports.deriveEvmKey = deriveEvmKey;
|
|
1324
|
+
exports.deriveWrapKey = deriveWrapKey;
|
|
1325
|
+
exports.evmSign = evmSign;
|
|
1326
|
+
exports.evmVerify = evmVerify;
|
|
1327
|
+
exports.getAuthNonce = getAuthNonce;
|
|
1328
|
+
exports.getRandomBytes = getRandomBytes;
|
|
1329
|
+
exports.hkdfSha256 = hkdfSha256;
|
|
1330
|
+
exports.normalizeWalletError = normalizeWalletError;
|
|
1331
|
+
exports.sealMasterSeed = sealMasterSeed;
|
|
1332
|
+
exports.selectSigner = selectSigner;
|
|
1333
|
+
exports.selectSignerLegacy = selectSigner2;
|
|
1334
|
+
exports.signAuthorization = signAuthorization;
|
|
1335
|
+
exports.signSession = signSession;
|
|
1336
|
+
exports.toChecksumAddress = toChecksumAddress;
|
|
1337
|
+
exports.unsealMasterSeed = unsealMasterSeed;
|
|
1338
|
+
exports.uploadBlob = uploadBlob;
|
|
1339
|
+
exports.uploadBlobViaPresign = uploadBlobViaPresign;
|
|
1340
|
+
exports.zeroize = zeroize;
|
|
1341
|
+
//# sourceMappingURL=index.cjs.map
|
|
1342
|
+
//# sourceMappingURL=index.cjs.map
|