@volr/react 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 ADDED
@@ -0,0 +1,2611 @@
1
+ 'use strict';
2
+
3
+ var react = require('react');
4
+ var sdkCore = require('@volr/sdk-core');
5
+ var axios = require('axios');
6
+ var jsxRuntime = require('react/jsx-runtime');
7
+ var viem = require('viem');
8
+
9
+ function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
10
+
11
+ var axios__default = /*#__PURE__*/_interopDefault(axios);
12
+
13
+ // src/react/Provider.tsx
14
+ var VolrContext = react.createContext(null);
15
+ var InternalAuthContext = react.createContext(null);
16
+ function mapBackendError(error) {
17
+ return new sdkCore.VolrError(error.code, error.message);
18
+ }
19
+ function isErrorResponse(response) {
20
+ return !response.ok;
21
+ }
22
+ function unwrapResponse(response) {
23
+ if (isErrorResponse(response)) {
24
+ throw mapBackendError(response.error);
25
+ }
26
+ return response.data;
27
+ }
28
+
29
+ // src/utils/ssr.ts
30
+ function isBrowser() {
31
+ return typeof window !== "undefined" && typeof document !== "undefined";
32
+ }
33
+ var safeStorage = {
34
+ getItem(key) {
35
+ if (!isBrowser()) {
36
+ return null;
37
+ }
38
+ try {
39
+ const value = localStorage.getItem(key);
40
+ if (!value || value === "undefined" || value === "null") {
41
+ return null;
42
+ }
43
+ return value;
44
+ } catch {
45
+ return null;
46
+ }
47
+ },
48
+ setItem(key, value) {
49
+ if (!isBrowser()) {
50
+ return;
51
+ }
52
+ try {
53
+ localStorage.setItem(key, value);
54
+ } catch {
55
+ }
56
+ },
57
+ removeItem(key) {
58
+ if (!isBrowser()) {
59
+ return;
60
+ }
61
+ try {
62
+ localStorage.removeItem(key);
63
+ } catch {
64
+ }
65
+ },
66
+ clear() {
67
+ if (!isBrowser()) {
68
+ return;
69
+ }
70
+ try {
71
+ localStorage.clear();
72
+ } catch {
73
+ }
74
+ }
75
+ };
76
+
77
+ // node_modules/@volr/shared/src/constants/storage.ts
78
+ var STORAGE_KEYS = {
79
+ accessToken: "volr:accessToken",
80
+ user: "volr:user",
81
+ provider: "volr:provider",
82
+ credentialId: "volr:credentialId",
83
+ selectedProject: "volr:selectedProject",
84
+ lastEmail: "volr:lastEmail"
85
+ };
86
+ var STORAGE_CHANNELS = {
87
+ session: "volr:session"
88
+ };
89
+
90
+ // src/headless/client.ts
91
+ var APIClient = class {
92
+ constructor(config) {
93
+ this.refreshPromise = null;
94
+ this.accessToken = null;
95
+ this.apiKey = null;
96
+ this.apiKey = config.apiKey || null;
97
+ this.accessToken = safeStorage.getItem(STORAGE_KEYS.accessToken);
98
+ this.api = axios__default.default.create({
99
+ baseURL: config.baseUrl.replace(/\/+$/, ""),
100
+ withCredentials: true,
101
+ headers: {
102
+ "Content-Type": "application/json"
103
+ },
104
+ transformRequest: [
105
+ (data) => {
106
+ if (data && typeof data === "object") {
107
+ return JSON.stringify(
108
+ data,
109
+ (_, value) => typeof value === "bigint" ? value.toString() : value
110
+ );
111
+ }
112
+ return data;
113
+ }
114
+ ]
115
+ });
116
+ this.api.interceptors.request.use((config2) => {
117
+ if (!config2.headers) {
118
+ config2.headers = {};
119
+ }
120
+ if (this.apiKey) {
121
+ config2.headers["X-API-Key"] = this.apiKey;
122
+ } else {
123
+ console.warn("[APIClient] X-API-Key not set. Some endpoints may fail.");
124
+ }
125
+ if (this.accessToken) {
126
+ config2.headers["Authorization"] = `Bearer ${this.accessToken}`;
127
+ }
128
+ return config2;
129
+ });
130
+ this.api.interceptors.response.use(
131
+ (response) => response,
132
+ async (error) => {
133
+ const originalRequest = error.config;
134
+ if (error.response?.status === 401 && !originalRequest._retry) {
135
+ originalRequest._retry = true;
136
+ try {
137
+ await this.refreshAccessToken();
138
+ if (this.accessToken) {
139
+ originalRequest.headers["Authorization"] = `Bearer ${this.accessToken}`;
140
+ }
141
+ return this.api(originalRequest);
142
+ } catch (refreshError) {
143
+ return Promise.reject(refreshError);
144
+ }
145
+ }
146
+ return Promise.reject(error);
147
+ }
148
+ );
149
+ }
150
+ /**
151
+ * Set API key
152
+ */
153
+ setApiKey(apiKey) {
154
+ this.apiKey = apiKey;
155
+ }
156
+ /**
157
+ * Get API key
158
+ */
159
+ getApiKey() {
160
+ return this.apiKey;
161
+ }
162
+ /**
163
+ * Set access token
164
+ */
165
+ setAccessToken(token) {
166
+ this.accessToken = token;
167
+ if (token) {
168
+ safeStorage.setItem(STORAGE_KEYS.accessToken, token);
169
+ } else {
170
+ safeStorage.removeItem(STORAGE_KEYS.accessToken);
171
+ }
172
+ }
173
+ /**
174
+ * Get access token
175
+ */
176
+ getAccessToken() {
177
+ return this.accessToken;
178
+ }
179
+ /**
180
+ * Refresh access token (single-flight)
181
+ */
182
+ async refreshAccessToken() {
183
+ if (this.refreshPromise) {
184
+ return this.refreshPromise;
185
+ }
186
+ this.refreshPromise = (async () => {
187
+ try {
188
+ const response = await this.api.post(
189
+ "/auth/refresh",
190
+ {}
191
+ );
192
+ const data = response.data;
193
+ if (isErrorResponse(data)) {
194
+ this.setAccessToken(null);
195
+ safeStorage.removeItem(STORAGE_KEYS.user);
196
+ throw new Error(data.error.message);
197
+ }
198
+ this.setAccessToken(data.data.accessToken);
199
+ if (data.data.user) {
200
+ safeStorage.setItem(STORAGE_KEYS.user, JSON.stringify(data.data.user));
201
+ }
202
+ } finally {
203
+ this.refreshPromise = null;
204
+ }
205
+ })();
206
+ return this.refreshPromise;
207
+ }
208
+ /**
209
+ * Make API request with automatic retry on 401
210
+ */
211
+ async request(endpoint, options = {}, idempotencyKey) {
212
+ const normalizedEndpoint = endpoint.startsWith("/") ? endpoint : `/${endpoint}`;
213
+ const config = {
214
+ method: options.method || "GET",
215
+ url: normalizedEndpoint
216
+ };
217
+ if (options.body) {
218
+ config.data = typeof options.body === "string" ? JSON.parse(options.body) : options.body;
219
+ }
220
+ if (idempotencyKey) {
221
+ config.headers = {
222
+ ...config.headers,
223
+ "Idempotency-Key": idempotencyKey
224
+ };
225
+ }
226
+ const response = await this.api.request(config);
227
+ return unwrapResponse(response.data);
228
+ }
229
+ /**
230
+ * POST request
231
+ */
232
+ async post(endpoint, body, idempotencyKey) {
233
+ const normalizedEndpoint = endpoint.startsWith("/") ? endpoint : `/${endpoint}`;
234
+ const config = {
235
+ method: "POST",
236
+ url: normalizedEndpoint,
237
+ data: body
238
+ };
239
+ if (idempotencyKey) {
240
+ config.headers = {
241
+ "Idempotency-Key": idempotencyKey
242
+ };
243
+ }
244
+ const response = await this.api.request(config);
245
+ return unwrapResponse(response.data);
246
+ }
247
+ /**
248
+ * POST request that returns raw binary (ArrayBuffer)
249
+ * - Uses axios instance with interceptors (auto 401 refresh)
250
+ * - Applies X-API-Key and Authorization automatically
251
+ */
252
+ async postBinary(endpoint, body) {
253
+ const normalizedEndpoint = endpoint.startsWith("/") ? endpoint : `/${endpoint}`;
254
+ if (!this.apiKey) {
255
+ throw new Error(
256
+ "API key not configured. Please ensure VolrProvider is initialized with projectApiKey in config."
257
+ );
258
+ }
259
+ const response = await this.api.request({
260
+ method: "POST",
261
+ url: normalizedEndpoint,
262
+ data: body,
263
+ responseType: "arraybuffer",
264
+ headers: {
265
+ "X-API-Key": this.apiKey,
266
+ // Explicitly set API key header
267
+ ...this.accessToken && { Authorization: `Bearer ${this.accessToken}` }
268
+ }
269
+ });
270
+ return response.data;
271
+ }
272
+ /**
273
+ * GET request
274
+ */
275
+ async get(endpoint) {
276
+ const normalizedEndpoint = endpoint.startsWith("/") ? endpoint : `/${endpoint}`;
277
+ const response = await this.api.get(normalizedEndpoint);
278
+ return unwrapResponse(response.data);
279
+ }
280
+ };
281
+
282
+ // src/headless/auth-sync.ts
283
+ var SessionSync = class {
284
+ constructor() {
285
+ this.channel = null;
286
+ this.listeners = /* @__PURE__ */ new Set();
287
+ if (typeof window !== "undefined" && "BroadcastChannel" in window) {
288
+ this.channel = new BroadcastChannel(STORAGE_CHANNELS.session);
289
+ this.channel.onmessage = (e) => {
290
+ this.handleChannelMessage(e.data);
291
+ };
292
+ }
293
+ if (typeof window !== "undefined") {
294
+ window.addEventListener("storage", (e) => {
295
+ if (e.key === STORAGE_KEYS.accessToken || e.key === STORAGE_KEYS.user) {
296
+ this.notifyListeners({
297
+ type: "REFRESH",
298
+ payload: {
299
+ accessToken: safeStorage.getItem(STORAGE_KEYS.accessToken) || ""
300
+ }
301
+ });
302
+ }
303
+ });
304
+ }
305
+ }
306
+ /**
307
+ * Subscribe to session events
308
+ */
309
+ subscribe(listener) {
310
+ this.listeners.add(listener);
311
+ return () => {
312
+ this.listeners.delete(listener);
313
+ };
314
+ }
315
+ /**
316
+ * Broadcast session event to other tabs
317
+ */
318
+ broadcast(event) {
319
+ if (this.channel) {
320
+ this.channel.postMessage(event);
321
+ }
322
+ }
323
+ /**
324
+ * Handle channel message
325
+ */
326
+ handleChannelMessage(event) {
327
+ this.notifyListeners(event);
328
+ }
329
+ /**
330
+ * Notify all listeners
331
+ */
332
+ notifyListeners(event) {
333
+ for (const listener of this.listeners) {
334
+ try {
335
+ listener(event);
336
+ } catch (error) {
337
+ console.error("SessionSync listener error:", error);
338
+ }
339
+ }
340
+ }
341
+ /**
342
+ * Cleanup
343
+ */
344
+ destroy() {
345
+ if (this.channel) {
346
+ this.channel.close();
347
+ this.channel = null;
348
+ }
349
+ this.listeners.clear();
350
+ }
351
+ };
352
+
353
+ // src/config/webauthn.ts
354
+ var AUTHENTICATOR_SELECTION = {
355
+ authenticatorAttachment: "platform",
356
+ userVerification: "required",
357
+ residentKey: "required"
358
+ };
359
+ var CREDENTIAL_MEDIATION = "required";
360
+ var USER_VERIFICATION = "required";
361
+ var WEBAUTHN_TIMEOUT = 6e4;
362
+ var PUBKEY_CRED_PARAMS = [
363
+ {
364
+ type: "public-key",
365
+ alg: -7
366
+ // ES256 (P-256)
367
+ }
368
+ ];
369
+ var ATTESTATION = "direct";
370
+
371
+ // src/adapters/passkey.ts
372
+ function createPasskeyAdapter(options = {}) {
373
+ const rpId = options.rpId || (typeof window !== "undefined" ? window.location.hostname : "localhost");
374
+ return {
375
+ async getPublicKey() {
376
+ const credential = await navigator.credentials.get({
377
+ publicKey: {
378
+ challenge: new Uint8Array(32),
379
+ // Dummy challenge for getting public key
380
+ allowCredentials: [],
381
+ // Empty = any credential
382
+ userVerification: "required"
383
+ }
384
+ });
385
+ if (!credential || !("response" in credential)) {
386
+ throw new Error("Failed to get passkey credential");
387
+ }
388
+ throw new Error(
389
+ "getPublicKey not implemented - use credential creation response"
390
+ );
391
+ },
392
+ async signP256(msgHash32) {
393
+ if (msgHash32.length !== 32) {
394
+ throw new Error("Message hash must be 32 bytes");
395
+ }
396
+ const credential = await navigator.credentials.get({
397
+ publicKey: {
398
+ challenge: msgHash32,
399
+ allowCredentials: [],
400
+ // Empty = any credential
401
+ userVerification: "required"
402
+ }
403
+ });
404
+ if (!credential || !("response" in credential)) {
405
+ throw new Error("Failed to get passkey credential for signing");
406
+ }
407
+ const response = credential.response;
408
+ const signature = response.signature;
409
+ const derSignature = new Uint8Array(signature);
410
+ if (derSignature[0] !== 48) {
411
+ throw new Error("Invalid DER signature format");
412
+ }
413
+ let offset = 2;
414
+ if (derSignature[offset] !== 2) {
415
+ throw new Error("Invalid DER signature: expected r marker");
416
+ }
417
+ offset++;
418
+ const rLength = derSignature[offset++];
419
+ const rBytes = derSignature.slice(offset, offset + rLength);
420
+ offset += rLength;
421
+ if (derSignature[offset] !== 2) {
422
+ throw new Error("Invalid DER signature: expected s marker");
423
+ }
424
+ offset++;
425
+ const sLength = derSignature[offset++];
426
+ const sBytes = derSignature.slice(offset, offset + sLength);
427
+ const r = new Uint8Array(32);
428
+ const s = new Uint8Array(32);
429
+ if (rBytes.length > 32 || sBytes.length > 32) {
430
+ throw new Error("Signature r or s exceeds 32 bytes");
431
+ }
432
+ r.set(rBytes, 32 - rBytes.length);
433
+ s.set(sBytes, 32 - sBytes.length);
434
+ return { r, s };
435
+ },
436
+ async authenticate(prfInput) {
437
+ if (!prfInput.credentialId) {
438
+ throw new Error(
439
+ "[PasskeyAdapter] credentialId is required for authentication. This usually means the passkey was not properly registered or user data is incomplete. Please re-enroll your passkey."
440
+ );
441
+ }
442
+ const hexString = prfInput.credentialId.startsWith("0x") ? prfInput.credentialId.slice(2) : prfInput.credentialId;
443
+ if (!/^[0-9a-fA-F]+$/.test(hexString)) {
444
+ console.error(
445
+ "[PasskeyAdapter] Invalid credentialId format (not hex):",
446
+ prfInput.credentialId
447
+ );
448
+ throw new Error(
449
+ `[PasskeyAdapter] Invalid credentialId format. Expected hex string, got: ${prfInput.credentialId}`
450
+ );
451
+ }
452
+ const normalizedHex = hexString.length % 2 === 0 ? hexString : "0" + hexString;
453
+ const hexPairs = normalizedHex.match(/.{1,2}/g);
454
+ if (!hexPairs) {
455
+ throw new Error(
456
+ `[PasskeyAdapter] Failed to parse hex string: ${normalizedHex}`
457
+ );
458
+ }
459
+ const credIdBytes = new Uint8Array(
460
+ hexPairs.map((byte) => parseInt(byte, 16))
461
+ );
462
+ const reconvertedHex = Array.from(credIdBytes).map((b) => b.toString(16).padStart(2, "0")).join("");
463
+ if (reconvertedHex.toLowerCase() !== normalizedHex.toLowerCase()) {
464
+ throw new Error(
465
+ `[PasskeyAdapter] credentialId conversion failed. Original: ${normalizedHex}, Reconverted: ${reconvertedHex}`
466
+ );
467
+ }
468
+ const allowCredentials = [
469
+ {
470
+ id: credIdBytes,
471
+ type: "public-key"
472
+ }
473
+ ];
474
+ let credential = null;
475
+ try {
476
+ credential = await navigator.credentials.get({
477
+ publicKey: {
478
+ challenge: crypto.getRandomValues(new Uint8Array(32)),
479
+ rpId,
480
+ allowCredentials,
481
+ userVerification: USER_VERIFICATION,
482
+ // Shared constant
483
+ extensions: {
484
+ prf: {
485
+ eval: {
486
+ first: prfInput.salt.buffer
487
+ }
488
+ }
489
+ }
490
+ },
491
+ mediation: CREDENTIAL_MEDIATION
492
+ // Use shared constant
493
+ });
494
+ } catch (error) {
495
+ console.error("[PasskeyAdapter] WebAuthn get() failed:", error);
496
+ console.error("[PasskeyAdapter] Error name:", error?.name);
497
+ console.error("[PasskeyAdapter] Error message:", error?.message);
498
+ if (error?.name === "NotAllowedError") {
499
+ throw new Error(
500
+ "[PasskeyAdapter] User cancelled the passkey prompt or authentication was denied. Please try again and complete the authentication."
501
+ );
502
+ } else if (error?.name === "NotFoundError" || error?.name === "InvalidStateError") {
503
+ throw new Error(
504
+ "[PasskeyAdapter] No passkey found matching the provided credentialId. This may happen if the passkey was deleted or the credentialId is incorrect. Please re-enroll your passkey."
505
+ );
506
+ } else if (error?.name === "NotSupportedError") {
507
+ throw new Error(
508
+ "[PasskeyAdapter] WebAuthn PRF extension is not supported by this browser or device. Please use a browser that supports WebAuthn PRF extension (Chrome 108+, Edge 108+, Safari 16.4+)."
509
+ );
510
+ }
511
+ throw new Error(
512
+ `[PasskeyAdapter] WebAuthn authentication failed: ${error?.message || "Unknown error"}`
513
+ );
514
+ }
515
+ if (!credential || !credential.response) {
516
+ console.error(
517
+ "[PasskeyAdapter] credential is null or missing response"
518
+ );
519
+ throw new Error(
520
+ "[PasskeyAdapter] Failed to get passkey credential for PRF. The passkey prompt may have been cancelled or no matching credential was found."
521
+ );
522
+ }
523
+ const extensionResults = credential.getClientExtensionResults();
524
+ if (!extensionResults.prf || !extensionResults.prf.results || !extensionResults.prf.results.first) {
525
+ throw new Error(
526
+ "[PasskeyAdapter] PRF extension not supported or PRF output missing"
527
+ );
528
+ }
529
+ const prfOutputBuffer = extensionResults.prf.results.first;
530
+ const prfOutput = new Uint8Array(prfOutputBuffer);
531
+ const credentialIdBytes = new Uint8Array(credential.rawId);
532
+ const credentialIdBase64 = btoa(String.fromCharCode(...credentialIdBytes)).replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "");
533
+ console.log("[PasskeyAdapter] WebAuthn prompt completed successfully");
534
+ return {
535
+ prfOutput,
536
+ credentialId: credentialIdBase64
537
+ };
538
+ }
539
+ };
540
+ }
541
+
542
+ // src/headless/passkey-restore.ts
543
+ async function restorePasskey(params) {
544
+ const {
545
+ client,
546
+ userId,
547
+ blobUrl,
548
+ prfInput,
549
+ credentialId: providedCredentialId
550
+ } = params;
551
+ const credentialId = providedCredentialId || prfInput.credentialId || safeStorage.getItem(STORAGE_KEYS.credentialId);
552
+ if (!credentialId) {
553
+ throw new Error(
554
+ "Credential ID not found. Please provide credentialId in params, prfInput, or ensure it is stored in localStorage. If you recently enrolled a passkey, try refreshing the page or re-enrolling."
555
+ );
556
+ }
557
+ console.log("[restorePasskey] Using credentialId:", credentialId ? "present" : "MISSING");
558
+ console.log(
559
+ "[restorePasskey] credentialId source:",
560
+ providedCredentialId ? "params" : prfInput.credentialId ? "prfInput" : "localStorage"
561
+ );
562
+ console.log("[restorePasskey] prfInput:", prfInput);
563
+ console.log("[restorePasskey] Step 1: Ensuring access token is fresh...");
564
+ const currentToken = client.getAccessToken();
565
+ if (!currentToken) {
566
+ console.log("[restorePasskey] No access token found, calling /auth/refresh...");
567
+ try {
568
+ await client.post("/auth/refresh", {});
569
+ console.log("[restorePasskey] Access token refreshed successfully");
570
+ } catch (error) {
571
+ console.error("[restorePasskey] Failed to refresh access token:", error);
572
+ throw new Error("Failed to refresh access token. Please log in again.");
573
+ }
574
+ }
575
+ console.log("[restorePasskey] Step 2: Downloading blob via backend proxy for blobUrl:", blobUrl);
576
+ const apiKey = client.getApiKey();
577
+ if (!apiKey) {
578
+ console.error("[restorePasskey] API key not set in client. This is required for blob download.");
579
+ throw new Error(
580
+ "API key not configured. Please ensure VolrProvider is initialized with projectApiKey in config."
581
+ );
582
+ }
583
+ console.log("[restorePasskey] API key is set:", apiKey ? "present" : "MISSING");
584
+ const arrayBuffer = await client.postBinary("/blob/download", { key: blobUrl });
585
+ const blobBytes = new Uint8Array(arrayBuffer);
586
+ const nonceLength = 12;
587
+ const cipherLength = blobBytes.length - nonceLength;
588
+ if (cipherLength <= 0) {
589
+ throw new Error("Invalid blob format: blob too small");
590
+ }
591
+ const cipher = blobBytes.slice(0, cipherLength);
592
+ const nonce = blobBytes.slice(cipherLength);
593
+ const keyStorageType = "passkey";
594
+ const version = "v1";
595
+ const aadBytes = new TextEncoder().encode(
596
+ `volr/master-seed/v1|${userId}|${keyStorageType}|${version}`
597
+ );
598
+ const passkeyAdapter = createPasskeyAdapter({
599
+ rpId: typeof window !== "undefined" ? window.location.hostname : "localhost"});
600
+ const provider = sdkCore.createPasskeyProvider(passkeyAdapter, {
601
+ prfInput: {
602
+ ...prfInput,
603
+ credentialId
604
+ // Add credentialId to prfInput
605
+ },
606
+ encryptedBlob: {
607
+ cipher,
608
+ nonce
609
+ },
610
+ aad: aadBytes
611
+ });
612
+ console.log("[restorePasskey] Provider created with credentialId:", credentialId);
613
+ return {
614
+ provider
615
+ };
616
+ }
617
+ function serializeBigIntDeep(obj) {
618
+ if (obj === null || obj === void 0) {
619
+ return obj;
620
+ }
621
+ if (typeof obj === "bigint") {
622
+ return obj.toString();
623
+ }
624
+ if (Array.isArray(obj)) {
625
+ return obj.map(serializeBigIntDeep);
626
+ }
627
+ if (typeof obj === "object") {
628
+ const result = {};
629
+ for (const key in obj) {
630
+ if (Object.prototype.hasOwnProperty.call(obj, key)) {
631
+ result[key] = serializeBigIntDeep(obj[key]);
632
+ }
633
+ }
634
+ return result;
635
+ }
636
+ return obj;
637
+ }
638
+ function VolrProvider({ config, children }) {
639
+ const REQUIRE_USER_GESTURE_TO_RESTORE = true;
640
+ const providerCountRef = react.useRef(0);
641
+ react.useEffect(() => {
642
+ providerCountRef.current++;
643
+ if (providerCountRef.current > 1) {
644
+ console.warn(
645
+ "Multiple VolrProvider components detected. This may cause session synchronization issues."
646
+ );
647
+ }
648
+ return () => {
649
+ providerCountRef.current--;
650
+ };
651
+ }, []);
652
+ const client = react.useMemo(() => {
653
+ if (!config.projectApiKey) {
654
+ throw new Error(
655
+ "VolrProvider requires config.projectApiKey. Please set VITE_PROJECT_API_KEY environment variable or provide projectApiKey in config."
656
+ );
657
+ }
658
+ const baseUrl = config.__devApiBaseUrl || config.apiBaseUrl;
659
+ return new APIClient({
660
+ baseUrl,
661
+ apiKey: config.projectApiKey
662
+ });
663
+ }, [config.apiBaseUrl, config.projectApiKey]);
664
+ const [user, setUser] = react.useState(() => {
665
+ if (typeof window === "undefined") return null;
666
+ const userStr = safeStorage.getItem(STORAGE_KEYS.user);
667
+ if (!userStr || userStr === "undefined" || userStr === "null") {
668
+ return null;
669
+ }
670
+ try {
671
+ return JSON.parse(userStr);
672
+ } catch {
673
+ return null;
674
+ }
675
+ });
676
+ const [provider, setProviderState] = react.useState(
677
+ null
678
+ );
679
+ const [isLoading, setIsLoading] = react.useState(true);
680
+ const [error, setError] = react.useState(null);
681
+ const [chainId] = react.useState(config.defaultChainId);
682
+ const [accessToken, setAccessTokenState] = react.useState(() => {
683
+ return client.getAccessToken();
684
+ });
685
+ const syncRef = react.useRef(null);
686
+ react.useEffect(() => {
687
+ syncRef.current = new SessionSync();
688
+ const unsubscribe = syncRef.current.subscribe((event) => {
689
+ if (event.type === "LOGIN") {
690
+ client.setAccessToken(event.payload.accessToken);
691
+ setAccessTokenState(event.payload.accessToken);
692
+ setUser(event.payload.user);
693
+ safeStorage.setItem(
694
+ STORAGE_KEYS.user,
695
+ JSON.stringify(event.payload.user)
696
+ );
697
+ client.setApiKey(config.projectApiKey);
698
+ } else if (event.type === "LOGOUT") {
699
+ client.setAccessToken(null);
700
+ setAccessTokenState(null);
701
+ setUser(null);
702
+ setProviderState(null);
703
+ safeStorage.removeItem(STORAGE_KEYS.user);
704
+ safeStorage.removeItem(STORAGE_KEYS.provider);
705
+ } else if (event.type === "REFRESH") {
706
+ client.setAccessToken(event.payload.accessToken);
707
+ setAccessTokenState(event.payload.accessToken);
708
+ } else if (event.type === "PROVIDER_SET") {
709
+ setUser((prev) => ({
710
+ ...prev,
711
+ keyStorageType: event.payload.keyStorageType,
712
+ address: event.payload.address
713
+ }));
714
+ safeStorage.setItem(STORAGE_KEYS.provider, event.payload.keyStorageType);
715
+ }
716
+ });
717
+ return () => {
718
+ unsubscribe();
719
+ syncRef.current?.destroy();
720
+ };
721
+ }, [client, config.projectApiKey]);
722
+ const setProvider = react.useCallback(
723
+ async (newProvider) => {
724
+ try {
725
+ setError(null);
726
+ const keyStorageType = newProvider.keyStorageType;
727
+ setProviderState(newProvider);
728
+ try {
729
+ const refreshResponse = await client.post(
730
+ "/auth/refresh",
731
+ {}
732
+ );
733
+ if (refreshResponse.user) {
734
+ console.log(
735
+ "[Provider] setProvider: User data refreshed from backend:",
736
+ refreshResponse.user
737
+ );
738
+ setUser(refreshResponse.user);
739
+ safeStorage.setItem(
740
+ STORAGE_KEYS.user,
741
+ JSON.stringify(refreshResponse.user)
742
+ );
743
+ } else {
744
+ setUser((prev) => ({
745
+ ...prev,
746
+ keyStorageType
747
+ }));
748
+ safeStorage.setItem(
749
+ STORAGE_KEYS.user,
750
+ JSON.stringify({
751
+ ...user,
752
+ keyStorageType
753
+ })
754
+ );
755
+ }
756
+ } catch (error2) {
757
+ console.warn(
758
+ "[Provider] setProvider: Failed to refresh user data, using partial update:",
759
+ error2
760
+ );
761
+ setUser((prev) => ({
762
+ ...prev,
763
+ keyStorageType
764
+ }));
765
+ safeStorage.setItem(
766
+ STORAGE_KEYS.user,
767
+ JSON.stringify({
768
+ ...user,
769
+ keyStorageType
770
+ })
771
+ );
772
+ }
773
+ safeStorage.setItem(STORAGE_KEYS.provider, keyStorageType);
774
+ syncRef.current?.broadcast({
775
+ type: "PROVIDER_SET",
776
+ payload: {
777
+ keyStorageType
778
+ }
779
+ });
780
+ } catch (err) {
781
+ const error2 = err instanceof Error ? err : new Error("Failed to set provider");
782
+ setError(error2);
783
+ throw error2;
784
+ }
785
+ },
786
+ [client, user]
787
+ );
788
+ const hasRecoveredRef = react.useRef(false);
789
+ const restorationAttemptedRef = react.useRef(false);
790
+ react.useEffect(() => {
791
+ if (config.autoRecoverOnLogin !== false && !hasRecoveredRef.current) {
792
+ hasRecoveredRef.current = true;
793
+ const recover = async () => {
794
+ try {
795
+ setIsLoading(true);
796
+ const response = await client.post(
797
+ "/auth/refresh",
798
+ {}
799
+ );
800
+ console.log("[Provider] /auth/refresh response:", response);
801
+ const refreshedToken = client.getAccessToken();
802
+ if (refreshedToken) {
803
+ if (response.user) {
804
+ console.log(
805
+ "[Provider] Setting user from response:",
806
+ response.user
807
+ );
808
+ console.log("[Provider] User fields:", {
809
+ id: response.user.id,
810
+ email: response.user.email,
811
+ accountId: response.user.accountId,
812
+ evmAddress: response.user.evmAddress,
813
+ signerType: response.user.signerType,
814
+ keyStorageType: response.user.keyStorageType,
815
+ walletConnector: response.user.walletConnector,
816
+ blobUrl: response.user.blobUrl,
817
+ prfInput: response.user.prfInput,
818
+ credentialId: response.user.credentialId
819
+ });
820
+ setUser(response.user);
821
+ safeStorage.setItem(
822
+ STORAGE_KEYS.user,
823
+ JSON.stringify(response.user)
824
+ );
825
+ if (!REQUIRE_USER_GESTURE_TO_RESTORE) ; else {
826
+ if (response.user.keyStorageType === "passkey") {
827
+ console.log(
828
+ "[Provider] TTL=0 mode: Provider restoration deferred until transaction (requires user gesture)"
829
+ );
830
+ if (!response.user.blobUrl || !response.user.prfInput) {
831
+ console.warn(
832
+ "[Provider] Passkey user detected but missing blobUrl or prfInput. User needs to re-enroll passkey."
833
+ );
834
+ }
835
+ }
836
+ }
837
+ } else {
838
+ console.log(
839
+ "[Provider] No user in response, loading from storage"
840
+ );
841
+ const userStr = safeStorage.getItem(STORAGE_KEYS.user);
842
+ if (userStr) {
843
+ try {
844
+ const storedUser = JSON.parse(userStr);
845
+ console.log(
846
+ "[Provider] Loaded user from storage:",
847
+ storedUser
848
+ );
849
+ setUser(storedUser);
850
+ } catch {
851
+ safeStorage.removeItem(STORAGE_KEYS.user);
852
+ }
853
+ }
854
+ }
855
+ setAccessTokenState(refreshedToken);
856
+ }
857
+ } catch {
858
+ client.setAccessToken(null);
859
+ setAccessTokenState(null);
860
+ setUser(null);
861
+ setProviderState(null);
862
+ safeStorage.removeItem(STORAGE_KEYS.user);
863
+ safeStorage.removeItem(STORAGE_KEYS.provider);
864
+ } finally {
865
+ setIsLoading(false);
866
+ }
867
+ };
868
+ recover();
869
+ } else {
870
+ setIsLoading(false);
871
+ }
872
+ }, [client, config.autoRecoverOnLogin]);
873
+ const precheck = react.useCallback(
874
+ async (input) => {
875
+ try {
876
+ setError(null);
877
+ if (!client.getAccessToken()) {
878
+ try {
879
+ await client.post("/auth/refresh", {});
880
+ } catch {
881
+ }
882
+ }
883
+ const response = await client.post(
884
+ "/wallet/precheck",
885
+ input
886
+ );
887
+ return response.quote;
888
+ } catch (err) {
889
+ const error2 = err instanceof Error ? err : new Error("Precheck failed");
890
+ const diag = err?.response?.data?.error?.developerDiagnostics;
891
+ if (diag) {
892
+ console.error("[volr][precheck] developerDiagnostics:", diag);
893
+ }
894
+ setError(error2);
895
+ throw error2;
896
+ }
897
+ },
898
+ [client]
899
+ );
900
+ const relay = react.useCallback(
901
+ async (input, opts) => {
902
+ try {
903
+ setError(null);
904
+ if (!client.getAccessToken()) {
905
+ try {
906
+ await client.post("/auth/refresh", {});
907
+ } catch {
908
+ }
909
+ }
910
+ const idempotencyKey = opts?.idempotencyKey ?? (typeof crypto !== "undefined" && crypto.randomUUID ? crypto.randomUUID() : `${Date.now()}-${Math.random()}`);
911
+ const serializedInput = serializeBigIntDeep(input);
912
+ const response = await client.post(
913
+ "/wallet/relay",
914
+ serializedInput,
915
+ idempotencyKey
916
+ );
917
+ return response;
918
+ } catch (err) {
919
+ const error2 = err instanceof Error ? err : new Error("Relay failed");
920
+ const diag = err?.response?.data?.error?.developerDiagnostics;
921
+ if (diag) {
922
+ console.error("[volr][relay] developerDiagnostics:", diag);
923
+ }
924
+ setError(error2);
925
+ throw error2;
926
+ }
927
+ },
928
+ [client]
929
+ );
930
+ const logout = react.useCallback(async () => {
931
+ try {
932
+ setError(null);
933
+ client.setAccessToken(null);
934
+ setUser(null);
935
+ setProviderState(null);
936
+ safeStorage.removeItem(STORAGE_KEYS.user);
937
+ safeStorage.removeItem(STORAGE_KEYS.accessToken);
938
+ safeStorage.removeItem(STORAGE_KEYS.provider);
939
+ syncRef.current?.broadcast({ type: "LOGOUT" });
940
+ } catch (err) {
941
+ const error2 = err instanceof Error ? err : new Error("Logout failed");
942
+ setError(error2);
943
+ throw error2;
944
+ }
945
+ }, [client]);
946
+ react.useEffect(() => {
947
+ if (!user) return;
948
+ if (typeof window === "undefined" || !window.ethereum) {
949
+ return;
950
+ }
951
+ const ethereum = window.ethereum;
952
+ const handleAccountsChanged = (accounts) => {
953
+ console.log("[Provider] accountsChanged event:", accounts);
954
+ if (accounts.length === 0) {
955
+ console.warn("[Provider] Wallet disconnected, logging out...");
956
+ setTimeout(() => logout(), 3e3);
957
+ } else if (user.evmAddress && accounts[0].toLowerCase() !== user.evmAddress.toLowerCase()) {
958
+ console.warn("[Provider] Account changed, logging out in 3 seconds...");
959
+ setTimeout(() => logout(), 3e3);
960
+ }
961
+ };
962
+ const handleChainChanged = (chainIdHex) => {
963
+ const newChainId = parseInt(chainIdHex, 16);
964
+ console.log("[Provider] chainChanged event:", newChainId);
965
+ setUser(
966
+ (prev) => prev ? { ...prev, lastWalletChainId: newChainId } : null
967
+ );
968
+ window.location.reload();
969
+ };
970
+ const handleDisconnect = () => {
971
+ console.log("[Provider] disconnect event");
972
+ setProviderState(null);
973
+ safeStorage.removeItem(STORAGE_KEYS.provider);
974
+ };
975
+ ethereum.on("accountsChanged", handleAccountsChanged);
976
+ ethereum.on("chainChanged", handleChainChanged);
977
+ ethereum.on("disconnect", handleDisconnect);
978
+ return () => {
979
+ ethereum.removeListener("accountsChanged", handleAccountsChanged);
980
+ ethereum.removeListener("chainChanged", handleChainChanged);
981
+ ethereum.removeListener("disconnect", handleDisconnect);
982
+ };
983
+ }, [user, logout, setUser]);
984
+ const publicValue = react.useMemo(
985
+ () => ({
986
+ config,
987
+ user,
988
+ provider,
989
+ setProvider,
990
+ setUser: (newUser) => {
991
+ setUser(newUser);
992
+ if (newUser) {
993
+ safeStorage.setItem(STORAGE_KEYS.user, JSON.stringify(newUser));
994
+ } else {
995
+ safeStorage.removeItem(STORAGE_KEYS.user);
996
+ }
997
+ },
998
+ chainId,
999
+ precheck,
1000
+ relay,
1001
+ logout,
1002
+ isLoading,
1003
+ error
1004
+ }),
1005
+ [
1006
+ config,
1007
+ user,
1008
+ provider,
1009
+ setProvider,
1010
+ chainId,
1011
+ precheck,
1012
+ relay,
1013
+ logout,
1014
+ isLoading,
1015
+ error
1016
+ ]
1017
+ );
1018
+ const internalValue = react.useMemo(
1019
+ () => ({
1020
+ session: {
1021
+ accessToken,
1022
+ refreshToken: null
1023
+ // Cookie-based, opaque
1024
+ },
1025
+ refreshAccessToken: async () => {
1026
+ await client.post("/auth/refresh", {});
1027
+ const token = client.getAccessToken();
1028
+ setAccessTokenState(token);
1029
+ },
1030
+ setAccessToken: (token) => {
1031
+ client.setAccessToken(token);
1032
+ setAccessTokenState(token);
1033
+ },
1034
+ client
1035
+ // Expose APIClient instance
1036
+ }),
1037
+ [client, accessToken]
1038
+ );
1039
+ return /* @__PURE__ */ jsxRuntime.jsx(VolrContext.Provider, { value: publicValue, children: /* @__PURE__ */ jsxRuntime.jsx(InternalAuthContext.Provider, { value: internalValue, children }) });
1040
+ }
1041
+ function useVolr() {
1042
+ const context = react.useContext(VolrContext);
1043
+ if (!context) {
1044
+ throw new Error("useVolr must be used within VolrProvider");
1045
+ }
1046
+ return context;
1047
+ }
1048
+
1049
+ // src/utils/validation.ts
1050
+ var DEFAULT_LIMITS = {
1051
+ MAX_CALLS: 8,
1052
+ MAX_GAS_LIMIT: 10000000n,
1053
+ MAX_DATA_BYTES: 1e5
1054
+ };
1055
+ function validatePolicyId(policyId) {
1056
+ if (!/^0x[a-fA-F0-9]{64}$/.test(policyId)) {
1057
+ throw new Error("policyId must be 0x-prefixed 32-byte hex string");
1058
+ }
1059
+ }
1060
+ function validateCallValue(value) {
1061
+ if (value !== 0n) {
1062
+ throw new Error("call.value must be 0 (policy does not allow ETH transfers)");
1063
+ }
1064
+ }
1065
+ function validateCalls(calls, config = {}) {
1066
+ const maxCalls = config.maxCalls ?? DEFAULT_LIMITS.MAX_CALLS;
1067
+ const maxGasLimit = config.maxGasLimit ?? DEFAULT_LIMITS.MAX_GAS_LIMIT;
1068
+ const maxDataBytes = config.maxDataBytes ?? DEFAULT_LIMITS.MAX_DATA_BYTES;
1069
+ if (!calls || calls.length === 0) {
1070
+ throw new Error("calls must not be empty");
1071
+ }
1072
+ if (calls.length > maxCalls) {
1073
+ throw new Error(`calls.length (${calls.length}) exceeds maximum (${maxCalls})`);
1074
+ }
1075
+ for (const call of calls) {
1076
+ validateCallValue(call.value);
1077
+ if (call.gasLimit <= 0n) {
1078
+ throw new Error("call.gasLimit must be > 0");
1079
+ }
1080
+ if (call.gasLimit > maxGasLimit) {
1081
+ throw new Error(`call.gasLimit (${call.gasLimit}) exceeds maximum (${maxGasLimit})`);
1082
+ }
1083
+ const dataBytes = call.data.startsWith("0x") ? (call.data.length - 2) / 2 : call.data.length / 2;
1084
+ if (dataBytes > maxDataBytes) {
1085
+ throw new Error(`call.data length (${dataBytes} bytes) exceeds maximum (${maxDataBytes} bytes)`);
1086
+ }
1087
+ if (!call.target.startsWith("0x") || call.target.length !== 42) {
1088
+ throw new Error("call.target must be valid Ethereum address");
1089
+ }
1090
+ }
1091
+ }
1092
+ function validatePrecheckInput(input, config = {}) {
1093
+ validatePolicyId(input.auth.policyId);
1094
+ validateCalls(input.calls, config);
1095
+ const now = Math.floor(Date.now() / 1e3);
1096
+ const skew = 60;
1097
+ if (input.auth.expiresAt <= now + skew) {
1098
+ throw new Error("auth.expiresAt must be > now + 60s");
1099
+ }
1100
+ if (input.auth.nonce < 0n) {
1101
+ throw new Error("auth.nonce must be >= 0");
1102
+ }
1103
+ }
1104
+ function validateRelayInput(input, config = {}) {
1105
+ if (input.chainId === 0) {
1106
+ throw new Error("chainId cannot be 0");
1107
+ }
1108
+ validatePrecheckInput(
1109
+ {
1110
+ auth: input.auth,
1111
+ calls: input.calls
1112
+ },
1113
+ config
1114
+ );
1115
+ if (!input.authorizationList || input.authorizationList.length !== 1) {
1116
+ throw new Error("authorizationList must have exactly 1 element");
1117
+ }
1118
+ input.authorizationList[0];
1119
+ if (!input.sessionSig.startsWith("0x") || input.sessionSig.length !== 132) {
1120
+ throw new Error("sessionSig must be 65-byte hex string (0x + 130 hex chars)");
1121
+ }
1122
+ }
1123
+
1124
+ // src/hooks/usePrecheck.ts
1125
+ function usePrecheck() {
1126
+ const { precheck: precheckContext } = useVolr();
1127
+ const [isLoading, setIsLoading] = react.useState(false);
1128
+ const [error, setError] = react.useState(null);
1129
+ const precheck = react.useCallback(
1130
+ async (input) => {
1131
+ try {
1132
+ setIsLoading(true);
1133
+ setError(null);
1134
+ validatePrecheckInput(input);
1135
+ return await precheckContext(input);
1136
+ } catch (err) {
1137
+ const error2 = err instanceof Error ? err : new Error("Precheck failed");
1138
+ setError(error2);
1139
+ throw error2;
1140
+ } finally {
1141
+ setIsLoading(false);
1142
+ }
1143
+ },
1144
+ [precheckContext]
1145
+ );
1146
+ return {
1147
+ precheck,
1148
+ isLoading,
1149
+ error
1150
+ };
1151
+ }
1152
+ function useInternalAuth() {
1153
+ const context = react.useContext(InternalAuthContext);
1154
+ if (!context) {
1155
+ throw new Error("useInternalAuth must be used within VolrProvider");
1156
+ }
1157
+ return context;
1158
+ }
1159
+ function useRelay() {
1160
+ const { relay: relayContext } = useVolr();
1161
+ const { client } = useInternalAuth();
1162
+ const [isLoading, setIsLoading] = react.useState(false);
1163
+ const [error, setError] = react.useState(null);
1164
+ const relay = react.useCallback(
1165
+ async (input, opts) => {
1166
+ if (!opts?.signer) {
1167
+ throw new Error("signer is required");
1168
+ }
1169
+ try {
1170
+ setIsLoading(true);
1171
+ setError(null);
1172
+ if (input.chainId === 0) {
1173
+ throw new Error("chainId cannot be 0");
1174
+ }
1175
+ const networkData = await client.get(
1176
+ `/networks/${input.chainId}`
1177
+ );
1178
+ const invokerAddress = networkData.invokerAddress;
1179
+ if (!invokerAddress) {
1180
+ throw new Error(
1181
+ `Invoker address not configured for chainId ${input.chainId}`
1182
+ );
1183
+ }
1184
+ const { sessionSig } = await sdkCore.signSession({
1185
+ signer: opts.signer,
1186
+ from: input.from,
1187
+ auth: input.auth,
1188
+ calls: input.calls,
1189
+ invokerAddress: input.from
1190
+ });
1191
+ if (!opts.rpcClient) {
1192
+ throw new Error("rpcClient is required for relay");
1193
+ }
1194
+ const authNonce = await sdkCore.getAuthNonce(
1195
+ opts.rpcClient,
1196
+ input.from,
1197
+ "sponsored"
1198
+ );
1199
+ const authorizationTuple = await sdkCore.signAuthorization({
1200
+ signer: opts.signer,
1201
+ chainId: input.chainId,
1202
+ address: invokerAddress,
1203
+ nonce: authNonce
1204
+ });
1205
+ if (authorizationTuple.address.toLowerCase() !== invokerAddress.toLowerCase()) {
1206
+ throw new Error(
1207
+ `Authorization tuple address mismatch: expected ${invokerAddress}, got ${authorizationTuple.address}`
1208
+ );
1209
+ }
1210
+ const relayInput = {
1211
+ ...input,
1212
+ sessionSig,
1213
+ authorizationList: [authorizationTuple]
1214
+ };
1215
+ validateRelayInput(relayInput);
1216
+ return await relayContext(relayInput, {
1217
+ idempotencyKey: opts.idempotencyKey
1218
+ });
1219
+ } catch (err) {
1220
+ const error2 = err instanceof Error ? err : new Error("Relay failed");
1221
+ setError(error2);
1222
+ throw error2;
1223
+ } finally {
1224
+ setIsLoading(false);
1225
+ }
1226
+ },
1227
+ [relayContext, client]
1228
+ );
1229
+ return {
1230
+ relay,
1231
+ isLoading,
1232
+ error
1233
+ };
1234
+ }
1235
+
1236
+ // src/utils/normalize.ts
1237
+ function normalizeHex(value) {
1238
+ const hex = value.startsWith("0x") ? value.slice(2) : value;
1239
+ return `0x${hex.toLowerCase()}`;
1240
+ }
1241
+ function normalizeHexArray(values) {
1242
+ return values.map(normalizeHex);
1243
+ }
1244
+ function buildCall(options) {
1245
+ const data = viem.encodeFunctionData({
1246
+ abi: options.abi,
1247
+ functionName: options.functionName,
1248
+ args: options.args
1249
+ });
1250
+ if (options.estimateGas) ;
1251
+ return {
1252
+ target: options.target,
1253
+ data,
1254
+ value: options.value ?? BigInt(0),
1255
+ gasLimit: options.gasLimit ?? BigInt(0)
1256
+ };
1257
+ }
1258
+ function buildCalls(options) {
1259
+ return options.map(buildCall);
1260
+ }
1261
+
1262
+ // src/utils/rpc.ts
1263
+ function createExtendedRPCClient(publicClient) {
1264
+ return {
1265
+ call: async (args) => {
1266
+ const result = await publicClient.call({
1267
+ to: args.to,
1268
+ data: args.data
1269
+ });
1270
+ return result?.data || "0x";
1271
+ },
1272
+ getTransactionCount: async (address, blockTag) => {
1273
+ const count = await publicClient.getTransactionCount({
1274
+ address,
1275
+ blockTag: blockTag || "pending"
1276
+ });
1277
+ return BigInt(count);
1278
+ }
1279
+ };
1280
+ }
1281
+
1282
+ // src/utils/network.ts
1283
+ var networkCache = /* @__PURE__ */ new Map();
1284
+ var CACHE_TTL_MS = 5 * 60 * 1e3;
1285
+ function createGetRpcUrl(deps) {
1286
+ const { client, rpcOverrides } = deps;
1287
+ return async function getRpcUrl(chainId) {
1288
+ const overrideUrl = rpcOverrides?.[chainId.toString()];
1289
+ if (overrideUrl) {
1290
+ return overrideUrl;
1291
+ }
1292
+ const cached = networkCache.get(chainId);
1293
+ if (cached && cached.rpcUrl && Date.now() - cached.timestamp < CACHE_TTL_MS) {
1294
+ return cached.rpcUrl;
1295
+ }
1296
+ const response = await client.get(`/networks/${chainId}?includeRpcUrl=true`);
1297
+ if (!response.rpcUrl) {
1298
+ throw new Error(
1299
+ `RPC URL not available for chainId ${chainId}. Please provide it in config.rpcOverrides[${chainId}]`
1300
+ );
1301
+ }
1302
+ networkCache.set(chainId, {
1303
+ rpcUrl: response.rpcUrl,
1304
+ name: response.name,
1305
+ timestamp: Date.now()
1306
+ });
1307
+ return response.rpcUrl;
1308
+ };
1309
+ }
1310
+ function createGetNetworkInfo(deps) {
1311
+ const { client } = deps;
1312
+ return async function getNetworkInfo(chainId, includeRpcUrl = false) {
1313
+ const cached = networkCache.get(chainId);
1314
+ if (cached && Date.now() - cached.timestamp < CACHE_TTL_MS) {
1315
+ return {
1316
+ name: cached.name || `Chain ${chainId}`,
1317
+ rpcUrl: includeRpcUrl ? cached.rpcUrl : void 0
1318
+ };
1319
+ }
1320
+ const response = await client.get(`/networks/${chainId}${includeRpcUrl ? "?includeRpcUrl=true" : ""}`);
1321
+ networkCache.set(chainId, {
1322
+ name: response.name,
1323
+ rpcUrl: response.rpcUrl,
1324
+ timestamp: Date.now()
1325
+ });
1326
+ return {
1327
+ name: response.name,
1328
+ rpcUrl: includeRpcUrl ? response.rpcUrl : void 0
1329
+ };
1330
+ };
1331
+ }
1332
+ function validatePolicyId2(policyId) {
1333
+ if (!policyId) {
1334
+ return;
1335
+ }
1336
+ if (!policyId.startsWith("0x")) {
1337
+ throw new Error("policyId must start with 0x");
1338
+ }
1339
+ const hexPart = policyId.slice(2);
1340
+ if (hexPart.length !== 64) {
1341
+ throw new Error(`policyId must be 32 bytes (64 hex chars), got ${hexPart.length / 2} bytes`);
1342
+ }
1343
+ if (!/^[0-9a-f]+$/i.test(hexPart)) {
1344
+ throw new Error("policyId must be valid hex string");
1345
+ }
1346
+ if (policyId.toLowerCase() === sdkCore.ZERO_HASH.toLowerCase()) {
1347
+ throw new Error("Zero policyId is not allowed. Omit policyId to use project-specific policyId from backend.");
1348
+ }
1349
+ }
1350
+ function validateCalls2(calls) {
1351
+ if (calls.length === 0) {
1352
+ throw new Error("calls array must not be empty");
1353
+ }
1354
+ if (calls.length > 8) {
1355
+ throw new Error("calls array must not exceed 8 items");
1356
+ }
1357
+ for (const call of calls) {
1358
+ if (call.value > 0n) {
1359
+ console.warn("Call has non-zero value. Most policies forbid ETH transfers.");
1360
+ }
1361
+ }
1362
+ }
1363
+ function defaultGasLimit(data) {
1364
+ const normalized = normalizeHex(data);
1365
+ return normalized === "0x" || normalized === "0x0" ? 21000n : 100000n;
1366
+ }
1367
+ function normalizeCall(call) {
1368
+ const normalizedTarget = viem.getAddress(call.target);
1369
+ const normalizedData = normalizeHex(call.data);
1370
+ const value = call.value ?? 0n;
1371
+ const gasLimit = call.gasLimit && call.gasLimit > 0n ? call.gasLimit : defaultGasLimit(normalizedData);
1372
+ return {
1373
+ target: normalizedTarget,
1374
+ data: normalizedData,
1375
+ value,
1376
+ gasLimit
1377
+ };
1378
+ }
1379
+ function normalizeCalls(calls) {
1380
+ return calls.map(normalizeCall);
1381
+ }
1382
+
1383
+ // src/wallet/preflight.ts
1384
+ function extractErrorMessage(err) {
1385
+ if (err && typeof err === "object") {
1386
+ const e = err;
1387
+ const parts = [];
1388
+ if (typeof e.shortMessage === "string") parts.push(e.shortMessage);
1389
+ if (typeof e.message === "string") parts.push(e.message);
1390
+ if (typeof e.cause?.message === "string") parts.push(e.cause.message);
1391
+ if (typeof e.details === "string") parts.push(e.details);
1392
+ const msg = parts.filter(Boolean).join(" | ");
1393
+ if (msg) return msg;
1394
+ }
1395
+ try {
1396
+ return String(err);
1397
+ } catch {
1398
+ return "Unknown error";
1399
+ }
1400
+ }
1401
+ function isIgnorableFundingError(err, message) {
1402
+ const m = message.toLowerCase();
1403
+ const e = err;
1404
+ const name = typeof e?.name === "string" ? e.name.toLowerCase() : "";
1405
+ if (name === "insufficientfundserror" || name.includes("insufficientfunds")) {
1406
+ return true;
1407
+ }
1408
+ if (typeof e?.code === "string") {
1409
+ const code = e.code.toLowerCase();
1410
+ if (code.includes("execution_reverted") || code.includes("call_exception")) {
1411
+ return false;
1412
+ }
1413
+ if (code.includes("insufficient_funds")) {
1414
+ return true;
1415
+ }
1416
+ }
1417
+ const fundingPatterns = [
1418
+ "insufficient funds for gas * price",
1419
+ "insufficient funds for gas",
1420
+ "for gas * price + value",
1421
+ "not enough funds for l1 fee",
1422
+ "insufficient funds for l1 fee",
1423
+ "insufficient balance for gas",
1424
+ "account balance too low"
1425
+ ];
1426
+ const hasFundingPattern = fundingPatterns.some((pattern) => m.includes(pattern));
1427
+ const hasRevertIndicator = m.includes("revert") || m.includes("execution reverted") || m.includes("call exception") || m.includes("vm execution error");
1428
+ return hasFundingPattern && !hasRevertIndicator;
1429
+ }
1430
+ async function preflightEstimate(publicClient, from, calls, opts) {
1431
+ for (let i = 0; i < calls.length; i++) {
1432
+ const c = calls[i];
1433
+ try {
1434
+ await publicClient.estimateGas({
1435
+ account: from,
1436
+ to: c.target,
1437
+ data: c.data,
1438
+ value: c.value ?? 0n
1439
+ });
1440
+ } catch (e) {
1441
+ const message = extractErrorMessage(e);
1442
+ const tolerateFunding = opts?.tolerateFundingErrors !== false;
1443
+ const isFundingError = isIgnorableFundingError(e, message);
1444
+ if (tolerateFunding && isFundingError) {
1445
+ try {
1446
+ await publicClient.call({
1447
+ account: from,
1448
+ to: c.target,
1449
+ data: c.data,
1450
+ value: c.value ?? 0n
1451
+ });
1452
+ console.log(
1453
+ `[preflightEstimate] Ignoring funding error for call #${i} (target ${c.target}): ${message}`
1454
+ );
1455
+ continue;
1456
+ } catch (callError) {
1457
+ const callMessage = extractErrorMessage(callError);
1458
+ console.error(
1459
+ `[preflightEstimate] Static call after funding error also failed for call #${i} (target ${c.target}):`,
1460
+ {
1461
+ originalError: { message, errorName: e?.name, errorCode: e?.code },
1462
+ callError: {
1463
+ message: callMessage,
1464
+ name: callError?.name,
1465
+ code: callError?.code
1466
+ }
1467
+ }
1468
+ );
1469
+ throw new Error(
1470
+ `Preflight failed (call #${i} target ${c.target}): ${callMessage}`
1471
+ );
1472
+ }
1473
+ }
1474
+ console.error(
1475
+ `[preflightEstimate] Preflight failed for call #${i} (target ${c.target}):`,
1476
+ {
1477
+ message,
1478
+ errorName: e?.name,
1479
+ errorCode: e?.code,
1480
+ isFundingError,
1481
+ tolerateFunding
1482
+ }
1483
+ );
1484
+ throw new Error(`Preflight failed (call #${i} target ${c.target}): ${message}`);
1485
+ }
1486
+ }
1487
+ }
1488
+ async function resolveSigner(input) {
1489
+ const { explicitSigner, provider, chainId, client, user, setProvider } = input;
1490
+ if (explicitSigner) {
1491
+ return { signer: explicitSigner, activeProvider: null };
1492
+ }
1493
+ if (provider) {
1494
+ await provider.ensureSession({ interactive: true, force: true });
1495
+ const signerContext = {
1496
+ provider,
1497
+ chainId,
1498
+ // Citrea testnet (5115) doesn't support P-256
1499
+ p256Hint: false
1500
+ };
1501
+ const signer = await sdkCore.selectSigner(signerContext);
1502
+ return { signer, activeProvider: provider };
1503
+ }
1504
+ if (user?.keyStorageType === "passkey" && user.blobUrl && user.prfInput && user.id) {
1505
+ const { provider: restoredProvider } = await restorePasskey({
1506
+ client,
1507
+ userId: user.id,
1508
+ blobUrl: user.blobUrl,
1509
+ prfInput: user.prfInput,
1510
+ credentialId: user.credentialId
1511
+ });
1512
+ await setProvider(restoredProvider);
1513
+ await restoredProvider.ensureSession({ interactive: true, force: true });
1514
+ const signerContext = {
1515
+ provider: restoredProvider,
1516
+ chainId,
1517
+ p256Hint: false
1518
+ };
1519
+ const signer = await sdkCore.selectSigner(signerContext);
1520
+ return { signer, activeProvider: restoredProvider };
1521
+ }
1522
+ throw new Error(
1523
+ "No wallet provider configured. Please set up a Passkey provider to sign transactions. SIWE is authentication-only."
1524
+ );
1525
+ }
1526
+
1527
+ // src/utils/defaults.ts
1528
+ var DEFAULT_EXPIRES_IN_SEC = 900;
1529
+ var DEFAULT_MODE = "sponsored";
1530
+ function defaultIdempotencyKey() {
1531
+ const c = globalThis.crypto;
1532
+ return c?.randomUUID?.() ?? `${Date.now()}-${Math.random().toString(36).substring(2, 15)}`;
1533
+ }
1534
+
1535
+ // src/wallet/auth.ts
1536
+ var GAS_CAP_BUFFER = 150000n;
1537
+ function computeTotalGasCap(calls) {
1538
+ return calls.reduce((sum, c) => sum + (c.gasLimit ?? 0n), 0n) + GAS_CAP_BUFFER;
1539
+ }
1540
+ function buildTempAuth(input) {
1541
+ const expiresAt = Math.floor(Date.now() / 1e3) + (input.expiresInSec ?? DEFAULT_EXPIRES_IN_SEC);
1542
+ const totalGasCap = computeTotalGasCap(input.calls);
1543
+ const sessionId = input.sessionId ?? 0n;
1544
+ const policySnapshotHash = input.policySnapshotHash ?? sdkCore.ZERO_HASH;
1545
+ const gasLimitMax = input.gasLimitMax ?? totalGasCap;
1546
+ const maxFeePerGas = input.maxFeePerGas ?? 1000000000n;
1547
+ const maxPriorityFeePerGas = input.maxPriorityFeePerGas ?? 1000000n;
1548
+ return {
1549
+ chainId: input.chainId,
1550
+ sessionKey: input.from,
1551
+ sessionId,
1552
+ expiresAt,
1553
+ policyId: normalizeHex(input.policyId),
1554
+ nonce: 0n,
1555
+ policySnapshotHash,
1556
+ gasLimitMax,
1557
+ maxFeePerGas,
1558
+ maxPriorityFeePerGas,
1559
+ totalGasCap
1560
+ };
1561
+ }
1562
+ function finalizeAuthWithNonce(tempAuth, quote) {
1563
+ let finalNonce;
1564
+ if (quote?.currentOpNonce !== void 0 && quote.currentOpNonce !== null && quote.currentOpNonce !== "") {
1565
+ const currentNonce = BigInt(quote.currentOpNonce);
1566
+ finalNonce = currentNonce + 1n;
1567
+ } else {
1568
+ finalNonce = BigInt(Math.floor(Date.now() / 1e3));
1569
+ }
1570
+ const finalAuth = {
1571
+ ...tempAuth,
1572
+ nonce: finalNonce
1573
+ };
1574
+ if (quote?.policyId) {
1575
+ finalAuth.policyId = normalizeHex(quote.policyId);
1576
+ }
1577
+ if (quote?.policySnapshotHash) {
1578
+ finalAuth.policySnapshotHash = quote.policySnapshotHash;
1579
+ }
1580
+ return finalAuth;
1581
+ }
1582
+
1583
+ // src/utils/tx-polling.ts
1584
+ async function pollTransactionStatus(txId, client, maxAttempts = 60, intervalMs = 5e3) {
1585
+ for (let attempt = 0; attempt < maxAttempts; attempt++) {
1586
+ try {
1587
+ const response = await client.get(`/wallet/transactions/${txId}`);
1588
+ const { status, txHash } = response;
1589
+ if (status === "CONFIRMED") {
1590
+ console.log(`[pollTransactionStatus] Transaction ${txId} confirmed with txHash ${txHash}`);
1591
+ return {
1592
+ txId,
1593
+ status: "CONFIRMED",
1594
+ txHash
1595
+ };
1596
+ }
1597
+ if (status === "FAILED") {
1598
+ console.error(`[pollTransactionStatus] Transaction ${txId} failed`);
1599
+ const diag = response.meta?.developerDiagnostics;
1600
+ if (diag) {
1601
+ console.error("[volr][relay] developerDiagnostics:", diag);
1602
+ }
1603
+ return {
1604
+ txId,
1605
+ status: "FAILED",
1606
+ txHash
1607
+ };
1608
+ }
1609
+ console.log(`[pollTransactionStatus] Transaction ${txId} is ${status}, attempt ${attempt + 1}/${maxAttempts}`);
1610
+ await new Promise((resolve) => setTimeout(resolve, intervalMs));
1611
+ } catch (error) {
1612
+ console.error(`[pollTransactionStatus] Error polling transaction ${txId}:`, error);
1613
+ await new Promise((resolve) => setTimeout(resolve, intervalMs));
1614
+ }
1615
+ }
1616
+ console.warn(`[pollTransactionStatus] Polling timeout for transaction ${txId}`);
1617
+ return {
1618
+ txId,
1619
+ status: "PENDING"
1620
+ };
1621
+ }
1622
+
1623
+ // src/wallet/sender.ts
1624
+ async function sendCalls(args) {
1625
+ const { chainId, from, calls, opts, deps } = args;
1626
+ if (chainId === 0) {
1627
+ throw new Error("chainId cannot be 0");
1628
+ }
1629
+ const effectivePolicyId = opts.policyId?.toLowerCase() === sdkCore.ZERO_HASH.toLowerCase() ? void 0 : opts.policyId;
1630
+ validatePolicyId2(effectivePolicyId);
1631
+ const normalizedFrom = from;
1632
+ const normalizedCalls = normalizeCalls(calls);
1633
+ validateCalls2(normalizedCalls);
1634
+ let currentUser = deps.user;
1635
+ if (deps.user?.keyStorageType === "passkey" && !deps.provider) {
1636
+ try {
1637
+ const refreshResponse = await deps.client.post("/auth/refresh", {});
1638
+ if (refreshResponse.user) {
1639
+ currentUser = refreshResponse.user;
1640
+ }
1641
+ } catch (error) {
1642
+ console.warn("[sendCalls] Failed to refresh user data before transaction:", error);
1643
+ }
1644
+ }
1645
+ if (opts.preflight !== false) {
1646
+ const tolerateFundingErrors = (opts.mode ?? "sponsored") !== "self";
1647
+ await preflightEstimate(deps.publicClient, normalizedFrom, normalizedCalls, { tolerateFundingErrors });
1648
+ }
1649
+ const { signer, activeProvider } = await resolveSigner({
1650
+ explicitSigner: opts.signer,
1651
+ provider: deps.provider,
1652
+ chainId,
1653
+ client: deps.client,
1654
+ user: currentUser,
1655
+ setProvider: deps.setProvider
1656
+ });
1657
+ let projectPolicyId;
1658
+ if (!effectivePolicyId) {
1659
+ const tempAuthForPrecheck = buildTempAuth({
1660
+ chainId,
1661
+ from: normalizedFrom,
1662
+ policyId: sdkCore.ZERO_HASH,
1663
+ calls: normalizedCalls,
1664
+ expiresInSec: opts.expiresInSec ?? DEFAULT_EXPIRES_IN_SEC
1665
+ });
1666
+ let precheckQuote;
1667
+ try {
1668
+ precheckQuote = await deps.precheck({ auth: tempAuthForPrecheck, calls: normalizedCalls });
1669
+ } catch (err) {
1670
+ throw new Error(`Precheck failed: ${err instanceof Error ? err.message : String(err)}`);
1671
+ }
1672
+ const quotePolicyId = precheckQuote.policyId;
1673
+ if (!quotePolicyId) {
1674
+ throw new Error("Backend did not return policyId in precheck response");
1675
+ }
1676
+ projectPolicyId = quotePolicyId;
1677
+ } else {
1678
+ projectPolicyId = effectivePolicyId;
1679
+ }
1680
+ const tempAuth = buildTempAuth({
1681
+ chainId,
1682
+ from: normalizedFrom,
1683
+ policyId: projectPolicyId,
1684
+ calls: normalizedCalls,
1685
+ expiresInSec: opts.expiresInSec ?? DEFAULT_EXPIRES_IN_SEC
1686
+ });
1687
+ let quote;
1688
+ try {
1689
+ quote = await deps.precheck({ auth: tempAuth, calls: normalizedCalls });
1690
+ } catch (err) {
1691
+ console.warn("[useVolrWallet] Precheck failed:", err);
1692
+ }
1693
+ const auth = finalizeAuthWithNonce(tempAuth, quote);
1694
+ const idempotencyKey = opts.idempotencyKey ?? defaultIdempotencyKey();
1695
+ try {
1696
+ const result = await deps.relay(
1697
+ { chainId, from: normalizedFrom, auth, calls: normalizedCalls },
1698
+ { signer, rpcClient: deps.rpcClient, idempotencyKey }
1699
+ );
1700
+ if (result.status === "QUEUED" || result.status === "PENDING") {
1701
+ console.log(`[useVolrWallet] Transaction ${result.txId} is ${result.status}, starting polling...`);
1702
+ return await pollTransactionStatus(result.txId, deps.client);
1703
+ }
1704
+ return result;
1705
+ } finally {
1706
+ if (activeProvider && activeProvider.lock) {
1707
+ try {
1708
+ await activeProvider.lock();
1709
+ } catch {
1710
+ }
1711
+ }
1712
+ }
1713
+ }
1714
+
1715
+ // src/hooks/useVolrWallet.ts
1716
+ function useVolrWallet() {
1717
+ const { user, config, provider, setProvider } = useVolr();
1718
+ const { precheck } = usePrecheck();
1719
+ const { relay } = useRelay();
1720
+ const { client } = useInternalAuth();
1721
+ const getRpcUrl = react.useCallback(
1722
+ createGetRpcUrl({ client, rpcOverrides: config.rpcOverrides }),
1723
+ [client, config.rpcOverrides]
1724
+ );
1725
+ const evm = react.useCallback(
1726
+ (chainId) => {
1727
+ if (chainId === 0) {
1728
+ throw new Error("chainId cannot be 0");
1729
+ }
1730
+ let publicClient = null;
1731
+ let extendedRpcClient = null;
1732
+ const ensureRpcClient = async () => {
1733
+ if (!publicClient) {
1734
+ const rpcUrl = await getRpcUrl(chainId);
1735
+ publicClient = viem.createPublicClient({
1736
+ transport: viem.http(rpcUrl)
1737
+ });
1738
+ extendedRpcClient = createExtendedRPCClient(publicClient);
1739
+ }
1740
+ return { publicClient, extendedRpcClient };
1741
+ };
1742
+ return {
1743
+ readContract: async (args) => {
1744
+ const { publicClient: client2 } = await ensureRpcClient();
1745
+ return client2.readContract(args);
1746
+ },
1747
+ sendTransaction: async (tx, opts) => {
1748
+ const { publicClient: publicClient2, extendedRpcClient: rpcClient } = await ensureRpcClient();
1749
+ const from = opts.from ?? user?.evmAddress;
1750
+ if (!from) {
1751
+ throw new Error(
1752
+ "from address is required. Provide it in opts.from or set user.evmAddress in VolrProvider. If you haven't set up a wallet provider yet, please complete the onboarding flow."
1753
+ );
1754
+ }
1755
+ const call = {
1756
+ target: viem.getAddress(tx.to),
1757
+ data: normalizeHex(tx.data),
1758
+ value: tx.value ?? 0n,
1759
+ gasLimit: tx.gasLimit ?? 0n
1760
+ };
1761
+ return await sendCalls({
1762
+ chainId,
1763
+ from: viem.getAddress(from),
1764
+ calls: [call],
1765
+ opts,
1766
+ deps: {
1767
+ publicClient: publicClient2,
1768
+ rpcClient,
1769
+ precheck,
1770
+ relay,
1771
+ client,
1772
+ user: user ?? null,
1773
+ provider: provider ?? null,
1774
+ setProvider
1775
+ }
1776
+ });
1777
+ },
1778
+ sendBatch: (async (calls, opts) => {
1779
+ const { publicClient: publicClient2, extendedRpcClient: rpcClient } = await ensureRpcClient();
1780
+ const from = opts.from ?? user?.evmAddress;
1781
+ if (!from) {
1782
+ throw new Error(
1783
+ "from address is required. Provide it in opts.from or set user.evmAddress in VolrProvider. If you haven't set up a wallet provider yet, please complete the onboarding flow."
1784
+ );
1785
+ }
1786
+ const isCallArray = (calls2) => {
1787
+ return Array.isArray(calls2) && calls2.length > 0 && "data" in calls2[0] && !("abi" in calls2[0]);
1788
+ };
1789
+ const builtCalls = isCallArray(calls) ? calls : buildCalls(calls);
1790
+ return await sendCalls({
1791
+ chainId,
1792
+ from: viem.getAddress(from),
1793
+ calls: builtCalls.map((c) => ({
1794
+ target: viem.getAddress(c.target),
1795
+ data: normalizeHex(c.data),
1796
+ value: c.value ?? 0n,
1797
+ gasLimit: c.gasLimit ?? 0n
1798
+ })),
1799
+ opts,
1800
+ deps: {
1801
+ publicClient: publicClient2,
1802
+ rpcClient,
1803
+ precheck,
1804
+ relay,
1805
+ client,
1806
+ user: user ?? null,
1807
+ provider: provider ?? null,
1808
+ setProvider
1809
+ }
1810
+ });
1811
+ })
1812
+ };
1813
+ },
1814
+ [user, config, provider, precheck, relay, getRpcUrl]
1815
+ );
1816
+ return { evm };
1817
+ }
1818
+ function createAxiosInstance(baseUrl, apiKey) {
1819
+ const instance = axios__default.default.create({
1820
+ baseURL: baseUrl.replace(/\/+$/, ""),
1821
+ // Remove trailing slashes
1822
+ withCredentials: true,
1823
+ // Include cookies
1824
+ headers: {
1825
+ "Content-Type": "application/json"
1826
+ }
1827
+ });
1828
+ instance.interceptors.request.use((config) => {
1829
+ if (apiKey) {
1830
+ config.headers["X-API-Key"] = apiKey;
1831
+ }
1832
+ return config;
1833
+ });
1834
+ instance.interceptors.response.use(
1835
+ (response) => response,
1836
+ (error) => {
1837
+ if (error.response?.data) {
1838
+ const errorData = error.response.data;
1839
+ if (errorData.error?.message) {
1840
+ error.message = errorData.error.message;
1841
+ }
1842
+ }
1843
+ return Promise.reject(error);
1844
+ }
1845
+ );
1846
+ return instance;
1847
+ }
1848
+
1849
+ // src/hooks/useVolrLogin.ts
1850
+ function useVolrLogin() {
1851
+ const { config, setUser } = useVolr();
1852
+ const { refreshAccessToken, setAccessToken } = useInternalAuth();
1853
+ const toVolrUser = react.useCallback((u) => {
1854
+ return {
1855
+ id: u.id,
1856
+ email: u.email,
1857
+ accountId: u.accountId ?? void 0,
1858
+ evmAddress: u.evmAddress,
1859
+ keyStorageType: u.keyStorageType ?? void 0,
1860
+ signerType: u.signerType ?? void 0,
1861
+ walletConnector: u.walletConnector ?? void 0,
1862
+ lastWalletChainId: u.lastWalletChainId ?? void 0,
1863
+ blobUrl: u.blobUrl ?? void 0,
1864
+ prfInput: u.prfInput ?? void 0,
1865
+ credentialId: u.credentialId ?? void 0
1866
+ };
1867
+ }, []);
1868
+ const api = react.useMemo(
1869
+ () => createAxiosInstance(config.apiBaseUrl, config.projectApiKey),
1870
+ [config.apiBaseUrl, config.projectApiKey]
1871
+ );
1872
+ const requestEmailCode = react.useCallback(
1873
+ async (email) => {
1874
+ const normalizedEmail = email.trim().toLowerCase();
1875
+ if (!normalizedEmail || !normalizedEmail.includes("@")) {
1876
+ throw new Error("Invalid email address");
1877
+ }
1878
+ const response = await api.post("/auth/email/send", {
1879
+ email: normalizedEmail
1880
+ });
1881
+ if (!response.data?.ok) {
1882
+ throw new Error(
1883
+ response.data?.error?.message || "Failed to send verification code"
1884
+ );
1885
+ }
1886
+ safeStorage.setItem(STORAGE_KEYS.lastEmail, normalizedEmail);
1887
+ },
1888
+ [api]
1889
+ );
1890
+ const verifyEmailCode = react.useCallback(
1891
+ async (email, code) => {
1892
+ const normalizedEmail = email.trim().toLowerCase();
1893
+ if (!normalizedEmail || !normalizedEmail.includes("@")) {
1894
+ throw new Error("Invalid email address");
1895
+ }
1896
+ const normalizedCode = code.trim();
1897
+ if (!/^\d{6}$/.test(normalizedCode)) {
1898
+ throw new Error("Invalid code format");
1899
+ }
1900
+ const response = await api.post("/auth/email/verify", {
1901
+ email: normalizedEmail,
1902
+ code: normalizedCode
1903
+ });
1904
+ if (!response.data?.ok) {
1905
+ throw new Error(
1906
+ response.data?.error?.message || "Invalid verification code"
1907
+ );
1908
+ }
1909
+ const verifyData = response.data;
1910
+ const userFromServer = verifyData?.data?.user;
1911
+ const isNewUser = !!verifyData?.data?.isNewUser;
1912
+ const accessToken = verifyData?.data?.accessToken || "";
1913
+ if (!accessToken) {
1914
+ throw new Error(
1915
+ "Access token is required but was not provided by the server"
1916
+ );
1917
+ }
1918
+ setAccessToken(accessToken);
1919
+ await refreshAccessToken();
1920
+ if (userFromServer) {
1921
+ setUser(toVolrUser(userFromServer));
1922
+ } else {
1923
+ setUser({ id: "", email: normalizedEmail });
1924
+ }
1925
+ return {
1926
+ userId: userFromServer?.id || "",
1927
+ isNewUser,
1928
+ keyStorageType: userFromServer?.keyStorageType ?? null,
1929
+ signerType: userFromServer?.signerType ?? null,
1930
+ accessToken
1931
+ };
1932
+ },
1933
+ [api, refreshAccessToken, setAccessToken, setUser, toVolrUser]
1934
+ );
1935
+ const handleSocialLogin = react.useCallback(
1936
+ async (provider) => {
1937
+ if (typeof window !== "undefined") {
1938
+ const baseUrl = config.apiBaseUrl.replace(/\/+$/, "");
1939
+ window.location.href = `${baseUrl}/auth/${provider}`;
1940
+ }
1941
+ },
1942
+ [config.apiBaseUrl]
1943
+ );
1944
+ const requestSiweNonce = react.useCallback(async () => {
1945
+ const response = await api.get("/auth/siwe/nonce");
1946
+ if (!response.data?.ok) {
1947
+ throw new Error(
1948
+ response.data?.error?.message || "Failed to generate nonce"
1949
+ );
1950
+ }
1951
+ return response.data.data.nonce;
1952
+ }, [api]);
1953
+ const verifySiweSignature = react.useCallback(
1954
+ async (message, signature, options) => {
1955
+ try {
1956
+ const response = await api.post("/auth/siwe/verify", {
1957
+ message,
1958
+ signature,
1959
+ walletConnector: options?.walletConnector,
1960
+ chainId: options?.chainId
1961
+ });
1962
+ if (!response.data?.ok) {
1963
+ console.error(
1964
+ "[verifySiweSignature] Backend returned error:",
1965
+ response.data
1966
+ );
1967
+ throw new Error(
1968
+ response.data?.error?.message || "Invalid SIWE signature"
1969
+ );
1970
+ }
1971
+ const verifyData = response.data;
1972
+ const userFromServer = verifyData?.data?.user;
1973
+ const isNewUser = !!verifyData?.data?.isNewUser;
1974
+ const accessToken = verifyData?.data?.accessToken || "";
1975
+ if (!accessToken) {
1976
+ throw new Error(
1977
+ "Access token is required but was not provided by the server"
1978
+ );
1979
+ }
1980
+ setAccessToken(accessToken);
1981
+ await refreshAccessToken();
1982
+ if (userFromServer) {
1983
+ setUser(toVolrUser(userFromServer));
1984
+ }
1985
+ return {
1986
+ userId: userFromServer?.id || "",
1987
+ isNewUser,
1988
+ keyStorageType: userFromServer?.keyStorageType ?? null,
1989
+ signerType: userFromServer?.signerType ?? null,
1990
+ accessToken
1991
+ };
1992
+ } catch (error) {
1993
+ console.error("[verifySiweSignature] Error details:", {
1994
+ message: error.message,
1995
+ response: error.response?.data,
1996
+ status: error.response?.status
1997
+ });
1998
+ throw error;
1999
+ }
2000
+ },
2001
+ [api, refreshAccessToken, setAccessToken, setUser, toVolrUser]
2002
+ );
2003
+ const handlePasskeyComplete = react.useCallback(async () => {
2004
+ }, []);
2005
+ return {
2006
+ requestEmailCode,
2007
+ verifyEmailCode,
2008
+ handleSocialLogin,
2009
+ requestSiweNonce,
2010
+ verifySiweSignature,
2011
+ handlePasskeyComplete
2012
+ };
2013
+ }
2014
+ async function jsonRpc(rpcUrl, method, params) {
2015
+ const payload = {
2016
+ jsonrpc: "2.0",
2017
+ id: Date.now(),
2018
+ method,
2019
+ params
2020
+ };
2021
+ const res = await fetch(rpcUrl, {
2022
+ method: "POST",
2023
+ headers: { "content-type": "application/json" },
2024
+ body: JSON.stringify(payload)
2025
+ });
2026
+ if (!res.ok) {
2027
+ throw new Error(`RPC ${method} failed: ${res.status} ${res.statusText}`);
2028
+ }
2029
+ const body = await res.json();
2030
+ if (body.error) {
2031
+ throw new Error(body.error.message || "RPC error");
2032
+ }
2033
+ return body.result;
2034
+ }
2035
+ function hexToBigInt(hex) {
2036
+ if (!hex) return 0n;
2037
+ return BigInt(hex);
2038
+ }
2039
+ function pad32(address) {
2040
+ return `0x${address.toLowerCase().replace(/^0x/, "").padStart(64, "0")}`;
2041
+ }
2042
+ function balanceOfCalldata(address) {
2043
+ const selector = "0x70a08231";
2044
+ return `${selector}${pad32(address).slice(2)}`;
2045
+ }
2046
+ function useDepositListener(input) {
2047
+ const { config } = useVolr();
2048
+ const { client } = useInternalAuth();
2049
+ const [status, setStatus] = react.useState({ state: "idle" });
2050
+ const intervalRef = react.useRef(null);
2051
+ const rpcUrlRef = react.useRef(null);
2052
+ const getRpcUrl = react.useCallback(
2053
+ createGetRpcUrl({ client, rpcOverrides: config.rpcOverrides }),
2054
+ [client, config.rpcOverrides]
2055
+ );
2056
+ react.useEffect(() => {
2057
+ let cancelled = false;
2058
+ const fetchBalance = async () => {
2059
+ try {
2060
+ if (!rpcUrlRef.current) {
2061
+ throw new Error("RPC URL not initialized");
2062
+ }
2063
+ const rpcUrl = rpcUrlRef.current;
2064
+ if (input.asset.kind === "native") {
2065
+ const result = await jsonRpc(rpcUrl, "eth_getBalance", [
2066
+ input.address,
2067
+ "latest"
2068
+ ]);
2069
+ return hexToBigInt(result);
2070
+ } else {
2071
+ const data = balanceOfCalldata(input.address);
2072
+ const result = await jsonRpc(rpcUrl, "eth_call", [
2073
+ { to: input.asset.token.address, data },
2074
+ "latest"
2075
+ ]);
2076
+ return hexToBigInt(result);
2077
+ }
2078
+ } catch (err) {
2079
+ throw new Error(
2080
+ err?.message || "Failed to fetch balance from RPC endpoint"
2081
+ );
2082
+ }
2083
+ };
2084
+ const start = async () => {
2085
+ try {
2086
+ if (!rpcUrlRef.current) {
2087
+ rpcUrlRef.current = await getRpcUrl(input.chainId);
2088
+ console.log("[DepositListener] RPC URL:", rpcUrlRef.current);
2089
+ }
2090
+ const initial = await fetchBalance();
2091
+ console.log("[DepositListener] Initial balance:", initial.toString());
2092
+ if (cancelled) return;
2093
+ setStatus({ state: "listening", balance: initial });
2094
+ } catch (e) {
2095
+ console.error("[DepositListener] Error:", e);
2096
+ if (cancelled) return;
2097
+ setStatus({ state: "error", message: e.message || String(e) });
2098
+ return;
2099
+ }
2100
+ const intervalMs = input.pollIntervalMs ?? config.deposit?.pollIntervalMs ?? 6e3;
2101
+ console.log("[DepositListener] Polling interval:", intervalMs, "ms");
2102
+ intervalRef.current = window.setInterval(async () => {
2103
+ try {
2104
+ const current = await fetchBalance();
2105
+ console.log("[DepositListener] Current balance:", current.toString());
2106
+ if (cancelled) return;
2107
+ setStatus((prev) => {
2108
+ if (prev.state === "listening") {
2109
+ if (current > prev.balance) {
2110
+ console.log("[DepositListener] \u2705 DEPOSIT DETECTED! Previous:", prev.balance.toString(), "New:", current.toString());
2111
+ return {
2112
+ state: "detected",
2113
+ previousBalance: prev.balance,
2114
+ newBalance: current,
2115
+ delta: current - prev.balance
2116
+ };
2117
+ }
2118
+ if (current !== prev.balance) {
2119
+ console.log("[DepositListener] \u26A0\uFE0F Balance changed (decreased or other). Previous:", prev.balance.toString(), "New:", current.toString());
2120
+ return {
2121
+ state: "detected",
2122
+ previousBalance: prev.balance,
2123
+ newBalance: current,
2124
+ delta: current - prev.balance
2125
+ };
2126
+ }
2127
+ return prev;
2128
+ }
2129
+ if (prev.state === "detected") {
2130
+ console.log("[DepositListener] Resuming listening from new balance:", current.toString());
2131
+ return { state: "listening", balance: current };
2132
+ }
2133
+ return prev;
2134
+ });
2135
+ } catch (err) {
2136
+ console.error("[DepositListener] Polling error:", err);
2137
+ if (cancelled) return;
2138
+ setStatus({ state: "error", message: err.message || String(err) });
2139
+ }
2140
+ }, intervalMs);
2141
+ };
2142
+ start();
2143
+ return () => {
2144
+ cancelled = true;
2145
+ rpcUrlRef.current = null;
2146
+ if (intervalRef.current !== null) {
2147
+ clearInterval(intervalRef.current);
2148
+ }
2149
+ };
2150
+ }, [getRpcUrl, input.chainId, input.address, JSON.stringify(input.asset)]);
2151
+ return status;
2152
+ }
2153
+ async function enrollPasskey(params) {
2154
+ const {
2155
+ client,
2156
+ baseUrl,
2157
+ apiKey,
2158
+ userId,
2159
+ userEmail,
2160
+ projectId,
2161
+ rpId = typeof window !== "undefined" ? window.location.hostname : "localhost",
2162
+ rpName = "Volr"
2163
+ } = params;
2164
+ if (!userId) {
2165
+ throw new Error("userId is required");
2166
+ }
2167
+ if (!userEmail) {
2168
+ throw new Error("userEmail is required");
2169
+ }
2170
+ if (!projectId) {
2171
+ throw new Error("projectId is required");
2172
+ }
2173
+ if (!baseUrl) {
2174
+ throw new Error("baseUrl is required");
2175
+ }
2176
+ if (!apiKey) {
2177
+ throw new Error("apiKey is required");
2178
+ }
2179
+ if (!navigator.credentials || !navigator.credentials.create) {
2180
+ throw new Error("WebAuthn API is not supported");
2181
+ }
2182
+ const challenge = new Uint8Array(32);
2183
+ crypto.getRandomValues(challenge);
2184
+ const userHandle = new TextEncoder().encode(userId);
2185
+ const tempCredentialId = "temp-" + Date.now();
2186
+ const origin = typeof window !== "undefined" ? window.location.origin : "https://localhost";
2187
+ const tempPrfInput = {
2188
+ origin,
2189
+ projectId,
2190
+ credentialId: tempCredentialId
2191
+ };
2192
+ const prfSalt = sdkCore.deriveWrapKey(tempPrfInput);
2193
+ const publicKeyCredentialCreationOptions = {
2194
+ challenge,
2195
+ rp: {
2196
+ name: rpName,
2197
+ id: rpId
2198
+ },
2199
+ user: {
2200
+ id: userHandle,
2201
+ name: userEmail,
2202
+ displayName: userEmail
2203
+ },
2204
+ pubKeyCredParams: PUBKEY_CRED_PARAMS,
2205
+ authenticatorSelection: AUTHENTICATOR_SELECTION,
2206
+ timeout: WEBAUTHN_TIMEOUT,
2207
+ attestation: ATTESTATION,
2208
+ extensions: {
2209
+ prf: {
2210
+ eval: {
2211
+ first: prfSalt.buffer
2212
+ }
2213
+ }
2214
+ }
2215
+ };
2216
+ const credential = await navigator.credentials.create({
2217
+ publicKey: publicKeyCredentialCreationOptions
2218
+ });
2219
+ if (!credential || !("response" in credential)) {
2220
+ throw new Error("Failed to create passkey credential");
2221
+ }
2222
+ const credentialId = Array.from(new Uint8Array(credential.rawId)).map((b) => b.toString(16).padStart(2, "0")).join("");
2223
+ const extensionResults = credential.getClientExtensionResults();
2224
+ if (!extensionResults.prf || !extensionResults.prf.results || !extensionResults.prf.results.first) {
2225
+ throw new Error("PRF extension not supported or PRF output missing. Please use a browser that supports WebAuthn PRF extension.");
2226
+ }
2227
+ const prfOutputBuffer = extensionResults.prf.results.first;
2228
+ const prfOutput = new Uint8Array(prfOutputBuffer);
2229
+ const prfInput = {
2230
+ origin,
2231
+ projectId,
2232
+ credentialId
2233
+ };
2234
+ const wrapKey = prfOutput;
2235
+ const masterKeyProvider = sdkCore.createMasterKeyProvider();
2236
+ const masterSeedHandle = await masterKeyProvider.generate();
2237
+ try {
2238
+ const keyStorageType = "passkey";
2239
+ const version = "v1";
2240
+ const aadBytes = new TextEncoder().encode(
2241
+ `volr/master-seed/v1|${userId}|${keyStorageType}|${version}`
2242
+ );
2243
+ const encryptedBlob = await sdkCore.sealMasterSeed(
2244
+ masterSeedHandle.bytes,
2245
+ wrapKey,
2246
+ aadBytes
2247
+ );
2248
+ const blob = new Blob(
2249
+ [
2250
+ encryptedBlob.cipher,
2251
+ encryptedBlob.nonce
2252
+ ],
2253
+ {
2254
+ type: "application/octet-stream"
2255
+ }
2256
+ );
2257
+ const accessToken = client.getAccessToken();
2258
+ if (!accessToken) {
2259
+ throw new Error("Access token is required for blob upload");
2260
+ }
2261
+ const { key: blobUrl } = await sdkCore.uploadBlob({
2262
+ baseUrl,
2263
+ apiKey,
2264
+ accessToken,
2265
+ blob
2266
+ // Don't pass axiosInstance - let uploadBlob create its own
2267
+ });
2268
+ if (!blobUrl) {
2269
+ throw new Error("Failed to upload blob: missing key");
2270
+ }
2271
+ const keypair = sdkCore.deriveEvmKey({ masterSeed: masterSeedHandle.bytes });
2272
+ const address = keypair.address;
2273
+ const registerResponse = await client.post("/wallet/provider/register", {
2274
+ keyStorageType: "passkey",
2275
+ credentialId,
2276
+ blobUrl,
2277
+ prfInput: {
2278
+ origin,
2279
+ projectId,
2280
+ credentialId
2281
+ },
2282
+ address
2283
+ });
2284
+ return {
2285
+ credentialId,
2286
+ blobUrl,
2287
+ prfInput,
2288
+ address,
2289
+ encryptedBlob: {
2290
+ cipher: encryptedBlob.cipher,
2291
+ nonce: encryptedBlob.nonce
2292
+ },
2293
+ aad: aadBytes,
2294
+ user: registerResponse.user
2295
+ };
2296
+ } finally {
2297
+ masterSeedHandle.destroy();
2298
+ }
2299
+ }
2300
+ function usePasskeyEnrollment() {
2301
+ const { config, setProvider, setUser, user } = useVolr();
2302
+ const { client } = useInternalAuth();
2303
+ const [step, setStep] = react.useState("idle");
2304
+ const [isEnrolling, setIsEnrolling] = react.useState(false);
2305
+ const [error, setError] = react.useState(null);
2306
+ const isEnrollingRef = react.useRef(false);
2307
+ const enroll = react.useCallback(async () => {
2308
+ if (isEnrollingRef.current || isEnrolling) return;
2309
+ isEnrollingRef.current = true;
2310
+ setIsEnrolling(true);
2311
+ setError(null);
2312
+ setStep("creating");
2313
+ try {
2314
+ if (!user?.id) {
2315
+ throw new Error("User ID is required for passkey enrollment");
2316
+ }
2317
+ if (!user?.email) {
2318
+ throw new Error("User email is required for passkey enrollment");
2319
+ }
2320
+ const accessToken = client.getAccessToken();
2321
+ if (!accessToken) {
2322
+ throw new Error("Access token is required for passkey enrollment. Please login first.");
2323
+ }
2324
+ const projectId = user.id;
2325
+ setStep("encrypting");
2326
+ const result = await enrollPasskey({
2327
+ client,
2328
+ baseUrl: config.apiBaseUrl,
2329
+ apiKey: config.projectApiKey,
2330
+ userId: user.id,
2331
+ userEmail: user.email,
2332
+ projectId
2333
+ });
2334
+ setStep("registering");
2335
+ if (typeof window !== "undefined") {
2336
+ safeStorage.setItem(STORAGE_KEYS.credentialId, result.credentialId);
2337
+ }
2338
+ const passkeyAdapter = createPasskeyAdapter({
2339
+ rpId: typeof window !== "undefined" ? window.location.hostname : "localhost",
2340
+ rpName: "Volr"
2341
+ });
2342
+ const provider = sdkCore.createPasskeyProvider(passkeyAdapter, {
2343
+ prfInput: result.prfInput,
2344
+ encryptedBlob: result.encryptedBlob,
2345
+ aad: result.aad
2346
+ });
2347
+ if (result.user) {
2348
+ setUser(result.user);
2349
+ } else {
2350
+ const nextUser = {
2351
+ ...user,
2352
+ id: user.id,
2353
+ email: user.email,
2354
+ keyStorageType: "passkey",
2355
+ evmAddress: result.address,
2356
+ blobUrl: result.blobUrl,
2357
+ prfInput: result.prfInput,
2358
+ credentialId: result.credentialId
2359
+ };
2360
+ setUser(nextUser);
2361
+ }
2362
+ await setProvider(provider);
2363
+ setStep("idle");
2364
+ } catch (err) {
2365
+ const error2 = err instanceof Error ? err : new Error("Failed to enroll passkey");
2366
+ setError(error2);
2367
+ setStep("idle");
2368
+ throw error2;
2369
+ } finally {
2370
+ setIsEnrolling(false);
2371
+ isEnrollingRef.current = false;
2372
+ }
2373
+ }, [
2374
+ client,
2375
+ config.apiBaseUrl,
2376
+ config.projectApiKey,
2377
+ user,
2378
+ setProvider,
2379
+ setUser,
2380
+ isEnrolling
2381
+ ]);
2382
+ return {
2383
+ enroll,
2384
+ step,
2385
+ isEnrolling,
2386
+ error
2387
+ };
2388
+ }
2389
+
2390
+ // src/headless/mpc-transport.ts
2391
+ function createBackendMpcTransport(params) {
2392
+ const { client } = params;
2393
+ let currentSessionToken;
2394
+ let cachedAddress;
2395
+ return {
2396
+ async ensureSession() {
2397
+ if (currentSessionToken) {
2398
+ return;
2399
+ }
2400
+ const response = await client.post(
2401
+ "/wallet/mpc/connect",
2402
+ {}
2403
+ );
2404
+ currentSessionToken = response.sessionToken;
2405
+ },
2406
+ async getAddress() {
2407
+ if (cachedAddress) {
2408
+ return cachedAddress;
2409
+ }
2410
+ await this.ensureSession();
2411
+ const response = await client.get(
2412
+ "/wallet/mpc/address"
2413
+ );
2414
+ cachedAddress = response.address;
2415
+ return cachedAddress;
2416
+ },
2417
+ async signMessage(hash32) {
2418
+ await this.ensureSession();
2419
+ const hashHex = Array.from(hash32).map((b) => b.toString(16).padStart(2, "0")).join("");
2420
+ const response = await client.post("/wallet/mpc/sign", {
2421
+ hash: hashHex
2422
+ });
2423
+ const rHex = response.r.startsWith("0x") ? response.r.slice(2) : response.r;
2424
+ const sHex = response.s.startsWith("0x") ? response.s.slice(2) : response.s;
2425
+ const r = new Uint8Array(32);
2426
+ const s = new Uint8Array(32);
2427
+ for (let i = 0; i < 32; i++) {
2428
+ r[i] = parseInt(rHex.slice(i * 2, i * 2 + 2), 16);
2429
+ s[i] = parseInt(sHex.slice(i * 2, i * 2 + 2), 16);
2430
+ }
2431
+ return {
2432
+ r,
2433
+ s,
2434
+ yParity: response.yParity
2435
+ };
2436
+ },
2437
+ async signTypedData(input) {
2438
+ await this.ensureSession();
2439
+ const response = await client.post(
2440
+ "/wallet/mpc/sign-typed",
2441
+ {
2442
+ domain: input.domain,
2443
+ types: input.types,
2444
+ message: input.message
2445
+ }
2446
+ );
2447
+ return response.signature;
2448
+ },
2449
+ async getPublicKey() {
2450
+ await this.ensureSession();
2451
+ const response = await client.get(
2452
+ "/wallet/mpc/public-key"
2453
+ );
2454
+ const pubKeyHex = response.publicKey.startsWith("0x") ? response.publicKey.slice(2) : response.publicKey;
2455
+ const pubKey = new Uint8Array(65);
2456
+ for (let i = 0; i < 65; i++) {
2457
+ pubKey[i] = parseInt(pubKeyHex.slice(i * 2, i * 2 + 2), 16);
2458
+ }
2459
+ return pubKey;
2460
+ }
2461
+ };
2462
+ }
2463
+
2464
+ // src/headless/wallet-provider.ts
2465
+ async function registerWalletProvider(params) {
2466
+ const { client, keyStorageType, address, credentialId, blobUrl, prfInput } = params;
2467
+ if (!keyStorageType) {
2468
+ throw new Error("keyStorageType is required");
2469
+ }
2470
+ if (!address) {
2471
+ throw new Error("address is required");
2472
+ }
2473
+ if (keyStorageType === "passkey") {
2474
+ if (!credentialId) {
2475
+ throw new Error("credentialId is required for passkey");
2476
+ }
2477
+ if (!blobUrl) {
2478
+ throw new Error("blobUrl is required for passkey");
2479
+ }
2480
+ if (!prfInput) {
2481
+ throw new Error("prfInput is required for passkey");
2482
+ }
2483
+ }
2484
+ await client.post("/wallet/provider/register", {
2485
+ keyStorageType,
2486
+ address,
2487
+ ...keyStorageType === "passkey" && {
2488
+ credentialId,
2489
+ blobUrl,
2490
+ prfInput
2491
+ }
2492
+ });
2493
+ }
2494
+ function useMpcConnection() {
2495
+ const { setProvider } = useVolr();
2496
+ const { client } = useInternalAuth();
2497
+ const [step, setStep] = react.useState("idle");
2498
+ const [isConnecting, setIsConnecting] = react.useState(false);
2499
+ const [error, setError] = react.useState(null);
2500
+ const connect = react.useCallback(async () => {
2501
+ if (isConnecting) return;
2502
+ setIsConnecting(true);
2503
+ setError(null);
2504
+ setStep("connecting");
2505
+ try {
2506
+ const transport = createBackendMpcTransport({ client });
2507
+ await transport.ensureSession();
2508
+ const address = await transport.getAddress();
2509
+ setStep("registering");
2510
+ await registerWalletProvider({
2511
+ client,
2512
+ keyStorageType: "mpc",
2513
+ address
2514
+ });
2515
+ const provider = sdkCore.createMpcProvider(transport);
2516
+ await setProvider(provider);
2517
+ setStep("idle");
2518
+ } catch (err) {
2519
+ const error2 = err instanceof Error ? err : new Error("Failed to connect to MPC provider");
2520
+ setError(error2);
2521
+ setStep("idle");
2522
+ throw error2;
2523
+ } finally {
2524
+ setIsConnecting(false);
2525
+ }
2526
+ }, [client, setProvider, isConnecting]);
2527
+ return {
2528
+ connect,
2529
+ step,
2530
+ isConnecting,
2531
+ error
2532
+ };
2533
+ }
2534
+
2535
+ // src/headless/blobs.ts
2536
+ async function uploadBlobViaPresign(params) {
2537
+ const { baseUrl, apiKey, blob, contentType = "application/octet-stream" } = params;
2538
+ const api = createAxiosInstance(baseUrl, apiKey);
2539
+ const presignResponse = await api.post("/blob/presign", {
2540
+ op: "put",
2541
+ contentType
2542
+ });
2543
+ const presignData = presignResponse.data?.data || presignResponse.data;
2544
+ const uploadUrl = presignData.url;
2545
+ const s3Key = presignData.s3Key;
2546
+ const putRes = await fetch(uploadUrl, {
2547
+ method: "PUT",
2548
+ body: blob,
2549
+ headers: {
2550
+ "Content-Type": contentType,
2551
+ "x-amz-server-side-encryption": "AES256"
2552
+ },
2553
+ mode: "cors"
2554
+ });
2555
+ if (!putRes.ok) {
2556
+ const text = await putRes.text().catch(() => "");
2557
+ throw new Error(`Failed to upload to S3 (${putRes.status}): ${text || putRes.statusText}`);
2558
+ }
2559
+ return { s3Key };
2560
+ }
2561
+
2562
+ Object.defineProperty(exports, "createMasterKeyProvider", {
2563
+ enumerable: true,
2564
+ get: function () { return sdkCore.createMasterKeyProvider; }
2565
+ });
2566
+ Object.defineProperty(exports, "createMpcProvider", {
2567
+ enumerable: true,
2568
+ get: function () { return sdkCore.createMpcProvider; }
2569
+ });
2570
+ Object.defineProperty(exports, "createPasskeyProvider", {
2571
+ enumerable: true,
2572
+ get: function () { return sdkCore.createPasskeyProvider; }
2573
+ });
2574
+ Object.defineProperty(exports, "deriveEvmKey", {
2575
+ enumerable: true,
2576
+ get: function () { return sdkCore.deriveEvmKey; }
2577
+ });
2578
+ Object.defineProperty(exports, "deriveWrapKey", {
2579
+ enumerable: true,
2580
+ get: function () { return sdkCore.deriveWrapKey; }
2581
+ });
2582
+ Object.defineProperty(exports, "sealMasterSeed", {
2583
+ enumerable: true,
2584
+ get: function () { return sdkCore.sealMasterSeed; }
2585
+ });
2586
+ Object.defineProperty(exports, "uploadBlob", {
2587
+ enumerable: true,
2588
+ get: function () { return sdkCore.uploadBlob; }
2589
+ });
2590
+ exports.DEFAULT_EXPIRES_IN_SEC = DEFAULT_EXPIRES_IN_SEC;
2591
+ exports.DEFAULT_MODE = DEFAULT_MODE;
2592
+ exports.VolrProvider = VolrProvider;
2593
+ exports.buildCall = buildCall;
2594
+ exports.buildCalls = buildCalls;
2595
+ exports.createGetNetworkInfo = createGetNetworkInfo;
2596
+ exports.createPasskeyAdapter = createPasskeyAdapter;
2597
+ exports.defaultIdempotencyKey = defaultIdempotencyKey;
2598
+ exports.normalizeHex = normalizeHex;
2599
+ exports.normalizeHexArray = normalizeHexArray;
2600
+ exports.uploadBlobViaPresign = uploadBlobViaPresign;
2601
+ exports.useDepositListener = useDepositListener;
2602
+ exports.useInternalAuth = useInternalAuth;
2603
+ exports.useMpcConnection = useMpcConnection;
2604
+ exports.usePasskeyEnrollment = usePasskeyEnrollment;
2605
+ exports.usePrecheck = usePrecheck;
2606
+ exports.useRelay = useRelay;
2607
+ exports.useVolr = useVolr;
2608
+ exports.useVolrLogin = useVolrLogin;
2609
+ exports.useVolrWallet = useVolrWallet;
2610
+ //# sourceMappingURL=index.cjs.map
2611
+ //# sourceMappingURL=index.cjs.map