@oasisprotocol/privana-sdk 0.5.1 → 0.5.3

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.
@@ -1,10 +1,10 @@
1
1
  "use client";
2
2
  'use strict';
3
3
 
4
+ var siwe = require('viem/siwe');
4
5
  var react = require('react');
5
6
  var wagmi = require('wagmi');
6
7
  var actions = require('wagmi/actions');
7
- var siwe = require('viem/siwe');
8
8
  var jsxRuntime = require('react/jsx-runtime');
9
9
  var viem = require('viem');
10
10
  var reactQuery = require('@tanstack/react-query');
@@ -97,8 +97,11 @@ function getExplorerAddressUrl(chainId, address) {
97
97
  return `${chain.explorerUrl}/address/${address}#tokentxns`;
98
98
  }
99
99
 
100
+ // src/sdk/auth/auth-clock-skew.ts
101
+ var AUTH_CLOCK_SKEW_MS = 3e4;
102
+
100
103
  // src/sdk/auth/hosted-auth.ts
101
- var HOSTED_AUTH_CLOCK_SKEW_MS = 3e4;
104
+ var HOSTED_AUTH_CLOCK_SKEW_MS = AUTH_CLOCK_SKEW_MS;
102
105
  var PKCE_CHARSET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~";
103
106
  var DEFAULT_RANDOM_LENGTH = 64;
104
107
  var HOSTED_AUTH_CALLBACK_QUERY_KEYS = ["code", "error", "error_description", "state"];
@@ -220,11 +223,32 @@ function isHostedAuthSessionActive(session, now = Date.now(), skewMs = HOSTED_AU
220
223
  function isHostedAuthRefreshActive(session, now = Date.now(), skewMs = HOSTED_AUTH_CLOCK_SKEW_MS) {
221
224
  return session.refreshExpiresAt > now + skewMs;
222
225
  }
223
-
224
- // src/sdk/auth/siwe.ts
226
+ var SIWE_MESSAGE_VALIDITY_MS = 24 * 60 * 60 * 1e3;
225
227
  function buildSiweStatement(chainId) {
226
228
  return `Sign in to Privana on chain ${chainId}`;
227
229
  }
230
+ async function buildSiweLoginMessage(api, params) {
231
+ const { address, chainId, apiUrl } = params;
232
+ const [{ domain }, { nonce }] = await Promise.all([
233
+ api.getSiweDomain(),
234
+ api.getSiweNonce(address)
235
+ ]);
236
+ const issuedAt = /* @__PURE__ */ new Date();
237
+ const expirationTime = new Date(issuedAt.getTime() + SIWE_MESSAGE_VALIDITY_MS);
238
+ const uri = typeof window !== "undefined" && window.location.origin ? window.location.origin : apiUrl;
239
+ const message = siwe.createSiweMessage({
240
+ address,
241
+ chainId,
242
+ domain,
243
+ expirationTime,
244
+ issuedAt,
245
+ nonce,
246
+ statement: buildSiweStatement(chainId),
247
+ uri,
248
+ version: "1"
249
+ });
250
+ return { message, expirationTime };
251
+ }
228
252
 
229
253
  // src/sdk/client/errors.ts
230
254
  var AccountingApiError = class _AccountingApiError extends Error {
@@ -349,9 +373,10 @@ var HttpClient = class {
349
373
  var PRIVATE_READ_TOKEN_HEADER = "X-SIWE-Token";
350
374
  var MAX_BATCH_BALANCE_TOKEN_IDS = 100;
351
375
  var MAX_HISTORY_PAGE_SIZE = 100;
352
- var PrivanaClient = class {
376
+ var PrivanaClient = class _PrivanaClient {
353
377
  constructor(config) {
354
378
  this.http = new HttpClient(config);
379
+ this.baseConfig = { ...config, headers: { ...config.headers } };
355
380
  }
356
381
  getBaseUrl() {
357
382
  return this.http.getBaseUrl();
@@ -621,10 +646,23 @@ var PrivanaClient = class {
621
646
  async getPendingOnRamps() {
622
647
  return this.http.get("/v1/accounting/onramp/pending");
623
648
  }
649
+ /**
650
+ * @deprecated This mutates the shared client's headers and displaces its Authorization
651
+ * (JWT bearer) header, which breaks concurrent JWT-authenticated requests. Use
652
+ * {@link PrivanaClient.withPrivateReadToken} instead, which returns a scoped client that
653
+ * authenticates the private read without touching the shared client's headers. Will be
654
+ * removed in a future release.
655
+ */
624
656
  setPrivateReadToken(token) {
625
657
  this.http.removeHeader("Authorization");
626
658
  this.http.setHeader(PRIVATE_READ_TOKEN_HEADER, token);
627
659
  }
660
+ /**
661
+ * @deprecated The shared client no longer carries a private-read token between requests;
662
+ * private reads now run on a scoped client from {@link PrivanaClient.withPrivateReadToken}.
663
+ * Returns whatever X-SIWE-Token is currently set on the shared client's headers. Will be
664
+ * removed in a future release.
665
+ */
628
666
  getPrivateReadToken() {
629
667
  return this.http.getHeader(PRIVATE_READ_TOKEN_HEADER);
630
668
  }
@@ -638,6 +676,26 @@ var PrivanaClient = class {
638
676
  clearBearerToken() {
639
677
  this.http.removeHeader("Authorization");
640
678
  }
679
+ /**
680
+ * Returns a scoped client whose requests authenticate with the given SIWE private-read
681
+ * token (X-SIWE-Token) instead of the client-wide JWT bearer. The scoped client shares
682
+ * baseUrl/timeout/base headers but owns a separate header set, so private reads issued
683
+ * through it never displace the real client's Authorization header: concurrent
684
+ * JWT-authenticated requests on the real client keep their bearer, and a bearer installed
685
+ * mid-flight (auto-login/hydration) is never erased by a private read.
686
+ */
687
+ withPrivateReadToken(token) {
688
+ const headers = {};
689
+ for (const [name, value] of Object.entries(this.baseConfig.headers ?? {})) {
690
+ const lower = name.toLowerCase();
691
+ if (lower === "authorization" || lower === PRIVATE_READ_TOKEN_HEADER.toLowerCase()) {
692
+ continue;
693
+ }
694
+ headers[name] = value;
695
+ }
696
+ headers[PRIVATE_READ_TOKEN_HEADER] = token;
697
+ return new _PrivanaClient({ ...this.baseConfig, headers });
698
+ }
641
699
  };
642
700
 
643
701
  // src/sdk/signatures/eip712-types.ts
@@ -867,8 +925,421 @@ function useSafeAccount() {
867
925
  return react.useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);
868
926
  }
869
927
 
928
+ // src/sdk/auth/siwe-persistence.ts
929
+ var PERSISTED_SIWE_AUTH_RECORD_VERSION = 2;
930
+ var warnedBlockedStorage = false;
931
+ function warnBlockedStorageOnce() {
932
+ if (warnedBlockedStorage) return;
933
+ warnedBlockedStorage = true;
934
+ if (typeof console !== "undefined" && typeof console.warn === "function") {
935
+ console.warn(
936
+ "Privana SIWE auth: localStorage is unavailable or blocked; sessions will stay in memory only."
937
+ );
938
+ }
939
+ }
940
+ function createSiweAuthStorageKey(apiUrl, chainId) {
941
+ const normalizedApiUrl = apiUrl.replace(/\/$/, "");
942
+ return ["privana", "siwe-auth", normalizedApiUrl, String(chainId)].join(":");
943
+ }
944
+ function buildPersistedSiweAuthRecordFromLogin(response, now, siweTokenExpiresAt) {
945
+ return {
946
+ version: PERSISTED_SIWE_AUTH_RECORD_VERSION,
947
+ tokens: {
948
+ siwe_token: response.siwe_token,
949
+ jwt_access_token: response.jwt_access_token,
950
+ jwt_refresh_token: response.jwt_refresh_token,
951
+ address: response.address
952
+ },
953
+ accessTokenExpiresAt: now + response.jwt_expires_in * 1e3,
954
+ refreshTokenExpiresAt: now + response.jwt_refresh_expires_in * 1e3,
955
+ siweTokenExpiresAt,
956
+ updatedAt: now
957
+ };
958
+ }
959
+ function applyRefreshToPersistedSiweAuthRecord(previous, response, now = Date.now()) {
960
+ return {
961
+ ...previous,
962
+ tokens: {
963
+ ...previous.tokens,
964
+ jwt_access_token: response.token,
965
+ jwt_refresh_token: response.refresh_token
966
+ },
967
+ accessTokenExpiresAt: now + response.expires_in * 1e3,
968
+ refreshTokenExpiresAt: now + response.refresh_expires_in * 1e3,
969
+ updatedAt: now
970
+ };
971
+ }
972
+ function isPersistedSiweAuthRecord(value) {
973
+ if (typeof value !== "object" || value === null) return false;
974
+ const record = value;
975
+ if (record.version !== PERSISTED_SIWE_AUTH_RECORD_VERSION) return false;
976
+ if (typeof record.accessTokenExpiresAt !== "number") return false;
977
+ if (typeof record.refreshTokenExpiresAt !== "number") return false;
978
+ if (typeof record.siweTokenExpiresAt !== "number") return false;
979
+ if (typeof record.updatedAt !== "number") return false;
980
+ const tokens = record.tokens;
981
+ if (typeof tokens !== "object" || tokens === null) return false;
982
+ const tokenFields = tokens;
983
+ return typeof tokenFields.siwe_token === "string" && typeof tokenFields.jwt_access_token === "string" && typeof tokenFields.jwt_refresh_token === "string" && typeof tokenFields.address === "string" && tokenFields.address.length > 0;
984
+ }
985
+ function isPersistedSiweAuthAccessActive(record, now = Date.now()) {
986
+ return record.accessTokenExpiresAt > now + AUTH_CLOCK_SKEW_MS;
987
+ }
988
+ function isPersistedSiweAuthRefreshActive(record, now = Date.now()) {
989
+ return record.refreshTokenExpiresAt > now + AUTH_CLOCK_SKEW_MS;
990
+ }
991
+ function parsePersistedSiweAuthRecord(raw, now = Date.now()) {
992
+ let parsed;
993
+ try {
994
+ parsed = JSON.parse(raw);
995
+ } catch {
996
+ return null;
997
+ }
998
+ if (!isPersistedSiweAuthRecord(parsed)) return null;
999
+ if (!isPersistedSiweAuthRefreshActive(parsed, now)) return null;
1000
+ return parsed;
1001
+ }
1002
+ function readPersistedSiweAuth(storage, key, now = Date.now()) {
1003
+ let raw;
1004
+ try {
1005
+ raw = storage.getItem(key);
1006
+ } catch {
1007
+ return null;
1008
+ }
1009
+ if (raw == null) return null;
1010
+ const record = parsePersistedSiweAuthRecord(raw, now);
1011
+ if (!record) removePersistedSiweAuth(storage, key);
1012
+ return record;
1013
+ }
1014
+ function writePersistedSiweAuth(storage, key, record) {
1015
+ try {
1016
+ storage.setItem(key, JSON.stringify(record));
1017
+ } catch {
1018
+ warnBlockedStorageOnce();
1019
+ }
1020
+ }
1021
+ function removePersistedSiweAuth(storage, key) {
1022
+ try {
1023
+ storage.removeItem(key);
1024
+ } catch {
1025
+ }
1026
+ }
1027
+ var localStorageProbe;
1028
+ function getSiweAuthLocalStorage() {
1029
+ if (localStorageProbe !== void 0) return localStorageProbe;
1030
+ if (typeof window === "undefined") return null;
1031
+ try {
1032
+ const storage = window.localStorage;
1033
+ storage.getItem("__privana_siwe_probe__");
1034
+ localStorageProbe = storage;
1035
+ } catch {
1036
+ warnBlockedStorageOnce();
1037
+ localStorageProbe = null;
1038
+ }
1039
+ return localStorageProbe;
1040
+ }
1041
+ function isAdoptableRecord(record, currentAddress, currentUpdatedAt) {
1042
+ if (!record || !currentAddress) return false;
1043
+ if (record.tokens.address.toLowerCase() !== currentAddress.toLowerCase()) return false;
1044
+ return record.updatedAt > (currentUpdatedAt ?? -1);
1045
+ }
1046
+ function resolveHydrationAction(record, connectedAddress, now = Date.now()) {
1047
+ if (!connectedAddress) return { type: "dormant" };
1048
+ if (!record) return { type: "dormant" };
1049
+ if (record.tokens.address.toLowerCase() !== connectedAddress.toLowerCase()) {
1050
+ return { type: "remove" };
1051
+ }
1052
+ if (isPersistedSiweAuthAccessActive(record, now)) return { type: "restore", record };
1053
+ if (isPersistedSiweAuthRefreshActive(record, now)) return { type: "refresh", record };
1054
+ return { type: "remove" };
1055
+ }
1056
+ function resolveStorageEvent(newValue, currentAddress, currentUpdatedAt, now = Date.now()) {
1057
+ if (!currentAddress) return { type: "ignore" };
1058
+ if (newValue == null) return { type: "logout" };
1059
+ const record = parsePersistedSiweAuthRecord(newValue, now);
1060
+ if (!record) return { type: "ignore" };
1061
+ if (!isAdoptableRecord(record, currentAddress, currentUpdatedAt)) return { type: "ignore" };
1062
+ return { type: "adopt", record };
1063
+ }
1064
+
1065
+ // src/sdk/auth/siwe-auth-policy.ts
1066
+ function resolveActiveSessionAddress(...candidates) {
1067
+ return candidates.find((candidate) => !!candidate) ?? null;
1068
+ }
1069
+ function resolveAutoLoginEffectAction(input) {
1070
+ const {
1071
+ status,
1072
+ isConnected,
1073
+ address,
1074
+ sessionAddress,
1075
+ autoLogin,
1076
+ isLoading,
1077
+ isHydrating,
1078
+ autoAttemptedAddress,
1079
+ authenticatingAddress
1080
+ } = input;
1081
+ const activeAddress2 = resolveActiveSessionAddress(sessionAddress, authenticatingAddress);
1082
+ if (status === "connecting" || status === "reconnecting") return { type: "wait" };
1083
+ if (!isConnected && activeAddress2) return { type: "reset-on-disconnect" };
1084
+ if (isConnected && address && activeAddress2 && address.toLowerCase() !== activeAddress2.toLowerCase()) {
1085
+ return { type: "clear-on-mismatch" };
1086
+ }
1087
+ if (!autoLogin) return { type: "noop" };
1088
+ if (isHydrating) return { type: "wait" };
1089
+ if (isConnected && address && !sessionAddress && !isLoading && autoAttemptedAddress !== address) {
1090
+ return { type: "auto-login" };
1091
+ }
1092
+ return { type: "noop" };
1093
+ }
1094
+
1095
+ // src/sdk/auth/auth-lifecycle.ts
1096
+ function initialAuthLifecycleState() {
1097
+ return {
1098
+ generation: 0,
1099
+ refreshData: null,
1100
+ currentRecord: null,
1101
+ autoAttemptedAddress: null,
1102
+ authenticatingAddress: null,
1103
+ hydratedAddress: null
1104
+ };
1105
+ }
1106
+ function reduceAuthLifecycle(state, event) {
1107
+ switch (event.type) {
1108
+ case "reset":
1109
+ return {
1110
+ generation: state.generation + 1,
1111
+ refreshData: null,
1112
+ currentRecord: null,
1113
+ autoAttemptedAddress: null,
1114
+ authenticatingAddress: null,
1115
+ hydratedAddress: null
1116
+ };
1117
+ case "commit":
1118
+ return {
1119
+ ...state,
1120
+ generation: state.generation + 1,
1121
+ refreshData: {
1122
+ refreshToken: event.record.tokens.jwt_refresh_token,
1123
+ refreshExpiresAt: event.record.refreshTokenExpiresAt
1124
+ },
1125
+ currentRecord: event.record
1126
+ };
1127
+ case "refresh":
1128
+ return {
1129
+ ...state,
1130
+ refreshData: event.refreshData,
1131
+ currentRecord: event.record
1132
+ };
1133
+ case "setAutoAttemptedAddress":
1134
+ return { ...state, autoAttemptedAddress: event.address };
1135
+ case "setAuthenticatingAddress":
1136
+ return { ...state, authenticatingAddress: event.address };
1137
+ case "setHydratedAddress":
1138
+ return { ...state, hydratedAddress: event.address };
1139
+ case "clearCurrentRecord":
1140
+ return { ...state, currentRecord: null };
1141
+ }
1142
+ }
1143
+
1144
+ // src/sdk/auth/auth-lifecycle-effects.ts
1145
+ function armPrivateRead(ports, input) {
1146
+ ports.cache.set(input.scopeKey, input.siweToken, input.siweExpiry);
1147
+ }
1148
+ function applyAccessRotation(ports, rotation) {
1149
+ ports.client.setBearerToken(rotation.accessToken);
1150
+ ports.react.setTokens(
1151
+ (prev) => prev ? {
1152
+ ...prev,
1153
+ jwt_access_token: rotation.accessToken,
1154
+ jwt_refresh_token: rotation.refreshToken
1155
+ } : prev
1156
+ );
1157
+ ports.react.setAccessTokenExpiresAt(rotation.accessTokenExpiresAt);
1158
+ }
1159
+ function publishSession(ports, record) {
1160
+ ports.client.setBearerToken(record.tokens.jwt_access_token);
1161
+ ports.react.setSession({ address: record.tokens.address });
1162
+ ports.react.setTokens({
1163
+ siwe_token: record.tokens.siwe_token,
1164
+ jwt_access_token: record.tokens.jwt_access_token,
1165
+ jwt_refresh_token: record.tokens.jwt_refresh_token,
1166
+ address: record.tokens.address
1167
+ });
1168
+ ports.react.setAccessTokenExpiresAt(record.accessTokenExpiresAt);
1169
+ }
1170
+ function persistRecordIfEnabled(ports, record) {
1171
+ if (!ports.persistJwt) return;
1172
+ if (record) ports.storage.write(record);
1173
+ else ports.storage.remove();
1174
+ }
1175
+ function clearSessionEffects(ports) {
1176
+ ports.client.clearPrivateReadToken();
1177
+ ports.client.clearBearerToken();
1178
+ ports.react.setSession(null);
1179
+ ports.react.setTokens(null);
1180
+ ports.react.setAccessTokenExpiresAt(null);
1181
+ ports.react.setIsLoading(false);
1182
+ ports.react.setIsHydrating(false);
1183
+ ports.react.setError(null);
1184
+ }
1185
+
1186
+ // src/sdk/auth/siwe-auth-controller.ts
1187
+ function activeAddress(ctrl) {
1188
+ return resolveActiveSessionAddress(
1189
+ ctrl.getSessionAddress(),
1190
+ ctrl.getState().currentRecord?.tokens.address
1191
+ );
1192
+ }
1193
+ function ctrlReset(ctrl, removeStorage) {
1194
+ ctrl.loginInFlight = false;
1195
+ ctrl.loginOwnerGeneration = -1;
1196
+ ctrl.hydrateOwnerGeneration = -1;
1197
+ ctrl.refreshPromise = null;
1198
+ ctrl.dispatch({ type: "reset" });
1199
+ clearSessionEffects(ctrl.ports);
1200
+ if (removeStorage) persistRecordIfEnabled(ctrl.ports, null);
1201
+ }
1202
+ function ctrlRestoreSession(ctrl, record) {
1203
+ ctrl.dispatch({ type: "commit", record });
1204
+ armPrivateRead(ctrl.ports, {
1205
+ siweToken: record.tokens.siwe_token,
1206
+ siweExpiry: record.siweTokenExpiresAt,
1207
+ scopeKey: ctrl.ports.makeScopeKey(record.tokens.address)
1208
+ });
1209
+ publishSession(ctrl.ports, record);
1210
+ }
1211
+ function ctrlAdoptNewerRecordIfAvailable(ctrl) {
1212
+ if (!ctrl.config.persistJwt) return false;
1213
+ const record = ctrl.ports.storage.read();
1214
+ if (!record) return false;
1215
+ const current = activeAddress(ctrl);
1216
+ if (!isAdoptableRecord(record, current, ctrl.getState().currentRecord?.updatedAt ?? null)) {
1217
+ return false;
1218
+ }
1219
+ ctrlRestoreSession(ctrl, record);
1220
+ return true;
1221
+ }
1222
+ async function ctrlLogin(ctrl) {
1223
+ const address = ctrl.config.address;
1224
+ if (!address) throw new Error("No wallet connected");
1225
+ if (ctrl.loginInFlight) return;
1226
+ ctrl.loginInFlight = true;
1227
+ ctrl.dispatch({ type: "setAutoAttemptedAddress", address });
1228
+ ctrl.dispatch({ type: "setAuthenticatingAddress", address });
1229
+ ctrl.ports.react.setIsLoading(true);
1230
+ ctrl.ports.react.setError(null);
1231
+ const generation = ctrl.getState().generation;
1232
+ ctrl.loginOwnerGeneration = generation;
1233
+ const { chainId, apiUrl } = ctrl.config;
1234
+ const api = ctrl.api;
1235
+ try {
1236
+ const { message, expirationTime } = await buildSiweLoginMessage(api, {
1237
+ address,
1238
+ chainId,
1239
+ apiUrl
1240
+ });
1241
+ const signature = await ctrl.signer.signSiweMessage({ account: address, message });
1242
+ const res = await api.loginWithSiwe({ siwe_message: message, signature });
1243
+ if (ctrl.getState().generation !== generation) return;
1244
+ const record = buildPersistedSiweAuthRecordFromLogin(res, Date.now(), expirationTime.getTime());
1245
+ ctrlRestoreSession(ctrl, record);
1246
+ persistRecordIfEnabled(ctrl.ports, record);
1247
+ } catch (err) {
1248
+ if (ctrl.loginOwnerGeneration === generation) {
1249
+ ctrl.ports.react.setError(err instanceof Error ? err : new Error("Sign-in failed"));
1250
+ }
1251
+ throw err;
1252
+ } finally {
1253
+ if (ctrl.loginOwnerGeneration === generation) {
1254
+ ctrl.ports.react.setIsLoading(false);
1255
+ ctrl.loginInFlight = false;
1256
+ ctrl.loginOwnerGeneration = -1;
1257
+ }
1258
+ }
1259
+ }
1260
+ async function ctrlLogout(ctrl) {
1261
+ const refreshToken = ctrl.getState().refreshData?.refreshToken;
1262
+ try {
1263
+ if (refreshToken) {
1264
+ try {
1265
+ await ctrl.api.logoutJwtSession({ refresh_token: refreshToken, revoke_all: true });
1266
+ } catch {
1267
+ }
1268
+ }
1269
+ } finally {
1270
+ ctrlReset(ctrl, ctrl.config.persistJwt);
1271
+ ctrl.dispatch({ type: "setAutoAttemptedAddress", address: ctrl.config.address ?? null });
1272
+ }
1273
+ }
1274
+ async function ctrlRefreshAccessToken(ctrl) {
1275
+ const data = ctrl.getState().refreshData;
1276
+ if (!data) return;
1277
+ if (Date.now() >= data.refreshExpiresAt - AUTH_CLOCK_SKEW_MS) {
1278
+ ctrlReset(ctrl, ctrl.config.persistJwt);
1279
+ return;
1280
+ }
1281
+ if (ctrl.refreshPromise) return ctrl.refreshPromise;
1282
+ const generation = ctrl.getState().generation;
1283
+ const promise = (async () => {
1284
+ try {
1285
+ if (ctrlAdoptNewerRecordIfAvailable(ctrl)) return;
1286
+ const res = await ctrl.api.refreshJwtSession({ refresh_token: data.refreshToken });
1287
+ if (ctrl.getState().generation !== generation) return;
1288
+ const refreshedAt = Date.now();
1289
+ const refreshData = {
1290
+ refreshToken: res.refresh_token,
1291
+ refreshExpiresAt: refreshedAt + res.refresh_expires_in * 1e3
1292
+ };
1293
+ const previous = ctrl.getState().currentRecord;
1294
+ if (previous && ctrl.config.persistJwt) {
1295
+ const nextRecord = applyRefreshToPersistedSiweAuthRecord(previous, res, refreshedAt);
1296
+ ctrl.dispatch({ type: "refresh", refreshData, record: nextRecord });
1297
+ ctrl.ports.storage.write(nextRecord);
1298
+ } else {
1299
+ ctrl.dispatch({ type: "refresh", refreshData, record: previous });
1300
+ }
1301
+ applyAccessRotation(ctrl.ports, {
1302
+ accessToken: res.token,
1303
+ refreshToken: res.refresh_token,
1304
+ accessTokenExpiresAt: refreshedAt + res.expires_in * 1e3
1305
+ });
1306
+ } catch {
1307
+ if (ctrl.getState().generation !== generation) return;
1308
+ if (!ctrlAdoptNewerRecordIfAvailable(ctrl)) ctrlReset(ctrl, ctrl.config.persistJwt);
1309
+ }
1310
+ })();
1311
+ ctrl.refreshPromise = promise;
1312
+ try {
1313
+ await promise;
1314
+ } finally {
1315
+ if (ctrl.refreshPromise === promise) ctrl.refreshPromise = null;
1316
+ }
1317
+ }
1318
+ async function ctrlHydrateViaRefresh(ctrl, record, address) {
1319
+ ctrl.dispatch({ type: "setAutoAttemptedAddress", address });
1320
+ ctrl.dispatch({ type: "setAuthenticatingAddress", address });
1321
+ ctrl.ports.react.setIsHydrating(true);
1322
+ ctrl.dispatch({ type: "commit", record });
1323
+ const hydrationGen = ctrl.getState().generation;
1324
+ ctrl.hydrateOwnerGeneration = hydrationGen;
1325
+ try {
1326
+ await ctrlRefreshAccessToken(ctrl).catch(() => {
1327
+ });
1328
+ const fresh = ctrl.getState().currentRecord;
1329
+ if (fresh && fresh.tokens.address.toLowerCase() === address.toLowerCase() && isPersistedSiweAuthAccessActive(fresh)) {
1330
+ ctrlRestoreSession(ctrl, fresh);
1331
+ } else if (ctrl.getState().generation === hydrationGen) {
1332
+ ctrl.dispatch({ type: "setAutoAttemptedAddress", address: null });
1333
+ }
1334
+ } finally {
1335
+ if (ctrl.hydrateOwnerGeneration === hydrationGen) {
1336
+ ctrl.ports.react.setIsHydrating(false);
1337
+ ctrl.hydrateOwnerGeneration = -1;
1338
+ }
1339
+ }
1340
+ }
1341
+
870
1342
  // src/sdk/hooks/private-read-token-store.ts
871
- var AUTH_CLOCK_SKEW_MS = 3e4;
872
1343
  var cache = /* @__PURE__ */ new Map();
873
1344
  function createScopeKey(apiUrl, chainId, address) {
874
1345
  return `${apiUrl.replace(/\/$/, "")}:${chainId}:${address.toLowerCase()}`;
@@ -888,164 +1359,240 @@ function setCachedPrivateReadToken(scopeKey, token, expiresAt) {
888
1359
  function deleteCachedPrivateReadToken(scopeKey) {
889
1360
  cache.delete(scopeKey);
890
1361
  }
891
- var DEFAULT_SIWE_VALIDITY_MS = 24 * 60 * 60 * 1e3;
892
- var AUTH_REFRESH_SKEW_MS = 3e4;
893
- var SiweAuthContext = react.createContext(null);
894
- function SiweAuthProvider({
895
- children,
896
- client,
897
- networkConfig,
898
- autoLogin = true
899
- }) {
1362
+
1363
+ // src/sdk/hooks/use-siwe-auth-lifecycle.ts
1364
+ function useSiweAuthLifecycle(deps) {
1365
+ const { storageKey, apiUrl, chainId, persistJwt, autoLogin, client, api } = deps;
900
1366
  const wagmiContext = react.useContext(wagmi.WagmiContext);
901
1367
  const { address, isConnected, status } = useSafeAccount();
902
1368
  const [session, setSession] = react.useState(null);
903
1369
  const [tokens, setTokens] = react.useState(null);
904
1370
  const [isLoading, setIsLoading] = react.useState(false);
1371
+ const [isHydrating, setIsHydrating] = react.useState(false);
905
1372
  const [error, setError] = react.useState(null);
906
1373
  const [accessTokenExpiresAt, setAccessTokenExpiresAt] = react.useState(null);
907
- const loginInFlight = react.useRef(false);
908
- const autoAttemptedAddress = react.useRef(null);
909
- const refreshInFlight = react.useRef(false);
910
- const refreshDataRef = react.useRef(null);
911
- const clearSession = react.useCallback(() => {
912
- refreshDataRef.current = null;
913
- setAccessTokenExpiresAt(null);
914
- client.clearPrivateReadToken();
915
- client.clearBearerToken();
916
- setSession(null);
917
- setTokens(null);
918
- setError(null);
919
- autoAttemptedAddress.current = null;
920
- }, [client]);
921
- const logout = react.useCallback(async () => {
922
- setError(null);
923
- const refreshToken = refreshDataRef.current?.refreshToken;
924
- try {
925
- if (refreshToken) {
926
- await client.logoutJwtSession({ refresh_token: refreshToken });
1374
+ const sessionRef = react.useRef(null);
1375
+ const prevPersistJwtRef = react.useRef(void 0);
1376
+ const authRef = react.useRef(initialAuthLifecycleState());
1377
+ const storageAdapter = react.useMemo(
1378
+ () => ({
1379
+ read: () => {
1380
+ const ls = getSiweAuthLocalStorage();
1381
+ return ls ? readPersistedSiweAuth(ls, storageKey) : null;
1382
+ },
1383
+ write: (record) => {
1384
+ const ls = getSiweAuthLocalStorage();
1385
+ if (ls) writePersistedSiweAuth(ls, storageKey, record);
1386
+ },
1387
+ remove: () => {
1388
+ const ls = getSiweAuthLocalStorage();
1389
+ if (ls) removePersistedSiweAuth(ls, storageKey);
927
1390
  }
928
- } finally {
929
- clearSession();
930
- autoAttemptedAddress.current = address ?? null;
931
- }
932
- }, [clearSession, client, address]);
933
- const login = react.useCallback(async () => {
934
- if (!wagmiContext) throw new Error("WagmiProvider is required for SIWE auth");
935
- if (!address) throw new Error("No wallet connected");
936
- if (loginInFlight.current) return;
937
- loginInFlight.current = true;
938
- setIsLoading(true);
939
- setError(null);
940
- try {
941
- const walletClient = await actions.getWalletClient(wagmiContext);
942
- if (!walletClient) throw new Error("No wallet client available");
943
- const [{ domain }, nonceRes] = await Promise.all([
944
- client.getSiweDomain(),
945
- client.getSiweNonce(address)
946
- ]);
947
- const issuedAt = /* @__PURE__ */ new Date();
948
- const expirationTime = new Date(issuedAt.getTime() + DEFAULT_SIWE_VALIDITY_MS);
949
- const uri = typeof window !== "undefined" && window.location.origin ? window.location.origin : networkConfig.apiUrl;
950
- const message = siwe.createSiweMessage({
951
- address,
952
- chainId: networkConfig.chainId,
953
- domain,
954
- uri,
955
- version: "1",
956
- nonce: nonceRes.nonce,
957
- statement: buildSiweStatement(networkConfig.chainId),
958
- issuedAt,
959
- expirationTime
960
- });
961
- const signature = await walletClient.signMessage({
962
- account: walletClient.account ?? address,
963
- message
964
- });
965
- const res = await client.loginWithSiwe({ siwe_message: message, signature });
966
- const loggedInAt = Date.now();
967
- client.setPrivateReadToken(res.siwe_token);
968
- client.setBearerToken(res.jwt_access_token);
969
- refreshDataRef.current = {
970
- refreshToken: res.jwt_refresh_token,
971
- refreshExpiresAt: loggedInAt + res.jwt_refresh_expires_in * 1e3
972
- };
973
- setCachedPrivateReadToken(
974
- createScopeKey(networkConfig.apiUrl, networkConfig.chainId, address),
975
- res.siwe_token,
976
- expirationTime.getTime()
977
- );
978
- setSession({ address: res.address });
979
- setTokens({
980
- siwe_token: res.siwe_token,
981
- jwt_access_token: res.jwt_access_token,
982
- jwt_refresh_token: res.jwt_refresh_token,
983
- address: res.address
984
- });
985
- setAccessTokenExpiresAt(loggedInAt + res.jwt_expires_in * 1e3);
986
- } catch (err) {
987
- setError(err instanceof Error ? err : new Error("Sign-in failed"));
988
- throw err;
989
- } finally {
990
- setIsLoading(false);
991
- loginInFlight.current = false;
992
- }
993
- }, [wagmiContext, address, client, networkConfig.chainId, networkConfig.apiUrl]);
994
- const refreshAccessToken = react.useCallback(async () => {
995
- const data = refreshDataRef.current;
996
- if (!data || refreshInFlight.current) return;
997
- if (Date.now() >= data.refreshExpiresAt - AUTH_REFRESH_SKEW_MS) {
998
- clearSession();
999
- return;
1000
- }
1001
- refreshInFlight.current = true;
1002
- try {
1003
- const res = await client.refreshJwtSession({ refresh_token: data.refreshToken });
1004
- const refreshedAt = Date.now();
1005
- client.setBearerToken(res.token);
1006
- refreshDataRef.current = {
1007
- refreshToken: res.refresh_token,
1008
- refreshExpiresAt: refreshedAt + res.refresh_expires_in * 1e3
1009
- };
1010
- setTokens(
1011
- (prev) => prev ? { ...prev, jwt_access_token: res.token, jwt_refresh_token: res.refresh_token } : prev
1012
- );
1013
- setAccessTokenExpiresAt(refreshedAt + res.expires_in * 1e3);
1014
- } catch {
1015
- clearSession();
1016
- } finally {
1017
- refreshInFlight.current = false;
1018
- }
1019
- }, [client, clearSession]);
1391
+ }),
1392
+ [storageKey]
1393
+ );
1394
+ const scopeInputsRef = react.useRef({ apiUrl, chainId });
1395
+ scopeInputsRef.current.apiUrl = apiUrl;
1396
+ scopeInputsRef.current.chainId = chainId;
1397
+ const wagmiContextRef = react.useRef(wagmiContext);
1398
+ wagmiContextRef.current = wagmiContext;
1399
+ const portsRef = react.useRef(null);
1400
+ if (!portsRef.current) {
1401
+ portsRef.current = {
1402
+ persistJwt,
1403
+ client,
1404
+ storage: storageAdapter,
1405
+ cache: { set: setCachedPrivateReadToken },
1406
+ react: {
1407
+ setSession,
1408
+ setTokens,
1409
+ setAccessTokenExpiresAt,
1410
+ setIsLoading,
1411
+ setIsHydrating,
1412
+ setError
1413
+ },
1414
+ makeScopeKey: (addr) => createScopeKey(scopeInputsRef.current.apiUrl, scopeInputsRef.current.chainId, addr)
1415
+ };
1416
+ }
1417
+ const ports = portsRef.current;
1418
+ ports.persistJwt = persistJwt;
1419
+ ports.client = client;
1420
+ ports.storage = storageAdapter;
1421
+ const ctrlRef = react.useRef(null);
1422
+ if (!ctrlRef.current) {
1423
+ ctrlRef.current = {
1424
+ config: { address, chainId, apiUrl, persistJwt },
1425
+ ports,
1426
+ api,
1427
+ signer: {
1428
+ signSiweMessage: async ({ account, message }) => {
1429
+ const ctx = wagmiContextRef.current;
1430
+ if (!ctx) throw new Error("WagmiProvider is required for SIWE auth");
1431
+ const walletClient = await actions.getWalletClient(ctx);
1432
+ if (!walletClient) throw new Error("No wallet client available");
1433
+ return walletClient.signMessage({
1434
+ account: walletClient.account ?? account,
1435
+ message
1436
+ });
1437
+ }
1438
+ },
1439
+ loginInFlight: false,
1440
+ loginOwnerGeneration: -1,
1441
+ hydrateOwnerGeneration: -1,
1442
+ refreshPromise: null,
1443
+ getState: () => authRef.current,
1444
+ getSessionAddress: () => sessionRef.current?.address ?? null,
1445
+ dispatch: (event) => {
1446
+ authRef.current = reduceAuthLifecycle(authRef.current, event);
1447
+ }
1448
+ };
1449
+ }
1450
+ const ctrl = ctrlRef.current;
1451
+ ctrl.config = { address, chainId, apiUrl, persistJwt };
1452
+ ctrl.api = api;
1453
+ const login = react.useCallback(() => ctrlLogin(ctrl), [ctrl]);
1454
+ const logout = react.useCallback(() => ctrlLogout(ctrl), [ctrl]);
1455
+ const refreshAccessToken = react.useCallback(() => ctrlRefreshAccessToken(ctrl), [ctrl]);
1456
+ const resetSession = react.useCallback(() => ctrlReset(ctrl, false), [ctrl]);
1457
+ const clearSession = react.useCallback(() => ctrlReset(ctrl, ctrl.config.persistJwt), [ctrl]);
1458
+ const restoreSession = react.useCallback(
1459
+ (record) => ctrlRestoreSession(ctrl, record),
1460
+ [ctrl]
1461
+ );
1462
+ const hydrateViaRefresh = react.useCallback(
1463
+ (record, addr) => ctrlHydrateViaRefresh(ctrl, record, addr),
1464
+ [ctrl]
1465
+ );
1020
1466
  react.useEffect(() => {
1021
1467
  if (accessTokenExpiresAt == null) return;
1022
- const delay = Math.max(accessTokenExpiresAt - AUTH_REFRESH_SKEW_MS - Date.now(), 0);
1468
+ const delay = Math.max(accessTokenExpiresAt - AUTH_CLOCK_SKEW_MS - Date.now(), 0);
1023
1469
  const timer = setTimeout(() => {
1024
1470
  void refreshAccessToken();
1025
1471
  }, delay);
1026
1472
  return () => clearTimeout(timer);
1027
1473
  }, [accessTokenExpiresAt, refreshAccessToken]);
1474
+ const scopeRef = react.useRef(void 0);
1028
1475
  react.useEffect(() => {
1029
- if (!autoLogin) return;
1476
+ const prev = scopeRef.current;
1477
+ scopeRef.current = { apiUrl, chainId, client };
1478
+ const scopeChanged = !!prev && !(prev.apiUrl === apiUrl && prev.chainId === chainId && prev.client === client);
1479
+ if (scopeChanged) {
1480
+ ctrlReset(ctrl, false);
1481
+ }
1482
+ if (!persistJwt) return;
1030
1483
  if (status === "connecting" || status === "reconnecting") return;
1031
- if (!isConnected && session) {
1032
- clearSession();
1033
- return;
1484
+ if (!isConnected || !address) return;
1485
+ if (ctrl.getState().hydratedAddress === address) return;
1486
+ ctrl.dispatch({ type: "setHydratedAddress", address });
1487
+ const action = resolveHydrationAction(storageAdapter.read(), address);
1488
+ switch (action.type) {
1489
+ case "restore":
1490
+ restoreSession(action.record);
1491
+ ctrl.dispatch({ type: "setAutoAttemptedAddress", address });
1492
+ break;
1493
+ case "refresh":
1494
+ void hydrateViaRefresh(action.record, address);
1495
+ break;
1496
+ case "remove":
1497
+ storageAdapter.remove();
1498
+ break;
1034
1499
  }
1035
- if (isConnected && address && session && address.toLowerCase() !== session.address.toLowerCase()) {
1036
- clearSession();
1037
- return;
1500
+ }, [
1501
+ apiUrl,
1502
+ chainId,
1503
+ client,
1504
+ persistJwt,
1505
+ status,
1506
+ isConnected,
1507
+ address,
1508
+ storageAdapter,
1509
+ ctrl,
1510
+ restoreSession,
1511
+ hydrateViaRefresh
1512
+ ]);
1513
+ react.useEffect(() => {
1514
+ const action = resolveAutoLoginEffectAction({
1515
+ status,
1516
+ isConnected,
1517
+ address: address ?? null,
1518
+ sessionAddress: session?.address ?? null,
1519
+ autoLogin,
1520
+ isLoading,
1521
+ isHydrating,
1522
+ autoAttemptedAddress: ctrl.getState().autoAttemptedAddress,
1523
+ authenticatingAddress: ctrl.getState().authenticatingAddress
1524
+ });
1525
+ switch (action.type) {
1526
+ case "wait":
1527
+ case "noop":
1528
+ return;
1529
+ case "reset-on-disconnect":
1530
+ resetSession();
1531
+ return;
1532
+ case "clear-on-mismatch":
1533
+ clearSession();
1534
+ return;
1535
+ case "auto-login":
1536
+ void login().catch(() => {
1537
+ });
1538
+ return;
1038
1539
  }
1039
- if (isConnected && address && !session && !isLoading && autoAttemptedAddress.current !== address) {
1040
- autoAttemptedAddress.current = address;
1041
- void login().catch(() => {
1042
- });
1540
+ }, [
1541
+ autoLogin,
1542
+ status,
1543
+ isConnected,
1544
+ address,
1545
+ session,
1546
+ isLoading,
1547
+ isHydrating,
1548
+ ctrl,
1549
+ login,
1550
+ clearSession,
1551
+ resetSession
1552
+ ]);
1553
+ react.useEffect(() => {
1554
+ if (!persistJwt || typeof window === "undefined") return;
1555
+ function onStorage(event) {
1556
+ if (event.key !== storageKey) return;
1557
+ const currentAddress = resolveActiveSessionAddress(
1558
+ sessionRef.current?.address,
1559
+ ctrl.getState().currentRecord?.tokens.address,
1560
+ ctrl.getState().authenticatingAddress
1561
+ );
1562
+ const action = resolveStorageEvent(
1563
+ event.newValue,
1564
+ currentAddress,
1565
+ ctrl.getState().currentRecord?.updatedAt ?? null
1566
+ );
1567
+ switch (action.type) {
1568
+ case "logout":
1569
+ resetSession();
1570
+ if (currentAddress)
1571
+ ctrl.dispatch({ type: "setAutoAttemptedAddress", address: currentAddress });
1572
+ break;
1573
+ case "adopt":
1574
+ restoreSession(action.record);
1575
+ break;
1576
+ }
1043
1577
  }
1044
- }, [autoLogin, status, isConnected, address, session, isLoading, login, clearSession]);
1045
- const value = react.useMemo(
1578
+ window.addEventListener("storage", onStorage);
1579
+ return () => window.removeEventListener("storage", onStorage);
1580
+ }, [persistJwt, storageKey, ctrl, restoreSession, resetSession]);
1581
+ react.useEffect(() => {
1582
+ const prev = prevPersistJwtRef.current;
1583
+ prevPersistJwtRef.current = persistJwt;
1584
+ if (!(prev === true && !persistJwt)) return;
1585
+ ctrl.dispatch({ type: "clearCurrentRecord" });
1586
+ storageAdapter.remove();
1587
+ }, [persistJwt, storageAdapter, ctrl]);
1588
+ react.useEffect(() => {
1589
+ sessionRef.current = session;
1590
+ }, [session]);
1591
+ return react.useMemo(
1046
1592
  () => ({
1047
1593
  isAuthenticated: !!session,
1048
- isLoading,
1594
+ isLoading: isLoading || isHydrating,
1595
+ isHydrating,
1049
1596
  error,
1050
1597
  session,
1051
1598
  accessToken: tokens?.jwt_access_token,
@@ -1053,8 +1600,38 @@ function SiweAuthProvider({
1053
1600
  login,
1054
1601
  logout
1055
1602
  }),
1056
- [session, isLoading, error, tokens, login, logout]
1603
+ [session, isLoading, isHydrating, error, tokens, login, logout]
1057
1604
  );
1605
+ }
1606
+ var SiweAuthContext = react.createContext(null);
1607
+ function SiweAuthProvider({
1608
+ children,
1609
+ client,
1610
+ networkConfig,
1611
+ autoLogin = true,
1612
+ persistJwt = false
1613
+ }) {
1614
+ const storageKey = react.useMemo(
1615
+ () => createSiweAuthStorageKey(networkConfig.apiUrl, networkConfig.chainId),
1616
+ [networkConfig.apiUrl, networkConfig.chainId]
1617
+ );
1618
+ const lifecycleClient = react.useMemo(
1619
+ () => ({
1620
+ setBearerToken: (t) => client.setBearerToken(t),
1621
+ clearBearerToken: () => client.clearBearerToken(),
1622
+ clearPrivateReadToken: () => client.clearPrivateReadToken()
1623
+ }),
1624
+ [client]
1625
+ );
1626
+ const value = useSiweAuthLifecycle({
1627
+ storageKey,
1628
+ apiUrl: networkConfig.apiUrl,
1629
+ chainId: networkConfig.chainId,
1630
+ persistJwt,
1631
+ autoLogin,
1632
+ client: lifecycleClient,
1633
+ api: client
1634
+ });
1058
1635
  return /* @__PURE__ */ jsxRuntime.jsx(SiweAuthContext.Provider, { value, children });
1059
1636
  }
1060
1637
  function useSiweAuth() {
@@ -1366,6 +1943,7 @@ function PrivanaProvider({
1366
1943
  client,
1367
1944
  networkConfig,
1368
1945
  autoLogin: typeof siweAuth === "object" ? siweAuth.autoLogin : void 0,
1946
+ persistJwt: typeof siweAuth === "object" ? siweAuth.persistJwt : void 0,
1369
1947
  children
1370
1948
  }
1371
1949
  ) : children });
@@ -1436,6 +2014,47 @@ function removeBrowserStorageItem(key) {
1436
2014
  }
1437
2015
  }
1438
2016
  }
2017
+ function canUseSharedBrowserStorage() {
2018
+ const storage = storageCandidate("localStorage");
2019
+ if (!storage) return false;
2020
+ const probeKey = "privana:shared-storage-probe";
2021
+ try {
2022
+ storage.setItem(probeKey, "1");
2023
+ storage.removeItem(probeKey);
2024
+ return true;
2025
+ } catch {
2026
+ return false;
2027
+ }
2028
+ }
2029
+ function setSharedBrowserStorageItem(key, value) {
2030
+ const storage = storageCandidate("localStorage");
2031
+ if (!storage) return false;
2032
+ try {
2033
+ storage.setItem(key, value);
2034
+ } catch {
2035
+ return false;
2036
+ }
2037
+ try {
2038
+ storageCandidate("sessionStorage")?.removeItem(key);
2039
+ } catch {
2040
+ }
2041
+ return true;
2042
+ }
2043
+ function getSharedBrowserStorageItem(key) {
2044
+ try {
2045
+ return storageCandidate("localStorage")?.getItem(key) ?? null;
2046
+ } catch {
2047
+ return null;
2048
+ }
2049
+ }
2050
+ function removeSharedBrowserStorageItem(key) {
2051
+ for (const storage of storageCandidates()) {
2052
+ try {
2053
+ storage.removeItem(key);
2054
+ } catch {
2055
+ }
2056
+ }
2057
+ }
1439
2058
 
1440
2059
  // src/sdk/hooks/pending-lock.ts
1441
2060
  var DEFAULT_LOCK_DURATION_SECONDS = 259200;
@@ -1451,6 +2070,25 @@ function applyLockBuffer(amount, buffer = DEFAULT_ONRAMP_LOCK_BUFFER) {
1451
2070
  function clampLockAmount(amount, maxAmount) {
1452
2071
  return maxAmount !== void 0 && maxAmount < amount ? maxAmount : amount;
1453
2072
  }
2073
+ function requireServiceAddress(serviceAddress) {
2074
+ if (!serviceAddress) {
2075
+ throw new Error("Service address not configured");
2076
+ }
2077
+ return serviceAddress;
2078
+ }
2079
+ function requireDepositLockOwner(walletAddress, beneficiary) {
2080
+ if (!walletAddress) throw new Error("No wallet connected");
2081
+ if (!beneficiary) throw new Error("No authenticated deposit account");
2082
+ if (walletAddress.toLowerCase() !== beneficiary.toLowerCase()) {
2083
+ throw new Error("Connected wallet does not match the authenticated deposit account");
2084
+ }
2085
+ return beneficiary;
2086
+ }
2087
+ function walletClientAccountAddress(walletClient) {
2088
+ const account = walletClient.account;
2089
+ if (!account) return void 0;
2090
+ return typeof account === "string" ? account : account.address;
2091
+ }
1454
2092
  async function createSignedLockRequest({
1455
2093
  client,
1456
2094
  walletClient,
@@ -1464,6 +2102,10 @@ async function createSignedLockRequest({
1464
2102
  if (amount <= 0n) {
1465
2103
  throw new Error("Lock amount must be positive");
1466
2104
  }
2105
+ const signerAddress = walletClientAccountAddress(walletClient);
2106
+ if (!signerAddress || signerAddress.toLowerCase() !== userAddress.toLowerCase()) {
2107
+ throw new Error("Connected wallet does not match the authenticated deposit account");
2108
+ }
1467
2109
  const expiry = BigInt(Math.floor(Date.now() / 1e3) + lockDuration);
1468
2110
  const { nonce } = await client.getLockNonce(userAddress);
1469
2111
  const signature = await signLockMessage({
@@ -1500,13 +2142,15 @@ var PostDepositLockError = class _PostDepositLockError extends Error {
1500
2142
  this.signedAmount = signedAmount;
1501
2143
  this.creditedAmount = creditedAmount;
1502
2144
  this.name = "PostDepositLockError";
2145
+ this.submissionMayHaveSucceeded = options?.submissionMayHaveSucceeded ?? false;
1503
2146
  Object.setPrototypeOf(this, _PostDepositLockError.prototype);
1504
2147
  }
1505
2148
  };
1506
2149
  async function submitPendingLock({
1507
2150
  client,
1508
2151
  payload,
1509
- creditedAmount
2152
+ creditedAmount,
2153
+ beforeSubmit
1510
2154
  }) {
1511
2155
  let signedAmount;
1512
2156
  try {
@@ -1530,13 +2174,14 @@ async function submitPendingLock({
1530
2174
  }
1531
2175
  if (creditedAmount !== void 0 && creditedAmount < signedAmount) {
1532
2176
  throw new PostDepositLockError(
1533
- `Credited amount (${creditedAmount}) is below the signed lock amount (${signedAmount})`,
2177
+ "Credited amount is below the signed lock amount",
1534
2178
  "credited-below-signed",
1535
2179
  signedAmount,
1536
2180
  creditedAmount
1537
2181
  );
1538
2182
  }
1539
2183
  try {
2184
+ beforeSubmit?.();
1540
2185
  return await client.lockFunds(payload);
1541
2186
  } catch (err) {
1542
2187
  throw new PostDepositLockError(
@@ -1581,7 +2226,6 @@ function clearPendingLock(userAddress, correlationId) {
1581
2226
  }
1582
2227
  var INITIAL_AUTH_BACKOFF_MS = 5e3;
1583
2228
  var MAX_AUTH_BACKOFF_MS = 6e4;
1584
- var DEFAULT_SIWE_AUTH_VALIDITY_MS = 24 * 60 * 60 * 1e3;
1585
2229
  var privateReadFailureCache = /* @__PURE__ */ new Map();
1586
2230
  var privateReadInflight = /* @__PURE__ */ new Map();
1587
2231
  async function executeHostedAuthPrivateReadRequest({
@@ -1606,13 +2250,13 @@ async function executeHostedAuthPrivateReadRequest({
1606
2250
  };
1607
2251
  await ensureHostedAuth(false);
1608
2252
  try {
1609
- return await request();
2253
+ return await request(client);
1610
2254
  } catch (error) {
1611
2255
  if (!(error instanceof AccountingApiError) || error.statusCode !== 401) {
1612
2256
  throw error;
1613
2257
  }
1614
2258
  await ensureHostedAuth(true);
1615
- return request();
2259
+ return request(client);
1616
2260
  }
1617
2261
  }
1618
2262
  function clearPrivateReadScope(scopeKey, client) {
@@ -1620,6 +2264,24 @@ function clearPrivateReadScope(scopeKey, client) {
1620
2264
  privateReadFailureCache.delete(scopeKey);
1621
2265
  client.clearPrivateReadToken();
1622
2266
  }
2267
+ async function executeSiwePrivateReadRequest({
2268
+ client,
2269
+ scopeKey,
2270
+ getToken,
2271
+ request
2272
+ }) {
2273
+ const run = (token2) => request(client.withPrivateReadToken(token2));
2274
+ const token = await getToken(false);
2275
+ try {
2276
+ return await run(token);
2277
+ } catch (error) {
2278
+ if (!(error instanceof AccountingApiError) || error.statusCode !== 401) {
2279
+ throw error;
2280
+ }
2281
+ clearPrivateReadScope(scopeKey, client);
2282
+ return run(await getToken(true));
2283
+ }
2284
+ }
1623
2285
  function recordPrivateReadFailure(scopeKey) {
1624
2286
  const previous = privateReadFailureCache.get(scopeKey);
1625
2287
  const backoffMs = Math.min(
@@ -1666,46 +2328,26 @@ function usePrivateReadRequest() {
1666
2328
  if (!walletAddress) {
1667
2329
  throw new Error("No wallet connected");
1668
2330
  }
1669
- const walletClient = await actions.getWalletClient(wagmiContext);
1670
- if (!walletClient) {
1671
- throw new Error("No wallet client available");
1672
- }
1673
2331
  const apiUrl = networkConfig.apiUrl;
1674
2332
  const scopeKey = createScopeKey(apiUrl, networkConfig.chainId, walletAddress);
1675
2333
  const getToken = async (forceRefresh) => {
1676
2334
  const inflight = privateReadInflight.get(scopeKey);
1677
- if (inflight) {
1678
- const token = await inflight;
1679
- client.setPrivateReadToken(token);
1680
- return token;
1681
- }
2335
+ if (inflight) return inflight;
1682
2336
  if (!forceRefresh) {
1683
2337
  const cached = getCachedPrivateReadToken(scopeKey);
1684
- if (cached) {
1685
- client.setPrivateReadToken(cached);
1686
- return cached;
1687
- }
2338
+ if (cached) return cached;
1688
2339
  }
1689
2340
  ensureFailureBackoff(scopeKey);
1690
2341
  const authPromise = (async () => {
1691
2342
  try {
1692
- const [{ domain }, nonceResponse] = await Promise.all([
1693
- client.getSiweDomain(),
1694
- client.getSiweNonce(walletAddress)
1695
- ]);
1696
- const issuedAt = /* @__PURE__ */ new Date();
1697
- const expirationTime = new Date(issuedAt.getTime() + DEFAULT_SIWE_AUTH_VALIDITY_MS);
1698
- const uri = typeof window !== "undefined" && window.location.origin ? window.location.origin : apiUrl;
1699
- const message = siwe.createSiweMessage({
2343
+ const walletClient = await actions.getWalletClient(wagmiContext);
2344
+ if (!walletClient) {
2345
+ throw new Error("No wallet client available");
2346
+ }
2347
+ const { message, expirationTime } = await buildSiweLoginMessage(client, {
1700
2348
  address: walletAddress,
1701
2349
  chainId: networkConfig.chainId,
1702
- domain,
1703
- expirationTime,
1704
- issuedAt,
1705
- nonce: nonceResponse.nonce,
1706
- statement: buildSiweStatement(networkConfig.chainId),
1707
- uri,
1708
- version: "1"
2350
+ apiUrl
1709
2351
  });
1710
2352
  const signature = await walletClient.signMessage({
1711
2353
  account: walletClient.account ?? walletAddress,
@@ -1717,7 +2359,6 @@ function usePrivateReadRequest() {
1717
2359
  });
1718
2360
  setCachedPrivateReadToken(scopeKey, login.siwe_token, expirationTime.getTime());
1719
2361
  privateReadFailureCache.delete(scopeKey);
1720
- client.setPrivateReadToken(login.siwe_token);
1721
2362
  return login.siwe_token;
1722
2363
  } catch (error) {
1723
2364
  const authError = error instanceof Error ? error : new Error("Failed to authenticate private reads");
@@ -1731,17 +2372,7 @@ function usePrivateReadRequest() {
1731
2372
  privateReadInflight.set(scopeKey, authPromise);
1732
2373
  return authPromise;
1733
2374
  };
1734
- await getToken(false);
1735
- try {
1736
- return await request();
1737
- } catch (error) {
1738
- if (!(error instanceof AccountingApiError) || error.statusCode !== 401) {
1739
- throw error;
1740
- }
1741
- clearPrivateReadScope(scopeKey, client);
1742
- await getToken(true);
1743
- return request();
1744
- }
2375
+ return executeSiwePrivateReadRequest({ client, scopeKey, getToken, request });
1745
2376
  },
1746
2377
  [
1747
2378
  client,
@@ -1768,7 +2399,6 @@ function usePrivateReadRequest() {
1768
2399
 
1769
2400
  // src/sdk/hooks/use-deposit-verification.ts
1770
2401
  function useDepositVerification(options = {}) {
1771
- const { client } = usePrivanaContext();
1772
2402
  const queryClient = reactQuery.useQueryClient();
1773
2403
  const { executePrivateRead } = usePrivateReadRequest();
1774
2404
  const pollInterval = options.pollInterval ?? 5e3;
@@ -1777,6 +2407,7 @@ function useDepositVerification(options = {}) {
1777
2407
  const [isVerifying, setIsVerifying] = react.useState(false);
1778
2408
  const [didTimeout, setDidTimeout] = react.useState(false);
1779
2409
  const [verificationFailed, setVerificationFailed] = react.useState(false);
2410
+ const [canCheckAnotherTransfer, setCanCheckAnotherTransfer] = react.useState(false);
1780
2411
  const [error, setError] = react.useState(null);
1781
2412
  const [txHash, setTxHash] = react.useState();
1782
2413
  const generationRef = react.useRef(0);
@@ -1812,14 +2443,16 @@ function useDepositVerification(options = {}) {
1812
2443
  const isStale = () => generation !== generationRef.current;
1813
2444
  const { hash, chainId, amount, logIndex } = ctx;
1814
2445
  setVerificationFailed(false);
2446
+ setCanCheckAnotherTransfer(false);
1815
2447
  setError(null);
1816
2448
  setDidTimeout(false);
1817
2449
  setIsVerifying(true);
1818
2450
  const pollStartTime = Date.now();
1819
- const markVerificationFailed = (err) => {
2451
+ const markVerificationFailed = (err, canCheckAnother = false) => {
1820
2452
  setIsVerifying(false);
1821
2453
  setError(err);
1822
2454
  setVerificationFailed(true);
2455
+ setCanCheckAnotherTransfer(canCheckAnother);
1823
2456
  onErrorRef.current?.(err);
1824
2457
  };
1825
2458
  const markVerificationTimedOut = () => {
@@ -1835,7 +2468,7 @@ function useDepositVerification(options = {}) {
1835
2468
  if (isStale()) return;
1836
2469
  try {
1837
2470
  const result = await executePrivateRead(
1838
- () => client.checkDeposit({
2471
+ (readClient) => readClient.checkDeposit({
1839
2472
  chain_id: chainId,
1840
2473
  tx_hash: hash,
1841
2474
  amount: amount.toString(),
@@ -1870,11 +2503,12 @@ function useDepositVerification(options = {}) {
1870
2503
  }
1871
2504
  if (isStale()) return;
1872
2505
  if (triggerResult.status === "credited") {
2506
+ const creditedAmount = creditedAmountFromResponse(triggerResult, amount);
1873
2507
  setIsVerifying(false);
1874
2508
  verificationContextRef.current = null;
1875
2509
  queryClient.invalidateQueries({ queryKey: ["accounting-balance"] });
1876
2510
  queryClient.invalidateQueries({ queryKey: ["accounting-history"] });
1877
- onCreditedRef.current?.(hash, triggerResult);
2511
+ onCreditedRef.current?.(hash, triggerResult, creditedAmount);
1878
2512
  return;
1879
2513
  }
1880
2514
  if (triggerResult.status === "error") {
@@ -1894,16 +2528,29 @@ function useDepositVerification(options = {}) {
1894
2528
  return true;
1895
2529
  }
1896
2530
  try {
1897
- const result = await executePrivateRead(() => client.getDepositStatus(depositId));
2531
+ const result = await executePrivateRead(
2532
+ (readClient) => readClient.getDepositStatus(depositId)
2533
+ );
1898
2534
  if (isStale()) return true;
1899
2535
  consecutiveFailures = 0;
1900
2536
  if (result.status === "credited") {
2537
+ let creditedAmount;
2538
+ try {
2539
+ creditedAmount = creditedAmountFromResponse(result, amount);
2540
+ } catch (err) {
2541
+ stopPolling();
2542
+ markVerificationFailed(
2543
+ err instanceof Error ? err : new Error("Deposit credited amount is invalid"),
2544
+ false
2545
+ );
2546
+ return true;
2547
+ }
1901
2548
  stopPolling();
1902
2549
  setIsVerifying(false);
1903
2550
  verificationContextRef.current = null;
1904
2551
  queryClient.invalidateQueries({ queryKey: ["accounting-balance"] });
1905
2552
  queryClient.invalidateQueries({ queryKey: ["accounting-history"] });
1906
- onCreditedRef.current?.(hash, result);
2553
+ onCreditedRef.current?.(hash, result, creditedAmount);
1907
2554
  return true;
1908
2555
  }
1909
2556
  if (result.status === "error") {
@@ -1935,20 +2582,11 @@ function useDepositVerification(options = {}) {
1935
2582
  } catch (err) {
1936
2583
  if (isStale()) return;
1937
2584
  stopPolling();
1938
- markVerificationFailed(
1939
- err instanceof Error ? err : new Error("Deposit verification failed")
1940
- );
2585
+ const error2 = err instanceof Error ? err : new Error("Deposit verification failed");
2586
+ markVerificationFailed(error2, isDefinitiveCandidateFailure(error2));
1941
2587
  }
1942
2588
  },
1943
- [
1944
- client,
1945
- executePrivateRead,
1946
- finalityRetryInterval,
1947
- pollInterval,
1948
- pollTimeout,
1949
- queryClient,
1950
- stopPolling
1951
- ]
2589
+ [executePrivateRead, finalityRetryInterval, pollInterval, pollTimeout, queryClient, stopPolling]
1952
2590
  );
1953
2591
  const verify = react.useCallback(
1954
2592
  async (ctx) => {
@@ -1979,12 +2617,14 @@ function useDepositVerification(options = {}) {
1979
2617
  setIsVerifying(false);
1980
2618
  setDidTimeout(false);
1981
2619
  setVerificationFailed(false);
2620
+ setCanCheckAnotherTransfer(false);
1982
2621
  setError(null);
1983
2622
  }, [stopPolling]);
1984
2623
  return {
1985
2624
  isVerifying,
1986
2625
  didTimeout,
1987
2626
  verificationFailed,
2627
+ canCheckAnotherTransfer,
1988
2628
  error,
1989
2629
  txHash,
1990
2630
  verify,
@@ -1992,6 +2632,19 @@ function useDepositVerification(options = {}) {
1992
2632
  reset
1993
2633
  };
1994
2634
  }
2635
+ function creditedAmountFromResponse(response, requestedAmount) {
2636
+ if (response.amount == null) return requestedAmount;
2637
+ try {
2638
+ const amount = BigInt(response.amount);
2639
+ if (amount <= 0n) throw new Error("non-positive amount");
2640
+ return amount;
2641
+ } catch (err) {
2642
+ throw new Error("Deposit API returned an invalid credited amount", { cause: err });
2643
+ }
2644
+ }
2645
+ function isDefinitiveCandidateFailure(error) {
2646
+ return error instanceof AccountingApiError && error.statusCode === 400;
2647
+ }
1995
2648
  function sleep(ms) {
1996
2649
  return new Promise((resolve) => setTimeout(resolve, ms));
1997
2650
  }
@@ -2309,6 +2962,12 @@ async function getConnectorClient(config, parameters = {}) {
2309
2962
  transport: (opts) => viem.custom(provider)({ ...opts, retryCount: 0 })
2310
2963
  });
2311
2964
  }
2965
+ function getBlockNumber(config, parameters = {}) {
2966
+ const { chainId, ...rest } = parameters;
2967
+ const client = config.getClient({ chainId });
2968
+ const action = getAction(client, actions$1.getBlockNumber, "getBlockNumber");
2969
+ return action(rest);
2970
+ }
2312
2971
 
2313
2972
  // ../../node_modules/@wagmi/core/dist/esm/actions/getChainId.js
2314
2973
  function getChainId2(config) {
@@ -2427,7 +3086,9 @@ function useFiatOnRamp(options) {
2427
3086
  const { address } = wagmi.useAccount();
2428
3087
  const { data: walletClient } = wagmi.useWalletClient();
2429
3088
  const { client, enabledTokens, networkConfig, serviceAddress } = usePrivanaContext();
2430
- const { executePrivateRead, privateReadReady } = usePrivateReadRequest();
3089
+ const { executePrivateRead, privateReadAddress, privateReadReady } = usePrivateReadRequest();
3090
+ const privateReadAddressRef = react.useRef(privateReadAddress);
3091
+ privateReadAddressRef.current = privateReadAddress;
2431
3092
  const { ensureCorrectChain } = useEnsureCorrectChain();
2432
3093
  const wagmiConfig = wagmi.useConfig();
2433
3094
  const queryClient = reactQuery.useQueryClient();
@@ -2449,7 +3110,6 @@ function useFiatOnRamp(options) {
2449
3110
  const activeIntentIdRef = react.useRef(null);
2450
3111
  const activeVerificationRecordRef = react.useRef(null);
2451
3112
  const activeVerificationKeyRef = react.useRef(null);
2452
- const activeVerificationAmountRef = react.useRef(null);
2453
3113
  const lockOwnerRef = react.useRef(null);
2454
3114
  const triggeredVerificationKeysRef = react.useRef(/* @__PURE__ */ new Set());
2455
3115
  const activeVerificationDoneRef = react.useRef(null);
@@ -2498,7 +3158,7 @@ function useFiatOnRamp(options) {
2498
3158
  void (async () => {
2499
3159
  try {
2500
3160
  emitDebug("deposit-address:request");
2501
- const resp = await executePrivateRead(() => client.getDepositAddress());
3161
+ const resp = await executePrivateRead((readClient) => readClient.getDepositAddress());
2502
3162
  if (cancelled) return;
2503
3163
  setDepositAddress(resp.deposit_address);
2504
3164
  const token = enabledTokens.find((t) => t.id.toLowerCase() === tokenId.toLowerCase());
@@ -2528,7 +3188,9 @@ function useFiatOnRamp(options) {
2528
3188
  }
2529
3189
  try {
2530
3190
  emitDebug("pending:request");
2531
- const { pending: rows } = await executePrivateRead(() => client.getPendingOnRamps());
3191
+ const { pending: rows } = await executePrivateRead(
3192
+ (readClient) => readClient.getPendingOnRamps()
3193
+ );
2532
3194
  setPending(rows);
2533
3195
  emitDebug("pending:success", {
2534
3196
  count: rows.length,
@@ -2538,7 +3200,7 @@ function useFiatOnRamp(options) {
2538
3200
  emitDebug("pending:error", errorPayload(err));
2539
3201
  console.warn("Failed to load pending on-ramps:", err);
2540
3202
  }
2541
- }, [client, emitDebug, executePrivateRead, privateReadReady]);
3203
+ }, [emitDebug, executePrivateRead, privateReadReady]);
2542
3204
  react.useEffect(() => {
2543
3205
  refreshPending();
2544
3206
  }, [refreshPending]);
@@ -2548,13 +3210,12 @@ function useFiatOnRamp(options) {
2548
3210
  if (key) triggeredVerificationKeysRef.current.delete(key);
2549
3211
  activeVerificationKeyRef.current = null;
2550
3212
  activeVerificationRecordRef.current = null;
2551
- activeVerificationAmountRef.current = null;
2552
3213
  setActiveVerificationId(null);
2553
3214
  activeVerificationDoneRef.current?.();
2554
3215
  activeVerificationDoneRef.current = null;
2555
3216
  }, []);
2556
3217
  const submitPendingLockAfterCredit = react.useCallback(
2557
- async (transactionId, userAddress) => {
3218
+ async (transactionId, userAddress, creditedAmount) => {
2558
3219
  const signedLock = loadPendingLock(userAddress, transactionId);
2559
3220
  if (!signedLock) {
2560
3221
  clearPendingLock(userAddress, transactionId);
@@ -2567,7 +3228,6 @@ function useFiatOnRamp(options) {
2567
3228
  (onLockFailedRef.current ?? onErrorRef.current)?.(error2);
2568
3229
  return;
2569
3230
  }
2570
- const creditedAmount = activeVerificationAmountRef.current ?? void 0;
2571
3231
  try {
2572
3232
  const result = await submitPendingLock({ client, payload: signedLock, creditedAmount });
2573
3233
  queryClient.invalidateQueries({ queryKey: ["accounting-balance"] });
@@ -2613,7 +3273,7 @@ function useFiatOnRamp(options) {
2613
3273
  });
2614
3274
  setFinalityProgress((prev) => ({ ...prev, [record.transaction_id]: message }));
2615
3275
  },
2616
- onCredited: (depositTxHash) => {
3276
+ onCredited: (depositTxHash, _response, creditedAmount) => {
2617
3277
  const record = activeVerificationRecordRef.current;
2618
3278
  const verificationKey = record ? getOnRampVerificationKey(record) : null;
2619
3279
  emitDebug("verification:credited", {
@@ -2630,9 +3290,9 @@ function useFiatOnRamp(options) {
2630
3290
  delete next[record.transaction_id];
2631
3291
  return next;
2632
3292
  });
2633
- const lockOwner = lockOwnerRef.current ?? address;
3293
+ const lockOwner = lockOwnerRef.current ?? privateReadAddress;
2634
3294
  if (lockOwner) {
2635
- void submitPendingLockAfterCredit(record.transaction_id, lockOwner);
3295
+ void submitPendingLockAfterCredit(record.transaction_id, lockOwner, creditedAmount);
2636
3296
  } else if (postDepositLock) {
2637
3297
  emitDebug("lock:owner-unavailable", { transactionId: record.transaction_id });
2638
3298
  (onLockFailedRef.current ?? onErrorRef.current)?.(
@@ -2651,7 +3311,7 @@ function useFiatOnRamp(options) {
2651
3311
  depositTxHash
2652
3312
  });
2653
3313
  const updated = await executePrivateRead(
2654
- () => client.updateOnRamp(record.transaction_id, {
3314
+ (readClient) => readClient.updateOnRamp(record.transaction_id, {
2655
3315
  deposit_tx_hash: depositTxHash
2656
3316
  })
2657
3317
  );
@@ -2712,8 +3372,10 @@ function useFiatOnRamp(options) {
2712
3372
  if (!token) throw new Error(`Unknown token: ${tokenId}`);
2713
3373
  if (!depositAddress) throw new Error("Privana deposit address is not ready");
2714
3374
  let lockAmount;
3375
+ let lockOwner;
2715
3376
  if (postDepositLock) {
2716
3377
  if (!address || !walletClient) throw new Error("Wallet not connected");
3378
+ lockOwner = requireDepositLockOwner(address, privateReadAddress);
2717
3379
  if (!quoteCurrencyAmount) {
2718
3380
  throw new Error(
2719
3381
  "postDepositLock requires quoteCurrencyAmount to derive the lock amount"
@@ -2742,7 +3404,7 @@ function useFiatOnRamp(options) {
2742
3404
  depositAddress
2743
3405
  });
2744
3406
  const record = await executePrivateRead(
2745
- () => client.createOnRampIntent({
3407
+ (readClient) => readClient.createOnRampIntent({
2746
3408
  wallet_address: depositAddress,
2747
3409
  token_id: tokenId,
2748
3410
  chain_id: token.chainId,
@@ -2751,22 +3413,25 @@ function useFiatOnRamp(options) {
2751
3413
  base_currency_amount: baseCurrencyAmount
2752
3414
  })
2753
3415
  );
2754
- if (postDepositLock && address && walletClient && lockAmount !== void 0) {
3416
+ if (postDepositLock && lockOwner && lockAmount !== void 0) {
2755
3417
  const signingWalletClient = await getWalletClient3(wagmiConfig, {
2756
3418
  chainId: networkConfig.chainId
2757
3419
  });
2758
3420
  const signedLock = await createSignedLockRequest({
2759
3421
  client,
2760
3422
  walletClient: signingWalletClient,
2761
- userAddress: address,
3423
+ userAddress: lockOwner,
2762
3424
  networkConfig,
2763
3425
  serviceAddress: requireServiceAddress(postDepositLock.serviceAddress ?? serviceAddress),
2764
3426
  tokenId,
2765
3427
  amount: lockAmount,
2766
3428
  lockDuration: postDepositLock.lockDuration
2767
3429
  });
2768
- savePendingLock(address, record.transaction_id, signedLock);
2769
- lockOwnerRef.current = address;
3430
+ if (privateReadAddressRef.current?.toLowerCase() !== lockOwner.toLowerCase()) {
3431
+ throw new Error("Authenticated deposit account changed while signing");
3432
+ }
3433
+ savePendingLock(lockOwner, record.transaction_id, signedLock);
3434
+ lockOwnerRef.current = lockOwner;
2770
3435
  emitDebug("intent:lock-signed", {
2771
3436
  transactionId: record.transaction_id,
2772
3437
  amount: signedLock.amount,
@@ -2798,6 +3463,7 @@ function useFiatOnRamp(options) {
2798
3463
  executePrivateRead,
2799
3464
  networkConfig,
2800
3465
  postDepositLock,
3466
+ privateReadAddress,
2801
3467
  serviceAddress,
2802
3468
  tokenId,
2803
3469
  wagmiConfig,
@@ -2823,7 +3489,7 @@ function useFiatOnRamp(options) {
2823
3489
  chainId: token.chainId
2824
3490
  });
2825
3491
  const record = await executePrivateRead(
2826
- () => client.updateOnRamp(transactionId, {
3492
+ (readClient) => readClient.updateOnRamp(transactionId, {
2827
3493
  token_id: tokenId,
2828
3494
  chain_id: token.chainId,
2829
3495
  moonpay_transaction_id: transactionId === moonpayTransactionId ? void 0 : moonpayTransactionId
@@ -2843,7 +3509,7 @@ function useFiatOnRamp(options) {
2843
3509
  console.warn("Failed to register on-ramp token mapping:", err);
2844
3510
  }
2845
3511
  },
2846
- [client, emitDebug, enabledTokens, executePrivateRead, tokenId]
3512
+ [emitDebug, enabledTokens, executePrivateRead, tokenId]
2847
3513
  );
2848
3514
  const handleTransactionCreated = react.useCallback(
2849
3515
  async (props) => {
@@ -2858,7 +3524,9 @@ function useFiatOnRamp(options) {
2858
3524
  setError(null);
2859
3525
  try {
2860
3526
  emitDebug("moonpay:onUrlSignatureRequested", summariseMoonPayUrl(url));
2861
- const { signature } = await executePrivateRead(() => client.signOnRampUrl({ url }));
3527
+ const { signature } = await executePrivateRead(
3528
+ (readClient) => readClient.signOnRampUrl({ url })
3529
+ );
2862
3530
  setStatus("awaiting-purchase");
2863
3531
  emitDebug("sign-url:success", {
2864
3532
  signatureLength: signature.length
@@ -2873,7 +3541,7 @@ function useFiatOnRamp(options) {
2873
3541
  throw err;
2874
3542
  }
2875
3543
  },
2876
- [client, emitDebug, executePrivateRead]
3544
+ [emitDebug, executePrivateRead]
2877
3545
  );
2878
3546
  const waitForOnChainHash = react.useCallback(
2879
3547
  async (transactionId) => {
@@ -2885,7 +3553,9 @@ function useFiatOnRamp(options) {
2885
3553
  });
2886
3554
  while (Date.now() - startTime < deliveryTimeout) {
2887
3555
  try {
2888
- const { pending: rows } = await executePrivateRead(() => client.getPendingOnRamps());
3556
+ const { pending: rows } = await executePrivateRead(
3557
+ (readClient) => readClient.getPendingOnRamps()
3558
+ );
2889
3559
  setPending(rows);
2890
3560
  const record = rows.find((r) => matchesOnRampTransaction(r, transactionId));
2891
3561
  emitDebug("delivery-poll:tick", {
@@ -2912,7 +3582,7 @@ function useFiatOnRamp(options) {
2912
3582
  emitDebug("delivery-poll:timeout", { transactionId });
2913
3583
  return null;
2914
3584
  },
2915
- [client, deliveryPollInterval, deliveryTimeout, emitDebug, executePrivateRead]
3585
+ [deliveryPollInterval, deliveryTimeout, emitDebug, executePrivateRead]
2916
3586
  );
2917
3587
  const triggerVerification = react.useCallback(
2918
3588
  async (record) => {
@@ -2980,7 +3650,6 @@ function useFiatOnRamp(options) {
2980
3650
  `Delivered amount (${record.quote_currency_amount}) is below the minimum deposit.`
2981
3651
  );
2982
3652
  }
2983
- activeVerificationAmountRef.current = amount;
2984
3653
  if (activeIntentIdRef.current === record.transaction_id) {
2985
3654
  setStatus("verifying");
2986
3655
  }
@@ -2999,7 +3668,6 @@ function useFiatOnRamp(options) {
2999
3668
  if (activeVerificationKeyRef.current === verificationKey) {
3000
3669
  activeVerificationKeyRef.current = null;
3001
3670
  activeVerificationRecordRef.current = null;
3002
- activeVerificationAmountRef.current = null;
3003
3671
  setActiveVerificationId(null);
3004
3672
  }
3005
3673
  throw err;
@@ -3245,12 +3913,6 @@ function matchesOnRampTransaction(record, transactionId) {
3245
3913
  function getOnRampVerificationKey(record) {
3246
3914
  return record.on_chain_tx_hash ?? record.transaction_id;
3247
3915
  }
3248
- function requireServiceAddress(serviceAddress) {
3249
- if (!serviceAddress) {
3250
- throw new Error("Service address not configured");
3251
- }
3252
- return serviceAddress;
3253
- }
3254
3916
  function summariseMoonPayEventProps(props) {
3255
3917
  return {
3256
3918
  id: props.id,
@@ -3684,7 +4346,7 @@ function FiatOnRampForm({
3684
4346
  ] }),
3685
4347
  lockPending && /* @__PURE__ */ jsxRuntime.jsxs("p", { className: "text-muted-foreground flex items-center gap-2 text-sm", children: [
3686
4348
  /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Loader2, { className: "size-4 animate-spin", "aria-hidden": true }),
3687
- "Purchase credited \u2014 locking your funds\u2026"
4349
+ "Purchase credited. Locking your funds\u2026"
3688
4350
  ] }),
3689
4351
  autoStart ? !visible && isPrePurchase && /* @__PURE__ */ jsxRuntime.jsx(Skeleton, { className: "h-[656px] w-full rounded-md" }) : isInitializing ? /* @__PURE__ */ jsxRuntime.jsx(Skeleton, { className: "h-9 w-full rounded-md" }) : /* @__PURE__ */ jsxRuntime.jsxs(Button, { type: "button", onClick: handleOpen, disabled: !canBuy, children: [
3690
4352
  (isBusy || visible) && /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Loader2, { className: "animate-spin", "aria-hidden": true }),
@@ -3744,6 +4406,7 @@ exports.buildHostedAuthSession = buildHostedAuthSession;
3744
4406
  exports.buildSiweStatement = buildSiweStatement;
3745
4407
  exports.buttonVariants = buttonVariants;
3746
4408
  exports.canUseBrowserStorage = canUseBrowserStorage;
4409
+ exports.canUseSharedBrowserStorage = canUseSharedBrowserStorage;
3747
4410
  exports.clampLockAmount = clampLockAmount;
3748
4411
  exports.clearHostedAuthPendingTransaction = clearHostedAuthPendingTransaction;
3749
4412
  exports.clearPendingLock = clearPendingLock;
@@ -3761,11 +4424,13 @@ exports.formatTimeRemaining = formatTimeRemaining;
3761
4424
  exports.formatTokenAmount = formatTokenAmount;
3762
4425
  exports.getAccountingContract = getAccountingContract;
3763
4426
  exports.getApiUrl = getApiUrl;
4427
+ exports.getBlockNumber = getBlockNumber;
3764
4428
  exports.getBrowserStorageItem = getBrowserStorageItem;
3765
4429
  exports.getChainById = getChainById;
3766
4430
  exports.getChainId = getChainId;
3767
4431
  exports.getExplorerAddressUrl = getExplorerAddressUrl;
3768
4432
  exports.getExplorerLabel = getExplorerLabel;
4433
+ exports.getSharedBrowserStorageItem = getSharedBrowserStorageItem;
3769
4434
  exports.getTransactionReceipt = getTransactionReceipt;
3770
4435
  exports.getWalletClient = getWalletClient3;
3771
4436
  exports.isHostedAuthRefreshActive = isHostedAuthRefreshActive;
@@ -3780,8 +4445,12 @@ exports.persistHostedAuthPendingTransaction = persistHostedAuthPendingTransactio
3780
4445
  exports.readHostedAuthPendingTransaction = readHostedAuthPendingTransaction;
3781
4446
  exports.readStoredHostedAuthSession = readStoredHostedAuthSession;
3782
4447
  exports.removeBrowserStorageItem = removeBrowserStorageItem;
4448
+ exports.removeSharedBrowserStorageItem = removeSharedBrowserStorageItem;
4449
+ exports.requireDepositLockOwner = requireDepositLockOwner;
4450
+ exports.requireServiceAddress = requireServiceAddress;
3783
4451
  exports.savePendingLock = savePendingLock;
3784
4452
  exports.setBrowserStorageItem = setBrowserStorageItem;
4453
+ exports.setSharedBrowserStorageItem = setSharedBrowserStorageItem;
3785
4454
  exports.shortenAddress = shortenAddress;
3786
4455
  exports.signLockMessage = signLockMessage;
3787
4456
  exports.signModifyLockMessage = signModifyLockMessage;
@@ -3801,5 +4470,5 @@ exports.useSafeAccount = useSafeAccount;
3801
4470
  exports.useSafePrivanaContext = useSafePrivanaContext;
3802
4471
  exports.useSiweAuth = useSiweAuth;
3803
4472
  exports.waitForTransactionReceipt = waitForTransactionReceipt;
3804
- //# sourceMappingURL=chunk-4IW4V7YJ.cjs.map
3805
- //# sourceMappingURL=chunk-4IW4V7YJ.cjs.map
4473
+ //# sourceMappingURL=chunk-7KEQHWZB.cjs.map
4474
+ //# sourceMappingURL=chunk-7KEQHWZB.cjs.map