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