@oasisprotocol/privana-sdk 0.5.2 → 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]
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]
1057
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
  }
@@ -2433,7 +3086,9 @@ function useFiatOnRamp(options) {
2433
3086
  const { address } = wagmi.useAccount();
2434
3087
  const { data: walletClient } = wagmi.useWalletClient();
2435
3088
  const { client, enabledTokens, networkConfig, serviceAddress } = usePrivanaContext();
2436
- const { executePrivateRead, privateReadReady } = usePrivateReadRequest();
3089
+ const { executePrivateRead, privateReadAddress, privateReadReady } = usePrivateReadRequest();
3090
+ const privateReadAddressRef = react.useRef(privateReadAddress);
3091
+ privateReadAddressRef.current = privateReadAddress;
2437
3092
  const { ensureCorrectChain } = useEnsureCorrectChain();
2438
3093
  const wagmiConfig = wagmi.useConfig();
2439
3094
  const queryClient = reactQuery.useQueryClient();
@@ -2455,7 +3110,6 @@ function useFiatOnRamp(options) {
2455
3110
  const activeIntentIdRef = react.useRef(null);
2456
3111
  const activeVerificationRecordRef = react.useRef(null);
2457
3112
  const activeVerificationKeyRef = react.useRef(null);
2458
- const activeVerificationAmountRef = react.useRef(null);
2459
3113
  const lockOwnerRef = react.useRef(null);
2460
3114
  const triggeredVerificationKeysRef = react.useRef(/* @__PURE__ */ new Set());
2461
3115
  const activeVerificationDoneRef = react.useRef(null);
@@ -2504,7 +3158,7 @@ function useFiatOnRamp(options) {
2504
3158
  void (async () => {
2505
3159
  try {
2506
3160
  emitDebug("deposit-address:request");
2507
- const resp = await executePrivateRead(() => client.getDepositAddress());
3161
+ const resp = await executePrivateRead((readClient) => readClient.getDepositAddress());
2508
3162
  if (cancelled) return;
2509
3163
  setDepositAddress(resp.deposit_address);
2510
3164
  const token = enabledTokens.find((t) => t.id.toLowerCase() === tokenId.toLowerCase());
@@ -2534,7 +3188,9 @@ function useFiatOnRamp(options) {
2534
3188
  }
2535
3189
  try {
2536
3190
  emitDebug("pending:request");
2537
- const { pending: rows } = await executePrivateRead(() => client.getPendingOnRamps());
3191
+ const { pending: rows } = await executePrivateRead(
3192
+ (readClient) => readClient.getPendingOnRamps()
3193
+ );
2538
3194
  setPending(rows);
2539
3195
  emitDebug("pending:success", {
2540
3196
  count: rows.length,
@@ -2544,7 +3200,7 @@ function useFiatOnRamp(options) {
2544
3200
  emitDebug("pending:error", errorPayload(err));
2545
3201
  console.warn("Failed to load pending on-ramps:", err);
2546
3202
  }
2547
- }, [client, emitDebug, executePrivateRead, privateReadReady]);
3203
+ }, [emitDebug, executePrivateRead, privateReadReady]);
2548
3204
  react.useEffect(() => {
2549
3205
  refreshPending();
2550
3206
  }, [refreshPending]);
@@ -2554,13 +3210,12 @@ function useFiatOnRamp(options) {
2554
3210
  if (key) triggeredVerificationKeysRef.current.delete(key);
2555
3211
  activeVerificationKeyRef.current = null;
2556
3212
  activeVerificationRecordRef.current = null;
2557
- activeVerificationAmountRef.current = null;
2558
3213
  setActiveVerificationId(null);
2559
3214
  activeVerificationDoneRef.current?.();
2560
3215
  activeVerificationDoneRef.current = null;
2561
3216
  }, []);
2562
3217
  const submitPendingLockAfterCredit = react.useCallback(
2563
- async (transactionId, userAddress) => {
3218
+ async (transactionId, userAddress, creditedAmount) => {
2564
3219
  const signedLock = loadPendingLock(userAddress, transactionId);
2565
3220
  if (!signedLock) {
2566
3221
  clearPendingLock(userAddress, transactionId);
@@ -2573,7 +3228,6 @@ function useFiatOnRamp(options) {
2573
3228
  (onLockFailedRef.current ?? onErrorRef.current)?.(error2);
2574
3229
  return;
2575
3230
  }
2576
- const creditedAmount = activeVerificationAmountRef.current ?? void 0;
2577
3231
  try {
2578
3232
  const result = await submitPendingLock({ client, payload: signedLock, creditedAmount });
2579
3233
  queryClient.invalidateQueries({ queryKey: ["accounting-balance"] });
@@ -2619,7 +3273,7 @@ function useFiatOnRamp(options) {
2619
3273
  });
2620
3274
  setFinalityProgress((prev) => ({ ...prev, [record.transaction_id]: message }));
2621
3275
  },
2622
- onCredited: (depositTxHash) => {
3276
+ onCredited: (depositTxHash, _response, creditedAmount) => {
2623
3277
  const record = activeVerificationRecordRef.current;
2624
3278
  const verificationKey = record ? getOnRampVerificationKey(record) : null;
2625
3279
  emitDebug("verification:credited", {
@@ -2636,9 +3290,9 @@ function useFiatOnRamp(options) {
2636
3290
  delete next[record.transaction_id];
2637
3291
  return next;
2638
3292
  });
2639
- const lockOwner = lockOwnerRef.current ?? address;
3293
+ const lockOwner = lockOwnerRef.current ?? privateReadAddress;
2640
3294
  if (lockOwner) {
2641
- void submitPendingLockAfterCredit(record.transaction_id, lockOwner);
3295
+ void submitPendingLockAfterCredit(record.transaction_id, lockOwner, creditedAmount);
2642
3296
  } else if (postDepositLock) {
2643
3297
  emitDebug("lock:owner-unavailable", { transactionId: record.transaction_id });
2644
3298
  (onLockFailedRef.current ?? onErrorRef.current)?.(
@@ -2657,7 +3311,7 @@ function useFiatOnRamp(options) {
2657
3311
  depositTxHash
2658
3312
  });
2659
3313
  const updated = await executePrivateRead(
2660
- () => client.updateOnRamp(record.transaction_id, {
3314
+ (readClient) => readClient.updateOnRamp(record.transaction_id, {
2661
3315
  deposit_tx_hash: depositTxHash
2662
3316
  })
2663
3317
  );
@@ -2718,8 +3372,10 @@ function useFiatOnRamp(options) {
2718
3372
  if (!token) throw new Error(`Unknown token: ${tokenId}`);
2719
3373
  if (!depositAddress) throw new Error("Privana deposit address is not ready");
2720
3374
  let lockAmount;
3375
+ let lockOwner;
2721
3376
  if (postDepositLock) {
2722
3377
  if (!address || !walletClient) throw new Error("Wallet not connected");
3378
+ lockOwner = requireDepositLockOwner(address, privateReadAddress);
2723
3379
  if (!quoteCurrencyAmount) {
2724
3380
  throw new Error(
2725
3381
  "postDepositLock requires quoteCurrencyAmount to derive the lock amount"
@@ -2748,7 +3404,7 @@ function useFiatOnRamp(options) {
2748
3404
  depositAddress
2749
3405
  });
2750
3406
  const record = await executePrivateRead(
2751
- () => client.createOnRampIntent({
3407
+ (readClient) => readClient.createOnRampIntent({
2752
3408
  wallet_address: depositAddress,
2753
3409
  token_id: tokenId,
2754
3410
  chain_id: token.chainId,
@@ -2757,22 +3413,25 @@ function useFiatOnRamp(options) {
2757
3413
  base_currency_amount: baseCurrencyAmount
2758
3414
  })
2759
3415
  );
2760
- if (postDepositLock && address && walletClient && lockAmount !== void 0) {
3416
+ if (postDepositLock && lockOwner && lockAmount !== void 0) {
2761
3417
  const signingWalletClient = await getWalletClient3(wagmiConfig, {
2762
3418
  chainId: networkConfig.chainId
2763
3419
  });
2764
3420
  const signedLock = await createSignedLockRequest({
2765
3421
  client,
2766
3422
  walletClient: signingWalletClient,
2767
- userAddress: address,
3423
+ userAddress: lockOwner,
2768
3424
  networkConfig,
2769
3425
  serviceAddress: requireServiceAddress(postDepositLock.serviceAddress ?? serviceAddress),
2770
3426
  tokenId,
2771
3427
  amount: lockAmount,
2772
3428
  lockDuration: postDepositLock.lockDuration
2773
3429
  });
2774
- savePendingLock(address, record.transaction_id, signedLock);
2775
- 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;
2776
3435
  emitDebug("intent:lock-signed", {
2777
3436
  transactionId: record.transaction_id,
2778
3437
  amount: signedLock.amount,
@@ -2804,6 +3463,7 @@ function useFiatOnRamp(options) {
2804
3463
  executePrivateRead,
2805
3464
  networkConfig,
2806
3465
  postDepositLock,
3466
+ privateReadAddress,
2807
3467
  serviceAddress,
2808
3468
  tokenId,
2809
3469
  wagmiConfig,
@@ -2829,7 +3489,7 @@ function useFiatOnRamp(options) {
2829
3489
  chainId: token.chainId
2830
3490
  });
2831
3491
  const record = await executePrivateRead(
2832
- () => client.updateOnRamp(transactionId, {
3492
+ (readClient) => readClient.updateOnRamp(transactionId, {
2833
3493
  token_id: tokenId,
2834
3494
  chain_id: token.chainId,
2835
3495
  moonpay_transaction_id: transactionId === moonpayTransactionId ? void 0 : moonpayTransactionId
@@ -2849,7 +3509,7 @@ function useFiatOnRamp(options) {
2849
3509
  console.warn("Failed to register on-ramp token mapping:", err);
2850
3510
  }
2851
3511
  },
2852
- [client, emitDebug, enabledTokens, executePrivateRead, tokenId]
3512
+ [emitDebug, enabledTokens, executePrivateRead, tokenId]
2853
3513
  );
2854
3514
  const handleTransactionCreated = react.useCallback(
2855
3515
  async (props) => {
@@ -2864,7 +3524,9 @@ function useFiatOnRamp(options) {
2864
3524
  setError(null);
2865
3525
  try {
2866
3526
  emitDebug("moonpay:onUrlSignatureRequested", summariseMoonPayUrl(url));
2867
- const { signature } = await executePrivateRead(() => client.signOnRampUrl({ url }));
3527
+ const { signature } = await executePrivateRead(
3528
+ (readClient) => readClient.signOnRampUrl({ url })
3529
+ );
2868
3530
  setStatus("awaiting-purchase");
2869
3531
  emitDebug("sign-url:success", {
2870
3532
  signatureLength: signature.length
@@ -2879,7 +3541,7 @@ function useFiatOnRamp(options) {
2879
3541
  throw err;
2880
3542
  }
2881
3543
  },
2882
- [client, emitDebug, executePrivateRead]
3544
+ [emitDebug, executePrivateRead]
2883
3545
  );
2884
3546
  const waitForOnChainHash = react.useCallback(
2885
3547
  async (transactionId) => {
@@ -2891,7 +3553,9 @@ function useFiatOnRamp(options) {
2891
3553
  });
2892
3554
  while (Date.now() - startTime < deliveryTimeout) {
2893
3555
  try {
2894
- const { pending: rows } = await executePrivateRead(() => client.getPendingOnRamps());
3556
+ const { pending: rows } = await executePrivateRead(
3557
+ (readClient) => readClient.getPendingOnRamps()
3558
+ );
2895
3559
  setPending(rows);
2896
3560
  const record = rows.find((r) => matchesOnRampTransaction(r, transactionId));
2897
3561
  emitDebug("delivery-poll:tick", {
@@ -2918,7 +3582,7 @@ function useFiatOnRamp(options) {
2918
3582
  emitDebug("delivery-poll:timeout", { transactionId });
2919
3583
  return null;
2920
3584
  },
2921
- [client, deliveryPollInterval, deliveryTimeout, emitDebug, executePrivateRead]
3585
+ [deliveryPollInterval, deliveryTimeout, emitDebug, executePrivateRead]
2922
3586
  );
2923
3587
  const triggerVerification = react.useCallback(
2924
3588
  async (record) => {
@@ -2986,7 +3650,6 @@ function useFiatOnRamp(options) {
2986
3650
  `Delivered amount (${record.quote_currency_amount}) is below the minimum deposit.`
2987
3651
  );
2988
3652
  }
2989
- activeVerificationAmountRef.current = amount;
2990
3653
  if (activeIntentIdRef.current === record.transaction_id) {
2991
3654
  setStatus("verifying");
2992
3655
  }
@@ -3005,7 +3668,6 @@ function useFiatOnRamp(options) {
3005
3668
  if (activeVerificationKeyRef.current === verificationKey) {
3006
3669
  activeVerificationKeyRef.current = null;
3007
3670
  activeVerificationRecordRef.current = null;
3008
- activeVerificationAmountRef.current = null;
3009
3671
  setActiveVerificationId(null);
3010
3672
  }
3011
3673
  throw err;
@@ -3251,12 +3913,6 @@ function matchesOnRampTransaction(record, transactionId) {
3251
3913
  function getOnRampVerificationKey(record) {
3252
3914
  return record.on_chain_tx_hash ?? record.transaction_id;
3253
3915
  }
3254
- function requireServiceAddress(serviceAddress) {
3255
- if (!serviceAddress) {
3256
- throw new Error("Service address not configured");
3257
- }
3258
- return serviceAddress;
3259
- }
3260
3916
  function summariseMoonPayEventProps(props) {
3261
3917
  return {
3262
3918
  id: props.id,
@@ -3690,7 +4346,7 @@ function FiatOnRampForm({
3690
4346
  ] }),
3691
4347
  lockPending && /* @__PURE__ */ jsxRuntime.jsxs("p", { className: "text-muted-foreground flex items-center gap-2 text-sm", children: [
3692
4348
  /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Loader2, { className: "size-4 animate-spin", "aria-hidden": true }),
3693
- "Purchase credited \u2014 locking your funds\u2026"
4349
+ "Purchase credited. Locking your funds\u2026"
3694
4350
  ] }),
3695
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: [
3696
4352
  (isBusy || visible) && /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Loader2, { className: "animate-spin", "aria-hidden": true }),
@@ -3750,6 +4406,7 @@ exports.buildHostedAuthSession = buildHostedAuthSession;
3750
4406
  exports.buildSiweStatement = buildSiweStatement;
3751
4407
  exports.buttonVariants = buttonVariants;
3752
4408
  exports.canUseBrowserStorage = canUseBrowserStorage;
4409
+ exports.canUseSharedBrowserStorage = canUseSharedBrowserStorage;
3753
4410
  exports.clampLockAmount = clampLockAmount;
3754
4411
  exports.clearHostedAuthPendingTransaction = clearHostedAuthPendingTransaction;
3755
4412
  exports.clearPendingLock = clearPendingLock;
@@ -3773,6 +4430,7 @@ exports.getChainById = getChainById;
3773
4430
  exports.getChainId = getChainId;
3774
4431
  exports.getExplorerAddressUrl = getExplorerAddressUrl;
3775
4432
  exports.getExplorerLabel = getExplorerLabel;
4433
+ exports.getSharedBrowserStorageItem = getSharedBrowserStorageItem;
3776
4434
  exports.getTransactionReceipt = getTransactionReceipt;
3777
4435
  exports.getWalletClient = getWalletClient3;
3778
4436
  exports.isHostedAuthRefreshActive = isHostedAuthRefreshActive;
@@ -3787,8 +4445,12 @@ exports.persistHostedAuthPendingTransaction = persistHostedAuthPendingTransactio
3787
4445
  exports.readHostedAuthPendingTransaction = readHostedAuthPendingTransaction;
3788
4446
  exports.readStoredHostedAuthSession = readStoredHostedAuthSession;
3789
4447
  exports.removeBrowserStorageItem = removeBrowserStorageItem;
4448
+ exports.removeSharedBrowserStorageItem = removeSharedBrowserStorageItem;
4449
+ exports.requireDepositLockOwner = requireDepositLockOwner;
4450
+ exports.requireServiceAddress = requireServiceAddress;
3790
4451
  exports.savePendingLock = savePendingLock;
3791
4452
  exports.setBrowserStorageItem = setBrowserStorageItem;
4453
+ exports.setSharedBrowserStorageItem = setSharedBrowserStorageItem;
3792
4454
  exports.shortenAddress = shortenAddress;
3793
4455
  exports.signLockMessage = signLockMessage;
3794
4456
  exports.signModifyLockMessage = signModifyLockMessage;
@@ -3808,5 +4470,5 @@ exports.useSafeAccount = useSafeAccount;
3808
4470
  exports.useSafePrivanaContext = useSafePrivanaContext;
3809
4471
  exports.useSiweAuth = useSiweAuth;
3810
4472
  exports.waitForTransactionReceipt = waitForTransactionReceipt;
3811
- //# sourceMappingURL=chunk-UHXVYWTF.cjs.map
3812
- //# sourceMappingURL=chunk-UHXVYWTF.cjs.map
4473
+ //# sourceMappingURL=chunk-7KEQHWZB.cjs.map
4474
+ //# sourceMappingURL=chunk-7KEQHWZB.cjs.map