@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.
- package/dist/{chunk-2AHJHISR.js → chunk-54FC6TO3.js} +1145 -26
- package/dist/chunk-54FC6TO3.js.map +1 -0
- package/dist/{chunk-EEAQ2NWF.cjs → chunk-ACLJPC75.cjs} +1145 -22
- package/dist/chunk-ACLJPC75.cjs.map +1 -0
- package/dist/index.cjs +2003 -312
- package/dist/index.cjs.map +1 -1
- package/dist/index.css +2 -2
- package/dist/index.css.map +1 -1
- package/dist/index.d.cts +137 -9
- package/dist/index.d.ts +137 -9
- package/dist/index.js +1758 -83
- package/dist/index.js.map +1 -1
- package/dist/{on-ramp-CdXqJjmm.d.cts → on-ramp-XLXsy0aI.d.cts} +15 -0
- package/dist/{on-ramp-CdXqJjmm.d.ts → on-ramp-XLXsy0aI.d.ts} +15 -0
- package/dist/on-ramp.cjs +10 -974
- package/dist/on-ramp.cjs.map +1 -1
- package/dist/on-ramp.d.cts +13 -3
- package/dist/on-ramp.d.ts +13 -3
- package/dist/on-ramp.js +1 -975
- package/dist/on-ramp.js.map +1 -1
- package/package.json +3 -2
- package/dist/chunk-2AHJHISR.js.map +0 -1
- package/dist/chunk-EEAQ2NWF.cjs.map +0 -1
|
@@ -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:
|
|
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
|
|
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:
|
|
1292
|
+
chainId: networkConfig.chainId,
|
|
1267
1293
|
domain,
|
|
1268
1294
|
expirationTime,
|
|
1269
1295
|
issuedAt,
|
|
1270
1296
|
nonce: nonceResponse.nonce,
|
|
1271
|
-
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-
|
|
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/
|
|
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
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
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-
|
|
1773
|
-
//# sourceMappingURL=chunk-
|
|
2895
|
+
//# sourceMappingURL=chunk-ACLJPC75.cjs.map
|
|
2896
|
+
//# sourceMappingURL=chunk-ACLJPC75.cjs.map
|