@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.js CHANGED
@@ -9750,10 +9750,42 @@ function useSessionSync({
9750
9750
  }
9751
9751
 
9752
9752
  // src/config/webauthn.ts
9753
+ function isWindows() {
9754
+ if (typeof navigator === "undefined") return false;
9755
+ const platform = navigator.platform || "";
9756
+ return /Win/.test(platform);
9757
+ }
9758
+ function isLinux() {
9759
+ if (typeof navigator === "undefined") return false;
9760
+ const platform = navigator.platform || "";
9761
+ const ua = navigator.userAgent;
9762
+ return /Linux/.test(platform) && !/CrOS/.test(ua) && !/Android/.test(ua);
9763
+ }
9764
+ function shouldForceCrossDevice() {
9765
+ return isWindows() || isLinux();
9766
+ }
9753
9767
  var AUTHENTICATOR_SELECTION = {
9754
9768
  userVerification: "required",
9755
9769
  residentKey: "required"
9756
9770
  };
9771
+ var AUTHENTICATOR_SELECTION_CROSS_DEVICE = {
9772
+ userVerification: "required",
9773
+ residentKey: "required",
9774
+ authenticatorAttachment: "cross-platform"
9775
+ };
9776
+ function getAuthenticatorSelection() {
9777
+ if (shouldForceCrossDevice()) {
9778
+ return AUTHENTICATOR_SELECTION_CROSS_DEVICE;
9779
+ }
9780
+ return AUTHENTICATOR_SELECTION;
9781
+ }
9782
+ function getWebAuthnHints() {
9783
+ if (shouldForceCrossDevice()) {
9784
+ return ["hybrid"];
9785
+ }
9786
+ return void 0;
9787
+ }
9788
+ var AUTHENTICATOR_TRANSPORTS = ["internal", "hybrid"];
9757
9789
  var CREDENTIAL_MEDIATION = "required";
9758
9790
  var USER_VERIFICATION = "required";
9759
9791
  var WEBAUTHN_TIMEOUT = 6e4;
@@ -9921,50 +9953,51 @@ function createPasskeyAdapter(options = {}) {
9921
9953
  return { r, s };
9922
9954
  },
9923
9955
  async authenticate(prfInput) {
9924
- if (!prfInput.credentialId) {
9925
- throw new Error(
9926
- "[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."
9927
- );
9928
- }
9929
- const hexString = prfInput.credentialId.startsWith("0x") ? prfInput.credentialId.slice(2) : prfInput.credentialId;
9930
- if (!/^[0-9a-fA-F]+$/.test(hexString)) {
9931
- console.error(
9932
- "[PasskeyAdapter] Invalid credentialId format (not hex):",
9933
- prfInput.credentialId
9934
- );
9935
- throw new Error(
9936
- `[PasskeyAdapter] Invalid credentialId format. Expected hex string, got: ${prfInput.credentialId}`
9937
- );
9938
- }
9939
- const normalizedHex = hexString.length % 2 === 0 ? hexString : "0" + hexString;
9940
- const hexPairs = normalizedHex.match(/.{1,2}/g);
9941
- if (!hexPairs) {
9942
- throw new Error(
9943
- `[PasskeyAdapter] Failed to parse hex string: ${normalizedHex}`
9944
- );
9945
- }
9946
- const credIdBytes = new Uint8Array(
9947
- hexPairs.map((byte) => parseInt(byte, 16))
9948
- );
9949
- const reconvertedHex = Array.from(credIdBytes).map((b) => b.toString(16).padStart(2, "0")).join("");
9950
- if (reconvertedHex.toLowerCase() !== normalizedHex.toLowerCase()) {
9951
- throw new Error(
9952
- `[PasskeyAdapter] credentialId conversion failed. Original: ${normalizedHex}, Reconverted: ${reconvertedHex}`
9956
+ const allowCredentials = [];
9957
+ if (prfInput.credentialId) {
9958
+ const hexString = prfInput.credentialId.startsWith("0x") ? prfInput.credentialId.slice(2) : prfInput.credentialId;
9959
+ if (!/^[0-9a-fA-F]+$/.test(hexString)) {
9960
+ console.error(
9961
+ "[PasskeyAdapter] Invalid credentialId format (not hex):",
9962
+ prfInput.credentialId
9963
+ );
9964
+ throw new Error(
9965
+ `[PasskeyAdapter] Invalid credentialId format. Expected hex string, got: ${prfInput.credentialId}`
9966
+ );
9967
+ }
9968
+ const normalizedHex = hexString.length % 2 === 0 ? hexString : "0" + hexString;
9969
+ const hexPairs = normalizedHex.match(/.{1,2}/g);
9970
+ if (!hexPairs) {
9971
+ throw new Error(
9972
+ `[PasskeyAdapter] Failed to parse hex string: ${normalizedHex}`
9973
+ );
9974
+ }
9975
+ const credIdBytes = new Uint8Array(
9976
+ hexPairs.map((byte) => parseInt(byte, 16))
9953
9977
  );
9954
- }
9955
- const allowCredentials = [
9956
- {
9957
- id: credIdBytes,
9958
- type: "public-key"
9978
+ const reconvertedHex = Array.from(credIdBytes).map((b) => b.toString(16).padStart(2, "0")).join("");
9979
+ if (reconvertedHex.toLowerCase() !== normalizedHex.toLowerCase()) {
9980
+ throw new Error(
9981
+ `[PasskeyAdapter] credentialId conversion failed. Original: ${normalizedHex}, Reconverted: ${reconvertedHex}`
9982
+ );
9959
9983
  }
9960
- ];
9984
+ const transports = shouldForceCrossDevice() ? ["hybrid"] : AUTHENTICATOR_TRANSPORTS;
9985
+ allowCredentials.push({
9986
+ id: credIdBytes,
9987
+ type: "public-key",
9988
+ transports
9989
+ });
9990
+ }
9991
+ const hints = getWebAuthnHints();
9961
9992
  return withWebAuthnLock(async () => {
9962
9993
  let credential = null;
9963
9994
  try {
9964
- credential = await navigator.credentials.get({
9995
+ const requestOptions = {
9965
9996
  publicKey: {
9966
9997
  challenge: crypto.getRandomValues(new Uint8Array(32)),
9967
9998
  rpId,
9999
+ // If allowCredentials is empty, the browser will use discoverable credentials.
10000
+ // This enables the account chooser and cross-device (hybrid) QR flows.
9968
10001
  allowCredentials,
9969
10002
  userVerification: USER_VERIFICATION,
9970
10003
  // Shared constant
@@ -9974,11 +10007,15 @@ function createPasskeyAdapter(options = {}) {
9974
10007
  first: prfInput.salt.buffer
9975
10008
  }
9976
10009
  }
9977
- }
10010
+ },
10011
+ // Chrome 128+ supports hints to prioritize certain authenticator types
10012
+ // On Windows/Linux, this prioritizes QR code over USB security key prompt
10013
+ ...hints && { hints }
9978
10014
  },
9979
10015
  mediation: CREDENTIAL_MEDIATION
9980
10016
  // Use shared constant
9981
- });
10017
+ };
10018
+ credential = await navigator.credentials.get(requestOptions);
9982
10019
  } catch (error) {
9983
10020
  console.error("[PasskeyAdapter] WebAuthn get() failed:", error);
9984
10021
  console.error("[PasskeyAdapter] Error name:", error?.name);
@@ -10364,6 +10401,10 @@ function VolrProvider({ config, children }) {
10364
10401
  const [isLoading, setIsLoading] = useState(true);
10365
10402
  const [error, setError] = useState(null);
10366
10403
  const [chainId] = useState(config.defaultChainId);
10404
+ const userRef = useRef(null);
10405
+ useEffect(() => {
10406
+ userRef.current = user;
10407
+ }, [user]);
10367
10408
  const [accessToken, setAccessTokenState] = useState(() => {
10368
10409
  return client.getAccessToken();
10369
10410
  });
@@ -10384,10 +10425,11 @@ function VolrProvider({ config, children }) {
10384
10425
  setError(null);
10385
10426
  const keyStorageType = newProvider.keyStorageType;
10386
10427
  setProviderState(newProvider);
10387
- const userHasCompleteData = user?.keyStorageType === "passkey" && user?.blobUrl && user?.prfInput && user?.id;
10428
+ const currentUser = userRef.current;
10429
+ const userHasCompleteData = currentUser?.keyStorageType === "passkey" && currentUser?.blobUrl && currentUser?.prfInput && currentUser?.id;
10388
10430
  if (userHasCompleteData) {
10389
10431
  console.log("[Provider] setProvider: User data already complete, skipping refresh");
10390
- if (user.keyStorageType !== keyStorageType) {
10432
+ if (currentUser?.keyStorageType !== keyStorageType) {
10391
10433
  setUser((prev) => ({ ...prev, keyStorageType }));
10392
10434
  }
10393
10435
  } else {
@@ -10419,7 +10461,7 @@ function VolrProvider({ config, children }) {
10419
10461
  throw error2;
10420
10462
  }
10421
10463
  },
10422
- [client, user, syncRef]
10464
+ [client, syncRef]
10423
10465
  );
10424
10466
  useAutoRecover({
10425
10467
  client,
@@ -18479,26 +18521,28 @@ function createGetRpcUrl(deps) {
18479
18521
  };
18480
18522
  }
18481
18523
  function createGetNetworkInfo(deps) {
18482
- const { client } = deps;
18524
+ const { client, rpcOverrides } = deps;
18483
18525
  return async function getNetworkInfo(chainId, includeRpcUrl = false) {
18526
+ const overrideUrl = rpcOverrides?.[chainId.toString()];
18484
18527
  const cached = networkCache.get(chainId);
18485
18528
  if (cached && Date.now() - cached.timestamp < CACHE_TTL_MS) {
18486
18529
  return {
18487
18530
  name: cached.name || `Chain ${chainId}`,
18488
- rpcUrl: includeRpcUrl ? cached.rpcUrl : void 0,
18531
+ rpcUrl: includeRpcUrl ? overrideUrl ?? cached.rpcUrl : void 0,
18489
18532
  invokerAddress: cached.invokerAddress
18490
18533
  };
18491
18534
  }
18492
18535
  const response = await client.get(`/networks/${chainId}${includeRpcUrl ? "?includeRpcUrl=true" : ""}`);
18493
18536
  networkCache.set(chainId, {
18494
18537
  name: response.name,
18495
- rpcUrl: response.rpcUrl,
18538
+ // If an override exists, cache it as the effective rpcUrl for this chainId
18539
+ rpcUrl: overrideUrl ?? response.rpcUrl,
18496
18540
  invokerAddress: response.invokerAddress,
18497
18541
  timestamp: Date.now()
18498
18542
  });
18499
18543
  return {
18500
18544
  name: response.name,
18501
- rpcUrl: includeRpcUrl ? response.rpcUrl : void 0,
18545
+ rpcUrl: includeRpcUrl ? overrideUrl ?? response.rpcUrl : void 0,
18502
18546
  invokerAddress: response.invokerAddress
18503
18547
  };
18504
18548
  };
@@ -18537,7 +18581,7 @@ function validateCalls2(calls) {
18537
18581
 
18538
18582
  // src/wallet/normalize.ts
18539
18583
  function normalizeCall(call2) {
18540
- const normalizedTarget = getAddress(call2.target);
18584
+ const normalizedTarget = normalizeHex(getAddress(call2.target));
18541
18585
  const normalizedData = normalizeHex(call2.data);
18542
18586
  const value = call2.value ?? 0n;
18543
18587
  const gasLimit = call2.gasLimit ?? 0n;
@@ -18773,7 +18817,7 @@ async function sendCalls(args) {
18773
18817
  if (chainId === 0) {
18774
18818
  throw new Error("chainId cannot be 0");
18775
18819
  }
18776
- const normalizedFrom = from14;
18820
+ const normalizedFrom = normalizeHex(from14);
18777
18821
  const normalizedCalls = normalizeCalls(calls);
18778
18822
  validateCalls2(normalizedCalls);
18779
18823
  if (!deps.provider && deps.user?.keyStorageType !== "passkey") {
@@ -18837,7 +18881,7 @@ async function sendCalls(args) {
18837
18881
  if (!quotePolicyId) {
18838
18882
  throw new Error("Backend did not return policyId in precheck response");
18839
18883
  }
18840
- const projectPolicyId = quotePolicyId;
18884
+ const projectPolicyId = normalizeHex(quotePolicyId);
18841
18885
  validatePolicyId2(projectPolicyId);
18842
18886
  const tempAuth = buildTempAuth({
18843
18887
  chainId,
@@ -19652,6 +19696,8 @@ async function enrollPasskey(params) {
19652
19696
  };
19653
19697
  const prfSalt = deriveWrapKey(tempPrfInput);
19654
19698
  const displayName = buildDisplayName(userEmail, userEvmAddress, userId);
19699
+ const authenticatorSelection = getAuthenticatorSelection();
19700
+ const hints = getWebAuthnHints();
19655
19701
  const publicKeyCredentialCreationOptions = {
19656
19702
  challenge: challenge2,
19657
19703
  rp: {
@@ -19664,7 +19710,7 @@ async function enrollPasskey(params) {
19664
19710
  displayName
19665
19711
  },
19666
19712
  pubKeyCredParams: PUBKEY_CRED_PARAMS,
19667
- authenticatorSelection: AUTHENTICATOR_SELECTION,
19713
+ authenticatorSelection,
19668
19714
  timeout: WEBAUTHN_TIMEOUT,
19669
19715
  attestation: ATTESTATION,
19670
19716
  extensions: {
@@ -19673,7 +19719,10 @@ async function enrollPasskey(params) {
19673
19719
  first: prfSalt.buffer
19674
19720
  }
19675
19721
  }
19676
- }
19722
+ },
19723
+ // Chrome 128+ supports hints to prioritize certain authenticator types
19724
+ // On Windows/Linux, this prioritizes QR code over USB security key prompt
19725
+ ...hints && { hints }
19677
19726
  };
19678
19727
  const credential = await navigator.credentials.create({
19679
19728
  publicKey: publicKeyCredentialCreationOptions
@@ -20867,7 +20916,12 @@ function getPlatformHint(platform) {
20867
20916
  case "Windows":
20868
20917
  return {
20869
20918
  hintKey: "passkey.hint.windows",
20870
- noteKey: "passkey.hint.note"
20919
+ noteKey: "passkey.hint.windowsNote"
20920
+ };
20921
+ case "Linux":
20922
+ return {
20923
+ hintKey: "passkey.hint.linux",
20924
+ noteKey: "passkey.hint.windowsNote"
20871
20925
  };
20872
20926
  default:
20873
20927
  return {
@@ -21061,6 +21115,8 @@ async function completeMigration(params) {
21061
21115
  credentialId: tempCredentialId
21062
21116
  };
21063
21117
  const prfSalt = deriveWrapKey(tempPrfInput);
21118
+ const authenticatorSelection = getAuthenticatorSelection();
21119
+ const hints = getWebAuthnHints();
21064
21120
  const publicKeyCredentialCreationOptions = {
21065
21121
  challenge: challenge2,
21066
21122
  rp: {
@@ -21073,7 +21129,7 @@ async function completeMigration(params) {
21073
21129
  displayName
21074
21130
  },
21075
21131
  pubKeyCredParams: PUBKEY_CRED_PARAMS,
21076
- authenticatorSelection: AUTHENTICATOR_SELECTION,
21132
+ authenticatorSelection,
21077
21133
  timeout: WEBAUTHN_TIMEOUT,
21078
21134
  attestation: ATTESTATION,
21079
21135
  extensions: {
@@ -21082,7 +21138,10 @@ async function completeMigration(params) {
21082
21138
  first: prfSalt.buffer
21083
21139
  }
21084
21140
  }
21085
- }
21141
+ },
21142
+ // Chrome 128+ supports hints to prioritize certain authenticator types
21143
+ // On Windows/Linux, this prioritizes QR code over USB security key prompt
21144
+ ...hints && { hints }
21086
21145
  };
21087
21146
  const credential = await navigator.credentials.create({
21088
21147
  publicKey: publicKeyCredentialCreationOptions