@daimo/pay 1.9.2 → 1.9.5
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/build/index.js +214 -111
- package/build/index.js.map +1 -1
- package/package.json +2 -2
package/build/index.js
CHANGED
|
@@ -2,7 +2,7 @@ import { http, useConnectors as useConnectors$1, useAccount, useSwitchChain, use
|
|
|
2
2
|
import { mainnet, base as base$1, polygon, optimism, arbitrum, linea, bsc, sepolia, baseSepolia, worldchain, mantle } from 'wagmi/chains';
|
|
3
3
|
import { safe, injected, coinbaseWallet, walletConnect } from '@wagmi/connectors';
|
|
4
4
|
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
5
|
-
import { DaimoPayIntentStatus, assert, DaimoPayOrderMode, readDaimoPayOrderID, getOrderDestChainId, assertNotNull, getChainName, arbitrum as arbitrum$1, base as base$2, bsc as bsc$1, ethereum, linea as linea$1, mantle as mantle$1, optimism as optimism$1, polygon as polygon$1, worldchain as worldchain$1, solana, getAddressContraction, supportedChains, getChainExplorerTxUrl, ExternalPaymentOptions, DepositAddressPaymentOptions, ethereumUSDC, polygonUSDC, baseUSDC, arbitrumUSDC, optimismUSDC, isCCTPV1Chain, debugJson, writeDaimoPayOrderID, getOrderSourceChainId, DaimoPayEventType, getDaimoPayOrderView } from '@daimo/pay-common';
|
|
5
|
+
import { DaimoPayIntentStatus, assert, DaimoPayOrderMode, isHydrated, readDaimoPayOrderID, getOrderDestChainId, assertNotNull, getChainName, arbitrum as arbitrum$1, base as base$2, bsc as bsc$1, ethereum, linea as linea$1, mantle as mantle$1, optimism as optimism$1, polygon as polygon$1, worldchain as worldchain$1, solana, getAddressContraction, supportedChains, getChainExplorerTxUrl, ExternalPaymentOptions, DepositAddressPaymentOptions, ethereumUSDC, polygonUSDC, baseUSDC, arbitrumUSDC, optimismUSDC, isCCTPV1Chain, debugJson, writeDaimoPayOrderID, DaimoPayOrderStatusSource, getOrderSourceChainId, DaimoPayEventType, getDaimoPayOrderView } from '@daimo/pay-common';
|
|
6
6
|
import { Buffer } from 'buffer';
|
|
7
7
|
import React, { createContext, useRef, useState, useEffect, useLayoutEffect, useMemo, useContext, useSyncExternalStore, useCallback, createElement } from 'react';
|
|
8
8
|
import styled$1, { css, keyframes, ThemeProvider } from 'styled-components';
|
|
@@ -22,7 +22,7 @@ import { VersionedTransaction } from '@solana/web3.js';
|
|
|
22
22
|
import { normalize } from 'viem/ens';
|
|
23
23
|
|
|
24
24
|
var name = "@daimo/pay";
|
|
25
|
-
var version = "1.9.
|
|
25
|
+
var version = "1.9.5";
|
|
26
26
|
var author = "Daimo";
|
|
27
27
|
var homepage = "https://pay.daimo.com";
|
|
28
28
|
var license = "BSD-2-Clause license";
|
|
@@ -61,7 +61,7 @@ var keywords = [
|
|
|
61
61
|
"crypto"
|
|
62
62
|
];
|
|
63
63
|
var dependencies = {
|
|
64
|
-
"@daimo/pay-common": "1.9.
|
|
64
|
+
"@daimo/pay-common": "1.9.5",
|
|
65
65
|
"@rollup/plugin-typescript": "^12.1.2",
|
|
66
66
|
"@solana/wallet-adapter-base": "^0.9.23",
|
|
67
67
|
"@solana/wallet-adapter-react": "^0.15.35",
|
|
@@ -2163,6 +2163,7 @@ function useLockBodyScroll(initialLocked) {
|
|
|
2163
2163
|
return [locked, setLocked];
|
|
2164
2164
|
}
|
|
2165
2165
|
|
|
2166
|
+
const WarningIcon = () => (jsx("svg", { xmlns: "http://www.w3.org/2000/svg", fill: "none", viewBox: "0 0 24 24", strokeWidth: 1.5, stroke: "currentColor", className: "size-6", width: "16", height: "16", children: jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M12 9v3.75m-9.303 3.376c-.866 1.5.217 3.374 1.948 3.374h14.71c1.73 0 2.813-1.874 1.948-3.374L13.949 3.378c-.866-1.5-3.032-1.5-3.898 0L2.697 16.126ZM12 15.75h.007v.008H12v-.008Z" }) }));
|
|
2166
2167
|
const ExternalLinkIcon = ({ ...props }) => (jsxs("svg", { "aria-hidden": "true", width: "16", height: "16", viewBox: "0 0 16 16", fill: "none", xmlns: "http://www.w3.org/2000/svg", style: {
|
|
2167
2168
|
left: 0,
|
|
2168
2169
|
top: 0,
|
|
@@ -2391,8 +2392,8 @@ function reduceUnhydrated(state, event) {
|
|
|
2391
2392
|
}
|
|
2392
2393
|
function reducePaymentUnpaid(state, event) {
|
|
2393
2394
|
switch (event.type) {
|
|
2394
|
-
case "
|
|
2395
|
-
return
|
|
2395
|
+
case "order_refreshed":
|
|
2396
|
+
return reduceOrderRefreshed(state, event.order);
|
|
2396
2397
|
case "error":
|
|
2397
2398
|
return {
|
|
2398
2399
|
type: "error",
|
|
@@ -2407,14 +2408,8 @@ function reducePaymentUnpaid(state, event) {
|
|
|
2407
2408
|
}
|
|
2408
2409
|
function reducePaymentStarted(state, event) {
|
|
2409
2410
|
switch (event.type) {
|
|
2410
|
-
case "order_refreshed":
|
|
2411
|
-
|
|
2412
|
-
return { type: "payment_started", order: event.order };
|
|
2413
|
-
}
|
|
2414
|
-
case "dest_processed":
|
|
2415
|
-
return event.order.intentStatus === DaimoPayIntentStatus.COMPLETED
|
|
2416
|
-
? { type: "payment_completed", order: event.order }
|
|
2417
|
-
: { type: "payment_bounced", order: event.order };
|
|
2411
|
+
case "order_refreshed":
|
|
2412
|
+
return reduceOrderRefreshed(state, event.order);
|
|
2418
2413
|
case "error":
|
|
2419
2414
|
return {
|
|
2420
2415
|
type: "error",
|
|
@@ -2427,6 +2422,21 @@ function reducePaymentStarted(state, event) {
|
|
|
2427
2422
|
return state;
|
|
2428
2423
|
}
|
|
2429
2424
|
}
|
|
2425
|
+
function reduceOrderRefreshed(state, order) {
|
|
2426
|
+
assert(isHydrated(order), `[PAYMENT_REDUCER] unhydrated`);
|
|
2427
|
+
switch (order.intentStatus) {
|
|
2428
|
+
case DaimoPayIntentStatus.UNPAID:
|
|
2429
|
+
return { type: "payment_unpaid", order };
|
|
2430
|
+
case DaimoPayIntentStatus.STARTED:
|
|
2431
|
+
return { type: "payment_started", order };
|
|
2432
|
+
case DaimoPayIntentStatus.COMPLETED:
|
|
2433
|
+
return { type: "payment_completed", order };
|
|
2434
|
+
case DaimoPayIntentStatus.BOUNCED:
|
|
2435
|
+
return { type: "payment_bounced", order };
|
|
2436
|
+
default:
|
|
2437
|
+
return state;
|
|
2438
|
+
}
|
|
2439
|
+
}
|
|
2430
2440
|
function reduceTerminal(state, event) {
|
|
2431
2441
|
switch (event.type) {
|
|
2432
2442
|
case "reset":
|
|
@@ -2460,10 +2470,9 @@ function waitForPaymentState(store, predicate) {
|
|
|
2460
2470
|
* Will poll the given function at the specified interval. Stops when the
|
|
2461
2471
|
* returned handle is invoked.
|
|
2462
2472
|
*/
|
|
2463
|
-
function startPolling({ key, intervalMs, pollFn, onResult, onError,
|
|
2473
|
+
function startPolling({ key, intervalMs, pollFn, onResult, onError, log = console.log, }) {
|
|
2464
2474
|
let active = true;
|
|
2465
2475
|
let timer;
|
|
2466
|
-
let errorCount = 0;
|
|
2467
2476
|
const stop = () => {
|
|
2468
2477
|
active = false;
|
|
2469
2478
|
clearTimeout(timer);
|
|
@@ -2475,7 +2484,6 @@ function startPolling({ key, intervalMs, pollFn, onResult, onError, maxErrors =
|
|
|
2475
2484
|
const res = await pollFn();
|
|
2476
2485
|
if (!active)
|
|
2477
2486
|
return;
|
|
2478
|
-
errorCount = 0;
|
|
2479
2487
|
log(`[POLL] ${key} success`);
|
|
2480
2488
|
onResult(res);
|
|
2481
2489
|
}
|
|
@@ -2484,11 +2492,6 @@ function startPolling({ key, intervalMs, pollFn, onResult, onError, maxErrors =
|
|
|
2484
2492
|
return;
|
|
2485
2493
|
log(`[POLL] ${key} error: ${e}`);
|
|
2486
2494
|
onError(e);
|
|
2487
|
-
errorCount++;
|
|
2488
|
-
if (errorCount >= maxErrors) {
|
|
2489
|
-
log(`[POLL] ${key} reached max errors (${maxErrors}), giving up`);
|
|
2490
|
-
return stop();
|
|
2491
|
-
}
|
|
2492
2495
|
}
|
|
2493
2496
|
timer = setTimeout(tick, intervalMs);
|
|
2494
2497
|
};
|
|
@@ -2526,7 +2529,7 @@ function attachPaymentEffectHandlers(store, trpc, log) {
|
|
|
2526
2529
|
if (prev.type !== next.type) {
|
|
2527
2530
|
// Start watching for source payment
|
|
2528
2531
|
if (next.type === "payment_unpaid") {
|
|
2529
|
-
|
|
2532
|
+
pollFindPayments(store, trpc, next.order.id);
|
|
2530
2533
|
}
|
|
2531
2534
|
// Refresh the order to watch for destination processing
|
|
2532
2535
|
if (next.type === "payment_started") {
|
|
@@ -2590,22 +2593,19 @@ function attachPaymentEffectHandlers(store, trpc, log) {
|
|
|
2590
2593
|
};
|
|
2591
2594
|
return cleanup;
|
|
2592
2595
|
}
|
|
2593
|
-
async function
|
|
2596
|
+
async function pollFindPayments(store, trpc, orderId) {
|
|
2594
2597
|
const key = `${PollerType.FIND_SOURCE_PAYMENT}:${orderId}`;
|
|
2595
2598
|
const stopPolling = startPolling({
|
|
2596
2599
|
key,
|
|
2597
2600
|
intervalMs: 1_000,
|
|
2598
|
-
pollFn: () => trpc.
|
|
2599
|
-
onResult: (
|
|
2601
|
+
pollFn: () => trpc.findOrderPayments.query({ orderId: orderId.toString() }),
|
|
2602
|
+
onResult: (order) => {
|
|
2600
2603
|
const state = store.getState();
|
|
2601
|
-
// Check that we're still in the payment_unpaid state
|
|
2602
2604
|
if (state.type !== "payment_unpaid") {
|
|
2603
2605
|
stopPolling();
|
|
2606
|
+
return;
|
|
2604
2607
|
}
|
|
2605
|
-
|
|
2606
|
-
stopPolling();
|
|
2607
|
-
store.dispatch({ type: "payment_started", order: state.order });
|
|
2608
|
-
}
|
|
2608
|
+
store.dispatch({ type: "order_refreshed", order });
|
|
2609
2609
|
},
|
|
2610
2610
|
onError: () => { },
|
|
2611
2611
|
});
|
|
@@ -2620,16 +2620,12 @@ async function pollRefreshOrder(store, trpc, orderId) {
|
|
|
2620
2620
|
onResult: (res) => {
|
|
2621
2621
|
const state = store.getState();
|
|
2622
2622
|
// Check that we're still in the payment_started state
|
|
2623
|
-
if (state.type !== "payment_started")
|
|
2623
|
+
if (state.type !== "payment_started") {
|
|
2624
|
+
stopPolling();
|
|
2624
2625
|
return;
|
|
2626
|
+
}
|
|
2625
2627
|
const order = res.order;
|
|
2626
2628
|
store.dispatch({ type: "order_refreshed", order });
|
|
2627
|
-
if (order.intentStatus === "payment_completed" ||
|
|
2628
|
-
order.intentStatus === "payment_bounced") {
|
|
2629
|
-
assert(order.mode === DaimoPayOrderMode.HYDRATED, `[PAYMENT_EFFECTS] order ${order.id} is ${order.intentStatus} but not hydrated`);
|
|
2630
|
-
store.dispatch({ type: "dest_processed", order });
|
|
2631
|
-
stopPolling();
|
|
2632
|
-
}
|
|
2633
2629
|
},
|
|
2634
2630
|
onError: () => { },
|
|
2635
2631
|
});
|
|
@@ -2738,7 +2734,7 @@ async function runHydratePayIdEffects(store, trpc, prev, event) {
|
|
|
2738
2734
|
async function runPayEthereumSourceEffects(store, trpc, prev, event) {
|
|
2739
2735
|
const orderId = prev.order.id;
|
|
2740
2736
|
try {
|
|
2741
|
-
await trpc.processSourcePayment.mutate({
|
|
2737
|
+
const order = await trpc.processSourcePayment.mutate({
|
|
2742
2738
|
orderId: orderId.toString(),
|
|
2743
2739
|
sourceInitiateTxHash: event.paymentTxHash,
|
|
2744
2740
|
sourceChainId: event.sourceChainId,
|
|
@@ -2746,8 +2742,7 @@ async function runPayEthereumSourceEffects(store, trpc, prev, event) {
|
|
|
2746
2742
|
sourceToken: event.sourceToken,
|
|
2747
2743
|
sourceAmount: event.sourceAmount.toString(),
|
|
2748
2744
|
});
|
|
2749
|
-
|
|
2750
|
-
store.dispatch({ type: "payment_started", order: prev.order });
|
|
2745
|
+
store.dispatch({ type: "order_refreshed", order });
|
|
2751
2746
|
}
|
|
2752
2747
|
catch (e) {
|
|
2753
2748
|
store.dispatch({ type: "error", order: prev.order, message: e.message });
|
|
@@ -2756,13 +2751,12 @@ async function runPayEthereumSourceEffects(store, trpc, prev, event) {
|
|
|
2756
2751
|
async function runPaySolanaSourceEffects(store, trpc, prev, event) {
|
|
2757
2752
|
const orderId = prev.order.id;
|
|
2758
2753
|
try {
|
|
2759
|
-
await trpc.processSolanaSourcePayment.mutate({
|
|
2754
|
+
const order = await trpc.processSolanaSourcePayment.mutate({
|
|
2760
2755
|
orderId: orderId.toString(),
|
|
2761
2756
|
startIntentTxHash: event.paymentTxHash,
|
|
2762
2757
|
token: event.sourceToken,
|
|
2763
2758
|
});
|
|
2764
|
-
|
|
2765
|
-
store.dispatch({ type: "payment_started", order: prev.order });
|
|
2759
|
+
store.dispatch({ type: "order_refreshed", order });
|
|
2766
2760
|
}
|
|
2767
2761
|
catch (e) {
|
|
2768
2762
|
store.dispatch({ type: "error", order: prev.order, message: e.message });
|
|
@@ -7696,7 +7690,7 @@ const QRPlaceholder = styled(motion.div) `
|
|
|
7696
7690
|
content: "";
|
|
7697
7691
|
position: absolute;
|
|
7698
7692
|
inset: 0;
|
|
7699
|
-
transform: scale(1.
|
|
7693
|
+
transform: scale(1.7) rotate(45deg);
|
|
7700
7694
|
background-image: linear-gradient(
|
|
7701
7695
|
90deg,
|
|
7702
7696
|
rgba(255, 255, 255, 0) 50%,
|
|
@@ -7739,16 +7733,6 @@ const LogoIcon = styled(motion.div) `
|
|
|
7739
7733
|
: css `
|
|
7740
7734
|
width: 28%;
|
|
7741
7735
|
height: 28%;
|
|
7742
|
-
border-radius: 17px;
|
|
7743
|
-
&:before {
|
|
7744
|
-
pointer-events: none;
|
|
7745
|
-
z-index: 2;
|
|
7746
|
-
content: "";
|
|
7747
|
-
position: absolute;
|
|
7748
|
-
inset: 0;
|
|
7749
|
-
border-radius: inherit;
|
|
7750
|
-
box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.02);
|
|
7751
|
-
}
|
|
7752
7736
|
`}
|
|
7753
7737
|
`;
|
|
7754
7738
|
|
|
@@ -8033,6 +8017,7 @@ const WalletItem = styled.button `
|
|
|
8033
8017
|
text-align: center;
|
|
8034
8018
|
transition: opacity 100ms ease;
|
|
8035
8019
|
opacity: ${(props) => (props.$waiting ? 0.4 : 1)};
|
|
8020
|
+
background: transparent;
|
|
8036
8021
|
`;
|
|
8037
8022
|
const WalletIcon = styled.div `
|
|
8038
8023
|
z-index: 9;
|
|
@@ -10270,9 +10255,10 @@ const SelectDepositAddressAmount = () => {
|
|
|
10270
10255
|
const { paymentState, setRoute, triggerResize } = usePayContext();
|
|
10271
10256
|
const { selectedDepositAddressOption } = paymentState;
|
|
10272
10257
|
const maxUsdLimit = paymentState.getOrderUsdLimit();
|
|
10273
|
-
|
|
10258
|
+
const minUsd = selectedDepositAddressOption?.minimumUsd ?? 0;
|
|
10259
|
+
const minimumMessage = `Minimum ${formatUsd(minUsd, "up")}`;
|
|
10274
10260
|
const [usdInput, setUsdInput] = useState("");
|
|
10275
|
-
const [message, setMessage] = useState(
|
|
10261
|
+
const [message, setMessage] = useState(minimumMessage);
|
|
10276
10262
|
const [continueDisabled, setContinueDisabled] = useState(true);
|
|
10277
10263
|
useEffect(() => {
|
|
10278
10264
|
triggerResize();
|
|
@@ -10289,7 +10275,7 @@ const SelectDepositAddressAmount = () => {
|
|
|
10289
10275
|
setMessage(`Maximum ${formatUsd(maxUsdLimit)}`);
|
|
10290
10276
|
}
|
|
10291
10277
|
else {
|
|
10292
|
-
setMessage(
|
|
10278
|
+
setMessage(minimumMessage);
|
|
10293
10279
|
}
|
|
10294
10280
|
const usd = Number(sanitizeNumber(value));
|
|
10295
10281
|
setContinueDisabled(usd <= 0 || usd > maxUsdLimit);
|
|
@@ -10612,6 +10598,8 @@ function SelectAnotherMethodButton() {
|
|
|
10612
10598
|
|
|
10613
10599
|
const SelectDepositAddressChain = () => {
|
|
10614
10600
|
const { setRoute, paymentState } = usePayContext();
|
|
10601
|
+
const pay = useDaimoPay();
|
|
10602
|
+
const { order } = pay;
|
|
10615
10603
|
const { isDepositFlow, setSelectedDepositAddressOption, depositAddressOptions, } = paymentState;
|
|
10616
10604
|
return (jsxs(PageContent, { children: [jsx(OrderHeader, { minified: true }), !depositAddressOptions.loading &&
|
|
10617
10605
|
depositAddressOptions.options?.length === 0 && (jsxs(ModalContent, { style: {
|
|
@@ -10625,6 +10613,9 @@ const SelectDepositAddressChain = () => {
|
|
|
10625
10613
|
id: option.id,
|
|
10626
10614
|
title: option.id,
|
|
10627
10615
|
icons: [option.logoURI],
|
|
10616
|
+
disabled: option.minimumUsd > 0 &&
|
|
10617
|
+
order?.mode === DaimoPayOrderMode.HYDRATED &&
|
|
10618
|
+
order.usdValue < option.minimumUsd,
|
|
10628
10619
|
onClick: () => {
|
|
10629
10620
|
setSelectedDepositAddressOption(option);
|
|
10630
10621
|
const meta = { event: "click-option", option: option.id };
|
|
@@ -10891,26 +10882,52 @@ function getDepositAddressOption(setRoute) {
|
|
|
10891
10882
|
};
|
|
10892
10883
|
}
|
|
10893
10884
|
|
|
10894
|
-
const TokenChainLogo = ({ token, size = 32, offset, }) => {
|
|
10895
|
-
const
|
|
10896
|
-
|
|
10897
|
-
return (jsxs(TokenChainContainer, { style: s1, children: [jsx("img", { src: token.logoURI, alt: token.symbol, style: { borderRadius: 9999 } }), jsx(ChainContainer$1, { style: s2, children: chainToLogo[token.chainId] })] }));
|
|
10885
|
+
const TokenChainLogo = ({ token, size = 32, offset = 0, }) => {
|
|
10886
|
+
const chainLogoSize = Math.round((size * 30) / 64);
|
|
10887
|
+
return (jsxs(TokenChainContainer, { "$size": size, children: [jsx(TokenImage, { src: token.logoURI, alt: token.symbol, "$size": size }), jsx(ChainContainer$1, { "$size": chainLogoSize, "$offset": offset, children: chainToLogo[token.chainId] })] }));
|
|
10898
10888
|
};
|
|
10899
10889
|
const TokenChainContainer = styled(motion.div) `
|
|
10900
|
-
|
|
10901
|
-
|
|
10890
|
+
position: relative;
|
|
10891
|
+
width: ${(props) => props.$size}px;
|
|
10892
|
+
height: ${(props) => props.$size}px;
|
|
10893
|
+
display: flex;
|
|
10894
|
+
align-items: center;
|
|
10895
|
+
justify-content: center;
|
|
10896
|
+
`;
|
|
10897
|
+
const TokenImage = styled.img `
|
|
10898
|
+
width: ${(props) => props.$size}px;
|
|
10899
|
+
height: ${(props) => props.$size}px;
|
|
10900
|
+
border-radius: 50%;
|
|
10901
|
+
object-fit: cover;
|
|
10902
|
+
transition: transform 0.2s ease;
|
|
10903
|
+
|
|
10904
|
+
${(props) => props.$showBorder &&
|
|
10905
|
+
`
|
|
10906
|
+
border: 2px solid var(--ck-body-background, #fff);
|
|
10907
|
+
`}
|
|
10902
10908
|
`;
|
|
10903
10909
|
const ChainContainer$1 = styled(motion.div) `
|
|
10904
10910
|
position: absolute;
|
|
10905
|
-
|
|
10906
|
-
|
|
10911
|
+
width: ${(props) => props.$size}px;
|
|
10912
|
+
height: ${(props) => props.$size}px;
|
|
10913
|
+
min-width: ${(props) => props.$size}px;
|
|
10914
|
+
min-height: ${(props) => props.$size}px;
|
|
10907
10915
|
bottom: 0px;
|
|
10908
|
-
right:
|
|
10916
|
+
right: ${(props) => props.$offset}px;
|
|
10917
|
+
border-radius: 50%;
|
|
10918
|
+
aspect-ratio: 1 / 1;
|
|
10919
|
+
overflow: hidden;
|
|
10920
|
+
background: ${(props) => props.$showBorder ? "var(--ck-body-background, #fff)" : "transparent"};
|
|
10921
|
+
display: flex;
|
|
10922
|
+
align-items: center;
|
|
10923
|
+
justify-content: center;
|
|
10924
|
+
flex-shrink: 0;
|
|
10909
10925
|
|
|
10910
10926
|
svg {
|
|
10911
|
-
position: absolute;
|
|
10912
10927
|
width: 100%;
|
|
10913
10928
|
height: 100%;
|
|
10929
|
+
border-radius: 50%;
|
|
10930
|
+
flex-shrink: 0;
|
|
10914
10931
|
}
|
|
10915
10932
|
`;
|
|
10916
10933
|
|
|
@@ -11309,7 +11326,8 @@ const CircleTimer = ({ total, size = 24, stroke = 3, currentTime, onTimeChange,
|
|
|
11309
11326
|
return () => clearInterval(id);
|
|
11310
11327
|
}, [target, onTimeChange]);
|
|
11311
11328
|
const ratio = Math.round((left * 100) / total); // 0-100
|
|
11312
|
-
|
|
11329
|
+
// Ensure stroke stays within viewBox: use floor to be conservative
|
|
11330
|
+
const radius = Math.floor((size - stroke) / 2);
|
|
11313
11331
|
const circumference = Math.round((2 * 314 * radius) / 100); // 2πr, π≈3.14
|
|
11314
11332
|
const dashoffset = Math.round((circumference * (100 - ratio)) / 100);
|
|
11315
11333
|
// colour transition: green → orange → red
|
|
@@ -11332,49 +11350,91 @@ function WaitingDepositAddress() {
|
|
|
11332
11350
|
const context = usePayContext();
|
|
11333
11351
|
const { triggerResize, paymentState } = context;
|
|
11334
11352
|
const { payWithDepositAddress, selectedDepositAddressOption } = paymentState;
|
|
11335
|
-
const
|
|
11353
|
+
const { order } = useDaimoPay();
|
|
11354
|
+
const [depAddr, setDepAddr] = useState();
|
|
11336
11355
|
const [failed, setFailed] = useState(false);
|
|
11356
|
+
// If we selected a deposit address option, generate the address...
|
|
11337
11357
|
const generateDepositAddress = () => {
|
|
11338
|
-
if (
|
|
11339
|
-
|
|
11340
|
-
|
|
11341
|
-
if (
|
|
11342
|
-
|
|
11343
|
-
|
|
11344
|
-
|
|
11345
|
-
|
|
11358
|
+
if (selectedDepositAddressOption == null) {
|
|
11359
|
+
if (order == null || !isHydrated(order))
|
|
11360
|
+
return;
|
|
11361
|
+
if (order.sourceTokenAmount == null)
|
|
11362
|
+
return;
|
|
11363
|
+
// Pay underpaid order
|
|
11364
|
+
const taPaid = order.sourceTokenAmount;
|
|
11365
|
+
const usdPaid = taPaid.usd; // TODO: get usdPaid directly from the order
|
|
11366
|
+
const usdToPay = Math.max(order.usdValue - usdPaid, 0.01);
|
|
11367
|
+
const dispDecimals = taPaid.token.displayDecimals;
|
|
11368
|
+
const unitsToPay = (usdToPay / taPaid.token.usd).toFixed(dispDecimals);
|
|
11369
|
+
const unitsPaid = (Number(taPaid.amount) /
|
|
11370
|
+
10 ** taPaid.token.decimals).toFixed(dispDecimals);
|
|
11371
|
+
// Hack to always show a <= 60 minute countdown
|
|
11372
|
+
let expirationS = (order.createdAt ?? 0) + 59.5 * 60;
|
|
11373
|
+
if (order.expirationTs != null &&
|
|
11374
|
+
Number(order.expirationTs) < expirationS) {
|
|
11375
|
+
expirationS = Number(order.expirationTs);
|
|
11376
|
+
}
|
|
11377
|
+
setDepAddr({
|
|
11378
|
+
address: order.intentAddr,
|
|
11379
|
+
amount: unitsToPay,
|
|
11380
|
+
underpayment: { unitsPaid, coin: taPaid.token.symbol },
|
|
11381
|
+
coins: `${taPaid.token.symbol} on ${getChainName(taPaid.token.chainId)}`,
|
|
11382
|
+
expirationS: expirationS,
|
|
11383
|
+
uri: order.intentAddr,
|
|
11384
|
+
displayToken: taPaid.token,
|
|
11385
|
+
logoURI: "", // Not needed for underpaid orders
|
|
11386
|
+
});
|
|
11387
|
+
}
|
|
11388
|
+
else {
|
|
11389
|
+
payWithDepositAddress(selectedDepositAddressOption.id).then((details) => {
|
|
11390
|
+
if (details) {
|
|
11391
|
+
setDepAddr({
|
|
11392
|
+
address: details.address,
|
|
11393
|
+
amount: details.amount,
|
|
11394
|
+
coins: details.suffix,
|
|
11395
|
+
expirationS: details.expirationS,
|
|
11396
|
+
uri: details.uri,
|
|
11397
|
+
displayToken: getDisplayToken(selectedDepositAddressOption),
|
|
11398
|
+
logoURI: selectedDepositAddressOption.logoURI,
|
|
11399
|
+
});
|
|
11400
|
+
}
|
|
11401
|
+
else {
|
|
11402
|
+
setFailed(true);
|
|
11403
|
+
}
|
|
11404
|
+
});
|
|
11405
|
+
}
|
|
11346
11406
|
};
|
|
11347
|
-
// TODO: load payment status, show underpayment
|
|
11348
11407
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
11349
11408
|
useEffect(generateDepositAddress, [selectedDepositAddressOption]);
|
|
11350
11409
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
11351
|
-
useEffect(triggerResize, [
|
|
11352
|
-
return (jsx(PageContent, { children:
|
|
11410
|
+
useEffect(triggerResize, [depAddr, failed]);
|
|
11411
|
+
return (jsx(PageContent, { children: failed ? (selectedDepositAddressOption && (jsx(DepositFailed, { name: selectedDepositAddressOption.id }))) : (jsx(DepositAddressInfo, { depAddr: depAddr, refresh: generateDepositAddress, triggerResize: triggerResize })) }));
|
|
11353
11412
|
}
|
|
11354
|
-
function DepositAddressInfo({
|
|
11413
|
+
function DepositAddressInfo({ depAddr, refresh, triggerResize, }) {
|
|
11355
11414
|
const { isMobile } = useIsMobile();
|
|
11356
|
-
const [remainingS, totalS] = useCountdown(
|
|
11357
|
-
const isExpired =
|
|
11415
|
+
const [remainingS, totalS] = useCountdown(depAddr?.expirationS);
|
|
11416
|
+
const isExpired = depAddr?.expirationS != null && remainingS === 0;
|
|
11358
11417
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
11359
11418
|
useEffect(triggerResize, [isExpired]);
|
|
11360
|
-
const
|
|
11361
|
-
|
|
11362
|
-
|
|
11363
|
-
|
|
11364
|
-
|
|
11365
|
-
|
|
11366
|
-
|
|
11367
|
-
|
|
11368
|
-
|
|
11369
|
-
|
|
11370
|
-
|
|
11371
|
-
|
|
11372
|
-
|
|
11373
|
-
|
|
11374
|
-
|
|
11375
|
-
|
|
11376
|
-
|
|
11377
|
-
|
|
11419
|
+
const logoOffset = isMobile ? 4 : 0;
|
|
11420
|
+
const logoElement = depAddr?.displayToken ? (jsx(TokenChainLogo, { token: depAddr.displayToken, size: 64, offset: logoOffset })) : (jsx("img", { src: depAddr?.logoURI, width: "64px", height: "64px" }));
|
|
11421
|
+
return (jsxs(ModalContent, { children: [isExpired ? (jsx(LogoRow, { children: jsx(Button, { onClick: refresh, style: { width: 128 }, children: "Refresh" }) })) : isMobile ? (jsx(LogoRow, { children: jsx(LogoWrap, { children: logoElement }) })) : (jsx(QRWrap, { children: jsx(CustomQRCode, { value: depAddr?.uri, contentPadding: 24, size: 200, image: logoElement }) })), jsx(CopyableInfo, { depAddr: depAddr, remainingS: remainingS, totalS: totalS })] }));
|
|
11422
|
+
}
|
|
11423
|
+
function getDisplayToken(meta) {
|
|
11424
|
+
switch (meta.id) {
|
|
11425
|
+
case DepositAddressPaymentOptions.OP_MAINNET:
|
|
11426
|
+
return optimismUSDC;
|
|
11427
|
+
case DepositAddressPaymentOptions.ARBITRUM:
|
|
11428
|
+
return arbitrumUSDC;
|
|
11429
|
+
case DepositAddressPaymentOptions.BASE:
|
|
11430
|
+
return baseUSDC;
|
|
11431
|
+
case DepositAddressPaymentOptions.POLYGON:
|
|
11432
|
+
return polygonUSDC;
|
|
11433
|
+
case DepositAddressPaymentOptions.ETH_L1:
|
|
11434
|
+
return ethereumUSDC;
|
|
11435
|
+
default:
|
|
11436
|
+
return null;
|
|
11437
|
+
}
|
|
11378
11438
|
}
|
|
11379
11439
|
const LogoWrap = styled.div `
|
|
11380
11440
|
position: relative;
|
|
@@ -11393,11 +11453,29 @@ const QRWrap = styled.div `
|
|
|
11393
11453
|
margin: 0 auto;
|
|
11394
11454
|
width: 280px;
|
|
11395
11455
|
`;
|
|
11396
|
-
function CopyableInfo({
|
|
11397
|
-
const
|
|
11398
|
-
const isExpired =
|
|
11399
|
-
return (jsxs(CopyableInfoWrapper, { children: [jsx(CopyRowOrThrobber, { title: "Send Exactly", value:
|
|
11456
|
+
function CopyableInfo({ depAddr, remainingS, totalS, }) {
|
|
11457
|
+
const underpayment = depAddr?.underpayment;
|
|
11458
|
+
const isExpired = depAddr?.expirationS != null && remainingS === 0;
|
|
11459
|
+
return (jsxs(CopyableInfoWrapper, { children: [underpayment && jsx(UnderpaymentInfo, { underpayment: underpayment }), jsx(CopyRowOrThrobber, { title: "Send Exactly", value: depAddr?.amount, smallText: depAddr?.coins, disabled: isExpired }), jsx(CopyRowOrThrobber, { title: "Receiving Address", value: depAddr?.address, valueText: depAddr?.address && getAddressContraction(depAddr.address), disabled: isExpired }), jsx(CountdownWrap, { children: jsx(CountdownTimer, { remainingS: remainingS, totalS: totalS }) })] }));
|
|
11400
11460
|
}
|
|
11461
|
+
function UnderpaymentInfo({ underpayment }) {
|
|
11462
|
+
return (jsxs(UnderpaymentWrapper, { children: [jsxs(UnderpaymentHeader, { children: [jsx(WarningIcon, {}), jsxs("span", { children: ["Received ", underpayment.unitsPaid, " ", underpayment.coin] })] }), jsx(SmallText, { children: "Finish by sending the extra amount below." })] }));
|
|
11463
|
+
}
|
|
11464
|
+
const UnderpaymentWrapper = styled.div `
|
|
11465
|
+
background: var(--ck-body-background-tertiary);
|
|
11466
|
+
border-radius: 8px;
|
|
11467
|
+
padding: 16px;
|
|
11468
|
+
margin: 0 4px 16px 4px;
|
|
11469
|
+
margin-bottom: 16px;
|
|
11470
|
+
`;
|
|
11471
|
+
const UnderpaymentHeader = styled.div `
|
|
11472
|
+
font-weight: 500;
|
|
11473
|
+
display: flex;
|
|
11474
|
+
justify-content: center;
|
|
11475
|
+
align-items: flex-end;
|
|
11476
|
+
gap: 8px;
|
|
11477
|
+
margin-bottom: 8px;
|
|
11478
|
+
`;
|
|
11401
11479
|
const CopyableInfoWrapper = styled.div `
|
|
11402
11480
|
display: flex;
|
|
11403
11481
|
flex-direction: column;
|
|
@@ -11441,8 +11519,8 @@ const formatTime = (sec) => {
|
|
|
11441
11519
|
const s = `${sec % 60}`.padStart(2, "0");
|
|
11442
11520
|
return `${m}:${s}`;
|
|
11443
11521
|
};
|
|
11444
|
-
function DepositFailed({
|
|
11445
|
-
return (jsxs(ModalContent, { style: { marginLeft: 24, marginRight: 24 }, children: [jsxs(ModalH1, { children: [
|
|
11522
|
+
function DepositFailed({ name }) {
|
|
11523
|
+
return (jsxs(ModalContent, { style: { marginLeft: 24, marginRight: 24 }, children: [jsxs(ModalH1, { children: [name, " unavailable"] }), jsxs(ModalBody, { children: ["We're unable to process ", name, " payments at this time. Please select another payment method."] }), jsx(SelectAnotherMethodButton, {})] }));
|
|
11446
11524
|
}
|
|
11447
11525
|
const CopyRow = styled.button `
|
|
11448
11526
|
display: block;
|
|
@@ -11451,6 +11529,7 @@ const CopyRow = styled.button `
|
|
|
11451
11529
|
padding: 8px 16px;
|
|
11452
11530
|
|
|
11453
11531
|
cursor: pointer;
|
|
11532
|
+
background-color: var(--ck-body-background);
|
|
11454
11533
|
|
|
11455
11534
|
display: flex;
|
|
11456
11535
|
align-items: center;
|
|
@@ -11489,6 +11568,16 @@ const ValueContainer = styled.div `
|
|
|
11489
11568
|
`;
|
|
11490
11569
|
const SmallText = styled.span `
|
|
11491
11570
|
font-size: 14px;
|
|
11571
|
+
color: var(--ck-primary-button-color);
|
|
11572
|
+
`;
|
|
11573
|
+
const ValueText = styled.span `
|
|
11574
|
+
font-size: 14px;
|
|
11575
|
+
font-weight: 600;
|
|
11576
|
+
color: var(--ck-primary-button-color);
|
|
11577
|
+
`;
|
|
11578
|
+
const LabelText = styled(ModalBody) `
|
|
11579
|
+
margin: 0;
|
|
11580
|
+
text-align: left;
|
|
11492
11581
|
`;
|
|
11493
11582
|
const pulse = keyframes `
|
|
11494
11583
|
0% {
|
|
@@ -11523,10 +11612,10 @@ function CopyRowOrThrobber({ title, value, valueText, smallText, disabled, }) {
|
|
|
11523
11612
|
setTimeout(() => setCopied(false), 1000);
|
|
11524
11613
|
};
|
|
11525
11614
|
if (!value) {
|
|
11526
|
-
return (jsxs(CopyRow, { children: [jsx(LabelRow, { children: jsx(
|
|
11615
|
+
return (jsxs(CopyRow, { children: [jsx(LabelRow, { children: jsx(LabelText, { children: title }) }), jsx(MainRow, { children: jsx(Skeleton, {}) })] }));
|
|
11527
11616
|
}
|
|
11528
11617
|
const displayValue = valueText || value;
|
|
11529
|
-
return (jsxs(CopyRow, { as: "button", onClick: handleCopy, disabled: disabled, children: [jsxs("div", { children: [jsx(LabelRow, { children: jsx(
|
|
11618
|
+
return (jsxs(CopyRow, { as: "button", onClick: handleCopy, disabled: disabled, children: [jsxs("div", { children: [jsx(LabelRow, { children: jsx(LabelText, { children: title }) }), jsx(MainRow, { children: jsxs(ValueContainer, { children: [jsx(ValueText, { children: displayValue }), smallText && jsx(SmallText, { children: smallText })] }) })] }), jsx(CopyIconWrap, { children: jsx(CopyToClipboardIcon, { copied: copied, dark: true }) })] }));
|
|
11530
11619
|
}
|
|
11531
11620
|
const CopyIconWrap = styled.div `
|
|
11532
11621
|
--color: var(--ck-copytoclipboard-stroke);
|
|
@@ -11983,7 +12072,12 @@ function useExternalPaymentOptions({ trpc, filterIds, platform, usdRequired, mod
|
|
|
11983
12072
|
if (usdRequired != null && mode != null) {
|
|
11984
12073
|
refreshExternalPaymentOptions(usdRequired, mode);
|
|
11985
12074
|
}
|
|
11986
|
-
|
|
12075
|
+
// TODO: this is an ugly way to handle polling/refresh
|
|
12076
|
+
// Notice the load-bearing JSON.stringify() to prevent a visible infinite
|
|
12077
|
+
// refresh glitch on the SelectMethod screen. Replace this useEffect().
|
|
12078
|
+
//
|
|
12079
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
12080
|
+
}, [usdRequired, JSON.stringify(filterIds), platform, mode, trpc]);
|
|
11987
12081
|
return { options, loading };
|
|
11988
12082
|
}
|
|
11989
12083
|
|
|
@@ -12709,13 +12803,22 @@ const DaimoPayUIProvider = ({ children, theme = "auto", mode = "auto", customThe
|
|
|
12709
12803
|
}
|
|
12710
12804
|
};
|
|
12711
12805
|
// Watch when the order gets paid and navigate to confirmation
|
|
12806
|
+
// ...if underpaid, go to the deposit addr screen, let the user finish paying.
|
|
12807
|
+
const isUnderpaid = pay.order?.mode === DaimoPayOrderMode.HYDRATED &&
|
|
12808
|
+
pay.order.sourceStatus === DaimoPayOrderStatusSource.WAITING_PAYMENT &&
|
|
12809
|
+
pay.order.sourceTokenAmount != null;
|
|
12712
12810
|
useEffect(() => {
|
|
12713
12811
|
if (pay.paymentState === "payment_started" ||
|
|
12714
12812
|
pay.paymentState === "payment_completed" ||
|
|
12715
12813
|
pay.paymentState === "payment_bounced") {
|
|
12716
12814
|
setRoute(ROUTES.CONFIRMATION, { event: "payment-started" });
|
|
12717
12815
|
}
|
|
12718
|
-
|
|
12816
|
+
else if (isUnderpaid) {
|
|
12817
|
+
paymentState.setSelectedDepositAddressOption(undefined);
|
|
12818
|
+
setRoute(ROUTES.WAITING_DEPOSIT_ADDRESS);
|
|
12819
|
+
}
|
|
12820
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
12821
|
+
}, [pay.paymentState, setRoute, isUnderpaid]);
|
|
12719
12822
|
const value = {
|
|
12720
12823
|
theme: ckTheme,
|
|
12721
12824
|
setTheme,
|