@oasisprotocol/privana-sdk 0.5.10 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -243,12 +243,21 @@ var SIWE_MESSAGE_VALIDITY_MS = 24 * 60 * 60 * 1e3;
243
243
  function buildSiweStatement(chainId) {
244
244
  return `Sign in to Privana on chain ${chainId}`;
245
245
  }
246
+ function pickSiweDomain(response) {
247
+ const allowed = response.domains;
248
+ if (!allowed?.length) throw new Error("SIWE domain response contains no domains");
249
+ const primary = allowed[0];
250
+ if (typeof window === "undefined" || !window.location?.host) return primary;
251
+ const host = window.location.host.toLowerCase();
252
+ return allowed.some((domain) => domain.toLowerCase() === host) ? host : primary;
253
+ }
246
254
  async function buildSiweLoginMessage(api, params) {
247
255
  const { address, chainId, apiUrl } = params;
248
- const [{ domain }, { nonce }] = await Promise.all([
256
+ const [domainResponse, { nonce }] = await Promise.all([
249
257
  api.getSiweDomain(),
250
258
  api.getSiweNonce(address)
251
259
  ]);
260
+ const domain = pickSiweDomain(domainResponse);
252
261
  const issuedAt = /* @__PURE__ */ new Date();
253
262
  const expirationTime = new Date(issuedAt.getTime() + SIWE_MESSAGE_VALIDITY_MS);
254
263
  const uri = typeof window !== "undefined" && window.location.origin ? window.location.origin : apiUrl;
@@ -582,7 +591,7 @@ var PrivanaClient = class _PrivanaClient {
582
591
  return this.http.get(`/v1/accounting/withdraw/${index}`);
583
592
  }
584
593
  async getSiweDomain() {
585
- return this.http.get("/v1/accounting/auth/domain");
594
+ return this.http.get("/v1/accounting/auth/domains");
586
595
  }
587
596
  async getSiweNonce(userAddress) {
588
597
  const user = normalizeAddress(userAddress);
@@ -1366,7 +1375,7 @@ async function ctrlHydrateViaRefresh(ctrl, record, address) {
1366
1375
  }
1367
1376
  }
1368
1377
 
1369
- // src/sdk/hooks/private-read-token-store.ts
1378
+ // src/sdk/utils/private-read-token-store.ts
1370
1379
  var cache = /* @__PURE__ */ new Map();
1371
1380
  function createScopeKey(apiUrl, chainId, address) {
1372
1381
  return `${apiUrl.replace(/\/$/, "")}:${chainId}:${address.toLowerCase()}`;
@@ -1991,395 +2000,129 @@ function useSafePrivanaContext() {
1991
2000
  return useContext(PrivanaContext);
1992
2001
  }
1993
2002
 
1994
- // src/sdk/hooks/browser-storage.ts
1995
- function storageCandidate(name) {
1996
- try {
1997
- if (typeof window === "undefined") return void 0;
1998
- return window[name] ?? void 0;
1999
- } catch {
2000
- return void 0;
2001
- }
2002
- }
2003
- function storageCandidates() {
2004
- return [storageCandidate("localStorage"), storageCandidate("sessionStorage")].filter(
2005
- (storage) => storage !== void 0
2006
- );
2007
- }
2008
- function canUseBrowserStorage() {
2009
- const probeKey = "privana:storage-probe";
2010
- for (const storage of storageCandidates()) {
2003
+ // src/sdk/utils/deposit-finality.ts
2004
+ async function checkDepositWithFinalityRetry({
2005
+ checkDeposit,
2006
+ isStale,
2007
+ onRetry,
2008
+ timeoutMs,
2009
+ retryIntervalMs,
2010
+ startedAt = Date.now(),
2011
+ now = Date.now,
2012
+ sleep = defaultSleep
2013
+ }) {
2014
+ while (true) {
2015
+ if (isStale()) return { kind: "stale" };
2011
2016
  try {
2012
- storage.setItem(probeKey, "1");
2013
- storage.removeItem(probeKey);
2014
- return true;
2015
- } catch {
2017
+ const response = await checkDeposit();
2018
+ if (!isInsufficientFinalityMessage(response.detail) || response.status !== "error") {
2019
+ return { kind: "response", response };
2020
+ }
2021
+ if (response.detail) onRetry(response.detail);
2022
+ } catch (error) {
2023
+ if (isStale()) return { kind: "stale" };
2024
+ if (!isInsufficientFinalityError(error)) throw error;
2025
+ onRetry(
2026
+ error instanceof AccountingApiError && error.detail ? error.detail : error instanceof Error ? error.message : String(error)
2027
+ );
2016
2028
  }
2029
+ if (now() - startedAt > timeoutMs) return { kind: "timeout" };
2030
+ await sleep(retryIntervalMs);
2017
2031
  }
2018
- return false;
2019
2032
  }
2020
- function setBrowserStorageItem(key, value) {
2021
- let stored = false;
2022
- for (const storage of storageCandidates()) {
2023
- try {
2024
- storage.setItem(key, value);
2025
- stored = true;
2026
- } catch {
2027
- }
2033
+ function isInsufficientFinalityError(error) {
2034
+ if (error instanceof AccountingApiError) {
2035
+ return isInsufficientFinalityMessage(error.detail) || isInsufficientFinalityMessage(error.message);
2028
2036
  }
2029
- return stored;
2037
+ return error instanceof Error && isInsufficientFinalityMessage(error.message);
2030
2038
  }
2031
- function getBrowserStorageItem(key) {
2032
- for (const storage of storageCandidates()) {
2033
- try {
2034
- const value = storage.getItem(key);
2035
- if (value !== null) return value;
2036
- } catch {
2037
- }
2038
- }
2039
- return null;
2039
+ function isInsufficientFinalityMessage(message) {
2040
+ return message?.includes("Insufficient finality") ?? false;
2040
2041
  }
2041
- function removeBrowserStorageItem(key) {
2042
- for (const storage of storageCandidates()) {
2043
- try {
2044
- storage.removeItem(key);
2045
- } catch {
2046
- }
2047
- }
2042
+ function defaultSleep(milliseconds) {
2043
+ return new Promise((resolve) => setTimeout(resolve, milliseconds));
2048
2044
  }
2049
- function canUseSharedBrowserStorage() {
2050
- const storage = storageCandidate("localStorage");
2051
- if (!storage) return false;
2052
- const probeKey = "privana:shared-storage-probe";
2045
+ var INITIAL_AUTH_BACKOFF_MS = 5e3;
2046
+ var MAX_AUTH_BACKOFF_MS = 6e4;
2047
+ var privateReadFailureCache = /* @__PURE__ */ new Map();
2048
+ var privateReadInflight = /* @__PURE__ */ new Map();
2049
+ async function executeHostedAuthPrivateReadRequest({
2050
+ client,
2051
+ hostedAuthSession,
2052
+ refreshHostedAuthSession,
2053
+ request
2054
+ }) {
2055
+ const ensureHostedAuth = async (forceRefresh) => {
2056
+ if (!hostedAuthSession) {
2057
+ throw new HostedAuthRequiredError();
2058
+ }
2059
+ if (!forceRefresh && isHostedAuthSessionActive(hostedAuthSession)) {
2060
+ client.clearPrivateReadToken();
2061
+ client.setBearerToken(hostedAuthSession.accessToken);
2062
+ return hostedAuthSession.accessToken;
2063
+ }
2064
+ const refreshed = await refreshHostedAuthSession();
2065
+ client.clearPrivateReadToken();
2066
+ client.setBearerToken(refreshed.accessToken);
2067
+ return refreshed.accessToken;
2068
+ };
2069
+ await ensureHostedAuth(false);
2053
2070
  try {
2054
- storage.setItem(probeKey, "1");
2055
- storage.removeItem(probeKey);
2056
- return true;
2057
- } catch {
2058
- return false;
2071
+ return await request(client);
2072
+ } catch (error) {
2073
+ if (!(error instanceof AccountingApiError) || error.statusCode !== 401) {
2074
+ throw error;
2075
+ }
2076
+ await ensureHostedAuth(true);
2077
+ return request(client);
2059
2078
  }
2060
2079
  }
2061
- function setSharedBrowserStorageItem(key, value) {
2062
- const storage = storageCandidate("localStorage");
2063
- if (!storage) return false;
2064
- try {
2065
- storage.setItem(key, value);
2066
- } catch {
2067
- return false;
2068
- }
2069
- try {
2070
- storageCandidate("sessionStorage")?.removeItem(key);
2071
- } catch {
2072
- }
2073
- return true;
2080
+ function clearPrivateReadScope(scopeKey, client) {
2081
+ deleteCachedPrivateReadToken(scopeKey);
2082
+ privateReadFailureCache.delete(scopeKey);
2083
+ client.clearPrivateReadToken();
2074
2084
  }
2075
- function getSharedBrowserStorageItem(key) {
2085
+ async function executeSiwePrivateReadRequest({
2086
+ client,
2087
+ scopeKey,
2088
+ getToken,
2089
+ request
2090
+ }) {
2091
+ const run = (token2) => request(client.withPrivateReadToken(token2));
2092
+ const token = await getToken(false);
2076
2093
  try {
2077
- return storageCandidate("localStorage")?.getItem(key) ?? null;
2078
- } catch {
2079
- return null;
2080
- }
2081
- }
2082
- function removeSharedBrowserStorageItem(key) {
2083
- for (const storage of storageCandidates()) {
2084
- try {
2085
- storage.removeItem(key);
2086
- } catch {
2094
+ return await run(token);
2095
+ } catch (error) {
2096
+ if (!(error instanceof AccountingApiError) || error.statusCode !== 401) {
2097
+ throw error;
2087
2098
  }
2099
+ clearPrivateReadScope(scopeKey, client);
2100
+ return run(await getToken(true));
2088
2101
  }
2089
2102
  }
2090
-
2091
- // src/sdk/hooks/pending-lock.ts
2092
- var DEFAULT_LOCK_DURATION_SECONDS = 259200;
2093
- var DEFAULT_ONRAMP_LOCK_BUFFER = 0.02;
2094
- var BUFFER_SCALE = 1000000n;
2095
- function applyLockBuffer(amount, buffer = DEFAULT_ONRAMP_LOCK_BUFFER) {
2096
- if (!Number.isFinite(buffer) || buffer < 0 || buffer >= 1) {
2097
- throw new Error(`Lock buffer must be in [0, 1), got ${buffer}`);
2098
- }
2099
- const shave = BigInt(Math.max(0, Math.ceil(buffer * Number(BUFFER_SCALE) - 1e-6)));
2100
- return amount * (BUFFER_SCALE - shave) / BUFFER_SCALE;
2101
- }
2102
- function clampLockAmount(amount, maxAmount) {
2103
- return maxAmount !== void 0 && maxAmount < amount ? maxAmount : amount;
2104
- }
2105
- function requireServiceAddress(serviceAddress) {
2106
- if (!serviceAddress) {
2107
- throw new Error("Service address not configured");
2108
- }
2109
- return serviceAddress;
2110
- }
2111
- function requireDepositLockOwner(walletAddress, beneficiary) {
2112
- if (!walletAddress) throw new Error("No wallet connected");
2113
- if (!beneficiary) throw new Error("No authenticated deposit account");
2114
- if (walletAddress.toLowerCase() !== beneficiary.toLowerCase()) {
2115
- throw new Error("Connected wallet does not match the authenticated deposit account");
2116
- }
2117
- return beneficiary;
2118
- }
2119
- function walletClientAccountAddress(walletClient) {
2120
- const account = walletClient.account;
2121
- if (!account) return void 0;
2122
- return typeof account === "string" ? account : account.address;
2103
+ function recordPrivateReadFailure(scopeKey) {
2104
+ const previous = privateReadFailureCache.get(scopeKey);
2105
+ const backoffMs = Math.min(
2106
+ previous ? previous.backoffMs * 2 : INITIAL_AUTH_BACKOFF_MS,
2107
+ MAX_AUTH_BACKOFF_MS
2108
+ );
2109
+ privateReadFailureCache.set(scopeKey, {
2110
+ backoffMs,
2111
+ retryAt: Date.now() + backoffMs
2112
+ });
2123
2113
  }
2124
- async function createSignedLockRequest({
2125
- client,
2126
- walletClient,
2127
- userAddress,
2128
- networkConfig,
2129
- serviceAddress,
2130
- tokenId,
2131
- amount,
2132
- lockDuration = DEFAULT_LOCK_DURATION_SECONDS
2133
- }) {
2134
- if (amount <= 0n) {
2135
- throw new Error("Lock amount must be positive");
2136
- }
2137
- const signerAddress = walletClientAccountAddress(walletClient);
2138
- if (!signerAddress || signerAddress.toLowerCase() !== userAddress.toLowerCase()) {
2139
- throw new Error("Connected wallet does not match the authenticated deposit account");
2114
+ function ensureFailureBackoff(scopeKey) {
2115
+ const failure = privateReadFailureCache.get(scopeKey);
2116
+ if (!failure) return;
2117
+ if (failure.retryAt <= Date.now()) {
2118
+ privateReadFailureCache.delete(scopeKey);
2119
+ return;
2140
2120
  }
2141
- const expiry = BigInt(Math.floor(Date.now() / 1e3) + lockDuration);
2142
- const { nonce } = await client.getLockNonce(userAddress);
2143
- const signature = await signLockMessage({
2144
- walletClient,
2145
- chainId: networkConfig.chainId,
2146
- verifyingContract: networkConfig.accountingContract,
2147
- message: {
2148
- serviceAddress,
2149
- tokenId,
2150
- amount,
2151
- expiry,
2152
- nonce: BigInt(nonce)
2153
- }
2154
- });
2155
- return {
2156
- service_address: serviceAddress,
2157
- token_id: tokenId,
2158
- amount: amount.toString(),
2159
- expiry: expiry.toString(),
2160
- nonce: String(nonce),
2161
- signature
2162
- };
2163
- }
2164
- var EXPIRY_SLACK_SECONDS = 60;
2165
- function isSignedLockUsable(payload) {
2166
- const expiry = Number(payload.expiry);
2167
- if (!Number.isFinite(expiry)) return false;
2168
- return expiry > Math.floor(Date.now() / 1e3) + EXPIRY_SLACK_SECONDS;
2169
- }
2170
- var PostDepositLockError = class _PostDepositLockError extends Error {
2171
- constructor(message, reason, signedAmount, creditedAmount, options) {
2172
- super(message, options);
2173
- this.reason = reason;
2174
- this.signedAmount = signedAmount;
2175
- this.creditedAmount = creditedAmount;
2176
- this.name = "PostDepositLockError";
2177
- this.submissionMayHaveSucceeded = options?.submissionMayHaveSucceeded ?? false;
2178
- Object.setPrototypeOf(this, _PostDepositLockError.prototype);
2179
- }
2180
- };
2181
- async function submitPendingLock({
2182
- client,
2183
- payload,
2184
- creditedAmount,
2185
- beforeSubmit
2186
- }) {
2187
- let signedAmount;
2188
- try {
2189
- signedAmount = BigInt(payload.amount);
2190
- } catch (err) {
2191
- throw new PostDepositLockError(
2192
- "Stored signed lock payload is malformed",
2193
- "submission-failed",
2194
- void 0,
2195
- creditedAmount,
2196
- { cause: err }
2197
- );
2198
- }
2199
- if (!isSignedLockUsable(payload)) {
2200
- throw new PostDepositLockError(
2201
- "Signed lock expired before the deposit was credited",
2202
- "expired",
2203
- signedAmount,
2204
- creditedAmount
2205
- );
2206
- }
2207
- if (creditedAmount !== void 0 && creditedAmount < signedAmount) {
2208
- throw new PostDepositLockError(
2209
- "Credited amount is below the signed lock amount",
2210
- "credited-below-signed",
2211
- signedAmount,
2212
- creditedAmount
2213
- );
2214
- }
2215
- try {
2216
- beforeSubmit?.();
2217
- return await client.lockFunds(payload);
2218
- } catch (err) {
2219
- throw new PostDepositLockError(
2220
- err instanceof Error ? err.message : "Lock submission failed",
2221
- "submission-failed",
2222
- signedAmount,
2223
- creditedAmount,
2224
- { cause: err }
2225
- );
2226
- }
2227
- }
2228
- function pendingLockKey(userAddress, correlationId) {
2229
- return `privana:pending-lock:${userAddress.toLowerCase()}:${correlationId}`;
2230
- }
2231
- function savePendingLock(userAddress, correlationId, payload) {
2232
- const record = { payload, savedAt: Date.now() };
2233
- const stored = setBrowserStorageItem(
2234
- pendingLockKey(userAddress, correlationId),
2235
- JSON.stringify(record)
2236
- );
2237
- if (!stored) {
2238
- throw new Error("Unable to persist signed lock for recovery");
2239
- }
2240
- }
2241
- function loadPendingLock(userAddress, correlationId) {
2242
- const key = pendingLockKey(userAddress, correlationId);
2243
- try {
2244
- const raw = getBrowserStorageItem(key);
2245
- if (!raw) return void 0;
2246
- const record = JSON.parse(raw);
2247
- if (!record?.payload?.signature) {
2248
- removeBrowserStorageItem(key);
2249
- return void 0;
2250
- }
2251
- return record.payload;
2252
- } catch {
2253
- return void 0;
2254
- }
2255
- }
2256
- function clearPendingLock(userAddress, correlationId) {
2257
- removeBrowserStorageItem(pendingLockKey(userAddress, correlationId));
2258
- }
2259
-
2260
- // src/sdk/hooks/deposit-finality.ts
2261
- async function checkDepositWithFinalityRetry({
2262
- checkDeposit,
2263
- isStale,
2264
- onRetry,
2265
- timeoutMs,
2266
- retryIntervalMs,
2267
- startedAt = Date.now(),
2268
- now = Date.now,
2269
- sleep = defaultSleep
2270
- }) {
2271
- while (true) {
2272
- if (isStale()) return { kind: "stale" };
2273
- try {
2274
- const response = await checkDeposit();
2275
- if (!isInsufficientFinalityMessage(response.detail) || response.status !== "error") {
2276
- return { kind: "response", response };
2277
- }
2278
- if (response.detail) onRetry(response.detail);
2279
- } catch (error) {
2280
- if (isStale()) return { kind: "stale" };
2281
- if (!isInsufficientFinalityError(error)) throw error;
2282
- onRetry(
2283
- error instanceof AccountingApiError && error.detail ? error.detail : error instanceof Error ? error.message : String(error)
2284
- );
2285
- }
2286
- if (now() - startedAt > timeoutMs) return { kind: "timeout" };
2287
- await sleep(retryIntervalMs);
2288
- }
2289
- }
2290
- function isInsufficientFinalityError(error) {
2291
- if (error instanceof AccountingApiError) {
2292
- return isInsufficientFinalityMessage(error.detail) || isInsufficientFinalityMessage(error.message);
2293
- }
2294
- return error instanceof Error && isInsufficientFinalityMessage(error.message);
2295
- }
2296
- function isInsufficientFinalityMessage(message) {
2297
- return message?.includes("Insufficient finality") ?? false;
2298
- }
2299
- function defaultSleep(milliseconds) {
2300
- return new Promise((resolve) => setTimeout(resolve, milliseconds));
2301
- }
2302
- var INITIAL_AUTH_BACKOFF_MS = 5e3;
2303
- var MAX_AUTH_BACKOFF_MS = 6e4;
2304
- var privateReadFailureCache = /* @__PURE__ */ new Map();
2305
- var privateReadInflight = /* @__PURE__ */ new Map();
2306
- async function executeHostedAuthPrivateReadRequest({
2307
- client,
2308
- hostedAuthSession,
2309
- refreshHostedAuthSession,
2310
- request
2311
- }) {
2312
- const ensureHostedAuth = async (forceRefresh) => {
2313
- if (!hostedAuthSession) {
2314
- throw new HostedAuthRequiredError();
2315
- }
2316
- if (!forceRefresh && isHostedAuthSessionActive(hostedAuthSession)) {
2317
- client.clearPrivateReadToken();
2318
- client.setBearerToken(hostedAuthSession.accessToken);
2319
- return hostedAuthSession.accessToken;
2320
- }
2321
- const refreshed = await refreshHostedAuthSession();
2322
- client.clearPrivateReadToken();
2323
- client.setBearerToken(refreshed.accessToken);
2324
- return refreshed.accessToken;
2325
- };
2326
- await ensureHostedAuth(false);
2327
- try {
2328
- return await request(client);
2329
- } catch (error) {
2330
- if (!(error instanceof AccountingApiError) || error.statusCode !== 401) {
2331
- throw error;
2332
- }
2333
- await ensureHostedAuth(true);
2334
- return request(client);
2335
- }
2336
- }
2337
- function clearPrivateReadScope(scopeKey, client) {
2338
- deleteCachedPrivateReadToken(scopeKey);
2339
- privateReadFailureCache.delete(scopeKey);
2340
- client.clearPrivateReadToken();
2341
- }
2342
- async function executeSiwePrivateReadRequest({
2343
- client,
2344
- scopeKey,
2345
- getToken,
2346
- request
2347
- }) {
2348
- const run = (token2) => request(client.withPrivateReadToken(token2));
2349
- const token = await getToken(false);
2350
- try {
2351
- return await run(token);
2352
- } catch (error) {
2353
- if (!(error instanceof AccountingApiError) || error.statusCode !== 401) {
2354
- throw error;
2355
- }
2356
- clearPrivateReadScope(scopeKey, client);
2357
- return run(await getToken(true));
2358
- }
2359
- }
2360
- function recordPrivateReadFailure(scopeKey) {
2361
- const previous = privateReadFailureCache.get(scopeKey);
2362
- const backoffMs = Math.min(
2363
- previous ? previous.backoffMs * 2 : INITIAL_AUTH_BACKOFF_MS,
2364
- MAX_AUTH_BACKOFF_MS
2365
- );
2366
- privateReadFailureCache.set(scopeKey, {
2367
- backoffMs,
2368
- retryAt: Date.now() + backoffMs
2369
- });
2370
- }
2371
- function ensureFailureBackoff(scopeKey) {
2372
- const failure = privateReadFailureCache.get(scopeKey);
2373
- if (!failure) return;
2374
- if (failure.retryAt <= Date.now()) {
2375
- privateReadFailureCache.delete(scopeKey);
2376
- return;
2377
- }
2378
- throw new Error(
2379
- `Private-read authentication is temporarily paused after a recent failure. Retry in ${Math.ceil(
2380
- (failure.retryAt - Date.now()) / 1e3
2381
- )}s.`
2382
- );
2121
+ throw new Error(
2122
+ `Private-read authentication is temporarily paused after a recent failure. Retry in ${Math.ceil(
2123
+ (failure.retryAt - Date.now()) / 1e3
2124
+ )}s.`
2125
+ );
2383
2126
  }
2384
2127
  function usePrivateReadRequest() {
2385
2128
  const wagmiContext = useContext(WagmiContext);
@@ -2703,6 +2446,272 @@ function creditedAmountFromResponse(response, requestedAmount) {
2703
2446
  function isDefinitiveCandidateFailure(error) {
2704
2447
  return error instanceof AccountingApiError && error.statusCode === 400;
2705
2448
  }
2449
+
2450
+ // src/sdk/utils/browser-storage.ts
2451
+ function storageCandidate(name) {
2452
+ try {
2453
+ if (typeof window === "undefined") return void 0;
2454
+ return window[name] ?? void 0;
2455
+ } catch {
2456
+ return void 0;
2457
+ }
2458
+ }
2459
+ function storageCandidates() {
2460
+ return [storageCandidate("localStorage"), storageCandidate("sessionStorage")].filter(
2461
+ (storage) => storage !== void 0
2462
+ );
2463
+ }
2464
+ function canUseBrowserStorage() {
2465
+ const probeKey = "privana:storage-probe";
2466
+ for (const storage of storageCandidates()) {
2467
+ try {
2468
+ storage.setItem(probeKey, "1");
2469
+ storage.removeItem(probeKey);
2470
+ return true;
2471
+ } catch {
2472
+ }
2473
+ }
2474
+ return false;
2475
+ }
2476
+ function setBrowserStorageItem(key, value) {
2477
+ let stored = false;
2478
+ for (const storage of storageCandidates()) {
2479
+ try {
2480
+ storage.setItem(key, value);
2481
+ stored = true;
2482
+ } catch {
2483
+ }
2484
+ }
2485
+ return stored;
2486
+ }
2487
+ function getBrowserStorageItem(key) {
2488
+ for (const storage of storageCandidates()) {
2489
+ try {
2490
+ const value = storage.getItem(key);
2491
+ if (value !== null) return value;
2492
+ } catch {
2493
+ }
2494
+ }
2495
+ return null;
2496
+ }
2497
+ function removeBrowserStorageItem(key) {
2498
+ for (const storage of storageCandidates()) {
2499
+ try {
2500
+ storage.removeItem(key);
2501
+ } catch {
2502
+ }
2503
+ }
2504
+ }
2505
+ function canUseSharedBrowserStorage() {
2506
+ const storage = storageCandidate("localStorage");
2507
+ if (!storage) return false;
2508
+ const probeKey = "privana:shared-storage-probe";
2509
+ try {
2510
+ storage.setItem(probeKey, "1");
2511
+ storage.removeItem(probeKey);
2512
+ return true;
2513
+ } catch {
2514
+ return false;
2515
+ }
2516
+ }
2517
+ function setSharedBrowserStorageItem(key, value) {
2518
+ const storage = storageCandidate("localStorage");
2519
+ if (!storage) return false;
2520
+ try {
2521
+ storage.setItem(key, value);
2522
+ } catch {
2523
+ return false;
2524
+ }
2525
+ try {
2526
+ storageCandidate("sessionStorage")?.removeItem(key);
2527
+ } catch {
2528
+ }
2529
+ return true;
2530
+ }
2531
+ function getSharedBrowserStorageItem(key) {
2532
+ try {
2533
+ return storageCandidate("localStorage")?.getItem(key) ?? null;
2534
+ } catch {
2535
+ return null;
2536
+ }
2537
+ }
2538
+ function removeSharedBrowserStorageItem(key) {
2539
+ for (const storage of storageCandidates()) {
2540
+ try {
2541
+ storage.removeItem(key);
2542
+ } catch {
2543
+ }
2544
+ }
2545
+ }
2546
+
2547
+ // src/sdk/utils/pending-lock.ts
2548
+ var DEFAULT_LOCK_DURATION_SECONDS = 259200;
2549
+ var DEFAULT_ONRAMP_LOCK_BUFFER = 0.02;
2550
+ var BUFFER_SCALE = 1000000n;
2551
+ function applyLockBuffer(amount, buffer = DEFAULT_ONRAMP_LOCK_BUFFER) {
2552
+ if (!Number.isFinite(buffer) || buffer < 0 || buffer >= 1) {
2553
+ throw new Error(`Lock buffer must be in [0, 1), got ${buffer}`);
2554
+ }
2555
+ const shave = BigInt(Math.max(0, Math.ceil(buffer * Number(BUFFER_SCALE) - 1e-6)));
2556
+ return amount * (BUFFER_SCALE - shave) / BUFFER_SCALE;
2557
+ }
2558
+ function clampLockAmount(amount, maxAmount) {
2559
+ return maxAmount !== void 0 && maxAmount < amount ? maxAmount : amount;
2560
+ }
2561
+ function requireServiceAddress(serviceAddress) {
2562
+ if (!serviceAddress) {
2563
+ throw new Error("Service address not configured");
2564
+ }
2565
+ return serviceAddress;
2566
+ }
2567
+ function requireDepositLockOwner(walletAddress, beneficiary) {
2568
+ if (!walletAddress) throw new Error("No wallet connected");
2569
+ if (!beneficiary) throw new Error("No authenticated deposit account");
2570
+ if (walletAddress.toLowerCase() !== beneficiary.toLowerCase()) {
2571
+ throw new Error("Connected wallet does not match the authenticated deposit account");
2572
+ }
2573
+ return beneficiary;
2574
+ }
2575
+ function walletClientAccountAddress(walletClient) {
2576
+ const account = walletClient.account;
2577
+ if (!account) return void 0;
2578
+ return typeof account === "string" ? account : account.address;
2579
+ }
2580
+ async function createSignedLockRequest({
2581
+ client,
2582
+ walletClient,
2583
+ userAddress,
2584
+ networkConfig,
2585
+ serviceAddress,
2586
+ tokenId,
2587
+ amount,
2588
+ lockDuration = DEFAULT_LOCK_DURATION_SECONDS
2589
+ }) {
2590
+ if (amount <= 0n) {
2591
+ throw new Error("Lock amount must be positive");
2592
+ }
2593
+ const signerAddress = walletClientAccountAddress(walletClient);
2594
+ if (!signerAddress || signerAddress.toLowerCase() !== userAddress.toLowerCase()) {
2595
+ throw new Error("Connected wallet does not match the authenticated deposit account");
2596
+ }
2597
+ const expiry = BigInt(Math.floor(Date.now() / 1e3) + lockDuration);
2598
+ const { nonce } = await client.getLockNonce(userAddress);
2599
+ const signature = await signLockMessage({
2600
+ walletClient,
2601
+ chainId: networkConfig.chainId,
2602
+ verifyingContract: networkConfig.accountingContract,
2603
+ message: {
2604
+ serviceAddress,
2605
+ tokenId,
2606
+ amount,
2607
+ expiry,
2608
+ nonce: BigInt(nonce)
2609
+ }
2610
+ });
2611
+ return {
2612
+ service_address: serviceAddress,
2613
+ token_id: tokenId,
2614
+ amount: amount.toString(),
2615
+ expiry: expiry.toString(),
2616
+ nonce: String(nonce),
2617
+ signature
2618
+ };
2619
+ }
2620
+ var EXPIRY_SLACK_SECONDS = 60;
2621
+ function isSignedLockUsable(payload) {
2622
+ const expiry = Number(payload.expiry);
2623
+ if (!Number.isFinite(expiry)) return false;
2624
+ return expiry > Math.floor(Date.now() / 1e3) + EXPIRY_SLACK_SECONDS;
2625
+ }
2626
+ var PostDepositLockError = class _PostDepositLockError extends Error {
2627
+ constructor(message, reason, signedAmount, creditedAmount, options) {
2628
+ super(message, options);
2629
+ this.reason = reason;
2630
+ this.signedAmount = signedAmount;
2631
+ this.creditedAmount = creditedAmount;
2632
+ this.name = "PostDepositLockError";
2633
+ this.submissionMayHaveSucceeded = options?.submissionMayHaveSucceeded ?? false;
2634
+ Object.setPrototypeOf(this, _PostDepositLockError.prototype);
2635
+ }
2636
+ };
2637
+ async function submitPendingLock({
2638
+ client,
2639
+ payload,
2640
+ creditedAmount,
2641
+ beforeSubmit
2642
+ }) {
2643
+ let signedAmount;
2644
+ try {
2645
+ signedAmount = BigInt(payload.amount);
2646
+ } catch (err) {
2647
+ throw new PostDepositLockError(
2648
+ "Stored signed lock payload is malformed",
2649
+ "submission-failed",
2650
+ void 0,
2651
+ creditedAmount,
2652
+ { cause: err }
2653
+ );
2654
+ }
2655
+ if (!isSignedLockUsable(payload)) {
2656
+ throw new PostDepositLockError(
2657
+ "Signed lock expired before the deposit was credited",
2658
+ "expired",
2659
+ signedAmount,
2660
+ creditedAmount
2661
+ );
2662
+ }
2663
+ if (creditedAmount !== void 0 && creditedAmount < signedAmount) {
2664
+ throw new PostDepositLockError(
2665
+ "Credited amount is below the signed lock amount",
2666
+ "credited-below-signed",
2667
+ signedAmount,
2668
+ creditedAmount
2669
+ );
2670
+ }
2671
+ try {
2672
+ beforeSubmit?.();
2673
+ return await client.lockFunds(payload);
2674
+ } catch (err) {
2675
+ throw new PostDepositLockError(
2676
+ err instanceof Error ? err.message : "Lock submission failed",
2677
+ "submission-failed",
2678
+ signedAmount,
2679
+ creditedAmount,
2680
+ { cause: err }
2681
+ );
2682
+ }
2683
+ }
2684
+ function pendingLockKey(userAddress, correlationId) {
2685
+ return `privana:pending-lock:${userAddress.toLowerCase()}:${correlationId}`;
2686
+ }
2687
+ function savePendingLock(userAddress, correlationId, payload) {
2688
+ const record = { payload, savedAt: Date.now() };
2689
+ const stored = setBrowserStorageItem(
2690
+ pendingLockKey(userAddress, correlationId),
2691
+ JSON.stringify(record)
2692
+ );
2693
+ if (!stored) {
2694
+ throw new Error("Unable to persist signed lock for recovery");
2695
+ }
2696
+ }
2697
+ function loadPendingLock(userAddress, correlationId) {
2698
+ const key = pendingLockKey(userAddress, correlationId);
2699
+ try {
2700
+ const raw = getBrowserStorageItem(key);
2701
+ if (!raw) return void 0;
2702
+ const record = JSON.parse(raw);
2703
+ if (!record?.payload?.signature) {
2704
+ removeBrowserStorageItem(key);
2705
+ return void 0;
2706
+ }
2707
+ return record.payload;
2708
+ } catch {
2709
+ return void 0;
2710
+ }
2711
+ }
2712
+ function clearPendingLock(userAddress, correlationId) {
2713
+ removeBrowserStorageItem(pendingLockKey(userAddress, correlationId));
2714
+ }
2706
2715
  var BYTES32_PATTERN = /^0x[0-9a-fA-F]{64}$/;
2707
2716
  var PROVIDER_ASSET_CODE_PATTERN = /^[a-zA-Z0-9_-]{1,64}$/;
2708
2717
  var TRANSAK_MINIMUM_TARGET_PERCENT = 105n;
@@ -6057,5 +6066,5 @@ function useTransakOnRamp(options) {
6057
6066
  }
6058
6067
 
6059
6068
  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, canRetryOnRampVerification, canUseBrowserStorage, canUseSharedBrowserStorage, clampLockAmount, clearHostedAuthPendingTransaction, clearPendingLock, cn, createDomain, createHostedAuthPendingStorageKey, createHostedAuthState, createHostedAuthStorageKey, createLockExpiry, createPkceChallenge, createPkceVerifier, createProductOnRampFlowSnapshot, createProductOnRampOutcomeCallbacks, createSignedLockRequest, formatCountdown, formatTimeRemaining, formatTokenAmount, getAccountingContract, getApiUrl, getBlockNumber, getBrowserStorageItem, getChainById, getChainId, getExplorerAddressUrl, getExplorerLabel, getSharedBrowserStorageItem, getTransactionReceipt, getTransakMinimumTargetBaseUnits, getWalletClient3 as getWalletClient, isHostedAuthRefreshActive, isHostedAuthSessionActive, isMoonPayProductOnRamp, isSignedLockUsable, loadPendingLock, matchesFrozenOnRampToken, matchesOnRampTransaction, matchesProductOnRampScope, normalizeAddress, normalizeHex, parseHostedAuthCallback, parseTokenAmount, persistHostedAuthPendingTransaction, readHostedAuthPendingTransaction, readStoredHostedAuthSession, removeBrowserStorageItem, removeSharedBrowserStorageItem, requireDepositLockOwner, requireServiceAddress, resolveProductOnRamp, savePendingLock, setBrowserStorageItem, setSharedBrowserStorageItem, shortenAddress, signLockMessage, signModifyLockMessage, signTransferLockedMessage, signTransferMessage, signWithdrawFromLockMessage, signWithdrawMessage, stripHostedAuthCallbackParams, submitPendingLock, syncHostedAuthSessionToClient, useDepositVerification, useEnsureCorrectChain, useFiatOnRamp, usePrivanaContext, usePrivateReadRequest, useSafeAccount, useSafePrivanaContext, useSiweAuth, useTransakOnRamp, waitForTransactionReceipt };
6060
- //# sourceMappingURL=chunk-RDYNQUMV.js.map
6061
- //# sourceMappingURL=chunk-RDYNQUMV.js.map
6069
+ //# sourceMappingURL=chunk-OHSIARZ3.js.map
6070
+ //# sourceMappingURL=chunk-OHSIARZ3.js.map