@daimo/pay 1.7.3 → 1.7.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
2
- import { ExternalPaymentOptions, assert, assertNotNull, debugJson, supportedChains, ethereum, 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';
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.3";
25
+ var version = "1.7.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.7.3",
64
+ "@daimo/pay-common": "1.7.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",
@@ -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();
@@ -377,7 +370,7 @@ const OtherWallets = ({ ...props }) => {
377
370
  overflow: "hidden",
378
371
  borderRadius: "27.5%",
379
372
  };
380
- return (jsxs("div", { style: column, ...props, children: [jsxs("div", { style: row, children: [jsx("div", { style: cell, children: jsx(Rainbow, {}) }), jsx("div", { style: cell, children: jsx(WalletConnect, { background: true }) })] }), jsxs("div", { style: row, children: [jsx("div", { style: cell, children: jsx(Family, {}) }), jsx("div", { style: cell, children: jsx(Ledger, {}) })] })] }));
373
+ return (jsxs("div", { style: column, ...props, children: [jsxs("div", { style: row, children: [jsx("div", { style: cell, children: jsx(Zerion, {}) }), jsx("div", { style: cell, children: jsx(WalletConnect, { background: true }) })] }), jsxs("div", { style: row, children: [jsx("div", { style: cell, children: jsx(Family, {}) }), jsx("div", { style: cell, children: jsx(Ledger, {}) })] })] }));
381
374
  };
382
375
  const Fordefi = ({ ...props }) => (jsxs("svg", { ...props, width: "88", height: "88", viewBox: "0 0 96 96", fill: "none", xmlns: "http://www.w3.org/2000/svg", children: [jsxs("g", { clipPath: "url(#clip0_14298_75627)", children: [jsx("path", { d: "M43.5075 62.5508H6V73.5954C6 79.2046 10.5379 83.7515 16.1357 83.7515H32.9997L43.5075 62.5508Z", fill: "#7994FF" }), jsx("path", { d: "M6.00098 39.1016H76.2075L68.0567 55.4841H6.00098V39.1016Z", fill: "#486DFF" }), jsx("path", { d: "M30.6398 12H6.09766V32.0282H89.8447V12H65.3025V26.9577H60.2423V12H35.7001V26.9577H30.6398V12Z", fill: "#5CD1FA" })] }), jsx("defs", { children: jsx("clipPath", { id: "clip0_14298_75627", children: jsx("rect", { width: "84", height: "72", fill: "white", transform: "translate(6 12)" }) }) })] }));
383
376
  const SquircleIcon = ({ icon, alt, }) => {
@@ -580,11 +573,12 @@ const walletConfigs = {
580
573
  edge: "https://rainbow.me/extension?utm_source=daimopay",
581
574
  brave: "https://rainbow.me/extension?utm_source=daimopay",
582
575
  },
583
- showInMobileConnectors: true,
576
+ showInMobileConnectors: false,
584
577
  isWcMobileConnector: false,
585
578
  getWalletConnectDeeplink: (uri) => {
586
- return `https://rnbwapp.com/wc?uri=${encodeURIComponent(uri)}&connector=daimopay`;
579
+ return `rainbow://wc?uri=${encodeURIComponent(uri)}&connector=daimopay`;
587
580
  },
581
+ walletDeepLink: "rainbow://",
588
582
  },
589
583
  "io.rabby": {
590
584
  name: "Rabby Wallet",
@@ -732,7 +726,7 @@ const walletConfigs = {
732
726
  getWalletConnectDeeplink: (uri) => {
733
727
  return `https://app.zerion.io/wc?uri=${encodeURIComponent(uri)}`;
734
728
  },
735
- showInMobileConnectors: false,
729
+ showInMobileConnectors: true,
736
730
  },
737
731
  slope: {
738
732
  name: "Slope",
@@ -838,51 +832,10 @@ function extractWcWalletFromProvider(p, log) {
838
832
  isWcMobileConnector: true,
839
833
  };
840
834
  }
841
- log(`[WCWALLET] name: ${name} wcWallet: ${wallet?.name} isWcMobileConnector: ${wallet?.isWcMobileConnector} provider: ${p}`);
835
+ log(`[WCWALLET] name: ${name} wcWallet: ${wallet?.name} isWcMobileConnector: ${wallet?.isWcMobileConnector} provider: `, p);
842
836
  return wallet;
843
837
  }
844
838
 
845
- function useGoogleFont(font) {
846
- useEffect(() => {
847
- if (!font)
848
- return;
849
- font = font.replace(/ /g, "+");
850
- const googleapis = document.createElement("link");
851
- googleapis.href = `https://fonts.googleapis.com`;
852
- googleapis.rel = "preconnect";
853
- const gstatic = document.createElement("link");
854
- gstatic.href = `https://fonts.gstatic.com`;
855
- gstatic.rel = "preconnect";
856
- gstatic.crossOrigin = "true";
857
- const link = document.createElement("link");
858
- link.href = `https://fonts.googleapis.com/css2?family=${font}:wght@400;500;600&display=swap`;
859
- link.rel = "stylesheet";
860
- document.head.appendChild(googleapis);
861
- document.head.appendChild(gstatic);
862
- document.head.appendChild(link);
863
- return () => {
864
- try {
865
- document.head.removeChild(googleapis);
866
- document.head.removeChild(gstatic);
867
- document.head.removeChild(link);
868
- }
869
- catch { }
870
- };
871
- }, [font]);
872
- }
873
- // TODO: This could be dynamic if theming wasn't set up as css variables
874
- function useThemeFont(theme) {
875
- const themeFonts = {
876
- web95: "Lato",
877
- retro: "Nunito",
878
- midnight: "Inter",
879
- minimal: "Inter",
880
- rounded: "Nunito",
881
- };
882
- const font = themeFonts[theme] ?? null;
883
- useGoogleFont(font ?? "");
884
- }
885
-
886
839
  /** Daimo Pay internal context. */
887
840
  const usePayContext = () => {
888
841
  const context = React.useContext(PayContext);
@@ -1174,7 +1127,7 @@ function useWalletPaymentOptions({ trpc, address, usdRequired, destChainId, pref
1174
1127
  };
1175
1128
  }
1176
1129
 
1177
- function usePaymentState({ trpc, daimoPayOrder, setDaimoPayOrder, setOpen, log, redirectReturnUrl, }) {
1130
+ function usePaymentState({ trpc, lockPayParams, daimoPayOrder, setDaimoPayOrder, setRoute, log, redirectReturnUrl, }) {
1178
1131
  // Browser state.
1179
1132
  const [platform, setPlatform] = useState();
1180
1133
  useEffect(() => {
@@ -1189,12 +1142,22 @@ function usePaymentState({ trpc, daimoPayOrder, setDaimoPayOrder, setOpen, log,
1189
1142
  // Solana wallet state.
1190
1143
  const solanaWallet = useWallet$1();
1191
1144
  const solanaPubKey = solanaWallet.publicKey?.toBase58();
1192
- // Daimo Pay order state.
1145
+ // TODO: backend should determine whether to show solana payment method
1146
+ const paymentOptions = daimoPayOrder?.metadata.payer?.paymentOptions;
1147
+ // Include by default if paymentOptions not provided. Solana bridging is only
1148
+ // supported on CCTP v1 chains.
1149
+ const showSolanaPaymentMethod = (paymentOptions == null ||
1150
+ paymentOptions.includes(ExternalPaymentOptions.Solana)) &&
1151
+ daimoPayOrder != null &&
1152
+ isCCTPV1Chain(getOrderDestChainId(daimoPayOrder));
1153
+ // Refs the survive re-renders and stores any updated param values while
1154
+ // lockPayParams is true
1155
+ const latestPayParamsRef = useRef();
1156
+ const latestPayIdRef = useRef();
1157
+ // Current pay params to do processing off of
1193
1158
  const [payParams, setPayParamsState] = useState();
1194
1159
  const [paymentWaitingMessage, setPaymentWaitingMessage] = useState();
1195
1160
  const [isDepositFlow, setIsDepositFlow] = useState(false);
1196
- // Payment UI config.
1197
- const [modalOptions, setModalOptions] = useState({});
1198
1161
  // UI state. Selection for external payment (Binance, etc) vs wallet payment.
1199
1162
  const externalPaymentOptions = useExternalPaymentOptions({
1200
1163
  trpc,
@@ -1332,16 +1295,16 @@ function usePaymentState({ trpc, daimoPayOrder, setDaimoPayOrder, setOpen, log,
1332
1295
  else {
1333
1296
  log(`[CHECKOUT] IGNORING refreshOrder, wrong ID: ${order.id} vs ${daimoPayOrder.id}`);
1334
1297
  }
1335
- }, [daimoPayOrder?.id]);
1298
+ }, [daimoPayOrder, trpc, setDaimoPayOrder, log]);
1336
1299
  /** User picked a different deposit amount. */
1337
1300
  const setChosenUsd = (usd) => {
1338
1301
  assert(!!daimoPayOrder, "[SET CHOSEN USD] daimoPayOrder cannot be null");
1339
1302
  const token = daimoPayOrder.destFinalCallTokenAmount.token;
1340
- const tokenUnits = (usd / token.usd).toString();
1303
+ const tokenUnits = (usd / token.priceFromUsd).toString();
1341
1304
  const tokenAmount = parseUnits(tokenUnits, token.decimals);
1342
1305
  // TODO: remove amount from destFinalCall, it is redundant with
1343
1306
  // destFinalCallTokenAmount. Here, we only modify one and not the other.
1344
- log(`[CHECKOUT] setting chosen USD amount to $${usd} = ${tokenUnits} ${token.symbol}`);
1307
+ log(`[CHECKOUT] chose USD amount $${usd} = ${tokenUnits} ${token.symbol}`);
1345
1308
  setDaimoPayOrder({
1346
1309
  ...daimoPayOrder,
1347
1310
  destFinalCallTokenAmount: {
@@ -1352,7 +1315,8 @@ function usePaymentState({ trpc, daimoPayOrder, setDaimoPayOrder, setOpen, log,
1352
1315
  });
1353
1316
  };
1354
1317
  const setPayId = useCallback(async (payId) => {
1355
- if (!payId)
1318
+ latestPayIdRef.current = payId;
1319
+ if (lockPayParams || !payId)
1356
1320
  return;
1357
1321
  const id = readDaimoPayOrderID(payId).toString();
1358
1322
  if (daimoPayOrder && BigInt(id) == daimoPayOrder.id) {
@@ -1366,15 +1330,19 @@ function usePaymentState({ trpc, daimoPayOrder, setDaimoPayOrder, setOpen, log,
1366
1330
  }
1367
1331
  log(`[CHECKOUT] setPayId: fetched order: ${JSON.stringify(order)}`);
1368
1332
  setDaimoPayOrder(order);
1369
- }, [daimoPayOrder]);
1333
+ }, [daimoPayOrder, lockPayParams, trpc, log, setDaimoPayOrder]);
1370
1334
  /** Called whenever params change. */
1371
1335
  const setPayParams = async (payParams) => {
1336
+ latestPayParamsRef.current = payParams;
1337
+ if (lockPayParams)
1338
+ return;
1372
1339
  assert(payParams != null, "[SET PAY PARAMS] payParams cannot be null");
1340
+ console.log("[SET PAY PARAMS] setting payParams");
1373
1341
  setPayParamsState(payParams);
1374
1342
  setIsDepositFlow(payParams.toUnits == null);
1375
1343
  generatePreviewOrder(payParams);
1376
1344
  };
1377
- const generatePreviewOrder = async (payParams) => {
1345
+ const generatePreviewOrder = useCallback(async (payParams) => {
1378
1346
  // toUnits is undefined if and only if we're in deposit flow.
1379
1347
  // Set dummy value for deposit flow, since user can edit the amount.
1380
1348
  const toUnits = payParams.toUnits == null ? "0" : payParams.toUnits;
@@ -1402,13 +1370,28 @@ function usePaymentState({ trpc, daimoPayOrder, setDaimoPayOrder, setOpen, log,
1402
1370
  });
1403
1371
  log(`[CHECKOUT] generated preview: ${JSON.stringify(orderPreview)}`);
1404
1372
  setDaimoPayOrder(orderPreview);
1405
- };
1406
- const onSuccess = ({ txHash, txURL }) => {
1407
- if (modalOptions?.closeOnSuccess) {
1408
- log(`[CHECKOUT] transaction succeeded, closing: ${txHash} ${txURL}`);
1409
- setTimeout(() => setOpen(false, { event: "wait-success" }), 1000);
1373
+ }, [trpc, log, setDaimoPayOrder]);
1374
+ const resetOrder = useCallback(() => {
1375
+ // Clear the old order & UI
1376
+ setDaimoPayOrder(undefined);
1377
+ setRoute(ROUTES.SELECT_METHOD);
1378
+ // Prefer an explicit payId, otherwise use the queued payParams
1379
+ if (latestPayIdRef.current) {
1380
+ setPayId(latestPayIdRef.current);
1381
+ latestPayIdRef.current = undefined;
1382
+ }
1383
+ else if (latestPayParamsRef.current) {
1384
+ const p = latestPayParamsRef.current;
1385
+ setPayParamsState(p);
1386
+ generatePreviewOrder(p);
1410
1387
  }
1411
- };
1388
+ }, [
1389
+ setDaimoPayOrder,
1390
+ setRoute,
1391
+ setPayId,
1392
+ setPayParamsState,
1393
+ generatePreviewOrder,
1394
+ ]);
1412
1395
  return {
1413
1396
  setPayId,
1414
1397
  payParams,
@@ -1416,18 +1399,18 @@ function usePaymentState({ trpc, daimoPayOrder, setDaimoPayOrder, setOpen, log,
1416
1399
  generatePreviewOrder,
1417
1400
  daimoPayOrder,
1418
1401
  isDepositFlow,
1419
- modalOptions,
1420
- setModalOptions,
1421
1402
  paymentWaitingMessage,
1422
1403
  selectedExternalOption,
1423
1404
  selectedTokenOption,
1424
1405
  selectedSolanaTokenOption,
1425
1406
  externalPaymentOptions,
1407
+ showSolanaPaymentMethod,
1426
1408
  walletPaymentOptions,
1427
1409
  solanaPaymentOptions,
1428
1410
  depositAddressOptions,
1429
1411
  selectedDepositAddressOption,
1430
1412
  getOrderUsdLimit,
1413
+ resetOrder,
1431
1414
  setPaymentWaitingMessage,
1432
1415
  setSelectedExternalOption,
1433
1416
  setSelectedTokenOption,
@@ -1439,7 +1422,6 @@ function usePaymentState({ trpc, daimoPayOrder, setDaimoPayOrder, setOpen, log,
1439
1422
  payWithDepositAddress,
1440
1423
  payWithSolanaToken,
1441
1424
  refreshOrder,
1442
- onSuccess,
1443
1425
  senderEnsName: senderEnsName ?? undefined,
1444
1426
  };
1445
1427
  }
@@ -4887,9 +4869,39 @@ const useWallet = (id) => {
4887
4869
  return null;
4888
4870
  return wallet;
4889
4871
  };
4890
- const useWallets = () => {
4872
+ const useWallets = (isMobile) => {
4891
4873
  const connectors = useConnectors();
4892
4874
  const context = usePayContext();
4875
+ if (isMobile) {
4876
+ const mobileWallets = [];
4877
+ // Add Rainbow first
4878
+ mobileWallets.push({
4879
+ id: "me.rainbow",
4880
+ ...walletConfigs["me.rainbow"],
4881
+ });
4882
+ // Add MetaMask second
4883
+ const metaMaskConnector = connectors.find((c) => c.id === "metaMask");
4884
+ if (metaMaskConnector) {
4885
+ mobileWallets.push({
4886
+ id: metaMaskConnector.id,
4887
+ connector: metaMaskConnector,
4888
+ ...walletConfigs["metaMask, metaMask-io, io.metamask, io.metamask.mobile, metaMaskSDK"],
4889
+ });
4890
+ }
4891
+ // Add WalletConnect and other wallets
4892
+ connectors.forEach((connector) => {
4893
+ if (connector.id === "metaMask")
4894
+ return;
4895
+ if (isCoinbaseWalletConnector(connector.id))
4896
+ return;
4897
+ mobileWallets.push({
4898
+ id: connector.id,
4899
+ connector,
4900
+ ...walletConfigs[connector.id],
4901
+ });
4902
+ });
4903
+ return mobileWallets;
4904
+ }
4893
4905
  const wallets = connectors.map((connector) => {
4894
4906
  // use overrides
4895
4907
  const walletId = Object.keys(walletConfigs).find(
@@ -4944,8 +4956,8 @@ const useWallets = () => {
4944
4956
  self.find((w) => w.id === "farcaster")))
4945
4957
  // order by isInstalled injected connectors first
4946
4958
  .sort((a, b) => {
4947
- const AisInstalled = a.isInstalled && isInjectedConnector(a.connector.type);
4948
- const BisInstalled = b.isInstalled && isInjectedConnector(b.connector.type);
4959
+ const AisInstalled = a.isInstalled && isInjectedConnector(a.connector?.type);
4960
+ const BisInstalled = b.isInstalled && isInjectedConnector(b.connector?.type);
4949
4961
  if (AisInstalled && !BisInstalled)
4950
4962
  return -1;
4951
4963
  if (!AisInstalled && BisInstalled)
@@ -6845,6 +6857,14 @@ function useWalletConnectUri({ enabled } = {
6845
6857
  };
6846
6858
  }
6847
6859
 
6860
+ /** Determines whether the current wagmi configuration supports a given chain. */
6861
+ function useChainIsSupported(chainId) {
6862
+ const { chains } = useConfig();
6863
+ if (!chainId)
6864
+ return false;
6865
+ return chains.some((x) => x.id === chainId);
6866
+ }
6867
+
6848
6868
  const Web3Context = React.createContext({
6849
6869
  connect: {
6850
6870
  getUri: () => "",
@@ -7246,15 +7266,15 @@ const ConnectorsContainer = styled.div `
7246
7266
  const ConnectorList = () => {
7247
7267
  const context = usePayContext();
7248
7268
  const { isMobile } = useIsMobile();
7249
- const wallets = useWallets();
7269
+ const wallets = useWallets(isMobile);
7250
7270
  const { lastConnectorId } = useLastConnector();
7251
7271
  const walletsToDisplay = context.options?.hideRecentBadge || lastConnectorId === "walletConnect" // do not hoist walletconnect to top of list
7252
7272
  ? wallets
7253
7273
  : [
7254
7274
  // move last used wallet to top of list
7255
7275
  // using .filter and spread to avoid mutating original array order with .sort
7256
- ...wallets.filter((wallet) => lastConnectorId === wallet.connector.id),
7257
- ...wallets.filter((wallet) => lastConnectorId !== wallet.connector.id),
7276
+ ...wallets.filter((wallet) => lastConnectorId === wallet.connector?.id),
7277
+ ...wallets.filter((wallet) => lastConnectorId !== wallet.connector?.id),
7258
7278
  ];
7259
7279
  return (jsxs(ScrollArea, { mobileDirection: "horizontal", children: [walletsToDisplay.length === 0 && (jsx(Alert, { error: true, children: "No connectors found in ConnectKit config." })), walletsToDisplay.length > 0 && (jsx(ConnectorsContainer, { "$mobile": isMobile, "$totalResults": walletsToDisplay.length, children: walletsToDisplay.map((wallet) => (jsx(ConnectorItem, { wallet: wallet, isRecent: wallet.id === lastConnectorId }, wallet.id))) }))] }));
7260
7280
  };
@@ -7271,7 +7291,7 @@ const ConnectorItem = ({ wallet, isRecent, }) => {
7271
7291
  const redirectToMoreWallets = isMobile && isWalletConnectConnector(wallet.id);
7272
7292
  // Safari requires opening popup on user gesture, so we connect immediately here
7273
7293
  const shouldConnectImmediately = (detectBrowser() === "safari" || detectBrowser() === "ios") &&
7274
- isCoinbaseWalletConnector(wallet.connector.id);
7294
+ isCoinbaseWalletConnector(wallet.connector?.id);
7275
7295
  if (redirectToMoreWallets || shouldConnectImmediately)
7276
7296
  deeplink = undefined; // mobile redirects to more wallets page
7277
7297
  return (jsxs(ConnectorButton, { type: "button", as: deeplink ? "a" : undefined, href: deeplink ? deeplink : undefined, disabled: context.route !== ROUTES.CONNECTORS, onClick: deeplink
@@ -8225,7 +8245,7 @@ const MobileConnectors = () => {
8225
8245
  // filter out installed wallets
8226
8246
  const walletsIdsToDisplay = Object.keys(walletConfigs).filter((walletId) => {
8227
8247
  const wallet = walletConfigs[walletId];
8228
- if (wallets.find((w) => w.connector.id === walletId))
8248
+ if (wallets.find((w) => w.connector?.id === walletId))
8229
8249
  return false;
8230
8250
  if (!wallet.getWalletConnectDeeplink)
8231
8251
  return false;
@@ -9357,7 +9377,7 @@ const ConnectWithInjector = ({ switchConnectMethod, forceState }) => {
9357
9377
  return (jsx(PageContent, { children: jsxs(Container$4, { children: [jsx(ModalHeading, { children: "Invalid State" }), jsx(ModalContent, { children: jsx(Alert, { children: "No connectors match the id given. This state should never happen." }) })] }) }));
9358
9378
  }
9359
9379
  // TODO: Make this more generic
9360
- if (isWalletConnectConnector(wallet?.connector.id)) {
9380
+ if (isWalletConnectConnector(wallet?.connector?.id)) {
9361
9381
  return (jsx(PageContent, { children: jsxs(Container$4, { children: [jsx(ModalHeading, { children: "Invalid State" }), jsx(ModalContent, { children: jsx(Alert, { children: "WalletConnect does not have an injection flow. This state should never happen." }) })] }) }));
9362
9382
  }
9363
9383
  return (jsx(PageContent, { children: jsxs(Container$4, { children: [jsx(ConnectingContainer, { children: jsxs(ConnectingAnimation, { "$shake": status === states$1.FAILED || status === states$1.REJECTED, "$circle": walletInfo.iconShape === "circle", children: [jsx(AnimatePresence, { children: (status === states$1.FAILED || status === states$1.REJECTED) && (jsx(RetryButton, { "aria-label": "Retry", initial: { opacity: 0, scale: 0.8 }, animate: { opacity: 1, scale: 1 }, exit: { opacity: 0, scale: 0.8 }, whileTap: { scale: 0.9 }, transition: { duration: 0.1 }, onClick: runConnect, children: jsx(RetryIconContainer, { children: jsx(Tooltip, { open: showTryAgainTooltip &&
@@ -9369,9 +9389,9 @@ const ConnectWithInjector = ({ switchConnectMethod, forceState }) => {
9369
9389
  transform: "scale(1.14)",
9370
9390
  position: "relative",
9371
9391
  width: "100%",
9372
- }, children: walletInfo.icon })) : (jsx(Fragment, { children: walletInfo.icon })), loading: status === states$1.CONNECTING }))] }) }), jsx(ModalContentContainer, { children: jsxs(AnimatePresence, { initial: false, children: [status === states$1.FAILED && (jsx(Content, { initial: "initial", animate: "animate", exit: "exit", variants: contentVariants$1, children: jsxs(ModalContent, { children: [jsxs(ModalH1, { "$error": true, children: [jsx(AlertIcon, {}), locales.injectionScreen_failed_h1] }), jsx(ModalBody, { children: locales.injectionScreen_failed_p })] }) }, states$1.FAILED)), status === states$1.REJECTED && (jsx(Content, { initial: "initial", animate: "animate", exit: "exit", variants: contentVariants$1, children: jsxs(ModalContent, { style: { paddingBottom: 28 }, children: [jsx(ModalH1, { children: locales.injectionScreen_rejected_h1 }), jsx(ModalBody, { children: locales.injectionScreen_rejected_p })] }) }, states$1.REJECTED)), (status === states$1.CONNECTING || status === states$1.EXPIRING) && (jsx(Content, { initial: "initial", animate: "animate", exit: "exit", variants: contentVariants$1, children: jsxs(ModalContent, { style: { paddingBottom: 28 }, children: [jsx(ModalH1, { children: wallet.connector.id === "injected"
9392
+ }, children: walletInfo.icon })) : (jsx(Fragment, { children: walletInfo.icon })), loading: status === states$1.CONNECTING }))] }) }), jsx(ModalContentContainer, { children: jsxs(AnimatePresence, { initial: false, children: [status === states$1.FAILED && (jsx(Content, { initial: "initial", animate: "animate", exit: "exit", variants: contentVariants$1, children: jsxs(ModalContent, { children: [jsxs(ModalH1, { "$error": true, children: [jsx(AlertIcon, {}), locales.injectionScreen_failed_h1] }), jsx(ModalBody, { children: locales.injectionScreen_failed_p })] }) }, states$1.FAILED)), status === states$1.REJECTED && (jsx(Content, { initial: "initial", animate: "animate", exit: "exit", variants: contentVariants$1, children: jsxs(ModalContent, { style: { paddingBottom: 28 }, children: [jsx(ModalH1, { children: locales.injectionScreen_rejected_h1 }), jsx(ModalBody, { children: locales.injectionScreen_rejected_p })] }) }, states$1.REJECTED)), (status === states$1.CONNECTING || status === states$1.EXPIRING) && (jsx(Content, { initial: "initial", animate: "animate", exit: "exit", variants: contentVariants$1, children: jsxs(ModalContent, { style: { paddingBottom: 28 }, children: [jsx(ModalH1, { children: wallet.connector?.id === "injected"
9373
9393
  ? locales.injectionScreen_connecting_injected_h1
9374
- : locales.injectionScreen_connecting_h1 }), jsx(ModalBody, { children: wallet.connector.id === "injected"
9394
+ : locales.injectionScreen_connecting_h1 }), jsx(ModalBody, { children: wallet.connector?.id === "injected"
9375
9395
  ? locales.injectionScreen_connecting_injected_p
9376
9396
  : locales.injectionScreen_connecting_p }), jsxs(Button, { icon: jsx(ExternalLinkIcon, {}), onClick: runConnect, children: ["Connect ", walletInfo.name] })] }) }, states$1.CONNECTING)), status === states$1.CONNECTED && (jsx(Content, { initial: "initial", animate: "animate", exit: "exit", variants: contentVariants$1, children: jsxs(ModalContent, { children: [jsxs(ModalH1, { "$valid": true, children: [jsx(TickIcon, {}), " ", locales.injectionScreen_connected_h1] }), jsx(ModalBody, { children: locales.injectionScreen_connected_p })] }) }, states$1.CONNECTED)), status === states$1.NOTCONNECTED && (jsx(Content, { initial: "initial", animate: "animate", exit: "exit", variants: contentVariants$1, children: jsxs(ModalContent, { children: [jsx(ModalH1, { children: locales.injectionScreen_notconnected_h1 }), jsx(ModalBody, { children: locales.injectionScreen_notconnected_p })] }) }, states$1.NOTCONNECTED)), status === states$1.UNAVAILABLE && (jsx(Content, { initial: "initial", animate: "animate", exit: "exit", variants: contentVariants$1, children: !extensionUrl ? (jsxs(Fragment, { children: [jsxs(ModalContent, { style: { paddingBottom: 12 }, children: [jsx(ModalH1, { children: locales.injectionScreen_unavailable_h1 }), jsx(ModalBody, { children: locales.injectionScreen_unavailable_p })] }), !wallet.isInstalled && suggestedExtension && (jsxs(Button, { href: suggestedExtension?.url, icon: jsx(BrowserIcon, { browser: suggestedExtension?.name }), children: ["Install on ", suggestedExtension?.label] }))] })) : (jsxs(Fragment, { children: [jsxs(ModalContent, { style: { paddingBottom: 18 }, children: [jsx(ModalH1, { children: locales.injectionScreen_install_h1 }), jsx(ModalBody, { children: locales.injectionScreen_install_p })] }), !wallet.isInstalled && extensionUrl && (jsx(Button, { href: extensionUrl, icon: jsx(BrowserIcon, {}), children: locales.installTheExtension }))] })) }, states$1.UNAVAILABLE))] }) })] }) }));
9377
9397
  };
@@ -9459,7 +9479,7 @@ const ConnectUsing = () => {
9459
9479
  useEffect(() => {
9460
9480
  // if no provider, change to qrcode
9461
9481
  const checkProvider = async () => {
9462
- const res = await wallet?.connector.getProvider();
9482
+ const res = await wallet?.connector?.getProvider();
9463
9483
  if (!res) {
9464
9484
  setStatus(states.QRCODE);
9465
9485
  setTimeout(context.triggerResize, 10); // delay required here for modal to resize
@@ -9566,20 +9586,17 @@ const Underline = styled(motion.span) `
9566
9586
  `;
9567
9587
 
9568
9588
  const Confirmation = () => {
9569
- const { paymentState, confirmationMessage } = usePayContext();
9589
+ const { paymentState, confirmationMessage, onSuccess } = usePayContext();
9570
9590
  const { daimoPayOrder } = paymentState;
9571
- const { done, txURL } = (() => {
9591
+ const { done, txURL } = useMemo(() => {
9572
9592
  if (daimoPayOrder && daimoPayOrder.mode === DaimoPayOrderMode.HYDRATED) {
9573
- // Frontends are optimistic, assume submits will be successful
9574
9593
  const { destStatus } = daimoPayOrder;
9575
- if (destStatus === DaimoPayOrderStatusDest.FAST_FINISH_SUBMITTED ||
9576
- destStatus === DaimoPayOrderStatusDest.FAST_FINISHED ||
9594
+ if (destStatus === DaimoPayOrderStatusDest.FAST_FINISHED ||
9577
9595
  destStatus === DaimoPayOrderStatusDest.CLAIM_SUCCESSFUL) {
9578
9596
  const txHash = daimoPayOrder.destFastFinishTxHash ?? daimoPayOrder.destClaimTxHash;
9579
- const chainId = daimoPayOrder.destFinalCallTokenAmount.token.chainId;
9597
+ const destChainId = getOrderDestChainId(daimoPayOrder);
9580
9598
  assert(txHash != null, `[CONFIRMATION] dest status: ${destStatus}, but missing txHash`);
9581
- const txURL = getChainExplorerTxUrl(chainId, txHash);
9582
- paymentState.onSuccess({ txHash, txURL });
9599
+ const txURL = getChainExplorerTxUrl(destChainId, txHash);
9583
9600
  return {
9584
9601
  done: true,
9585
9602
  txURL,
@@ -9590,7 +9607,12 @@ const Confirmation = () => {
9590
9607
  done: false,
9591
9608
  txURL: undefined,
9592
9609
  };
9593
- })();
9610
+ }, [daimoPayOrder]);
9611
+ useEffect(() => {
9612
+ if (done) {
9613
+ onSuccess();
9614
+ }
9615
+ }, [done, onSuccess]);
9594
9616
  return (jsx(PageContent, { style: {
9595
9617
  display: "flex",
9596
9618
  justifyContent: "center",
@@ -9634,8 +9656,6 @@ const Link = styled.a `
9634
9656
  `;
9635
9657
  const SuccessIcon = styled(TickIcon) `
9636
9658
  color: var(--ck-body-color-valid);
9637
-
9638
- transform: scale(0.5);
9639
9659
  transition: all 0.2s ease-in-out;
9640
9660
  position: absolute;
9641
9661
  opacity: ${(props) => (props.$status ? 1 : 0)};
@@ -9646,7 +9666,6 @@ const Spinner$1 = styled(LoadingCircleIcon) `
9646
9666
  transition: all 0.2s ease-in-out;
9647
9667
  animation: rotateSpinner 400ms linear infinite;
9648
9668
  opacity: ${(props) => (props.$status ? 0 : 1)};
9649
- transform: ${(props) => (props.$status ? "scale(0.5)" : "scale(1)")};
9650
9669
 
9651
9670
  @keyframes rotateSpinner {
9652
9671
  0% {
@@ -9761,8 +9780,8 @@ const ChainLogoContainer = styled(motion.div) `
9761
9780
  }
9762
9781
  `;
9763
9782
 
9764
- const TokenLogoSpinner = ({ token, showSpinner = true, }) => {
9765
- return (jsx(LoadingContainer$2, { children: jsx(AnimationContainer$1, { "$circle": true, children: jsxs(AnimatePresence, { children: [chainToLogo[token.chainId] && (jsx(ChainLogoContainer, { children: chainToLogo[token.chainId] }, "ChainLogoContainer")), jsx(CircleSpinner, { logo: jsx("img", { src: token.logoURI, alt: token.symbol }), loading: showSpinner, unavailable: false }, "CircleSpinner")] }) }) }));
9783
+ const TokenLogoSpinner = ({ token }) => {
9784
+ return (jsx(LoadingContainer$2, { children: jsx(AnimationContainer$1, { "$circle": true, children: jsxs(AnimatePresence, { children: [chainToLogo[token.chainId] && (jsx(ChainLogoContainer, { children: chainToLogo[token.chainId] }, "ChainLogoContainer")), jsx(CircleSpinner, { logo: jsx("img", { src: token.logoURI, alt: token.symbol }), loading: false, unavailable: false }, "CircleSpinner")] }) }) }));
9766
9785
  };
9767
9786
 
9768
9787
  var PayState$1;
@@ -9869,12 +9888,12 @@ const PayWithToken = () => {
9869
9888
  if (selectedTokenOption == null) {
9870
9889
  return jsx(PageContent, {});
9871
9890
  }
9872
- return (jsxs(PageContent, { children: [jsx(TokenLogoSpinner, { token: selectedTokenOption.required.token }), jsxs(ModalContent, { style: { paddingBottom: 0 }, "$preserveDisplay": true, children: [jsx(ModalH1, { children: payState }), jsx(PaymentBreakdown, { paymentOption: selectedTokenOption }), payState === PayState$1.RequestingPayment && wcWallet && isMobile && (jsxs(Button, { icon: jsx(ExternalLinkIcon, {}), onClick: wcWallet.isWcMobileConnector
9891
+ return (jsxs(PageContent, { children: [jsx(TokenLogoSpinner, { token: selectedTokenOption.required.token }), jsxs(ModalContent, { style: { paddingBottom: 0 }, "$preserveDisplay": true, children: [jsx(ModalH1, { children: payState }), jsx(PaymentBreakdown, { paymentOption: selectedTokenOption }), payState === PayState$1.RequestingPayment && wcWallet && isMobile && (jsx(Button, { icon: jsx(ExternalLinkIcon, {}), onClick: wcWallet.isWcMobileConnector
9873
9892
  ? () => handleTransfer(selectedTokenOption)
9874
9893
  : undefined, href: wcWallet.isWcMobileConnector
9875
9894
  ? undefined
9876
9895
  : wcWallet.walletDeepLink ||
9877
- wcWallet.getWalletConnectDeeplink?.(""), children: ["Pay with ", wcWallet.name] })), payState === PayState$1.RequestCancelled && (jsx(Button, { onClick: () => handleTransfer(selectedTokenOption), children: "Retry Payment" }))] })] }));
9896
+ wcWallet.getWalletConnectDeeplink?.(""), children: "Tap Here to Pay" })), payState === PayState$1.RequestCancelled && (jsx(Button, { onClick: () => handleTransfer(selectedTokenOption), children: "Retry Payment" }))] })] }));
9878
9897
  };
9879
9898
 
9880
9899
  /**
@@ -10087,7 +10106,7 @@ const MultiCurrencySelectAmount = ({ selectedTokenOption, setSelectedTokenOption
10087
10106
  tokenSymbol: balanceToken.symbol,
10088
10107
  });
10089
10108
  };
10090
- return (jsxs(PageContent, { children: [jsx(TokenLogoSpinner, { token: balanceToken, showSpinner: false }), jsxs(ModalContent, { "$preserveDisplay": true, children: [jsxs(AmountInputContainer$2, { children: [jsx(MaxButton, { style: { visibility: "hidden" }, children: "Max" }), jsx(AmountInputField, { value: isEditingUsd ? usdValue : tokenValue, onChange: handleAmountChange, currency: isEditingUsd ? "$" : balanceToken.symbol, onKeyDown: handleKeyDown }), jsx(MaxButton, { onClick: handleMax, children: "Max" })] }), balanceToken.fiatIso !== "USD" && (jsx(SwitchContainer, { children: jsx(SwitchButton, { onClick: handleSwitchCurrency, children: jsx(SecondaryAmount, { children: isEditingUsd
10109
+ return (jsxs(PageContent, { children: [jsx(TokenLogoSpinner, { token: balanceToken }), jsxs(ModalContent, { "$preserveDisplay": true, children: [jsxs(AmountInputContainer$2, { children: [jsx(MaxButton, { style: { visibility: "hidden" }, children: "Max" }), jsx(AmountInputField, { value: isEditingUsd ? usdValue : tokenValue, onChange: handleAmountChange, currency: isEditingUsd ? "$" : balanceToken.symbol, onKeyDown: handleKeyDown }), jsx(MaxButton, { onClick: handleMax, children: "Max" })] }), balanceToken.fiatISO !== "USD" && (jsx(SwitchContainer, { children: jsx(SwitchButton, { onClick: handleSwitchCurrency, children: jsx(SecondaryAmount, { children: isEditingUsd
10091
10110
  ? `${tokenValue} ${balanceToken.symbol}`
10092
10111
  : `$${usdValue}` }) }) })), message && jsx(ModalBody, { children: message }), jsx(Button, { onClick: handleContinue, disabled: continueDisabled, children: "Continue" })] })] }));
10093
10112
  };
@@ -10135,13 +10154,13 @@ const SelectAmount = () => {
10135
10154
  return (jsx(MultiCurrencySelectAmount, { selectedTokenOption: selectedTokenOption, setSelectedTokenOption: setSelectedTokenOption, nextPage: ROUTES.PAY_WITH_TOKEN }));
10136
10155
  };
10137
10156
 
10138
- const ExternalPaymentSpinner = ({ logoURI, logoShape, showSpinner = true, }) => {
10157
+ const ExternalPaymentSpinner = ({ logoURI, logoShape, }) => {
10139
10158
  const optionSpinner = (() => {
10140
10159
  if (logoShape === "circle") {
10141
- return (jsx(CircleSpinner, { logo: jsx("img", { src: logoURI }), loading: showSpinner, unavailable: false }));
10160
+ return (jsx(CircleSpinner, { logo: jsx("img", { src: logoURI }), loading: false, unavailable: false }));
10142
10161
  }
10143
10162
  else {
10144
- return (jsx(SquircleSpinner, { logo: jsx("img", { src: logoURI }), loading: showSpinner }));
10163
+ return jsx(SquircleSpinner, { logo: jsx("img", { src: logoURI }), loading: false });
10145
10164
  }
10146
10165
  })();
10147
10166
  return (jsx(LoadingContainer$2, { children: jsx(AnimationContainer$1, { "$circle": logoShape === "circle", children: jsx(AnimatePresence, { children: optionSpinner }) }) }));
@@ -10187,7 +10206,7 @@ const SelectDepositAddressAmount = () => {
10187
10206
  paymentState.setChosenUsd(amountUsd);
10188
10207
  setRoute(ROUTES.WAITING_DEPOSIT_ADDRESS, { amountUsd });
10189
10208
  };
10190
- return (jsxs(PageContent, { children: [jsx(ExternalPaymentSpinner, { logoURI: selectedDepositAddressOption.logoURI, logoShape: "circle", showSpinner: false }), jsxs(ModalContent, { "$preserveDisplay": true, children: [jsx(AmountInputContainer$1, { children: jsx(AmountInputField, { value: usdInput, onChange: handleAmountChange, onKeyDown: handleKeyDown }) }), message && jsx(ModalBody, { children: message }), jsx(Button, { onClick: handleContinue, disabled: continueDisabled, children: "Continue" })] })] }));
10209
+ return (jsxs(PageContent, { children: [jsx(ExternalPaymentSpinner, { logoURI: selectedDepositAddressOption.logoURI, logoShape: "circle" }), jsxs(ModalContent, { "$preserveDisplay": true, children: [jsx(AmountInputContainer$1, { children: jsx(AmountInputField, { value: usdInput, onChange: handleAmountChange, onKeyDown: handleKeyDown }) }), message && jsx(ModalBody, { children: message }), jsx(Button, { onClick: handleContinue, disabled: continueDisabled, children: "Continue" })] })] }));
10191
10210
  };
10192
10211
  const AmountInputContainer$1 = styled.div `
10193
10212
  display: flex;
@@ -10449,7 +10468,7 @@ const IconStackItem = styled(motion.div) `
10449
10468
  border-radius: 22.5%;
10450
10469
  `;
10451
10470
 
10452
- const OptionsContainer = styled$1.div `
10471
+ const OptionsContainer = styled.div `
10453
10472
  width: 100%;
10454
10473
  margin-top: 1rem;
10455
10474
  `;
@@ -10598,7 +10617,7 @@ const SelectExternalAmount = () => {
10598
10617
  paymentState.setChosenUsd(amountUsd);
10599
10618
  setRoute(ROUTES.WAITING_EXTERNAL, { amountUsd });
10600
10619
  };
10601
- return (jsxs(PageContent, { children: [jsx(ExternalPaymentSpinner, { logoURI: selectedExternalOption.logoURI, logoShape: selectedExternalOption.logoShape, showSpinner: false }), jsxs(ModalContent, { "$preserveDisplay": true, children: [jsx(AmountInputContainer, { children: jsx(AmountInputField, { value: usdInput, onChange: handleAmountChange, onKeyDown: handleKeyDown }) }), message && jsx(ModalBody, { children: message }), jsx(Button, { onClick: handleContinue, disabled: continueDisabled, children: "Continue" })] })] }));
10620
+ return (jsxs(PageContent, { children: [jsx(ExternalPaymentSpinner, { logoURI: selectedExternalOption.logoURI, logoShape: selectedExternalOption.logoShape }), jsxs(ModalContent, { "$preserveDisplay": true, children: [jsx(AmountInputContainer, { children: jsx(AmountInputField, { value: usdInput, onChange: handleAmountChange, onKeyDown: handleKeyDown }) }), message && jsx(ModalBody, { children: message }), jsx(Button, { onClick: handleContinue, disabled: continueDisabled, children: "Continue" })] })] }));
10602
10621
  };
10603
10622
  const AmountInputContainer = styled.div `
10604
10623
  display: flex;
@@ -10630,7 +10649,7 @@ function SelectMethod() {
10630
10649
  const { connected: isSolanaConnected, wallet: solanaWallet, publicKey, } = useWallet$1();
10631
10650
  const { setRoute, paymentState, wcWallet, log } = usePayContext();
10632
10651
  const { disconnectAsync } = useDisconnect();
10633
- const { daimoPayOrder, setSelectedExternalOption, externalPaymentOptions, depositAddressOptions, senderEnsName, } = paymentState;
10652
+ const { daimoPayOrder, setSelectedExternalOption, externalPaymentOptions, showSolanaPaymentMethod, depositAddressOptions, senderEnsName, } = paymentState;
10634
10653
  const paymentOptions = daimoPayOrder?.metadata.payer?.paymentOptions;
10635
10654
  const getConnectedWalletOptions = () => {
10636
10655
  const showChainLogo = isEthConnected && isSolanaConnected;
@@ -10665,7 +10684,7 @@ function SelectMethod() {
10665
10684
  };
10666
10685
  connectedOptions.push(connectedEthWalletOption);
10667
10686
  }
10668
- if (isSolanaConnected && includeSolana) {
10687
+ if (isSolanaConnected && showSolanaPaymentMethod) {
10669
10688
  const solWalletDisplayName = getAddressContraction(publicKey?.toBase58() ?? "");
10670
10689
  const connectedSolWalletOption = {
10671
10690
  id: "connectedSolanaWallet",
@@ -10690,13 +10709,9 @@ function SelectMethod() {
10690
10709
  }
10691
10710
  return connectedOptions;
10692
10711
  };
10693
- // Solana payment option
10694
- // Include by default if paymentOptions not provided
10695
- const includeSolana = paymentOptions == null ||
10696
- paymentOptions.includes(ExternalPaymentOptions.Solana);
10697
10712
  // Deposit address options, e.g. Bitcoin, Tron, Zcash, etc.
10698
10713
  // Include by default if paymentOptions not provided
10699
- const includeDepositAddressOption = paymentOptions == null ||
10714
+ const showDepositAddressMethod = paymentOptions == null ||
10700
10715
  paymentOptions.includes(ExternalPaymentOptions.ExternalChains);
10701
10716
  const connectedWalletOptions = getConnectedWalletOptions();
10702
10717
  const unconnectedWalletOption = {
@@ -10714,7 +10729,7 @@ function SelectMethod() {
10714
10729
  options.push(...connectedWalletOptions);
10715
10730
  options.push(unconnectedWalletOption);
10716
10731
  log(`[SELECT_METHOD] loading: ${externalPaymentOptions.loading}, options: ${JSON.stringify(externalPaymentOptions.options)}`);
10717
- if (includeSolana) {
10732
+ if (showSolanaPaymentMethod) {
10718
10733
  const solanaOption = getSolanaOption(isIOS);
10719
10734
  if (solanaOption) {
10720
10735
  options.push(solanaOption);
@@ -10738,7 +10753,7 @@ function SelectMethod() {
10738
10753
  disabled: option.disabled,
10739
10754
  subtitle: option.message,
10740
10755
  })));
10741
- if (includeDepositAddressOption) {
10756
+ if (showDepositAddressMethod) {
10742
10757
  const depositAddressOption = getDepositAddressOption(depositAddressOptions);
10743
10758
  options.push(depositAddressOption);
10744
10759
  }
@@ -10998,7 +11013,7 @@ var PayState;
10998
11013
  })(PayState || (PayState = {}));
10999
11014
  const PayWithSolanaToken = () => {
11000
11015
  const { triggerResize, paymentState, setRoute } = usePayContext();
11001
- const { payParams, generatePreviewOrder, selectedSolanaTokenOption, payWithSolanaToken, } = paymentState;
11016
+ const { selectedSolanaTokenOption, payWithSolanaToken } = paymentState;
11002
11017
  const [payState, setPayState] = useState(PayState.RequestingPayment);
11003
11018
  const handleTransfer = async () => {
11004
11019
  try {
@@ -11135,6 +11150,7 @@ const WaitingExternal = () => {
11135
11150
  const context = usePayContext();
11136
11151
  const { triggerResize, paymentState, setRoute } = context;
11137
11152
  const trpc = context.trpc;
11153
+ const { isMobile } = useIsMobile();
11138
11154
  const { selectedExternalOption, payWithExternal, paymentWaitingMessage, daimoPayOrder, } = paymentState;
11139
11155
  const [externalURL, setExternalURL] = useState(null);
11140
11156
  useEffect(() => {
@@ -11160,17 +11176,19 @@ const WaitingExternal = () => {
11160
11176
  });
11161
11177
  }, [selectedExternalOption]);
11162
11178
  const openExternalWindow = (url) => {
11163
- if (selectedExternalOption?.id === "Coinbase") {
11164
- //opening Coinbase onramp in a popup window in portrait mode in the center of the screen
11179
+ if (isMobile) {
11180
+ // on mobile: open in a new tab
11181
+ window.open(url, "_blank");
11182
+ }
11183
+ else {
11184
+ // on desktop: open in a popup window in
11185
+ // portrait mode in the center of the screen
11165
11186
  const width = 500;
11166
11187
  const height = 700;
11167
11188
  const left = Math.max(0, Math.floor((window.innerWidth - width) / 2) + window.screenX);
11168
11189
  const top = Math.max(0, Math.floor((window.innerHeight - height) / 2) + window.screenY);
11169
11190
  window.open(url, "popupWindow", `width=${width},height=${height},left=${left},top=${top},scrollbars=yes`);
11170
11191
  }
11171
- else {
11172
- window.open(url, "_blank");
11173
- }
11174
11192
  };
11175
11193
  const waitingMessageLength = paymentWaitingMessage?.length;
11176
11194
  useEffect(() => {
@@ -11186,18 +11204,18 @@ const WaitingExternal = () => {
11186
11204
  }, children: selectedExternalOption.cta })] }));
11187
11205
  };
11188
11206
 
11189
- const customThemeDefault = {};
11190
- const DaimoPayModal = ({ mode = "auto", theme = "auto", customTheme = customThemeDefault, lang = "en-US", }) => {
11207
+ const DaimoPayModal = ({ mode, theme, customTheme, lang, }) => {
11191
11208
  const context = usePayContext();
11209
+ const { setMode, setTheme, setCustomTheme, setLang } = context;
11192
11210
  const paymentState = context.paymentState;
11193
- const { payParams, generatePreviewOrder, isDepositFlow, setPaymentWaitingMessage, setSelectedExternalOption, setSelectedTokenOption, setSelectedSolanaTokenOption, setSelectedDepositAddressOption, } = paymentState;
11211
+ const { payParams, generatePreviewOrder, isDepositFlow, showSolanaPaymentMethod, setPaymentWaitingMessage, setSelectedExternalOption, setSelectedTokenOption, setSelectedSolanaTokenOption, setSelectedDepositAddressOption, } = paymentState;
11194
11212
  const { isConnected: isEthConnected, connector, chain, address, } = useAccount();
11195
11213
  const { connected: isSolanaConnected } = useWallet$1();
11196
11214
  const { daimoPayOrder } = paymentState;
11197
11215
  const paymentOptions = daimoPayOrder?.metadata.payer?.paymentOptions;
11198
11216
  // Solana payment option
11199
11217
  // Include by default if paymentOptions not provided
11200
- const includeSolana = paymentOptions == null ||
11218
+ paymentOptions == null ||
11201
11219
  paymentOptions.includes(ExternalPaymentOptions.Solana);
11202
11220
  const chainIsSupported = useChainIsSupported(chain?.id);
11203
11221
  //if chain is unsupported we enforce a "switch chain" prompt
@@ -11317,34 +11335,46 @@ const DaimoPayModal = ({ mode = "auto", theme = "auto", customTheme = customThem
11317
11335
  }
11318
11336
  context.setOpen(false, { event: "click-close" });
11319
11337
  }
11320
- // Separate effect for initial route when modal opens
11338
+ // If the user has a wallet already connected upon opening the modal, go
11339
+ // straight to the select token screen
11321
11340
  useEffect(() => {
11322
- if (context.open &&
11323
- !isSolanaConnected &&
11324
- (context.wcWallet || isEthConnected)) {
11325
- // Only set initial route if we're at SELECT_METHOD when opening
11326
- if (context.route === ROUTES.SELECT_METHOD) {
11327
- context.setRoute(ROUTES.SELECT_TOKEN, {
11328
- event: "eth_connected_on_open",
11329
- walletId: connector?.id,
11330
- chainId: chain?.id,
11331
- address,
11332
- });
11333
- }
11341
+ if (!context.open)
11342
+ return;
11343
+ if (context.route !== ROUTES.SELECT_METHOD)
11344
+ return;
11345
+ const ethMethodAvailable = context.wcWallet != null || isEthConnected;
11346
+ const solanaMethodAvailable = isSolanaConnected && showSolanaPaymentMethod;
11347
+ // Skip to token selection if exactly one wallet is connected. If both
11348
+ // wallets are connected, stay on the SELECT_METHOD screen to allow the
11349
+ // user to select which wallet to use
11350
+ if (ethMethodAvailable && !solanaMethodAvailable) {
11351
+ context.setRoute(ROUTES.SELECT_TOKEN, {
11352
+ event: "eth_connected_on_open",
11353
+ walletId: connector?.id,
11354
+ chainId: chain?.id,
11355
+ address,
11356
+ });
11334
11357
  }
11335
- else if (context.open &&
11336
- isSolanaConnected &&
11337
- !isEthConnected &&
11338
- context.wcWallet === undefined &&
11339
- includeSolana) {
11340
- if (context.route === ROUTES.SELECT_METHOD) {
11341
- context.setRoute(ROUTES.SOLANA_SELECT_TOKEN, {
11342
- event: "solana_connected_on_open",
11343
- });
11344
- }
11358
+ else if (solanaMethodAvailable && !ethMethodAvailable) {
11359
+ context.setRoute(ROUTES.SOLANA_SELECT_TOKEN, {
11360
+ event: "solana_connected_on_open",
11361
+ });
11345
11362
  }
11346
- // If both are connected, do nothing and stay on SELECT_METHOD
11347
- }, [context.open]);
11363
+ // Don't include context.route in the dependency array otherwise the user
11364
+ // can't go back from the select token screen to the select method screen
11365
+ // eslint-disable-next-line react-hooks/exhaustive-deps
11366
+ }, [
11367
+ context.open,
11368
+ context.wcWallet,
11369
+ isEthConnected,
11370
+ isSolanaConnected,
11371
+ showSolanaPaymentMethod,
11372
+ address,
11373
+ chain?.id,
11374
+ connector?.id,
11375
+ ]);
11376
+ // If we're on the connect page and the user successfully connects their
11377
+ // wallet, go to the select token page
11348
11378
  useEffect(() => {
11349
11379
  if (context.route === ROUTES.CONNECT ||
11350
11380
  context.route === ROUTES.CONNECTORS ||
@@ -11358,11 +11388,12 @@ const DaimoPayModal = ({ mode = "auto", theme = "auto", customTheme = customThem
11358
11388
  });
11359
11389
  }
11360
11390
  }
11361
- }, [isEthConnected, context.route]);
11362
- useEffect(() => context.setMode(mode), [mode]);
11363
- useEffect(() => context.setTheme(theme), [theme]);
11364
- useEffect(() => context.setCustomTheme(customTheme), [customTheme]);
11365
- useEffect(() => context.setLang(lang), [lang]);
11391
+ // eslint-disable-next-line react-hooks/exhaustive-deps
11392
+ }, [isEthConnected, context.route, connector?.id, chain?.id, address]);
11393
+ useEffect(() => setMode(mode), [mode, setMode]);
11394
+ useEffect(() => setTheme(theme), [theme, setTheme]);
11395
+ useEffect(() => setCustomTheme(customTheme), [customTheme, setCustomTheme]);
11396
+ useEffect(() => setLang(lang), [lang, setLang]);
11366
11397
  /* When pulling data into WalletConnect, it prioritises the og:title tag over the title tag */
11367
11398
  useEffect(() => {
11368
11399
  const appName = getAppName();
@@ -11466,7 +11497,10 @@ const DaimoPayProviderWithoutSolana = ({ children, theme = "auto", mode = "auto"
11466
11497
  onCloseRef.current = fn;
11467
11498
  }, []);
11468
11499
  const [open, setOpenState] = useState(false);
11500
+ const [lockPayParams, setLockPayParams] = useState(false);
11501
+ const [paymentCompleted, setPaymentCompleted] = useState(false);
11469
11502
  const [route, setRouteState] = useState(ROUTES.SELECT_METHOD);
11503
+ const [modalOptions, setModalOptions] = useState();
11470
11504
  // Daimo Pay context
11471
11505
  const [daimoPayOrder, setDaimoPayOrderInner] = useState();
11472
11506
  const [pendingConnectorId, setPendingConnectorId] = useState(undefined);
@@ -11477,22 +11511,46 @@ const DaimoPayProviderWithoutSolana = ({ children, theme = "auto", mode = "auto"
11477
11511
  const [errorMessage, setErrorMessage] = useState("");
11478
11512
  const [confirmationMessage, setConfirmationMessage] = useState(undefined);
11479
11513
  const [redirectReturnUrl, setRedirectReturnUrl] = useState(undefined);
11480
- const log = debugMode ? console.log : () => { };
11514
+ const log = useMemo(() => (debugMode ? console.log : () => { }), [debugMode]);
11481
11515
  // Connect to the Daimo Pay TRPC API
11482
- const trpc = useMemo(() => createTrpcClient(payApiUrl, sessionId), [payApiUrl]);
11516
+ const trpc = useMemo(() => createTrpcClient(payApiUrl, sessionId), [payApiUrl, sessionId]);
11483
11517
  const [resize, onResize] = useState(0);
11484
11518
  const setOpen = useCallback((open, meta) => {
11485
11519
  setOpenState(open);
11520
+ // Lock pay params starting from the first time the modal is opened to
11521
+ // prevent the daimo pay order from changing from under the user
11522
+ if (open) {
11523
+ setLockPayParams(true);
11524
+ }
11525
+ // Reset payment state on close if resetOnSuccess is true
11526
+ if (!open && paymentCompleted && modalOptions?.resetOnSuccess) {
11527
+ setPaymentCompleted(false);
11528
+ setLockPayParams(false);
11529
+ paymentState.resetOrder();
11530
+ }
11531
+ // Log the open/close event
11486
11532
  trpc.nav.mutate({
11487
11533
  action: open ? "navOpenPay" : "navClosePay",
11488
11534
  orderId: daimoPayOrder?.id?.toString(),
11489
11535
  data: meta ?? {},
11490
11536
  });
11537
+ // Run the onOpen and onClose callbacks
11491
11538
  if (open)
11492
11539
  onOpenRef.current?.();
11493
11540
  else
11494
11541
  onCloseRef.current?.();
11495
- }, [trpc, daimoPayOrder?.id]);
11542
+ },
11543
+ // We don't have good caching on paymentState, so don't include it as a dep
11544
+ // eslint-disable-next-line react-hooks/exhaustive-deps
11545
+ [trpc, daimoPayOrder?.id, modalOptions?.resetOnSuccess, paymentCompleted]);
11546
+ // Callback when a payment is successfully completed (regardless of whether
11547
+ // the final call succeeded or bounced)
11548
+ const onSuccess = useCallback(() => {
11549
+ if (modalOptions?.closeOnSuccess) {
11550
+ setTimeout(() => setOpen(false, { event: "wait-success" }), 1000);
11551
+ }
11552
+ setPaymentCompleted(true);
11553
+ }, [modalOptions?.closeOnSuccess, setOpen, setPaymentCompleted]);
11496
11554
  const setRoute = useCallback((route, data) => {
11497
11555
  const action = route.replace("daimoPay", "");
11498
11556
  log(`[SET ROUTE] ${action} ${daimoPayOrder?.id} ${debugJson(data ?? {})}`);
@@ -11503,23 +11561,11 @@ const DaimoPayProviderWithoutSolana = ({ children, theme = "auto", mode = "auto"
11503
11561
  });
11504
11562
  setRouteState(route);
11505
11563
  }, [trpc, daimoPayOrder?.id, log]);
11506
- // Include Google Font that is needed for a themes
11507
- if (opts.embedGoogleFonts)
11508
- useThemeFont(ckTheme);
11509
11564
  // Other Configuration
11510
11565
  useEffect(() => setTheme(theme), [theme]);
11511
11566
  useEffect(() => setLang(opts.language || "en-US"), [opts.language]);
11512
11567
  useEffect(() => setErrorMessage(null), [route, open]);
11513
- // Check if chain is supported, elsewise redirect to switches page
11514
- const { chain, isConnected, connector } = useAccount();
11515
- const isChainSupported = useChainIsSupported(chain?.id);
11516
- useEffect(() => {
11517
- if (isConnected && opts.enforceSupportedChains && !isChainSupported) {
11518
- setOpen(true);
11519
- if (route !== ROUTES.SWITCHNETWORKS)
11520
- setRoute(ROUTES.SWITCHNETWORKS);
11521
- }
11522
- }, [isConnected, isChainSupported, chain, route, open]);
11568
+ const { connector } = useAccount();
11523
11569
  // Single source of truth for the currently-connected wallet is the connector
11524
11570
  // exposed by wagmi. See useAccount(). We watch this connector and use it to
11525
11571
  // extract the current WalletConnect wallet, if any.
@@ -11530,6 +11576,10 @@ const DaimoPayProviderWithoutSolana = ({ children, theme = "auto", mode = "auto"
11530
11576
  // set refresh context when payment status changes; done via setDaimoPayOrder.
11531
11577
  const setDaimoPayOrder = useCallback((order) => {
11532
11578
  setDaimoPayOrderInner(order);
11579
+ if (order == null) {
11580
+ log(`[PAY] setDaimoPayOrder: reset`);
11581
+ return;
11582
+ }
11533
11583
  let extra = `> $${order.destFinalCallTokenAmount.usd.toFixed(2)} to ${order.destFinalCallTokenAmount.token.chainId} ${order.destFinalCall.to}`;
11534
11584
  if (order.mode === DaimoPayOrderMode.HYDRATED) {
11535
11585
  extra += ` via ${order.intentAddr} ${order.sourceStatus} ${order.intentStatus}`;
@@ -11538,9 +11588,10 @@ const DaimoPayProviderWithoutSolana = ({ children, theme = "auto", mode = "auto"
11538
11588
  }, [log]);
11539
11589
  const paymentState = usePaymentState({
11540
11590
  trpc,
11591
+ lockPayParams,
11541
11592
  daimoPayOrder,
11542
11593
  setDaimoPayOrder,
11543
- setOpen,
11594
+ setRoute,
11544
11595
  log,
11545
11596
  redirectReturnUrl,
11546
11597
  });
@@ -11564,11 +11615,13 @@ const DaimoPayProviderWithoutSolana = ({ children, theme = "auto", mode = "auto"
11564
11615
  log(`[PAY] polling in ${intervalMs}ms`);
11565
11616
  const timeout = setTimeout(() => retryBackoff("refreshOrder", () => paymentState.refreshOrder()), intervalMs);
11566
11617
  return () => clearTimeout(timeout);
11567
- }, [daimoPayOrder]);
11618
+ // We don't have good caching on paymentState, so don't include it as a dep
11619
+ // eslint-disable-next-line react-hooks/exhaustive-deps
11620
+ }, [daimoPayOrder, log]);
11568
11621
  const showPayment = async (modalOptions) => {
11569
11622
  const id = daimoPayOrder?.id;
11570
11623
  log(`[PAY] showing payment ${debugJson({ id, modalOptions })}`);
11571
- paymentState.setModalOptions(modalOptions);
11624
+ setModalOptions(modalOptions);
11572
11625
  setOpen(true);
11573
11626
  if (daimoPayOrder &&
11574
11627
  daimoPayOrder.mode === DaimoPayOrderMode.HYDRATED &&
@@ -11608,6 +11661,7 @@ const DaimoPayProviderWithoutSolana = ({ children, theme = "auto", mode = "auto"
11608
11661
  // Other configuration
11609
11662
  options: opts,
11610
11663
  errorMessage,
11664
+ onSuccess,
11611
11665
  confirmationMessage,
11612
11666
  setConfirmationMessage,
11613
11667
  redirectReturnUrl,
@@ -11891,6 +11945,7 @@ function DaimoPayButtonCustom(props) {
11891
11945
  else if (payParams != null) {
11892
11946
  paymentState.setPayParams(payParams);
11893
11947
  }
11948
+ // eslint-disable-next-line react-hooks/exhaustive-deps
11894
11949
  }, [payId, JSON.stringify(payParams || {})]);
11895
11950
  // Set the confirmation message
11896
11951
  const { setConfirmationMessage } = context;
@@ -11922,30 +11977,32 @@ function DaimoPayButtonCustom(props) {
11922
11977
  const intentStatus = order?.intentStatus;
11923
11978
  const hydOrder = order?.mode === DaimoPayOrderMode.HYDRATED ? order : null;
11924
11979
  // Functions to show and hide the modal
11925
- const { children, closeOnSuccess } = props;
11926
- const modalOptions = { closeOnSuccess };
11927
- const show = () => {
11980
+ const { children, closeOnSuccess, resetOnSuccess } = props;
11981
+ const show = useCallback(() => {
11928
11982
  if (paymentState.daimoPayOrder == null)
11929
11983
  return;
11984
+ const modalOptions = { closeOnSuccess, resetOnSuccess };
11930
11985
  context.showPayment(modalOptions);
11931
- };
11932
- const hide = () => context.setOpen(false);
11986
+ }, [context, paymentState.daimoPayOrder, closeOnSuccess, resetOnSuccess]);
11987
+ const hide = useCallback(() => context.setOpen(false), [context]);
11933
11988
  // Emit event handlers when payment status changes
11989
+ const sentStart = useRef(false);
11934
11990
  useEffect(() => {
11935
11991
  if (hydOrder == null)
11936
11992
  return;
11937
11993
  if (intentStatus === DaimoPayIntentStatus.UNPAID)
11938
11994
  return;
11939
- if (intentStatus === DaimoPayIntentStatus.STARTED) {
11995
+ if (!sentStart.current && hydOrder.sourceTokenAmount) {
11996
+ sentStart.current = true;
11940
11997
  onPaymentStarted?.({
11941
11998
  type: DaimoPayIntentStatus.STARTED,
11942
11999
  paymentId: writeDaimoPayOrderID(hydOrder.id),
11943
- chainId: hydOrder.destFinalCallTokenAmount.token.chainId,
11944
- txHash: assertNotNull(hydOrder.sourceInitiateTxHash, `[PAY BUTTON] source initiate tx hash null on order ${hydOrder.id} when intent status is ${intentStatus}`),
12000
+ chainId: hydOrder.sourceTokenAmount?.token.chainId,
12001
+ txHash: hydOrder.sourceInitiateTxHash ?? null,
11945
12002
  payment: getDaimoPayOrderView(hydOrder),
11946
12003
  });
11947
12004
  }
11948
- else if (intentStatus === DaimoPayIntentStatus.COMPLETED ||
12005
+ if (intentStatus === DaimoPayIntentStatus.COMPLETED ||
11949
12006
  intentStatus === DaimoPayIntentStatus.BOUNCED) {
11950
12007
  const event = {
11951
12008
  type: intentStatus,
@@ -11961,7 +12018,7 @@ function DaimoPayButtonCustom(props) {
11961
12018
  onPaymentBounced?.(event);
11962
12019
  }
11963
12020
  }
11964
- }, [hydOrder?.id, intentStatus]);
12021
+ }, [hydOrder?.id, intentStatus, hydOrder?.sourceTokenAmount?.token.chainId]);
11965
12022
  // Open the modal by default if the defaultOpen prop is true
11966
12023
  useEffect(() => {
11967
12024
  if (props.defaultOpen && order != null) {