@volr/react 0.1.130 → 0.1.132

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 CHANGED
@@ -9774,10 +9774,42 @@ function useSessionSync({
9774
9774
  }
9775
9775
 
9776
9776
  // src/config/webauthn.ts
9777
+ function isWindows() {
9778
+ if (typeof navigator === "undefined") return false;
9779
+ const platform = navigator.platform || "";
9780
+ return /Win/.test(platform);
9781
+ }
9782
+ function isLinux() {
9783
+ if (typeof navigator === "undefined") return false;
9784
+ const platform = navigator.platform || "";
9785
+ const ua = navigator.userAgent;
9786
+ return /Linux/.test(platform) && !/CrOS/.test(ua) && !/Android/.test(ua);
9787
+ }
9788
+ function shouldForceCrossDevice() {
9789
+ return isWindows() || isLinux();
9790
+ }
9777
9791
  var AUTHENTICATOR_SELECTION = {
9778
9792
  userVerification: "required",
9779
9793
  residentKey: "required"
9780
9794
  };
9795
+ var AUTHENTICATOR_SELECTION_CROSS_DEVICE = {
9796
+ userVerification: "required",
9797
+ residentKey: "required",
9798
+ authenticatorAttachment: "cross-platform"
9799
+ };
9800
+ function getAuthenticatorSelection() {
9801
+ if (shouldForceCrossDevice()) {
9802
+ return AUTHENTICATOR_SELECTION_CROSS_DEVICE;
9803
+ }
9804
+ return AUTHENTICATOR_SELECTION;
9805
+ }
9806
+ function getWebAuthnHints() {
9807
+ if (shouldForceCrossDevice()) {
9808
+ return ["hybrid"];
9809
+ }
9810
+ return void 0;
9811
+ }
9812
+ var AUTHENTICATOR_TRANSPORTS = ["internal", "hybrid"];
9781
9813
  var CREDENTIAL_MEDIATION = "required";
9782
9814
  var USER_VERIFICATION = "required";
9783
9815
  var WEBAUTHN_TIMEOUT = 6e4;
@@ -9945,50 +9977,51 @@ function createPasskeyAdapter(options = {}) {
9945
9977
  return { r, s };
9946
9978
  },
9947
9979
  async authenticate(prfInput) {
9948
- if (!prfInput.credentialId) {
9949
- throw new Error(
9950
- "[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."
9951
- );
9952
- }
9953
- const hexString = prfInput.credentialId.startsWith("0x") ? prfInput.credentialId.slice(2) : prfInput.credentialId;
9954
- if (!/^[0-9a-fA-F]+$/.test(hexString)) {
9955
- console.error(
9956
- "[PasskeyAdapter] Invalid credentialId format (not hex):",
9957
- prfInput.credentialId
9958
- );
9959
- throw new Error(
9960
- `[PasskeyAdapter] Invalid credentialId format. Expected hex string, got: ${prfInput.credentialId}`
9961
- );
9962
- }
9963
- const normalizedHex = hexString.length % 2 === 0 ? hexString : "0" + hexString;
9964
- const hexPairs = normalizedHex.match(/.{1,2}/g);
9965
- if (!hexPairs) {
9966
- throw new Error(
9967
- `[PasskeyAdapter] Failed to parse hex string: ${normalizedHex}`
9968
- );
9969
- }
9970
- const credIdBytes = new Uint8Array(
9971
- hexPairs.map((byte) => parseInt(byte, 16))
9972
- );
9973
- const reconvertedHex = Array.from(credIdBytes).map((b) => b.toString(16).padStart(2, "0")).join("");
9974
- if (reconvertedHex.toLowerCase() !== normalizedHex.toLowerCase()) {
9975
- throw new Error(
9976
- `[PasskeyAdapter] credentialId conversion failed. Original: ${normalizedHex}, Reconverted: ${reconvertedHex}`
9980
+ const allowCredentials = [];
9981
+ if (prfInput.credentialId) {
9982
+ const hexString = prfInput.credentialId.startsWith("0x") ? prfInput.credentialId.slice(2) : prfInput.credentialId;
9983
+ if (!/^[0-9a-fA-F]+$/.test(hexString)) {
9984
+ console.error(
9985
+ "[PasskeyAdapter] Invalid credentialId format (not hex):",
9986
+ prfInput.credentialId
9987
+ );
9988
+ throw new Error(
9989
+ `[PasskeyAdapter] Invalid credentialId format. Expected hex string, got: ${prfInput.credentialId}`
9990
+ );
9991
+ }
9992
+ const normalizedHex = hexString.length % 2 === 0 ? hexString : "0" + hexString;
9993
+ const hexPairs = normalizedHex.match(/.{1,2}/g);
9994
+ if (!hexPairs) {
9995
+ throw new Error(
9996
+ `[PasskeyAdapter] Failed to parse hex string: ${normalizedHex}`
9997
+ );
9998
+ }
9999
+ const credIdBytes = new Uint8Array(
10000
+ hexPairs.map((byte) => parseInt(byte, 16))
9977
10001
  );
9978
- }
9979
- const allowCredentials = [
9980
- {
9981
- id: credIdBytes,
9982
- type: "public-key"
10002
+ const reconvertedHex = Array.from(credIdBytes).map((b) => b.toString(16).padStart(2, "0")).join("");
10003
+ if (reconvertedHex.toLowerCase() !== normalizedHex.toLowerCase()) {
10004
+ throw new Error(
10005
+ `[PasskeyAdapter] credentialId conversion failed. Original: ${normalizedHex}, Reconverted: ${reconvertedHex}`
10006
+ );
9983
10007
  }
9984
- ];
10008
+ const transports = shouldForceCrossDevice() ? ["hybrid"] : AUTHENTICATOR_TRANSPORTS;
10009
+ allowCredentials.push({
10010
+ id: credIdBytes,
10011
+ type: "public-key",
10012
+ transports
10013
+ });
10014
+ }
10015
+ const hints = getWebAuthnHints();
9985
10016
  return withWebAuthnLock(async () => {
9986
10017
  let credential = null;
9987
10018
  try {
9988
- credential = await navigator.credentials.get({
10019
+ const requestOptions = {
9989
10020
  publicKey: {
9990
10021
  challenge: crypto.getRandomValues(new Uint8Array(32)),
9991
10022
  rpId,
10023
+ // If allowCredentials is empty, the browser will use discoverable credentials.
10024
+ // This enables the account chooser and cross-device (hybrid) QR flows.
9992
10025
  allowCredentials,
9993
10026
  userVerification: USER_VERIFICATION,
9994
10027
  // Shared constant
@@ -9998,11 +10031,15 @@ function createPasskeyAdapter(options = {}) {
9998
10031
  first: prfInput.salt.buffer
9999
10032
  }
10000
10033
  }
10001
- }
10034
+ },
10035
+ // Chrome 128+ supports hints to prioritize certain authenticator types
10036
+ // On Windows/Linux, this prioritizes QR code over USB security key prompt
10037
+ ...hints && { hints }
10002
10038
  },
10003
10039
  mediation: CREDENTIAL_MEDIATION
10004
10040
  // Use shared constant
10005
- });
10041
+ };
10042
+ credential = await navigator.credentials.get(requestOptions);
10006
10043
  } catch (error) {
10007
10044
  console.error("[PasskeyAdapter] WebAuthn get() failed:", error);
10008
10045
  console.error("[PasskeyAdapter] Error name:", error?.name);
@@ -10388,6 +10425,10 @@ function VolrProvider({ config, children }) {
10388
10425
  const [isLoading, setIsLoading] = react.useState(true);
10389
10426
  const [error, setError] = react.useState(null);
10390
10427
  const [chainId] = react.useState(config.defaultChainId);
10428
+ const userRef = react.useRef(null);
10429
+ react.useEffect(() => {
10430
+ userRef.current = user;
10431
+ }, [user]);
10391
10432
  const [accessToken, setAccessTokenState] = react.useState(() => {
10392
10433
  return client.getAccessToken();
10393
10434
  });
@@ -10408,10 +10449,11 @@ function VolrProvider({ config, children }) {
10408
10449
  setError(null);
10409
10450
  const keyStorageType = newProvider.keyStorageType;
10410
10451
  setProviderState(newProvider);
10411
- const userHasCompleteData = user?.keyStorageType === "passkey" && user?.blobUrl && user?.prfInput && user?.id;
10452
+ const currentUser = userRef.current;
10453
+ const userHasCompleteData = currentUser?.keyStorageType === "passkey" && currentUser?.blobUrl && currentUser?.prfInput && currentUser?.id;
10412
10454
  if (userHasCompleteData) {
10413
10455
  console.log("[Provider] setProvider: User data already complete, skipping refresh");
10414
- if (user.keyStorageType !== keyStorageType) {
10456
+ if (currentUser?.keyStorageType !== keyStorageType) {
10415
10457
  setUser((prev) => ({ ...prev, keyStorageType }));
10416
10458
  }
10417
10459
  } else {
@@ -10443,7 +10485,7 @@ function VolrProvider({ config, children }) {
10443
10485
  throw error2;
10444
10486
  }
10445
10487
  },
10446
- [client, user, syncRef]
10488
+ [client, syncRef]
10447
10489
  );
10448
10490
  useAutoRecover({
10449
10491
  client,
@@ -18503,26 +18545,28 @@ function createGetRpcUrl(deps) {
18503
18545
  };
18504
18546
  }
18505
18547
  function createGetNetworkInfo(deps) {
18506
- const { client } = deps;
18548
+ const { client, rpcOverrides } = deps;
18507
18549
  return async function getNetworkInfo(chainId, includeRpcUrl = false) {
18550
+ const overrideUrl = rpcOverrides?.[chainId.toString()];
18508
18551
  const cached = networkCache.get(chainId);
18509
18552
  if (cached && Date.now() - cached.timestamp < CACHE_TTL_MS) {
18510
18553
  return {
18511
18554
  name: cached.name || `Chain ${chainId}`,
18512
- rpcUrl: includeRpcUrl ? cached.rpcUrl : void 0,
18555
+ rpcUrl: includeRpcUrl ? overrideUrl ?? cached.rpcUrl : void 0,
18513
18556
  invokerAddress: cached.invokerAddress
18514
18557
  };
18515
18558
  }
18516
18559
  const response = await client.get(`/networks/${chainId}${includeRpcUrl ? "?includeRpcUrl=true" : ""}`);
18517
18560
  networkCache.set(chainId, {
18518
18561
  name: response.name,
18519
- rpcUrl: response.rpcUrl,
18562
+ // If an override exists, cache it as the effective rpcUrl for this chainId
18563
+ rpcUrl: overrideUrl ?? response.rpcUrl,
18520
18564
  invokerAddress: response.invokerAddress,
18521
18565
  timestamp: Date.now()
18522
18566
  });
18523
18567
  return {
18524
18568
  name: response.name,
18525
- rpcUrl: includeRpcUrl ? response.rpcUrl : void 0,
18569
+ rpcUrl: includeRpcUrl ? overrideUrl ?? response.rpcUrl : void 0,
18526
18570
  invokerAddress: response.invokerAddress
18527
18571
  };
18528
18572
  };
@@ -18561,7 +18605,7 @@ function validateCalls2(calls) {
18561
18605
 
18562
18606
  // src/wallet/normalize.ts
18563
18607
  function normalizeCall(call2) {
18564
- const normalizedTarget = getAddress(call2.target);
18608
+ const normalizedTarget = normalizeHex(getAddress(call2.target));
18565
18609
  const normalizedData = normalizeHex(call2.data);
18566
18610
  const value = call2.value ?? 0n;
18567
18611
  const gasLimit = call2.gasLimit ?? 0n;
@@ -18797,7 +18841,7 @@ async function sendCalls(args) {
18797
18841
  if (chainId === 0) {
18798
18842
  throw new Error("chainId cannot be 0");
18799
18843
  }
18800
- const normalizedFrom = from14;
18844
+ const normalizedFrom = normalizeHex(from14);
18801
18845
  const normalizedCalls = normalizeCalls(calls);
18802
18846
  validateCalls2(normalizedCalls);
18803
18847
  if (!deps.provider && deps.user?.keyStorageType !== "passkey") {
@@ -18861,7 +18905,7 @@ async function sendCalls(args) {
18861
18905
  if (!quotePolicyId) {
18862
18906
  throw new Error("Backend did not return policyId in precheck response");
18863
18907
  }
18864
- const projectPolicyId = quotePolicyId;
18908
+ const projectPolicyId = normalizeHex(quotePolicyId);
18865
18909
  validatePolicyId2(projectPolicyId);
18866
18910
  const tempAuth = buildTempAuth({
18867
18911
  chainId,
@@ -19676,6 +19720,8 @@ async function enrollPasskey(params) {
19676
19720
  };
19677
19721
  const prfSalt = sdkCore.deriveWrapKey(tempPrfInput);
19678
19722
  const displayName = buildDisplayName(userEmail, userEvmAddress, userId);
19723
+ const authenticatorSelection = getAuthenticatorSelection();
19724
+ const hints = getWebAuthnHints();
19679
19725
  const publicKeyCredentialCreationOptions = {
19680
19726
  challenge: challenge2,
19681
19727
  rp: {
@@ -19688,7 +19734,7 @@ async function enrollPasskey(params) {
19688
19734
  displayName
19689
19735
  },
19690
19736
  pubKeyCredParams: PUBKEY_CRED_PARAMS,
19691
- authenticatorSelection: AUTHENTICATOR_SELECTION,
19737
+ authenticatorSelection,
19692
19738
  timeout: WEBAUTHN_TIMEOUT,
19693
19739
  attestation: ATTESTATION,
19694
19740
  extensions: {
@@ -19697,7 +19743,10 @@ async function enrollPasskey(params) {
19697
19743
  first: prfSalt.buffer
19698
19744
  }
19699
19745
  }
19700
- }
19746
+ },
19747
+ // Chrome 128+ supports hints to prioritize certain authenticator types
19748
+ // On Windows/Linux, this prioritizes QR code over USB security key prompt
19749
+ ...hints && { hints }
19701
19750
  };
19702
19751
  const credential = await navigator.credentials.create({
19703
19752
  publicKey: publicKeyCredentialCreationOptions
@@ -20891,7 +20940,12 @@ function getPlatformHint(platform) {
20891
20940
  case "Windows":
20892
20941
  return {
20893
20942
  hintKey: "passkey.hint.windows",
20894
- noteKey: "passkey.hint.note"
20943
+ noteKey: "passkey.hint.windowsNote"
20944
+ };
20945
+ case "Linux":
20946
+ return {
20947
+ hintKey: "passkey.hint.linux",
20948
+ noteKey: "passkey.hint.windowsNote"
20895
20949
  };
20896
20950
  default:
20897
20951
  return {
@@ -21085,6 +21139,8 @@ async function completeMigration(params) {
21085
21139
  credentialId: tempCredentialId
21086
21140
  };
21087
21141
  const prfSalt = sdkCore.deriveWrapKey(tempPrfInput);
21142
+ const authenticatorSelection = getAuthenticatorSelection();
21143
+ const hints = getWebAuthnHints();
21088
21144
  const publicKeyCredentialCreationOptions = {
21089
21145
  challenge: challenge2,
21090
21146
  rp: {
@@ -21097,7 +21153,7 @@ async function completeMigration(params) {
21097
21153
  displayName
21098
21154
  },
21099
21155
  pubKeyCredParams: PUBKEY_CRED_PARAMS,
21100
- authenticatorSelection: AUTHENTICATOR_SELECTION,
21156
+ authenticatorSelection,
21101
21157
  timeout: WEBAUTHN_TIMEOUT,
21102
21158
  attestation: ATTESTATION,
21103
21159
  extensions: {
@@ -21106,7 +21162,10 @@ async function completeMigration(params) {
21106
21162
  first: prfSalt.buffer
21107
21163
  }
21108
21164
  }
21109
- }
21165
+ },
21166
+ // Chrome 128+ supports hints to prioritize certain authenticator types
21167
+ // On Windows/Linux, this prioritizes QR code over USB security key prompt
21168
+ ...hints && { hints }
21110
21169
  };
21111
21170
  const credential = await navigator.credentials.create({
21112
21171
  publicKey: publicKeyCredentialCreationOptions