@volr/react 0.1.20 → 0.1.22

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
@@ -9330,6 +9330,21 @@ var APIClient = class {
9330
9330
  }
9331
9331
  };
9332
9332
 
9333
+ // src/config/backend.ts
9334
+ var DEFAULT_API_BASE_URL = "https://api.volr.io";
9335
+ function resolveApiBaseUrl(config) {
9336
+ const anyConfig = config;
9337
+ const devOverride = anyConfig.__devApiBaseUrl;
9338
+ if (devOverride && typeof devOverride === "string") {
9339
+ return devOverride.replace(/\/+$/, "");
9340
+ }
9341
+ const override = anyConfig.apiBaseUrl;
9342
+ if (override && typeof override === "string") {
9343
+ return override.replace(/\/+$/, "");
9344
+ }
9345
+ return DEFAULT_API_BASE_URL;
9346
+ }
9347
+
9333
9348
  // src/headless/auth-sync.ts
9334
9349
  var SessionSync = class {
9335
9350
  constructor() {
@@ -9343,11 +9358,12 @@ var SessionSync = class {
9343
9358
  }
9344
9359
  if (typeof window !== "undefined") {
9345
9360
  window.addEventListener("storage", (e) => {
9346
- if (e.key === STORAGE_KEYS.accessToken || e.key === STORAGE_KEYS.user) {
9361
+ if (e.key === STORAGE_KEYS.accessToken || e.key === STORAGE_KEYS.refreshToken || e.key === STORAGE_KEYS.user) {
9347
9362
  this.notifyListeners({
9348
9363
  type: "REFRESH",
9349
9364
  payload: {
9350
- accessToken: safeStorage.getItem(STORAGE_KEYS.accessToken) || ""
9365
+ accessToken: safeStorage.getItem(STORAGE_KEYS.accessToken) || "",
9366
+ refreshToken: safeStorage.getItem(STORAGE_KEYS.refreshToken)
9351
9367
  }
9352
9368
  });
9353
9369
  }
@@ -9401,19 +9417,63 @@ var SessionSync = class {
9401
9417
  }
9402
9418
  };
9403
9419
 
9404
- // src/config/backend.ts
9405
- var DEFAULT_API_BASE_URL = "https://api.volr.io";
9406
- function resolveApiBaseUrl(config) {
9407
- const anyConfig = config;
9408
- const devOverride = anyConfig.__devApiBaseUrl;
9409
- if (devOverride && typeof devOverride === "string") {
9410
- return devOverride.replace(/\/+$/, "");
9411
- }
9412
- const override = anyConfig.apiBaseUrl;
9413
- if (override && typeof override === "string") {
9414
- return override.replace(/\/+$/, "");
9415
- }
9416
- return DEFAULT_API_BASE_URL;
9420
+ // src/react/hooks/useSessionSync.ts
9421
+ function useSessionSync({
9422
+ client,
9423
+ config,
9424
+ setAccessTokenState,
9425
+ setRefreshTokenState,
9426
+ setUser,
9427
+ setProviderState
9428
+ }) {
9429
+ const syncRef = react.useRef(null);
9430
+ react.useEffect(() => {
9431
+ syncRef.current = new SessionSync();
9432
+ const unsubscribe = syncRef.current.subscribe((event) => {
9433
+ if (event.type === "LOGIN") {
9434
+ client.setAccessToken(event.payload.accessToken);
9435
+ setAccessTokenState(event.payload.accessToken);
9436
+ client.setRefreshToken(event.payload.refreshToken);
9437
+ setRefreshTokenState(event.payload.refreshToken);
9438
+ setUser(event.payload.user);
9439
+ safeStorage.setItem(
9440
+ STORAGE_KEYS.user,
9441
+ JSON.stringify(event.payload.user)
9442
+ );
9443
+ client.setApiKey(config.projectApiKey);
9444
+ } else if (event.type === "LOGOUT") {
9445
+ client.setAccessToken(null);
9446
+ client.setRefreshToken(null);
9447
+ setAccessTokenState(null);
9448
+ setRefreshTokenState(null);
9449
+ setUser(null);
9450
+ setProviderState(null);
9451
+ safeStorage.removeItem(STORAGE_KEYS.user);
9452
+ safeStorage.removeItem(STORAGE_KEYS.provider);
9453
+ } else if (event.type === "REFRESH") {
9454
+ client.setAccessToken(event.payload.accessToken);
9455
+ setAccessTokenState(event.payload.accessToken);
9456
+ if (event.payload.refreshToken) {
9457
+ client.setRefreshToken(event.payload.refreshToken);
9458
+ setRefreshTokenState(event.payload.refreshToken);
9459
+ }
9460
+ } else if (event.type === "PROVIDER_SET") {
9461
+ setUser(
9462
+ (prev) => prev ? {
9463
+ ...prev,
9464
+ keyStorageType: event.payload.keyStorageType,
9465
+ address: event.payload.address
9466
+ } : null
9467
+ );
9468
+ safeStorage.setItem(STORAGE_KEYS.provider, event.payload.keyStorageType);
9469
+ }
9470
+ });
9471
+ return () => {
9472
+ unsubscribe();
9473
+ syncRef.current?.destroy();
9474
+ };
9475
+ }, [client, config.projectApiKey, setAccessTokenState, setRefreshTokenState, setUser, setProviderState]);
9476
+ return syncRef;
9417
9477
  }
9418
9478
 
9419
9479
  // src/config/webauthn.ts
@@ -9676,6 +9736,120 @@ async function restorePasskey(params) {
9676
9736
  provider
9677
9737
  };
9678
9738
  }
9739
+
9740
+ // src/react/hooks/useAutoRecover.ts
9741
+ function useAutoRecover({
9742
+ client,
9743
+ config,
9744
+ setAccessTokenState,
9745
+ setRefreshTokenState,
9746
+ setUser,
9747
+ setProviderState,
9748
+ setProvider,
9749
+ setIsLoading
9750
+ }) {
9751
+ const REQUIRE_USER_GESTURE_TO_RESTORE = true;
9752
+ const hasRecoveredRef = react.useRef(false);
9753
+ const restorationAttemptedRef = react.useRef(false);
9754
+ react.useEffect(() => {
9755
+ if (config.autoRecoverOnLogin !== false && !hasRecoveredRef.current) {
9756
+ hasRecoveredRef.current = true;
9757
+ const recover = async () => {
9758
+ try {
9759
+ setIsLoading(true);
9760
+ if (!client.getRefreshToken()) {
9761
+ console.log("[Provider] No refresh token found, skipping auto-recover");
9762
+ setIsLoading(false);
9763
+ return;
9764
+ }
9765
+ const refreshedUser = await client.refreshSession();
9766
+ console.log("[Provider] Session refreshed, user:", refreshedUser);
9767
+ setAccessTokenState(client.getAccessToken());
9768
+ setRefreshTokenState(client.getRefreshToken());
9769
+ if (refreshedUser) {
9770
+ setUser(refreshedUser);
9771
+ if (!REQUIRE_USER_GESTURE_TO_RESTORE) ; else if (refreshedUser.keyStorageType === "passkey") {
9772
+ console.log("[Provider] TTL=0 mode: Provider restoration deferred");
9773
+ if (!refreshedUser.blobUrl || !refreshedUser.prfInput) {
9774
+ console.warn("[Provider] Passkey user missing blobUrl or prfInput");
9775
+ }
9776
+ }
9777
+ } else {
9778
+ const userStr = safeStorage.getItem(STORAGE_KEYS.user);
9779
+ if (userStr) {
9780
+ try {
9781
+ setUser(JSON.parse(userStr));
9782
+ } catch {
9783
+ safeStorage.removeItem(STORAGE_KEYS.user);
9784
+ }
9785
+ }
9786
+ }
9787
+ } catch {
9788
+ client.setAccessToken(null);
9789
+ client.setRefreshToken(null);
9790
+ setAccessTokenState(null);
9791
+ setRefreshTokenState(null);
9792
+ setUser(null);
9793
+ setProviderState(null);
9794
+ safeStorage.removeItem(STORAGE_KEYS.user);
9795
+ safeStorage.removeItem(STORAGE_KEYS.provider);
9796
+ } finally {
9797
+ setIsLoading(false);
9798
+ }
9799
+ };
9800
+ recover();
9801
+ } else {
9802
+ setIsLoading(false);
9803
+ }
9804
+ }, [client, config.autoRecoverOnLogin, setAccessTokenState, setRefreshTokenState, setUser, setProviderState, setProvider, setIsLoading]);
9805
+ }
9806
+ function useWalletEvents({
9807
+ user,
9808
+ logout,
9809
+ setUser,
9810
+ setProviderState
9811
+ }) {
9812
+ react.useEffect(() => {
9813
+ if (!user) return;
9814
+ if (typeof window === "undefined" || !window.ethereum) {
9815
+ return;
9816
+ }
9817
+ const ethereum = window.ethereum;
9818
+ const handleAccountsChanged = (accounts) => {
9819
+ console.log("[Provider] accountsChanged event:", accounts);
9820
+ if (accounts.length === 0) {
9821
+ console.warn("[Provider] Wallet disconnected, logging out...");
9822
+ setTimeout(() => logout(), 3e3);
9823
+ } else if (user.evmAddress && accounts[0].toLowerCase() !== user.evmAddress.toLowerCase()) {
9824
+ console.warn("[Provider] Account changed, logging out in 3 seconds...");
9825
+ setTimeout(() => logout(), 3e3);
9826
+ }
9827
+ };
9828
+ const handleChainChanged = (chainIdHex) => {
9829
+ const newChainId = parseInt(chainIdHex, 16);
9830
+ console.log("[Provider] chainChanged event:", newChainId);
9831
+ setUser(
9832
+ (prev) => prev ? { ...prev, lastWalletChainId: newChainId } : null
9833
+ );
9834
+ window.location.reload();
9835
+ };
9836
+ const handleDisconnect = () => {
9837
+ console.log("[Provider] disconnect event");
9838
+ setProviderState(null);
9839
+ safeStorage.removeItem(STORAGE_KEYS.provider);
9840
+ };
9841
+ ethereum.on("accountsChanged", handleAccountsChanged);
9842
+ ethereum.on("chainChanged", handleChainChanged);
9843
+ ethereum.on("disconnect", handleDisconnect);
9844
+ return () => {
9845
+ ethereum.removeListener("accountsChanged", handleAccountsChanged);
9846
+ ethereum.removeListener("chainChanged", handleChainChanged);
9847
+ ethereum.removeListener("disconnect", handleDisconnect);
9848
+ };
9849
+ }, [user, logout, setUser, setProviderState]);
9850
+ }
9851
+
9852
+ // src/utils/json.ts
9679
9853
  function serializeBigIntDeep(obj) {
9680
9854
  if (obj === null || obj === void 0) {
9681
9855
  return obj;
@@ -9697,8 +9871,62 @@ function serializeBigIntDeep(obj) {
9697
9871
  }
9698
9872
  return obj;
9699
9873
  }
9874
+
9875
+ // src/react/hooks/useVolrActions.ts
9876
+ function useVolrActions({
9877
+ client,
9878
+ setError
9879
+ }) {
9880
+ const precheck = react.useCallback(
9881
+ async (input) => {
9882
+ try {
9883
+ setError(null);
9884
+ await client.ensureAccessToken();
9885
+ const response = await client.post(
9886
+ "/wallet/precheck",
9887
+ input
9888
+ );
9889
+ return response.quote;
9890
+ } catch (err) {
9891
+ const error = err instanceof Error ? err : new Error("Precheck failed");
9892
+ const diag = err?.response?.data?.error?.developerDiagnostics;
9893
+ if (diag) {
9894
+ console.error("[volr][precheck] developerDiagnostics:", diag);
9895
+ }
9896
+ setError(error);
9897
+ throw error;
9898
+ }
9899
+ },
9900
+ [client, setError]
9901
+ );
9902
+ const relay = react.useCallback(
9903
+ async (input, opts) => {
9904
+ try {
9905
+ setError(null);
9906
+ await client.ensureAccessToken();
9907
+ const idempotencyKey = opts?.idempotencyKey ?? (typeof crypto !== "undefined" && crypto.randomUUID ? crypto.randomUUID() : `${Date.now()}-${Math.random()}`);
9908
+ const serializedInput = serializeBigIntDeep(input);
9909
+ const response = await client.post(
9910
+ "/wallet/relay",
9911
+ serializedInput,
9912
+ idempotencyKey
9913
+ );
9914
+ return response;
9915
+ } catch (err) {
9916
+ const error = err instanceof Error ? err : new Error("Relay failed");
9917
+ const diag = err?.response?.data?.error?.developerDiagnostics;
9918
+ if (diag) {
9919
+ console.error("[volr][relay] developerDiagnostics:", diag);
9920
+ }
9921
+ setError(error);
9922
+ throw error;
9923
+ }
9924
+ },
9925
+ [client, setError]
9926
+ );
9927
+ return { precheck, relay };
9928
+ }
9700
9929
  function VolrProvider({ config, children }) {
9701
- const REQUIRE_USER_GESTURE_TO_RESTORE = true;
9702
9930
  const providerCountRef = react.useRef(0);
9703
9931
  react.useEffect(() => {
9704
9932
  providerCountRef.current++;
@@ -9747,45 +9975,14 @@ function VolrProvider({ config, children }) {
9747
9975
  const [refreshToken, setRefreshTokenState] = react.useState(() => {
9748
9976
  return client.getRefreshToken();
9749
9977
  });
9750
- const syncRef = react.useRef(null);
9751
- react.useEffect(() => {
9752
- syncRef.current = new SessionSync();
9753
- const unsubscribe = syncRef.current.subscribe((event) => {
9754
- if (event.type === "LOGIN") {
9755
- client.setAccessToken(event.payload.accessToken);
9756
- setAccessTokenState(event.payload.accessToken);
9757
- setUser(event.payload.user);
9758
- safeStorage.setItem(
9759
- STORAGE_KEYS.user,
9760
- JSON.stringify(event.payload.user)
9761
- );
9762
- client.setApiKey(config.projectApiKey);
9763
- } else if (event.type === "LOGOUT") {
9764
- client.setAccessToken(null);
9765
- client.setRefreshToken(null);
9766
- setAccessTokenState(null);
9767
- setRefreshTokenState(null);
9768
- setUser(null);
9769
- setProviderState(null);
9770
- safeStorage.removeItem(STORAGE_KEYS.user);
9771
- safeStorage.removeItem(STORAGE_KEYS.provider);
9772
- } else if (event.type === "REFRESH") {
9773
- client.setAccessToken(event.payload.accessToken);
9774
- setAccessTokenState(event.payload.accessToken);
9775
- } else if (event.type === "PROVIDER_SET") {
9776
- setUser((prev) => ({
9777
- ...prev,
9778
- keyStorageType: event.payload.keyStorageType,
9779
- address: event.payload.address
9780
- }));
9781
- safeStorage.setItem(STORAGE_KEYS.provider, event.payload.keyStorageType);
9782
- }
9783
- });
9784
- return () => {
9785
- unsubscribe();
9786
- syncRef.current?.destroy();
9787
- };
9788
- }, [client, config.projectApiKey]);
9978
+ const syncRef = useSessionSync({
9979
+ client,
9980
+ config,
9981
+ setAccessTokenState,
9982
+ setRefreshTokenState,
9983
+ setUser,
9984
+ setProviderState
9985
+ });
9789
9986
  const setProvider = react.useCallback(
9790
9987
  async (newProvider) => {
9791
9988
  try {
@@ -9817,108 +10014,22 @@ function VolrProvider({ config, children }) {
9817
10014
  throw error2;
9818
10015
  }
9819
10016
  },
9820
- [client, user]
9821
- );
9822
- const hasRecoveredRef = react.useRef(false);
9823
- const restorationAttemptedRef = react.useRef(false);
9824
- react.useEffect(() => {
9825
- if (config.autoRecoverOnLogin !== false && !hasRecoveredRef.current) {
9826
- hasRecoveredRef.current = true;
9827
- const recover = async () => {
9828
- try {
9829
- setIsLoading(true);
9830
- if (!client.getRefreshToken()) {
9831
- console.log("[Provider] No refresh token found, skipping auto-recover");
9832
- setIsLoading(false);
9833
- return;
9834
- }
9835
- const refreshedUser = await client.refreshSession();
9836
- console.log("[Provider] Session refreshed, user:", refreshedUser);
9837
- setAccessTokenState(client.getAccessToken());
9838
- setRefreshTokenState(client.getRefreshToken());
9839
- if (refreshedUser) {
9840
- setUser(refreshedUser);
9841
- if (!REQUIRE_USER_GESTURE_TO_RESTORE) ; else if (refreshedUser.keyStorageType === "passkey") {
9842
- console.log("[Provider] TTL=0 mode: Provider restoration deferred");
9843
- if (!refreshedUser.blobUrl || !refreshedUser.prfInput) {
9844
- console.warn("[Provider] Passkey user missing blobUrl or prfInput");
9845
- }
9846
- }
9847
- } else {
9848
- const userStr = safeStorage.getItem(STORAGE_KEYS.user);
9849
- if (userStr) {
9850
- try {
9851
- setUser(JSON.parse(userStr));
9852
- } catch {
9853
- safeStorage.removeItem(STORAGE_KEYS.user);
9854
- }
9855
- }
9856
- }
9857
- } catch {
9858
- client.setAccessToken(null);
9859
- client.setRefreshToken(null);
9860
- setAccessTokenState(null);
9861
- setRefreshTokenState(null);
9862
- setUser(null);
9863
- setProviderState(null);
9864
- safeStorage.removeItem(STORAGE_KEYS.user);
9865
- safeStorage.removeItem(STORAGE_KEYS.provider);
9866
- } finally {
9867
- setIsLoading(false);
9868
- }
9869
- };
9870
- recover();
9871
- } else {
9872
- setIsLoading(false);
9873
- }
9874
- }, [client, config.autoRecoverOnLogin]);
9875
- const precheck = react.useCallback(
9876
- async (input) => {
9877
- try {
9878
- setError(null);
9879
- await client.ensureAccessToken();
9880
- const response = await client.post(
9881
- "/wallet/precheck",
9882
- input
9883
- );
9884
- return response.quote;
9885
- } catch (err) {
9886
- const error2 = err instanceof Error ? err : new Error("Precheck failed");
9887
- const diag = err?.response?.data?.error?.developerDiagnostics;
9888
- if (diag) {
9889
- console.error("[volr][precheck] developerDiagnostics:", diag);
9890
- }
9891
- setError(error2);
9892
- throw error2;
9893
- }
9894
- },
9895
- [client]
9896
- );
9897
- const relay = react.useCallback(
9898
- async (input, opts) => {
9899
- try {
9900
- setError(null);
9901
- await client.ensureAccessToken();
9902
- const idempotencyKey = opts?.idempotencyKey ?? (typeof crypto !== "undefined" && crypto.randomUUID ? crypto.randomUUID() : `${Date.now()}-${Math.random()}`);
9903
- const serializedInput = serializeBigIntDeep(input);
9904
- const response = await client.post(
9905
- "/wallet/relay",
9906
- serializedInput,
9907
- idempotencyKey
9908
- );
9909
- return response;
9910
- } catch (err) {
9911
- const error2 = err instanceof Error ? err : new Error("Relay failed");
9912
- const diag = err?.response?.data?.error?.developerDiagnostics;
9913
- if (diag) {
9914
- console.error("[volr][relay] developerDiagnostics:", diag);
9915
- }
9916
- setError(error2);
9917
- throw error2;
9918
- }
9919
- },
9920
- [client]
10017
+ [client, user, syncRef]
9921
10018
  );
10019
+ useAutoRecover({
10020
+ client,
10021
+ config,
10022
+ setAccessTokenState,
10023
+ setRefreshTokenState,
10024
+ setUser,
10025
+ setProviderState,
10026
+ setProvider,
10027
+ setIsLoading
10028
+ });
10029
+ const { precheck, relay } = useVolrActions({
10030
+ client,
10031
+ setError
10032
+ });
9922
10033
  const logout = react.useCallback(async () => {
9923
10034
  try {
9924
10035
  setError(null);
@@ -9938,45 +10049,13 @@ function VolrProvider({ config, children }) {
9938
10049
  setError(error2);
9939
10050
  throw error2;
9940
10051
  }
9941
- }, [client]);
9942
- react.useEffect(() => {
9943
- if (!user) return;
9944
- if (typeof window === "undefined" || !window.ethereum) {
9945
- return;
9946
- }
9947
- const ethereum = window.ethereum;
9948
- const handleAccountsChanged = (accounts) => {
9949
- console.log("[Provider] accountsChanged event:", accounts);
9950
- if (accounts.length === 0) {
9951
- console.warn("[Provider] Wallet disconnected, logging out...");
9952
- setTimeout(() => logout(), 3e3);
9953
- } else if (user.evmAddress && accounts[0].toLowerCase() !== user.evmAddress.toLowerCase()) {
9954
- console.warn("[Provider] Account changed, logging out in 3 seconds...");
9955
- setTimeout(() => logout(), 3e3);
9956
- }
9957
- };
9958
- const handleChainChanged = (chainIdHex) => {
9959
- const newChainId = parseInt(chainIdHex, 16);
9960
- console.log("[Provider] chainChanged event:", newChainId);
9961
- setUser(
9962
- (prev) => prev ? { ...prev, lastWalletChainId: newChainId } : null
9963
- );
9964
- window.location.reload();
9965
- };
9966
- const handleDisconnect = () => {
9967
- console.log("[Provider] disconnect event");
9968
- setProviderState(null);
9969
- safeStorage.removeItem(STORAGE_KEYS.provider);
9970
- };
9971
- ethereum.on("accountsChanged", handleAccountsChanged);
9972
- ethereum.on("chainChanged", handleChainChanged);
9973
- ethereum.on("disconnect", handleDisconnect);
9974
- return () => {
9975
- ethereum.removeListener("accountsChanged", handleAccountsChanged);
9976
- ethereum.removeListener("chainChanged", handleChainChanged);
9977
- ethereum.removeListener("disconnect", handleDisconnect);
9978
- };
9979
- }, [user, logout, setUser]);
10052
+ }, [client, syncRef]);
10053
+ useWalletEvents({
10054
+ user,
10055
+ logout,
10056
+ setUser,
10057
+ setProviderState
10058
+ });
9980
10059
  const publicValue = react.useMemo(
9981
10060
  () => ({
9982
10061
  config,
@@ -17866,6 +17945,8 @@ function http(url, config = {}) {
17866
17945
  // ../node_modules/viem/_esm/index.js
17867
17946
  init_encodeFunctionData();
17868
17947
  init_getAddress();
17948
+ init_formatEther();
17949
+ init_formatUnits();
17869
17950
 
17870
17951
  // src/utils/normalize.ts
17871
17952
  function normalizeHex(value) {
@@ -18023,112 +18104,6 @@ function normalizeCall(call2) {
18023
18104
  function normalizeCalls(calls) {
18024
18105
  return calls.map(normalizeCall);
18025
18106
  }
18026
-
18027
- // src/wallet/preflight.ts
18028
- function extractErrorMessage(err) {
18029
- if (err && typeof err === "object") {
18030
- const e = err;
18031
- const parts = [];
18032
- if (typeof e.shortMessage === "string") parts.push(e.shortMessage);
18033
- if (typeof e.message === "string") parts.push(e.message);
18034
- if (typeof e.cause?.message === "string") parts.push(e.cause.message);
18035
- if (typeof e.details === "string") parts.push(e.details);
18036
- const msg = parts.filter(Boolean).join(" | ");
18037
- if (msg) return msg;
18038
- }
18039
- try {
18040
- return String(err);
18041
- } catch {
18042
- return "Unknown error";
18043
- }
18044
- }
18045
- function isIgnorableFundingError(err, message) {
18046
- const m = message.toLowerCase();
18047
- const e = err;
18048
- const name = typeof e?.name === "string" ? e.name.toLowerCase() : "";
18049
- if (name === "insufficientfundserror" || name.includes("insufficientfunds")) {
18050
- return true;
18051
- }
18052
- if (typeof e?.code === "string") {
18053
- const code = e.code.toLowerCase();
18054
- if (code.includes("execution_reverted") || code.includes("call_exception")) {
18055
- return false;
18056
- }
18057
- if (code.includes("insufficient_funds")) {
18058
- return true;
18059
- }
18060
- }
18061
- const fundingPatterns = [
18062
- "insufficient funds for gas * price",
18063
- "insufficient funds for gas",
18064
- "for gas * price + value",
18065
- "not enough funds for l1 fee",
18066
- "insufficient funds for l1 fee",
18067
- "insufficient balance for gas",
18068
- "account balance too low"
18069
- ];
18070
- const hasFundingPattern = fundingPatterns.some((pattern) => m.includes(pattern));
18071
- const hasRevertIndicator = m.includes("revert") || m.includes("execution reverted") || m.includes("call exception") || m.includes("vm execution error");
18072
- return hasFundingPattern && !hasRevertIndicator;
18073
- }
18074
- async function preflightEstimate(publicClient, from14, calls, opts) {
18075
- for (let i = 0; i < calls.length; i++) {
18076
- const c = calls[i];
18077
- try {
18078
- await publicClient.estimateGas({
18079
- account: from14,
18080
- to: c.target,
18081
- data: c.data,
18082
- value: c.value ?? 0n
18083
- });
18084
- } catch (e) {
18085
- const message = extractErrorMessage(e);
18086
- const tolerateFunding = opts?.tolerateFundingErrors !== false;
18087
- const isFundingError = isIgnorableFundingError(e, message);
18088
- if (tolerateFunding && isFundingError) {
18089
- try {
18090
- await publicClient.call({
18091
- account: from14,
18092
- to: c.target,
18093
- data: c.data,
18094
- value: c.value ?? 0n
18095
- });
18096
- console.log(
18097
- `[preflightEstimate] Ignoring funding error for call #${i} (target ${c.target}): ${message}`
18098
- );
18099
- continue;
18100
- } catch (callError) {
18101
- const callMessage = extractErrorMessage(callError);
18102
- console.error(
18103
- `[preflightEstimate] Static call after funding error also failed for call #${i} (target ${c.target}):`,
18104
- {
18105
- originalError: { message, errorName: e?.name, errorCode: e?.code },
18106
- callError: {
18107
- message: callMessage,
18108
- name: callError?.name,
18109
- code: callError?.code
18110
- }
18111
- }
18112
- );
18113
- throw new Error(
18114
- `Preflight failed (call #${i} target ${c.target}): ${callMessage}`
18115
- );
18116
- }
18117
- }
18118
- console.error(
18119
- `[preflightEstimate] Preflight failed for call #${i} (target ${c.target}):`,
18120
- {
18121
- message,
18122
- errorName: e?.name,
18123
- errorCode: e?.code,
18124
- isFundingError,
18125
- tolerateFunding
18126
- }
18127
- );
18128
- throw new Error(`Preflight failed (call #${i} target ${c.target}): ${message}`);
18129
- }
18130
- }
18131
- }
18132
18107
  async function resolveSigner(input) {
18133
18108
  const { explicitSigner, provider, chainId, client, user, setProvider } = input;
18134
18109
  if (explicitSigner) {
@@ -18225,6 +18200,40 @@ function finalizeAuthWithNonce(tempAuth, quote) {
18225
18200
  }
18226
18201
 
18227
18202
  // src/utils/tx-polling.ts
18203
+ function extractFailureReason(response) {
18204
+ const reasons = [];
18205
+ const meta = response.meta;
18206
+ if (meta) {
18207
+ if (typeof meta.revertReason === "string" && meta.revertReason) {
18208
+ reasons.push(meta.revertReason);
18209
+ }
18210
+ if (typeof meta.failureReason === "string" && meta.failureReason) {
18211
+ reasons.push(meta.failureReason);
18212
+ }
18213
+ const diag = meta.developerDiagnostics;
18214
+ if (diag) {
18215
+ if (typeof diag.revertReason === "string" && diag.revertReason) {
18216
+ reasons.push(diag.revertReason);
18217
+ }
18218
+ if (typeof diag.error === "string" && diag.error) {
18219
+ reasons.push(diag.error);
18220
+ }
18221
+ const simErrors = diag.simulationErrors;
18222
+ if (Array.isArray(simErrors)) {
18223
+ for (const err of simErrors) {
18224
+ if (typeof err.error === "string") {
18225
+ reasons.push(`Simulation error: ${err.error}`);
18226
+ }
18227
+ }
18228
+ }
18229
+ }
18230
+ }
18231
+ const uniqueReasons = [...new Set(reasons)];
18232
+ if (uniqueReasons.length > 0) {
18233
+ return uniqueReasons.join("; ");
18234
+ }
18235
+ return "Transaction failed on-chain (no specific reason provided)";
18236
+ }
18228
18237
  async function pollTransactionStatus(txId, client, maxAttempts = 60, intervalMs = 5e3) {
18229
18238
  for (let attempt = 0; attempt < maxAttempts; attempt++) {
18230
18239
  try {
@@ -18241,15 +18250,17 @@ async function pollTransactionStatus(txId, client, maxAttempts = 60, intervalMs
18241
18250
  };
18242
18251
  }
18243
18252
  if (status === "FAILED") {
18244
- console.error(`[pollTransactionStatus] Transaction ${txId} failed`);
18245
- const diag = response.meta?.developerDiagnostics;
18246
- if (diag) {
18247
- console.error("[volr][relay] developerDiagnostics:", diag);
18253
+ const failureReason = extractFailureReason(response);
18254
+ console.error(`[pollTransactionStatus] Transaction ${txId} failed: ${failureReason}`);
18255
+ const meta = response.meta;
18256
+ if (meta) {
18257
+ console.error("[pollTransactionStatus] Full meta:", JSON.stringify(meta, null, 2));
18248
18258
  }
18249
18259
  return {
18250
18260
  txId,
18251
18261
  status: "FAILED",
18252
- txHash
18262
+ txHash,
18263
+ reason: failureReason
18253
18264
  };
18254
18265
  }
18255
18266
  console.log(`[pollTransactionStatus] Transaction ${txId} is ${status}, attempt ${attempt + 1}/${maxAttempts}`);
@@ -18299,19 +18310,6 @@ async function sendCalls(args) {
18299
18310
  keyStorageType: deps.user?.keyStorageType
18300
18311
  });
18301
18312
  }
18302
- if (opts.preflight !== false) {
18303
- console.log("[sendCalls] Running preflight estimate...");
18304
- const tolerateFundingErrors = (opts.mode ?? "sponsored") !== "self";
18305
- await preflightEstimate(
18306
- deps.publicClient,
18307
- normalizedFrom,
18308
- normalizedCalls,
18309
- { tolerateFundingErrors }
18310
- );
18311
- console.log("[sendCalls] Preflight estimate done");
18312
- } else {
18313
- console.log("[sendCalls] Preflight skipped");
18314
- }
18315
18313
  console.log("[sendCalls] Building temp auth for first precheck...");
18316
18314
  const tempAuthForPrecheck = buildTempAuth({
18317
18315
  chainId,
@@ -18327,11 +18325,34 @@ async function sendCalls(args) {
18327
18325
  auth: tempAuthForPrecheck,
18328
18326
  calls: normalizedCalls
18329
18327
  });
18330
- console.log("[sendCalls] First precheck done, policyId:", precheckQuote.policyId);
18328
+ console.log("[sendCalls] First precheck done:", {
18329
+ policyId: precheckQuote.policyId,
18330
+ simulationSuccess: precheckQuote.simulationSuccess,
18331
+ estimatedGasPerCall: precheckQuote.estimatedGasPerCall
18332
+ });
18331
18333
  } catch (err) {
18332
18334
  console.error("[sendCalls] First precheck failed:", err);
18333
18335
  throw err instanceof Error ? err : new Error(String(err));
18334
18336
  }
18337
+ if (precheckQuote.simulationSuccess === false && precheckQuote.simulationErrors?.length) {
18338
+ const errors = precheckQuote.simulationErrors;
18339
+ console.error("[sendCalls] Backend simulation failed:", errors);
18340
+ const errorMessages = errors.map(
18341
+ (e) => `Call #${e.index}: ${e.error}${e.revertData ? ` (data: ${e.revertData})` : ""}`
18342
+ ).join("; ");
18343
+ throw new Error(`Transaction simulation failed: ${errorMessages}`);
18344
+ }
18345
+ if (precheckQuote.estimatedGasPerCall?.length) {
18346
+ console.log("[sendCalls] Applying estimated gas limits from backend...");
18347
+ for (let i = 0; i < normalizedCalls.length && i < precheckQuote.estimatedGasPerCall.length; i++) {
18348
+ const estimatedGas = BigInt(precheckQuote.estimatedGasPerCall[i] ?? "0");
18349
+ const currentGas = normalizedCalls[i]?.gasLimit ?? 0n;
18350
+ if (estimatedGas > currentGas) {
18351
+ console.log(`[sendCalls] Call #${i}: updating gasLimit from ${currentGas} to ${estimatedGas}`);
18352
+ normalizedCalls[i].gasLimit = estimatedGas;
18353
+ }
18354
+ }
18355
+ }
18335
18356
  const quotePolicyId = precheckQuote.policyId;
18336
18357
  if (!quotePolicyId) {
18337
18358
  throw new Error("Backend did not return policyId in precheck response");
@@ -18344,6 +18365,7 @@ async function sendCalls(args) {
18344
18365
  from: normalizedFrom,
18345
18366
  policyId: projectPolicyId,
18346
18367
  calls: normalizedCalls,
18368
+ // Now with updated gas limits
18347
18369
  expiresInSec: opts.expiresInSec ?? DEFAULT_EXPIRES_IN_SEC
18348
18370
  });
18349
18371
  let quote;
@@ -18562,6 +18584,10 @@ function useVolrLogin() {
18562
18584
  setAccessToken(accessToken);
18563
18585
  if (refreshToken) {
18564
18586
  setRefreshToken(refreshToken);
18587
+ } else {
18588
+ console.warn(
18589
+ "[useVolrLogin] Access token received but refresh token is missing. Check if backend is updated to return refresh token in body."
18590
+ );
18565
18591
  }
18566
18592
  if (userFromServer) {
18567
18593
  setUser(toVolrUser(userFromServer));
@@ -19307,6 +19333,219 @@ async function uploadBlobViaPresign(params) {
19307
19333
  }
19308
19334
  return { s3Key };
19309
19335
  }
19336
+
19337
+ // src/utils/contract-analysis.ts
19338
+ var EIP7702_PREFIX = "0xef0100";
19339
+ async function isEIP7702Delegated(publicClient, address) {
19340
+ try {
19341
+ const code = await publicClient.getCode({ address });
19342
+ if (!code || code === "0x" || code.length < 8) {
19343
+ return false;
19344
+ }
19345
+ return code.toLowerCase().startsWith(EIP7702_PREFIX);
19346
+ } catch {
19347
+ return false;
19348
+ }
19349
+ }
19350
+ async function analyzeContractForEIP7702(publicClient, target, calldata) {
19351
+ const notes = [];
19352
+ let isContract = false;
19353
+ let codeSize = 0;
19354
+ let mayCheckSenderCode = false;
19355
+ let mayUseDelegationGuard = false;
19356
+ try {
19357
+ const code = await publicClient.getCode({ address: target });
19358
+ isContract = !!code && code !== "0x";
19359
+ codeSize = code ? (code.length - 2) / 2 : 0;
19360
+ if (!isContract || !code) {
19361
+ notes.push("Target is not a contract (EOA or empty address)");
19362
+ } else {
19363
+ const codeLower = code.toLowerCase();
19364
+ if (codeLower.includes("3b")) {
19365
+ mayCheckSenderCode = true;
19366
+ notes.push("Contract contains EXTCODESIZE opcode - may check msg.sender.code.length");
19367
+ }
19368
+ if (codeLower.includes("ef0100")) {
19369
+ mayUseDelegationGuard = true;
19370
+ notes.push("Contract contains EIP-7702 prefix bytes - may use DelegationGuard pattern");
19371
+ }
19372
+ }
19373
+ } catch (error) {
19374
+ notes.push(`Failed to fetch contract code: ${error instanceof Error ? error.message : "Unknown error"}`);
19375
+ }
19376
+ const functionSelector = calldata && calldata.length >= 10 ? calldata.slice(0, 10) : "0x";
19377
+ return {
19378
+ target,
19379
+ isContract,
19380
+ codeSize,
19381
+ mayCheckSenderCode,
19382
+ mayUseDelegationGuard,
19383
+ functionSelector,
19384
+ notes
19385
+ };
19386
+ }
19387
+ async function diagnoseTransactionFailure(publicClient, from14, target, calldata) {
19388
+ const recommendations = [];
19389
+ const fromCode = await publicClient.getCode({ address: from14 });
19390
+ const fromHasCode = !!fromCode && fromCode !== "0x";
19391
+ const fromIsDelegated = await isEIP7702Delegated(publicClient, from14);
19392
+ const targetAnalysis = await analyzeContractForEIP7702(publicClient, target, calldata);
19393
+ if (!targetAnalysis.isContract) {
19394
+ recommendations.push("Target is not a contract. Verify the target address is correct.");
19395
+ }
19396
+ if (targetAnalysis.mayCheckSenderCode) {
19397
+ recommendations.push(
19398
+ "Target contract may check msg.sender.code.length. This can cause issues because in EIP-7702, the sender EOA temporarily has code."
19399
+ );
19400
+ }
19401
+ if (targetAnalysis.mayUseDelegationGuard) {
19402
+ recommendations.push(
19403
+ "Target contract may use DelegationGuard pattern to block EIP-7702 delegated accounts. This is a security measure some contracts use. You may need to contact the contract developers or use a different approach."
19404
+ );
19405
+ }
19406
+ if (!fromIsDelegated && fromHasCode) {
19407
+ recommendations.push(
19408
+ "Sender address has code but is not EIP-7702 delegated. This may indicate the address is a contract, not an EOA."
19409
+ );
19410
+ }
19411
+ if (recommendations.length === 0) {
19412
+ recommendations.push(
19413
+ "No obvious EIP-7702 compatibility issues detected. The failure may be due to contract logic (e.g., insufficient balance, invalid parameters). Check the contract's requirements and your transaction parameters."
19414
+ );
19415
+ }
19416
+ return {
19417
+ fromAnalysis: {
19418
+ isDelegated: fromIsDelegated,
19419
+ hasCode: fromHasCode
19420
+ },
19421
+ targetAnalysis,
19422
+ recommendations
19423
+ };
19424
+ }
19425
+
19426
+ // src/utils/wallet-debug.ts
19427
+ async function getWalletState(publicClient, address) {
19428
+ const [balance, code, nonce] = await Promise.all([
19429
+ publicClient.getBalance({ address }),
19430
+ publicClient.getCode({ address }),
19431
+ publicClient.getTransactionCount({ address })
19432
+ ]);
19433
+ const hasCode = !!code && code !== "0x";
19434
+ const codeLength = code ? (code.length - 2) / 2 : 0;
19435
+ const isEIP7702Delegated2 = hasCode && code.toLowerCase().startsWith("0xef0100");
19436
+ return {
19437
+ address,
19438
+ ethBalance: formatEther(balance),
19439
+ ethBalanceWei: balance,
19440
+ hasCode,
19441
+ codeLength,
19442
+ nonce,
19443
+ isEIP7702Delegated: isEIP7702Delegated2
19444
+ };
19445
+ }
19446
+ async function getERC20Balance(publicClient, walletAddress, tokenAddress, decimals = 18) {
19447
+ const balance = await publicClient.readContract({
19448
+ address: tokenAddress,
19449
+ abi: [
19450
+ {
19451
+ name: "balanceOf",
19452
+ type: "function",
19453
+ stateMutability: "view",
19454
+ inputs: [{ name: "account", type: "address" }],
19455
+ outputs: [{ type: "uint256" }]
19456
+ }
19457
+ ],
19458
+ functionName: "balanceOf",
19459
+ args: [walletAddress]
19460
+ });
19461
+ return {
19462
+ address: walletAddress,
19463
+ tokenAddress,
19464
+ balance: formatUnits(balance, decimals),
19465
+ balanceRaw: balance,
19466
+ decimals
19467
+ };
19468
+ }
19469
+ async function compareWalletStates(publicClient, address1, address2) {
19470
+ const [wallet1, wallet2] = await Promise.all([
19471
+ getWalletState(publicClient, address1),
19472
+ getWalletState(publicClient, address2)
19473
+ ]);
19474
+ const differences = [];
19475
+ if (wallet1.hasCode !== wallet2.hasCode) {
19476
+ differences.push(
19477
+ `Code presence differs: ${address1} has ${wallet1.hasCode ? "code" : "no code"}, ${address2} has ${wallet2.hasCode ? "code" : "no code"}`
19478
+ );
19479
+ }
19480
+ if (wallet1.isEIP7702Delegated !== wallet2.isEIP7702Delegated) {
19481
+ differences.push(
19482
+ `EIP-7702 delegation differs: ${address1} is ${wallet1.isEIP7702Delegated ? "" : "not "}delegated, ${address2} is ${wallet2.isEIP7702Delegated ? "" : "not "}delegated`
19483
+ );
19484
+ }
19485
+ const balanceDiff = wallet1.ethBalanceWei - wallet2.ethBalanceWei;
19486
+ if (balanceDiff !== 0n) {
19487
+ differences.push(
19488
+ `ETH balance differs by ${formatEther(balanceDiff > 0n ? balanceDiff : -balanceDiff)} ETH`
19489
+ );
19490
+ }
19491
+ return { wallet1, wallet2, differences };
19492
+ }
19493
+ async function compareERC20Balances(publicClient, address1, address2, tokenAddress, decimals = 18) {
19494
+ const [wallet1, wallet2] = await Promise.all([
19495
+ getERC20Balance(publicClient, address1, tokenAddress, decimals),
19496
+ getERC20Balance(publicClient, address2, tokenAddress, decimals)
19497
+ ]);
19498
+ const differenceRaw = wallet1.balanceRaw - wallet2.balanceRaw;
19499
+ return {
19500
+ wallet1,
19501
+ wallet2,
19502
+ difference: formatUnits(differenceRaw > 0n ? differenceRaw : -differenceRaw, decimals),
19503
+ differenceRaw
19504
+ };
19505
+ }
19506
+ async function debugTransactionFailure(publicClient, failingWallet, workingWallet, tokenAddress, tokenDecimals = 18) {
19507
+ const analysis = [];
19508
+ const stateComparison = await compareWalletStates(
19509
+ publicClient,
19510
+ failingWallet,
19511
+ workingWallet
19512
+ );
19513
+ if (stateComparison.wallet1.isEIP7702Delegated && !stateComparison.wallet2.isEIP7702Delegated) {
19514
+ analysis.push(
19515
+ "The failing wallet has EIP-7702 delegation while the working wallet does not. This may cause issues if the target contract checks msg.sender.code.length or uses DelegationGuard."
19516
+ );
19517
+ }
19518
+ if (stateComparison.wallet1.hasCode && !stateComparison.wallet2.hasCode) {
19519
+ analysis.push(
19520
+ "The failing wallet has code while the working wallet is a pure EOA. Some contracts reject calls from addresses with code."
19521
+ );
19522
+ }
19523
+ let tokenComparison;
19524
+ if (tokenAddress) {
19525
+ tokenComparison = await compareERC20Balances(
19526
+ publicClient,
19527
+ failingWallet,
19528
+ workingWallet,
19529
+ tokenAddress,
19530
+ tokenDecimals
19531
+ );
19532
+ if (tokenComparison.wallet1.balanceRaw < tokenComparison.wallet2.balanceRaw) {
19533
+ analysis.push(
19534
+ `The failing wallet has less token balance (${tokenComparison.wallet1.balance}) than the working wallet (${tokenComparison.wallet2.balance}). Ensure sufficient balance for the transaction.`
19535
+ );
19536
+ }
19537
+ }
19538
+ if (analysis.length === 0) {
19539
+ analysis.push(
19540
+ "No obvious differences found between wallets. The issue may be related to contract-specific state (e.g., positions, allowances, whitelists) or transaction parameters."
19541
+ );
19542
+ }
19543
+ return {
19544
+ stateComparison,
19545
+ tokenComparison,
19546
+ analysis
19547
+ };
19548
+ }
19310
19549
  /*! Bundled license information:
19311
19550
 
19312
19551
  @noble/hashes/esm/utils.js:
@@ -19352,11 +19591,19 @@ Object.defineProperty(exports, "uploadBlob", {
19352
19591
  exports.DEFAULT_EXPIRES_IN_SEC = DEFAULT_EXPIRES_IN_SEC;
19353
19592
  exports.DEFAULT_MODE = DEFAULT_MODE;
19354
19593
  exports.VolrProvider = VolrProvider;
19594
+ exports.analyzeContractForEIP7702 = analyzeContractForEIP7702;
19355
19595
  exports.buildCall = buildCall;
19356
19596
  exports.buildCalls = buildCalls;
19597
+ exports.compareERC20Balances = compareERC20Balances;
19598
+ exports.compareWalletStates = compareWalletStates;
19357
19599
  exports.createGetNetworkInfo = createGetNetworkInfo;
19358
19600
  exports.createPasskeyAdapter = createPasskeyAdapter;
19601
+ exports.debugTransactionFailure = debugTransactionFailure;
19359
19602
  exports.defaultIdempotencyKey = defaultIdempotencyKey;
19603
+ exports.diagnoseTransactionFailure = diagnoseTransactionFailure;
19604
+ exports.getERC20Balance = getERC20Balance;
19605
+ exports.getWalletState = getWalletState;
19606
+ exports.isEIP7702Delegated = isEIP7702Delegated;
19360
19607
  exports.normalizeHex = normalizeHex;
19361
19608
  exports.normalizeHexArray = normalizeHexArray;
19362
19609
  exports.uploadBlobViaPresign = uploadBlobViaPresign;