@mintmoney/react 0.1.0-alpha.30 → 0.1.0-alpha.31
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/esm/api/checkouts.js +3 -10
- package/dist/esm/api/checkouts.js.map +1 -1
- package/dist/esm/api/generated.js +16 -500
- package/dist/esm/api/generated.js.map +1 -1
- package/dist/esm/api/payments.js +28 -29
- package/dist/esm/api/payments.js.map +1 -1
- package/dist/esm/api/types.js.map +1 -1
- package/dist/esm/checkout/modal.js +7 -7
- package/dist/esm/checkout/modal.js.map +1 -1
- package/dist/esm/checkout/views/crypto-intent/direct-details.js +135 -16
- package/dist/esm/checkout/views/crypto-intent/direct-details.js.map +1 -1
- package/dist/esm/checkout/views/crypto-intent/methods.js +5 -5
- package/dist/esm/checkout/views/crypto-intent/methods.js.map +1 -1
- package/dist/esm/checkout/views/crypto-intent/wallet-payment-confirmation-amount.js +30 -0
- package/dist/esm/checkout/views/crypto-intent/wallet-payment-confirmation-amount.js.map +1 -0
- package/dist/esm/checkout/views/crypto-intent/wallet-payment-options.js +680 -0
- package/dist/esm/checkout/views/crypto-intent/wallet-payment-options.js.map +1 -0
- package/dist/esm/checkout/views/crypto-intent/wallet-select.js +3 -31
- package/dist/esm/checkout/views/crypto-intent/wallet-select.js.map +1 -1
- package/dist/esm/checkout/views/initial.js +1 -4
- package/dist/esm/checkout/views/initial.js.map +1 -1
- package/dist/esm/components/wallet-selector/wallets/wagmi/index.js +8 -1
- package/dist/esm/components/wallet-selector/wallets/wagmi/index.js.map +1 -1
- package/dist/esm/config.js +1 -1
- package/dist/esm/config.js.map +1 -1
- package/dist/esm/integrations/evm-chain.js +22 -0
- package/dist/esm/integrations/evm-chain.js.map +1 -0
- package/dist/esm/integrations/index.js +1 -0
- package/dist/esm/integrations/index.js.map +1 -1
- package/dist/esm/integrations/lifi.js +200 -0
- package/dist/esm/integrations/lifi.js.map +1 -0
- package/dist/esm/state/checkout/payment-attempt.js +43 -0
- package/dist/esm/state/checkout/payment-attempt.js.map +1 -0
- package/dist/esm/state/checkout/store.js +120 -30
- package/dist/esm/state/checkout/store.js.map +1 -1
- package/dist/types/api/checkouts.d.ts +0 -1
- package/dist/types/api/checkouts.d.ts.map +1 -1
- package/dist/types/api/generated.d.ts +162 -1406
- package/dist/types/api/generated.d.ts.map +1 -1
- package/dist/types/api/payments.d.ts +18 -8
- package/dist/types/api/payments.d.ts.map +1 -1
- package/dist/types/api/types.d.ts +6 -8
- package/dist/types/api/types.d.ts.map +1 -1
- package/dist/types/checkout/views/crypto-intent/direct-details.d.ts.map +1 -1
- package/dist/types/checkout/views/crypto-intent/wallet-payment-confirmation-amount.d.ts +13 -0
- package/dist/types/checkout/views/crypto-intent/wallet-payment-confirmation-amount.d.ts.map +1 -0
- package/dist/types/checkout/views/crypto-intent/wallet-payment-options.d.ts +3 -0
- package/dist/types/checkout/views/crypto-intent/wallet-payment-options.d.ts.map +1 -0
- package/dist/types/checkout/views/crypto-intent/wallet-select.d.ts.map +1 -1
- package/dist/types/checkout/views/initial.d.ts.map +1 -1
- package/dist/types/components/wallet-selector/domain.d.ts +9 -0
- package/dist/types/components/wallet-selector/domain.d.ts.map +1 -1
- package/dist/types/components/wallet-selector/wallets/wagmi/index.d.ts +449 -0
- package/dist/types/components/wallet-selector/wallets/wagmi/index.d.ts.map +1 -1
- package/dist/types/config.d.ts +64 -1
- package/dist/types/config.d.ts.map +1 -1
- package/dist/types/integrations/evm-chain.d.ts +3 -0
- package/dist/types/integrations/evm-chain.d.ts.map +1 -0
- package/dist/types/integrations/index.d.ts +1 -0
- package/dist/types/integrations/index.d.ts.map +1 -1
- package/dist/types/integrations/lifi.d.ts +14 -0
- package/dist/types/integrations/lifi.d.ts.map +1 -0
- package/dist/types/state/checkout/payment-attempt.d.ts +9 -0
- package/dist/types/state/checkout/payment-attempt.d.ts.map +1 -0
- package/dist/types/state/checkout/store.d.ts +12 -86
- package/dist/types/state/checkout/store.d.ts.map +1 -1
- package/package.json +4 -2
- package/dist/esm/checkout/views/crypto-intent/currency-select.js +0 -44
- package/dist/esm/checkout/views/crypto-intent/currency-select.js.map +0 -1
- package/dist/esm/components/crypto/default-currency-selector.js +0 -62
- package/dist/esm/components/crypto/default-currency-selector.js.map +0 -1
- package/dist/types/checkout/views/crypto-intent/currency-select.d.ts +0 -3
- package/dist/types/checkout/views/crypto-intent/currency-select.d.ts.map +0 -1
- package/dist/types/components/crypto/default-currency-selector.d.ts +0 -9
- package/dist/types/components/crypto/default-currency-selector.d.ts.map +0 -1
|
@@ -0,0 +1,680 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { useCallback, useContext, useEffect, useRef, useState } from "react";
|
|
3
|
+
import { keyframes, styled } from "styled-components";
|
|
4
|
+
import { confirmPayment } from "../../../api/payments.js";
|
|
5
|
+
import { PaymentStatus } from "../../../api/types.js";
|
|
6
|
+
import { StyledButton, Text, useModalWithView, } from "../../../components/index.js";
|
|
7
|
+
import { WalletContext } from "../../../components/wallet-selector/provider.js";
|
|
8
|
+
import { useMintMoneyConfig } from "../../../context.js";
|
|
9
|
+
import { executeWalletPaymentOption, fetchWalletPaymentQuote, findWalletPaymentOptions, } from "../../../integrations/index.js";
|
|
10
|
+
import { createWalletOptionPaymentAttempt, executeConnectedWalletPayment, resetWalletPaymentState, setProcessing, setSelectedCurrency, setWalletPaymentState, startPaymentPolling, stopPaymentPolling, useCheckoutStore, } from "../../../state/checkout/store.js";
|
|
11
|
+
import { ModalViewContainer } from "../../container.js";
|
|
12
|
+
import { ModalBackButton } from "../back.js";
|
|
13
|
+
import { resolveWalletPaymentConfirmationAmount } from "./wallet-payment-confirmation-amount.js";
|
|
14
|
+
const OptionsContainer = styled.div `
|
|
15
|
+
display: flex;
|
|
16
|
+
flex-direction: column;
|
|
17
|
+
gap: 10px;
|
|
18
|
+
max-height: min(360px, 48vh);
|
|
19
|
+
overflow-y: auto;
|
|
20
|
+
padding-right: 4px;
|
|
21
|
+
overscroll-behavior: contain;
|
|
22
|
+
`;
|
|
23
|
+
const OptionCard = styled.div `
|
|
24
|
+
border: 1px solid #e5e7eb;
|
|
25
|
+
border-radius: 16px;
|
|
26
|
+
padding: 14px;
|
|
27
|
+
background: #ffffff;
|
|
28
|
+
cursor: pointer;
|
|
29
|
+
transition:
|
|
30
|
+
border-color 120ms ease,
|
|
31
|
+
box-shadow 120ms ease,
|
|
32
|
+
transform 120ms ease;
|
|
33
|
+
|
|
34
|
+
&:hover {
|
|
35
|
+
border-color: #cbd5e1;
|
|
36
|
+
transform: translateY(-1px);
|
|
37
|
+
}
|
|
38
|
+
`;
|
|
39
|
+
const OptionHeader = styled.div `
|
|
40
|
+
display: flex;
|
|
41
|
+
align-items: center;
|
|
42
|
+
justify-content: space-between;
|
|
43
|
+
gap: 12px;
|
|
44
|
+
`;
|
|
45
|
+
const OptionHeaderLeft = styled.div `
|
|
46
|
+
display: flex;
|
|
47
|
+
align-items: center;
|
|
48
|
+
gap: 10px;
|
|
49
|
+
min-width: 0;
|
|
50
|
+
flex: 1;
|
|
51
|
+
`;
|
|
52
|
+
const TokenIconWrap = styled.div `
|
|
53
|
+
position: relative;
|
|
54
|
+
width: 38px;
|
|
55
|
+
height: 38px;
|
|
56
|
+
flex-shrink: 0;
|
|
57
|
+
`;
|
|
58
|
+
const TokenIcon = styled.div `
|
|
59
|
+
width: 100%;
|
|
60
|
+
height: 100%;
|
|
61
|
+
border-radius: 999px;
|
|
62
|
+
display: inline-flex;
|
|
63
|
+
align-items: center;
|
|
64
|
+
justify-content: center;
|
|
65
|
+
font-size: 12px;
|
|
66
|
+
font-weight: 700;
|
|
67
|
+
color: ${({ $token }) => ($token === "ETH" ? "#ffffff" : "#1d4ed8")};
|
|
68
|
+
background: ${({ $token }) => $token === "ETH"
|
|
69
|
+
? "linear-gradient(135deg, #64748b, #0f172a)"
|
|
70
|
+
: "linear-gradient(135deg, #dbeafe, #bfdbfe)"};
|
|
71
|
+
background-image: ${({ $iconUrl }) => $iconUrl ? `url(${$iconUrl})` : "none"};
|
|
72
|
+
background-size: cover;
|
|
73
|
+
background-position: center;
|
|
74
|
+
overflow: hidden;
|
|
75
|
+
`;
|
|
76
|
+
const ChainBadge = styled.div `
|
|
77
|
+
position: absolute;
|
|
78
|
+
right: -2px;
|
|
79
|
+
bottom: -2px;
|
|
80
|
+
width: 18px;
|
|
81
|
+
height: 18px;
|
|
82
|
+
border-radius: 999px;
|
|
83
|
+
border: 1.5px solid #ffffff;
|
|
84
|
+
background: #ffffff;
|
|
85
|
+
background-image: ${({ $iconUrl }) => $iconUrl ? `url(${$iconUrl})` : "none"};
|
|
86
|
+
background-size: 85%;
|
|
87
|
+
background-position: center;
|
|
88
|
+
background-repeat: no-repeat;
|
|
89
|
+
box-shadow: 0 1px 4px rgba(15, 23, 42, 0.12);
|
|
90
|
+
overflow: hidden;
|
|
91
|
+
`;
|
|
92
|
+
const OptionIdentity = styled.div `
|
|
93
|
+
display: flex;
|
|
94
|
+
flex-direction: column;
|
|
95
|
+
gap: 2px;
|
|
96
|
+
min-width: 0;
|
|
97
|
+
flex: 1;
|
|
98
|
+
`;
|
|
99
|
+
const OptionBalance = styled.div `
|
|
100
|
+
display: flex;
|
|
101
|
+
flex-direction: column;
|
|
102
|
+
align-items: flex-end;
|
|
103
|
+
gap: 2px;
|
|
104
|
+
flex-shrink: 0;
|
|
105
|
+
`;
|
|
106
|
+
const OptionsHeaderRow = styled.div `
|
|
107
|
+
display: flex;
|
|
108
|
+
align-items: baseline;
|
|
109
|
+
justify-content: space-between;
|
|
110
|
+
gap: 12px;
|
|
111
|
+
`;
|
|
112
|
+
const OptionsHeaderLabel = styled.div `
|
|
113
|
+
font-size: 11px;
|
|
114
|
+
line-height: 1;
|
|
115
|
+
color: #64748b;
|
|
116
|
+
letter-spacing: 0.02em;
|
|
117
|
+
text-transform: none;
|
|
118
|
+
white-space: nowrap;
|
|
119
|
+
padding-right: 8px;
|
|
120
|
+
`;
|
|
121
|
+
const FastBadge = styled.span `
|
|
122
|
+
display: inline-flex;
|
|
123
|
+
align-items: center;
|
|
124
|
+
border-radius: 999px;
|
|
125
|
+
gap: 4px;
|
|
126
|
+
background: #ecfdf5;
|
|
127
|
+
color: #047857;
|
|
128
|
+
font-size: 12px;
|
|
129
|
+
font-weight: 600;
|
|
130
|
+
line-height: 1;
|
|
131
|
+
padding: 6px 9px;
|
|
132
|
+
white-space: nowrap;
|
|
133
|
+
`;
|
|
134
|
+
const EmptyState = styled.div `
|
|
135
|
+
border: 1px dashed #d1d5db;
|
|
136
|
+
border-radius: 16px;
|
|
137
|
+
padding: 18px;
|
|
138
|
+
text-align: center;
|
|
139
|
+
background: #f9fafb;
|
|
140
|
+
`;
|
|
141
|
+
const confirmTrayEnter = keyframes `
|
|
142
|
+
from {
|
|
143
|
+
opacity: 0;
|
|
144
|
+
transform: perspective(1000px) rotateY(-86deg) scale(0.96);
|
|
145
|
+
transform-origin: center;
|
|
146
|
+
}
|
|
147
|
+
to {
|
|
148
|
+
opacity: 1;
|
|
149
|
+
opacity: 1;
|
|
150
|
+
transform: perspective(1000px) rotateY(0deg) scale(1);
|
|
151
|
+
}
|
|
152
|
+
`;
|
|
153
|
+
const ConfirmScreen = styled.div `
|
|
154
|
+
margin-top: 8px;
|
|
155
|
+
border: 1px solid #b7efd1;
|
|
156
|
+
border-radius: 22px;
|
|
157
|
+
padding: 18px 16px 16px;
|
|
158
|
+
background: linear-gradient(180deg, #ecfdf5 0%, #dcfce7 100%);
|
|
159
|
+
box-shadow: 0 14px 30px rgba(16, 185, 129, 0.12);
|
|
160
|
+
display: flex;
|
|
161
|
+
flex-direction: column;
|
|
162
|
+
gap: 16px;
|
|
163
|
+
animation: ${confirmTrayEnter} 220ms cubic-bezier(0.22, 1, 0.36, 1);
|
|
164
|
+
`;
|
|
165
|
+
const ConfirmMeta = styled.div `
|
|
166
|
+
display: flex;
|
|
167
|
+
align-items: center;
|
|
168
|
+
justify-content: space-between;
|
|
169
|
+
gap: 12px;
|
|
170
|
+
`;
|
|
171
|
+
const ConfirmAmount = styled.div `
|
|
172
|
+
display: flex;
|
|
173
|
+
align-items: center;
|
|
174
|
+
gap: 10px;
|
|
175
|
+
`;
|
|
176
|
+
const ConfirmTextBlock = styled.div `
|
|
177
|
+
display: flex;
|
|
178
|
+
flex-direction: column;
|
|
179
|
+
gap: 2px;
|
|
180
|
+
`;
|
|
181
|
+
const ConfirmActions = styled.div `
|
|
182
|
+
display: flex;
|
|
183
|
+
flex-direction: column;
|
|
184
|
+
gap: 10px;
|
|
185
|
+
`;
|
|
186
|
+
const QuoteBlock = styled.div `
|
|
187
|
+
display: flex;
|
|
188
|
+
flex-direction: column;
|
|
189
|
+
gap: 10px;
|
|
190
|
+
`;
|
|
191
|
+
const QuoteRow = styled.div `
|
|
192
|
+
display: flex;
|
|
193
|
+
align-items: flex-start;
|
|
194
|
+
justify-content: space-between;
|
|
195
|
+
gap: 12px;
|
|
196
|
+
`;
|
|
197
|
+
const ContentStage = styled.div `
|
|
198
|
+
display: flex;
|
|
199
|
+
flex-direction: column;
|
|
200
|
+
gap: 16px;
|
|
201
|
+
min-height: 0;
|
|
202
|
+
`;
|
|
203
|
+
const buildDefaultOption = (context) => ({
|
|
204
|
+
id: "direct-settlement",
|
|
205
|
+
label: `${context.settlementCurrency.asset_name} on ${context.settlementCurrency.chain_name}`,
|
|
206
|
+
subtitle: "Fastest Route. You can pay directly without needing to swap.",
|
|
207
|
+
badge: "Fastest",
|
|
208
|
+
fromChain: context.settlementCurrency.chain,
|
|
209
|
+
fromChainName: context.settlementCurrency.chain_name,
|
|
210
|
+
fromToken: context.settlementCurrency.asset,
|
|
211
|
+
fromTokenName: context.settlementCurrency.asset_name,
|
|
212
|
+
fromAmount: null,
|
|
213
|
+
quoteId: null,
|
|
214
|
+
routeId: null,
|
|
215
|
+
kind: "direct",
|
|
216
|
+
});
|
|
217
|
+
const formatDisplayAmount = (amount, maximumFractionDigits = 4) => {
|
|
218
|
+
if (!amount)
|
|
219
|
+
return null;
|
|
220
|
+
const parsed = Number(amount);
|
|
221
|
+
if (!Number.isFinite(parsed))
|
|
222
|
+
return amount;
|
|
223
|
+
if (parsed >= 1000)
|
|
224
|
+
return parsed.toLocaleString(undefined, { maximumFractionDigits: 2 });
|
|
225
|
+
if (parsed >= 1) {
|
|
226
|
+
return parsed.toLocaleString(undefined, {
|
|
227
|
+
minimumFractionDigits: 2,
|
|
228
|
+
maximumFractionDigits: 2,
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
if (parsed >= 0.01) {
|
|
232
|
+
return parsed.toLocaleString(undefined, {
|
|
233
|
+
minimumFractionDigits: 2,
|
|
234
|
+
maximumFractionDigits,
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
return parsed.toLocaleString(undefined, {
|
|
238
|
+
minimumFractionDigits: 4,
|
|
239
|
+
maximumFractionDigits,
|
|
240
|
+
});
|
|
241
|
+
};
|
|
242
|
+
const formatTokenBalance = (tokenName, amount) => {
|
|
243
|
+
const isEth = tokenName.toUpperCase() === "ETH";
|
|
244
|
+
const formatted = formatDisplayAmount(amount, isEth ? 8 : 4);
|
|
245
|
+
if (!formatted)
|
|
246
|
+
return null;
|
|
247
|
+
if (tokenName.toLowerCase().includes("usd"))
|
|
248
|
+
return `$${formatted}`;
|
|
249
|
+
if (isEth)
|
|
250
|
+
return `ETH ${formatted}`;
|
|
251
|
+
return formatted;
|
|
252
|
+
};
|
|
253
|
+
const formatMoneyAmount = (currency, amount) => {
|
|
254
|
+
try {
|
|
255
|
+
return new Intl.NumberFormat(undefined, {
|
|
256
|
+
style: "currency",
|
|
257
|
+
currency,
|
|
258
|
+
minimumFractionDigits: 2,
|
|
259
|
+
maximumFractionDigits: 2,
|
|
260
|
+
}).format(amount);
|
|
261
|
+
}
|
|
262
|
+
catch {
|
|
263
|
+
return `${currency} ${amount.toFixed(2)}`;
|
|
264
|
+
}
|
|
265
|
+
};
|
|
266
|
+
const getTokenMonogram = (tokenName, tokenSymbol) => {
|
|
267
|
+
if (tokenSymbol)
|
|
268
|
+
return tokenSymbol.slice(0, 2).toUpperCase();
|
|
269
|
+
return tokenName.slice(0, 2).toUpperCase();
|
|
270
|
+
};
|
|
271
|
+
const buildDirectQuote = (option, context) => {
|
|
272
|
+
const amount = String(context.cryptoPaymentAttempt.amount_formatted);
|
|
273
|
+
const formattedAmount = formatDisplayAmount(amount) ?? amount;
|
|
274
|
+
const amountDisplay = formatTokenBalance(option.fromTokenSymbol ?? option.fromTokenName, formattedAmount) ?? formattedAmount;
|
|
275
|
+
return {
|
|
276
|
+
quoteId: option.quoteId,
|
|
277
|
+
routeId: option.routeId,
|
|
278
|
+
toolName: "Direct transfer",
|
|
279
|
+
fromAmount: amount,
|
|
280
|
+
fromAmountDisplay: amountDisplay,
|
|
281
|
+
toAmount: amount,
|
|
282
|
+
toAmountDisplay: amountDisplay,
|
|
283
|
+
estimatedDurationMinutes: 1,
|
|
284
|
+
};
|
|
285
|
+
};
|
|
286
|
+
const PAYMENT_QUOTE_EXPIRED_MESSAGE = "This payment quote has expired. Please create a new payment.";
|
|
287
|
+
const PAYMENT_NO_LONGER_PAYABLE_MESSAGE = "This payment is no longer payable. Please create a new payment.";
|
|
288
|
+
const parseExpiryTime = (value) => {
|
|
289
|
+
if (!value)
|
|
290
|
+
return null;
|
|
291
|
+
const parsed = Date.parse(value);
|
|
292
|
+
return Number.isFinite(parsed) ? parsed : null;
|
|
293
|
+
};
|
|
294
|
+
const getActiveAttemptExpiryTime = (payment, attempt) => {
|
|
295
|
+
return parseExpiryTime(attempt?.expires_at ?? payment?.expires_at);
|
|
296
|
+
};
|
|
297
|
+
const hasExpiredAttempt = (payment, attempt) => {
|
|
298
|
+
const expiryTime = getActiveAttemptExpiryTime(payment, attempt);
|
|
299
|
+
return expiryTime !== null && Date.now() >= expiryTime;
|
|
300
|
+
};
|
|
301
|
+
const CryptoIntentWalletPaymentOptionsViewContent = () => {
|
|
302
|
+
const modalRef = useRef(null);
|
|
303
|
+
const { goToView } = useModalWithView();
|
|
304
|
+
const config = useMintMoneyConfig();
|
|
305
|
+
const { wallet } = useContext(WalletContext);
|
|
306
|
+
const { payment, cryptoPaymentAttempt, cryptoCurrency, walletPayment, loading, processing, } = useCheckoutStore();
|
|
307
|
+
const [executingId, setExecutingId] = useState(null);
|
|
308
|
+
const buildContext = useCallback(async (currentPayment) => {
|
|
309
|
+
const state = useCheckoutStore.getState();
|
|
310
|
+
const currentCheckout = state.checkout;
|
|
311
|
+
const currentCryptoCurrency = state.cryptoCurrency;
|
|
312
|
+
if (!currentCheckout || !currentCryptoCurrency || !wallet) {
|
|
313
|
+
throw new Error("Wallet payment context is not ready");
|
|
314
|
+
}
|
|
315
|
+
const resolvedPayment = currentPayment ?? state.payment;
|
|
316
|
+
if (!resolvedPayment) {
|
|
317
|
+
throw new Error("Missing payment");
|
|
318
|
+
}
|
|
319
|
+
const walletAddress = await wallet.getAddress();
|
|
320
|
+
if (!walletAddress) {
|
|
321
|
+
throw new Error("Failed to get wallet address");
|
|
322
|
+
}
|
|
323
|
+
return {
|
|
324
|
+
checkout: currentCheckout,
|
|
325
|
+
payment: resolvedPayment,
|
|
326
|
+
settlementCurrency: currentCryptoCurrency,
|
|
327
|
+
walletAddress,
|
|
328
|
+
};
|
|
329
|
+
}, [wallet]);
|
|
330
|
+
useEffect(() => {
|
|
331
|
+
if (!wallet || !cryptoCurrency) {
|
|
332
|
+
goToView("failed", { reason: "wallet_not_connected" });
|
|
333
|
+
return;
|
|
334
|
+
}
|
|
335
|
+
let cancelled = false;
|
|
336
|
+
const loadOptions = async () => {
|
|
337
|
+
try {
|
|
338
|
+
setProcessing(true);
|
|
339
|
+
setSelectedCurrency(cryptoCurrency);
|
|
340
|
+
resetWalletPaymentState();
|
|
341
|
+
setWalletPaymentState({ status: "loading", error: null, options: [] });
|
|
342
|
+
const preparedContext = await buildContext();
|
|
343
|
+
const providerOptions = await findWalletPaymentOptions(preparedContext);
|
|
344
|
+
const options = providerOptions.length > 0
|
|
345
|
+
? providerOptions
|
|
346
|
+
: [buildDefaultOption(preparedContext)];
|
|
347
|
+
if (cancelled)
|
|
348
|
+
return;
|
|
349
|
+
setWalletPaymentState({
|
|
350
|
+
status: "ready",
|
|
351
|
+
options,
|
|
352
|
+
selectedOptionId: null,
|
|
353
|
+
error: null,
|
|
354
|
+
});
|
|
355
|
+
}
|
|
356
|
+
catch (error) {
|
|
357
|
+
if (cancelled)
|
|
358
|
+
return;
|
|
359
|
+
const message = error instanceof Error
|
|
360
|
+
? error.message
|
|
361
|
+
: "Failed to load payment options";
|
|
362
|
+
setWalletPaymentState({
|
|
363
|
+
status: "failed",
|
|
364
|
+
error: message,
|
|
365
|
+
options: [],
|
|
366
|
+
});
|
|
367
|
+
}
|
|
368
|
+
finally {
|
|
369
|
+
if (!cancelled) {
|
|
370
|
+
setProcessing(false);
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
};
|
|
374
|
+
void loadOptions();
|
|
375
|
+
return () => {
|
|
376
|
+
cancelled = true;
|
|
377
|
+
};
|
|
378
|
+
}, [buildContext, goToView, payment?.id, wallet]);
|
|
379
|
+
const selectedOption = walletPayment.options.find((option) => option.id === walletPayment.selectedOptionId) ?? null;
|
|
380
|
+
const confirmationAmount = selectedOption && payment
|
|
381
|
+
? resolveWalletPaymentConfirmationAmount({
|
|
382
|
+
option: selectedOption,
|
|
383
|
+
quote: walletPayment.quote,
|
|
384
|
+
attempt: cryptoPaymentAttempt,
|
|
385
|
+
})
|
|
386
|
+
: null;
|
|
387
|
+
const formattedCost = formatMoneyAmount(payment?.currency ?? "USD", Number(payment?.amount ?? 0));
|
|
388
|
+
useEffect(() => {
|
|
389
|
+
if (!payment)
|
|
390
|
+
return;
|
|
391
|
+
if (payment.status === PaymentStatus.FAILED) {
|
|
392
|
+
setWalletPaymentState({
|
|
393
|
+
status: "failed",
|
|
394
|
+
error: PAYMENT_NO_LONGER_PAYABLE_MESSAGE,
|
|
395
|
+
});
|
|
396
|
+
goToView("failed", { reason: PAYMENT_NO_LONGER_PAYABLE_MESSAGE });
|
|
397
|
+
return;
|
|
398
|
+
}
|
|
399
|
+
if (hasExpiredAttempt(payment, cryptoPaymentAttempt ?? payment.payment_attempt)) {
|
|
400
|
+
setWalletPaymentState({
|
|
401
|
+
status: "failed",
|
|
402
|
+
error: PAYMENT_QUOTE_EXPIRED_MESSAGE,
|
|
403
|
+
});
|
|
404
|
+
goToView("failed", { reason: PAYMENT_QUOTE_EXPIRED_MESSAGE });
|
|
405
|
+
}
|
|
406
|
+
}, [
|
|
407
|
+
cryptoPaymentAttempt?.expires_at,
|
|
408
|
+
goToView,
|
|
409
|
+
payment,
|
|
410
|
+
payment?.payment_attempt?.expires_at,
|
|
411
|
+
payment?.status,
|
|
412
|
+
]);
|
|
413
|
+
useEffect(() => {
|
|
414
|
+
if (!payment?.id)
|
|
415
|
+
return;
|
|
416
|
+
const confirmationStatus = config.cryptoCheckoutConfig.paymentConfirmationStatus;
|
|
417
|
+
const poller = startPaymentPolling((status) => {
|
|
418
|
+
if (status === PaymentStatus.PAID || status === confirmationStatus) {
|
|
419
|
+
goToView("paymentSuccess");
|
|
420
|
+
return;
|
|
421
|
+
}
|
|
422
|
+
if (status === PaymentStatus.FAILED) {
|
|
423
|
+
setWalletPaymentState({
|
|
424
|
+
status: "failed",
|
|
425
|
+
error: PAYMENT_NO_LONGER_PAYABLE_MESSAGE,
|
|
426
|
+
});
|
|
427
|
+
goToView("failed", { reason: PAYMENT_NO_LONGER_PAYABLE_MESSAGE });
|
|
428
|
+
}
|
|
429
|
+
}, confirmationStatus);
|
|
430
|
+
return () => {
|
|
431
|
+
poller.stop();
|
|
432
|
+
stopPaymentPolling();
|
|
433
|
+
};
|
|
434
|
+
}, [
|
|
435
|
+
config.cryptoCheckoutConfig.paymentConfirmationStatus,
|
|
436
|
+
goToView,
|
|
437
|
+
payment?.id,
|
|
438
|
+
]);
|
|
439
|
+
const handlePickOption = (option) => {
|
|
440
|
+
setWalletPaymentState({
|
|
441
|
+
selectedOptionId: option.id,
|
|
442
|
+
error: null,
|
|
443
|
+
quoteId: null,
|
|
444
|
+
routeId: null,
|
|
445
|
+
quoteStatus: "idle",
|
|
446
|
+
quote: null,
|
|
447
|
+
quoteError: null,
|
|
448
|
+
});
|
|
449
|
+
};
|
|
450
|
+
const handleReturnToOptions = () => {
|
|
451
|
+
setWalletPaymentState({
|
|
452
|
+
selectedOptionId: null,
|
|
453
|
+
error: null,
|
|
454
|
+
quoteStatus: "idle",
|
|
455
|
+
quote: null,
|
|
456
|
+
quoteError: null,
|
|
457
|
+
});
|
|
458
|
+
};
|
|
459
|
+
useEffect(() => {
|
|
460
|
+
if (!selectedOption || !payment?.id || !cryptoCurrency) {
|
|
461
|
+
return;
|
|
462
|
+
}
|
|
463
|
+
let cancelled = false;
|
|
464
|
+
let refreshTimer = null;
|
|
465
|
+
const clearRefreshTimer = () => {
|
|
466
|
+
if (refreshTimer) {
|
|
467
|
+
clearTimeout(refreshTimer);
|
|
468
|
+
refreshTimer = null;
|
|
469
|
+
}
|
|
470
|
+
};
|
|
471
|
+
const loadQuote = async () => {
|
|
472
|
+
clearRefreshTimer();
|
|
473
|
+
try {
|
|
474
|
+
const preparedContext = await buildContext();
|
|
475
|
+
if (hasExpiredAttempt(preparedContext.payment, preparedContext.payment.payment_attempt)) {
|
|
476
|
+
throw new Error(PAYMENT_QUOTE_EXPIRED_MESSAGE);
|
|
477
|
+
}
|
|
478
|
+
const walletAddress = await wallet?.getAddress();
|
|
479
|
+
if (!walletAddress) {
|
|
480
|
+
throw new Error("Failed to get wallet address");
|
|
481
|
+
}
|
|
482
|
+
const ensuredAttempt = await createWalletOptionPaymentAttempt(selectedOption, walletAddress, null);
|
|
483
|
+
if (hasExpiredAttempt(preparedContext.payment, ensuredAttempt)) {
|
|
484
|
+
throw new Error(PAYMENT_QUOTE_EXPIRED_MESSAGE);
|
|
485
|
+
}
|
|
486
|
+
const quoteContext = {
|
|
487
|
+
...preparedContext,
|
|
488
|
+
cryptoPaymentAttempt: ensuredAttempt,
|
|
489
|
+
};
|
|
490
|
+
if (selectedOption.kind === "direct") {
|
|
491
|
+
const directQuote = buildDirectQuote(selectedOption, quoteContext);
|
|
492
|
+
if (cancelled)
|
|
493
|
+
return;
|
|
494
|
+
setWalletPaymentState({
|
|
495
|
+
quoteStatus: "ready",
|
|
496
|
+
quote: directQuote,
|
|
497
|
+
quoteId: directQuote.quoteId,
|
|
498
|
+
routeId: directQuote.routeId,
|
|
499
|
+
quoteError: null,
|
|
500
|
+
});
|
|
501
|
+
return;
|
|
502
|
+
}
|
|
503
|
+
if (!cancelled) {
|
|
504
|
+
setWalletPaymentState({
|
|
505
|
+
quoteStatus: "loading",
|
|
506
|
+
quote: null,
|
|
507
|
+
quoteError: null,
|
|
508
|
+
quoteId: null,
|
|
509
|
+
routeId: null,
|
|
510
|
+
});
|
|
511
|
+
}
|
|
512
|
+
const quote = await fetchWalletPaymentQuote({
|
|
513
|
+
option: selectedOption,
|
|
514
|
+
context: quoteContext,
|
|
515
|
+
});
|
|
516
|
+
if (cancelled)
|
|
517
|
+
return;
|
|
518
|
+
setWalletPaymentState({
|
|
519
|
+
quoteStatus: "ready",
|
|
520
|
+
quote,
|
|
521
|
+
quoteId: quote.quoteId,
|
|
522
|
+
routeId: quote.routeId,
|
|
523
|
+
quoteError: null,
|
|
524
|
+
});
|
|
525
|
+
const expiryTime = getActiveAttemptExpiryTime(preparedContext.payment, ensuredAttempt);
|
|
526
|
+
if (expiryTime && expiryTime > Date.now()) {
|
|
527
|
+
refreshTimer = setTimeout(() => {
|
|
528
|
+
void loadQuote();
|
|
529
|
+
}, Math.min(30_000, Math.max(1_000, expiryTime - Date.now())));
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
catch (error) {
|
|
533
|
+
if (cancelled)
|
|
534
|
+
return;
|
|
535
|
+
const message = error instanceof Error ? error.message : "Failed to load route quote";
|
|
536
|
+
setWalletPaymentState({
|
|
537
|
+
quoteStatus: "failed",
|
|
538
|
+
quote: null,
|
|
539
|
+
quoteError: message,
|
|
540
|
+
});
|
|
541
|
+
}
|
|
542
|
+
};
|
|
543
|
+
void loadQuote();
|
|
544
|
+
return () => {
|
|
545
|
+
cancelled = true;
|
|
546
|
+
clearRefreshTimer();
|
|
547
|
+
};
|
|
548
|
+
}, [buildContext, payment?.id, selectedOption?.id, wallet]);
|
|
549
|
+
const handleSelectOption = async (option) => {
|
|
550
|
+
if (!wallet || !payment || !cryptoCurrency) {
|
|
551
|
+
goToView("failed", { reason: "wallet_not_connected" });
|
|
552
|
+
return;
|
|
553
|
+
}
|
|
554
|
+
try {
|
|
555
|
+
setExecutingId(option.id);
|
|
556
|
+
setProcessing(true);
|
|
557
|
+
setSelectedCurrency(cryptoCurrency);
|
|
558
|
+
setWalletPaymentState({
|
|
559
|
+
status: "executing",
|
|
560
|
+
selectedOptionId: option.id,
|
|
561
|
+
error: null,
|
|
562
|
+
quoteId: null,
|
|
563
|
+
routeId: null,
|
|
564
|
+
});
|
|
565
|
+
const walletAddress = await wallet.getAddress();
|
|
566
|
+
if (!walletAddress) {
|
|
567
|
+
throw new Error("Failed to get wallet address");
|
|
568
|
+
}
|
|
569
|
+
const ensuredAttempt = await createWalletOptionPaymentAttempt(option, walletAddress, option.kind === "direct" ? null : walletPayment.quote);
|
|
570
|
+
if (hasExpiredAttempt(payment, ensuredAttempt)) {
|
|
571
|
+
throw new Error(PAYMENT_QUOTE_EXPIRED_MESSAGE);
|
|
572
|
+
}
|
|
573
|
+
const preparedContext = {
|
|
574
|
+
...(await buildContext(payment)),
|
|
575
|
+
cryptoPaymentAttempt: ensuredAttempt,
|
|
576
|
+
};
|
|
577
|
+
if (option.kind === "direct") {
|
|
578
|
+
await executeConnectedWalletPayment(wallet);
|
|
579
|
+
const txHash = useCheckoutStore.getState().transfer.transactionHash;
|
|
580
|
+
if (!txHash) {
|
|
581
|
+
throw new Error("Missing transaction hash for direct wallet payment");
|
|
582
|
+
}
|
|
583
|
+
await confirmPayment(payment.id, { txHash });
|
|
584
|
+
setWalletPaymentState({
|
|
585
|
+
status: "submitted",
|
|
586
|
+
sourceTxHash: txHash,
|
|
587
|
+
destinationTxHash: null,
|
|
588
|
+
quoteId: null,
|
|
589
|
+
routeId: null,
|
|
590
|
+
});
|
|
591
|
+
}
|
|
592
|
+
else {
|
|
593
|
+
const freshQuote = await fetchWalletPaymentQuote({
|
|
594
|
+
option,
|
|
595
|
+
context: preparedContext,
|
|
596
|
+
});
|
|
597
|
+
if (!freshQuote.quoteId) {
|
|
598
|
+
throw new Error("Missing LI.FI quote id for routed wallet payment");
|
|
599
|
+
}
|
|
600
|
+
setWalletPaymentState({
|
|
601
|
+
quoteStatus: "ready",
|
|
602
|
+
quote: freshQuote,
|
|
603
|
+
quoteId: freshQuote.quoteId,
|
|
604
|
+
routeId: freshQuote.routeId,
|
|
605
|
+
quoteError: null,
|
|
606
|
+
});
|
|
607
|
+
const result = await executeWalletPaymentOption({
|
|
608
|
+
option,
|
|
609
|
+
wallet,
|
|
610
|
+
context: preparedContext,
|
|
611
|
+
quote: freshQuote,
|
|
612
|
+
});
|
|
613
|
+
const confirmationTxHash = result?.destinationTxHash ?? null;
|
|
614
|
+
if (!confirmationTxHash) {
|
|
615
|
+
throw new Error("Missing destination transaction hash for routed wallet payment");
|
|
616
|
+
}
|
|
617
|
+
await confirmPayment(payment.id, {
|
|
618
|
+
txHash: confirmationTxHash,
|
|
619
|
+
lifiQuote: {
|
|
620
|
+
quote_id: freshQuote.quoteId,
|
|
621
|
+
route_id: freshQuote.routeId ?? null,
|
|
622
|
+
route: freshQuote.raw,
|
|
623
|
+
},
|
|
624
|
+
});
|
|
625
|
+
setWalletPaymentState({
|
|
626
|
+
status: "submitted",
|
|
627
|
+
sourceTxHash: result?.sourceTxHash ?? null,
|
|
628
|
+
destinationTxHash: confirmationTxHash,
|
|
629
|
+
quoteId: freshQuote.quoteId,
|
|
630
|
+
routeId: freshQuote.routeId,
|
|
631
|
+
});
|
|
632
|
+
}
|
|
633
|
+
goToView("paymentStatus");
|
|
634
|
+
}
|
|
635
|
+
catch (error) {
|
|
636
|
+
const message = error instanceof Error
|
|
637
|
+
? error.message
|
|
638
|
+
: "Failed to execute wallet payment";
|
|
639
|
+
setWalletPaymentState({
|
|
640
|
+
status: "failed",
|
|
641
|
+
error: message,
|
|
642
|
+
});
|
|
643
|
+
goToView("failed", { reason: message });
|
|
644
|
+
}
|
|
645
|
+
finally {
|
|
646
|
+
setExecutingId(null);
|
|
647
|
+
setProcessing(false);
|
|
648
|
+
}
|
|
649
|
+
};
|
|
650
|
+
return (_jsxs(ModalViewContainer, { ref: modalRef, children: [selectedOption ? (_jsx(Text, { size: "base", weight: "semibold", color: config.theme.font.color.primary, children: "Confirm your payment" })) : null, walletPayment.status === "failed" ? (_jsx(EmptyState, { children: _jsx(Text, { size: "sm", color: config.theme.font.color.secondary, children: walletPayment.error ||
|
|
651
|
+
"We could not find wallet payment options right now." }) })) : null, walletPayment.status === "loading" ? (_jsx(EmptyState, { children: _jsx(Text, { size: "sm", color: config.theme.font.color.secondary, children: "Checking which tokens you can use..." }) })) : null, walletPayment.status === "ready" ||
|
|
652
|
+
walletPayment.status === "executing" ? (_jsx(ContentStage, { children: !selectedOption ? (_jsxs(_Fragment, { children: [_jsxs(OptionsHeaderRow, { children: [_jsx(Text, { size: "base", weight: "semibold", color: config.theme.font.color.primary, children: "Choose how you want to pay." }), _jsx(OptionsHeaderLabel, { children: "Your Balance" })] }), _jsx(OptionsContainer, { children: walletPayment.options.map((option) => (_jsx(OptionCard, { onClick: () => handlePickOption(option), role: "button", "aria-pressed": walletPayment.selectedOptionId === option.id, children: _jsxs(OptionHeader, { children: [_jsxs(OptionHeaderLeft, { children: [_jsxs(TokenIconWrap, { children: [_jsx(TokenIcon, { "$token": option.fromTokenName === "ETH" ? "ETH" : "USD", "$iconUrl": option.fromTokenIconUrl, children: !option.fromTokenIconUrl
|
|
653
|
+
? getTokenMonogram(option.fromTokenName, option.fromTokenSymbol)
|
|
654
|
+
: null }), _jsx(ChainBadge, { "$iconUrl": option.fromChainIconUrl, children: !option.fromChainIconUrl
|
|
655
|
+
? getTokenMonogram(option.fromChainName)
|
|
656
|
+
: null })] }), _jsxs(OptionIdentity, { children: [_jsx(Text, { size: "base", weight: "semibold", color: config.theme.font.color.primary, children: option.fromTokenName }), _jsxs(Text, { size: "sm", color: config.theme.font.color.secondary, children: ["On ", option.fromChainName] })] })] }), _jsxs(OptionBalance, { children: [_jsx(Text, { size: "base", weight: "semibold", color: config.theme.font.color.primary, children: formatTokenBalance(option.fromTokenName, option.fromAmount) ?? "0" }), option.kind === "direct" ? (_jsxs(FastBadge, { children: [_jsx("span", { children: "\u26A1" }), _jsx("span", { children: "Fast" })] })) : typeof option.estimatedDurationMinutes ===
|
|
657
|
+
"number" ? (_jsxs(Text, { size: "xs", color: config.theme.font.color.tertiary, children: ["~", option.estimatedDurationMinutes, " min"] })) : null] })] }) }, option.id))) }), _jsx(ModalBackButton, { view: "cryptoIntent", disabled: processing })] })) : (_jsxs(ConfirmScreen, { children: [_jsx(ConfirmMeta, { children: _jsxs(ConfirmAmount, { children: [_jsxs(TokenIconWrap, { children: [_jsx(TokenIcon, { "$token": selectedOption.fromTokenName === "ETH" ? "ETH" : "USD", "$iconUrl": selectedOption.fromTokenIconUrl, children: !selectedOption.fromTokenIconUrl
|
|
658
|
+
? getTokenMonogram(selectedOption.fromTokenName, selectedOption.fromTokenSymbol)
|
|
659
|
+
: null }), _jsx(ChainBadge, { "$iconUrl": selectedOption.fromChainIconUrl, children: !selectedOption.fromChainIconUrl
|
|
660
|
+
? getTokenMonogram(selectedOption.fromChainName)
|
|
661
|
+
: null })] }), _jsxs(ConfirmTextBlock, { children: [_jsx(Text, { size: "base", weight: "semibold", color: config.theme.font.color.primary, children: selectedOption.fromTokenName }), _jsxs(Text, { size: "sm", color: config.theme.font.color.secondary, children: ["On ", selectedOption.fromChainName] })] })] }) }), _jsxs(QuoteBlock, { children: [_jsxs(QuoteRow, { children: [_jsx(Text, { size: "sm", color: config.theme.font.color.secondary, children: "Cost" }), _jsx(Text, { size: "sm", weight: "semibold", color: config.theme.font.color.primary, children: formattedCost })] }), confirmationAmount?.amount ? (_jsxs(QuoteRow, { children: [_jsx(Text, { size: "sm", color: config.theme.font.color.secondary, children: "You Pay" }), _jsx(Text, { size: "sm", weight: "semibold", color: config.theme.font.color.primary, children: confirmationAmount.amount })] })) : null, walletPayment.quoteStatus === "loading" ? (_jsx(Text, { size: "sm", color: config.theme.font.color.secondary, children: "Fetching quote..." })) : null, walletPayment.quoteStatus === "failed" ? (_jsx(Text, { size: "sm", color: config.theme.font.color.secondary, children: walletPayment.quoteError ||
|
|
662
|
+
"We couldn't fetch a quote right now." })) : null, typeof walletPayment.quote?.estimatedDurationMinutes ===
|
|
663
|
+
"number" ? (_jsxs(QuoteRow, { children: [_jsx(Text, { size: "sm", color: config.theme.font.color.secondary, children: "Estimated time" }), _jsxs(Text, { size: "sm", weight: "semibold", color: config.theme.font.color.primary, children: ["~", walletPayment.quote.estimatedDurationMinutes, " min"] })] })) : null] }), _jsx(ConfirmActions, { children: _jsx(StyledButton, { onClick: () => {
|
|
664
|
+
if (selectedOption) {
|
|
665
|
+
void handleSelectOption(selectedOption);
|
|
666
|
+
}
|
|
667
|
+
}, disabled: processing ||
|
|
668
|
+
loading.CREATE_PAYMENT_ATTEMPT ||
|
|
669
|
+
!selectedOption ||
|
|
670
|
+
hasExpiredAttempt(payment, useCheckoutStore.getState().cryptoPaymentAttempt) ||
|
|
671
|
+
walletPayment.quoteStatus === "loading" ||
|
|
672
|
+
(selectedOption.kind !== "direct" &&
|
|
673
|
+
walletPayment.quoteStatus !== "ready"), children: executingId === selectedOption.id
|
|
674
|
+
? "Preparing..."
|
|
675
|
+
: walletPayment.quoteStatus === "loading"
|
|
676
|
+
? "Fetching quote..."
|
|
677
|
+
: `Pay with ${selectedOption.fromTokenName}` }) })] }, selectedOption.id)) })) : null, selectedOption ? (_jsx(StyledButton, { variant: "secondary", onClick: handleReturnToOptions, disabled: processing, children: "Back" })) : null] }));
|
|
678
|
+
};
|
|
679
|
+
export { CryptoIntentWalletPaymentOptionsViewContent };
|
|
680
|
+
//# sourceMappingURL=wallet-payment-options.js.map
|