@daimo/pay 1.7.4 → 1.7.6

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.d.ts CHANGED
@@ -68,6 +68,7 @@ type DaimoPayContextOptions = {
68
68
  /** Modal UI options, set on the pay button triggering that modal. */
69
69
  type DaimoPayModalOptions = {
70
70
  closeOnSuccess?: boolean;
71
+ resetOnSuccess?: boolean;
71
72
  };
72
73
  /** Additional payment options. Onchain payments are always enabled. */
73
74
  type PaymentOption = "Daimo" | "Coinbase" | "Binance" | "RampNetwork" | "Solana" | "ExternalChains" | "Lemon";
@@ -256,6 +257,8 @@ type PayButtonCommonProps = PayButtonPaymentProps & {
256
257
  closeOnSuccess?: boolean;
257
258
  /** Open the modal by default. */
258
259
  defaultOpen?: boolean;
260
+ /** Reset the payment after a successful payment. */
261
+ resetOnSuccess?: boolean;
259
262
  /** Custom message to display on confirmation page. */
260
263
  confirmationMessage?: string;
261
264
  /** Redirect URL to return to the app. E.g. after Coinbase, Binance, RampNetwork. */
@@ -303,10 +306,9 @@ declare namespace DaimoPayButtonCustom {
303
306
  * to the payment's configured refund address on the destination chain.
304
307
  */
305
308
  declare function useDaimoPayStatus(): {
306
- paymentId?: string;
307
- status?: DaimoPayIntentStatus;
308
- reset: () => void;
309
- };
309
+ paymentId: string;
310
+ status: DaimoPayIntentStatus;
311
+ } | undefined;
310
312
 
311
313
  /** Icon for an EVM chain, given chain ID. No ID shows a loading spinner. */
312
314
  declare const Chain: React$1.FC<{
@@ -479,8 +481,6 @@ interface PaymentState {
479
481
  resetOrder: () => void;
480
482
  daimoPayOrder: DaimoPayOrder | undefined;
481
483
  isDepositFlow: boolean;
482
- modalOptions: DaimoPayModalOptions;
483
- setModalOptions: (modalOptions: DaimoPayModalOptions) => void;
484
484
  paymentWaitingMessage: string | undefined;
485
485
  externalPaymentOptions: ReturnType<typeof useExternalPaymentOptions>;
486
486
  showSolanaPaymentMethod: boolean;
@@ -503,10 +503,6 @@ interface PaymentState {
503
503
  payWithDepositAddress: (option: DepositAddressPaymentOptions) => Promise<DepositAddressPaymentOptionData | null>;
504
504
  payWithSolanaToken: (inputToken: SolanaPublicKey) => Promise<string | undefined>;
505
505
  refreshOrder: () => Promise<void>;
506
- onSuccess: (args: {
507
- txHash: string;
508
- txURL?: string;
509
- }) => void;
510
506
  senderEnsName: string | undefined;
511
507
  }
512
508
 
@@ -555,6 +551,8 @@ type PayContextValue = {
555
551
  paymentState: PaymentState;
556
552
  /** TRPC API client. Internal use only. */
557
553
  trpc: any;
554
+ /** Callback to call when the payment is successful. */
555
+ onSuccess: () => void;
558
556
  /** Custom message to display on confirmation page. */
559
557
  confirmationMessage?: string;
560
558
  setConfirmationMessage: React$1.Dispatch<React$1.SetStateAction<string | undefined>>;
package/build/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
2
2
  import { ExternalPaymentOptions, assert, assertNotNull, debugJson, supportedChains, ethereum, isCCTPV1Chain, getOrderDestChainId, readDaimoPayOrderID, getChainName, arbitrum as arbitrum$1, base as base$2, blast as blast$1, bsc as bsc$1, linea as linea$1, mantle as mantle$1, optimism as optimism$1, polygon as polygon$1, worldchain as worldchain$1, getAddressContraction, writeDaimoPayOrderID, DaimoPayOrderMode, DaimoPayOrderStatusDest, getChainExplorerTxUrl, DaimoPayIntentStatus, retryBackoff, DaimoPayOrderStatusSource, getDaimoPayOrderView } from '@daimo/pay-common';
3
3
  import { Buffer } from 'buffer';
4
- import React, { useState, useEffect, createContext, useCallback, useRef, useLayoutEffect, useMemo, createElement } from 'react';
4
+ import React, { useState, useEffect, createContext, useRef, useCallback, useLayoutEffect, useMemo, createElement } from 'react';
5
5
  import styled$1, { css, keyframes, ThemeProvider } from 'styled-components';
6
6
  import { http, useConfig, useAccountEffect, useWriteContract, useSendTransaction, useAccount, useEnsName, useConnectors as useConnectors$1, useSwitchChain, useConnect as useConnect$1, useDisconnect, useChainId, WagmiContext, createConfig, useEnsAddress, useEnsAvatar } from 'wagmi';
7
7
  import { mainnet, base as base$1, polygon, optimism, arbitrum, linea, bsc, sepolia, baseSepolia, worldchain, blast, mantle } from 'wagmi/chains';
@@ -22,7 +22,7 @@ import { WalletSignTransactionError, WalletSendTransactionError } from '@solana/
22
22
  import { normalize } from 'viem/ens';
23
23
 
24
24
  var name = "@daimo/pay";
25
- var version = "1.7.4";
25
+ var version = "1.7.6";
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.7.4",
64
+ "@daimo/pay-common": "1.7.6",
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",
@@ -94,6 +94,7 @@ var devDependencies = {
94
94
  "@types/react-dom": "^18.2.18",
95
95
  "@types/rollup-plugin-peer-deps-external": "^2.2.5",
96
96
  "@types/styled-components": "^5.1.25",
97
+ "eslint-plugin-react-hooks": "^5.2.0",
97
98
  rollup: "^3.29.5",
98
99
  "rollup-plugin-dts": "^6.1.1",
99
100
  "rollup-plugin-peer-deps-external": "^2.2.4",
@@ -244,14 +245,6 @@ const defaultConfig = ({ appName = "Daimo Pay", appIcon, appDescription, appUrl,
244
245
  return config;
245
246
  };
246
247
 
247
- /** Determines whether the current wagmi configuration supports a given chain. */
248
- function useChainIsSupported(chainId) {
249
- const { chains } = useConfig();
250
- if (!chainId)
251
- return false;
252
- return chains.some((x) => x.id === chainId);
253
- }
254
-
255
248
  /** Returns currently configured wagmi chains. */
256
249
  function useChains() {
257
250
  const wagmi = useConfig();
@@ -543,6 +536,7 @@ const walletConfigs = {
543
536
  },
544
537
  "metaMask, metaMask-io, io.metamask, io.metamask.mobile, metaMaskSDK": {
545
538
  name: "MetaMask",
539
+ shortName: "MetaMask",
546
540
  icon: jsx(Logos$1.MetaMask, {}),
547
541
  iconConnector: jsx(Logos$1.MetaMask, {}),
548
542
  iconShouldShrink: true,
@@ -843,47 +837,6 @@ function extractWcWalletFromProvider(p, log) {
843
837
  return wallet;
844
838
  }
845
839
 
846
- function useGoogleFont(font) {
847
- useEffect(() => {
848
- if (!font)
849
- return;
850
- font = font.replace(/ /g, "+");
851
- const googleapis = document.createElement("link");
852
- googleapis.href = `https://fonts.googleapis.com`;
853
- googleapis.rel = "preconnect";
854
- const gstatic = document.createElement("link");
855
- gstatic.href = `https://fonts.gstatic.com`;
856
- gstatic.rel = "preconnect";
857
- gstatic.crossOrigin = "true";
858
- const link = document.createElement("link");
859
- link.href = `https://fonts.googleapis.com/css2?family=${font}:wght@400;500;600&display=swap`;
860
- link.rel = "stylesheet";
861
- document.head.appendChild(googleapis);
862
- document.head.appendChild(gstatic);
863
- document.head.appendChild(link);
864
- return () => {
865
- try {
866
- document.head.removeChild(googleapis);
867
- document.head.removeChild(gstatic);
868
- document.head.removeChild(link);
869
- }
870
- catch { }
871
- };
872
- }, [font]);
873
- }
874
- // TODO: This could be dynamic if theming wasn't set up as css variables
875
- function useThemeFont(theme) {
876
- const themeFonts = {
877
- web95: "Lato",
878
- retro: "Nunito",
879
- midnight: "Inter",
880
- minimal: "Inter",
881
- rounded: "Nunito",
882
- };
883
- const font = themeFonts[theme] ?? null;
884
- useGoogleFont(font ?? "");
885
- }
886
-
887
840
  /** Daimo Pay internal context. */
888
841
  const usePayContext = () => {
889
842
  const context = React.useContext(PayContext);
@@ -1175,7 +1128,7 @@ function useWalletPaymentOptions({ trpc, address, usdRequired, destChainId, pref
1175
1128
  };
1176
1129
  }
1177
1130
 
1178
- function usePaymentState({ trpc, daimoPayOrder, setDaimoPayOrder, setOpen, log, redirectReturnUrl, }) {
1131
+ function usePaymentState({ trpc, lockPayParams, daimoPayOrder, setDaimoPayOrder, setRoute, log, redirectReturnUrl, }) {
1179
1132
  // Browser state.
1180
1133
  const [platform, setPlatform] = useState();
1181
1134
  useEffect(() => {
@@ -1198,12 +1151,14 @@ function usePaymentState({ trpc, daimoPayOrder, setDaimoPayOrder, setOpen, log,
1198
1151
  paymentOptions.includes(ExternalPaymentOptions.Solana)) &&
1199
1152
  daimoPayOrder != null &&
1200
1153
  isCCTPV1Chain(getOrderDestChainId(daimoPayOrder));
1201
- // Daimo Pay order state.
1154
+ // Refs the survive re-renders and stores any updated param values while
1155
+ // lockPayParams is true
1156
+ const latestPayParamsRef = useRef();
1157
+ const latestPayIdRef = useRef();
1158
+ // Current pay params to do processing off of
1202
1159
  const [payParams, setPayParamsState] = useState();
1203
1160
  const [paymentWaitingMessage, setPaymentWaitingMessage] = useState();
1204
1161
  const [isDepositFlow, setIsDepositFlow] = useState(false);
1205
- // Payment UI config.
1206
- const [modalOptions, setModalOptions] = useState({});
1207
1162
  // UI state. Selection for external payment (Binance, etc) vs wallet payment.
1208
1163
  const externalPaymentOptions = useExternalPaymentOptions({
1209
1164
  trpc,
@@ -1341,7 +1296,7 @@ function usePaymentState({ trpc, daimoPayOrder, setDaimoPayOrder, setOpen, log,
1341
1296
  else {
1342
1297
  log(`[CHECKOUT] IGNORING refreshOrder, wrong ID: ${order.id} vs ${daimoPayOrder.id}`);
1343
1298
  }
1344
- }, [daimoPayOrder?.id]);
1299
+ }, [daimoPayOrder, trpc, setDaimoPayOrder, log]);
1345
1300
  /** User picked a different deposit amount. */
1346
1301
  const setChosenUsd = (usd) => {
1347
1302
  assert(!!daimoPayOrder, "[SET CHOSEN USD] daimoPayOrder cannot be null");
@@ -1361,7 +1316,8 @@ function usePaymentState({ trpc, daimoPayOrder, setDaimoPayOrder, setOpen, log,
1361
1316
  });
1362
1317
  };
1363
1318
  const setPayId = useCallback(async (payId) => {
1364
- if (!payId)
1319
+ latestPayIdRef.current = payId;
1320
+ if (lockPayParams || !payId)
1365
1321
  return;
1366
1322
  const id = readDaimoPayOrderID(payId).toString();
1367
1323
  if (daimoPayOrder && BigInt(id) == daimoPayOrder.id) {
@@ -1375,15 +1331,19 @@ function usePaymentState({ trpc, daimoPayOrder, setDaimoPayOrder, setOpen, log,
1375
1331
  }
1376
1332
  log(`[CHECKOUT] setPayId: fetched order: ${JSON.stringify(order)}`);
1377
1333
  setDaimoPayOrder(order);
1378
- }, [daimoPayOrder]);
1334
+ }, [daimoPayOrder, lockPayParams, trpc, log, setDaimoPayOrder]);
1379
1335
  /** Called whenever params change. */
1380
1336
  const setPayParams = async (payParams) => {
1337
+ latestPayParamsRef.current = payParams;
1338
+ if (lockPayParams)
1339
+ return;
1381
1340
  assert(payParams != null, "[SET PAY PARAMS] payParams cannot be null");
1341
+ console.log("[SET PAY PARAMS] setting payParams");
1382
1342
  setPayParamsState(payParams);
1383
1343
  setIsDepositFlow(payParams.toUnits == null);
1384
1344
  generatePreviewOrder(payParams);
1385
1345
  };
1386
- const generatePreviewOrder = async (payParams) => {
1346
+ const generatePreviewOrder = useCallback(async (payParams) => {
1387
1347
  // toUnits is undefined if and only if we're in deposit flow.
1388
1348
  // Set dummy value for deposit flow, since user can edit the amount.
1389
1349
  const toUnits = payParams.toUnits == null ? "0" : payParams.toUnits;
@@ -1411,16 +1371,28 @@ function usePaymentState({ trpc, daimoPayOrder, setDaimoPayOrder, setOpen, log,
1411
1371
  });
1412
1372
  log(`[CHECKOUT] generated preview: ${JSON.stringify(orderPreview)}`);
1413
1373
  setDaimoPayOrder(orderPreview);
1414
- };
1415
- const onSuccess = ({ txHash, txURL }) => {
1416
- if (modalOptions?.closeOnSuccess) {
1417
- log(`[CHECKOUT] transaction succeeded, closing: ${txHash} ${txURL}`);
1418
- setTimeout(() => setOpen(false, { event: "wait-success" }), 1000);
1419
- }
1420
- };
1421
- const resetOrder = () => {
1374
+ }, [trpc, log, setDaimoPayOrder]);
1375
+ const resetOrder = useCallback(() => {
1376
+ // Clear the old order & UI
1422
1377
  setDaimoPayOrder(undefined);
1423
- };
1378
+ setRoute(ROUTES.SELECT_METHOD);
1379
+ // Prefer an explicit payId, otherwise use the queued payParams
1380
+ if (latestPayIdRef.current) {
1381
+ setPayId(latestPayIdRef.current);
1382
+ latestPayIdRef.current = undefined;
1383
+ }
1384
+ else if (latestPayParamsRef.current) {
1385
+ const p = latestPayParamsRef.current;
1386
+ setPayParamsState(p);
1387
+ generatePreviewOrder(p);
1388
+ }
1389
+ }, [
1390
+ setDaimoPayOrder,
1391
+ setRoute,
1392
+ setPayId,
1393
+ setPayParamsState,
1394
+ generatePreviewOrder,
1395
+ ]);
1424
1396
  return {
1425
1397
  setPayId,
1426
1398
  payParams,
@@ -1428,8 +1400,6 @@ function usePaymentState({ trpc, daimoPayOrder, setDaimoPayOrder, setOpen, log,
1428
1400
  generatePreviewOrder,
1429
1401
  daimoPayOrder,
1430
1402
  isDepositFlow,
1431
- modalOptions,
1432
- setModalOptions,
1433
1403
  paymentWaitingMessage,
1434
1404
  selectedExternalOption,
1435
1405
  selectedTokenOption,
@@ -1453,7 +1423,6 @@ function usePaymentState({ trpc, daimoPayOrder, setDaimoPayOrder, setOpen, log,
1453
1423
  payWithDepositAddress,
1454
1424
  payWithSolanaToken,
1455
1425
  refreshOrder,
1456
- onSuccess,
1457
1426
  senderEnsName: senderEnsName ?? undefined,
1458
1427
  };
1459
1428
  }
@@ -4906,32 +4875,42 @@ const useWallets = (isMobile) => {
4906
4875
  const context = usePayContext();
4907
4876
  if (isMobile) {
4908
4877
  const mobileWallets = [];
4909
- // Add Rainbow first
4910
- mobileWallets.push({
4911
- id: "me.rainbow",
4912
- ...walletConfigs["me.rainbow"],
4913
- });
4914
- // Add MetaMask second
4915
- const metaMaskConnector = connectors.find((c) => c.id === "metaMask");
4916
- if (metaMaskConnector) {
4917
- mobileWallets.push({
4918
- id: metaMaskConnector.id,
4919
- connector: metaMaskConnector,
4920
- ...walletConfigs["metaMask, metaMask-io, io.metamask, io.metamask.mobile, metaMaskSDK"],
4921
- });
4922
- }
4923
- // Add WalletConnect and other wallets
4878
+ // Add injected wallet (if any) first
4924
4879
  connectors.forEach((connector) => {
4925
4880
  if (connector.id === "metaMask")
4926
4881
  return;
4882
+ if (connector.id === "walletConnect")
4883
+ return;
4927
4884
  if (isCoinbaseWalletConnector(connector.id))
4928
4885
  return;
4929
4886
  mobileWallets.push({
4930
4887
  id: connector.id,
4931
4888
  connector,
4932
- ...walletConfigs[connector.id],
4889
+ shortName: connector.name,
4890
+ iconConnector: jsx("img", { src: connector.icon, alt: connector.name }),
4891
+ iconShape: "squircle",
4933
4892
  });
4934
4893
  });
4894
+ function addIfNotPresent(idList) {
4895
+ if (mobileWallets.find((w) => idList.includes(w.id)))
4896
+ return;
4897
+ const wallet = assertNotNull(walletConfigs[idList], () => `missing ${idList}`);
4898
+ mobileWallets.push({
4899
+ id: idList,
4900
+ ...wallet,
4901
+ });
4902
+ }
4903
+ addIfNotPresent("me.rainbow");
4904
+ addIfNotPresent("metaMask, metaMask-io, io.metamask, io.metamask.mobile, metaMaskSDK");
4905
+ // Add WalletConnect last
4906
+ const walletConnectConnector = connectors.find((c) => c.id === "walletConnect");
4907
+ if (walletConnectConnector) {
4908
+ mobileWallets.push({
4909
+ id: walletConnectConnector.id,
4910
+ connector: walletConnectConnector,
4911
+ ...walletConfigs[walletConnectConnector.id],
4912
+ });
4913
+ }
4935
4914
  return mobileWallets;
4936
4915
  }
4937
4916
  const wallets = connectors.map((connector) => {
@@ -6889,6 +6868,14 @@ function useWalletConnectUri({ enabled } = {
6889
6868
  };
6890
6869
  }
6891
6870
 
6871
+ /** Determines whether the current wagmi configuration supports a given chain. */
6872
+ function useChainIsSupported(chainId) {
6873
+ const { chains } = useConfig();
6874
+ if (!chainId)
6875
+ return false;
6876
+ return chains.some((x) => x.id === chainId);
6877
+ }
6878
+
6892
6879
  const Web3Context = React.createContext({
6893
6880
  connect: {
6894
6881
  getUri: () => "",
@@ -9610,20 +9597,17 @@ const Underline = styled(motion.span) `
9610
9597
  `;
9611
9598
 
9612
9599
  const Confirmation = () => {
9613
- const { paymentState, confirmationMessage } = usePayContext();
9600
+ const { paymentState, confirmationMessage, onSuccess } = usePayContext();
9614
9601
  const { daimoPayOrder } = paymentState;
9615
- const { done, txURL } = (() => {
9602
+ const { done, txURL } = useMemo(() => {
9616
9603
  if (daimoPayOrder && daimoPayOrder.mode === DaimoPayOrderMode.HYDRATED) {
9617
- // Frontends are optimistic, assume submits will be successful
9618
9604
  const { destStatus } = daimoPayOrder;
9619
- if (destStatus === DaimoPayOrderStatusDest.FAST_FINISH_SUBMITTED ||
9620
- destStatus === DaimoPayOrderStatusDest.FAST_FINISHED ||
9605
+ if (destStatus === DaimoPayOrderStatusDest.FAST_FINISHED ||
9621
9606
  destStatus === DaimoPayOrderStatusDest.CLAIM_SUCCESSFUL) {
9622
9607
  const txHash = daimoPayOrder.destFastFinishTxHash ?? daimoPayOrder.destClaimTxHash;
9623
- const chainId = daimoPayOrder.destFinalCallTokenAmount.token.chainId;
9608
+ const destChainId = getOrderDestChainId(daimoPayOrder);
9624
9609
  assert(txHash != null, `[CONFIRMATION] dest status: ${destStatus}, but missing txHash`);
9625
- const txURL = getChainExplorerTxUrl(chainId, txHash);
9626
- paymentState.onSuccess({ txHash, txURL });
9610
+ const txURL = getChainExplorerTxUrl(destChainId, txHash);
9627
9611
  return {
9628
9612
  done: true,
9629
9613
  txURL,
@@ -9634,7 +9618,12 @@ const Confirmation = () => {
9634
9618
  done: false,
9635
9619
  txURL: undefined,
9636
9620
  };
9637
- })();
9621
+ }, [daimoPayOrder]);
9622
+ useEffect(() => {
9623
+ if (done) {
9624
+ onSuccess();
9625
+ }
9626
+ }, [done, onSuccess]);
9638
9627
  return (jsx(PageContent, { style: {
9639
9628
  display: "flex",
9640
9629
  justifyContent: "center",
@@ -9678,8 +9667,6 @@ const Link = styled.a `
9678
9667
  `;
9679
9668
  const SuccessIcon = styled(TickIcon) `
9680
9669
  color: var(--ck-body-color-valid);
9681
-
9682
- transform: scale(0.5);
9683
9670
  transition: all 0.2s ease-in-out;
9684
9671
  position: absolute;
9685
9672
  opacity: ${(props) => (props.$status ? 1 : 0)};
@@ -9690,7 +9677,6 @@ const Spinner$1 = styled(LoadingCircleIcon) `
9690
9677
  transition: all 0.2s ease-in-out;
9691
9678
  animation: rotateSpinner 400ms linear infinite;
9692
9679
  opacity: ${(props) => (props.$status ? 0 : 1)};
9693
- transform: ${(props) => (props.$status ? "scale(0.5)" : "scale(1)")};
9694
9680
 
9695
9681
  @keyframes rotateSpinner {
9696
9682
  0% {
@@ -11229,9 +11215,9 @@ const WaitingExternal = () => {
11229
11215
  }, children: selectedExternalOption.cta })] }));
11230
11216
  };
11231
11217
 
11232
- const customThemeDefault = {};
11233
- const DaimoPayModal = ({ mode = "auto", theme = "auto", customTheme = customThemeDefault, lang = "en-US", }) => {
11218
+ const DaimoPayModal = ({ mode, theme, customTheme, lang, }) => {
11234
11219
  const context = usePayContext();
11220
+ const { setMode, setTheme, setCustomTheme, setLang } = context;
11235
11221
  const paymentState = context.paymentState;
11236
11222
  const { payParams, generatePreviewOrder, isDepositFlow, showSolanaPaymentMethod, setPaymentWaitingMessage, setSelectedExternalOption, setSelectedTokenOption, setSelectedSolanaTokenOption, setSelectedDepositAddressOption, } = paymentState;
11237
11223
  const { isConnected: isEthConnected, connector, chain, address, } = useAccount();
@@ -11387,12 +11373,16 @@ const DaimoPayModal = ({ mode = "auto", theme = "auto", customTheme = customThem
11387
11373
  }
11388
11374
  // Don't include context.route in the dependency array otherwise the user
11389
11375
  // can't go back from the select token screen to the select method screen
11376
+ // eslint-disable-next-line react-hooks/exhaustive-deps
11390
11377
  }, [
11391
11378
  context.open,
11392
11379
  context.wcWallet,
11393
11380
  isEthConnected,
11394
11381
  isSolanaConnected,
11395
11382
  showSolanaPaymentMethod,
11383
+ address,
11384
+ chain?.id,
11385
+ connector?.id,
11396
11386
  ]);
11397
11387
  // If we're on the connect page and the user successfully connects their
11398
11388
  // wallet, go to the select token page
@@ -11409,11 +11399,12 @@ const DaimoPayModal = ({ mode = "auto", theme = "auto", customTheme = customThem
11409
11399
  });
11410
11400
  }
11411
11401
  }
11402
+ // eslint-disable-next-line react-hooks/exhaustive-deps
11412
11403
  }, [isEthConnected, context.route, connector?.id, chain?.id, address]);
11413
- useEffect(() => context.setMode(mode), [mode]);
11414
- useEffect(() => context.setTheme(theme), [theme]);
11415
- useEffect(() => context.setCustomTheme(customTheme), [customTheme]);
11416
- useEffect(() => context.setLang(lang), [lang]);
11404
+ useEffect(() => setMode(mode), [mode, setMode]);
11405
+ useEffect(() => setTheme(theme), [theme, setTheme]);
11406
+ useEffect(() => setCustomTheme(customTheme), [customTheme, setCustomTheme]);
11407
+ useEffect(() => setLang(lang), [lang, setLang]);
11417
11408
  /* When pulling data into WalletConnect, it prioritises the og:title tag over the title tag */
11418
11409
  useEffect(() => {
11419
11410
  const appName = getAppName();
@@ -11517,7 +11508,10 @@ const DaimoPayProviderWithoutSolana = ({ children, theme = "auto", mode = "auto"
11517
11508
  onCloseRef.current = fn;
11518
11509
  }, []);
11519
11510
  const [open, setOpenState] = useState(false);
11511
+ const [lockPayParams, setLockPayParams] = useState(false);
11512
+ const [paymentCompleted, setPaymentCompleted] = useState(false);
11520
11513
  const [route, setRouteState] = useState(ROUTES.SELECT_METHOD);
11514
+ const [modalOptions, setModalOptions] = useState();
11521
11515
  // Daimo Pay context
11522
11516
  const [daimoPayOrder, setDaimoPayOrderInner] = useState();
11523
11517
  const [pendingConnectorId, setPendingConnectorId] = useState(undefined);
@@ -11528,22 +11522,46 @@ const DaimoPayProviderWithoutSolana = ({ children, theme = "auto", mode = "auto"
11528
11522
  const [errorMessage, setErrorMessage] = useState("");
11529
11523
  const [confirmationMessage, setConfirmationMessage] = useState(undefined);
11530
11524
  const [redirectReturnUrl, setRedirectReturnUrl] = useState(undefined);
11531
- const log = debugMode ? console.log : () => { };
11525
+ const log = useMemo(() => (debugMode ? console.log : () => { }), [debugMode]);
11532
11526
  // Connect to the Daimo Pay TRPC API
11533
- const trpc = useMemo(() => createTrpcClient(payApiUrl, sessionId), [payApiUrl]);
11527
+ const trpc = useMemo(() => createTrpcClient(payApiUrl, sessionId), [payApiUrl, sessionId]);
11534
11528
  const [resize, onResize] = useState(0);
11535
11529
  const setOpen = useCallback((open, meta) => {
11536
11530
  setOpenState(open);
11531
+ // Lock pay params starting from the first time the modal is opened to
11532
+ // prevent the daimo pay order from changing from under the user
11533
+ if (open) {
11534
+ setLockPayParams(true);
11535
+ }
11536
+ // Reset payment state on close if resetOnSuccess is true
11537
+ if (!open && paymentCompleted && modalOptions?.resetOnSuccess) {
11538
+ setPaymentCompleted(false);
11539
+ setLockPayParams(false);
11540
+ paymentState.resetOrder();
11541
+ }
11542
+ // Log the open/close event
11537
11543
  trpc.nav.mutate({
11538
11544
  action: open ? "navOpenPay" : "navClosePay",
11539
11545
  orderId: daimoPayOrder?.id?.toString(),
11540
11546
  data: meta ?? {},
11541
11547
  });
11548
+ // Run the onOpen and onClose callbacks
11542
11549
  if (open)
11543
11550
  onOpenRef.current?.();
11544
11551
  else
11545
11552
  onCloseRef.current?.();
11546
- }, [trpc, daimoPayOrder?.id]);
11553
+ },
11554
+ // We don't have good caching on paymentState, so don't include it as a dep
11555
+ // eslint-disable-next-line react-hooks/exhaustive-deps
11556
+ [trpc, daimoPayOrder?.id, modalOptions?.resetOnSuccess, paymentCompleted]);
11557
+ // Callback when a payment is successfully completed (regardless of whether
11558
+ // the final call succeeded or bounced)
11559
+ const onSuccess = useCallback(() => {
11560
+ if (modalOptions?.closeOnSuccess) {
11561
+ setTimeout(() => setOpen(false, { event: "wait-success" }), 1000);
11562
+ }
11563
+ setPaymentCompleted(true);
11564
+ }, [modalOptions?.closeOnSuccess, setOpen, setPaymentCompleted]);
11547
11565
  const setRoute = useCallback((route, data) => {
11548
11566
  const action = route.replace("daimoPay", "");
11549
11567
  log(`[SET ROUTE] ${action} ${daimoPayOrder?.id} ${debugJson(data ?? {})}`);
@@ -11554,23 +11572,11 @@ const DaimoPayProviderWithoutSolana = ({ children, theme = "auto", mode = "auto"
11554
11572
  });
11555
11573
  setRouteState(route);
11556
11574
  }, [trpc, daimoPayOrder?.id, log]);
11557
- // Include Google Font that is needed for a themes
11558
- if (opts.embedGoogleFonts)
11559
- useThemeFont(ckTheme);
11560
11575
  // Other Configuration
11561
11576
  useEffect(() => setTheme(theme), [theme]);
11562
11577
  useEffect(() => setLang(opts.language || "en-US"), [opts.language]);
11563
11578
  useEffect(() => setErrorMessage(null), [route, open]);
11564
- // Check if chain is supported, elsewise redirect to switches page
11565
- const { chain, isConnected, connector } = useAccount();
11566
- const isChainSupported = useChainIsSupported(chain?.id);
11567
- useEffect(() => {
11568
- if (isConnected && opts.enforceSupportedChains && !isChainSupported) {
11569
- setOpen(true);
11570
- if (route !== ROUTES.SWITCHNETWORKS)
11571
- setRoute(ROUTES.SWITCHNETWORKS);
11572
- }
11573
- }, [isConnected, isChainSupported, chain, route, open]);
11579
+ const { connector } = useAccount();
11574
11580
  // Single source of truth for the currently-connected wallet is the connector
11575
11581
  // exposed by wagmi. See useAccount(). We watch this connector and use it to
11576
11582
  // extract the current WalletConnect wallet, if any.
@@ -11593,9 +11599,10 @@ const DaimoPayProviderWithoutSolana = ({ children, theme = "auto", mode = "auto"
11593
11599
  }, [log]);
11594
11600
  const paymentState = usePaymentState({
11595
11601
  trpc,
11602
+ lockPayParams,
11596
11603
  daimoPayOrder,
11597
11604
  setDaimoPayOrder,
11598
- setOpen,
11605
+ setRoute,
11599
11606
  log,
11600
11607
  redirectReturnUrl,
11601
11608
  });
@@ -11619,11 +11626,13 @@ const DaimoPayProviderWithoutSolana = ({ children, theme = "auto", mode = "auto"
11619
11626
  log(`[PAY] polling in ${intervalMs}ms`);
11620
11627
  const timeout = setTimeout(() => retryBackoff("refreshOrder", () => paymentState.refreshOrder()), intervalMs);
11621
11628
  return () => clearTimeout(timeout);
11622
- }, [daimoPayOrder]);
11629
+ // We don't have good caching on paymentState, so don't include it as a dep
11630
+ // eslint-disable-next-line react-hooks/exhaustive-deps
11631
+ }, [daimoPayOrder, log]);
11623
11632
  const showPayment = async (modalOptions) => {
11624
11633
  const id = daimoPayOrder?.id;
11625
11634
  log(`[PAY] showing payment ${debugJson({ id, modalOptions })}`);
11626
- paymentState.setModalOptions(modalOptions);
11635
+ setModalOptions(modalOptions);
11627
11636
  setOpen(true);
11628
11637
  if (daimoPayOrder &&
11629
11638
  daimoPayOrder.mode === DaimoPayOrderMode.HYDRATED &&
@@ -11663,6 +11672,7 @@ const DaimoPayProviderWithoutSolana = ({ children, theme = "auto", mode = "auto"
11663
11672
  // Other configuration
11664
11673
  options: opts,
11665
11674
  errorMessage,
11675
+ onSuccess,
11666
11676
  confirmationMessage,
11667
11677
  setConfirmationMessage,
11668
11678
  redirectReturnUrl,
@@ -11946,6 +11956,7 @@ function DaimoPayButtonCustom(props) {
11946
11956
  else if (payParams != null) {
11947
11957
  paymentState.setPayParams(payParams);
11948
11958
  }
11959
+ // eslint-disable-next-line react-hooks/exhaustive-deps
11949
11960
  }, [payId, JSON.stringify(payParams || {})]);
11950
11961
  // Set the confirmation message
11951
11962
  const { setConfirmationMessage } = context;
@@ -11977,14 +11988,14 @@ function DaimoPayButtonCustom(props) {
11977
11988
  const intentStatus = order?.intentStatus;
11978
11989
  const hydOrder = order?.mode === DaimoPayOrderMode.HYDRATED ? order : null;
11979
11990
  // Functions to show and hide the modal
11980
- const { children, closeOnSuccess } = props;
11981
- const modalOptions = { closeOnSuccess };
11982
- const show = () => {
11991
+ const { children, closeOnSuccess, resetOnSuccess } = props;
11992
+ const show = useCallback(() => {
11983
11993
  if (paymentState.daimoPayOrder == null)
11984
11994
  return;
11995
+ const modalOptions = { closeOnSuccess, resetOnSuccess };
11985
11996
  context.showPayment(modalOptions);
11986
- };
11987
- const hide = () => context.setOpen(false);
11997
+ }, [context, paymentState.daimoPayOrder, closeOnSuccess, resetOnSuccess]);
11998
+ const hide = useCallback(() => context.setOpen(false), [context]);
11988
11999
  // Emit event handlers when payment status changes
11989
12000
  const sentStart = useRef(false);
11990
12001
  useEffect(() => {
@@ -12079,12 +12090,11 @@ function DaimoPayButtonInner({ disabled }) {
12079
12090
  */
12080
12091
  function useDaimoPayStatus() {
12081
12092
  const { paymentState } = usePayContext();
12082
- const reset = paymentState.resetOrder;
12083
12093
  if (!paymentState || !paymentState.daimoPayOrder)
12084
- return { reset };
12094
+ return undefined;
12085
12095
  const order = paymentState.daimoPayOrder;
12086
12096
  const paymentId = writeDaimoPayOrderID(order.id);
12087
- return { paymentId, status: order.intentStatus, reset };
12097
+ return { paymentId, status: order.intentStatus };
12088
12098
  }
12089
12099
 
12090
12100
  function addressToNumber(address) {