@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 +567 -320
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +132 -6
- package/dist/index.d.ts +132 -6
- package/dist/index.js +560 -321
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
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/
|
|
9405
|
-
|
|
9406
|
-
|
|
9407
|
-
|
|
9408
|
-
|
|
9409
|
-
|
|
9410
|
-
|
|
9411
|
-
|
|
9412
|
-
|
|
9413
|
-
|
|
9414
|
-
|
|
9415
|
-
|
|
9416
|
-
|
|
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 =
|
|
9751
|
-
|
|
9752
|
-
|
|
9753
|
-
|
|
9754
|
-
|
|
9755
|
-
|
|
9756
|
-
|
|
9757
|
-
|
|
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
|
-
|
|
9943
|
-
|
|
9944
|
-
|
|
9945
|
-
|
|
9946
|
-
|
|
9947
|
-
|
|
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
|
-
|
|
18245
|
-
|
|
18246
|
-
|
|
18247
|
-
|
|
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
|
|
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;
|