@oasisprotocol/privana-sdk 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/index.cjs +506 -203
- package/dist/index.cjs.map +1 -1
- package/dist/index.css +1 -1
- package/dist/index.css.map +1 -1
- package/dist/index.d.cts +40 -3
- package/dist/index.d.ts +40 -3
- package/dist/index.js +507 -206
- package/dist/index.js.map +1 -1
- package/dist/powered_by_privana-YV2FNNVG.svg +4 -0
- package/dist/powered_by_privana.svg +4 -0
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -3,13 +3,13 @@
|
|
|
3
3
|
|
|
4
4
|
var react = require('react');
|
|
5
5
|
var viem = require('viem');
|
|
6
|
+
var siwe = require('viem/siwe');
|
|
7
|
+
var wagmi = require('wagmi');
|
|
8
|
+
var actions = require('wagmi/actions');
|
|
6
9
|
var jsxRuntime = require('react/jsx-runtime');
|
|
7
10
|
var reactQuery = require('@tanstack/react-query');
|
|
8
11
|
var clsx = require('clsx');
|
|
9
12
|
var tailwindMerge = require('tailwind-merge');
|
|
10
|
-
var siwe = require('viem/siwe');
|
|
11
|
-
var wagmi = require('wagmi');
|
|
12
|
-
var actions = require('wagmi/actions');
|
|
13
13
|
var actions$1 = require('viem/actions');
|
|
14
14
|
var reactSlot = require('@radix-ui/react-slot');
|
|
15
15
|
var classVarianceAuthority = require('class-variance-authority');
|
|
@@ -51,7 +51,7 @@ var config_default = {
|
|
|
51
51
|
chainId: 23295,
|
|
52
52
|
name: "Sapphire Testnet",
|
|
53
53
|
accountingContract: "0xaF8e5de153A584528B57DD4B9B0195956BBDF571",
|
|
54
|
-
apiUrl: "https://
|
|
54
|
+
apiUrl: "https://testnet.privana.finance"
|
|
55
55
|
},
|
|
56
56
|
mainnet: {
|
|
57
57
|
chainId: 23294,
|
|
@@ -791,6 +791,235 @@ async function signWithdrawFromLockMessage({
|
|
|
791
791
|
});
|
|
792
792
|
return signature;
|
|
793
793
|
}
|
|
794
|
+
var defaultResult = {
|
|
795
|
+
address: void 0,
|
|
796
|
+
isConnected: false,
|
|
797
|
+
status: "disconnected"
|
|
798
|
+
};
|
|
799
|
+
function useSafeAccount() {
|
|
800
|
+
const context = react.useContext(wagmi.WagmiContext);
|
|
801
|
+
const cacheRef = react.useRef(defaultResult);
|
|
802
|
+
const subscribe = react.useCallback(
|
|
803
|
+
(onChange) => {
|
|
804
|
+
if (!context) return () => {
|
|
805
|
+
};
|
|
806
|
+
return actions.watchAccount(context, { onChange });
|
|
807
|
+
},
|
|
808
|
+
[context]
|
|
809
|
+
);
|
|
810
|
+
const getSnapshot = react.useCallback(() => {
|
|
811
|
+
if (!context) return defaultResult;
|
|
812
|
+
const account = actions.getAccount(context);
|
|
813
|
+
if (cacheRef.current.address !== account.address || cacheRef.current.isConnected !== account.isConnected || cacheRef.current.status !== account.status) {
|
|
814
|
+
cacheRef.current = {
|
|
815
|
+
address: account.address,
|
|
816
|
+
isConnected: account.isConnected,
|
|
817
|
+
status: account.status
|
|
818
|
+
};
|
|
819
|
+
}
|
|
820
|
+
return cacheRef.current;
|
|
821
|
+
}, [context]);
|
|
822
|
+
const getServerSnapshot = react.useCallback(() => defaultResult, []);
|
|
823
|
+
return react.useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
// src/sdk/hooks/private-read-token-store.ts
|
|
827
|
+
var AUTH_CLOCK_SKEW_MS = 3e4;
|
|
828
|
+
var cache = /* @__PURE__ */ new Map();
|
|
829
|
+
function createScopeKey(apiUrl, chainId, address) {
|
|
830
|
+
return `${apiUrl.replace(/\/$/, "")}:${chainId}:${address.toLowerCase()}`;
|
|
831
|
+
}
|
|
832
|
+
function getCachedPrivateReadToken(scopeKey) {
|
|
833
|
+
const cached = cache.get(scopeKey);
|
|
834
|
+
if (!cached) return null;
|
|
835
|
+
if (cached.expiresAt <= Date.now() + AUTH_CLOCK_SKEW_MS) {
|
|
836
|
+
cache.delete(scopeKey);
|
|
837
|
+
return null;
|
|
838
|
+
}
|
|
839
|
+
return cached.token;
|
|
840
|
+
}
|
|
841
|
+
function setCachedPrivateReadToken(scopeKey, token, expiresAt) {
|
|
842
|
+
cache.set(scopeKey, { token, expiresAt });
|
|
843
|
+
}
|
|
844
|
+
function deleteCachedPrivateReadToken(scopeKey) {
|
|
845
|
+
cache.delete(scopeKey);
|
|
846
|
+
}
|
|
847
|
+
var DEFAULT_SIWE_VALIDITY_MS = 24 * 60 * 60 * 1e3;
|
|
848
|
+
var DEFAULT_STATEMENT = "Sign in to access your private account data.";
|
|
849
|
+
var AUTH_REFRESH_SKEW_MS = 3e4;
|
|
850
|
+
var SiweAuthContext = react.createContext(null);
|
|
851
|
+
function SiweAuthProvider({
|
|
852
|
+
children,
|
|
853
|
+
client,
|
|
854
|
+
networkConfig,
|
|
855
|
+
autoLogin = true,
|
|
856
|
+
statement
|
|
857
|
+
}) {
|
|
858
|
+
const wagmiContext = react.useContext(wagmi.WagmiContext);
|
|
859
|
+
const { address, isConnected, status } = useSafeAccount();
|
|
860
|
+
const [session, setSession] = react.useState(null);
|
|
861
|
+
const [tokens, setTokens] = react.useState(null);
|
|
862
|
+
const [isLoading, setIsLoading] = react.useState(false);
|
|
863
|
+
const [error, setError] = react.useState(null);
|
|
864
|
+
const [accessTokenExpiresAt, setAccessTokenExpiresAt] = react.useState(null);
|
|
865
|
+
const loginInFlight = react.useRef(false);
|
|
866
|
+
const autoAttemptedAddress = react.useRef(null);
|
|
867
|
+
const refreshInFlight = react.useRef(false);
|
|
868
|
+
const refreshDataRef = react.useRef(null);
|
|
869
|
+
const clearSession = react.useCallback(() => {
|
|
870
|
+
refreshDataRef.current = null;
|
|
871
|
+
setAccessTokenExpiresAt(null);
|
|
872
|
+
client.clearPrivateReadToken();
|
|
873
|
+
client.clearBearerToken();
|
|
874
|
+
setSession(null);
|
|
875
|
+
setTokens(null);
|
|
876
|
+
setError(null);
|
|
877
|
+
autoAttemptedAddress.current = null;
|
|
878
|
+
}, [client]);
|
|
879
|
+
const logout = react.useCallback(async () => {
|
|
880
|
+
setError(null);
|
|
881
|
+
const refreshToken = refreshDataRef.current?.refreshToken;
|
|
882
|
+
try {
|
|
883
|
+
if (refreshToken) {
|
|
884
|
+
await client.logoutJwtSession({ refresh_token: refreshToken });
|
|
885
|
+
}
|
|
886
|
+
} finally {
|
|
887
|
+
clearSession();
|
|
888
|
+
autoAttemptedAddress.current = address ?? null;
|
|
889
|
+
}
|
|
890
|
+
}, [clearSession, client, address]);
|
|
891
|
+
const login = react.useCallback(async () => {
|
|
892
|
+
if (!wagmiContext) throw new Error("WagmiProvider is required for SIWE auth");
|
|
893
|
+
if (!address) throw new Error("No wallet connected");
|
|
894
|
+
if (loginInFlight.current) return;
|
|
895
|
+
loginInFlight.current = true;
|
|
896
|
+
setIsLoading(true);
|
|
897
|
+
setError(null);
|
|
898
|
+
try {
|
|
899
|
+
const walletClient = await actions.getWalletClient(wagmiContext);
|
|
900
|
+
if (!walletClient) throw new Error("No wallet client available");
|
|
901
|
+
const [{ domain }, nonceRes] = await Promise.all([
|
|
902
|
+
client.getSiweDomain(),
|
|
903
|
+
client.getSiweNonce(address)
|
|
904
|
+
]);
|
|
905
|
+
const issuedAt = /* @__PURE__ */ new Date();
|
|
906
|
+
const expirationTime = new Date(issuedAt.getTime() + DEFAULT_SIWE_VALIDITY_MS);
|
|
907
|
+
const uri = typeof window !== "undefined" && window.location.origin ? window.location.origin : networkConfig.apiUrl;
|
|
908
|
+
const message = siwe.createSiweMessage({
|
|
909
|
+
address,
|
|
910
|
+
chainId: networkConfig.chainId,
|
|
911
|
+
domain,
|
|
912
|
+
uri,
|
|
913
|
+
version: "1",
|
|
914
|
+
nonce: nonceRes.nonce,
|
|
915
|
+
statement: statement ?? DEFAULT_STATEMENT,
|
|
916
|
+
issuedAt,
|
|
917
|
+
expirationTime
|
|
918
|
+
});
|
|
919
|
+
const signature = await walletClient.signMessage({
|
|
920
|
+
account: walletClient.account ?? address,
|
|
921
|
+
message
|
|
922
|
+
});
|
|
923
|
+
const res = await client.loginWithSiwe({ siwe_message: message, signature });
|
|
924
|
+
const loggedInAt = Date.now();
|
|
925
|
+
client.setPrivateReadToken(res.siwe_token);
|
|
926
|
+
client.setBearerToken(res.jwt_access_token);
|
|
927
|
+
refreshDataRef.current = {
|
|
928
|
+
refreshToken: res.jwt_refresh_token,
|
|
929
|
+
refreshExpiresAt: loggedInAt + res.jwt_refresh_expires_in * 1e3
|
|
930
|
+
};
|
|
931
|
+
setCachedPrivateReadToken(
|
|
932
|
+
createScopeKey(networkConfig.apiUrl, networkConfig.chainId, address),
|
|
933
|
+
res.siwe_token,
|
|
934
|
+
expirationTime.getTime()
|
|
935
|
+
);
|
|
936
|
+
setSession({ address: res.address });
|
|
937
|
+
setTokens({
|
|
938
|
+
siwe_token: res.siwe_token,
|
|
939
|
+
jwt_access_token: res.jwt_access_token,
|
|
940
|
+
jwt_refresh_token: res.jwt_refresh_token,
|
|
941
|
+
address: res.address
|
|
942
|
+
});
|
|
943
|
+
setAccessTokenExpiresAt(loggedInAt + res.jwt_expires_in * 1e3);
|
|
944
|
+
} catch (err) {
|
|
945
|
+
setError(err instanceof Error ? err : new Error("Sign-in failed"));
|
|
946
|
+
throw err;
|
|
947
|
+
} finally {
|
|
948
|
+
setIsLoading(false);
|
|
949
|
+
loginInFlight.current = false;
|
|
950
|
+
}
|
|
951
|
+
}, [wagmiContext, address, client, networkConfig.chainId, networkConfig.apiUrl, statement]);
|
|
952
|
+
const refreshAccessToken = react.useCallback(async () => {
|
|
953
|
+
const data = refreshDataRef.current;
|
|
954
|
+
if (!data || refreshInFlight.current) return;
|
|
955
|
+
if (Date.now() >= data.refreshExpiresAt - AUTH_REFRESH_SKEW_MS) {
|
|
956
|
+
clearSession();
|
|
957
|
+
return;
|
|
958
|
+
}
|
|
959
|
+
refreshInFlight.current = true;
|
|
960
|
+
try {
|
|
961
|
+
const res = await client.refreshJwtSession({ refresh_token: data.refreshToken });
|
|
962
|
+
const refreshedAt = Date.now();
|
|
963
|
+
client.setBearerToken(res.token);
|
|
964
|
+
refreshDataRef.current = {
|
|
965
|
+
refreshToken: res.refresh_token,
|
|
966
|
+
refreshExpiresAt: refreshedAt + res.refresh_expires_in * 1e3
|
|
967
|
+
};
|
|
968
|
+
setTokens(
|
|
969
|
+
(prev) => prev ? { ...prev, jwt_access_token: res.token, jwt_refresh_token: res.refresh_token } : prev
|
|
970
|
+
);
|
|
971
|
+
setAccessTokenExpiresAt(refreshedAt + res.expires_in * 1e3);
|
|
972
|
+
} catch {
|
|
973
|
+
clearSession();
|
|
974
|
+
} finally {
|
|
975
|
+
refreshInFlight.current = false;
|
|
976
|
+
}
|
|
977
|
+
}, [client, clearSession]);
|
|
978
|
+
react.useEffect(() => {
|
|
979
|
+
if (accessTokenExpiresAt == null) return;
|
|
980
|
+
const delay = Math.max(accessTokenExpiresAt - AUTH_REFRESH_SKEW_MS - Date.now(), 0);
|
|
981
|
+
const timer = setTimeout(() => {
|
|
982
|
+
void refreshAccessToken();
|
|
983
|
+
}, delay);
|
|
984
|
+
return () => clearTimeout(timer);
|
|
985
|
+
}, [accessTokenExpiresAt, refreshAccessToken]);
|
|
986
|
+
react.useEffect(() => {
|
|
987
|
+
if (!autoLogin) return;
|
|
988
|
+
if (status === "connecting" || status === "reconnecting") return;
|
|
989
|
+
if (!isConnected && session) {
|
|
990
|
+
clearSession();
|
|
991
|
+
return;
|
|
992
|
+
}
|
|
993
|
+
if (isConnected && address && session && address.toLowerCase() !== session.address.toLowerCase()) {
|
|
994
|
+
clearSession();
|
|
995
|
+
return;
|
|
996
|
+
}
|
|
997
|
+
if (isConnected && address && !session && !isLoading && autoAttemptedAddress.current !== address) {
|
|
998
|
+
autoAttemptedAddress.current = address;
|
|
999
|
+
void login().catch(() => {
|
|
1000
|
+
});
|
|
1001
|
+
}
|
|
1002
|
+
}, [autoLogin, status, isConnected, address, session, isLoading, login, clearSession]);
|
|
1003
|
+
const value = react.useMemo(
|
|
1004
|
+
() => ({
|
|
1005
|
+
isAuthenticated: !!session,
|
|
1006
|
+
isLoading,
|
|
1007
|
+
error,
|
|
1008
|
+
session,
|
|
1009
|
+
accessToken: tokens?.jwt_access_token,
|
|
1010
|
+
tokens,
|
|
1011
|
+
login,
|
|
1012
|
+
logout
|
|
1013
|
+
}),
|
|
1014
|
+
[session, isLoading, error, tokens, login, logout]
|
|
1015
|
+
);
|
|
1016
|
+
return /* @__PURE__ */ jsxRuntime.jsx(SiweAuthContext.Provider, { value, children });
|
|
1017
|
+
}
|
|
1018
|
+
function useSiweAuth() {
|
|
1019
|
+
const ctx = react.useContext(SiweAuthContext);
|
|
1020
|
+
if (!ctx) throw new Error("useSiweAuth must be used within SiweAuthProvider");
|
|
1021
|
+
return ctx;
|
|
1022
|
+
}
|
|
794
1023
|
var PrivanaContext = react.createContext(null);
|
|
795
1024
|
function readStoredHostedAuthSession(storage, hostedAuthStorageKey, now = Date.now()) {
|
|
796
1025
|
const raw = storage.getItem(hostedAuthStorageKey);
|
|
@@ -831,8 +1060,14 @@ function PrivanaProvider({
|
|
|
831
1060
|
chains,
|
|
832
1061
|
pollingInterval = 1e4,
|
|
833
1062
|
serviceAddress,
|
|
834
|
-
hostedAuth
|
|
1063
|
+
hostedAuth,
|
|
1064
|
+
siweAuth
|
|
835
1065
|
}) {
|
|
1066
|
+
if (hostedAuth && siweAuth) {
|
|
1067
|
+
throw new Error(
|
|
1068
|
+
"PrivanaProvider: `hostedAuth` and `siweAuth` are mutually exclusive - provide only one. When both are set, private reads use hosted auth and the in-app SIWE login is ignored."
|
|
1069
|
+
);
|
|
1070
|
+
}
|
|
836
1071
|
const networkConfig = react.useMemo(() => {
|
|
837
1072
|
const config = {
|
|
838
1073
|
...DEFAULT_NETWORK_CONFIG,
|
|
@@ -1067,7 +1302,16 @@ function PrivanaProvider({
|
|
|
1067
1302
|
refreshHostedAuthSession
|
|
1068
1303
|
]
|
|
1069
1304
|
);
|
|
1070
|
-
return /* @__PURE__ */ jsxRuntime.jsx(PrivanaContext.Provider, { value, children
|
|
1305
|
+
return /* @__PURE__ */ jsxRuntime.jsx(PrivanaContext.Provider, { value, children: siweAuth ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
1306
|
+
SiweAuthProvider,
|
|
1307
|
+
{
|
|
1308
|
+
client,
|
|
1309
|
+
networkConfig,
|
|
1310
|
+
autoLogin: siweAuth.autoLogin,
|
|
1311
|
+
statement: siweAuth.statement,
|
|
1312
|
+
children
|
|
1313
|
+
}
|
|
1314
|
+
) : children });
|
|
1071
1315
|
}
|
|
1072
1316
|
function usePrivanaContext() {
|
|
1073
1317
|
const context = react.useContext(PrivanaContext);
|
|
@@ -1327,40 +1571,10 @@ function formatTimeRemaining(expiryTimestamp) {
|
|
|
1327
1571
|
}
|
|
1328
1572
|
return `${minutes}m left`;
|
|
1329
1573
|
}
|
|
1330
|
-
var defaultResult = {
|
|
1331
|
-
address: void 0,
|
|
1332
|
-
isConnected: false
|
|
1333
|
-
};
|
|
1334
|
-
function useSafeAccount() {
|
|
1335
|
-
const context = react.useContext(wagmi.WagmiContext);
|
|
1336
|
-
const cacheRef = react.useRef(defaultResult);
|
|
1337
|
-
const subscribe = react.useCallback(
|
|
1338
|
-
(onChange) => {
|
|
1339
|
-
if (!context) return () => {
|
|
1340
|
-
};
|
|
1341
|
-
return actions.watchAccount(context, { onChange });
|
|
1342
|
-
},
|
|
1343
|
-
[context]
|
|
1344
|
-
);
|
|
1345
|
-
const getSnapshot = react.useCallback(() => {
|
|
1346
|
-
if (!context) return defaultResult;
|
|
1347
|
-
const account = actions.getAccount(context);
|
|
1348
|
-
if (cacheRef.current.address !== account.address || cacheRef.current.isConnected !== account.isConnected) {
|
|
1349
|
-
cacheRef.current = { address: account.address, isConnected: account.isConnected };
|
|
1350
|
-
}
|
|
1351
|
-
return cacheRef.current;
|
|
1352
|
-
}, [context]);
|
|
1353
|
-
const getServerSnapshot = react.useCallback(() => defaultResult, []);
|
|
1354
|
-
return react.useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);
|
|
1355
|
-
}
|
|
1356
|
-
|
|
1357
|
-
// src/sdk/hooks/use-private-read-request.ts
|
|
1358
|
-
var AUTH_CLOCK_SKEW_MS = 3e4;
|
|
1359
1574
|
var INITIAL_AUTH_BACKOFF_MS = 5e3;
|
|
1360
1575
|
var MAX_AUTH_BACKOFF_MS = 6e4;
|
|
1361
1576
|
var DEFAULT_SIWE_AUTH_VALIDITY_MS = 24 * 60 * 60 * 1e3;
|
|
1362
1577
|
var PRIVATE_READ_STATEMENT = "Sign in to Privana to access private account data.";
|
|
1363
|
-
var privateReadTokenCache = /* @__PURE__ */ new Map();
|
|
1364
1578
|
var privateReadFailureCache = /* @__PURE__ */ new Map();
|
|
1365
1579
|
var privateReadInflight = /* @__PURE__ */ new Map();
|
|
1366
1580
|
async function executeHostedAuthPrivateReadRequest({
|
|
@@ -1394,20 +1608,8 @@ async function executeHostedAuthPrivateReadRequest({
|
|
|
1394
1608
|
return request();
|
|
1395
1609
|
}
|
|
1396
1610
|
}
|
|
1397
|
-
function createScopeKey(apiUrl, deploymentChainId, address) {
|
|
1398
|
-
return `${apiUrl.replace(/\/$/, "")}:${deploymentChainId}:${address.toLowerCase()}`;
|
|
1399
|
-
}
|
|
1400
|
-
function getCachedPrivateReadToken(scopeKey) {
|
|
1401
|
-
const cached = privateReadTokenCache.get(scopeKey);
|
|
1402
|
-
if (!cached) return null;
|
|
1403
|
-
if (cached.expiresAt <= Date.now() + AUTH_CLOCK_SKEW_MS) {
|
|
1404
|
-
privateReadTokenCache.delete(scopeKey);
|
|
1405
|
-
return null;
|
|
1406
|
-
}
|
|
1407
|
-
return cached.token;
|
|
1408
|
-
}
|
|
1409
1611
|
function clearPrivateReadScope(scopeKey, client) {
|
|
1410
|
-
|
|
1612
|
+
deleteCachedPrivateReadToken(scopeKey);
|
|
1411
1613
|
privateReadFailureCache.delete(scopeKey);
|
|
1412
1614
|
client.clearPrivateReadToken();
|
|
1413
1615
|
}
|
|
@@ -1506,10 +1708,7 @@ function usePrivateReadRequest() {
|
|
|
1506
1708
|
siwe_message: message,
|
|
1507
1709
|
signature
|
|
1508
1710
|
});
|
|
1509
|
-
|
|
1510
|
-
token: login.siwe_token,
|
|
1511
|
-
expiresAt: expirationTime.getTime()
|
|
1512
|
-
});
|
|
1711
|
+
setCachedPrivateReadToken(scopeKey, login.siwe_token, expirationTime.getTime());
|
|
1513
1712
|
privateReadFailureCache.delete(scopeKey);
|
|
1514
1713
|
client.setPrivateReadToken(login.siwe_token);
|
|
1515
1714
|
return login.siwe_token;
|
|
@@ -1630,6 +1829,12 @@ function getAction(client, actionFn, name) {
|
|
|
1630
1829
|
function getChainId2(config) {
|
|
1631
1830
|
return config.state.chainId;
|
|
1632
1831
|
}
|
|
1832
|
+
async function getTransactionReceipt(config, parameters) {
|
|
1833
|
+
const { chainId, ...rest } = parameters;
|
|
1834
|
+
const client = config.getClient({ chainId });
|
|
1835
|
+
const action = getAction(client, actions$1.getTransactionReceipt, "getTransactionReceipt");
|
|
1836
|
+
return action(rest);
|
|
1837
|
+
}
|
|
1633
1838
|
async function waitForTransactionReceipt(config, parameters) {
|
|
1634
1839
|
const { chainId, timeout = 0, ...rest } = parameters;
|
|
1635
1840
|
const client = config.getClient({ chainId });
|
|
@@ -1710,6 +1915,36 @@ function useEnsureCorrectChain() {
|
|
|
1710
1915
|
}
|
|
1711
1916
|
|
|
1712
1917
|
// src/sdk/hooks/use-deposit.ts
|
|
1918
|
+
var STALE_MS = 30 * 60 * 1e3;
|
|
1919
|
+
function storageKey(address) {
|
|
1920
|
+
return `privana:pending-deposit:${address.toLowerCase()}`;
|
|
1921
|
+
}
|
|
1922
|
+
function savePendingDeposit(address, data) {
|
|
1923
|
+
try {
|
|
1924
|
+
sessionStorage.setItem(storageKey(address), JSON.stringify(data));
|
|
1925
|
+
} catch {
|
|
1926
|
+
}
|
|
1927
|
+
}
|
|
1928
|
+
function loadPendingDeposit(address) {
|
|
1929
|
+
try {
|
|
1930
|
+
const raw = sessionStorage.getItem(storageKey(address));
|
|
1931
|
+
if (!raw) return null;
|
|
1932
|
+
const data = JSON.parse(raw);
|
|
1933
|
+
if (Date.now() - data.savedAt > STALE_MS) {
|
|
1934
|
+
clearPendingDeposit(address);
|
|
1935
|
+
return null;
|
|
1936
|
+
}
|
|
1937
|
+
return data;
|
|
1938
|
+
} catch {
|
|
1939
|
+
return null;
|
|
1940
|
+
}
|
|
1941
|
+
}
|
|
1942
|
+
function clearPendingDeposit(address) {
|
|
1943
|
+
try {
|
|
1944
|
+
sessionStorage.removeItem(storageKey(address));
|
|
1945
|
+
} catch {
|
|
1946
|
+
}
|
|
1947
|
+
}
|
|
1713
1948
|
function useDeposit(options = {}) {
|
|
1714
1949
|
const { address } = wagmi.useAccount();
|
|
1715
1950
|
const { client, enabledTokens, getChainById: getChainById2 } = usePrivanaContext();
|
|
@@ -1777,13 +2012,19 @@ function useDeposit(options = {}) {
|
|
|
1777
2012
|
const isSendingTx = isWritingContract || isSendingNative;
|
|
1778
2013
|
const sendError = writeError ?? sendNativeError;
|
|
1779
2014
|
const { ensureCorrectChain } = useEnsureCorrectChain();
|
|
2015
|
+
const invalidateGeneration = react.useCallback(() => {
|
|
2016
|
+
generationRef.current++;
|
|
2017
|
+
}, []);
|
|
1780
2018
|
react.useEffect(() => {
|
|
1781
2019
|
return () => {
|
|
2020
|
+
invalidateGeneration();
|
|
1782
2021
|
if (pollIntervalRef.current) {
|
|
1783
2022
|
clearTimeout(pollIntervalRef.current);
|
|
2023
|
+
pollIntervalRef.current = null;
|
|
1784
2024
|
}
|
|
1785
2025
|
};
|
|
1786
|
-
}, []);
|
|
2026
|
+
}, [invalidateGeneration]);
|
|
2027
|
+
const resumedAddressRef = react.useRef(void 0);
|
|
1787
2028
|
const stopPolling = react.useCallback(() => {
|
|
1788
2029
|
if (pollIntervalRef.current) {
|
|
1789
2030
|
clearTimeout(pollIntervalRef.current);
|
|
@@ -1793,6 +2034,7 @@ function useDeposit(options = {}) {
|
|
|
1793
2034
|
const reset = react.useCallback(() => {
|
|
1794
2035
|
generationRef.current++;
|
|
1795
2036
|
stopPolling();
|
|
2037
|
+
if (address) clearPendingDeposit(address);
|
|
1796
2038
|
verificationContextRef.current = null;
|
|
1797
2039
|
setDepositAddress(null);
|
|
1798
2040
|
setTxHash(void 0);
|
|
@@ -1805,7 +2047,7 @@ function useDeposit(options = {}) {
|
|
|
1805
2047
|
addressMutation.reset();
|
|
1806
2048
|
resetWriteContract();
|
|
1807
2049
|
resetSendTransaction();
|
|
1808
|
-
}, [addressMutation, resetWriteContract, resetSendTransaction, stopPolling]);
|
|
2050
|
+
}, [address, addressMutation, resetWriteContract, resetSendTransaction, stopPolling]);
|
|
1809
2051
|
const runVerification = react.useCallback(
|
|
1810
2052
|
async (ctx, generation) => {
|
|
1811
2053
|
const isStale = () => generation !== generationRef.current;
|
|
@@ -1833,6 +2075,7 @@ function useDeposit(options = {}) {
|
|
|
1833
2075
|
if (triggerResult.status === "credited") {
|
|
1834
2076
|
setIsWaitingForProcessing(false);
|
|
1835
2077
|
verificationContextRef.current = null;
|
|
2078
|
+
if (address) clearPendingDeposit(address);
|
|
1836
2079
|
queryClient.invalidateQueries({ queryKey: ["accounting-balance"] });
|
|
1837
2080
|
queryClient.invalidateQueries({ queryKey: ["accounting-history"] });
|
|
1838
2081
|
onCreditedRef.current?.(hash, triggerResult);
|
|
@@ -1866,6 +2109,7 @@ function useDeposit(options = {}) {
|
|
|
1866
2109
|
stopPolling();
|
|
1867
2110
|
setIsWaitingForProcessing(false);
|
|
1868
2111
|
verificationContextRef.current = null;
|
|
2112
|
+
if (address) clearPendingDeposit(address);
|
|
1869
2113
|
queryClient.invalidateQueries({ queryKey: ["accounting-balance"] });
|
|
1870
2114
|
queryClient.invalidateQueries({ queryKey: ["accounting-history"] });
|
|
1871
2115
|
onCreditedRef.current?.(hash, result);
|
|
@@ -1905,7 +2149,7 @@ function useDeposit(options = {}) {
|
|
|
1905
2149
|
);
|
|
1906
2150
|
}
|
|
1907
2151
|
},
|
|
1908
|
-
[client, executePrivateRead, pollInterval, pollTimeout, queryClient, stopPolling]
|
|
2152
|
+
[address, client, executePrivateRead, pollInterval, pollTimeout, queryClient, stopPolling]
|
|
1909
2153
|
);
|
|
1910
2154
|
const retryVerification = react.useCallback(async () => {
|
|
1911
2155
|
const ctx = verificationContextRef.current;
|
|
@@ -1917,6 +2161,55 @@ function useDeposit(options = {}) {
|
|
|
1917
2161
|
const generation = generationRef.current;
|
|
1918
2162
|
await runVerification(ctx, generation);
|
|
1919
2163
|
}, [runVerification, stopPolling]);
|
|
2164
|
+
react.useEffect(() => {
|
|
2165
|
+
if (!address || resumedAddressRef.current === address) return;
|
|
2166
|
+
const persisted = loadPendingDeposit(address);
|
|
2167
|
+
if (!persisted) return;
|
|
2168
|
+
resumedAddressRef.current = address;
|
|
2169
|
+
const hash = persisted.txHash;
|
|
2170
|
+
setTxHash(hash);
|
|
2171
|
+
setDepositAddress(persisted.depositAddress);
|
|
2172
|
+
setIsWaitingForConfirmation(true);
|
|
2173
|
+
const ctx = {
|
|
2174
|
+
hash,
|
|
2175
|
+
chainId: persisted.chainId,
|
|
2176
|
+
amount: BigInt(persisted.amount)
|
|
2177
|
+
};
|
|
2178
|
+
verificationContextRef.current = ctx;
|
|
2179
|
+
const generation = ++generationRef.current;
|
|
2180
|
+
const isStale = () => generation !== generationRef.current;
|
|
2181
|
+
(async () => {
|
|
2182
|
+
try {
|
|
2183
|
+
let confirmed = false;
|
|
2184
|
+
try {
|
|
2185
|
+
await getTransactionReceipt(config, { hash, chainId: persisted.chainId });
|
|
2186
|
+
confirmed = true;
|
|
2187
|
+
} catch {
|
|
2188
|
+
}
|
|
2189
|
+
if (!confirmed) {
|
|
2190
|
+
await waitForTransactionReceipt(config, {
|
|
2191
|
+
hash,
|
|
2192
|
+
chainId: persisted.chainId,
|
|
2193
|
+
confirmations
|
|
2194
|
+
});
|
|
2195
|
+
}
|
|
2196
|
+
if (isStale()) return;
|
|
2197
|
+
setIsWaitingForConfirmation(false);
|
|
2198
|
+
onDepositSuccessRef.current?.(hash);
|
|
2199
|
+
queryClient.invalidateQueries({ queryKey: ["readContract"] });
|
|
2200
|
+
await runVerification(ctx, generation);
|
|
2201
|
+
} catch (err) {
|
|
2202
|
+
if (isStale()) return;
|
|
2203
|
+
setIsWaitingForConfirmation(false);
|
|
2204
|
+
stopPolling();
|
|
2205
|
+
const error2 = err instanceof Error ? err : new Error("Deposit verification failed");
|
|
2206
|
+
setIsWaitingForProcessing(false);
|
|
2207
|
+
setDepositError(error2);
|
|
2208
|
+
setVerificationFailed(true);
|
|
2209
|
+
onErrorRef.current?.(error2);
|
|
2210
|
+
}
|
|
2211
|
+
})();
|
|
2212
|
+
}, [address, config, confirmations, queryClient, runVerification, stopPolling]);
|
|
1920
2213
|
const deposit = react.useCallback(
|
|
1921
2214
|
async (params) => {
|
|
1922
2215
|
if (verificationContextRef.current) {
|
|
@@ -1973,10 +2266,21 @@ function useDeposit(options = {}) {
|
|
|
1973
2266
|
amount: params.amount
|
|
1974
2267
|
};
|
|
1975
2268
|
verificationContextRef.current = ctx;
|
|
2269
|
+
savePendingDeposit(address, {
|
|
2270
|
+
txHash: hash,
|
|
2271
|
+
chainId: sourceChain.id,
|
|
2272
|
+
amount: params.amount.toString(),
|
|
2273
|
+
depositAddress: addrResponse,
|
|
2274
|
+
savedAt: Date.now()
|
|
2275
|
+
});
|
|
1976
2276
|
try {
|
|
1977
2277
|
setIsWaitingForConfirmation(true);
|
|
1978
2278
|
try {
|
|
1979
|
-
await waitForTransactionReceipt(config, {
|
|
2279
|
+
await waitForTransactionReceipt(config, {
|
|
2280
|
+
hash,
|
|
2281
|
+
chainId: sourceChain.id,
|
|
2282
|
+
confirmations
|
|
2283
|
+
});
|
|
1980
2284
|
} finally {
|
|
1981
2285
|
if (!isStale()) setIsWaitingForConfirmation(false);
|
|
1982
2286
|
}
|
|
@@ -3174,6 +3478,7 @@ function DepositForm({
|
|
|
3174
3478
|
selectedToken,
|
|
3175
3479
|
onTokenSelect,
|
|
3176
3480
|
onPendingChange,
|
|
3481
|
+
onUnsafeToCloseChange,
|
|
3177
3482
|
onSuccess
|
|
3178
3483
|
}) {
|
|
3179
3484
|
const { isConnected, address } = wagmi.useAccount();
|
|
@@ -3202,7 +3507,7 @@ function DepositForm({
|
|
|
3202
3507
|
}
|
|
3203
3508
|
});
|
|
3204
3509
|
const walletBalance = isNative ? nativeBalanceData?.value : erc20Balance;
|
|
3205
|
-
const formattedWalletBalance = walletBalance ? formatTokenAmount(walletBalance.toString(), selectedToken.decimals) : "0";
|
|
3510
|
+
const formattedWalletBalance = walletBalance ? formatTokenAmount(walletBalance.toString(), selectedToken.decimals) : "0.00";
|
|
3206
3511
|
const handleMaxClick = () => {
|
|
3207
3512
|
if (formattedWalletBalance && parseFloat(formattedWalletBalance) > 0) {
|
|
3208
3513
|
setAmount(formattedWalletBalance.replace(/[\s\u2009]/g, ""));
|
|
@@ -3269,6 +3574,9 @@ function DepositForm({
|
|
|
3269
3574
|
react.useEffect(() => {
|
|
3270
3575
|
onPendingChange?.(isPending && !cancelled);
|
|
3271
3576
|
}, [isPending, cancelled, onPendingChange]);
|
|
3577
|
+
react.useEffect(() => {
|
|
3578
|
+
onUnsafeToCloseChange?.((isGettingAddress || isSendingTransaction) && !cancelled);
|
|
3579
|
+
}, [isGettingAddress, isSendingTransaction, cancelled, onUnsafeToCloseChange]);
|
|
3272
3580
|
const handleCancel = () => {
|
|
3273
3581
|
setCancelled(true);
|
|
3274
3582
|
reset();
|
|
@@ -3432,7 +3740,12 @@ function DepositForm({
|
|
|
3432
3740
|
)
|
|
3433
3741
|
] });
|
|
3434
3742
|
}
|
|
3435
|
-
function WithdrawForm({
|
|
3743
|
+
function WithdrawForm({
|
|
3744
|
+
selectedToken,
|
|
3745
|
+
onTokenSelect,
|
|
3746
|
+
onPendingChange,
|
|
3747
|
+
onUnsafeToCloseChange
|
|
3748
|
+
}) {
|
|
3436
3749
|
const { isConnected, address } = wagmi.useAccount();
|
|
3437
3750
|
const { chains, getChainById: getChainById2 } = usePrivanaContext();
|
|
3438
3751
|
const [amount, setAmount] = react.useState("");
|
|
@@ -3485,8 +3798,10 @@ function WithdrawForm({ selectedToken, onTokenSelect, onPendingChange }) {
|
|
|
3485
3798
|
}
|
|
3486
3799
|
}, [error]);
|
|
3487
3800
|
react.useEffect(() => {
|
|
3488
|
-
|
|
3489
|
-
|
|
3801
|
+
const pending = isPending && !cancelled;
|
|
3802
|
+
onPendingChange?.(pending);
|
|
3803
|
+
onUnsafeToCloseChange?.(pending);
|
|
3804
|
+
}, [isPending, cancelled, onPendingChange, onUnsafeToCloseChange]);
|
|
3490
3805
|
const handleCancel = () => {
|
|
3491
3806
|
setCancelled(true);
|
|
3492
3807
|
reset();
|
|
@@ -3645,9 +3960,6 @@ function CloseIcon() {
|
|
|
3645
3960
|
}
|
|
3646
3961
|
) });
|
|
3647
3962
|
}
|
|
3648
|
-
function SearchIcon() {
|
|
3649
|
-
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-3 w-3 rounded-full border-[1.5px] border-current" });
|
|
3650
|
-
}
|
|
3651
3963
|
function ChevronRight() {
|
|
3652
3964
|
return /* @__PURE__ */ jsxRuntime.jsx("svg", { width: "10", height: "5", viewBox: "0 0 12 6", className: "-rotate-90", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
3653
3965
|
"path",
|
|
@@ -3708,7 +4020,7 @@ function BalanceCards({
|
|
|
3708
4020
|
});
|
|
3709
4021
|
const { totalLocked, isLoading: lockedLoading } = useLockedFunds({ enabled: showLockedFunds });
|
|
3710
4022
|
const formattedBalance = formatTokenAmount(balanceWei, selectedToken.decimals);
|
|
3711
|
-
const formattedLocked = showLockedFunds ? formatTokenAmount(String(totalLocked), selectedToken.decimals) : "0";
|
|
4023
|
+
const formattedLocked = showLockedFunds ? formatTokenAmount(String(totalLocked), selectedToken.decimals) : "0.00";
|
|
3712
4024
|
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: cn("flex gap-2", disabled && "opacity-50"), children: [
|
|
3713
4025
|
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
3714
4026
|
"button",
|
|
@@ -3771,7 +4083,7 @@ function Tabs({
|
|
|
3771
4083
|
"div",
|
|
3772
4084
|
{
|
|
3773
4085
|
className: cn(
|
|
3774
|
-
"bg-input absolute top-1 bottom-1 left-1 w-[calc(50%-
|
|
4086
|
+
"bg-input absolute top-1 bottom-1 left-1 w-[calc(50%-8px)] rounded-md transition-transform duration-200",
|
|
3775
4087
|
activeTab === "withdraw" && "translate-x-[calc(100%+8px)]"
|
|
3776
4088
|
)
|
|
3777
4089
|
}
|
|
@@ -3806,7 +4118,7 @@ function Tabs({
|
|
|
3806
4118
|
}
|
|
3807
4119
|
);
|
|
3808
4120
|
}
|
|
3809
|
-
function LockedFundsView({ onBack
|
|
4121
|
+
function LockedFundsView({ onBack }) {
|
|
3810
4122
|
const { getTokenById } = usePrivanaContext();
|
|
3811
4123
|
const { locks, isLoading } = useLockedFunds();
|
|
3812
4124
|
const { unlockFunds, unlockAllExpired, isPending } = useUnlockFunds();
|
|
@@ -3839,27 +4151,17 @@ function LockedFundsView({ onBack, onClose }) {
|
|
|
3839
4151
|
};
|
|
3840
4152
|
const expiredCount = locks.filter((l) => l.is_expired).length;
|
|
3841
4153
|
return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
3842
|
-
/* @__PURE__ */ jsxRuntime.
|
|
3843
|
-
/* @__PURE__ */ jsxRuntime.
|
|
3844
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3845
|
-
"button",
|
|
3846
|
-
{
|
|
3847
|
-
onClick: onBack,
|
|
3848
|
-
className: "text-muted-foreground hover:text-foreground flex h-6 w-6 cursor-pointer items-center justify-center transition-colors",
|
|
3849
|
-
children: /* @__PURE__ */ jsxRuntime.jsx(ChevronLeft, {})
|
|
3850
|
-
}
|
|
3851
|
-
),
|
|
3852
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-foreground text-xl leading-6 font-medium", children: "Locked Funds" })
|
|
3853
|
-
] }),
|
|
3854
|
-
onClose && /* @__PURE__ */ jsxRuntime.jsx(
|
|
4154
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center justify-between px-5 py-4", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
|
|
4155
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3855
4156
|
"button",
|
|
3856
4157
|
{
|
|
3857
|
-
onClick:
|
|
3858
|
-
className: "text-muted-foreground hover:text-foreground flex h-6 w-6 cursor-pointer items-center justify-center transition-colors",
|
|
3859
|
-
children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
4158
|
+
onClick: onBack,
|
|
4159
|
+
className: "text-muted-foreground hover:text-foreground -ml-2 flex h-6 w-6 cursor-pointer items-center justify-center transition-colors",
|
|
4160
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(ChevronLeft, {})
|
|
3860
4161
|
}
|
|
3861
|
-
)
|
|
3862
|
-
|
|
4162
|
+
),
|
|
4163
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-foreground text-xl leading-6 font-medium", children: "Locked Funds" })
|
|
4164
|
+
] }) }),
|
|
3863
4165
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "bg-muted flex min-h-0 flex-1 flex-col rounded-[10px] p-2", children: [
|
|
3864
4166
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-1 overflow-y-auto", children: isLoading ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-col gap-2 p-3", children: [1, 2].map((i) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex animate-pulse items-center gap-3 rounded-lg p-3", children: [
|
|
3865
4167
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "bg-secondary h-10 w-10 rounded-full" }),
|
|
@@ -3944,34 +4246,24 @@ function BalanceTokenRow({ token }) {
|
|
|
3944
4246
|
isLoading ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "bg-secondary h-4 w-16 animate-pulse rounded" }) : /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-muted-foreground text-sm", children: formattedBalance })
|
|
3945
4247
|
] });
|
|
3946
4248
|
}
|
|
3947
|
-
function BalanceDetailsView({ onBack
|
|
4249
|
+
function BalanceDetailsView({ onBack }) {
|
|
3948
4250
|
const { enabledTokens, chains } = usePrivanaContext();
|
|
3949
4251
|
const [selectedChainId, setSelectedChainId] = react.useState(chains[0]?.id ?? 84532);
|
|
3950
4252
|
const chainTokens = react.useMemo(() => {
|
|
3951
4253
|
return enabledTokens.filter((t) => t.chainId === selectedChainId);
|
|
3952
4254
|
}, [enabledTokens, selectedChainId]);
|
|
3953
4255
|
return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
3954
|
-
/* @__PURE__ */ jsxRuntime.
|
|
3955
|
-
/* @__PURE__ */ jsxRuntime.
|
|
3956
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3957
|
-
"button",
|
|
3958
|
-
{
|
|
3959
|
-
onClick: onBack,
|
|
3960
|
-
className: "text-muted-foreground hover:text-foreground flex h-6 w-6 cursor-pointer items-center justify-center transition-colors",
|
|
3961
|
-
children: /* @__PURE__ */ jsxRuntime.jsx(ChevronLeft, {})
|
|
3962
|
-
}
|
|
3963
|
-
),
|
|
3964
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-foreground text-xl leading-6 font-medium", children: "Token Balances" })
|
|
3965
|
-
] }),
|
|
3966
|
-
onClose && /* @__PURE__ */ jsxRuntime.jsx(
|
|
4256
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center justify-between px-5 py-4", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
|
|
4257
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3967
4258
|
"button",
|
|
3968
4259
|
{
|
|
3969
|
-
onClick:
|
|
3970
|
-
className: "text-muted-foreground hover:text-foreground flex h-6 w-6 cursor-pointer items-center justify-center transition-colors",
|
|
3971
|
-
children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
4260
|
+
onClick: onBack,
|
|
4261
|
+
className: "text-muted-foreground hover:text-foreground -ml-2 flex h-6 w-6 cursor-pointer items-center justify-center transition-colors",
|
|
4262
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(ChevronLeft, {})
|
|
3972
4263
|
}
|
|
3973
|
-
)
|
|
3974
|
-
|
|
4264
|
+
),
|
|
4265
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-foreground text-xl leading-6 font-medium", children: "Token Balances" })
|
|
4266
|
+
] }) }),
|
|
3975
4267
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex min-h-0 flex-1 gap-2", children: [
|
|
3976
4268
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "bg-muted flex flex-1 flex-col overflow-hidden rounded-[10px] p-2", children: [
|
|
3977
4269
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-4 pt-4 pb-2", children: /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-muted-foreground text-sm", children: "Network" }) }),
|
|
@@ -4041,48 +4333,30 @@ function TokenRow({
|
|
|
4041
4333
|
}
|
|
4042
4334
|
function TokenSelectorView({
|
|
4043
4335
|
onBack,
|
|
4044
|
-
onClose,
|
|
4045
4336
|
onSelect,
|
|
4046
4337
|
selectedTokenId
|
|
4047
4338
|
}) {
|
|
4048
|
-
const [tokenSearch, setTokenSearch] = react.useState("");
|
|
4049
4339
|
const { enabledTokens, chains } = usePrivanaContext();
|
|
4050
4340
|
const [selectedChainId, setSelectedChainId] = react.useState(chains[0]?.id ?? 84532);
|
|
4051
4341
|
const chainTokens = react.useMemo(() => {
|
|
4052
4342
|
return enabledTokens.filter((t) => t.chainId === selectedChainId);
|
|
4053
4343
|
}, [enabledTokens, selectedChainId]);
|
|
4054
|
-
const filteredTokens = react.useMemo(() => {
|
|
4055
|
-
if (!tokenSearch) return chainTokens;
|
|
4056
|
-
return chainTokens.filter(
|
|
4057
|
-
(t) => t.symbol.toLowerCase().includes(tokenSearch.toLowerCase()) || t.name.toLowerCase().includes(tokenSearch.toLowerCase())
|
|
4058
|
-
);
|
|
4059
|
-
}, [tokenSearch, chainTokens]);
|
|
4060
4344
|
const handleTokenSelect = (token) => {
|
|
4061
4345
|
onSelect(token);
|
|
4062
4346
|
onBack();
|
|
4063
4347
|
};
|
|
4064
4348
|
return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
4065
|
-
/* @__PURE__ */ jsxRuntime.
|
|
4066
|
-
/* @__PURE__ */ jsxRuntime.
|
|
4067
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
4068
|
-
"button",
|
|
4069
|
-
{
|
|
4070
|
-
onClick: onBack,
|
|
4071
|
-
className: "text-muted-foreground hover:text-foreground flex h-6 w-6 cursor-pointer items-center justify-center transition-colors",
|
|
4072
|
-
children: /* @__PURE__ */ jsxRuntime.jsx(ChevronLeft, {})
|
|
4073
|
-
}
|
|
4074
|
-
),
|
|
4075
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-foreground text-xl leading-6 font-medium", children: "Select Token" })
|
|
4076
|
-
] }),
|
|
4077
|
-
onClose && /* @__PURE__ */ jsxRuntime.jsx(
|
|
4349
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center justify-between px-5 py-4", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
|
|
4350
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
4078
4351
|
"button",
|
|
4079
4352
|
{
|
|
4080
|
-
onClick:
|
|
4081
|
-
className: "text-muted-foreground hover:text-foreground flex h-6 w-6 cursor-pointer items-center justify-center transition-colors",
|
|
4082
|
-
children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
4353
|
+
onClick: onBack,
|
|
4354
|
+
className: "text-muted-foreground hover:text-foreground -ml-2 flex h-6 w-6 cursor-pointer items-center justify-center transition-colors",
|
|
4355
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(ChevronLeft, {})
|
|
4083
4356
|
}
|
|
4084
|
-
)
|
|
4085
|
-
|
|
4357
|
+
),
|
|
4358
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-foreground text-xl leading-6 font-medium", children: "Select Token" })
|
|
4359
|
+
] }) }),
|
|
4086
4360
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex min-h-0 flex-1 gap-2", children: [
|
|
4087
4361
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "bg-muted flex flex-1 flex-col overflow-hidden rounded-[10px] p-2", children: [
|
|
4088
4362
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-4 pt-4 pb-2", children: /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-muted-foreground text-sm", children: "Network" }) }),
|
|
@@ -4106,23 +4380,8 @@ function TokenSelectorView({
|
|
|
4106
4380
|
}) })
|
|
4107
4381
|
] }),
|
|
4108
4382
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "bg-muted flex flex-[2] flex-col overflow-hidden rounded-[10px] p-2", children: [
|
|
4109
|
-
/* @__PURE__ */ jsxRuntime.
|
|
4110
|
-
|
|
4111
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-3", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "border-border bg-input flex items-center gap-2.5 rounded-lg border px-3 py-2.5", children: [
|
|
4112
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-muted-foreground", children: /* @__PURE__ */ jsxRuntime.jsx(SearchIcon, {}) }),
|
|
4113
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
4114
|
-
"input",
|
|
4115
|
-
{
|
|
4116
|
-
type: "text",
|
|
4117
|
-
placeholder: "Search",
|
|
4118
|
-
value: tokenSearch,
|
|
4119
|
-
onChange: (e) => setTokenSearch(e.target.value),
|
|
4120
|
-
className: "text-foreground placeholder:text-muted-foreground flex-1 bg-transparent text-sm outline-none"
|
|
4121
|
-
}
|
|
4122
|
-
)
|
|
4123
|
-
] }) })
|
|
4124
|
-
] }),
|
|
4125
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "mt-2 flex-1 overflow-y-auto", children: filteredTokens.map((token) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
4383
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-4 pt-4 pb-2", children: /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-muted-foreground text-sm", children: "Token" }) }),
|
|
4384
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "mt-2 flex-1 overflow-y-auto", children: chainTokens.map((token) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
4126
4385
|
TokenRow,
|
|
4127
4386
|
{
|
|
4128
4387
|
token,
|
|
@@ -4136,9 +4395,8 @@ function TokenSelectorView({
|
|
|
4136
4395
|
] });
|
|
4137
4396
|
}
|
|
4138
4397
|
function ModalBody({
|
|
4139
|
-
onClose,
|
|
4140
4398
|
onViewChange,
|
|
4141
|
-
|
|
4399
|
+
onCloseBlockedChange,
|
|
4142
4400
|
showLockedFunds = true,
|
|
4143
4401
|
defaultTab = "deposit",
|
|
4144
4402
|
onDepositSuccess
|
|
@@ -4147,65 +4405,78 @@ function ModalBody({
|
|
|
4147
4405
|
const [selectedToken, setSelectedToken] = react.useState(defaultToken);
|
|
4148
4406
|
const [activeTab, setActiveTab] = react.useState(defaultTab);
|
|
4149
4407
|
const [currentView, setCurrentView] = react.useState("main");
|
|
4150
|
-
const [
|
|
4408
|
+
const [isInteractionPending, setIsInteractionPending] = react.useState(false);
|
|
4151
4409
|
react.useEffect(() => {
|
|
4152
4410
|
if (!selectedToken && defaultToken) {
|
|
4153
4411
|
setSelectedToken(defaultToken);
|
|
4154
4412
|
}
|
|
4155
4413
|
}, [selectedToken, defaultToken]);
|
|
4156
|
-
const
|
|
4157
|
-
|
|
4158
|
-
|
|
4414
|
+
const handlePendingChange = (isPending) => {
|
|
4415
|
+
setIsInteractionPending(isPending);
|
|
4416
|
+
};
|
|
4417
|
+
const handleCloseBlockedChange = (isBlocked) => {
|
|
4418
|
+
onCloseBlockedChange?.(isBlocked);
|
|
4159
4419
|
};
|
|
4160
4420
|
const handleViewChange = (view) => {
|
|
4161
|
-
|
|
4162
|
-
|
|
4421
|
+
const update = () => {
|
|
4422
|
+
setCurrentView(view);
|
|
4423
|
+
onViewChange?.(view);
|
|
4424
|
+
};
|
|
4425
|
+
if (typeof document !== "undefined" && typeof document.startViewTransition === "function") {
|
|
4426
|
+
document.documentElement.dataset.privanaVtDir = view === "main" ? "back" : "forward";
|
|
4427
|
+
const transition = document.startViewTransition(update);
|
|
4428
|
+
transition.finished.then(() => {
|
|
4429
|
+
delete document.documentElement.dataset.privanaVtDir;
|
|
4430
|
+
});
|
|
4431
|
+
} else {
|
|
4432
|
+
update();
|
|
4433
|
+
}
|
|
4163
4434
|
};
|
|
4164
4435
|
const handleTokenSelect = (token) => {
|
|
4165
4436
|
setSelectedToken(token);
|
|
4166
4437
|
};
|
|
4167
4438
|
if (tokensStatus === "loading" || !selectedToken) {
|
|
4168
|
-
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-2
|
|
4439
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-2", children: [
|
|
4169
4440
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "bg-secondary h-25 animate-pulse rounded-[10px]" }),
|
|
4170
4441
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "bg-secondary h-11 animate-pulse rounded-[10px]" }),
|
|
4171
4442
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "bg-secondary h-50 animate-pulse rounded-[10px]" })
|
|
4172
4443
|
] });
|
|
4173
4444
|
}
|
|
4174
4445
|
if (currentView === "locked-funds") {
|
|
4175
|
-
return /* @__PURE__ */ jsxRuntime.jsx(LockedFundsView, { onBack: () => handleViewChange("main")
|
|
4446
|
+
return /* @__PURE__ */ jsxRuntime.jsx(LockedFundsView, { onBack: () => handleViewChange("main") });
|
|
4176
4447
|
}
|
|
4177
4448
|
if (currentView === "balance-details") {
|
|
4178
|
-
return /* @__PURE__ */ jsxRuntime.jsx(BalanceDetailsView, { onBack: () => handleViewChange("main")
|
|
4449
|
+
return /* @__PURE__ */ jsxRuntime.jsx(BalanceDetailsView, { onBack: () => handleViewChange("main") });
|
|
4179
4450
|
}
|
|
4180
4451
|
if (currentView === "select-token") {
|
|
4181
4452
|
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
4182
4453
|
TokenSelectorView,
|
|
4183
4454
|
{
|
|
4184
4455
|
onBack: () => handleViewChange("main"),
|
|
4185
|
-
onClose,
|
|
4186
4456
|
onSelect: handleTokenSelect,
|
|
4187
4457
|
selectedTokenId: selectedToken.id
|
|
4188
4458
|
}
|
|
4189
4459
|
);
|
|
4190
4460
|
}
|
|
4191
4461
|
return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-2 pb-4", children: [
|
|
4192
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { className: cn(
|
|
4462
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: cn(isInteractionPending && "pointer-events-none"), children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
4193
4463
|
BalanceCards,
|
|
4194
4464
|
{
|
|
4195
4465
|
selectedToken,
|
|
4196
4466
|
onLockedFundsClick: () => handleViewChange("locked-funds"),
|
|
4197
4467
|
onBalanceClick: () => handleViewChange("balance-details"),
|
|
4198
4468
|
showLockedFunds,
|
|
4199
|
-
disabled:
|
|
4469
|
+
disabled: isInteractionPending
|
|
4200
4470
|
}
|
|
4201
4471
|
) }),
|
|
4202
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { className: cn(
|
|
4472
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: cn(isInteractionPending && "pointer-events-none"), children: /* @__PURE__ */ jsxRuntime.jsx(Tabs, { activeTab, onTabChange: setActiveTab, disabled: isInteractionPending }) }),
|
|
4203
4473
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "bg-muted rounded-[10px] p-5", children: activeTab === "deposit" ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
4204
4474
|
DepositForm,
|
|
4205
4475
|
{
|
|
4206
4476
|
selectedToken,
|
|
4207
4477
|
onTokenSelect: () => handleViewChange("select-token"),
|
|
4208
|
-
onPendingChange:
|
|
4478
|
+
onPendingChange: handlePendingChange,
|
|
4479
|
+
onUnsafeToCloseChange: handleCloseBlockedChange,
|
|
4209
4480
|
onSuccess: onDepositSuccess
|
|
4210
4481
|
}
|
|
4211
4482
|
) : /* @__PURE__ */ jsxRuntime.jsx(
|
|
@@ -4213,7 +4484,8 @@ function ModalBody({
|
|
|
4213
4484
|
{
|
|
4214
4485
|
selectedToken,
|
|
4215
4486
|
onTokenSelect: () => handleViewChange("select-token"),
|
|
4216
|
-
onPendingChange:
|
|
4487
|
+
onPendingChange: handlePendingChange,
|
|
4488
|
+
onUnsafeToCloseChange: handleCloseBlockedChange
|
|
4217
4489
|
}
|
|
4218
4490
|
) })
|
|
4219
4491
|
] }) });
|
|
@@ -4225,55 +4497,84 @@ function PrivanaModal({
|
|
|
4225
4497
|
defaultTab,
|
|
4226
4498
|
onDepositSuccess
|
|
4227
4499
|
}) {
|
|
4228
|
-
const [
|
|
4500
|
+
const [currentView, setCurrentView] = react.useState("main");
|
|
4229
4501
|
const titleId = react.useId();
|
|
4230
4502
|
const descId = react.useId();
|
|
4231
|
-
const
|
|
4232
|
-
|
|
4233
|
-
|
|
4234
|
-
}
|
|
4235
|
-
if (!isOpen) {
|
|
4236
|
-
onClose();
|
|
4237
|
-
}
|
|
4503
|
+
const [isCloseBlocked, setIsCloseBlocked] = react.useState(false);
|
|
4504
|
+
const handleClose = () => {
|
|
4505
|
+
if (!isCloseBlocked) onClose();
|
|
4238
4506
|
};
|
|
4239
|
-
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
4240
|
-
|
|
4507
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
4508
|
+
Dialog,
|
|
4241
4509
|
{
|
|
4242
|
-
|
|
4243
|
-
|
|
4244
|
-
|
|
4245
|
-
|
|
4246
|
-
|
|
4247
|
-
|
|
4248
|
-
|
|
4249
|
-
|
|
4250
|
-
|
|
4251
|
-
|
|
4252
|
-
|
|
4253
|
-
|
|
4254
|
-
|
|
4510
|
+
open,
|
|
4511
|
+
onOpenChange: (isOpen) => {
|
|
4512
|
+
if (!isOpen) handleClose();
|
|
4513
|
+
},
|
|
4514
|
+
children: /* @__PURE__ */ jsxRuntime.jsxs(
|
|
4515
|
+
DialogContent,
|
|
4516
|
+
{
|
|
4517
|
+
"data-privana": true,
|
|
4518
|
+
"data-view": currentView,
|
|
4519
|
+
showCloseButton: false,
|
|
4520
|
+
onInteractOutside: isCloseBlocked ? (e) => e.preventDefault() : void 0,
|
|
4521
|
+
onEscapeKeyDown: isCloseBlocked ? (e) => e.preventDefault() : void 0,
|
|
4522
|
+
className: "bg-card flex h-[596px] max-h-[95dvh] w-[560px] max-w-[95vw] flex-col gap-2 overflow-hidden rounded-2xl border-0 p-2 pb-[2.75rem]",
|
|
4523
|
+
"aria-labelledby": titleId,
|
|
4524
|
+
"aria-describedby": descId,
|
|
4525
|
+
children: [
|
|
4526
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
4255
4527
|
"button",
|
|
4256
4528
|
{
|
|
4257
|
-
|
|
4258
|
-
|
|
4529
|
+
"data-privana-close": true,
|
|
4530
|
+
onClick: handleClose,
|
|
4531
|
+
disabled: isCloseBlocked,
|
|
4532
|
+
"aria-label": "Close",
|
|
4533
|
+
className: cn(
|
|
4534
|
+
"absolute top-6 right-5 z-20 flex h-6 w-6 items-center justify-center transition-colors",
|
|
4535
|
+
isCloseBlocked ? "text-muted-foreground/40 cursor-not-allowed" : "text-muted-foreground hover:text-foreground cursor-pointer"
|
|
4536
|
+
),
|
|
4259
4537
|
children: /* @__PURE__ */ jsxRuntime.jsx(CloseIcon, {})
|
|
4260
4538
|
}
|
|
4539
|
+
),
|
|
4540
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
4541
|
+
"div",
|
|
4542
|
+
{
|
|
4543
|
+
"data-privana-content": true,
|
|
4544
|
+
"data-view": currentView,
|
|
4545
|
+
className: "flex min-h-0 flex-1 flex-col gap-2",
|
|
4546
|
+
children: [
|
|
4547
|
+
/* @__PURE__ */ jsxRuntime.jsx(DialogTitle, { id: titleId, className: "sr-only", children: "Privana" }),
|
|
4548
|
+
/* @__PURE__ */ jsxRuntime.jsx(DialogDescription, { id: descId, className: "sr-only", children: "Deposit or withdraw tokens from your Privana" }),
|
|
4549
|
+
currentView === "main" && /* @__PURE__ */ jsxRuntime.jsx(DialogHeader, { children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center px-5 py-4", children: /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-foreground text-xl leading-6 font-medium", children: "Privana" }) }) }),
|
|
4550
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
4551
|
+
ModalBody,
|
|
4552
|
+
{
|
|
4553
|
+
onCloseBlockedChange: setIsCloseBlocked,
|
|
4554
|
+
onViewChange: setCurrentView,
|
|
4555
|
+
showLockedFunds,
|
|
4556
|
+
defaultTab,
|
|
4557
|
+
onDepositSuccess
|
|
4558
|
+
}
|
|
4559
|
+
)
|
|
4560
|
+
]
|
|
4561
|
+
}
|
|
4562
|
+
),
|
|
4563
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
4564
|
+
"a",
|
|
4565
|
+
{
|
|
4566
|
+
"data-privana-footer": true,
|
|
4567
|
+
href: "https://privana.finance",
|
|
4568
|
+
target: "_blank",
|
|
4569
|
+
rel: "noopener noreferrer",
|
|
4570
|
+
"aria-label": "Powered by Privana"
|
|
4571
|
+
}
|
|
4261
4572
|
)
|
|
4262
|
-
]
|
|
4263
|
-
|
|
4264
|
-
|
|
4265
|
-
ModalBody,
|
|
4266
|
-
{
|
|
4267
|
-
onClose: isTransactionPending ? void 0 : onClose,
|
|
4268
|
-
onTransactionPendingChange: setIsTransactionPending,
|
|
4269
|
-
showLockedFunds,
|
|
4270
|
-
defaultTab,
|
|
4271
|
-
onDepositSuccess
|
|
4272
|
-
}
|
|
4273
|
-
)
|
|
4274
|
-
]
|
|
4573
|
+
]
|
|
4574
|
+
}
|
|
4575
|
+
)
|
|
4275
4576
|
}
|
|
4276
|
-
)
|
|
4577
|
+
);
|
|
4277
4578
|
}
|
|
4278
4579
|
function PrivanaInlineModal({
|
|
4279
4580
|
className,
|
|
@@ -4374,6 +4675,7 @@ exports.PrivanaInlineModal = PrivanaInlineModal;
|
|
|
4374
4675
|
exports.PrivanaModal = PrivanaModal;
|
|
4375
4676
|
exports.PrivanaProvider = PrivanaProvider;
|
|
4376
4677
|
exports.SUPPORTED_CHAINS = SUPPORTED_CHAINS;
|
|
4678
|
+
exports.SiweAuthProvider = SiweAuthProvider;
|
|
4377
4679
|
exports.Skeleton = Skeleton;
|
|
4378
4680
|
exports.TRANSFER_LOCKED_TYPES = TRANSFER_LOCKED_TYPES;
|
|
4379
4681
|
exports.TRANSFER_TYPES = TRANSFER_TYPES;
|
|
@@ -4428,6 +4730,7 @@ exports.usePrivanaClient = usePrivanaClient;
|
|
|
4428
4730
|
exports.usePrivanaContext = usePrivanaContext;
|
|
4429
4731
|
exports.useSafeAccount = useSafeAccount;
|
|
4430
4732
|
exports.useSafePrivanaContext = useSafePrivanaContext;
|
|
4733
|
+
exports.useSiweAuth = useSiweAuth;
|
|
4431
4734
|
exports.useTokenInfo = useTokenInfo;
|
|
4432
4735
|
exports.useTokenList = useTokenList;
|
|
4433
4736
|
exports.useTotalLockedBalance = useTotalLockedBalance;
|