@oasisprotocol/privana-sdk 0.4.3 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -13,6 +13,8 @@ var classVarianceAuthority = require('class-variance-authority');
13
13
  var clsx = require('clsx');
14
14
  var tailwindMerge = require('tailwind-merge');
15
15
  var actions$1 = require('viem/actions');
16
+ var lucideReact = require('lucide-react');
17
+ var moonpayReact = require('@moonpay/moonpay-react');
16
18
 
17
19
  // ../../shared/config.json
18
20
  var config_default = {
@@ -20,12 +22,14 @@ var config_default = {
20
22
  {
21
23
  id: 84532,
22
24
  name: "Base Sepolia",
23
- explorerUrl: "https://sepolia.basescan.org"
25
+ explorerUrl: "https://sepolia.basescan.org",
26
+ explorerName: "BaseScan"
24
27
  },
25
28
  {
26
29
  id: 11155111,
27
30
  name: "Ethereum Sepolia",
28
- explorerUrl: "https://sepolia.etherscan.io"
31
+ explorerUrl: "https://sepolia.etherscan.io",
32
+ explorerName: "Etherscan"
29
33
  }
30
34
  ],
31
35
  networks: {
@@ -76,11 +80,16 @@ function normalizeAddress(value) {
76
80
  var SUPPORTED_CHAINS = config_default.chains.map((chain) => ({
77
81
  id: chain.id,
78
82
  name: chain.name,
79
- explorerUrl: chain.explorerUrl
83
+ explorerUrl: chain.explorerUrl,
84
+ explorerName: chain.explorerName
80
85
  }));
81
86
  function getChainById(chainId) {
82
87
  return SUPPORTED_CHAINS.find((c) => c.id === chainId);
83
88
  }
89
+ function getExplorerLabel(chainId) {
90
+ const chain = getChainById(chainId);
91
+ return `View on ${chain?.explorerName ?? "Explorer"}`;
92
+ }
84
93
  function getExplorerAddressUrl(chainId, address) {
85
94
  const chain = getChainById(chainId);
86
95
  if (!chain) return void 0;
@@ -211,6 +220,11 @@ function isHostedAuthRefreshActive(session, now = Date.now(), skewMs = HOSTED_AU
211
220
  return session.refreshExpiresAt > now + skewMs;
212
221
  }
213
222
 
223
+ // src/sdk/auth/siwe.ts
224
+ function buildSiweStatement(chainId) {
225
+ return `Sign in to Privana on chain ${chainId}`;
226
+ }
227
+
214
228
  // src/sdk/client/errors.ts
215
229
  var AccountingApiError = class _AccountingApiError extends Error {
216
230
  constructor(message, statusCode, detail) {
@@ -665,15 +679,13 @@ function deleteCachedPrivateReadToken(scopeKey) {
665
679
  cache.delete(scopeKey);
666
680
  }
667
681
  var DEFAULT_SIWE_VALIDITY_MS = 24 * 60 * 60 * 1e3;
668
- var DEFAULT_STATEMENT = "Sign in to access your private account data.";
669
682
  var AUTH_REFRESH_SKEW_MS = 3e4;
670
683
  var SiweAuthContext = react.createContext(null);
671
684
  function SiweAuthProvider({
672
685
  children,
673
686
  client,
674
687
  networkConfig,
675
- autoLogin = true,
676
- statement
688
+ autoLogin = true
677
689
  }) {
678
690
  const wagmiContext = react.useContext(wagmi.WagmiContext);
679
691
  const { address, isConnected, status } = useSafeAccount();
@@ -732,7 +744,7 @@ function SiweAuthProvider({
732
744
  uri,
733
745
  version: "1",
734
746
  nonce: nonceRes.nonce,
735
- statement: statement ?? DEFAULT_STATEMENT,
747
+ statement: buildSiweStatement(networkConfig.chainId),
736
748
  issuedAt,
737
749
  expirationTime
738
750
  });
@@ -768,7 +780,7 @@ function SiweAuthProvider({
768
780
  setIsLoading(false);
769
781
  loginInFlight.current = false;
770
782
  }
771
- }, [wagmiContext, address, client, networkConfig.chainId, networkConfig.apiUrl, statement]);
783
+ }, [wagmiContext, address, client, networkConfig.chainId, networkConfig.apiUrl]);
772
784
  const refreshAccessToken = react.useCallback(async () => {
773
785
  const data = refreshDataRef.current;
774
786
  if (!data || refreshInFlight.current) return;
@@ -840,6 +852,15 @@ function useSiweAuth() {
840
852
  if (!ctx) throw new Error("useSiweAuth must be used within SiweAuthProvider");
841
853
  return ctx;
842
854
  }
855
+
856
+ // src/sdk/moonpay-currency-codes.ts
857
+ var MOONPAY_CURRENCY_CODE_BY_TOKEN_ID = {
858
+ // Sandbox MPT test token (Ethereum Sepolia).
859
+ "0xbd3a41ffd21be1cfcdca7a4e7755842a5b78c9443fb7ea008e6a7314f0caea87": "usdc"
860
+ };
861
+ function resolveMoonpayCurrencyCode(tokenId) {
862
+ return MOONPAY_CURRENCY_CODE_BY_TOKEN_ID[tokenId.toLowerCase()];
863
+ }
843
864
  var PrivanaContext = react.createContext(null);
844
865
  function readStoredHostedAuthSession(storage, hostedAuthStorageKey, now = Date.now()) {
845
866
  const raw = storage.getItem(hostedAuthStorageKey);
@@ -880,6 +901,8 @@ function PrivanaProvider({
880
901
  chains,
881
902
  pollingInterval = 1e4,
882
903
  serviceAddress,
904
+ serviceName,
905
+ serviceIcon,
883
906
  hostedAuth,
884
907
  siweAuth
885
908
  }) {
@@ -931,7 +954,8 @@ function PrivanaProvider({
931
954
  decimals: t.decimals,
932
955
  contract: t.token_address ?? viem.zeroAddress,
933
956
  name: t.name,
934
- chainId: t.chain_id
957
+ chainId: t.chain_id,
958
+ moonpayCurrencyCode: resolveMoonpayCurrencyCode(t.token_id)
935
959
  }))
936
960
  );
937
961
  setTokensStatus("ready");
@@ -1098,6 +1122,8 @@ function PrivanaProvider({
1098
1122
  tokensError,
1099
1123
  pollingInterval,
1100
1124
  serviceAddress,
1125
+ serviceName,
1126
+ serviceIcon,
1101
1127
  hostedAuthConfig,
1102
1128
  hostedAuthSession,
1103
1129
  setHostedAuthSession,
@@ -1115,6 +1141,8 @@ function PrivanaProvider({
1115
1141
  tokensError,
1116
1142
  pollingInterval,
1117
1143
  serviceAddress,
1144
+ serviceName,
1145
+ serviceIcon,
1118
1146
  hostedAuthConfig,
1119
1147
  hostedAuthSession,
1120
1148
  setHostedAuthSession,
@@ -1127,8 +1155,7 @@ function PrivanaProvider({
1127
1155
  {
1128
1156
  client,
1129
1157
  networkConfig,
1130
- autoLogin: siweAuth.autoLogin,
1131
- statement: siweAuth.statement,
1158
+ autoLogin: typeof siweAuth === "object" ? siweAuth.autoLogin : void 0,
1132
1159
  children
1133
1160
  }
1134
1161
  ) : children });
@@ -1146,7 +1173,6 @@ function useSafePrivanaContext() {
1146
1173
  var INITIAL_AUTH_BACKOFF_MS = 5e3;
1147
1174
  var MAX_AUTH_BACKOFF_MS = 6e4;
1148
1175
  var DEFAULT_SIWE_AUTH_VALIDITY_MS = 24 * 60 * 60 * 1e3;
1149
- var PRIVATE_READ_STATEMENT = "Sign in to Privana to access private account data.";
1150
1176
  var privateReadFailureCache = /* @__PURE__ */ new Map();
1151
1177
  var privateReadInflight = /* @__PURE__ */ new Map();
1152
1178
  async function executeHostedAuthPrivateReadRequest({
@@ -1263,12 +1289,12 @@ function usePrivateReadRequest() {
1263
1289
  const uri = typeof window !== "undefined" && window.location.origin ? window.location.origin : apiUrl;
1264
1290
  const message = siwe.createSiweMessage({
1265
1291
  address: walletAddress,
1266
- chainId: walletClient.chain?.id ?? networkConfig.chainId,
1292
+ chainId: networkConfig.chainId,
1267
1293
  domain,
1268
1294
  expirationTime,
1269
1295
  issuedAt,
1270
1296
  nonce: nonceResponse.nonce,
1271
- statement: PRIVATE_READ_STATEMENT,
1297
+ statement: buildSiweStatement(networkConfig.chainId),
1272
1298
  uri,
1273
1299
  version: "1"
1274
1300
  });
@@ -1659,7 +1685,7 @@ function Skeleton({ className, ...props }) {
1659
1685
  "div",
1660
1686
  {
1661
1687
  "data-slot": "skeleton",
1662
- className: cn("bg-accent animate-pulse rounded-md", className),
1688
+ className: cn("bg-secondary animate-pulse rounded-md", className),
1663
1689
  ...props
1664
1690
  }
1665
1691
  );
@@ -1676,7 +1702,10 @@ function getAction(client, actionFn, name) {
1676
1702
  return (params) => actionFn(client, params);
1677
1703
  }
1678
1704
 
1679
- // ../../node_modules/@wagmi/core/dist/esm/actions/getTransactionReceipt.js
1705
+ // ../../node_modules/@wagmi/core/dist/esm/actions/getChainId.js
1706
+ function getChainId2(config) {
1707
+ return config.state.chainId;
1708
+ }
1680
1709
  async function getTransactionReceipt(config, parameters) {
1681
1710
  const { chainId, ...rest } = parameters;
1682
1711
  const client = config.getClient({ chainId });
@@ -1710,14 +1739,1105 @@ async function waitForTransactionReceipt(config, parameters) {
1710
1739
  chainId: client.chain.id
1711
1740
  };
1712
1741
  }
1713
-
1714
- // ../../node_modules/@wagmi/core/dist/esm/actions/getChainId.js
1715
- function getChainId2(config) {
1716
- return config.state.chainId;
1742
+ var DEFAULT_DELIVERY_TIMEOUT_MS = 12e4;
1743
+ var DEFAULT_VERIFICATION_TIMEOUT_MS = 10 * 6e4;
1744
+ var DEFAULT_FINALITY_RETRY_INTERVAL_MS = 15e3;
1745
+ var ERC20_TRANSFER_EVENT = viem.parseAbiItem(
1746
+ "event Transfer(address indexed from, address indexed to, uint256 value)"
1747
+ );
1748
+ function useFiatOnRamp(options) {
1749
+ const { tokenId, onCredited, onError, onDebugEvent } = options;
1750
+ const deliveryTimeout = options.deliveryTimeout ?? DEFAULT_DELIVERY_TIMEOUT_MS;
1751
+ const deliveryPollInterval = options.deliveryPollInterval ?? 3e3;
1752
+ const verificationTimeout = options.verificationTimeout ?? DEFAULT_VERIFICATION_TIMEOUT_MS;
1753
+ const finalityRetryInterval = options.finalityRetryInterval ?? DEFAULT_FINALITY_RETRY_INTERVAL_MS;
1754
+ const { client, enabledTokens } = usePrivanaContext();
1755
+ const { executePrivateRead, privateReadReady } = usePrivateReadRequest();
1756
+ const wagmiConfig = wagmi.useConfig();
1757
+ const selectedToken = enabledTokens.find((t) => t.id.toLowerCase() === tokenId.toLowerCase());
1758
+ const [status, setStatus] = react.useState("idle");
1759
+ const [pending, setPending] = react.useState([]);
1760
+ const [error, setError] = react.useState(null);
1761
+ const [depositAddress, setDepositAddress] = react.useState();
1762
+ const [minDepositBaseUnits, setMinDepositBaseUnits] = react.useState();
1763
+ const [activeIntentId, setActiveIntentId] = react.useState(null);
1764
+ const [activeVerificationId, setActiveVerificationId] = react.useState(null);
1765
+ const [finalityProgress, setFinalityProgress] = react.useState({});
1766
+ const onCreditedRef = react.useRef(onCredited);
1767
+ const onErrorRef = react.useRef(onError);
1768
+ const onDebugEventRef = react.useRef(onDebugEvent);
1769
+ const statusRef = react.useRef(status);
1770
+ const activeIntentIdRef = react.useRef(null);
1771
+ const activeVerificationRecordRef = react.useRef(null);
1772
+ const activeVerificationKeyRef = react.useRef(null);
1773
+ const triggeredVerificationKeysRef = react.useRef(/* @__PURE__ */ new Set());
1774
+ const closeReconcilePromiseRef = react.useRef(null);
1775
+ const purchaseInitiatedRef = react.useRef(false);
1776
+ react.useEffect(() => {
1777
+ onCreditedRef.current = onCredited;
1778
+ onErrorRef.current = onError;
1779
+ onDebugEventRef.current = onDebugEvent;
1780
+ }, [onCredited, onError, onDebugEvent]);
1781
+ react.useEffect(() => {
1782
+ statusRef.current = status;
1783
+ }, [status]);
1784
+ react.useEffect(() => {
1785
+ activeIntentIdRef.current = activeIntentId;
1786
+ }, [activeIntentId]);
1787
+ react.useEffect(() => {
1788
+ activeIntentIdRef.current = null;
1789
+ setActiveIntentId(null);
1790
+ }, [tokenId]);
1791
+ const emitDebug = react.useCallback(
1792
+ (event, payload) => {
1793
+ onDebugEventRef.current?.({
1794
+ at: (/* @__PURE__ */ new Date()).toISOString(),
1795
+ event,
1796
+ status: statusRef.current,
1797
+ tokenId,
1798
+ payload
1799
+ });
1800
+ },
1801
+ [tokenId]
1802
+ );
1803
+ react.useEffect(() => {
1804
+ emitDebug("private-read-state", { privateReadReady });
1805
+ }, [emitDebug, privateReadReady]);
1806
+ react.useEffect(() => {
1807
+ if (!privateReadReady) {
1808
+ emitDebug("deposit-address:skip", { reason: "private-read-not-ready" });
1809
+ setDepositAddress(void 0);
1810
+ setMinDepositBaseUnits(void 0);
1811
+ return;
1812
+ }
1813
+ let cancelled = false;
1814
+ void (async () => {
1815
+ try {
1816
+ emitDebug("deposit-address:request");
1817
+ const resp = await executePrivateRead(() => client.getDepositAddress());
1818
+ if (cancelled) return;
1819
+ setDepositAddress(resp.deposit_address);
1820
+ const token = enabledTokens.find((t) => t.id.toLowerCase() === tokenId.toLowerCase());
1821
+ const mins = token ? resp.min_deposit?.[String(token.chainId)] : void 0;
1822
+ if (mins?.erc20) setMinDepositBaseUnits(BigInt(mins.erc20));
1823
+ emitDebug("deposit-address:success", {
1824
+ depositAddress: resp.deposit_address,
1825
+ selectedToken: token ? summariseToken(token) : null,
1826
+ minDepositBaseUnits: mins?.erc20 ?? null
1827
+ });
1828
+ } catch (err) {
1829
+ if (!cancelled) {
1830
+ emitDebug("deposit-address:error", errorPayload(err));
1831
+ console.warn("Failed to fetch Privana deposit address:", err);
1832
+ }
1833
+ }
1834
+ })();
1835
+ return () => {
1836
+ cancelled = true;
1837
+ };
1838
+ }, [client, emitDebug, enabledTokens, executePrivateRead, privateReadReady, tokenId]);
1839
+ const refreshPending = react.useCallback(async () => {
1840
+ if (!privateReadReady) {
1841
+ emitDebug("pending:skip", { reason: "private-read-not-ready" });
1842
+ setPending([]);
1843
+ return;
1844
+ }
1845
+ try {
1846
+ emitDebug("pending:request");
1847
+ const { pending: rows } = await executePrivateRead(() => client.getPendingOnRamps());
1848
+ setPending(rows);
1849
+ emitDebug("pending:success", {
1850
+ count: rows.length,
1851
+ rows: rows.map(summariseOnRampRecord)
1852
+ });
1853
+ } catch (err) {
1854
+ emitDebug("pending:error", errorPayload(err));
1855
+ console.warn("Failed to load pending on-ramps:", err);
1856
+ }
1857
+ }, [client, emitDebug, executePrivateRead, privateReadReady]);
1858
+ react.useEffect(() => {
1859
+ refreshPending();
1860
+ }, [refreshPending]);
1861
+ const clearActiveVerification = react.useCallback(() => {
1862
+ const key = activeVerificationKeyRef.current;
1863
+ if (key) triggeredVerificationKeysRef.current.delete(key);
1864
+ activeVerificationKeyRef.current = null;
1865
+ activeVerificationRecordRef.current = null;
1866
+ setActiveVerificationId(null);
1867
+ }, []);
1868
+ const { verify } = useDepositVerification({
1869
+ pollTimeout: verificationTimeout,
1870
+ pollInterval: options.verificationPollInterval,
1871
+ finalityRetryInterval,
1872
+ onCheckRetry: (message) => {
1873
+ const record = activeVerificationRecordRef.current;
1874
+ if (!record) return;
1875
+ emitDebug("verification:check-retry", {
1876
+ message,
1877
+ record: summariseOnRampRecord(record)
1878
+ });
1879
+ setFinalityProgress((prev) => ({ ...prev, [record.transaction_id]: message }));
1880
+ },
1881
+ onCredited: (depositTxHash) => {
1882
+ const record = activeVerificationRecordRef.current;
1883
+ emitDebug("verification:credited", {
1884
+ depositTxHash,
1885
+ record: record ? summariseOnRampRecord(record) : null
1886
+ });
1887
+ if (record && activeIntentIdRef.current === record.transaction_id) {
1888
+ setStatus("credited");
1889
+ }
1890
+ if (record) {
1891
+ setFinalityProgress((prev) => {
1892
+ if (!(record.transaction_id in prev)) return prev;
1893
+ const next = { ...prev };
1894
+ delete next[record.transaction_id];
1895
+ return next;
1896
+ });
1897
+ }
1898
+ void (async () => {
1899
+ try {
1900
+ if (record && depositTxHash.startsWith("0x")) {
1901
+ emitDebug("onramp:mark-deposit-triggered-request", {
1902
+ transactionId: record.transaction_id,
1903
+ depositTxHash
1904
+ });
1905
+ const updated = await executePrivateRead(
1906
+ () => client.updateOnRamp(record.transaction_id, {
1907
+ deposit_tx_hash: depositTxHash
1908
+ })
1909
+ );
1910
+ emitDebug("onramp:mark-deposit-triggered-success", {
1911
+ record: summariseOnRampRecord(updated)
1912
+ });
1913
+ }
1914
+ } catch (err) {
1915
+ emitDebug("onramp:mark-deposit-triggered-error", errorPayload(err));
1916
+ console.warn("Failed to mark on-ramp row complete:", err);
1917
+ } finally {
1918
+ await refreshPending();
1919
+ clearActiveVerification();
1920
+ if (record && activeIntentIdRef.current === record.transaction_id) {
1921
+ activeIntentIdRef.current = null;
1922
+ setActiveIntentId(null);
1923
+ }
1924
+ }
1925
+ })();
1926
+ onCreditedRef.current?.(depositTxHash);
1927
+ },
1928
+ onCheckTimeout: (depositTxHash) => {
1929
+ const record = activeVerificationRecordRef.current;
1930
+ const err = new Error(
1931
+ "Privana verification is still pending. Retry from the pending on-ramp list if it does not complete."
1932
+ );
1933
+ emitDebug("verification:timeout", { depositTxHash, message: err.message });
1934
+ clearActiveVerification();
1935
+ if (!record || activeIntentIdRef.current === record.transaction_id) {
1936
+ setStatus("failed");
1937
+ setError(err);
1938
+ }
1939
+ void refreshPending();
1940
+ onErrorRef.current?.(err);
1941
+ },
1942
+ onError: (err) => {
1943
+ const record = activeVerificationRecordRef.current;
1944
+ emitDebug("verification:error", errorPayload(err));
1945
+ clearActiveVerification();
1946
+ if (!record || activeIntentIdRef.current === record.transaction_id) {
1947
+ setStatus("failed");
1948
+ setError(err);
1949
+ }
1950
+ onErrorRef.current?.(err);
1951
+ }
1952
+ });
1953
+ const prepareOnRampIntent = react.useCallback(
1954
+ async ({
1955
+ currencyCode,
1956
+ baseCurrencyCode,
1957
+ baseCurrencyAmount
1958
+ }) => {
1959
+ try {
1960
+ setError(null);
1961
+ purchaseInitiatedRef.current = false;
1962
+ const token = enabledTokens.find((t) => t.id.toLowerCase() === tokenId.toLowerCase());
1963
+ if (!token) throw new Error(`Unknown token: ${tokenId}`);
1964
+ if (!depositAddress) throw new Error("Privana deposit address is not ready");
1965
+ emitDebug("intent:create-request", {
1966
+ tokenId,
1967
+ chainId: token.chainId,
1968
+ currencyCode,
1969
+ baseCurrencyCode: baseCurrencyCode ?? null,
1970
+ baseCurrencyAmount: baseCurrencyAmount ?? null,
1971
+ depositAddress
1972
+ });
1973
+ const record = await executePrivateRead(
1974
+ () => client.createOnRampIntent({
1975
+ wallet_address: depositAddress,
1976
+ token_id: tokenId,
1977
+ chain_id: token.chainId,
1978
+ moonpay_currency_code: currencyCode,
1979
+ base_currency_code: baseCurrencyCode,
1980
+ base_currency_amount: baseCurrencyAmount
1981
+ })
1982
+ );
1983
+ activeIntentIdRef.current = record.transaction_id;
1984
+ setActiveIntentId(record.transaction_id);
1985
+ emitDebug("intent:create-success", {
1986
+ record: summariseOnRampRecord(record)
1987
+ });
1988
+ return record;
1989
+ } catch (err) {
1990
+ const e = err instanceof Error ? err : new Error("Failed to create on-ramp intent");
1991
+ setStatus("failed");
1992
+ setError(e);
1993
+ emitDebug("intent:create-error", errorPayload(e));
1994
+ onErrorRef.current?.(e);
1995
+ throw e;
1996
+ }
1997
+ },
1998
+ [client, depositAddress, emitDebug, enabledTokens, executePrivateRead, tokenId]
1999
+ );
2000
+ const registerOnRampTokenMapping = react.useCallback(
2001
+ async (moonpayTransactionId) => {
2002
+ const token = enabledTokens.find((t) => t.id.toLowerCase() === tokenId.toLowerCase());
2003
+ if (!token) {
2004
+ emitDebug("register-token-mapping:skip", {
2005
+ moonpayTransactionId,
2006
+ reason: "selected-token-not-found"
2007
+ });
2008
+ return;
2009
+ }
2010
+ const transactionId = activeIntentIdRef.current ?? moonpayTransactionId;
2011
+ try {
2012
+ emitDebug("register-token-mapping:request", {
2013
+ transactionId,
2014
+ moonpayTransactionId,
2015
+ tokenId,
2016
+ chainId: token.chainId
2017
+ });
2018
+ const record = await executePrivateRead(
2019
+ () => client.updateOnRamp(transactionId, {
2020
+ token_id: tokenId,
2021
+ chain_id: token.chainId,
2022
+ moonpay_transaction_id: transactionId === moonpayTransactionId ? void 0 : moonpayTransactionId
2023
+ })
2024
+ );
2025
+ emitDebug("register-token-mapping:success", {
2026
+ transactionId,
2027
+ moonpayTransactionId,
2028
+ record: summariseOnRampRecord(record)
2029
+ });
2030
+ } catch (err) {
2031
+ emitDebug("register-token-mapping:error", {
2032
+ transactionId,
2033
+ moonpayTransactionId,
2034
+ ...errorPayload(err)
2035
+ });
2036
+ console.warn("Failed to register on-ramp token mapping:", err);
2037
+ }
2038
+ },
2039
+ [client, emitDebug, enabledTokens, executePrivateRead, tokenId]
2040
+ );
2041
+ const handleTransactionCreated = react.useCallback(
2042
+ async (props) => {
2043
+ emitDebug("moonpay:onTransactionCreated", summariseMoonPayEventProps(props));
2044
+ purchaseInitiatedRef.current = true;
2045
+ await registerOnRampTokenMapping(props.id);
2046
+ },
2047
+ [emitDebug, registerOnRampTokenMapping]
2048
+ );
2049
+ const signUrl = react.useCallback(
2050
+ async (url) => {
2051
+ setError(null);
2052
+ try {
2053
+ emitDebug("moonpay:onUrlSignatureRequested", summariseMoonPayUrl(url));
2054
+ const { signature } = await executePrivateRead(() => client.signOnRampUrl({ url }));
2055
+ setStatus("awaiting-purchase");
2056
+ emitDebug("sign-url:success", {
2057
+ signatureLength: signature.length
2058
+ });
2059
+ return signature;
2060
+ } catch (err) {
2061
+ const e = err instanceof Error ? err : new Error("Failed to sign on-ramp URL");
2062
+ setStatus("failed");
2063
+ setError(e);
2064
+ emitDebug("sign-url:error", errorPayload(e));
2065
+ onErrorRef.current?.(e);
2066
+ throw err;
2067
+ }
2068
+ },
2069
+ [client, emitDebug, executePrivateRead]
2070
+ );
2071
+ const waitForOnChainHash = react.useCallback(
2072
+ async (transactionId) => {
2073
+ const startTime = Date.now();
2074
+ emitDebug("delivery-poll:start", {
2075
+ transactionId,
2076
+ deliveryTimeout,
2077
+ deliveryPollInterval
2078
+ });
2079
+ while (Date.now() - startTime < deliveryTimeout) {
2080
+ try {
2081
+ const { pending: rows } = await executePrivateRead(() => client.getPendingOnRamps());
2082
+ setPending(rows);
2083
+ const record = rows.find((r) => matchesOnRampTransaction(r, transactionId));
2084
+ emitDebug("delivery-poll:tick", {
2085
+ transactionId,
2086
+ count: rows.length,
2087
+ matchingRecord: record ? summariseOnRampRecord(record) : null
2088
+ });
2089
+ if (record?.on_chain_tx_hash && record.quote_currency_amount) {
2090
+ emitDebug("delivery-poll:success", {
2091
+ transactionId,
2092
+ record: summariseOnRampRecord(record)
2093
+ });
2094
+ return record;
2095
+ }
2096
+ } catch (err) {
2097
+ emitDebug("delivery-poll:error", {
2098
+ transactionId,
2099
+ ...errorPayload(err)
2100
+ });
2101
+ console.warn("Polling pending on-ramps failed:", err);
2102
+ }
2103
+ await new Promise((r) => setTimeout(r, deliveryPollInterval));
2104
+ }
2105
+ emitDebug("delivery-poll:timeout", { transactionId });
2106
+ return null;
2107
+ },
2108
+ [client, deliveryPollInterval, deliveryTimeout, emitDebug, executePrivateRead]
2109
+ );
2110
+ const triggerVerification = react.useCallback(
2111
+ async (record) => {
2112
+ const verificationKey = getOnRampVerificationKey(record);
2113
+ if (triggeredVerificationKeysRef.current.has(verificationKey)) {
2114
+ emitDebug("verification:skip-duplicate", {
2115
+ verificationKey,
2116
+ record: summariseOnRampRecord(record)
2117
+ });
2118
+ return;
2119
+ }
2120
+ triggeredVerificationKeysRef.current.add(verificationKey);
2121
+ activeVerificationKeyRef.current = verificationKey;
2122
+ activeVerificationRecordRef.current = record;
2123
+ setActiveVerificationId(record.transaction_id);
2124
+ setFinalityProgress((prev) => {
2125
+ if (!(record.transaction_id in prev)) return prev;
2126
+ const next = { ...prev };
2127
+ delete next[record.transaction_id];
2128
+ return next;
2129
+ });
2130
+ emitDebug("verification:start", {
2131
+ verificationKey,
2132
+ record: summariseOnRampRecord(record)
2133
+ });
2134
+ try {
2135
+ if (!record.on_chain_tx_hash || !record.quote_currency_amount) {
2136
+ throw new Error("On-ramp record missing on-chain tx hash or delivered amount");
2137
+ }
2138
+ if (record.chain_id === void 0 || !record.wallet_address) {
2139
+ throw new Error("On-ramp record missing chain id or wallet address");
2140
+ }
2141
+ const recordTokenId = record.token_id;
2142
+ if (!recordTokenId) {
2143
+ throw new Error("On-ramp record missing token id");
2144
+ }
2145
+ const token = enabledTokens.find((t) => t.id.toLowerCase() === recordTokenId.toLowerCase());
2146
+ if (!token) throw new Error(`Unknown token: ${recordTokenId}`);
2147
+ if (token.chainId !== record.chain_id) {
2148
+ throw new Error(
2149
+ `Token ${recordTokenId} is on chain ${token.chainId} but record is on chain ${record.chain_id}`
2150
+ );
2151
+ }
2152
+ const amount = await resolveDeliveredAmount({
2153
+ onChainTxHash: record.on_chain_tx_hash,
2154
+ chainId: record.chain_id,
2155
+ walletAddress: record.wallet_address,
2156
+ token,
2157
+ fallbackAmount: record.quote_currency_amount,
2158
+ wagmiConfig,
2159
+ emitDebug
2160
+ });
2161
+ if (minDepositBaseUnits !== void 0 && amount < minDepositBaseUnits) {
2162
+ emitDebug("verification:below-minimum", {
2163
+ quoteCurrencyAmount: record.quote_currency_amount,
2164
+ minDepositBaseUnits: String(minDepositBaseUnits)
2165
+ });
2166
+ throw new Error(
2167
+ `Delivered amount (${record.quote_currency_amount}) is below the minimum deposit.`
2168
+ );
2169
+ }
2170
+ if (activeIntentIdRef.current === record.transaction_id) {
2171
+ setStatus("verifying");
2172
+ }
2173
+ emitDebug("verification:check-deposit-request", {
2174
+ hash: record.on_chain_tx_hash,
2175
+ chainId: record.chain_id,
2176
+ amount: amount.toString()
2177
+ });
2178
+ await verify({
2179
+ hash: record.on_chain_tx_hash,
2180
+ chainId: record.chain_id,
2181
+ amount
2182
+ });
2183
+ } catch (err) {
2184
+ triggeredVerificationKeysRef.current.delete(verificationKey);
2185
+ if (activeVerificationKeyRef.current === verificationKey) {
2186
+ activeVerificationKeyRef.current = null;
2187
+ activeVerificationRecordRef.current = null;
2188
+ setActiveVerificationId(null);
2189
+ }
2190
+ throw err;
2191
+ }
2192
+ },
2193
+ [emitDebug, enabledTokens, minDepositBaseUnits, verify, wagmiConfig]
2194
+ );
2195
+ const handleTransactionCompleted = react.useCallback(
2196
+ async (props) => {
2197
+ emitDebug("moonpay:onTransactionCompleted", summariseMoonPayEventProps(props));
2198
+ try {
2199
+ setStatus("awaiting-delivery");
2200
+ await registerOnRampTokenMapping(props.id);
2201
+ const transactionId = activeIntentIdRef.current ?? props.id;
2202
+ const record = await waitForOnChainHash(transactionId);
2203
+ if (!record) {
2204
+ const err = new Error(
2205
+ "Backend has not yet confirmed delivery. You can finish from the pending list."
2206
+ );
2207
+ emitDebug("moonpay:completed-without-backend-row", {
2208
+ transactionId,
2209
+ moonpayTransactionId: props.id,
2210
+ message: err.message
2211
+ });
2212
+ setStatus("failed");
2213
+ setError(err);
2214
+ onErrorRef.current?.(err);
2215
+ return;
2216
+ }
2217
+ await triggerVerification(record);
2218
+ } catch (err) {
2219
+ const e = err instanceof Error ? err : new Error("Verification failed");
2220
+ emitDebug("moonpay:onTransactionCompleted-error", errorPayload(e));
2221
+ setStatus("failed");
2222
+ setError(e);
2223
+ onErrorRef.current?.(e);
2224
+ }
2225
+ },
2226
+ [emitDebug, registerOnRampTokenMapping, triggerVerification, waitForOnChainHash]
2227
+ );
2228
+ const handleWidgetClosed = react.useCallback(async () => {
2229
+ if (closeReconcilePromiseRef.current) return closeReconcilePromiseRef.current;
2230
+ closeReconcilePromiseRef.current = (async () => {
2231
+ const previousStatus = statusRef.current;
2232
+ const transactionId = activeIntentIdRef.current;
2233
+ emitDebug("moonpay:widget-closed-reconcile", {
2234
+ previousStatus,
2235
+ transactionId
2236
+ });
2237
+ if (!transactionId) {
2238
+ await refreshPending();
2239
+ return;
2240
+ }
2241
+ if (!purchaseInitiatedRef.current) {
2242
+ emitDebug("moonpay:widget-closed-without-purchase", {
2243
+ previousStatus,
2244
+ transactionId
2245
+ });
2246
+ await refreshPending();
2247
+ if (previousStatus === "awaiting-purchase" || previousStatus === "awaiting-delivery") {
2248
+ setStatus("idle");
2249
+ }
2250
+ return;
2251
+ }
2252
+ try {
2253
+ setStatus("awaiting-delivery");
2254
+ const record = await waitForOnChainHash(transactionId);
2255
+ if (record) {
2256
+ await triggerVerification(record);
2257
+ return;
2258
+ }
2259
+ await refreshPending();
2260
+ if (previousStatus === "awaiting-purchase" || previousStatus === "awaiting-delivery") {
2261
+ setStatus("idle");
2262
+ }
2263
+ } catch (err) {
2264
+ const e = err instanceof Error ? err : new Error("On-ramp reconciliation failed");
2265
+ emitDebug("moonpay:widget-closed-reconcile-error", errorPayload(e));
2266
+ setStatus("failed");
2267
+ setError(e);
2268
+ onErrorRef.current?.(e);
2269
+ }
2270
+ })();
2271
+ try {
2272
+ await closeReconcilePromiseRef.current;
2273
+ } finally {
2274
+ closeReconcilePromiseRef.current = null;
2275
+ }
2276
+ }, [emitDebug, refreshPending, triggerVerification, waitForOnChainHash]);
2277
+ const finishPendingVerification = react.useCallback(
2278
+ async (record) => {
2279
+ try {
2280
+ emitDebug("pending:finish-verification", {
2281
+ record: summariseOnRampRecord(record)
2282
+ });
2283
+ await triggerVerification(record);
2284
+ } catch (err) {
2285
+ const e = err instanceof Error ? err : new Error("Verification failed");
2286
+ emitDebug("pending:finish-verification-error", errorPayload(e));
2287
+ setStatus("failed");
2288
+ setError(e);
2289
+ onErrorRef.current?.(e);
2290
+ throw e;
2291
+ }
2292
+ },
2293
+ [emitDebug, triggerVerification]
2294
+ );
2295
+ const triggerVerificationRef = react.useRef(triggerVerification);
2296
+ react.useEffect(() => {
2297
+ triggerVerificationRef.current = triggerVerification;
2298
+ }, [triggerVerification]);
2299
+ react.useEffect(() => {
2300
+ let cancelled = false;
2301
+ void (async () => {
2302
+ for (const record of pending) {
2303
+ if (cancelled) break;
2304
+ if (!record.on_chain_tx_hash || !record.quote_currency_amount) continue;
2305
+ const key = getOnRampVerificationKey(record);
2306
+ if (triggeredVerificationKeysRef.current.has(key)) continue;
2307
+ try {
2308
+ await triggerVerificationRef.current(record);
2309
+ } catch {
2310
+ }
2311
+ }
2312
+ })();
2313
+ return () => {
2314
+ cancelled = true;
2315
+ };
2316
+ }, [pending]);
2317
+ return {
2318
+ status,
2319
+ activeIntentId,
2320
+ pending,
2321
+ activeVerificationId,
2322
+ error,
2323
+ finalityProgress,
2324
+ depositAddress,
2325
+ minDepositBaseUnits,
2326
+ selectedToken,
2327
+ prepareOnRampIntent,
2328
+ signUrl,
2329
+ handleTransactionCreated,
2330
+ handleTransactionCompleted,
2331
+ finishPendingVerification,
2332
+ handleWidgetClosed,
2333
+ refreshPending
2334
+ };
2335
+ }
2336
+ function summariseToken(token) {
2337
+ return {
2338
+ tokenId: token.id,
2339
+ chainId: token.chainId,
2340
+ symbol: token.symbol ?? null,
2341
+ decimals: token.decimals ?? null
2342
+ };
2343
+ }
2344
+ function summariseOnRampRecord(record) {
2345
+ return {
2346
+ transaction_id: record.transaction_id,
2347
+ external_transaction_id: record.external_transaction_id ?? null,
2348
+ moonpay_transaction_id: record.moonpay_transaction_id ?? null,
2349
+ status: record.status,
2350
+ wallet_address: record.wallet_address,
2351
+ token_id: record.token_id,
2352
+ chain_id: record.chain_id,
2353
+ moonpay_currency_code: record.moonpay_currency_code ?? null,
2354
+ quote_currency_amount: record.quote_currency_amount ?? null,
2355
+ on_chain_tx_hash: record.on_chain_tx_hash ?? null,
2356
+ deposit_tx_hash: record.deposit_tx_hash ?? null,
2357
+ deposit_triggered_at: record.deposit_triggered_at ?? null,
2358
+ credited_at: record.credited_at ?? null
2359
+ };
2360
+ }
2361
+ async function resolveDeliveredAmount({
2362
+ onChainTxHash,
2363
+ chainId,
2364
+ walletAddress,
2365
+ token,
2366
+ fallbackAmount,
2367
+ wagmiConfig,
2368
+ emitDebug
2369
+ }) {
2370
+ if (token.contract === viem.zeroAddress) {
2371
+ return viem.parseUnits(fallbackAmount, token.decimals);
2372
+ }
2373
+ let receiptError;
2374
+ try {
2375
+ const receipt = await waitForTransactionReceipt(wagmiConfig, {
2376
+ hash: onChainTxHash,
2377
+ chainId,
2378
+ timeout: 6e4,
2379
+ pollingInterval: 4e3
2380
+ });
2381
+ let delivered = 0n;
2382
+ for (const log of receipt.logs) {
2383
+ if (log.address.toLowerCase() !== token.contract.toLowerCase()) continue;
2384
+ try {
2385
+ const decoded = viem.decodeEventLog({
2386
+ abi: [ERC20_TRANSFER_EVENT],
2387
+ data: log.data,
2388
+ topics: log.topics
2389
+ });
2390
+ if (decoded.eventName !== "Transfer") continue;
2391
+ const to = decoded.args.to.toLowerCase();
2392
+ if (to !== walletAddress.toLowerCase()) continue;
2393
+ delivered += decoded.args.value;
2394
+ } catch {
2395
+ }
2396
+ }
2397
+ if (delivered > 0n) {
2398
+ emitDebug("verification:amount-from-receipt", {
2399
+ amount: delivered.toString(),
2400
+ tokenAddress: token.contract,
2401
+ walletAddress,
2402
+ moonpayQuoteCurrencyAmount: fallbackAmount
2403
+ });
2404
+ return delivered;
2405
+ }
2406
+ emitDebug("verification:amount-from-receipt-missing", {
2407
+ tokenAddress: token.contract,
2408
+ walletAddress,
2409
+ moonpayQuoteCurrencyAmount: fallbackAmount
2410
+ });
2411
+ } catch (err) {
2412
+ emitDebug("verification:amount-from-receipt-error", errorPayload(err));
2413
+ receiptError = err;
2414
+ }
2415
+ const errorDetail = receiptError instanceof Error ? receiptError.message : receiptError === void 0 ? `no ${token.symbol} Transfer to ${walletAddress} found` : String(receiptError);
2416
+ throw new Error(
2417
+ `Unable to derive delivered ${token.symbol} amount from ${onChainTxHash}: ${errorDetail}`
2418
+ );
2419
+ }
2420
+ function matchesOnRampTransaction(record, transactionId) {
2421
+ return record.transaction_id === transactionId || record.external_transaction_id === transactionId || record.moonpay_transaction_id === transactionId;
2422
+ }
2423
+ function getOnRampVerificationKey(record) {
2424
+ return record.on_chain_tx_hash ?? record.transaction_id;
2425
+ }
2426
+ function summariseMoonPayEventProps(props) {
2427
+ return {
2428
+ id: props.id,
2429
+ externalTransactionId: props.externalTransactionId,
2430
+ status: props.status,
2431
+ walletAddress: props.walletAddress,
2432
+ walletAddressTag: props.walletAddressTag,
2433
+ baseCurrencyAmount: props.baseCurrencyAmount,
2434
+ quoteCurrencyAmount: props.quoteCurrencyAmount,
2435
+ baseCurrency: props.baseCurrency,
2436
+ quoteCurrency: props.quoteCurrency,
2437
+ createdAt: props.createdAt
2438
+ };
2439
+ }
2440
+ function summariseMoonPayUrl(url) {
2441
+ try {
2442
+ const parsed = new URL(url);
2443
+ const params = parsed.searchParams;
2444
+ return {
2445
+ origin: parsed.origin,
2446
+ pathname: parsed.pathname,
2447
+ apiKeyPrefix: params.get("apiKey")?.slice(0, 8) ?? null,
2448
+ currencyCode: params.get("currencyCode"),
2449
+ baseCurrencyCode: params.get("baseCurrencyCode"),
2450
+ baseCurrencyAmount: params.get("baseCurrencyAmount"),
2451
+ walletAddress: params.get("walletAddress"),
2452
+ externalCustomerId: params.get("externalCustomerId"),
2453
+ externalTransactionId: params.get("externalTransactionId"),
2454
+ redirectURL: params.get("redirectURL"),
2455
+ signaturePresent: params.has("signature")
2456
+ };
2457
+ } catch {
2458
+ return { parseError: true, length: url.length };
2459
+ }
2460
+ }
2461
+ function errorPayload(err) {
2462
+ if (err instanceof Error) {
2463
+ return {
2464
+ name: err.name,
2465
+ message: err.message,
2466
+ stack: err.stack?.split("\n").slice(0, 4).join("\n")
2467
+ };
2468
+ }
2469
+ return { message: String(err) };
2470
+ }
2471
+ function useMoonPayBuyWidget({
2472
+ variant,
2473
+ visible,
2474
+ autoStart,
2475
+ canBuy,
2476
+ openWidget,
2477
+ refreshPending,
2478
+ theme,
2479
+ themeId,
2480
+ colorCode,
2481
+ baseCurrencyCode,
2482
+ baseCurrencyAmount,
2483
+ lockAmount,
2484
+ paymentMethod,
2485
+ currencyCode,
2486
+ depositAddress,
2487
+ externalCustomerId,
2488
+ externalTransactionId,
2489
+ onClose,
2490
+ onCloseOverlay,
2491
+ onReady,
2492
+ onUrlSignatureRequested,
2493
+ onTransactionCreated,
2494
+ onTransactionCompleted
2495
+ }) {
2496
+ const autoStartedRef = react.useRef(false);
2497
+ react.useEffect(() => {
2498
+ if (!autoStart || autoStartedRef.current || !canBuy) return;
2499
+ autoStartedRef.current = true;
2500
+ void openWidget();
2501
+ }, [autoStart, canBuy, openWidget]);
2502
+ react.useEffect(() => {
2503
+ if (variant !== "embedded" || !visible) return;
2504
+ const id = setInterval(() => void refreshPending(), 5e3);
2505
+ return () => clearInterval(id);
2506
+ }, [variant, visible, refreshPending]);
2507
+ const callbacksRef = react.useRef({
2508
+ onClose,
2509
+ onCloseOverlay,
2510
+ onReady,
2511
+ onUrlSignatureRequested,
2512
+ onTransactionCreated,
2513
+ onTransactionCompleted
2514
+ });
2515
+ react.useEffect(() => {
2516
+ callbacksRef.current = {
2517
+ onClose,
2518
+ onCloseOverlay,
2519
+ onReady,
2520
+ onUrlSignatureRequested,
2521
+ onTransactionCreated,
2522
+ onTransactionCompleted
2523
+ };
2524
+ });
2525
+ const overlayNode = react.useMemo(
2526
+ () => variant === "overlay" ? buildOverlayNode() : void 0,
2527
+ [variant]
2528
+ );
2529
+ return react.useMemo(() => {
2530
+ if (!visible || !depositAddress || !externalTransactionId) return null;
2531
+ return /* @__PURE__ */ jsxRuntime.jsx(
2532
+ moonpayReact.MoonPayBuyWidget,
2533
+ {
2534
+ variant,
2535
+ visible: true,
2536
+ theme,
2537
+ themeId,
2538
+ colorCode,
2539
+ overlayNode,
2540
+ baseCurrencyCode,
2541
+ baseCurrencyAmount,
2542
+ lockAmount: lockAmount ? "true" : void 0,
2543
+ paymentMethod,
2544
+ currencyCode,
2545
+ walletAddress: depositAddress,
2546
+ externalCustomerId,
2547
+ externalTransactionId,
2548
+ onClose: () => callbacksRef.current.onClose(),
2549
+ onCloseOverlay: () => callbacksRef.current.onCloseOverlay(),
2550
+ onReady: () => callbacksRef.current.onReady(),
2551
+ onUrlSignatureRequested: (url) => callbacksRef.current.onUrlSignatureRequested(url),
2552
+ onTransactionCreated: (props) => callbacksRef.current.onTransactionCreated(props),
2553
+ onTransactionCompleted: (props) => callbacksRef.current.onTransactionCompleted(props)
2554
+ }
2555
+ );
2556
+ }, [
2557
+ visible,
2558
+ depositAddress,
2559
+ externalTransactionId,
2560
+ variant,
2561
+ theme,
2562
+ themeId,
2563
+ colorCode,
2564
+ overlayNode,
2565
+ baseCurrencyCode,
2566
+ baseCurrencyAmount,
2567
+ lockAmount,
2568
+ paymentMethod,
2569
+ currencyCode,
2570
+ externalCustomerId
2571
+ ]);
2572
+ }
2573
+ function buildOverlayNode() {
2574
+ if (typeof document === "undefined") return void 0;
2575
+ const wrap = document.createElement("div");
2576
+ wrap.style.cssText = "display:flex;flex-direction:column;align-items:center;gap:12px";
2577
+ wrap.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" width="139" height="20.61" viewBox="0 0 139 20.61"><path d="M15.08,20.61L10.49,20.61L10.49,0.00L17.96,0.00L18.81,0.02L19.62,0.10L20.38,0.22L21.11,0.39L21.79,0.60L22.43,0.87L23.03,1.18L23.58,1.53L24.08,1.92L24.53,2.35L24.93,2.82L25.29,3.33L25.59,3.88L25.84,4.47L26.03,5.09L26.17,5.75L26.25,6.44L26.28,7.17L26.28,7.17L26.28,7.62L26.25,8.33L26.17,9.01L26.03,9.66L25.84,10.27L25.59,10.86L25.29,11.41L24.93,11.93L24.53,12.40L24.08,12.83L23.58,13.23L23.03,13.58L22.43,13.89L21.79,14.15L21.11,14.37L20.38,14.54L19.62,14.66L18.81,14.73L17.96,14.76L17.96,14.76L15.08,14.76L15.08,20.61ZM18.19,3.98L15.08,3.98L15.08,10.78L18.19,10.78L18.53,10.77L18.87,10.73L19.18,10.68L19.48,10.59L19.76,10.49L20.02,10.36L20.27,10.21L20.50,10.04L20.70,9.85L20.89,9.64L21.06,9.42L21.21,9.17L21.34,8.91L21.45,8.63L21.53,8.34L21.59,8.04L21.62,7.72L21.63,7.39L21.63,7.39L21.62,7.05L21.59,6.72L21.53,6.40L21.45,6.10L21.34,5.82L21.21,5.56L21.06,5.31L20.89,5.09L20.70,4.88L20.50,4.69L20.27,4.53L20.02,4.38L19.76,4.26L19.48,4.16L19.18,4.08L18.87,4.02L18.53,3.99L18.19,3.98L18.19,3.98ZM31.76,20.61L27.16,20.61L27.16,0.00L35.20,0.00L36.03,0.02L36.82,0.09L37.58,0.19L38.30,0.34L38.98,0.53L39.62,0.77L40.23,1.05L40.78,1.37L41.28,1.73L41.74,2.13L42.15,2.57L42.51,3.05L42.82,3.58L43.07,4.15L43.26,4.76L43.40,5.42L43.49,6.12L43.52,6.86L43.52,6.86L43.52,7.31L43.49,8.04L43.40,8.73L43.26,9.38L43.07,9.98L42.82,10.54L42.51,11.06L42.51,11.06L42.15,11.53L41.75,11.97L41.30,12.36L40.80,12.72L40.26,13.03L39.68,13.30L39.68,13.30L44.89,20.61L39.57,20.61L35.12,14.06L31.76,14.06L31.76,20.61ZM35.56,3.89L31.76,3.89L31.76,10.44L35.56,10.44L35.89,10.43L36.21,10.40L36.51,10.34L36.79,10.26L37.06,10.16L37.31,10.04L37.55,9.89L37.77,9.73L37.97,9.55L38.15,9.35L38.32,9.13L38.46,8.89L38.59,8.64L38.69,8.37L38.77,8.09L38.82,7.79L38.86,7.49L38.87,7.17L38.86,6.85L38.82,6.54L38.77,6.25L38.69,5.97L38.59,5.70L38.46,5.45L38.32,5.21L38.15,4.99L37.97,4.79L37.77,4.61L37.55,4.44L37.31,4.30L37.06,4.17L36.79,4.07L36.51,3.99L36.21,3.94L35.89,3.90L35.56,3.89L35.56,3.89ZM50.25,20.61L45.66,20.61L45.66,0.17L50.25,0.17L50.25,20.61ZM64.88,20.61L57.41,20.61L51.19,0.17L55.92,0.17L60.71,16.88L61.66,16.88L66.09,0.17L70.68,0.17L64.88,20.61ZM71.44,20.61L66.85,20.61L73.60,0.17L81.02,0.17L88.02,20.61L83.26,20.61L81.61,15.54L73.07,15.54L71.44,20.61ZM74.27,11.73L80.35,11.73L77.80,3.92L76.76,3.92L74.27,11.73ZM92.94,20.61L88.68,20.61L88.68,0.17L96.21,0.17L103.91,16.88L104.30,16.88L104.30,0.17L108.62,0.17L108.62,20.61L101.03,20.61L93.33,3.89L92.94,3.89L92.94,20.61ZM113.87,20.61L109.28,20.61L116.02,0.17L123.44,0.17L130.44,20.61L125.68,20.61L124.03,15.54L115.49,15.54L113.87,20.61ZM116.70,11.73L122.77,11.73L120.22,3.92L119.19,3.92L116.70,11.73Z" fill="#ffffff"/></svg><span>Your secure checkout is loading</span>`;
2578
+ return wrap;
2579
+ }
2580
+ function FiatOnRampForm({
2581
+ tokenId,
2582
+ currencyCode,
2583
+ baseCurrencyCode = "usd",
2584
+ defaultBaseCurrencyAmount = "100",
2585
+ tokenSymbol,
2586
+ theme,
2587
+ themeId,
2588
+ colorCode,
2589
+ variant = "overlay",
2590
+ autoStart = false,
2591
+ lockAmount,
2592
+ paymentMethod,
2593
+ onCredited,
2594
+ onError,
2595
+ onDebugEvent
2596
+ }) {
2597
+ const { address } = wagmi.useAccount();
2598
+ const [visible, setVisible] = react.useState(false);
2599
+ const [isPreparing, setIsPreparing] = react.useState(false);
2600
+ const [rowError, setRowError] = react.useState(null);
2601
+ const {
2602
+ status,
2603
+ activeIntentId,
2604
+ pending,
2605
+ activeVerificationId,
2606
+ error,
2607
+ finalityProgress,
2608
+ depositAddress,
2609
+ minDepositBaseUnits,
2610
+ selectedToken,
2611
+ prepareOnRampIntent,
2612
+ signUrl,
2613
+ handleTransactionCreated,
2614
+ handleTransactionCompleted,
2615
+ finishPendingVerification,
2616
+ handleWidgetClosed,
2617
+ refreshPending
2618
+ } = useFiatOnRamp({ tokenId, onCredited, onError, onDebugEvent });
2619
+ const decimals = selectedToken?.decimals;
2620
+ const displaySymbol = tokenSymbol ?? selectedToken?.symbol ?? currencyCode.toUpperCase();
2621
+ const emitFormDebug = react.useCallback(
2622
+ (event, payload) => {
2623
+ onDebugEvent?.({
2624
+ at: (/* @__PURE__ */ new Date()).toISOString(),
2625
+ event,
2626
+ status,
2627
+ tokenId,
2628
+ payload
2629
+ });
2630
+ },
2631
+ [onDebugEvent, status, tokenId]
2632
+ );
2633
+ 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;
2635
+ const isBusy = isPreparing || status === "awaiting-purchase";
2636
+ const isInitializing = !!address && !depositAddress;
2637
+ const isPrePurchase = status === "idle" || status === "awaiting-purchase";
2638
+ const isVerifying = status === "awaiting-delivery" || status === "verifying";
2639
+ const blockReasons = react.useMemo(
2640
+ () => [
2641
+ !address ? "wallet-not-connected" : null,
2642
+ !depositAddress ? "deposit-address-not-loaded" : null,
2643
+ isBusy ? `busy:${isPreparing ? "preparing" : status}` : null,
2644
+ visible ? "widget-open" : null,
2645
+ isBelowMin ? "below-minimum" : null
2646
+ ].filter((reason) => Boolean(reason)),
2647
+ [address, depositAddress, isBelowMin, isBusy, isPreparing, status, visible]
2648
+ );
2649
+ const canBuy = blockReasons.length === 0;
2650
+ const handleOpen = react.useCallback(async () => {
2651
+ if (!canBuy) {
2652
+ emitFormDebug("form:open-blocked", {
2653
+ reasons: blockReasons,
2654
+ currencyCode,
2655
+ tokenSymbol: displaySymbol,
2656
+ tokenDecimals: decimals ?? null,
2657
+ baseCurrencyCode,
2658
+ defaultBaseCurrencyAmount,
2659
+ depositAddress: depositAddress ?? null,
2660
+ walletAddress: address ?? null,
2661
+ status
2662
+ });
2663
+ return;
2664
+ }
2665
+ setIsPreparing(true);
2666
+ emitFormDebug("form:open-click", {
2667
+ currencyCode,
2668
+ tokenSymbol: displaySymbol,
2669
+ tokenDecimals: decimals ?? null,
2670
+ baseCurrencyCode,
2671
+ defaultBaseCurrencyAmount,
2672
+ depositAddress: depositAddress ?? null,
2673
+ walletConnected: Boolean(address)
2674
+ });
2675
+ try {
2676
+ const intent = await prepareOnRampIntent({
2677
+ currencyCode,
2678
+ baseCurrencyCode,
2679
+ baseCurrencyAmount: defaultBaseCurrencyAmount
2680
+ });
2681
+ emitFormDebug("form:intent-ready", {
2682
+ transactionId: intent.transaction_id,
2683
+ externalTransactionId: intent.external_transaction_id ?? null
2684
+ });
2685
+ setVisible(true);
2686
+ } catch (err) {
2687
+ const error2 = err instanceof Error ? err : new Error("Failed to prepare MoonPay on-ramp");
2688
+ emitFormDebug("form:intent-error", {
2689
+ name: error2.name,
2690
+ message: error2.message
2691
+ });
2692
+ } finally {
2693
+ setIsPreparing(false);
2694
+ }
2695
+ }, [
2696
+ address,
2697
+ baseCurrencyCode,
2698
+ blockReasons,
2699
+ canBuy,
2700
+ currencyCode,
2701
+ decimals,
2702
+ displaySymbol,
2703
+ defaultBaseCurrencyAmount,
2704
+ depositAddress,
2705
+ emitFormDebug,
2706
+ prepareOnRampIntent,
2707
+ status
2708
+ ]);
2709
+ const handleClose = react.useCallback(async () => {
2710
+ emitFormDebug("moonpay:onClose");
2711
+ setVisible(false);
2712
+ await handleWidgetClosed();
2713
+ }, [emitFormDebug, handleWidgetClosed]);
2714
+ const handleCloseOverlay = react.useCallback(async () => {
2715
+ emitFormDebug("moonpay:onCloseOverlay");
2716
+ setVisible(false);
2717
+ await handleWidgetClosed();
2718
+ }, [emitFormDebug, handleWidgetClosed]);
2719
+ const handleReady = react.useCallback(async () => {
2720
+ emitFormDebug("moonpay:onReady");
2721
+ }, [emitFormDebug]);
2722
+ const widgetElement = useMoonPayBuyWidget({
2723
+ variant,
2724
+ visible,
2725
+ autoStart,
2726
+ canBuy,
2727
+ openWidget: handleOpen,
2728
+ refreshPending,
2729
+ theme,
2730
+ themeId,
2731
+ colorCode,
2732
+ baseCurrencyCode,
2733
+ baseCurrencyAmount: defaultBaseCurrencyAmount,
2734
+ lockAmount,
2735
+ paymentMethod,
2736
+ currencyCode,
2737
+ depositAddress,
2738
+ externalCustomerId: address?.toLowerCase(),
2739
+ externalTransactionId: activeIntentId,
2740
+ onClose: handleClose,
2741
+ onCloseOverlay: handleCloseOverlay,
2742
+ onReady: handleReady,
2743
+ onUrlSignatureRequested: signUrl,
2744
+ onTransactionCreated: handleTransactionCreated,
2745
+ onTransactionCompleted: handleTransactionCompleted
2746
+ });
2747
+ react.useEffect(() => {
2748
+ if (variant !== "embedded" || !visible) return;
2749
+ if (isVerifying || status === "credited") {
2750
+ setVisible(false);
2751
+ void refreshPending();
2752
+ }
2753
+ }, [variant, visible, isVerifying, status, refreshPending]);
2754
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { "data-privana": true, className: "flex flex-col gap-4", children: [
2755
+ pending.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-2", children: [
2756
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-foreground text-sm font-medium", children: "Validating purchases" }),
2757
+ pending.map((record) => {
2758
+ const progress = parseFinalityProgress(finalityProgress[record.transaction_id]);
2759
+ const hasProgress = !!finalityProgress[record.transaction_id];
2760
+ const isStalled = !hasProgress && Date.now() / 1e3 - (record.updated_at ?? 0) > 60;
2761
+ const isActivelyVerifying = record.transaction_id === activeVerificationId;
2762
+ const showRetry = rowError?.id === record.transaction_id || isStalled && !isActivelyVerifying;
2763
+ return /* @__PURE__ */ jsxRuntime.jsxs(
2764
+ "div",
2765
+ {
2766
+ className: "border-border flex flex-col gap-1 rounded-md border p-2",
2767
+ children: [
2768
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between gap-2", children: [
2769
+ /* @__PURE__ */ jsxRuntime.jsxs("p", { className: "text-muted-foreground flex items-center gap-1 text-xs", children: [
2770
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Loader2, { className: "size-3 animate-spin", "aria-hidden": true }),
2771
+ progress ?? "Verifying\u2026"
2772
+ ] }),
2773
+ /* @__PURE__ */ jsxRuntime.jsxs("p", { className: "text-muted-foreground text-xs", children: [
2774
+ record.quote_currency_amount ?? "?",
2775
+ " ",
2776
+ displaySymbol
2777
+ ] })
2778
+ ] }),
2779
+ rowError?.id === record.transaction_id && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-destructive text-xs", children: rowError.message }),
2780
+ showRetry && /* @__PURE__ */ jsxRuntime.jsx(
2781
+ Button,
2782
+ {
2783
+ type: "button",
2784
+ variant: "outline",
2785
+ size: "sm",
2786
+ onClick: async () => {
2787
+ setRowError(null);
2788
+ try {
2789
+ await finishPendingVerification(record);
2790
+ } catch (err) {
2791
+ setRowError({
2792
+ id: record.transaction_id,
2793
+ message: err instanceof Error ? err.message : "Verification failed"
2794
+ });
2795
+ }
2796
+ },
2797
+ children: "Retry"
2798
+ }
2799
+ )
2800
+ ]
2801
+ },
2802
+ record.transaction_id
2803
+ );
2804
+ })
2805
+ ] }),
2806
+ status === "credited" && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col items-center gap-2 py-8 text-center", children: [
2807
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.CircleCheckIcon, { className: "text-primary size-8", "aria-hidden": true }),
2808
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-foreground text-sm font-medium", children: "Purchase credited" }),
2809
+ /* @__PURE__ */ jsxRuntime.jsxs("p", { className: "text-muted-foreground text-sm", children: [
2810
+ "Your ",
2811
+ displaySymbol,
2812
+ " deposit is now available in your balance."
2813
+ ] })
2814
+ ] }),
2815
+ 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
+ (isBusy || visible) && /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Loader2, { className: "animate-spin", "aria-hidden": true }),
2817
+ "Buy"
2818
+ ] }),
2819
+ widgetElement,
2820
+ error && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-destructive text-sm", role: "alert", children: error.message }),
2821
+ isBelowMin && minFiatGate !== void 0 && /* @__PURE__ */ jsxRuntime.jsxs("p", { className: "text-destructive text-sm", role: "alert", children: [
2822
+ "Minimum purchase is ~$",
2823
+ minFiatGate.toFixed(2),
2824
+ "."
2825
+ ] }),
2826
+ isVerifying && pending.length === 0 && /* @__PURE__ */ jsxRuntime.jsxs("p", { className: "text-muted-foreground flex items-center gap-2 text-sm", children: [
2827
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Loader2, { className: "size-4 animate-spin", "aria-hidden": true }),
2828
+ "Verifying your purchase\u2026"
2829
+ ] })
2830
+ ] });
2831
+ }
2832
+ function parseFinalityProgress(message) {
2833
+ if (!message) return null;
2834
+ const match = message.match(/(\d+\/\d+)\s+confirmations/i);
2835
+ return match ? `${match[1]} confirmations` : null;
1717
2836
  }
1718
2837
 
1719
2838
  exports.AccountingApiError = AccountingApiError;
1720
2839
  exports.Button = Button;
2840
+ exports.FiatOnRampForm = FiatOnRampForm;
1721
2841
  exports.HOSTED_AUTH_CLOCK_SKEW_MS = HOSTED_AUTH_CLOCK_SKEW_MS;
1722
2842
  exports.HostedAuthError = HostedAuthError;
1723
2843
  exports.HostedAuthRequiredError = HostedAuthRequiredError;
@@ -1733,6 +2853,7 @@ exports.Skeleton = Skeleton;
1733
2853
  exports.ValidationError = ValidationError;
1734
2854
  exports.applyRefreshResponse = applyRefreshResponse;
1735
2855
  exports.buildHostedAuthSession = buildHostedAuthSession;
2856
+ exports.buildSiweStatement = buildSiweStatement;
1736
2857
  exports.buttonVariants = buttonVariants;
1737
2858
  exports.clearHostedAuthPendingTransaction = clearHostedAuthPendingTransaction;
1738
2859
  exports.cn = cn;
@@ -1749,6 +2870,7 @@ exports.getChainById = getChainById;
1749
2870
  exports.getChainId = getChainId;
1750
2871
  exports.getChainId2 = getChainId2;
1751
2872
  exports.getExplorerAddressUrl = getExplorerAddressUrl;
2873
+ exports.getExplorerLabel = getExplorerLabel;
1752
2874
  exports.getTransactionReceipt = getTransactionReceipt;
1753
2875
  exports.isHostedAuthRefreshActive = isHostedAuthRefreshActive;
1754
2876
  exports.isHostedAuthSessionActive = isHostedAuthSessionActive;
@@ -1763,11 +2885,12 @@ exports.shortenAddress = shortenAddress;
1763
2885
  exports.stripHostedAuthCallbackParams = stripHostedAuthCallbackParams;
1764
2886
  exports.syncHostedAuthSessionToClient = syncHostedAuthSessionToClient;
1765
2887
  exports.useDepositVerification = useDepositVerification;
2888
+ exports.useFiatOnRamp = useFiatOnRamp;
1766
2889
  exports.usePrivanaContext = usePrivanaContext;
1767
2890
  exports.usePrivateReadRequest = usePrivateReadRequest;
1768
2891
  exports.useSafeAccount = useSafeAccount;
1769
2892
  exports.useSafePrivanaContext = useSafePrivanaContext;
1770
2893
  exports.useSiweAuth = useSiweAuth;
1771
2894
  exports.waitForTransactionReceipt = waitForTransactionReceipt;
1772
- //# sourceMappingURL=chunk-EEAQ2NWF.cjs.map
1773
- //# sourceMappingURL=chunk-EEAQ2NWF.cjs.map
2895
+ //# sourceMappingURL=chunk-ACLJPC75.cjs.map
2896
+ //# sourceMappingURL=chunk-ACLJPC75.cjs.map