@oasisprotocol/privana-sdk 0.5.1 → 0.5.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,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';
@@ -11,7 +11,7 @@ import { cva } from 'class-variance-authority';
11
11
  import { clsx } from 'clsx';
12
12
  import { twMerge } from 'tailwind-merge';
13
13
  import { parseAccount, getAddress } from 'viem/utils';
14
- import { getTransactionReceipt as getTransactionReceipt$1, waitForTransactionReceipt as waitForTransactionReceipt$1, getTransaction, call } from 'viem/actions';
14
+ import { getBlockNumber as getBlockNumber$1, getTransactionReceipt as getTransactionReceipt$1, waitForTransactionReceipt as waitForTransactionReceipt$1, getTransaction, call } from 'viem/actions';
15
15
  import { Loader2, CircleCheckIcon } from 'lucide-react';
16
16
  import { MoonPayBuyWidget } from '@moonpay/moonpay-react';
17
17
 
@@ -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]
1055
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]
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
  }
@@ -2307,6 +2960,12 @@ async function getConnectorClient(config, parameters = {}) {
2307
2960
  transport: (opts) => custom(provider)({ ...opts, retryCount: 0 })
2308
2961
  });
2309
2962
  }
2963
+ function getBlockNumber(config, parameters = {}) {
2964
+ const { chainId, ...rest } = parameters;
2965
+ const client = config.getClient({ chainId });
2966
+ const action = getAction(client, getBlockNumber$1, "getBlockNumber");
2967
+ return action(rest);
2968
+ }
2310
2969
 
2311
2970
  // ../../node_modules/@wagmi/core/dist/esm/actions/getChainId.js
2312
2971
  function getChainId2(config) {
@@ -2425,7 +3084,9 @@ function useFiatOnRamp(options) {
2425
3084
  const { address } = useAccount();
2426
3085
  const { data: walletClient } = useWalletClient();
2427
3086
  const { client, enabledTokens, networkConfig, serviceAddress } = usePrivanaContext();
2428
- const { executePrivateRead, privateReadReady } = usePrivateReadRequest();
3087
+ const { executePrivateRead, privateReadAddress, privateReadReady } = usePrivateReadRequest();
3088
+ const privateReadAddressRef = useRef(privateReadAddress);
3089
+ privateReadAddressRef.current = privateReadAddress;
2429
3090
  const { ensureCorrectChain } = useEnsureCorrectChain();
2430
3091
  const wagmiConfig = useConfig();
2431
3092
  const queryClient = useQueryClient();
@@ -2447,7 +3108,6 @@ function useFiatOnRamp(options) {
2447
3108
  const activeIntentIdRef = useRef(null);
2448
3109
  const activeVerificationRecordRef = useRef(null);
2449
3110
  const activeVerificationKeyRef = useRef(null);
2450
- const activeVerificationAmountRef = useRef(null);
2451
3111
  const lockOwnerRef = useRef(null);
2452
3112
  const triggeredVerificationKeysRef = useRef(/* @__PURE__ */ new Set());
2453
3113
  const activeVerificationDoneRef = useRef(null);
@@ -2496,7 +3156,7 @@ function useFiatOnRamp(options) {
2496
3156
  void (async () => {
2497
3157
  try {
2498
3158
  emitDebug("deposit-address:request");
2499
- const resp = await executePrivateRead(() => client.getDepositAddress());
3159
+ const resp = await executePrivateRead((readClient) => readClient.getDepositAddress());
2500
3160
  if (cancelled) return;
2501
3161
  setDepositAddress(resp.deposit_address);
2502
3162
  const token = enabledTokens.find((t) => t.id.toLowerCase() === tokenId.toLowerCase());
@@ -2526,7 +3186,9 @@ function useFiatOnRamp(options) {
2526
3186
  }
2527
3187
  try {
2528
3188
  emitDebug("pending:request");
2529
- const { pending: rows } = await executePrivateRead(() => client.getPendingOnRamps());
3189
+ const { pending: rows } = await executePrivateRead(
3190
+ (readClient) => readClient.getPendingOnRamps()
3191
+ );
2530
3192
  setPending(rows);
2531
3193
  emitDebug("pending:success", {
2532
3194
  count: rows.length,
@@ -2536,7 +3198,7 @@ function useFiatOnRamp(options) {
2536
3198
  emitDebug("pending:error", errorPayload(err));
2537
3199
  console.warn("Failed to load pending on-ramps:", err);
2538
3200
  }
2539
- }, [client, emitDebug, executePrivateRead, privateReadReady]);
3201
+ }, [emitDebug, executePrivateRead, privateReadReady]);
2540
3202
  useEffect(() => {
2541
3203
  refreshPending();
2542
3204
  }, [refreshPending]);
@@ -2546,13 +3208,12 @@ function useFiatOnRamp(options) {
2546
3208
  if (key) triggeredVerificationKeysRef.current.delete(key);
2547
3209
  activeVerificationKeyRef.current = null;
2548
3210
  activeVerificationRecordRef.current = null;
2549
- activeVerificationAmountRef.current = null;
2550
3211
  setActiveVerificationId(null);
2551
3212
  activeVerificationDoneRef.current?.();
2552
3213
  activeVerificationDoneRef.current = null;
2553
3214
  }, []);
2554
3215
  const submitPendingLockAfterCredit = useCallback(
2555
- async (transactionId, userAddress) => {
3216
+ async (transactionId, userAddress, creditedAmount) => {
2556
3217
  const signedLock = loadPendingLock(userAddress, transactionId);
2557
3218
  if (!signedLock) {
2558
3219
  clearPendingLock(userAddress, transactionId);
@@ -2565,7 +3226,6 @@ function useFiatOnRamp(options) {
2565
3226
  (onLockFailedRef.current ?? onErrorRef.current)?.(error2);
2566
3227
  return;
2567
3228
  }
2568
- const creditedAmount = activeVerificationAmountRef.current ?? void 0;
2569
3229
  try {
2570
3230
  const result = await submitPendingLock({ client, payload: signedLock, creditedAmount });
2571
3231
  queryClient.invalidateQueries({ queryKey: ["accounting-balance"] });
@@ -2611,7 +3271,7 @@ function useFiatOnRamp(options) {
2611
3271
  });
2612
3272
  setFinalityProgress((prev) => ({ ...prev, [record.transaction_id]: message }));
2613
3273
  },
2614
- onCredited: (depositTxHash) => {
3274
+ onCredited: (depositTxHash, _response, creditedAmount) => {
2615
3275
  const record = activeVerificationRecordRef.current;
2616
3276
  const verificationKey = record ? getOnRampVerificationKey(record) : null;
2617
3277
  emitDebug("verification:credited", {
@@ -2628,9 +3288,9 @@ function useFiatOnRamp(options) {
2628
3288
  delete next[record.transaction_id];
2629
3289
  return next;
2630
3290
  });
2631
- const lockOwner = lockOwnerRef.current ?? address;
3291
+ const lockOwner = lockOwnerRef.current ?? privateReadAddress;
2632
3292
  if (lockOwner) {
2633
- void submitPendingLockAfterCredit(record.transaction_id, lockOwner);
3293
+ void submitPendingLockAfterCredit(record.transaction_id, lockOwner, creditedAmount);
2634
3294
  } else if (postDepositLock) {
2635
3295
  emitDebug("lock:owner-unavailable", { transactionId: record.transaction_id });
2636
3296
  (onLockFailedRef.current ?? onErrorRef.current)?.(
@@ -2649,7 +3309,7 @@ function useFiatOnRamp(options) {
2649
3309
  depositTxHash
2650
3310
  });
2651
3311
  const updated = await executePrivateRead(
2652
- () => client.updateOnRamp(record.transaction_id, {
3312
+ (readClient) => readClient.updateOnRamp(record.transaction_id, {
2653
3313
  deposit_tx_hash: depositTxHash
2654
3314
  })
2655
3315
  );
@@ -2710,8 +3370,10 @@ function useFiatOnRamp(options) {
2710
3370
  if (!token) throw new Error(`Unknown token: ${tokenId}`);
2711
3371
  if (!depositAddress) throw new Error("Privana deposit address is not ready");
2712
3372
  let lockAmount;
3373
+ let lockOwner;
2713
3374
  if (postDepositLock) {
2714
3375
  if (!address || !walletClient) throw new Error("Wallet not connected");
3376
+ lockOwner = requireDepositLockOwner(address, privateReadAddress);
2715
3377
  if (!quoteCurrencyAmount) {
2716
3378
  throw new Error(
2717
3379
  "postDepositLock requires quoteCurrencyAmount to derive the lock amount"
@@ -2740,7 +3402,7 @@ function useFiatOnRamp(options) {
2740
3402
  depositAddress
2741
3403
  });
2742
3404
  const record = await executePrivateRead(
2743
- () => client.createOnRampIntent({
3405
+ (readClient) => readClient.createOnRampIntent({
2744
3406
  wallet_address: depositAddress,
2745
3407
  token_id: tokenId,
2746
3408
  chain_id: token.chainId,
@@ -2749,22 +3411,25 @@ function useFiatOnRamp(options) {
2749
3411
  base_currency_amount: baseCurrencyAmount
2750
3412
  })
2751
3413
  );
2752
- if (postDepositLock && address && walletClient && lockAmount !== void 0) {
3414
+ if (postDepositLock && lockOwner && lockAmount !== void 0) {
2753
3415
  const signingWalletClient = await getWalletClient3(wagmiConfig, {
2754
3416
  chainId: networkConfig.chainId
2755
3417
  });
2756
3418
  const signedLock = await createSignedLockRequest({
2757
3419
  client,
2758
3420
  walletClient: signingWalletClient,
2759
- userAddress: address,
3421
+ userAddress: lockOwner,
2760
3422
  networkConfig,
2761
3423
  serviceAddress: requireServiceAddress(postDepositLock.serviceAddress ?? serviceAddress),
2762
3424
  tokenId,
2763
3425
  amount: lockAmount,
2764
3426
  lockDuration: postDepositLock.lockDuration
2765
3427
  });
2766
- savePendingLock(address, record.transaction_id, signedLock);
2767
- 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;
2768
3433
  emitDebug("intent:lock-signed", {
2769
3434
  transactionId: record.transaction_id,
2770
3435
  amount: signedLock.amount,
@@ -2796,6 +3461,7 @@ function useFiatOnRamp(options) {
2796
3461
  executePrivateRead,
2797
3462
  networkConfig,
2798
3463
  postDepositLock,
3464
+ privateReadAddress,
2799
3465
  serviceAddress,
2800
3466
  tokenId,
2801
3467
  wagmiConfig,
@@ -2821,7 +3487,7 @@ function useFiatOnRamp(options) {
2821
3487
  chainId: token.chainId
2822
3488
  });
2823
3489
  const record = await executePrivateRead(
2824
- () => client.updateOnRamp(transactionId, {
3490
+ (readClient) => readClient.updateOnRamp(transactionId, {
2825
3491
  token_id: tokenId,
2826
3492
  chain_id: token.chainId,
2827
3493
  moonpay_transaction_id: transactionId === moonpayTransactionId ? void 0 : moonpayTransactionId
@@ -2841,7 +3507,7 @@ function useFiatOnRamp(options) {
2841
3507
  console.warn("Failed to register on-ramp token mapping:", err);
2842
3508
  }
2843
3509
  },
2844
- [client, emitDebug, enabledTokens, executePrivateRead, tokenId]
3510
+ [emitDebug, enabledTokens, executePrivateRead, tokenId]
2845
3511
  );
2846
3512
  const handleTransactionCreated = useCallback(
2847
3513
  async (props) => {
@@ -2856,7 +3522,9 @@ function useFiatOnRamp(options) {
2856
3522
  setError(null);
2857
3523
  try {
2858
3524
  emitDebug("moonpay:onUrlSignatureRequested", summariseMoonPayUrl(url));
2859
- const { signature } = await executePrivateRead(() => client.signOnRampUrl({ url }));
3525
+ const { signature } = await executePrivateRead(
3526
+ (readClient) => readClient.signOnRampUrl({ url })
3527
+ );
2860
3528
  setStatus("awaiting-purchase");
2861
3529
  emitDebug("sign-url:success", {
2862
3530
  signatureLength: signature.length
@@ -2871,7 +3539,7 @@ function useFiatOnRamp(options) {
2871
3539
  throw err;
2872
3540
  }
2873
3541
  },
2874
- [client, emitDebug, executePrivateRead]
3542
+ [emitDebug, executePrivateRead]
2875
3543
  );
2876
3544
  const waitForOnChainHash = useCallback(
2877
3545
  async (transactionId) => {
@@ -2883,7 +3551,9 @@ function useFiatOnRamp(options) {
2883
3551
  });
2884
3552
  while (Date.now() - startTime < deliveryTimeout) {
2885
3553
  try {
2886
- const { pending: rows } = await executePrivateRead(() => client.getPendingOnRamps());
3554
+ const { pending: rows } = await executePrivateRead(
3555
+ (readClient) => readClient.getPendingOnRamps()
3556
+ );
2887
3557
  setPending(rows);
2888
3558
  const record = rows.find((r) => matchesOnRampTransaction(r, transactionId));
2889
3559
  emitDebug("delivery-poll:tick", {
@@ -2910,7 +3580,7 @@ function useFiatOnRamp(options) {
2910
3580
  emitDebug("delivery-poll:timeout", { transactionId });
2911
3581
  return null;
2912
3582
  },
2913
- [client, deliveryPollInterval, deliveryTimeout, emitDebug, executePrivateRead]
3583
+ [deliveryPollInterval, deliveryTimeout, emitDebug, executePrivateRead]
2914
3584
  );
2915
3585
  const triggerVerification = useCallback(
2916
3586
  async (record) => {
@@ -2978,7 +3648,6 @@ function useFiatOnRamp(options) {
2978
3648
  `Delivered amount (${record.quote_currency_amount}) is below the minimum deposit.`
2979
3649
  );
2980
3650
  }
2981
- activeVerificationAmountRef.current = amount;
2982
3651
  if (activeIntentIdRef.current === record.transaction_id) {
2983
3652
  setStatus("verifying");
2984
3653
  }
@@ -2997,7 +3666,6 @@ function useFiatOnRamp(options) {
2997
3666
  if (activeVerificationKeyRef.current === verificationKey) {
2998
3667
  activeVerificationKeyRef.current = null;
2999
3668
  activeVerificationRecordRef.current = null;
3000
- activeVerificationAmountRef.current = null;
3001
3669
  setActiveVerificationId(null);
3002
3670
  }
3003
3671
  throw err;
@@ -3243,12 +3911,6 @@ function matchesOnRampTransaction(record, transactionId) {
3243
3911
  function getOnRampVerificationKey(record) {
3244
3912
  return record.on_chain_tx_hash ?? record.transaction_id;
3245
3913
  }
3246
- function requireServiceAddress(serviceAddress) {
3247
- if (!serviceAddress) {
3248
- throw new Error("Service address not configured");
3249
- }
3250
- return serviceAddress;
3251
- }
3252
3914
  function summariseMoonPayEventProps(props) {
3253
3915
  return {
3254
3916
  id: props.id,
@@ -3682,7 +4344,7 @@ function FiatOnRampForm({
3682
4344
  ] }),
3683
4345
  lockPending && /* @__PURE__ */ jsxs("p", { className: "text-muted-foreground flex items-center gap-2 text-sm", children: [
3684
4346
  /* @__PURE__ */ jsx(Loader2, { className: "size-4 animate-spin", "aria-hidden": true }),
3685
- "Purchase credited \u2014 locking your funds\u2026"
4347
+ "Purchase credited. Locking your funds\u2026"
3686
4348
  ] }),
3687
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: [
3688
4350
  (isBusy || visible) && /* @__PURE__ */ jsx(Loader2, { className: "animate-spin", "aria-hidden": true }),
@@ -3711,6 +4373,6 @@ function parseFinalityProgress(message) {
3711
4373
  return match ? `${match[1]} confirmations` : null;
3712
4374
  }
3713
4375
 
3714
- 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, 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 };
3715
- //# sourceMappingURL=chunk-SAJ7K5WT.js.map
3716
- //# sourceMappingURL=chunk-SAJ7K5WT.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