@oasisprotocol/privana-sdk 0.5.0 → 0.5.2

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,16 +1,17 @@
1
1
  "use client";
2
2
  import { createContext, useContext, useRef, useCallback, useSyncExternalStore, useState, useEffect, useMemo } from 'react';
3
- import { WagmiContext, useConfig, useAccount } from 'wagmi';
3
+ import { WagmiContext, useConfig, useChainId, useSwitchChain, useAccount, useWalletClient } from 'wagmi';
4
4
  import { watchAccount, getAccount, getWalletClient } from 'wagmi/actions';
5
5
  import { createSiweMessage } from 'viem/siwe';
6
6
  import { jsx, jsxs } from 'react/jsx-runtime';
7
- import { parseAbiItem, zeroAddress, hexToString, parseUnits, decodeEventLog, formatUnits } from 'viem';
7
+ import { parseAbiItem, zeroAddress, walletActions, hexToString, parseUnits, decodeEventLog, formatUnits, createClient, custom } from 'viem';
8
8
  import { useQueryClient } from '@tanstack/react-query';
9
9
  import { Slot } from '@radix-ui/react-slot';
10
10
  import { cva } from 'class-variance-authority';
11
11
  import { clsx } from 'clsx';
12
12
  import { twMerge } from 'tailwind-merge';
13
- import { getTransactionReceipt as getTransactionReceipt$1, waitForTransactionReceipt as waitForTransactionReceipt$1, getTransaction, call } from 'viem/actions';
13
+ import { parseAccount, getAddress } from 'viem/utils';
14
+ import { getBlockNumber as getBlockNumber$1, getTransactionReceipt as getTransactionReceipt$1, waitForTransactionReceipt as waitForTransactionReceipt$1, getTransaction, call } from 'viem/actions';
14
15
  import { Loader2, CircleCheckIcon } from 'lucide-react';
15
16
  import { MoonPayBuyWidget } from '@moonpay/moonpay-react';
16
17
 
@@ -34,7 +35,7 @@ var config_default = {
34
35
  testnet: {
35
36
  chainId: 23295,
36
37
  name: "Sapphire Testnet",
37
- accountingContract: "0xaF8e5de153A584528B57DD4B9B0195956BBDF571",
38
+ accountingContract: "0xad3C76e4E621C0cfF7540479Ee9B0A945723A642",
38
39
  apiUrl: "https://api.testnet.privana.finance"
39
40
  },
40
41
  mainnet: {
@@ -372,6 +373,19 @@ var PrivanaClient = class {
372
373
  async getDepositStatus(depositId) {
373
374
  return this.http.get(`/v1/accounting/deposits/status/${depositId}`);
374
375
  }
376
+ async getPendingDeposits(request) {
377
+ const params = new URLSearchParams({ chain_id: String(request.chain_id) });
378
+ if (request.version !== void 0) params.set("version", String(request.version));
379
+ if (request.token_address !== void 0) {
380
+ params.set("token_address", normalizeAddress(request.token_address));
381
+ }
382
+ if (request.lookback_blocks !== void 0) {
383
+ params.set("lookback_blocks", String(request.lookback_blocks));
384
+ }
385
+ return this.http.get(
386
+ `/v1/accounting/deposits/pending?${params.toString()}`
387
+ );
388
+ }
375
389
  async getBalance(tokenId) {
376
390
  const token = normalizeHex(tokenId);
377
391
  return this.http.get(`/v1/accounting/balances/${token}`);
@@ -623,6 +637,202 @@ var PrivanaClient = class {
623
637
  this.http.removeHeader("Authorization");
624
638
  }
625
639
  };
640
+
641
+ // src/sdk/signatures/eip712-types.ts
642
+ function createDomain(chainId, verifyingContract) {
643
+ return {
644
+ name: "AccountingModule",
645
+ version: "1",
646
+ chainId,
647
+ verifyingContract
648
+ };
649
+ }
650
+ var LOCK_TYPES = {
651
+ Lock: [
652
+ { name: "serviceAddress", type: "address" },
653
+ { name: "tokenId", type: "bytes32" },
654
+ { name: "amount", type: "uint256" },
655
+ { name: "expiry", type: "uint256" },
656
+ { name: "nonce", type: "uint256" }
657
+ ]
658
+ };
659
+ var TRANSFER_TYPES = {
660
+ Transfer: [
661
+ { name: "toAddress", type: "address" },
662
+ { name: "tokenId", type: "bytes32" },
663
+ { name: "amount", type: "uint256" },
664
+ { name: "nonce", type: "uint256" }
665
+ ]
666
+ };
667
+ var TRANSFER_LOCKED_TYPES = {
668
+ TransferLocked: [
669
+ { name: "userAddress", type: "address" },
670
+ { name: "toAddress", type: "address" },
671
+ { name: "lockId", type: "uint256" },
672
+ { name: "amount", type: "uint256" },
673
+ { name: "nonce", type: "uint256" },
674
+ { name: "serviceAddress", type: "address" }
675
+ ]
676
+ };
677
+ var WITHDRAW_TYPES = {
678
+ Withdraw: [
679
+ { name: "tokenId", type: "bytes32" },
680
+ { name: "amount", type: "uint256" },
681
+ { name: "nonce", type: "uint256" }
682
+ ]
683
+ };
684
+ var MODIFY_LOCK_TYPES = {
685
+ ModifyLock: [
686
+ { name: "lockId", type: "uint256" },
687
+ { name: "amount", type: "uint256" },
688
+ { name: "newExpiry", type: "uint256" },
689
+ { name: "nonce", type: "uint256" }
690
+ ]
691
+ };
692
+ var WITHDRAW_FROM_LOCK_TYPES = {
693
+ WithdrawFromLock: [
694
+ { name: "userAddress", type: "address" },
695
+ { name: "toAddress", type: "address" },
696
+ { name: "lockId", type: "uint256" },
697
+ { name: "amount", type: "uint256" },
698
+ { name: "nonce", type: "uint256" }
699
+ ]
700
+ };
701
+
702
+ // src/sdk/signatures/sign-lock.ts
703
+ async function signLockMessage({
704
+ walletClient,
705
+ chainId,
706
+ verifyingContract,
707
+ message
708
+ }) {
709
+ const account = walletClient.account;
710
+ if (!account) {
711
+ throw new Error("No account connected to wallet client");
712
+ }
713
+ const domain = createDomain(chainId, verifyingContract);
714
+ const signature = await walletClient.signTypedData({
715
+ account,
716
+ domain,
717
+ types: LOCK_TYPES,
718
+ primaryType: "Lock",
719
+ message
720
+ });
721
+ return signature;
722
+ }
723
+ function createLockExpiry(minutesFromNow = 60) {
724
+ return BigInt(Math.floor(Date.now() / 1e3) + minutesFromNow * 60);
725
+ }
726
+
727
+ // src/sdk/signatures/sign-modify-lock.ts
728
+ async function signModifyLockMessage({
729
+ walletClient,
730
+ chainId,
731
+ verifyingContract,
732
+ message
733
+ }) {
734
+ const account = walletClient.account;
735
+ if (!account) {
736
+ throw new Error("No account connected to wallet client");
737
+ }
738
+ const domain = createDomain(chainId, verifyingContract);
739
+ const signature = await walletClient.signTypedData({
740
+ account,
741
+ domain,
742
+ types: MODIFY_LOCK_TYPES,
743
+ primaryType: "ModifyLock",
744
+ message
745
+ });
746
+ return signature;
747
+ }
748
+
749
+ // src/sdk/signatures/sign-transfer.ts
750
+ async function signTransferMessage({
751
+ walletClient,
752
+ chainId,
753
+ verifyingContract,
754
+ message
755
+ }) {
756
+ const account = walletClient.account;
757
+ if (!account) {
758
+ throw new Error("No account connected to wallet client");
759
+ }
760
+ const domain = createDomain(chainId, verifyingContract);
761
+ const signature = await walletClient.signTypedData({
762
+ account,
763
+ domain,
764
+ types: TRANSFER_TYPES,
765
+ primaryType: "Transfer",
766
+ message
767
+ });
768
+ return signature;
769
+ }
770
+
771
+ // src/sdk/signatures/sign-transfer-locked.ts
772
+ async function signTransferLockedMessage({
773
+ walletClient,
774
+ chainId,
775
+ verifyingContract,
776
+ message
777
+ }) {
778
+ const account = walletClient.account;
779
+ if (!account) {
780
+ throw new Error("No account connected to wallet client");
781
+ }
782
+ const domain = createDomain(chainId, verifyingContract);
783
+ const signature = await walletClient.signTypedData({
784
+ account,
785
+ domain,
786
+ types: TRANSFER_LOCKED_TYPES,
787
+ primaryType: "TransferLocked",
788
+ message
789
+ });
790
+ return signature;
791
+ }
792
+
793
+ // src/sdk/signatures/sign-withdraw.ts
794
+ async function signWithdrawMessage({
795
+ walletClient,
796
+ chainId,
797
+ verifyingContract,
798
+ message
799
+ }) {
800
+ const account = walletClient.account;
801
+ if (!account) {
802
+ throw new Error("No account connected to wallet client");
803
+ }
804
+ const domain = createDomain(chainId, verifyingContract);
805
+ const signature = await walletClient.signTypedData({
806
+ account,
807
+ domain,
808
+ types: WITHDRAW_TYPES,
809
+ primaryType: "Withdraw",
810
+ message
811
+ });
812
+ return signature;
813
+ }
814
+
815
+ // src/sdk/signatures/sign-withdraw-from-lock.ts
816
+ async function signWithdrawFromLockMessage({
817
+ walletClient,
818
+ chainId,
819
+ verifyingContract,
820
+ message
821
+ }) {
822
+ const account = walletClient.account;
823
+ if (!account) {
824
+ throw new Error("No account connected to wallet client");
825
+ }
826
+ const domain = createDomain(chainId, verifyingContract);
827
+ const signature = await walletClient.signTypedData({
828
+ account,
829
+ domain,
830
+ types: WITHDRAW_FROM_LOCK_TYPES,
831
+ primaryType: "WithdrawFromLock",
832
+ message
833
+ });
834
+ return signature;
835
+ }
626
836
  var defaultResult = {
627
837
  address: void 0,
628
838
  isConnected: false,
@@ -1168,6 +1378,205 @@ function usePrivanaContext() {
1168
1378
  function useSafePrivanaContext() {
1169
1379
  return useContext(PrivanaContext);
1170
1380
  }
1381
+
1382
+ // src/sdk/hooks/browser-storage.ts
1383
+ function storageCandidate(name) {
1384
+ try {
1385
+ if (typeof window === "undefined") return void 0;
1386
+ return window[name] ?? void 0;
1387
+ } catch {
1388
+ return void 0;
1389
+ }
1390
+ }
1391
+ function storageCandidates() {
1392
+ return [storageCandidate("localStorage"), storageCandidate("sessionStorage")].filter(
1393
+ (storage) => storage !== void 0
1394
+ );
1395
+ }
1396
+ function canUseBrowserStorage() {
1397
+ const probeKey = "privana:storage-probe";
1398
+ for (const storage of storageCandidates()) {
1399
+ try {
1400
+ storage.setItem(probeKey, "1");
1401
+ storage.removeItem(probeKey);
1402
+ return true;
1403
+ } catch {
1404
+ }
1405
+ }
1406
+ return false;
1407
+ }
1408
+ function setBrowserStorageItem(key, value) {
1409
+ let stored = false;
1410
+ for (const storage of storageCandidates()) {
1411
+ try {
1412
+ storage.setItem(key, value);
1413
+ stored = true;
1414
+ } catch {
1415
+ }
1416
+ }
1417
+ return stored;
1418
+ }
1419
+ function getBrowserStorageItem(key) {
1420
+ for (const storage of storageCandidates()) {
1421
+ try {
1422
+ const value = storage.getItem(key);
1423
+ if (value !== null) return value;
1424
+ } catch {
1425
+ }
1426
+ }
1427
+ return null;
1428
+ }
1429
+ function removeBrowserStorageItem(key) {
1430
+ for (const storage of storageCandidates()) {
1431
+ try {
1432
+ storage.removeItem(key);
1433
+ } catch {
1434
+ }
1435
+ }
1436
+ }
1437
+
1438
+ // src/sdk/hooks/pending-lock.ts
1439
+ var DEFAULT_LOCK_DURATION_SECONDS = 259200;
1440
+ var DEFAULT_ONRAMP_LOCK_BUFFER = 0.02;
1441
+ var BUFFER_SCALE = 1000000n;
1442
+ function applyLockBuffer(amount, buffer = DEFAULT_ONRAMP_LOCK_BUFFER) {
1443
+ if (!Number.isFinite(buffer) || buffer < 0 || buffer >= 1) {
1444
+ throw new Error(`Lock buffer must be in [0, 1), got ${buffer}`);
1445
+ }
1446
+ const shave = BigInt(Math.max(0, Math.ceil(buffer * Number(BUFFER_SCALE) - 1e-6)));
1447
+ return amount * (BUFFER_SCALE - shave) / BUFFER_SCALE;
1448
+ }
1449
+ function clampLockAmount(amount, maxAmount) {
1450
+ return maxAmount !== void 0 && maxAmount < amount ? maxAmount : amount;
1451
+ }
1452
+ async function createSignedLockRequest({
1453
+ client,
1454
+ walletClient,
1455
+ userAddress,
1456
+ networkConfig,
1457
+ serviceAddress,
1458
+ tokenId,
1459
+ amount,
1460
+ lockDuration = DEFAULT_LOCK_DURATION_SECONDS
1461
+ }) {
1462
+ if (amount <= 0n) {
1463
+ throw new Error("Lock amount must be positive");
1464
+ }
1465
+ const expiry = BigInt(Math.floor(Date.now() / 1e3) + lockDuration);
1466
+ const { nonce } = await client.getLockNonce(userAddress);
1467
+ const signature = await signLockMessage({
1468
+ walletClient,
1469
+ chainId: networkConfig.chainId,
1470
+ verifyingContract: networkConfig.accountingContract,
1471
+ message: {
1472
+ serviceAddress,
1473
+ tokenId,
1474
+ amount,
1475
+ expiry,
1476
+ nonce: BigInt(nonce)
1477
+ }
1478
+ });
1479
+ return {
1480
+ service_address: serviceAddress,
1481
+ token_id: tokenId,
1482
+ amount: amount.toString(),
1483
+ expiry: expiry.toString(),
1484
+ nonce: String(nonce),
1485
+ signature
1486
+ };
1487
+ }
1488
+ var EXPIRY_SLACK_SECONDS = 60;
1489
+ function isSignedLockUsable(payload) {
1490
+ const expiry = Number(payload.expiry);
1491
+ if (!Number.isFinite(expiry)) return false;
1492
+ return expiry > Math.floor(Date.now() / 1e3) + EXPIRY_SLACK_SECONDS;
1493
+ }
1494
+ var PostDepositLockError = class _PostDepositLockError extends Error {
1495
+ constructor(message, reason, signedAmount, creditedAmount, options) {
1496
+ super(message, options);
1497
+ this.reason = reason;
1498
+ this.signedAmount = signedAmount;
1499
+ this.creditedAmount = creditedAmount;
1500
+ this.name = "PostDepositLockError";
1501
+ Object.setPrototypeOf(this, _PostDepositLockError.prototype);
1502
+ }
1503
+ };
1504
+ async function submitPendingLock({
1505
+ client,
1506
+ payload,
1507
+ creditedAmount
1508
+ }) {
1509
+ let signedAmount;
1510
+ try {
1511
+ signedAmount = BigInt(payload.amount);
1512
+ } catch (err) {
1513
+ throw new PostDepositLockError(
1514
+ "Stored signed lock payload is malformed",
1515
+ "submission-failed",
1516
+ void 0,
1517
+ creditedAmount,
1518
+ { cause: err }
1519
+ );
1520
+ }
1521
+ if (!isSignedLockUsable(payload)) {
1522
+ throw new PostDepositLockError(
1523
+ "Signed lock expired before the deposit was credited",
1524
+ "expired",
1525
+ signedAmount,
1526
+ creditedAmount
1527
+ );
1528
+ }
1529
+ if (creditedAmount !== void 0 && creditedAmount < signedAmount) {
1530
+ throw new PostDepositLockError(
1531
+ `Credited amount (${creditedAmount}) is below the signed lock amount (${signedAmount})`,
1532
+ "credited-below-signed",
1533
+ signedAmount,
1534
+ creditedAmount
1535
+ );
1536
+ }
1537
+ try {
1538
+ return await client.lockFunds(payload);
1539
+ } catch (err) {
1540
+ throw new PostDepositLockError(
1541
+ err instanceof Error ? err.message : "Lock submission failed",
1542
+ "submission-failed",
1543
+ signedAmount,
1544
+ creditedAmount,
1545
+ { cause: err }
1546
+ );
1547
+ }
1548
+ }
1549
+ function pendingLockKey(userAddress, correlationId) {
1550
+ return `privana:pending-lock:${userAddress.toLowerCase()}:${correlationId}`;
1551
+ }
1552
+ function savePendingLock(userAddress, correlationId, payload) {
1553
+ const record = { payload, savedAt: Date.now() };
1554
+ const stored = setBrowserStorageItem(
1555
+ pendingLockKey(userAddress, correlationId),
1556
+ JSON.stringify(record)
1557
+ );
1558
+ if (!stored) {
1559
+ throw new Error("Unable to persist signed lock for recovery");
1560
+ }
1561
+ }
1562
+ function loadPendingLock(userAddress, correlationId) {
1563
+ const key = pendingLockKey(userAddress, correlationId);
1564
+ try {
1565
+ const raw = getBrowserStorageItem(key);
1566
+ if (!raw) return void 0;
1567
+ const record = JSON.parse(raw);
1568
+ if (!record?.payload?.signature) {
1569
+ removeBrowserStorageItem(key);
1570
+ return void 0;
1571
+ }
1572
+ return record.payload;
1573
+ } catch {
1574
+ return void 0;
1575
+ }
1576
+ }
1577
+ function clearPendingLock(userAddress, correlationId) {
1578
+ removeBrowserStorageItem(pendingLockKey(userAddress, correlationId));
1579
+ }
1171
1580
  var INITIAL_AUTH_BACKOFF_MS = 5e3;
1172
1581
  var MAX_AUTH_BACKOFF_MS = 6e4;
1173
1582
  var DEFAULT_SIWE_AUTH_VALIDITY_MS = 24 * 60 * 60 * 1e3;
@@ -1399,7 +1808,7 @@ function useDepositVerification(options = {}) {
1399
1808
  const runVerification = useCallback(
1400
1809
  async (ctx, generation) => {
1401
1810
  const isStale = () => generation !== generationRef.current;
1402
- const { hash, chainId, amount } = ctx;
1811
+ const { hash, chainId, amount, logIndex } = ctx;
1403
1812
  setVerificationFailed(false);
1404
1813
  setError(null);
1405
1814
  setDidTimeout(false);
@@ -1427,7 +1836,8 @@ function useDepositVerification(options = {}) {
1427
1836
  () => client.checkDeposit({
1428
1837
  chain_id: chainId,
1429
1838
  tx_hash: hash,
1430
- amount: amount.toString()
1839
+ amount: amount.toString(),
1840
+ log_index: logIndex
1431
1841
  })
1432
1842
  );
1433
1843
  if (result.status === "error" && isInsufficientFinalityMessage(result.detail)) {
@@ -1632,6 +2042,12 @@ function formatTimeRemaining(expiryTimestamp) {
1632
2042
  }
1633
2043
  return `${minutes}m left`;
1634
2044
  }
2045
+ function formatCountdown(secondsLeft) {
2046
+ const clamped = Math.max(0, secondsLeft);
2047
+ const minutes = Math.floor(clamped / 60);
2048
+ const seconds = clamped % 60;
2049
+ return `${minutes}m:${String(seconds).padStart(2, "0")}s`;
2050
+ }
1635
2051
  var buttonVariants = cva(
1636
2052
  "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
1637
2053
  {
@@ -1700,6 +2116,204 @@ function getAction(client, actionFn, name) {
1700
2116
  return (params) => actionFn(client, params);
1701
2117
  }
1702
2118
 
2119
+ // ../../node_modules/@wagmi/core/dist/esm/version.js
2120
+ var version = "3.3.1";
2121
+
2122
+ // ../../node_modules/@wagmi/core/dist/esm/utils/getVersion.js
2123
+ var getVersion = () => `@wagmi/core@${version}`;
2124
+
2125
+ // ../../node_modules/@wagmi/core/dist/esm/errors/base.js
2126
+ var __classPrivateFieldGet = function(receiver, state, kind, f) {
2127
+ if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
2128
+ return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
2129
+ };
2130
+ var _BaseError_instances;
2131
+ var _BaseError_walk;
2132
+ var BaseError = class _BaseError extends Error {
2133
+ get docsBaseUrl() {
2134
+ return "https://wagmi.sh/core";
2135
+ }
2136
+ get version() {
2137
+ return getVersion();
2138
+ }
2139
+ constructor(shortMessage, options = {}) {
2140
+ super();
2141
+ _BaseError_instances.add(this);
2142
+ Object.defineProperty(this, "details", {
2143
+ enumerable: true,
2144
+ configurable: true,
2145
+ writable: true,
2146
+ value: void 0
2147
+ });
2148
+ Object.defineProperty(this, "docsPath", {
2149
+ enumerable: true,
2150
+ configurable: true,
2151
+ writable: true,
2152
+ value: void 0
2153
+ });
2154
+ Object.defineProperty(this, "metaMessages", {
2155
+ enumerable: true,
2156
+ configurable: true,
2157
+ writable: true,
2158
+ value: void 0
2159
+ });
2160
+ Object.defineProperty(this, "shortMessage", {
2161
+ enumerable: true,
2162
+ configurable: true,
2163
+ writable: true,
2164
+ value: void 0
2165
+ });
2166
+ Object.defineProperty(this, "name", {
2167
+ enumerable: true,
2168
+ configurable: true,
2169
+ writable: true,
2170
+ value: "WagmiCoreError"
2171
+ });
2172
+ const details = options.cause instanceof _BaseError ? options.cause.details : options.cause?.message ? options.cause.message : options.details;
2173
+ const docsPath = options.cause instanceof _BaseError ? options.cause.docsPath || options.docsPath : options.docsPath;
2174
+ this.message = [
2175
+ shortMessage || "An error occurred.",
2176
+ "",
2177
+ ...options.metaMessages ? [...options.metaMessages, ""] : [],
2178
+ ...docsPath ? [
2179
+ `Docs: ${this.docsBaseUrl}${docsPath}.html${options.docsSlug ? `#${options.docsSlug}` : ""}`
2180
+ ] : [],
2181
+ ...details ? [`Details: ${details}`] : [],
2182
+ `Version: ${this.version}`
2183
+ ].join("\n");
2184
+ if (options.cause)
2185
+ this.cause = options.cause;
2186
+ this.details = details;
2187
+ this.docsPath = docsPath;
2188
+ this.metaMessages = options.metaMessages;
2189
+ this.shortMessage = shortMessage;
2190
+ }
2191
+ walk(fn) {
2192
+ return __classPrivateFieldGet(this, _BaseError_instances, "m", _BaseError_walk).call(this, this, fn);
2193
+ }
2194
+ };
2195
+ _BaseError_instances = /* @__PURE__ */ new WeakSet(), _BaseError_walk = function _BaseError_walk2(err, fn) {
2196
+ if (fn?.(err))
2197
+ return err;
2198
+ if (err.cause)
2199
+ return __classPrivateFieldGet(this, _BaseError_instances, "m", _BaseError_walk2).call(this, err.cause, fn);
2200
+ return err;
2201
+ };
2202
+
2203
+ // ../../node_modules/@wagmi/core/dist/esm/errors/config.js
2204
+ var ConnectorNotConnectedError = class extends BaseError {
2205
+ constructor() {
2206
+ super("Connector not connected.");
2207
+ Object.defineProperty(this, "name", {
2208
+ enumerable: true,
2209
+ configurable: true,
2210
+ writable: true,
2211
+ value: "ConnectorNotConnectedError"
2212
+ });
2213
+ }
2214
+ };
2215
+ var ConnectorAccountNotFoundError = class extends BaseError {
2216
+ constructor({ address, connector }) {
2217
+ super(`Account "${address}" not found for connector "${connector.name}".`);
2218
+ Object.defineProperty(this, "name", {
2219
+ enumerable: true,
2220
+ configurable: true,
2221
+ writable: true,
2222
+ value: "ConnectorAccountNotFoundError"
2223
+ });
2224
+ }
2225
+ };
2226
+ var ConnectorChainMismatchError = class extends BaseError {
2227
+ constructor({ connectionChainId, connectorChainId }) {
2228
+ super(`The current chain of the connector (id: ${connectorChainId}) does not match the connection's chain (id: ${connectionChainId}).`, {
2229
+ metaMessages: [
2230
+ `Current Chain ID: ${connectorChainId}`,
2231
+ `Expected Chain ID: ${connectionChainId}`
2232
+ ]
2233
+ });
2234
+ Object.defineProperty(this, "name", {
2235
+ enumerable: true,
2236
+ configurable: true,
2237
+ writable: true,
2238
+ value: "ConnectorChainMismatchError"
2239
+ });
2240
+ }
2241
+ };
2242
+ var ConnectorUnavailableReconnectingError = class extends BaseError {
2243
+ constructor({ connector }) {
2244
+ super(`Connector "${connector.name}" unavailable while reconnecting.`, {
2245
+ details: [
2246
+ "During the reconnection step, the only connector methods guaranteed to be available are: `id`, `name`, `type`, `uid`.",
2247
+ "All other methods are not guaranteed to be available until reconnection completes and connectors are fully restored.",
2248
+ "This error commonly occurs for connectors that asynchronously inject after reconnection has already started."
2249
+ ].join(" ")
2250
+ });
2251
+ Object.defineProperty(this, "name", {
2252
+ enumerable: true,
2253
+ configurable: true,
2254
+ writable: true,
2255
+ value: "ConnectorUnavailableReconnectingError"
2256
+ });
2257
+ }
2258
+ };
2259
+ async function getConnectorClient(config, parameters = {}) {
2260
+ const { assertChainId = true } = parameters;
2261
+ let connection;
2262
+ if (parameters.connector) {
2263
+ const { connector: connector2 } = parameters;
2264
+ if (config.state.status === "reconnecting" && !connector2.getAccounts && !connector2.getChainId)
2265
+ throw new ConnectorUnavailableReconnectingError({ connector: connector2 });
2266
+ const [accounts, chainId2] = await Promise.all([
2267
+ connector2.getAccounts().catch((e) => {
2268
+ if (parameters.account === null)
2269
+ return [];
2270
+ throw e;
2271
+ }),
2272
+ connector2.getChainId()
2273
+ ]);
2274
+ connection = {
2275
+ accounts,
2276
+ chainId: chainId2,
2277
+ connector: connector2
2278
+ };
2279
+ } else
2280
+ connection = config.state.connections.get(config.state.current);
2281
+ if (!connection)
2282
+ throw new ConnectorNotConnectedError();
2283
+ const chainId = parameters.chainId ?? connection.chainId;
2284
+ const connectorChainId = await connection.connector.getChainId();
2285
+ if (assertChainId && connectorChainId !== chainId)
2286
+ throw new ConnectorChainMismatchError({
2287
+ connectionChainId: chainId,
2288
+ connectorChainId
2289
+ });
2290
+ const connector = connection.connector;
2291
+ if (connector.getClient)
2292
+ return connector.getClient({ chainId });
2293
+ const account = parseAccount(parameters.account ?? connection.accounts[0]);
2294
+ if (account)
2295
+ account.address = getAddress(account.address);
2296
+ if (parameters.account && !connection.accounts.some((x) => x.toLowerCase() === account.address.toLowerCase()))
2297
+ throw new ConnectorAccountNotFoundError({
2298
+ address: account.address,
2299
+ connector
2300
+ });
2301
+ const chain = config.chains.find((chain2) => chain2.id === chainId);
2302
+ const provider = await connection.connector.getProvider({ chainId });
2303
+ return createClient({
2304
+ account,
2305
+ chain,
2306
+ name: "Connector Client",
2307
+ transport: (opts) => custom(provider)({ ...opts, retryCount: 0 })
2308
+ });
2309
+ }
2310
+ function getBlockNumber(config, parameters = {}) {
2311
+ const { chainId, ...rest } = parameters;
2312
+ const client = config.getClient({ chainId });
2313
+ const action = getAction(client, getBlockNumber$1, "getBlockNumber");
2314
+ return action(rest);
2315
+ }
2316
+
1703
2317
  // ../../node_modules/@wagmi/core/dist/esm/actions/getChainId.js
1704
2318
  function getChainId2(config) {
1705
2319
  return config.state.chainId;
@@ -1710,6 +2324,10 @@ async function getTransactionReceipt(config, parameters) {
1710
2324
  const action = getAction(client, getTransactionReceipt$1, "getTransactionReceipt");
1711
2325
  return action(rest);
1712
2326
  }
2327
+ async function getWalletClient3(config, parameters = {}) {
2328
+ const client = await getConnectorClient(config, parameters);
2329
+ return client.extend(walletActions);
2330
+ }
1713
2331
  async function waitForTransactionReceipt(config, parameters) {
1714
2332
  const { chainId, timeout = 0, ...rest } = parameters;
1715
2333
  const client = config.getClient({ chainId });
@@ -1737,6 +2355,59 @@ async function waitForTransactionReceipt(config, parameters) {
1737
2355
  chainId: client.chain.id
1738
2356
  };
1739
2357
  }
2358
+ function useEnsureCorrectChain() {
2359
+ const config = useConfig();
2360
+ const chainId = useChainId();
2361
+ const { switchChainAsync } = useSwitchChain();
2362
+ const waitUntilOnChain = useCallback(
2363
+ async (expectedChainId, timeoutMs, pollIntervalMs = 250) => {
2364
+ const startedAt = Date.now();
2365
+ while (Date.now() - startedAt < timeoutMs) {
2366
+ const currentChainId = getChainId2(config);
2367
+ if (currentChainId === expectedChainId) return true;
2368
+ await new Promise((resolve) => setTimeout(resolve, pollIntervalMs));
2369
+ }
2370
+ return false;
2371
+ },
2372
+ [config]
2373
+ );
2374
+ const ensureCorrectChain = useCallback(
2375
+ async (targetChainId) => {
2376
+ const currentChainId = getChainId2(config);
2377
+ if (currentChainId === targetChainId) return false;
2378
+ let switchErrorMessage;
2379
+ try {
2380
+ const timeoutPromise = new Promise((_, reject) => {
2381
+ setTimeout(() => reject(new Error("Chain switch timeout")), 3e3);
2382
+ });
2383
+ await Promise.race([switchChainAsync({ chainId: targetChainId }), timeoutPromise]);
2384
+ } catch (error) {
2385
+ if (error instanceof Error && error.message.includes("Unsupported Chain")) {
2386
+ console.warn("Got 'Unsupported Chain' error, chain may have switched anyway.");
2387
+ } else {
2388
+ switchErrorMessage = error instanceof Error ? error.message : "Unknown chain switch error";
2389
+ }
2390
+ }
2391
+ const settled = await waitUntilOnChain(targetChainId, 2e4);
2392
+ if (settled) return true;
2393
+ if (switchErrorMessage) {
2394
+ throw new Error(
2395
+ `Failed to switch to chain (Chain ID: ${targetChainId}): ${switchErrorMessage}`
2396
+ );
2397
+ }
2398
+ throw new Error(`Chain switch did not settle in time (expected ${targetChainId}).`);
2399
+ },
2400
+ [config, switchChainAsync, waitUntilOnChain]
2401
+ );
2402
+ const isOnChain = useCallback((targetChainId) => chainId === targetChainId, [chainId]);
2403
+ return {
2404
+ chainId,
2405
+ ensureCorrectChain,
2406
+ isOnChain
2407
+ };
2408
+ }
2409
+
2410
+ // src/sdk/hooks/use-fiat-on-ramp.ts
1740
2411
  var DEFAULT_DELIVERY_TIMEOUT_MS = 12e4;
1741
2412
  var DEFAULT_VERIFICATION_TIMEOUT_MS = 10 * 6e4;
1742
2413
  var DEFAULT_FINALITY_RETRY_INTERVAL_MS = 15e3;
@@ -1744,14 +2415,26 @@ var ERC20_TRANSFER_EVENT = parseAbiItem(
1744
2415
  "event Transfer(address indexed from, address indexed to, uint256 value)"
1745
2416
  );
1746
2417
  function useFiatOnRamp(options) {
1747
- const { tokenId, onCredited, onError, onDebugEvent } = options;
2418
+ const {
2419
+ tokenId,
2420
+ postDepositLock,
2421
+ onCredited,
2422
+ onLockSubmitted,
2423
+ onLockFailed,
2424
+ onError,
2425
+ onDebugEvent
2426
+ } = options;
1748
2427
  const deliveryTimeout = options.deliveryTimeout ?? DEFAULT_DELIVERY_TIMEOUT_MS;
1749
2428
  const deliveryPollInterval = options.deliveryPollInterval ?? 3e3;
1750
2429
  const verificationTimeout = options.verificationTimeout ?? DEFAULT_VERIFICATION_TIMEOUT_MS;
1751
2430
  const finalityRetryInterval = options.finalityRetryInterval ?? DEFAULT_FINALITY_RETRY_INTERVAL_MS;
1752
- const { client, enabledTokens } = usePrivanaContext();
2431
+ const { address } = useAccount();
2432
+ const { data: walletClient } = useWalletClient();
2433
+ const { client, enabledTokens, networkConfig, serviceAddress } = usePrivanaContext();
1753
2434
  const { executePrivateRead, privateReadReady } = usePrivateReadRequest();
2435
+ const { ensureCorrectChain } = useEnsureCorrectChain();
1754
2436
  const wagmiConfig = useConfig();
2437
+ const queryClient = useQueryClient();
1755
2438
  const selectedToken = enabledTokens.find((t) => t.id.toLowerCase() === tokenId.toLowerCase());
1756
2439
  const [status, setStatus] = useState("idle");
1757
2440
  const [pending, setPending] = useState([]);
@@ -1762,20 +2445,27 @@ function useFiatOnRamp(options) {
1762
2445
  const [activeVerificationId, setActiveVerificationId] = useState(null);
1763
2446
  const [finalityProgress, setFinalityProgress] = useState({});
1764
2447
  const onCreditedRef = useRef(onCredited);
2448
+ const onLockSubmittedRef = useRef(onLockSubmitted);
2449
+ const onLockFailedRef = useRef(onLockFailed);
1765
2450
  const onErrorRef = useRef(onError);
1766
2451
  const onDebugEventRef = useRef(onDebugEvent);
1767
2452
  const statusRef = useRef(status);
1768
2453
  const activeIntentIdRef = useRef(null);
1769
2454
  const activeVerificationRecordRef = useRef(null);
1770
2455
  const activeVerificationKeyRef = useRef(null);
2456
+ const activeVerificationAmountRef = useRef(null);
2457
+ const lockOwnerRef = useRef(null);
1771
2458
  const triggeredVerificationKeysRef = useRef(/* @__PURE__ */ new Set());
2459
+ const activeVerificationDoneRef = useRef(null);
1772
2460
  const closeReconcilePromiseRef = useRef(null);
1773
2461
  const purchaseInitiatedRef = useRef(false);
1774
2462
  useEffect(() => {
1775
2463
  onCreditedRef.current = onCredited;
2464
+ onLockSubmittedRef.current = onLockSubmitted;
2465
+ onLockFailedRef.current = onLockFailed;
1776
2466
  onErrorRef.current = onError;
1777
2467
  onDebugEventRef.current = onDebugEvent;
1778
- }, [onCredited, onError, onDebugEvent]);
2468
+ }, [onCredited, onLockSubmitted, onLockFailed, onError, onDebugEvent]);
1779
2469
  useEffect(() => {
1780
2470
  statusRef.current = status;
1781
2471
  }, [status]);
@@ -1856,13 +2546,64 @@ function useFiatOnRamp(options) {
1856
2546
  useEffect(() => {
1857
2547
  refreshPending();
1858
2548
  }, [refreshPending]);
1859
- const clearActiveVerification = useCallback(() => {
2549
+ const clearActiveVerification = useCallback((expectedKey) => {
1860
2550
  const key = activeVerificationKeyRef.current;
2551
+ if (expectedKey != null && key !== null && key !== expectedKey) return;
1861
2552
  if (key) triggeredVerificationKeysRef.current.delete(key);
1862
2553
  activeVerificationKeyRef.current = null;
1863
2554
  activeVerificationRecordRef.current = null;
2555
+ activeVerificationAmountRef.current = null;
1864
2556
  setActiveVerificationId(null);
2557
+ activeVerificationDoneRef.current?.();
2558
+ activeVerificationDoneRef.current = null;
1865
2559
  }, []);
2560
+ const submitPendingLockAfterCredit = useCallback(
2561
+ async (transactionId, userAddress) => {
2562
+ const signedLock = loadPendingLock(userAddress, transactionId);
2563
+ if (!signedLock) {
2564
+ clearPendingLock(userAddress, transactionId);
2565
+ if (!postDepositLock || transactionId !== activeIntentIdRef.current) return;
2566
+ const error2 = new PostDepositLockError(
2567
+ "No persisted signed lock found for this on-ramp",
2568
+ "not-found"
2569
+ );
2570
+ emitDebug("lock:not-found", { transactionId });
2571
+ (onLockFailedRef.current ?? onErrorRef.current)?.(error2);
2572
+ return;
2573
+ }
2574
+ const creditedAmount = activeVerificationAmountRef.current ?? void 0;
2575
+ try {
2576
+ const result = await submitPendingLock({ client, payload: signedLock, creditedAmount });
2577
+ queryClient.invalidateQueries({ queryKey: ["accounting-balance"] });
2578
+ queryClient.invalidateQueries({ queryKey: ["accounting-locked-funds"] });
2579
+ queryClient.invalidateQueries({ queryKey: ["accounting-total-locked-balance"] });
2580
+ queryClient.invalidateQueries({ queryKey: ["accounting-history"] });
2581
+ emitDebug("lock:submitted", {
2582
+ transactionId,
2583
+ amount: signedLock.amount,
2584
+ submissionId: result.submission_id
2585
+ });
2586
+ onLockSubmittedRef.current?.(result);
2587
+ } catch (err) {
2588
+ const error2 = err instanceof PostDepositLockError ? err : new PostDepositLockError(
2589
+ err instanceof Error ? err.message : "Lock submission failed",
2590
+ "submission-failed",
2591
+ BigInt(signedLock.amount),
2592
+ creditedAmount,
2593
+ { cause: err }
2594
+ );
2595
+ emitDebug("lock:failed", {
2596
+ transactionId,
2597
+ reason: error2.reason,
2598
+ message: error2.message
2599
+ });
2600
+ (onLockFailedRef.current ?? onErrorRef.current)?.(error2);
2601
+ } finally {
2602
+ clearPendingLock(userAddress, transactionId);
2603
+ }
2604
+ },
2605
+ [client, emitDebug, postDepositLock, queryClient]
2606
+ );
1866
2607
  const { verify } = useDepositVerification({
1867
2608
  pollTimeout: verificationTimeout,
1868
2609
  pollInterval: options.verificationPollInterval,
@@ -1878,6 +2619,7 @@ function useFiatOnRamp(options) {
1878
2619
  },
1879
2620
  onCredited: (depositTxHash) => {
1880
2621
  const record = activeVerificationRecordRef.current;
2622
+ const verificationKey = record ? getOnRampVerificationKey(record) : null;
1881
2623
  emitDebug("verification:credited", {
1882
2624
  depositTxHash,
1883
2625
  record: record ? summariseOnRampRecord(record) : null
@@ -1892,6 +2634,18 @@ function useFiatOnRamp(options) {
1892
2634
  delete next[record.transaction_id];
1893
2635
  return next;
1894
2636
  });
2637
+ const lockOwner = lockOwnerRef.current ?? address;
2638
+ if (lockOwner) {
2639
+ void submitPendingLockAfterCredit(record.transaction_id, lockOwner);
2640
+ } else if (postDepositLock) {
2641
+ emitDebug("lock:owner-unavailable", { transactionId: record.transaction_id });
2642
+ (onLockFailedRef.current ?? onErrorRef.current)?.(
2643
+ new PostDepositLockError(
2644
+ "No wallet address available to look up the signed lock for this on-ramp",
2645
+ "not-found"
2646
+ )
2647
+ );
2648
+ }
1895
2649
  }
1896
2650
  void (async () => {
1897
2651
  try {
@@ -1914,7 +2668,7 @@ function useFiatOnRamp(options) {
1914
2668
  console.warn("Failed to mark on-ramp row complete:", err);
1915
2669
  } finally {
1916
2670
  await refreshPending();
1917
- clearActiveVerification();
2671
+ clearActiveVerification(verificationKey);
1918
2672
  if (record && activeIntentIdRef.current === record.transaction_id) {
1919
2673
  activeIntentIdRef.current = null;
1920
2674
  setActiveIntentId(null);
@@ -1952,7 +2706,8 @@ function useFiatOnRamp(options) {
1952
2706
  async ({
1953
2707
  currencyCode,
1954
2708
  baseCurrencyCode,
1955
- baseCurrencyAmount
2709
+ baseCurrencyAmount,
2710
+ quoteCurrencyAmount
1956
2711
  }) => {
1957
2712
  try {
1958
2713
  setError(null);
@@ -1960,12 +2715,34 @@ function useFiatOnRamp(options) {
1960
2715
  const token = enabledTokens.find((t) => t.id.toLowerCase() === tokenId.toLowerCase());
1961
2716
  if (!token) throw new Error(`Unknown token: ${tokenId}`);
1962
2717
  if (!depositAddress) throw new Error("Privana deposit address is not ready");
2718
+ let lockAmount;
2719
+ if (postDepositLock) {
2720
+ if (!address || !walletClient) throw new Error("Wallet not connected");
2721
+ if (!quoteCurrencyAmount) {
2722
+ throw new Error(
2723
+ "postDepositLock requires quoteCurrencyAmount to derive the lock amount"
2724
+ );
2725
+ }
2726
+ if (!canUseBrowserStorage()) {
2727
+ throw new Error("Browser storage is required for locked on-ramp recovery");
2728
+ }
2729
+ const buffered = applyLockBuffer(
2730
+ parseUnits(quoteCurrencyAmount, token.decimals),
2731
+ postDepositLock.buffer
2732
+ );
2733
+ lockAmount = clampLockAmount(buffered, postDepositLock.maxAmount);
2734
+ if (lockAmount <= 0n) {
2735
+ throw new Error(`Post-deposit lock amount must be positive, got ${lockAmount}`);
2736
+ }
2737
+ await ensureCorrectChain(networkConfig.chainId);
2738
+ }
1963
2739
  emitDebug("intent:create-request", {
1964
2740
  tokenId,
1965
2741
  chainId: token.chainId,
1966
2742
  currencyCode,
1967
2743
  baseCurrencyCode: baseCurrencyCode ?? null,
1968
2744
  baseCurrencyAmount: baseCurrencyAmount ?? null,
2745
+ quoteCurrencyAmount: quoteCurrencyAmount ?? null,
1969
2746
  depositAddress
1970
2747
  });
1971
2748
  const record = await executePrivateRead(
@@ -1978,6 +2755,28 @@ function useFiatOnRamp(options) {
1978
2755
  base_currency_amount: baseCurrencyAmount
1979
2756
  })
1980
2757
  );
2758
+ if (postDepositLock && address && walletClient && lockAmount !== void 0) {
2759
+ const signingWalletClient = await getWalletClient3(wagmiConfig, {
2760
+ chainId: networkConfig.chainId
2761
+ });
2762
+ const signedLock = await createSignedLockRequest({
2763
+ client,
2764
+ walletClient: signingWalletClient,
2765
+ userAddress: address,
2766
+ networkConfig,
2767
+ serviceAddress: requireServiceAddress(postDepositLock.serviceAddress ?? serviceAddress),
2768
+ tokenId,
2769
+ amount: lockAmount,
2770
+ lockDuration: postDepositLock.lockDuration
2771
+ });
2772
+ savePendingLock(address, record.transaction_id, signedLock);
2773
+ lockOwnerRef.current = address;
2774
+ emitDebug("intent:lock-signed", {
2775
+ transactionId: record.transaction_id,
2776
+ amount: signedLock.amount,
2777
+ expiry: signedLock.expiry
2778
+ });
2779
+ }
1981
2780
  activeIntentIdRef.current = record.transaction_id;
1982
2781
  setActiveIntentId(record.transaction_id);
1983
2782
  emitDebug("intent:create-success", {
@@ -1993,7 +2792,21 @@ function useFiatOnRamp(options) {
1993
2792
  throw e;
1994
2793
  }
1995
2794
  },
1996
- [client, depositAddress, emitDebug, enabledTokens, executePrivateRead, tokenId]
2795
+ [
2796
+ address,
2797
+ client,
2798
+ depositAddress,
2799
+ emitDebug,
2800
+ enabledTokens,
2801
+ ensureCorrectChain,
2802
+ executePrivateRead,
2803
+ networkConfig,
2804
+ postDepositLock,
2805
+ serviceAddress,
2806
+ tokenId,
2807
+ wagmiConfig,
2808
+ walletClient
2809
+ ]
1997
2810
  );
1998
2811
  const registerOnRampTokenMapping = useCallback(
1999
2812
  async (moonpayTransactionId) => {
@@ -2115,6 +2928,12 @@ function useFiatOnRamp(options) {
2115
2928
  });
2116
2929
  return;
2117
2930
  }
2931
+ const supersededKey = activeVerificationKeyRef.current;
2932
+ if (supersededKey && supersededKey !== verificationKey) {
2933
+ triggeredVerificationKeysRef.current.delete(supersededKey);
2934
+ }
2935
+ activeVerificationDoneRef.current?.();
2936
+ activeVerificationDoneRef.current = null;
2118
2937
  triggeredVerificationKeysRef.current.add(verificationKey);
2119
2938
  activeVerificationKeyRef.current = verificationKey;
2120
2939
  activeVerificationRecordRef.current = record;
@@ -2165,6 +2984,7 @@ function useFiatOnRamp(options) {
2165
2984
  `Delivered amount (${record.quote_currency_amount}) is below the minimum deposit.`
2166
2985
  );
2167
2986
  }
2987
+ activeVerificationAmountRef.current = amount;
2168
2988
  if (activeIntentIdRef.current === record.transaction_id) {
2169
2989
  setStatus("verifying");
2170
2990
  }
@@ -2183,6 +3003,7 @@ function useFiatOnRamp(options) {
2183
3003
  if (activeVerificationKeyRef.current === verificationKey) {
2184
3004
  activeVerificationKeyRef.current = null;
2185
3005
  activeVerificationRecordRef.current = null;
3006
+ activeVerificationAmountRef.current = null;
2186
3007
  setActiveVerificationId(null);
2187
3008
  }
2188
3009
  throw err;
@@ -2241,6 +3062,7 @@ function useFiatOnRamp(options) {
2241
3062
  previousStatus,
2242
3063
  transactionId
2243
3064
  });
3065
+ if (address) clearPendingLock(address, transactionId);
2244
3066
  await refreshPending();
2245
3067
  if (previousStatus === "awaiting-purchase" || previousStatus === "awaiting-delivery") {
2246
3068
  setStatus("idle");
@@ -2271,7 +3093,7 @@ function useFiatOnRamp(options) {
2271
3093
  } finally {
2272
3094
  closeReconcilePromiseRef.current = null;
2273
3095
  }
2274
- }, [emitDebug, refreshPending, triggerVerification, waitForOnChainHash]);
3096
+ }, [address, emitDebug, refreshPending, triggerVerification, waitForOnChainHash]);
2275
3097
  const finishPendingVerification = useCallback(
2276
3098
  async (record) => {
2277
3099
  try {
@@ -2305,6 +3127,12 @@ function useFiatOnRamp(options) {
2305
3127
  try {
2306
3128
  await triggerVerificationRef.current(record);
2307
3129
  } catch {
3130
+ continue;
3131
+ }
3132
+ if (activeVerificationKeyRef.current === key) {
3133
+ await new Promise((resolve) => {
3134
+ activeVerificationDoneRef.current = resolve;
3135
+ });
2308
3136
  }
2309
3137
  }
2310
3138
  })();
@@ -2421,6 +3249,12 @@ function matchesOnRampTransaction(record, transactionId) {
2421
3249
  function getOnRampVerificationKey(record) {
2422
3250
  return record.on_chain_tx_hash ?? record.transaction_id;
2423
3251
  }
3252
+ function requireServiceAddress(serviceAddress) {
3253
+ if (!serviceAddress) {
3254
+ throw new Error("Service address not configured");
3255
+ }
3256
+ return serviceAddress;
3257
+ }
2424
3258
  function summariseMoonPayEventProps(props) {
2425
3259
  return {
2426
3260
  id: props.id,
@@ -2478,6 +3312,7 @@ function useMoonPayBuyWidget({
2478
3312
  colorCode,
2479
3313
  baseCurrencyCode,
2480
3314
  baseCurrencyAmount,
3315
+ quoteCurrencyAmount,
2481
3316
  lockAmount,
2482
3317
  paymentMethod,
2483
3318
  currencyCode,
@@ -2537,6 +3372,7 @@ function useMoonPayBuyWidget({
2537
3372
  overlayNode,
2538
3373
  baseCurrencyCode,
2539
3374
  baseCurrencyAmount,
3375
+ quoteCurrencyAmount,
2540
3376
  lockAmount: lockAmount ? "true" : void 0,
2541
3377
  paymentMethod,
2542
3378
  currencyCode,
@@ -2562,6 +3398,7 @@ function useMoonPayBuyWidget({
2562
3398
  overlayNode,
2563
3399
  baseCurrencyCode,
2564
3400
  baseCurrencyAmount,
3401
+ quoteCurrencyAmount,
2565
3402
  lockAmount,
2566
3403
  paymentMethod,
2567
3404
  currencyCode,
@@ -2580,6 +3417,7 @@ function FiatOnRampForm({
2580
3417
  currencyCode,
2581
3418
  baseCurrencyCode = "usd",
2582
3419
  defaultBaseCurrencyAmount = "100",
3420
+ quoteCurrencyAmount,
2583
3421
  tokenSymbol,
2584
3422
  theme,
2585
3423
  themeId,
@@ -2588,7 +3426,10 @@ function FiatOnRampForm({
2588
3426
  autoStart = false,
2589
3427
  lockAmount,
2590
3428
  paymentMethod,
3429
+ postDepositLock,
2591
3430
  onCredited,
3431
+ onLockSubmitted,
3432
+ onLockFailed,
2592
3433
  onError,
2593
3434
  onDebugEvent
2594
3435
  }) {
@@ -2596,6 +3437,8 @@ function FiatOnRampForm({
2596
3437
  const [visible, setVisible] = useState(false);
2597
3438
  const [isPreparing, setIsPreparing] = useState(false);
2598
3439
  const [rowError, setRowError] = useState(null);
3440
+ const [lockError, setLockError] = useState(null);
3441
+ const [lockSettled, setLockSettled] = useState(false);
2599
3442
  const {
2600
3443
  status,
2601
3444
  activeIntentId,
@@ -2613,7 +3456,25 @@ function FiatOnRampForm({
2613
3456
  finishPendingVerification,
2614
3457
  handleWidgetClosed,
2615
3458
  refreshPending
2616
- } = useFiatOnRamp({ tokenId, onCredited, onError, onDebugEvent });
3459
+ } = useFiatOnRamp({
3460
+ tokenId,
3461
+ postDepositLock,
3462
+ onCredited,
3463
+ // Lock callbacks aren't intent-keyed, so a resumed background row's lock
3464
+ // can settle these flags while a newer purchase is still locking — a
3465
+ // transient overpromise that the newer lock's own outcome then corrects.
3466
+ onLockSubmitted: (response) => {
3467
+ setLockError(null);
3468
+ setLockSettled(true);
3469
+ onLockSubmitted?.(response);
3470
+ },
3471
+ onLockFailed: (err) => {
3472
+ setLockError(err.message);
3473
+ onLockFailed?.(err);
3474
+ },
3475
+ onError,
3476
+ onDebugEvent
3477
+ });
2617
3478
  const decimals = selectedToken?.decimals;
2618
3479
  const displaySymbol = tokenSymbol ?? selectedToken?.symbol ?? currencyCode.toUpperCase();
2619
3480
  const emitFormDebug = useCallback(
@@ -2629,8 +3490,17 @@ function FiatOnRampForm({
2629
3490
  [onDebugEvent, status, tokenId]
2630
3491
  );
2631
3492
  const minFiatGate = minDepositBaseUnits !== void 0 && decimals !== void 0 ? Number(formatUnits(minDepositBaseUnits, decimals)) * 1.05 : void 0;
2632
- const isBelowMin = minFiatGate !== void 0 && Number(defaultBaseCurrencyAmount) < minFiatGate;
3493
+ const { units: quoteBaseUnits, failed: quoteParseFailed } = (() => {
3494
+ if (!quoteCurrencyAmount || decimals === void 0) return { units: void 0, failed: false };
3495
+ try {
3496
+ return { units: parseUnits(quoteCurrencyAmount, decimals), failed: false };
3497
+ } catch {
3498
+ return { units: void 0, failed: true };
3499
+ }
3500
+ })();
3501
+ const isBelowMin = quoteBaseUnits !== void 0 && minDepositBaseUnits !== void 0 ? quoteBaseUnits < minDepositBaseUnits : minFiatGate !== void 0 && Number(defaultBaseCurrencyAmount) < minFiatGate;
2633
3502
  const isBusy = isPreparing || status === "awaiting-purchase";
3503
+ const lockPending = !!postDepositLock && status === "credited" && !lockSettled && !lockError;
2634
3504
  const isInitializing = !!address && !depositAddress;
2635
3505
  const isPrePurchase = status === "idle" || status === "awaiting-purchase";
2636
3506
  const isVerifying = status === "awaiting-delivery" || status === "verifying";
@@ -2640,9 +3510,10 @@ function FiatOnRampForm({
2640
3510
  !depositAddress ? "deposit-address-not-loaded" : null,
2641
3511
  isBusy ? `busy:${isPreparing ? "preparing" : status}` : null,
2642
3512
  visible ? "widget-open" : null,
2643
- isBelowMin ? "below-minimum" : null
3513
+ isBelowMin ? "below-minimum" : null,
3514
+ quoteParseFailed ? "invalid-quote-amount" : null
2644
3515
  ].filter((reason) => Boolean(reason)),
2645
- [address, depositAddress, isBelowMin, isBusy, isPreparing, status, visible]
3516
+ [address, depositAddress, isBelowMin, isBusy, isPreparing, quoteParseFailed, status, visible]
2646
3517
  );
2647
3518
  const canBuy = blockReasons.length === 0;
2648
3519
  const handleOpen = useCallback(async () => {
@@ -2661,6 +3532,8 @@ function FiatOnRampForm({
2661
3532
  return;
2662
3533
  }
2663
3534
  setIsPreparing(true);
3535
+ setLockError(null);
3536
+ setLockSettled(false);
2664
3537
  emitFormDebug("form:open-click", {
2665
3538
  currencyCode,
2666
3539
  tokenSymbol: displaySymbol,
@@ -2674,7 +3547,8 @@ function FiatOnRampForm({
2674
3547
  const intent = await prepareOnRampIntent({
2675
3548
  currencyCode,
2676
3549
  baseCurrencyCode,
2677
- baseCurrencyAmount: defaultBaseCurrencyAmount
3550
+ baseCurrencyAmount: defaultBaseCurrencyAmount,
3551
+ quoteCurrencyAmount
2678
3552
  });
2679
3553
  emitFormDebug("form:intent-ready", {
2680
3554
  transactionId: intent.transaction_id,
@@ -2699,6 +3573,7 @@ function FiatOnRampForm({
2699
3573
  decimals,
2700
3574
  displaySymbol,
2701
3575
  defaultBaseCurrencyAmount,
3576
+ quoteCurrencyAmount,
2702
3577
  depositAddress,
2703
3578
  emitFormDebug,
2704
3579
  prepareOnRampIntent,
@@ -2729,6 +3604,7 @@ function FiatOnRampForm({
2729
3604
  colorCode,
2730
3605
  baseCurrencyCode,
2731
3606
  baseCurrencyAmount: defaultBaseCurrencyAmount,
3607
+ quoteCurrencyAmount,
2732
3608
  lockAmount,
2733
3609
  paymentMethod,
2734
3610
  currencyCode,
@@ -2801,7 +3677,7 @@ function FiatOnRampForm({
2801
3677
  );
2802
3678
  })
2803
3679
  ] }),
2804
- status === "credited" && /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center gap-2 py-8 text-center", children: [
3680
+ status === "credited" && !lockPending && !lockError && /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center gap-2 py-8 text-center", children: [
2805
3681
  /* @__PURE__ */ jsx(CircleCheckIcon, { className: "text-primary size-8", "aria-hidden": true }),
2806
3682
  /* @__PURE__ */ jsx("p", { className: "text-foreground text-sm font-medium", children: "Purchase credited" }),
2807
3683
  /* @__PURE__ */ jsxs("p", { className: "text-muted-foreground text-sm", children: [
@@ -2810,12 +3686,20 @@ function FiatOnRampForm({
2810
3686
  " deposit is now available in your balance."
2811
3687
  ] })
2812
3688
  ] }),
3689
+ lockPending && /* @__PURE__ */ jsxs("p", { className: "text-muted-foreground flex items-center gap-2 text-sm", children: [
3690
+ /* @__PURE__ */ jsx(Loader2, { className: "size-4 animate-spin", "aria-hidden": true }),
3691
+ "Purchase credited \u2014 locking your funds\u2026"
3692
+ ] }),
2813
3693
  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: [
2814
3694
  (isBusy || visible) && /* @__PURE__ */ jsx(Loader2, { className: "animate-spin", "aria-hidden": true }),
2815
3695
  "Buy"
2816
3696
  ] }),
2817
3697
  widgetElement,
2818
3698
  error && /* @__PURE__ */ jsx("p", { className: "text-destructive text-sm", role: "alert", children: error.message }),
3699
+ lockError && /* @__PURE__ */ jsxs("p", { className: "text-destructive text-sm", role: "alert", children: [
3700
+ "Purchase credited to your account, but locking the funds to a service failed: ",
3701
+ lockError
3702
+ ] }),
2819
3703
  isBelowMin && minFiatGate !== void 0 && /* @__PURE__ */ jsxs("p", { className: "text-destructive text-sm", role: "alert", children: [
2820
3704
  "Minimum purchase is ~$",
2821
3705
  minFiatGate.toFixed(2),
@@ -2833,6 +3717,6 @@ function parseFinalityProgress(message) {
2833
3717
  return match ? `${match[1]} confirmations` : null;
2834
3718
  }
2835
3719
 
2836
- export { AccountingApiError, Button, FiatOnRampForm, HOSTED_AUTH_CLOCK_SKEW_MS, HostedAuthError, HostedAuthRequiredError, HostedAuthStateMismatchError, HttpClient, NETWORK_CONFIG, NetworkError, PrivanaClient, PrivanaProvider, SUPPORTED_CHAINS, SiweAuthProvider, Skeleton, ValidationError, applyRefreshResponse, buildHostedAuthSession, buildSiweStatement, buttonVariants, clearHostedAuthPendingTransaction, cn, createHostedAuthPendingStorageKey, createHostedAuthState, createHostedAuthStorageKey, createPkceChallenge, createPkceVerifier, formatTimeRemaining, formatTokenAmount, getAccountingContract, getApiUrl, getChainById, getChainId, getChainId2, getExplorerAddressUrl, getExplorerLabel, getTransactionReceipt, isHostedAuthRefreshActive, isHostedAuthSessionActive, normalizeAddress, normalizeHex, parseHostedAuthCallback, parseTokenAmount, persistHostedAuthPendingTransaction, readHostedAuthPendingTransaction, readStoredHostedAuthSession, shortenAddress, stripHostedAuthCallbackParams, syncHostedAuthSessionToClient, useDepositVerification, useFiatOnRamp, usePrivanaContext, usePrivateReadRequest, useSafeAccount, useSafePrivanaContext, useSiweAuth, waitForTransactionReceipt };
2837
- //# sourceMappingURL=chunk-54FC6TO3.js.map
2838
- //# sourceMappingURL=chunk-54FC6TO3.js.map
3720
+ export { AccountingApiError, Button, DEFAULT_LOCK_DURATION_SECONDS, DEFAULT_ONRAMP_LOCK_BUFFER, FiatOnRampForm, HOSTED_AUTH_CLOCK_SKEW_MS, HostedAuthError, HostedAuthRequiredError, HostedAuthStateMismatchError, HttpClient, LOCK_TYPES, MODIFY_LOCK_TYPES, NETWORK_CONFIG, NetworkError, PostDepositLockError, PrivanaClient, PrivanaProvider, SUPPORTED_CHAINS, SiweAuthProvider, Skeleton, TRANSFER_LOCKED_TYPES, TRANSFER_TYPES, ValidationError, WITHDRAW_FROM_LOCK_TYPES, WITHDRAW_TYPES, applyLockBuffer, applyRefreshResponse, buildHostedAuthSession, buildSiweStatement, buttonVariants, canUseBrowserStorage, clampLockAmount, clearHostedAuthPendingTransaction, clearPendingLock, cn, createDomain, createHostedAuthPendingStorageKey, createHostedAuthState, createHostedAuthStorageKey, createLockExpiry, createPkceChallenge, createPkceVerifier, createSignedLockRequest, formatCountdown, formatTimeRemaining, formatTokenAmount, getAccountingContract, getApiUrl, getBlockNumber, getBrowserStorageItem, getChainById, getChainId, getExplorerAddressUrl, getExplorerLabel, getTransactionReceipt, getWalletClient3 as getWalletClient, isHostedAuthRefreshActive, isHostedAuthSessionActive, isSignedLockUsable, loadPendingLock, normalizeAddress, normalizeHex, parseHostedAuthCallback, parseTokenAmount, persistHostedAuthPendingTransaction, readHostedAuthPendingTransaction, readStoredHostedAuthSession, removeBrowserStorageItem, savePendingLock, setBrowserStorageItem, shortenAddress, signLockMessage, signModifyLockMessage, signTransferLockedMessage, signTransferMessage, signWithdrawFromLockMessage, signWithdrawMessage, stripHostedAuthCallbackParams, submitPendingLock, syncHostedAuthSessionToClient, useDepositVerification, useEnsureCorrectChain, useFiatOnRamp, usePrivanaContext, usePrivateReadRequest, useSafeAccount, useSafePrivanaContext, useSiweAuth, waitForTransactionReceipt };
3721
+ //# sourceMappingURL=chunk-ZVUMV4NR.js.map
3722
+ //# sourceMappingURL=chunk-ZVUMV4NR.js.map