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