@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.
@@ -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,198 @@ 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
+
1705
2313
  // ../../node_modules/@wagmi/core/dist/esm/actions/getChainId.js
1706
2314
  function getChainId2(config) {
1707
2315
  return config.state.chainId;
@@ -1712,6 +2320,10 @@ async function getTransactionReceipt(config, parameters) {
1712
2320
  const action = getAction(client, actions$1.getTransactionReceipt, "getTransactionReceipt");
1713
2321
  return action(rest);
1714
2322
  }
2323
+ async function getWalletClient3(config, parameters = {}) {
2324
+ const client = await getConnectorClient(config, parameters);
2325
+ return client.extend(viem.walletActions);
2326
+ }
1715
2327
  async function waitForTransactionReceipt(config, parameters) {
1716
2328
  const { chainId, timeout = 0, ...rest } = parameters;
1717
2329
  const client = config.getClient({ chainId });
@@ -1739,6 +2351,59 @@ async function waitForTransactionReceipt(config, parameters) {
1739
2351
  chainId: client.chain.id
1740
2352
  };
1741
2353
  }
2354
+ function useEnsureCorrectChain() {
2355
+ const config = wagmi.useConfig();
2356
+ const chainId = wagmi.useChainId();
2357
+ const { switchChainAsync } = wagmi.useSwitchChain();
2358
+ const waitUntilOnChain = react.useCallback(
2359
+ async (expectedChainId, timeoutMs, pollIntervalMs = 250) => {
2360
+ const startedAt = Date.now();
2361
+ while (Date.now() - startedAt < timeoutMs) {
2362
+ const currentChainId = getChainId2(config);
2363
+ if (currentChainId === expectedChainId) return true;
2364
+ await new Promise((resolve) => setTimeout(resolve, pollIntervalMs));
2365
+ }
2366
+ return false;
2367
+ },
2368
+ [config]
2369
+ );
2370
+ const ensureCorrectChain = react.useCallback(
2371
+ async (targetChainId) => {
2372
+ const currentChainId = getChainId2(config);
2373
+ if (currentChainId === targetChainId) return false;
2374
+ let switchErrorMessage;
2375
+ try {
2376
+ const timeoutPromise = new Promise((_, reject) => {
2377
+ setTimeout(() => reject(new Error("Chain switch timeout")), 3e3);
2378
+ });
2379
+ await Promise.race([switchChainAsync({ chainId: targetChainId }), timeoutPromise]);
2380
+ } catch (error) {
2381
+ if (error instanceof Error && error.message.includes("Unsupported Chain")) {
2382
+ console.warn("Got 'Unsupported Chain' error, chain may have switched anyway.");
2383
+ } else {
2384
+ switchErrorMessage = error instanceof Error ? error.message : "Unknown chain switch error";
2385
+ }
2386
+ }
2387
+ const settled = await waitUntilOnChain(targetChainId, 2e4);
2388
+ if (settled) return true;
2389
+ if (switchErrorMessage) {
2390
+ throw new Error(
2391
+ `Failed to switch to chain (Chain ID: ${targetChainId}): ${switchErrorMessage}`
2392
+ );
2393
+ }
2394
+ throw new Error(`Chain switch did not settle in time (expected ${targetChainId}).`);
2395
+ },
2396
+ [config, switchChainAsync, waitUntilOnChain]
2397
+ );
2398
+ const isOnChain = react.useCallback((targetChainId) => chainId === targetChainId, [chainId]);
2399
+ return {
2400
+ chainId,
2401
+ ensureCorrectChain,
2402
+ isOnChain
2403
+ };
2404
+ }
2405
+
2406
+ // src/sdk/hooks/use-fiat-on-ramp.ts
1742
2407
  var DEFAULT_DELIVERY_TIMEOUT_MS = 12e4;
1743
2408
  var DEFAULT_VERIFICATION_TIMEOUT_MS = 10 * 6e4;
1744
2409
  var DEFAULT_FINALITY_RETRY_INTERVAL_MS = 15e3;
@@ -1746,14 +2411,26 @@ var ERC20_TRANSFER_EVENT = viem.parseAbiItem(
1746
2411
  "event Transfer(address indexed from, address indexed to, uint256 value)"
1747
2412
  );
1748
2413
  function useFiatOnRamp(options) {
1749
- const { tokenId, onCredited, onError, onDebugEvent } = options;
2414
+ const {
2415
+ tokenId,
2416
+ postDepositLock,
2417
+ onCredited,
2418
+ onLockSubmitted,
2419
+ onLockFailed,
2420
+ onError,
2421
+ onDebugEvent
2422
+ } = options;
1750
2423
  const deliveryTimeout = options.deliveryTimeout ?? DEFAULT_DELIVERY_TIMEOUT_MS;
1751
2424
  const deliveryPollInterval = options.deliveryPollInterval ?? 3e3;
1752
2425
  const verificationTimeout = options.verificationTimeout ?? DEFAULT_VERIFICATION_TIMEOUT_MS;
1753
2426
  const finalityRetryInterval = options.finalityRetryInterval ?? DEFAULT_FINALITY_RETRY_INTERVAL_MS;
1754
- const { client, enabledTokens } = usePrivanaContext();
2427
+ const { address } = wagmi.useAccount();
2428
+ const { data: walletClient } = wagmi.useWalletClient();
2429
+ const { client, enabledTokens, networkConfig, serviceAddress } = usePrivanaContext();
1755
2430
  const { executePrivateRead, privateReadReady } = usePrivateReadRequest();
2431
+ const { ensureCorrectChain } = useEnsureCorrectChain();
1756
2432
  const wagmiConfig = wagmi.useConfig();
2433
+ const queryClient = reactQuery.useQueryClient();
1757
2434
  const selectedToken = enabledTokens.find((t) => t.id.toLowerCase() === tokenId.toLowerCase());
1758
2435
  const [status, setStatus] = react.useState("idle");
1759
2436
  const [pending, setPending] = react.useState([]);
@@ -1764,20 +2441,27 @@ function useFiatOnRamp(options) {
1764
2441
  const [activeVerificationId, setActiveVerificationId] = react.useState(null);
1765
2442
  const [finalityProgress, setFinalityProgress] = react.useState({});
1766
2443
  const onCreditedRef = react.useRef(onCredited);
2444
+ const onLockSubmittedRef = react.useRef(onLockSubmitted);
2445
+ const onLockFailedRef = react.useRef(onLockFailed);
1767
2446
  const onErrorRef = react.useRef(onError);
1768
2447
  const onDebugEventRef = react.useRef(onDebugEvent);
1769
2448
  const statusRef = react.useRef(status);
1770
2449
  const activeIntentIdRef = react.useRef(null);
1771
2450
  const activeVerificationRecordRef = react.useRef(null);
1772
2451
  const activeVerificationKeyRef = react.useRef(null);
2452
+ const activeVerificationAmountRef = react.useRef(null);
2453
+ const lockOwnerRef = react.useRef(null);
1773
2454
  const triggeredVerificationKeysRef = react.useRef(/* @__PURE__ */ new Set());
2455
+ const activeVerificationDoneRef = react.useRef(null);
1774
2456
  const closeReconcilePromiseRef = react.useRef(null);
1775
2457
  const purchaseInitiatedRef = react.useRef(false);
1776
2458
  react.useEffect(() => {
1777
2459
  onCreditedRef.current = onCredited;
2460
+ onLockSubmittedRef.current = onLockSubmitted;
2461
+ onLockFailedRef.current = onLockFailed;
1778
2462
  onErrorRef.current = onError;
1779
2463
  onDebugEventRef.current = onDebugEvent;
1780
- }, [onCredited, onError, onDebugEvent]);
2464
+ }, [onCredited, onLockSubmitted, onLockFailed, onError, onDebugEvent]);
1781
2465
  react.useEffect(() => {
1782
2466
  statusRef.current = status;
1783
2467
  }, [status]);
@@ -1858,13 +2542,64 @@ function useFiatOnRamp(options) {
1858
2542
  react.useEffect(() => {
1859
2543
  refreshPending();
1860
2544
  }, [refreshPending]);
1861
- const clearActiveVerification = react.useCallback(() => {
2545
+ const clearActiveVerification = react.useCallback((expectedKey) => {
1862
2546
  const key = activeVerificationKeyRef.current;
2547
+ if (expectedKey != null && key !== null && key !== expectedKey) return;
1863
2548
  if (key) triggeredVerificationKeysRef.current.delete(key);
1864
2549
  activeVerificationKeyRef.current = null;
1865
2550
  activeVerificationRecordRef.current = null;
2551
+ activeVerificationAmountRef.current = null;
1866
2552
  setActiveVerificationId(null);
2553
+ activeVerificationDoneRef.current?.();
2554
+ activeVerificationDoneRef.current = null;
1867
2555
  }, []);
2556
+ const submitPendingLockAfterCredit = react.useCallback(
2557
+ async (transactionId, userAddress) => {
2558
+ const signedLock = loadPendingLock(userAddress, transactionId);
2559
+ if (!signedLock) {
2560
+ clearPendingLock(userAddress, transactionId);
2561
+ if (!postDepositLock || transactionId !== activeIntentIdRef.current) return;
2562
+ const error2 = new PostDepositLockError(
2563
+ "No persisted signed lock found for this on-ramp",
2564
+ "not-found"
2565
+ );
2566
+ emitDebug("lock:not-found", { transactionId });
2567
+ (onLockFailedRef.current ?? onErrorRef.current)?.(error2);
2568
+ return;
2569
+ }
2570
+ const creditedAmount = activeVerificationAmountRef.current ?? void 0;
2571
+ try {
2572
+ const result = await submitPendingLock({ client, payload: signedLock, creditedAmount });
2573
+ queryClient.invalidateQueries({ queryKey: ["accounting-balance"] });
2574
+ queryClient.invalidateQueries({ queryKey: ["accounting-locked-funds"] });
2575
+ queryClient.invalidateQueries({ queryKey: ["accounting-total-locked-balance"] });
2576
+ queryClient.invalidateQueries({ queryKey: ["accounting-history"] });
2577
+ emitDebug("lock:submitted", {
2578
+ transactionId,
2579
+ amount: signedLock.amount,
2580
+ submissionId: result.submission_id
2581
+ });
2582
+ onLockSubmittedRef.current?.(result);
2583
+ } catch (err) {
2584
+ const error2 = err instanceof PostDepositLockError ? err : new PostDepositLockError(
2585
+ err instanceof Error ? err.message : "Lock submission failed",
2586
+ "submission-failed",
2587
+ BigInt(signedLock.amount),
2588
+ creditedAmount,
2589
+ { cause: err }
2590
+ );
2591
+ emitDebug("lock:failed", {
2592
+ transactionId,
2593
+ reason: error2.reason,
2594
+ message: error2.message
2595
+ });
2596
+ (onLockFailedRef.current ?? onErrorRef.current)?.(error2);
2597
+ } finally {
2598
+ clearPendingLock(userAddress, transactionId);
2599
+ }
2600
+ },
2601
+ [client, emitDebug, postDepositLock, queryClient]
2602
+ );
1868
2603
  const { verify } = useDepositVerification({
1869
2604
  pollTimeout: verificationTimeout,
1870
2605
  pollInterval: options.verificationPollInterval,
@@ -1880,6 +2615,7 @@ function useFiatOnRamp(options) {
1880
2615
  },
1881
2616
  onCredited: (depositTxHash) => {
1882
2617
  const record = activeVerificationRecordRef.current;
2618
+ const verificationKey = record ? getOnRampVerificationKey(record) : null;
1883
2619
  emitDebug("verification:credited", {
1884
2620
  depositTxHash,
1885
2621
  record: record ? summariseOnRampRecord(record) : null
@@ -1894,6 +2630,18 @@ function useFiatOnRamp(options) {
1894
2630
  delete next[record.transaction_id];
1895
2631
  return next;
1896
2632
  });
2633
+ const lockOwner = lockOwnerRef.current ?? address;
2634
+ if (lockOwner) {
2635
+ void submitPendingLockAfterCredit(record.transaction_id, lockOwner);
2636
+ } else if (postDepositLock) {
2637
+ emitDebug("lock:owner-unavailable", { transactionId: record.transaction_id });
2638
+ (onLockFailedRef.current ?? onErrorRef.current)?.(
2639
+ new PostDepositLockError(
2640
+ "No wallet address available to look up the signed lock for this on-ramp",
2641
+ "not-found"
2642
+ )
2643
+ );
2644
+ }
1897
2645
  }
1898
2646
  void (async () => {
1899
2647
  try {
@@ -1916,7 +2664,7 @@ function useFiatOnRamp(options) {
1916
2664
  console.warn("Failed to mark on-ramp row complete:", err);
1917
2665
  } finally {
1918
2666
  await refreshPending();
1919
- clearActiveVerification();
2667
+ clearActiveVerification(verificationKey);
1920
2668
  if (record && activeIntentIdRef.current === record.transaction_id) {
1921
2669
  activeIntentIdRef.current = null;
1922
2670
  setActiveIntentId(null);
@@ -1954,7 +2702,8 @@ function useFiatOnRamp(options) {
1954
2702
  async ({
1955
2703
  currencyCode,
1956
2704
  baseCurrencyCode,
1957
- baseCurrencyAmount
2705
+ baseCurrencyAmount,
2706
+ quoteCurrencyAmount
1958
2707
  }) => {
1959
2708
  try {
1960
2709
  setError(null);
@@ -1962,12 +2711,34 @@ function useFiatOnRamp(options) {
1962
2711
  const token = enabledTokens.find((t) => t.id.toLowerCase() === tokenId.toLowerCase());
1963
2712
  if (!token) throw new Error(`Unknown token: ${tokenId}`);
1964
2713
  if (!depositAddress) throw new Error("Privana deposit address is not ready");
2714
+ let lockAmount;
2715
+ if (postDepositLock) {
2716
+ if (!address || !walletClient) throw new Error("Wallet not connected");
2717
+ if (!quoteCurrencyAmount) {
2718
+ throw new Error(
2719
+ "postDepositLock requires quoteCurrencyAmount to derive the lock amount"
2720
+ );
2721
+ }
2722
+ if (!canUseBrowserStorage()) {
2723
+ throw new Error("Browser storage is required for locked on-ramp recovery");
2724
+ }
2725
+ const buffered = applyLockBuffer(
2726
+ viem.parseUnits(quoteCurrencyAmount, token.decimals),
2727
+ postDepositLock.buffer
2728
+ );
2729
+ lockAmount = clampLockAmount(buffered, postDepositLock.maxAmount);
2730
+ if (lockAmount <= 0n) {
2731
+ throw new Error(`Post-deposit lock amount must be positive, got ${lockAmount}`);
2732
+ }
2733
+ await ensureCorrectChain(networkConfig.chainId);
2734
+ }
1965
2735
  emitDebug("intent:create-request", {
1966
2736
  tokenId,
1967
2737
  chainId: token.chainId,
1968
2738
  currencyCode,
1969
2739
  baseCurrencyCode: baseCurrencyCode ?? null,
1970
2740
  baseCurrencyAmount: baseCurrencyAmount ?? null,
2741
+ quoteCurrencyAmount: quoteCurrencyAmount ?? null,
1971
2742
  depositAddress
1972
2743
  });
1973
2744
  const record = await executePrivateRead(
@@ -1980,6 +2751,28 @@ function useFiatOnRamp(options) {
1980
2751
  base_currency_amount: baseCurrencyAmount
1981
2752
  })
1982
2753
  );
2754
+ if (postDepositLock && address && walletClient && lockAmount !== void 0) {
2755
+ const signingWalletClient = await getWalletClient3(wagmiConfig, {
2756
+ chainId: networkConfig.chainId
2757
+ });
2758
+ const signedLock = await createSignedLockRequest({
2759
+ client,
2760
+ walletClient: signingWalletClient,
2761
+ userAddress: address,
2762
+ networkConfig,
2763
+ serviceAddress: requireServiceAddress(postDepositLock.serviceAddress ?? serviceAddress),
2764
+ tokenId,
2765
+ amount: lockAmount,
2766
+ lockDuration: postDepositLock.lockDuration
2767
+ });
2768
+ savePendingLock(address, record.transaction_id, signedLock);
2769
+ lockOwnerRef.current = address;
2770
+ emitDebug("intent:lock-signed", {
2771
+ transactionId: record.transaction_id,
2772
+ amount: signedLock.amount,
2773
+ expiry: signedLock.expiry
2774
+ });
2775
+ }
1983
2776
  activeIntentIdRef.current = record.transaction_id;
1984
2777
  setActiveIntentId(record.transaction_id);
1985
2778
  emitDebug("intent:create-success", {
@@ -1995,7 +2788,21 @@ function useFiatOnRamp(options) {
1995
2788
  throw e;
1996
2789
  }
1997
2790
  },
1998
- [client, depositAddress, emitDebug, enabledTokens, executePrivateRead, tokenId]
2791
+ [
2792
+ address,
2793
+ client,
2794
+ depositAddress,
2795
+ emitDebug,
2796
+ enabledTokens,
2797
+ ensureCorrectChain,
2798
+ executePrivateRead,
2799
+ networkConfig,
2800
+ postDepositLock,
2801
+ serviceAddress,
2802
+ tokenId,
2803
+ wagmiConfig,
2804
+ walletClient
2805
+ ]
1999
2806
  );
2000
2807
  const registerOnRampTokenMapping = react.useCallback(
2001
2808
  async (moonpayTransactionId) => {
@@ -2117,6 +2924,12 @@ function useFiatOnRamp(options) {
2117
2924
  });
2118
2925
  return;
2119
2926
  }
2927
+ const supersededKey = activeVerificationKeyRef.current;
2928
+ if (supersededKey && supersededKey !== verificationKey) {
2929
+ triggeredVerificationKeysRef.current.delete(supersededKey);
2930
+ }
2931
+ activeVerificationDoneRef.current?.();
2932
+ activeVerificationDoneRef.current = null;
2120
2933
  triggeredVerificationKeysRef.current.add(verificationKey);
2121
2934
  activeVerificationKeyRef.current = verificationKey;
2122
2935
  activeVerificationRecordRef.current = record;
@@ -2167,6 +2980,7 @@ function useFiatOnRamp(options) {
2167
2980
  `Delivered amount (${record.quote_currency_amount}) is below the minimum deposit.`
2168
2981
  );
2169
2982
  }
2983
+ activeVerificationAmountRef.current = amount;
2170
2984
  if (activeIntentIdRef.current === record.transaction_id) {
2171
2985
  setStatus("verifying");
2172
2986
  }
@@ -2185,6 +2999,7 @@ function useFiatOnRamp(options) {
2185
2999
  if (activeVerificationKeyRef.current === verificationKey) {
2186
3000
  activeVerificationKeyRef.current = null;
2187
3001
  activeVerificationRecordRef.current = null;
3002
+ activeVerificationAmountRef.current = null;
2188
3003
  setActiveVerificationId(null);
2189
3004
  }
2190
3005
  throw err;
@@ -2243,6 +3058,7 @@ function useFiatOnRamp(options) {
2243
3058
  previousStatus,
2244
3059
  transactionId
2245
3060
  });
3061
+ if (address) clearPendingLock(address, transactionId);
2246
3062
  await refreshPending();
2247
3063
  if (previousStatus === "awaiting-purchase" || previousStatus === "awaiting-delivery") {
2248
3064
  setStatus("idle");
@@ -2273,7 +3089,7 @@ function useFiatOnRamp(options) {
2273
3089
  } finally {
2274
3090
  closeReconcilePromiseRef.current = null;
2275
3091
  }
2276
- }, [emitDebug, refreshPending, triggerVerification, waitForOnChainHash]);
3092
+ }, [address, emitDebug, refreshPending, triggerVerification, waitForOnChainHash]);
2277
3093
  const finishPendingVerification = react.useCallback(
2278
3094
  async (record) => {
2279
3095
  try {
@@ -2307,6 +3123,12 @@ function useFiatOnRamp(options) {
2307
3123
  try {
2308
3124
  await triggerVerificationRef.current(record);
2309
3125
  } catch {
3126
+ continue;
3127
+ }
3128
+ if (activeVerificationKeyRef.current === key) {
3129
+ await new Promise((resolve) => {
3130
+ activeVerificationDoneRef.current = resolve;
3131
+ });
2310
3132
  }
2311
3133
  }
2312
3134
  })();
@@ -2423,6 +3245,12 @@ function matchesOnRampTransaction(record, transactionId) {
2423
3245
  function getOnRampVerificationKey(record) {
2424
3246
  return record.on_chain_tx_hash ?? record.transaction_id;
2425
3247
  }
3248
+ function requireServiceAddress(serviceAddress) {
3249
+ if (!serviceAddress) {
3250
+ throw new Error("Service address not configured");
3251
+ }
3252
+ return serviceAddress;
3253
+ }
2426
3254
  function summariseMoonPayEventProps(props) {
2427
3255
  return {
2428
3256
  id: props.id,
@@ -2480,6 +3308,7 @@ function useMoonPayBuyWidget({
2480
3308
  colorCode,
2481
3309
  baseCurrencyCode,
2482
3310
  baseCurrencyAmount,
3311
+ quoteCurrencyAmount,
2483
3312
  lockAmount,
2484
3313
  paymentMethod,
2485
3314
  currencyCode,
@@ -2539,6 +3368,7 @@ function useMoonPayBuyWidget({
2539
3368
  overlayNode,
2540
3369
  baseCurrencyCode,
2541
3370
  baseCurrencyAmount,
3371
+ quoteCurrencyAmount,
2542
3372
  lockAmount: lockAmount ? "true" : void 0,
2543
3373
  paymentMethod,
2544
3374
  currencyCode,
@@ -2564,6 +3394,7 @@ function useMoonPayBuyWidget({
2564
3394
  overlayNode,
2565
3395
  baseCurrencyCode,
2566
3396
  baseCurrencyAmount,
3397
+ quoteCurrencyAmount,
2567
3398
  lockAmount,
2568
3399
  paymentMethod,
2569
3400
  currencyCode,
@@ -2582,6 +3413,7 @@ function FiatOnRampForm({
2582
3413
  currencyCode,
2583
3414
  baseCurrencyCode = "usd",
2584
3415
  defaultBaseCurrencyAmount = "100",
3416
+ quoteCurrencyAmount,
2585
3417
  tokenSymbol,
2586
3418
  theme,
2587
3419
  themeId,
@@ -2590,7 +3422,10 @@ function FiatOnRampForm({
2590
3422
  autoStart = false,
2591
3423
  lockAmount,
2592
3424
  paymentMethod,
3425
+ postDepositLock,
2593
3426
  onCredited,
3427
+ onLockSubmitted,
3428
+ onLockFailed,
2594
3429
  onError,
2595
3430
  onDebugEvent
2596
3431
  }) {
@@ -2598,6 +3433,8 @@ function FiatOnRampForm({
2598
3433
  const [visible, setVisible] = react.useState(false);
2599
3434
  const [isPreparing, setIsPreparing] = react.useState(false);
2600
3435
  const [rowError, setRowError] = react.useState(null);
3436
+ const [lockError, setLockError] = react.useState(null);
3437
+ const [lockSettled, setLockSettled] = react.useState(false);
2601
3438
  const {
2602
3439
  status,
2603
3440
  activeIntentId,
@@ -2615,7 +3452,25 @@ function FiatOnRampForm({
2615
3452
  finishPendingVerification,
2616
3453
  handleWidgetClosed,
2617
3454
  refreshPending
2618
- } = useFiatOnRamp({ tokenId, onCredited, onError, onDebugEvent });
3455
+ } = useFiatOnRamp({
3456
+ tokenId,
3457
+ postDepositLock,
3458
+ onCredited,
3459
+ // Lock callbacks aren't intent-keyed, so a resumed background row's lock
3460
+ // can settle these flags while a newer purchase is still locking — a
3461
+ // transient overpromise that the newer lock's own outcome then corrects.
3462
+ onLockSubmitted: (response) => {
3463
+ setLockError(null);
3464
+ setLockSettled(true);
3465
+ onLockSubmitted?.(response);
3466
+ },
3467
+ onLockFailed: (err) => {
3468
+ setLockError(err.message);
3469
+ onLockFailed?.(err);
3470
+ },
3471
+ onError,
3472
+ onDebugEvent
3473
+ });
2619
3474
  const decimals = selectedToken?.decimals;
2620
3475
  const displaySymbol = tokenSymbol ?? selectedToken?.symbol ?? currencyCode.toUpperCase();
2621
3476
  const emitFormDebug = react.useCallback(
@@ -2631,8 +3486,17 @@ function FiatOnRampForm({
2631
3486
  [onDebugEvent, status, tokenId]
2632
3487
  );
2633
3488
  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;
3489
+ const { units: quoteBaseUnits, failed: quoteParseFailed } = (() => {
3490
+ if (!quoteCurrencyAmount || decimals === void 0) return { units: void 0, failed: false };
3491
+ try {
3492
+ return { units: viem.parseUnits(quoteCurrencyAmount, decimals), failed: false };
3493
+ } catch {
3494
+ return { units: void 0, failed: true };
3495
+ }
3496
+ })();
3497
+ const isBelowMin = quoteBaseUnits !== void 0 && minDepositBaseUnits !== void 0 ? quoteBaseUnits < minDepositBaseUnits : minFiatGate !== void 0 && Number(defaultBaseCurrencyAmount) < minFiatGate;
2635
3498
  const isBusy = isPreparing || status === "awaiting-purchase";
3499
+ const lockPending = !!postDepositLock && status === "credited" && !lockSettled && !lockError;
2636
3500
  const isInitializing = !!address && !depositAddress;
2637
3501
  const isPrePurchase = status === "idle" || status === "awaiting-purchase";
2638
3502
  const isVerifying = status === "awaiting-delivery" || status === "verifying";
@@ -2642,9 +3506,10 @@ function FiatOnRampForm({
2642
3506
  !depositAddress ? "deposit-address-not-loaded" : null,
2643
3507
  isBusy ? `busy:${isPreparing ? "preparing" : status}` : null,
2644
3508
  visible ? "widget-open" : null,
2645
- isBelowMin ? "below-minimum" : null
3509
+ isBelowMin ? "below-minimum" : null,
3510
+ quoteParseFailed ? "invalid-quote-amount" : null
2646
3511
  ].filter((reason) => Boolean(reason)),
2647
- [address, depositAddress, isBelowMin, isBusy, isPreparing, status, visible]
3512
+ [address, depositAddress, isBelowMin, isBusy, isPreparing, quoteParseFailed, status, visible]
2648
3513
  );
2649
3514
  const canBuy = blockReasons.length === 0;
2650
3515
  const handleOpen = react.useCallback(async () => {
@@ -2663,6 +3528,8 @@ function FiatOnRampForm({
2663
3528
  return;
2664
3529
  }
2665
3530
  setIsPreparing(true);
3531
+ setLockError(null);
3532
+ setLockSettled(false);
2666
3533
  emitFormDebug("form:open-click", {
2667
3534
  currencyCode,
2668
3535
  tokenSymbol: displaySymbol,
@@ -2676,7 +3543,8 @@ function FiatOnRampForm({
2676
3543
  const intent = await prepareOnRampIntent({
2677
3544
  currencyCode,
2678
3545
  baseCurrencyCode,
2679
- baseCurrencyAmount: defaultBaseCurrencyAmount
3546
+ baseCurrencyAmount: defaultBaseCurrencyAmount,
3547
+ quoteCurrencyAmount
2680
3548
  });
2681
3549
  emitFormDebug("form:intent-ready", {
2682
3550
  transactionId: intent.transaction_id,
@@ -2701,6 +3569,7 @@ function FiatOnRampForm({
2701
3569
  decimals,
2702
3570
  displaySymbol,
2703
3571
  defaultBaseCurrencyAmount,
3572
+ quoteCurrencyAmount,
2704
3573
  depositAddress,
2705
3574
  emitFormDebug,
2706
3575
  prepareOnRampIntent,
@@ -2731,6 +3600,7 @@ function FiatOnRampForm({
2731
3600
  colorCode,
2732
3601
  baseCurrencyCode,
2733
3602
  baseCurrencyAmount: defaultBaseCurrencyAmount,
3603
+ quoteCurrencyAmount,
2734
3604
  lockAmount,
2735
3605
  paymentMethod,
2736
3606
  currencyCode,
@@ -2803,7 +3673,7 @@ function FiatOnRampForm({
2803
3673
  );
2804
3674
  })
2805
3675
  ] }),
2806
- status === "credited" && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col items-center gap-2 py-8 text-center", children: [
3676
+ status === "credited" && !lockPending && !lockError && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col items-center gap-2 py-8 text-center", children: [
2807
3677
  /* @__PURE__ */ jsxRuntime.jsx(lucideReact.CircleCheckIcon, { className: "text-primary size-8", "aria-hidden": true }),
2808
3678
  /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-foreground text-sm font-medium", children: "Purchase credited" }),
2809
3679
  /* @__PURE__ */ jsxRuntime.jsxs("p", { className: "text-muted-foreground text-sm", children: [
@@ -2812,12 +3682,20 @@ function FiatOnRampForm({
2812
3682
  " deposit is now available in your balance."
2813
3683
  ] })
2814
3684
  ] }),
3685
+ lockPending && /* @__PURE__ */ jsxRuntime.jsxs("p", { className: "text-muted-foreground flex items-center gap-2 text-sm", children: [
3686
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Loader2, { className: "size-4 animate-spin", "aria-hidden": true }),
3687
+ "Purchase credited \u2014 locking your funds\u2026"
3688
+ ] }),
2815
3689
  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
3690
  (isBusy || visible) && /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Loader2, { className: "animate-spin", "aria-hidden": true }),
2817
3691
  "Buy"
2818
3692
  ] }),
2819
3693
  widgetElement,
2820
3694
  error && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-destructive text-sm", role: "alert", children: error.message }),
3695
+ lockError && /* @__PURE__ */ jsxRuntime.jsxs("p", { className: "text-destructive text-sm", role: "alert", children: [
3696
+ "Purchase credited to your account, but locking the funds to a service failed: ",
3697
+ lockError
3698
+ ] }),
2821
3699
  isBelowMin && minFiatGate !== void 0 && /* @__PURE__ */ jsxRuntime.jsxs("p", { className: "text-destructive text-sm", role: "alert", children: [
2822
3700
  "Minimum purchase is ~$",
2823
3701
  minFiatGate.toFixed(2),
@@ -2837,43 +3715,63 @@ function parseFinalityProgress(message) {
2837
3715
 
2838
3716
  exports.AccountingApiError = AccountingApiError;
2839
3717
  exports.Button = Button;
3718
+ exports.DEFAULT_LOCK_DURATION_SECONDS = DEFAULT_LOCK_DURATION_SECONDS;
3719
+ exports.DEFAULT_ONRAMP_LOCK_BUFFER = DEFAULT_ONRAMP_LOCK_BUFFER;
2840
3720
  exports.FiatOnRampForm = FiatOnRampForm;
2841
3721
  exports.HOSTED_AUTH_CLOCK_SKEW_MS = HOSTED_AUTH_CLOCK_SKEW_MS;
2842
3722
  exports.HostedAuthError = HostedAuthError;
2843
3723
  exports.HostedAuthRequiredError = HostedAuthRequiredError;
2844
3724
  exports.HostedAuthStateMismatchError = HostedAuthStateMismatchError;
2845
3725
  exports.HttpClient = HttpClient;
3726
+ exports.LOCK_TYPES = LOCK_TYPES;
3727
+ exports.MODIFY_LOCK_TYPES = MODIFY_LOCK_TYPES;
2846
3728
  exports.NETWORK_CONFIG = NETWORK_CONFIG;
2847
3729
  exports.NetworkError = NetworkError;
3730
+ exports.PostDepositLockError = PostDepositLockError;
2848
3731
  exports.PrivanaClient = PrivanaClient;
2849
3732
  exports.PrivanaProvider = PrivanaProvider;
2850
3733
  exports.SUPPORTED_CHAINS = SUPPORTED_CHAINS;
2851
3734
  exports.SiweAuthProvider = SiweAuthProvider;
2852
3735
  exports.Skeleton = Skeleton;
3736
+ exports.TRANSFER_LOCKED_TYPES = TRANSFER_LOCKED_TYPES;
3737
+ exports.TRANSFER_TYPES = TRANSFER_TYPES;
2853
3738
  exports.ValidationError = ValidationError;
3739
+ exports.WITHDRAW_FROM_LOCK_TYPES = WITHDRAW_FROM_LOCK_TYPES;
3740
+ exports.WITHDRAW_TYPES = WITHDRAW_TYPES;
3741
+ exports.applyLockBuffer = applyLockBuffer;
2854
3742
  exports.applyRefreshResponse = applyRefreshResponse;
2855
3743
  exports.buildHostedAuthSession = buildHostedAuthSession;
2856
3744
  exports.buildSiweStatement = buildSiweStatement;
2857
3745
  exports.buttonVariants = buttonVariants;
3746
+ exports.canUseBrowserStorage = canUseBrowserStorage;
3747
+ exports.clampLockAmount = clampLockAmount;
2858
3748
  exports.clearHostedAuthPendingTransaction = clearHostedAuthPendingTransaction;
3749
+ exports.clearPendingLock = clearPendingLock;
2859
3750
  exports.cn = cn;
3751
+ exports.createDomain = createDomain;
2860
3752
  exports.createHostedAuthPendingStorageKey = createHostedAuthPendingStorageKey;
2861
3753
  exports.createHostedAuthState = createHostedAuthState;
2862
3754
  exports.createHostedAuthStorageKey = createHostedAuthStorageKey;
3755
+ exports.createLockExpiry = createLockExpiry;
2863
3756
  exports.createPkceChallenge = createPkceChallenge;
2864
3757
  exports.createPkceVerifier = createPkceVerifier;
3758
+ exports.createSignedLockRequest = createSignedLockRequest;
3759
+ exports.formatCountdown = formatCountdown;
2865
3760
  exports.formatTimeRemaining = formatTimeRemaining;
2866
3761
  exports.formatTokenAmount = formatTokenAmount;
2867
3762
  exports.getAccountingContract = getAccountingContract;
2868
3763
  exports.getApiUrl = getApiUrl;
3764
+ exports.getBrowserStorageItem = getBrowserStorageItem;
2869
3765
  exports.getChainById = getChainById;
2870
3766
  exports.getChainId = getChainId;
2871
- exports.getChainId2 = getChainId2;
2872
3767
  exports.getExplorerAddressUrl = getExplorerAddressUrl;
2873
3768
  exports.getExplorerLabel = getExplorerLabel;
2874
3769
  exports.getTransactionReceipt = getTransactionReceipt;
3770
+ exports.getWalletClient = getWalletClient3;
2875
3771
  exports.isHostedAuthRefreshActive = isHostedAuthRefreshActive;
2876
3772
  exports.isHostedAuthSessionActive = isHostedAuthSessionActive;
3773
+ exports.isSignedLockUsable = isSignedLockUsable;
3774
+ exports.loadPendingLock = loadPendingLock;
2877
3775
  exports.normalizeAddress = normalizeAddress;
2878
3776
  exports.normalizeHex = normalizeHex;
2879
3777
  exports.parseHostedAuthCallback = parseHostedAuthCallback;
@@ -2881,10 +3779,21 @@ exports.parseTokenAmount = parseTokenAmount;
2881
3779
  exports.persistHostedAuthPendingTransaction = persistHostedAuthPendingTransaction;
2882
3780
  exports.readHostedAuthPendingTransaction = readHostedAuthPendingTransaction;
2883
3781
  exports.readStoredHostedAuthSession = readStoredHostedAuthSession;
3782
+ exports.removeBrowserStorageItem = removeBrowserStorageItem;
3783
+ exports.savePendingLock = savePendingLock;
3784
+ exports.setBrowserStorageItem = setBrowserStorageItem;
2884
3785
  exports.shortenAddress = shortenAddress;
3786
+ exports.signLockMessage = signLockMessage;
3787
+ exports.signModifyLockMessage = signModifyLockMessage;
3788
+ exports.signTransferLockedMessage = signTransferLockedMessage;
3789
+ exports.signTransferMessage = signTransferMessage;
3790
+ exports.signWithdrawFromLockMessage = signWithdrawFromLockMessage;
3791
+ exports.signWithdrawMessage = signWithdrawMessage;
2885
3792
  exports.stripHostedAuthCallbackParams = stripHostedAuthCallbackParams;
3793
+ exports.submitPendingLock = submitPendingLock;
2886
3794
  exports.syncHostedAuthSessionToClient = syncHostedAuthSessionToClient;
2887
3795
  exports.useDepositVerification = useDepositVerification;
3796
+ exports.useEnsureCorrectChain = useEnsureCorrectChain;
2888
3797
  exports.useFiatOnRamp = useFiatOnRamp;
2889
3798
  exports.usePrivanaContext = usePrivanaContext;
2890
3799
  exports.usePrivateReadRequest = usePrivateReadRequest;
@@ -2892,5 +3801,5 @@ exports.useSafeAccount = useSafeAccount;
2892
3801
  exports.useSafePrivanaContext = useSafePrivanaContext;
2893
3802
  exports.useSiweAuth = useSiweAuth;
2894
3803
  exports.waitForTransactionReceipt = waitForTransactionReceipt;
2895
- //# sourceMappingURL=chunk-ACLJPC75.cjs.map
2896
- //# sourceMappingURL=chunk-ACLJPC75.cjs.map
3804
+ //# sourceMappingURL=chunk-4IW4V7YJ.cjs.map
3805
+ //# sourceMappingURL=chunk-4IW4V7YJ.cjs.map