@oasisprotocol/privana-sdk 0.5.0 → 0.5.1

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,15 +1,16 @@
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 { parseAccount, getAddress } from 'viem/utils';
13
14
  import { 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';
@@ -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,198 @@ 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
+
1703
2311
  // ../../node_modules/@wagmi/core/dist/esm/actions/getChainId.js
1704
2312
  function getChainId2(config) {
1705
2313
  return config.state.chainId;
@@ -1710,6 +2318,10 @@ async function getTransactionReceipt(config, parameters) {
1710
2318
  const action = getAction(client, getTransactionReceipt$1, "getTransactionReceipt");
1711
2319
  return action(rest);
1712
2320
  }
2321
+ async function getWalletClient3(config, parameters = {}) {
2322
+ const client = await getConnectorClient(config, parameters);
2323
+ return client.extend(walletActions);
2324
+ }
1713
2325
  async function waitForTransactionReceipt(config, parameters) {
1714
2326
  const { chainId, timeout = 0, ...rest } = parameters;
1715
2327
  const client = config.getClient({ chainId });
@@ -1737,6 +2349,59 @@ async function waitForTransactionReceipt(config, parameters) {
1737
2349
  chainId: client.chain.id
1738
2350
  };
1739
2351
  }
2352
+ function useEnsureCorrectChain() {
2353
+ const config = useConfig();
2354
+ const chainId = useChainId();
2355
+ const { switchChainAsync } = useSwitchChain();
2356
+ const waitUntilOnChain = useCallback(
2357
+ async (expectedChainId, timeoutMs, pollIntervalMs = 250) => {
2358
+ const startedAt = Date.now();
2359
+ while (Date.now() - startedAt < timeoutMs) {
2360
+ const currentChainId = getChainId2(config);
2361
+ if (currentChainId === expectedChainId) return true;
2362
+ await new Promise((resolve) => setTimeout(resolve, pollIntervalMs));
2363
+ }
2364
+ return false;
2365
+ },
2366
+ [config]
2367
+ );
2368
+ const ensureCorrectChain = useCallback(
2369
+ async (targetChainId) => {
2370
+ const currentChainId = getChainId2(config);
2371
+ if (currentChainId === targetChainId) return false;
2372
+ let switchErrorMessage;
2373
+ try {
2374
+ const timeoutPromise = new Promise((_, reject) => {
2375
+ setTimeout(() => reject(new Error("Chain switch timeout")), 3e3);
2376
+ });
2377
+ await Promise.race([switchChainAsync({ chainId: targetChainId }), timeoutPromise]);
2378
+ } catch (error) {
2379
+ if (error instanceof Error && error.message.includes("Unsupported Chain")) {
2380
+ console.warn("Got 'Unsupported Chain' error, chain may have switched anyway.");
2381
+ } else {
2382
+ switchErrorMessage = error instanceof Error ? error.message : "Unknown chain switch error";
2383
+ }
2384
+ }
2385
+ const settled = await waitUntilOnChain(targetChainId, 2e4);
2386
+ if (settled) return true;
2387
+ if (switchErrorMessage) {
2388
+ throw new Error(
2389
+ `Failed to switch to chain (Chain ID: ${targetChainId}): ${switchErrorMessage}`
2390
+ );
2391
+ }
2392
+ throw new Error(`Chain switch did not settle in time (expected ${targetChainId}).`);
2393
+ },
2394
+ [config, switchChainAsync, waitUntilOnChain]
2395
+ );
2396
+ const isOnChain = useCallback((targetChainId) => chainId === targetChainId, [chainId]);
2397
+ return {
2398
+ chainId,
2399
+ ensureCorrectChain,
2400
+ isOnChain
2401
+ };
2402
+ }
2403
+
2404
+ // src/sdk/hooks/use-fiat-on-ramp.ts
1740
2405
  var DEFAULT_DELIVERY_TIMEOUT_MS = 12e4;
1741
2406
  var DEFAULT_VERIFICATION_TIMEOUT_MS = 10 * 6e4;
1742
2407
  var DEFAULT_FINALITY_RETRY_INTERVAL_MS = 15e3;
@@ -1744,14 +2409,26 @@ var ERC20_TRANSFER_EVENT = parseAbiItem(
1744
2409
  "event Transfer(address indexed from, address indexed to, uint256 value)"
1745
2410
  );
1746
2411
  function useFiatOnRamp(options) {
1747
- const { tokenId, onCredited, onError, onDebugEvent } = options;
2412
+ const {
2413
+ tokenId,
2414
+ postDepositLock,
2415
+ onCredited,
2416
+ onLockSubmitted,
2417
+ onLockFailed,
2418
+ onError,
2419
+ onDebugEvent
2420
+ } = options;
1748
2421
  const deliveryTimeout = options.deliveryTimeout ?? DEFAULT_DELIVERY_TIMEOUT_MS;
1749
2422
  const deliveryPollInterval = options.deliveryPollInterval ?? 3e3;
1750
2423
  const verificationTimeout = options.verificationTimeout ?? DEFAULT_VERIFICATION_TIMEOUT_MS;
1751
2424
  const finalityRetryInterval = options.finalityRetryInterval ?? DEFAULT_FINALITY_RETRY_INTERVAL_MS;
1752
- const { client, enabledTokens } = usePrivanaContext();
2425
+ const { address } = useAccount();
2426
+ const { data: walletClient } = useWalletClient();
2427
+ const { client, enabledTokens, networkConfig, serviceAddress } = usePrivanaContext();
1753
2428
  const { executePrivateRead, privateReadReady } = usePrivateReadRequest();
2429
+ const { ensureCorrectChain } = useEnsureCorrectChain();
1754
2430
  const wagmiConfig = useConfig();
2431
+ const queryClient = useQueryClient();
1755
2432
  const selectedToken = enabledTokens.find((t) => t.id.toLowerCase() === tokenId.toLowerCase());
1756
2433
  const [status, setStatus] = useState("idle");
1757
2434
  const [pending, setPending] = useState([]);
@@ -1762,20 +2439,27 @@ function useFiatOnRamp(options) {
1762
2439
  const [activeVerificationId, setActiveVerificationId] = useState(null);
1763
2440
  const [finalityProgress, setFinalityProgress] = useState({});
1764
2441
  const onCreditedRef = useRef(onCredited);
2442
+ const onLockSubmittedRef = useRef(onLockSubmitted);
2443
+ const onLockFailedRef = useRef(onLockFailed);
1765
2444
  const onErrorRef = useRef(onError);
1766
2445
  const onDebugEventRef = useRef(onDebugEvent);
1767
2446
  const statusRef = useRef(status);
1768
2447
  const activeIntentIdRef = useRef(null);
1769
2448
  const activeVerificationRecordRef = useRef(null);
1770
2449
  const activeVerificationKeyRef = useRef(null);
2450
+ const activeVerificationAmountRef = useRef(null);
2451
+ const lockOwnerRef = useRef(null);
1771
2452
  const triggeredVerificationKeysRef = useRef(/* @__PURE__ */ new Set());
2453
+ const activeVerificationDoneRef = useRef(null);
1772
2454
  const closeReconcilePromiseRef = useRef(null);
1773
2455
  const purchaseInitiatedRef = useRef(false);
1774
2456
  useEffect(() => {
1775
2457
  onCreditedRef.current = onCredited;
2458
+ onLockSubmittedRef.current = onLockSubmitted;
2459
+ onLockFailedRef.current = onLockFailed;
1776
2460
  onErrorRef.current = onError;
1777
2461
  onDebugEventRef.current = onDebugEvent;
1778
- }, [onCredited, onError, onDebugEvent]);
2462
+ }, [onCredited, onLockSubmitted, onLockFailed, onError, onDebugEvent]);
1779
2463
  useEffect(() => {
1780
2464
  statusRef.current = status;
1781
2465
  }, [status]);
@@ -1856,13 +2540,64 @@ function useFiatOnRamp(options) {
1856
2540
  useEffect(() => {
1857
2541
  refreshPending();
1858
2542
  }, [refreshPending]);
1859
- const clearActiveVerification = useCallback(() => {
2543
+ const clearActiveVerification = useCallback((expectedKey) => {
1860
2544
  const key = activeVerificationKeyRef.current;
2545
+ if (expectedKey != null && key !== null && key !== expectedKey) return;
1861
2546
  if (key) triggeredVerificationKeysRef.current.delete(key);
1862
2547
  activeVerificationKeyRef.current = null;
1863
2548
  activeVerificationRecordRef.current = null;
2549
+ activeVerificationAmountRef.current = null;
1864
2550
  setActiveVerificationId(null);
2551
+ activeVerificationDoneRef.current?.();
2552
+ activeVerificationDoneRef.current = null;
1865
2553
  }, []);
2554
+ const submitPendingLockAfterCredit = useCallback(
2555
+ async (transactionId, userAddress) => {
2556
+ const signedLock = loadPendingLock(userAddress, transactionId);
2557
+ if (!signedLock) {
2558
+ clearPendingLock(userAddress, transactionId);
2559
+ if (!postDepositLock || transactionId !== activeIntentIdRef.current) return;
2560
+ const error2 = new PostDepositLockError(
2561
+ "No persisted signed lock found for this on-ramp",
2562
+ "not-found"
2563
+ );
2564
+ emitDebug("lock:not-found", { transactionId });
2565
+ (onLockFailedRef.current ?? onErrorRef.current)?.(error2);
2566
+ return;
2567
+ }
2568
+ const creditedAmount = activeVerificationAmountRef.current ?? void 0;
2569
+ try {
2570
+ const result = await submitPendingLock({ client, payload: signedLock, creditedAmount });
2571
+ queryClient.invalidateQueries({ queryKey: ["accounting-balance"] });
2572
+ queryClient.invalidateQueries({ queryKey: ["accounting-locked-funds"] });
2573
+ queryClient.invalidateQueries({ queryKey: ["accounting-total-locked-balance"] });
2574
+ queryClient.invalidateQueries({ queryKey: ["accounting-history"] });
2575
+ emitDebug("lock:submitted", {
2576
+ transactionId,
2577
+ amount: signedLock.amount,
2578
+ submissionId: result.submission_id
2579
+ });
2580
+ onLockSubmittedRef.current?.(result);
2581
+ } catch (err) {
2582
+ const error2 = err instanceof PostDepositLockError ? err : new PostDepositLockError(
2583
+ err instanceof Error ? err.message : "Lock submission failed",
2584
+ "submission-failed",
2585
+ BigInt(signedLock.amount),
2586
+ creditedAmount,
2587
+ { cause: err }
2588
+ );
2589
+ emitDebug("lock:failed", {
2590
+ transactionId,
2591
+ reason: error2.reason,
2592
+ message: error2.message
2593
+ });
2594
+ (onLockFailedRef.current ?? onErrorRef.current)?.(error2);
2595
+ } finally {
2596
+ clearPendingLock(userAddress, transactionId);
2597
+ }
2598
+ },
2599
+ [client, emitDebug, postDepositLock, queryClient]
2600
+ );
1866
2601
  const { verify } = useDepositVerification({
1867
2602
  pollTimeout: verificationTimeout,
1868
2603
  pollInterval: options.verificationPollInterval,
@@ -1878,6 +2613,7 @@ function useFiatOnRamp(options) {
1878
2613
  },
1879
2614
  onCredited: (depositTxHash) => {
1880
2615
  const record = activeVerificationRecordRef.current;
2616
+ const verificationKey = record ? getOnRampVerificationKey(record) : null;
1881
2617
  emitDebug("verification:credited", {
1882
2618
  depositTxHash,
1883
2619
  record: record ? summariseOnRampRecord(record) : null
@@ -1892,6 +2628,18 @@ function useFiatOnRamp(options) {
1892
2628
  delete next[record.transaction_id];
1893
2629
  return next;
1894
2630
  });
2631
+ const lockOwner = lockOwnerRef.current ?? address;
2632
+ if (lockOwner) {
2633
+ void submitPendingLockAfterCredit(record.transaction_id, lockOwner);
2634
+ } else if (postDepositLock) {
2635
+ emitDebug("lock:owner-unavailable", { transactionId: record.transaction_id });
2636
+ (onLockFailedRef.current ?? onErrorRef.current)?.(
2637
+ new PostDepositLockError(
2638
+ "No wallet address available to look up the signed lock for this on-ramp",
2639
+ "not-found"
2640
+ )
2641
+ );
2642
+ }
1895
2643
  }
1896
2644
  void (async () => {
1897
2645
  try {
@@ -1914,7 +2662,7 @@ function useFiatOnRamp(options) {
1914
2662
  console.warn("Failed to mark on-ramp row complete:", err);
1915
2663
  } finally {
1916
2664
  await refreshPending();
1917
- clearActiveVerification();
2665
+ clearActiveVerification(verificationKey);
1918
2666
  if (record && activeIntentIdRef.current === record.transaction_id) {
1919
2667
  activeIntentIdRef.current = null;
1920
2668
  setActiveIntentId(null);
@@ -1952,7 +2700,8 @@ function useFiatOnRamp(options) {
1952
2700
  async ({
1953
2701
  currencyCode,
1954
2702
  baseCurrencyCode,
1955
- baseCurrencyAmount
2703
+ baseCurrencyAmount,
2704
+ quoteCurrencyAmount
1956
2705
  }) => {
1957
2706
  try {
1958
2707
  setError(null);
@@ -1960,12 +2709,34 @@ function useFiatOnRamp(options) {
1960
2709
  const token = enabledTokens.find((t) => t.id.toLowerCase() === tokenId.toLowerCase());
1961
2710
  if (!token) throw new Error(`Unknown token: ${tokenId}`);
1962
2711
  if (!depositAddress) throw new Error("Privana deposit address is not ready");
2712
+ let lockAmount;
2713
+ if (postDepositLock) {
2714
+ if (!address || !walletClient) throw new Error("Wallet not connected");
2715
+ if (!quoteCurrencyAmount) {
2716
+ throw new Error(
2717
+ "postDepositLock requires quoteCurrencyAmount to derive the lock amount"
2718
+ );
2719
+ }
2720
+ if (!canUseBrowserStorage()) {
2721
+ throw new Error("Browser storage is required for locked on-ramp recovery");
2722
+ }
2723
+ const buffered = applyLockBuffer(
2724
+ parseUnits(quoteCurrencyAmount, token.decimals),
2725
+ postDepositLock.buffer
2726
+ );
2727
+ lockAmount = clampLockAmount(buffered, postDepositLock.maxAmount);
2728
+ if (lockAmount <= 0n) {
2729
+ throw new Error(`Post-deposit lock amount must be positive, got ${lockAmount}`);
2730
+ }
2731
+ await ensureCorrectChain(networkConfig.chainId);
2732
+ }
1963
2733
  emitDebug("intent:create-request", {
1964
2734
  tokenId,
1965
2735
  chainId: token.chainId,
1966
2736
  currencyCode,
1967
2737
  baseCurrencyCode: baseCurrencyCode ?? null,
1968
2738
  baseCurrencyAmount: baseCurrencyAmount ?? null,
2739
+ quoteCurrencyAmount: quoteCurrencyAmount ?? null,
1969
2740
  depositAddress
1970
2741
  });
1971
2742
  const record = await executePrivateRead(
@@ -1978,6 +2749,28 @@ function useFiatOnRamp(options) {
1978
2749
  base_currency_amount: baseCurrencyAmount
1979
2750
  })
1980
2751
  );
2752
+ if (postDepositLock && address && walletClient && lockAmount !== void 0) {
2753
+ const signingWalletClient = await getWalletClient3(wagmiConfig, {
2754
+ chainId: networkConfig.chainId
2755
+ });
2756
+ const signedLock = await createSignedLockRequest({
2757
+ client,
2758
+ walletClient: signingWalletClient,
2759
+ userAddress: address,
2760
+ networkConfig,
2761
+ serviceAddress: requireServiceAddress(postDepositLock.serviceAddress ?? serviceAddress),
2762
+ tokenId,
2763
+ amount: lockAmount,
2764
+ lockDuration: postDepositLock.lockDuration
2765
+ });
2766
+ savePendingLock(address, record.transaction_id, signedLock);
2767
+ lockOwnerRef.current = address;
2768
+ emitDebug("intent:lock-signed", {
2769
+ transactionId: record.transaction_id,
2770
+ amount: signedLock.amount,
2771
+ expiry: signedLock.expiry
2772
+ });
2773
+ }
1981
2774
  activeIntentIdRef.current = record.transaction_id;
1982
2775
  setActiveIntentId(record.transaction_id);
1983
2776
  emitDebug("intent:create-success", {
@@ -1993,7 +2786,21 @@ function useFiatOnRamp(options) {
1993
2786
  throw e;
1994
2787
  }
1995
2788
  },
1996
- [client, depositAddress, emitDebug, enabledTokens, executePrivateRead, tokenId]
2789
+ [
2790
+ address,
2791
+ client,
2792
+ depositAddress,
2793
+ emitDebug,
2794
+ enabledTokens,
2795
+ ensureCorrectChain,
2796
+ executePrivateRead,
2797
+ networkConfig,
2798
+ postDepositLock,
2799
+ serviceAddress,
2800
+ tokenId,
2801
+ wagmiConfig,
2802
+ walletClient
2803
+ ]
1997
2804
  );
1998
2805
  const registerOnRampTokenMapping = useCallback(
1999
2806
  async (moonpayTransactionId) => {
@@ -2115,6 +2922,12 @@ function useFiatOnRamp(options) {
2115
2922
  });
2116
2923
  return;
2117
2924
  }
2925
+ const supersededKey = activeVerificationKeyRef.current;
2926
+ if (supersededKey && supersededKey !== verificationKey) {
2927
+ triggeredVerificationKeysRef.current.delete(supersededKey);
2928
+ }
2929
+ activeVerificationDoneRef.current?.();
2930
+ activeVerificationDoneRef.current = null;
2118
2931
  triggeredVerificationKeysRef.current.add(verificationKey);
2119
2932
  activeVerificationKeyRef.current = verificationKey;
2120
2933
  activeVerificationRecordRef.current = record;
@@ -2165,6 +2978,7 @@ function useFiatOnRamp(options) {
2165
2978
  `Delivered amount (${record.quote_currency_amount}) is below the minimum deposit.`
2166
2979
  );
2167
2980
  }
2981
+ activeVerificationAmountRef.current = amount;
2168
2982
  if (activeIntentIdRef.current === record.transaction_id) {
2169
2983
  setStatus("verifying");
2170
2984
  }
@@ -2183,6 +2997,7 @@ function useFiatOnRamp(options) {
2183
2997
  if (activeVerificationKeyRef.current === verificationKey) {
2184
2998
  activeVerificationKeyRef.current = null;
2185
2999
  activeVerificationRecordRef.current = null;
3000
+ activeVerificationAmountRef.current = null;
2186
3001
  setActiveVerificationId(null);
2187
3002
  }
2188
3003
  throw err;
@@ -2241,6 +3056,7 @@ function useFiatOnRamp(options) {
2241
3056
  previousStatus,
2242
3057
  transactionId
2243
3058
  });
3059
+ if (address) clearPendingLock(address, transactionId);
2244
3060
  await refreshPending();
2245
3061
  if (previousStatus === "awaiting-purchase" || previousStatus === "awaiting-delivery") {
2246
3062
  setStatus("idle");
@@ -2271,7 +3087,7 @@ function useFiatOnRamp(options) {
2271
3087
  } finally {
2272
3088
  closeReconcilePromiseRef.current = null;
2273
3089
  }
2274
- }, [emitDebug, refreshPending, triggerVerification, waitForOnChainHash]);
3090
+ }, [address, emitDebug, refreshPending, triggerVerification, waitForOnChainHash]);
2275
3091
  const finishPendingVerification = useCallback(
2276
3092
  async (record) => {
2277
3093
  try {
@@ -2305,6 +3121,12 @@ function useFiatOnRamp(options) {
2305
3121
  try {
2306
3122
  await triggerVerificationRef.current(record);
2307
3123
  } catch {
3124
+ continue;
3125
+ }
3126
+ if (activeVerificationKeyRef.current === key) {
3127
+ await new Promise((resolve) => {
3128
+ activeVerificationDoneRef.current = resolve;
3129
+ });
2308
3130
  }
2309
3131
  }
2310
3132
  })();
@@ -2421,6 +3243,12 @@ function matchesOnRampTransaction(record, transactionId) {
2421
3243
  function getOnRampVerificationKey(record) {
2422
3244
  return record.on_chain_tx_hash ?? record.transaction_id;
2423
3245
  }
3246
+ function requireServiceAddress(serviceAddress) {
3247
+ if (!serviceAddress) {
3248
+ throw new Error("Service address not configured");
3249
+ }
3250
+ return serviceAddress;
3251
+ }
2424
3252
  function summariseMoonPayEventProps(props) {
2425
3253
  return {
2426
3254
  id: props.id,
@@ -2478,6 +3306,7 @@ function useMoonPayBuyWidget({
2478
3306
  colorCode,
2479
3307
  baseCurrencyCode,
2480
3308
  baseCurrencyAmount,
3309
+ quoteCurrencyAmount,
2481
3310
  lockAmount,
2482
3311
  paymentMethod,
2483
3312
  currencyCode,
@@ -2537,6 +3366,7 @@ function useMoonPayBuyWidget({
2537
3366
  overlayNode,
2538
3367
  baseCurrencyCode,
2539
3368
  baseCurrencyAmount,
3369
+ quoteCurrencyAmount,
2540
3370
  lockAmount: lockAmount ? "true" : void 0,
2541
3371
  paymentMethod,
2542
3372
  currencyCode,
@@ -2562,6 +3392,7 @@ function useMoonPayBuyWidget({
2562
3392
  overlayNode,
2563
3393
  baseCurrencyCode,
2564
3394
  baseCurrencyAmount,
3395
+ quoteCurrencyAmount,
2565
3396
  lockAmount,
2566
3397
  paymentMethod,
2567
3398
  currencyCode,
@@ -2580,6 +3411,7 @@ function FiatOnRampForm({
2580
3411
  currencyCode,
2581
3412
  baseCurrencyCode = "usd",
2582
3413
  defaultBaseCurrencyAmount = "100",
3414
+ quoteCurrencyAmount,
2583
3415
  tokenSymbol,
2584
3416
  theme,
2585
3417
  themeId,
@@ -2588,7 +3420,10 @@ function FiatOnRampForm({
2588
3420
  autoStart = false,
2589
3421
  lockAmount,
2590
3422
  paymentMethod,
3423
+ postDepositLock,
2591
3424
  onCredited,
3425
+ onLockSubmitted,
3426
+ onLockFailed,
2592
3427
  onError,
2593
3428
  onDebugEvent
2594
3429
  }) {
@@ -2596,6 +3431,8 @@ function FiatOnRampForm({
2596
3431
  const [visible, setVisible] = useState(false);
2597
3432
  const [isPreparing, setIsPreparing] = useState(false);
2598
3433
  const [rowError, setRowError] = useState(null);
3434
+ const [lockError, setLockError] = useState(null);
3435
+ const [lockSettled, setLockSettled] = useState(false);
2599
3436
  const {
2600
3437
  status,
2601
3438
  activeIntentId,
@@ -2613,7 +3450,25 @@ function FiatOnRampForm({
2613
3450
  finishPendingVerification,
2614
3451
  handleWidgetClosed,
2615
3452
  refreshPending
2616
- } = useFiatOnRamp({ tokenId, onCredited, onError, onDebugEvent });
3453
+ } = useFiatOnRamp({
3454
+ tokenId,
3455
+ postDepositLock,
3456
+ onCredited,
3457
+ // Lock callbacks aren't intent-keyed, so a resumed background row's lock
3458
+ // can settle these flags while a newer purchase is still locking — a
3459
+ // transient overpromise that the newer lock's own outcome then corrects.
3460
+ onLockSubmitted: (response) => {
3461
+ setLockError(null);
3462
+ setLockSettled(true);
3463
+ onLockSubmitted?.(response);
3464
+ },
3465
+ onLockFailed: (err) => {
3466
+ setLockError(err.message);
3467
+ onLockFailed?.(err);
3468
+ },
3469
+ onError,
3470
+ onDebugEvent
3471
+ });
2617
3472
  const decimals = selectedToken?.decimals;
2618
3473
  const displaySymbol = tokenSymbol ?? selectedToken?.symbol ?? currencyCode.toUpperCase();
2619
3474
  const emitFormDebug = useCallback(
@@ -2629,8 +3484,17 @@ function FiatOnRampForm({
2629
3484
  [onDebugEvent, status, tokenId]
2630
3485
  );
2631
3486
  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;
3487
+ const { units: quoteBaseUnits, failed: quoteParseFailed } = (() => {
3488
+ if (!quoteCurrencyAmount || decimals === void 0) return { units: void 0, failed: false };
3489
+ try {
3490
+ return { units: parseUnits(quoteCurrencyAmount, decimals), failed: false };
3491
+ } catch {
3492
+ return { units: void 0, failed: true };
3493
+ }
3494
+ })();
3495
+ const isBelowMin = quoteBaseUnits !== void 0 && minDepositBaseUnits !== void 0 ? quoteBaseUnits < minDepositBaseUnits : minFiatGate !== void 0 && Number(defaultBaseCurrencyAmount) < minFiatGate;
2633
3496
  const isBusy = isPreparing || status === "awaiting-purchase";
3497
+ const lockPending = !!postDepositLock && status === "credited" && !lockSettled && !lockError;
2634
3498
  const isInitializing = !!address && !depositAddress;
2635
3499
  const isPrePurchase = status === "idle" || status === "awaiting-purchase";
2636
3500
  const isVerifying = status === "awaiting-delivery" || status === "verifying";
@@ -2640,9 +3504,10 @@ function FiatOnRampForm({
2640
3504
  !depositAddress ? "deposit-address-not-loaded" : null,
2641
3505
  isBusy ? `busy:${isPreparing ? "preparing" : status}` : null,
2642
3506
  visible ? "widget-open" : null,
2643
- isBelowMin ? "below-minimum" : null
3507
+ isBelowMin ? "below-minimum" : null,
3508
+ quoteParseFailed ? "invalid-quote-amount" : null
2644
3509
  ].filter((reason) => Boolean(reason)),
2645
- [address, depositAddress, isBelowMin, isBusy, isPreparing, status, visible]
3510
+ [address, depositAddress, isBelowMin, isBusy, isPreparing, quoteParseFailed, status, visible]
2646
3511
  );
2647
3512
  const canBuy = blockReasons.length === 0;
2648
3513
  const handleOpen = useCallback(async () => {
@@ -2661,6 +3526,8 @@ function FiatOnRampForm({
2661
3526
  return;
2662
3527
  }
2663
3528
  setIsPreparing(true);
3529
+ setLockError(null);
3530
+ setLockSettled(false);
2664
3531
  emitFormDebug("form:open-click", {
2665
3532
  currencyCode,
2666
3533
  tokenSymbol: displaySymbol,
@@ -2674,7 +3541,8 @@ function FiatOnRampForm({
2674
3541
  const intent = await prepareOnRampIntent({
2675
3542
  currencyCode,
2676
3543
  baseCurrencyCode,
2677
- baseCurrencyAmount: defaultBaseCurrencyAmount
3544
+ baseCurrencyAmount: defaultBaseCurrencyAmount,
3545
+ quoteCurrencyAmount
2678
3546
  });
2679
3547
  emitFormDebug("form:intent-ready", {
2680
3548
  transactionId: intent.transaction_id,
@@ -2699,6 +3567,7 @@ function FiatOnRampForm({
2699
3567
  decimals,
2700
3568
  displaySymbol,
2701
3569
  defaultBaseCurrencyAmount,
3570
+ quoteCurrencyAmount,
2702
3571
  depositAddress,
2703
3572
  emitFormDebug,
2704
3573
  prepareOnRampIntent,
@@ -2729,6 +3598,7 @@ function FiatOnRampForm({
2729
3598
  colorCode,
2730
3599
  baseCurrencyCode,
2731
3600
  baseCurrencyAmount: defaultBaseCurrencyAmount,
3601
+ quoteCurrencyAmount,
2732
3602
  lockAmount,
2733
3603
  paymentMethod,
2734
3604
  currencyCode,
@@ -2801,7 +3671,7 @@ function FiatOnRampForm({
2801
3671
  );
2802
3672
  })
2803
3673
  ] }),
2804
- status === "credited" && /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center gap-2 py-8 text-center", children: [
3674
+ status === "credited" && !lockPending && !lockError && /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center gap-2 py-8 text-center", children: [
2805
3675
  /* @__PURE__ */ jsx(CircleCheckIcon, { className: "text-primary size-8", "aria-hidden": true }),
2806
3676
  /* @__PURE__ */ jsx("p", { className: "text-foreground text-sm font-medium", children: "Purchase credited" }),
2807
3677
  /* @__PURE__ */ jsxs("p", { className: "text-muted-foreground text-sm", children: [
@@ -2810,12 +3680,20 @@ function FiatOnRampForm({
2810
3680
  " deposit is now available in your balance."
2811
3681
  ] })
2812
3682
  ] }),
3683
+ lockPending && /* @__PURE__ */ jsxs("p", { className: "text-muted-foreground flex items-center gap-2 text-sm", children: [
3684
+ /* @__PURE__ */ jsx(Loader2, { className: "size-4 animate-spin", "aria-hidden": true }),
3685
+ "Purchase credited \u2014 locking your funds\u2026"
3686
+ ] }),
2813
3687
  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
3688
  (isBusy || visible) && /* @__PURE__ */ jsx(Loader2, { className: "animate-spin", "aria-hidden": true }),
2815
3689
  "Buy"
2816
3690
  ] }),
2817
3691
  widgetElement,
2818
3692
  error && /* @__PURE__ */ jsx("p", { className: "text-destructive text-sm", role: "alert", children: error.message }),
3693
+ lockError && /* @__PURE__ */ jsxs("p", { className: "text-destructive text-sm", role: "alert", children: [
3694
+ "Purchase credited to your account, but locking the funds to a service failed: ",
3695
+ lockError
3696
+ ] }),
2819
3697
  isBelowMin && minFiatGate !== void 0 && /* @__PURE__ */ jsxs("p", { className: "text-destructive text-sm", role: "alert", children: [
2820
3698
  "Minimum purchase is ~$",
2821
3699
  minFiatGate.toFixed(2),
@@ -2833,6 +3711,6 @@ function parseFinalityProgress(message) {
2833
3711
  return match ? `${match[1]} confirmations` : null;
2834
3712
  }
2835
3713
 
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
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