@daimo/pay 1.7.2 → 1.7.4

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,5 +1,5 @@
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
4
  import React, { useState, useEffect, createContext, useCallback, useRef, useLayoutEffect, useMemo, createElement } from 'react';
5
5
  import styled$1, { css, keyframes, ThemeProvider } from 'styled-components';
@@ -7,7 +7,7 @@ import { http, useConfig, useAccountEffect, useWriteContract, useSendTransaction
7
7
  import { mainnet, base as base$1, polygon, optimism, arbitrum, linea, bsc, sepolia, baseSepolia, worldchain, blast, mantle } from 'wagmi/chains';
8
8
  import { safe, injected, coinbaseWallet, walletConnect } from '@wagmi/connectors';
9
9
  import { useConnection, useWallet as useWallet$1, ConnectionProvider, WalletProvider } from '@solana/wallet-adapter-react';
10
- import { hexToBytes, zeroAddress, erc20Abi, getAddress, parseUnits, formatUnits } from 'viem';
10
+ import { hexToBytes, zeroAddress, erc20Abi, getAddress, formatUnits, parseUnits } from 'viem';
11
11
  import { VersionedTransaction } from '@solana/web3.js';
12
12
  import { createTRPCClient, httpBatchLink } from '@trpc/client';
13
13
  import { motion, AnimatePresence, MotionConfig } from 'framer-motion';
@@ -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.2";
25
+ var version = "1.7.4";
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.2",
64
+ "@daimo/pay-common": "1.7.4",
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",
@@ -377,7 +377,7 @@ const OtherWallets = ({ ...props }) => {
377
377
  overflow: "hidden",
378
378
  borderRadius: "27.5%",
379
379
  };
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, {}) })] })] }));
380
+ 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
381
  };
382
382
  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
383
  const SquircleIcon = ({ icon, alt, }) => {
@@ -580,11 +580,12 @@ const walletConfigs = {
580
580
  edge: "https://rainbow.me/extension?utm_source=daimopay",
581
581
  brave: "https://rainbow.me/extension?utm_source=daimopay",
582
582
  },
583
- showInMobileConnectors: true,
583
+ showInMobileConnectors: false,
584
584
  isWcMobileConnector: false,
585
585
  getWalletConnectDeeplink: (uri) => {
586
- return `https://rnbwapp.com/wc?uri=${encodeURIComponent(uri)}&connector=daimopay`;
586
+ return `rainbow://wc?uri=${encodeURIComponent(uri)}&connector=daimopay`;
587
587
  },
588
+ walletDeepLink: "rainbow://",
588
589
  },
589
590
  "io.rabby": {
590
591
  name: "Rabby Wallet",
@@ -732,7 +733,7 @@ const walletConfigs = {
732
733
  getWalletConnectDeeplink: (uri) => {
733
734
  return `https://app.zerion.io/wc?uri=${encodeURIComponent(uri)}`;
734
735
  },
735
- showInMobileConnectors: false,
736
+ showInMobileConnectors: true,
736
737
  },
737
738
  slope: {
738
739
  name: "Slope",
@@ -838,7 +839,7 @@ function extractWcWalletFromProvider(p, log) {
838
839
  isWcMobileConnector: true,
839
840
  };
840
841
  }
841
- log(`[WCWALLET] name: ${name} wcWallet: ${wallet?.name} isWcMobileConnector: ${wallet?.isWcMobileConnector} provider: ${p}`);
842
+ log(`[WCWALLET] name: ${name} wcWallet: ${wallet?.name} isWcMobileConnector: ${wallet?.isWcMobileConnector} provider: `, p);
842
843
  return wallet;
843
844
  }
844
845
 
@@ -997,7 +998,7 @@ function useOrderUsdLimits({ trpc }) {
997
998
  return { limits, loading };
998
999
  }
999
1000
 
1000
- function usePayWithSolanaToken({ trpc, daimoPayOrder, setDaimoPayOrder, createOrHydrate, log, }) {
1001
+ function usePayWithSolanaToken({ trpc, refundAddress, daimoPayOrder, setDaimoPayOrder, createOrHydrate, log, }) {
1001
1002
  const { connection } = useConnection();
1002
1003
  const wallet = useWallet$1();
1003
1004
  const payWithSolanaToken = async (inputToken) => {
@@ -1006,6 +1007,7 @@ function usePayWithSolanaToken({ trpc, daimoPayOrder, setDaimoPayOrder, createOr
1006
1007
  const orderId = daimoPayOrder.id;
1007
1008
  const { hydratedOrder } = await createOrHydrate({
1008
1009
  order: daimoPayOrder,
1010
+ refundAddress,
1009
1011
  });
1010
1012
  log(`[CHECKOUT] Hydrated order: ${JSON.stringify(hydratedOrder)}, checking out with Solana ${inputToken}`);
1011
1013
  const txHash = await (async () => {
@@ -1040,7 +1042,7 @@ function usePayWithSolanaToken({ trpc, daimoPayOrder, setDaimoPayOrder, createOr
1040
1042
  return { payWithSolanaToken };
1041
1043
  }
1042
1044
 
1043
- function usePayWithToken({ trpc, senderAddr, daimoPayOrder, setDaimoPayOrder, createOrHydrate, log, }) {
1045
+ function usePayWithToken({ trpc, senderAddr, refundAddress, daimoPayOrder, setDaimoPayOrder, createOrHydrate, log, }) {
1044
1046
  const { writeContractAsync } = useWriteContract();
1045
1047
  const { sendTransactionAsync } = useSendTransaction();
1046
1048
  /** Commit to a token + amount = initiate payment. */
@@ -1051,7 +1053,8 @@ function usePayWithToken({ trpc, senderAddr, daimoPayOrder, setDaimoPayOrder, cr
1051
1053
  const paymentAmount = BigInt(required.amount) + BigInt(fees.amount);
1052
1054
  const { hydratedOrder } = await createOrHydrate({
1053
1055
  order: daimoPayOrder,
1054
- refundAddress: senderAddr,
1056
+ // Use the developer-provided refund address. Default to the sender.
1057
+ refundAddress: refundAddress ?? senderAddr,
1055
1058
  });
1056
1059
  log(`[PAY TOKEN] hydrated order: ${JSON.stringify(hydratedOrder)}, paying ${paymentAmount} of token ${required.token.token}`);
1057
1060
  setDaimoPayOrder(hydratedOrder);
@@ -1187,6 +1190,14 @@ function usePaymentState({ trpc, daimoPayOrder, setDaimoPayOrder, setOpen, log,
1187
1190
  // Solana wallet state.
1188
1191
  const solanaWallet = useWallet$1();
1189
1192
  const solanaPubKey = solanaWallet.publicKey?.toBase58();
1193
+ // TODO: backend should determine whether to show solana payment method
1194
+ const paymentOptions = daimoPayOrder?.metadata.payer?.paymentOptions;
1195
+ // Include by default if paymentOptions not provided. Solana bridging is only
1196
+ // supported on CCTP v1 chains.
1197
+ const showSolanaPaymentMethod = (paymentOptions == null ||
1198
+ paymentOptions.includes(ExternalPaymentOptions.Solana)) &&
1199
+ daimoPayOrder != null &&
1200
+ isCCTPV1Chain(getOrderDestChainId(daimoPayOrder));
1190
1201
  // Daimo Pay order state.
1191
1202
  const [payParams, setPayParamsState] = useState();
1192
1203
  const [paymentWaitingMessage, setPaymentWaitingMessage] = useState();
@@ -1260,6 +1271,7 @@ function usePaymentState({ trpc, daimoPayOrder, setDaimoPayOrder, setOpen, log,
1260
1271
  const { payWithToken } = usePayWithToken({
1261
1272
  trpc,
1262
1273
  senderAddr,
1274
+ refundAddress: payParams?.refundAddress,
1263
1275
  daimoPayOrder,
1264
1276
  setDaimoPayOrder,
1265
1277
  createOrHydrate,
@@ -1267,6 +1279,7 @@ function usePaymentState({ trpc, daimoPayOrder, setDaimoPayOrder, setOpen, log,
1267
1279
  });
1268
1280
  const { payWithSolanaToken } = usePayWithSolanaToken({
1269
1281
  trpc,
1282
+ refundAddress: payParams?.refundAddress,
1270
1283
  daimoPayOrder,
1271
1284
  setDaimoPayOrder,
1272
1285
  createOrHydrate,
@@ -1333,11 +1346,11 @@ function usePaymentState({ trpc, daimoPayOrder, setDaimoPayOrder, setOpen, log,
1333
1346
  const setChosenUsd = (usd) => {
1334
1347
  assert(!!daimoPayOrder, "[SET CHOSEN USD] daimoPayOrder cannot be null");
1335
1348
  const token = daimoPayOrder.destFinalCallTokenAmount.token;
1336
- const tokenUnits = (usd / token.usd).toString();
1349
+ const tokenUnits = (usd / token.priceFromUsd).toString();
1337
1350
  const tokenAmount = parseUnits(tokenUnits, token.decimals);
1338
1351
  // TODO: remove amount from destFinalCall, it is redundant with
1339
1352
  // destFinalCallTokenAmount. Here, we only modify one and not the other.
1340
- log(`[CHECKOUT] setting chosen USD amount to $${usd} = ${tokenUnits} ${token.symbol}`);
1353
+ log(`[CHECKOUT] chose USD amount $${usd} = ${tokenUnits} ${token.symbol}`);
1341
1354
  setDaimoPayOrder({
1342
1355
  ...daimoPayOrder,
1343
1356
  destFinalCallTokenAmount: {
@@ -1394,6 +1407,7 @@ function usePaymentState({ trpc, daimoPayOrder, setDaimoPayOrder, setOpen, log,
1394
1407
  },
1395
1408
  externalId: payParams.externalId,
1396
1409
  userMetadata: payParams.metadata,
1410
+ refundAddress: payParams.refundAddress,
1397
1411
  });
1398
1412
  log(`[CHECKOUT] generated preview: ${JSON.stringify(orderPreview)}`);
1399
1413
  setDaimoPayOrder(orderPreview);
@@ -1404,6 +1418,9 @@ function usePaymentState({ trpc, daimoPayOrder, setDaimoPayOrder, setOpen, log,
1404
1418
  setTimeout(() => setOpen(false, { event: "wait-success" }), 1000);
1405
1419
  }
1406
1420
  };
1421
+ const resetOrder = () => {
1422
+ setDaimoPayOrder(undefined);
1423
+ };
1407
1424
  return {
1408
1425
  setPayId,
1409
1426
  payParams,
@@ -1418,11 +1435,13 @@ function usePaymentState({ trpc, daimoPayOrder, setDaimoPayOrder, setOpen, log,
1418
1435
  selectedTokenOption,
1419
1436
  selectedSolanaTokenOption,
1420
1437
  externalPaymentOptions,
1438
+ showSolanaPaymentMethod,
1421
1439
  walletPaymentOptions,
1422
1440
  solanaPaymentOptions,
1423
1441
  depositAddressOptions,
1424
1442
  selectedDepositAddressOption,
1425
1443
  getOrderUsdLimit,
1444
+ resetOrder,
1426
1445
  setPaymentWaitingMessage,
1427
1446
  setSelectedExternalOption,
1428
1447
  setSelectedTokenOption,
@@ -4882,9 +4901,39 @@ const useWallet = (id) => {
4882
4901
  return null;
4883
4902
  return wallet;
4884
4903
  };
4885
- const useWallets = () => {
4904
+ const useWallets = (isMobile) => {
4886
4905
  const connectors = useConnectors();
4887
4906
  const context = usePayContext();
4907
+ if (isMobile) {
4908
+ 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
4924
+ connectors.forEach((connector) => {
4925
+ if (connector.id === "metaMask")
4926
+ return;
4927
+ if (isCoinbaseWalletConnector(connector.id))
4928
+ return;
4929
+ mobileWallets.push({
4930
+ id: connector.id,
4931
+ connector,
4932
+ ...walletConfigs[connector.id],
4933
+ });
4934
+ });
4935
+ return mobileWallets;
4936
+ }
4888
4937
  const wallets = connectors.map((connector) => {
4889
4938
  // use overrides
4890
4939
  const walletId = Object.keys(walletConfigs).find(
@@ -4939,8 +4988,8 @@ const useWallets = () => {
4939
4988
  self.find((w) => w.id === "farcaster")))
4940
4989
  // order by isInstalled injected connectors first
4941
4990
  .sort((a, b) => {
4942
- const AisInstalled = a.isInstalled && isInjectedConnector(a.connector.type);
4943
- const BisInstalled = b.isInstalled && isInjectedConnector(b.connector.type);
4991
+ const AisInstalled = a.isInstalled && isInjectedConnector(a.connector?.type);
4992
+ const BisInstalled = b.isInstalled && isInjectedConnector(b.connector?.type);
4944
4993
  if (AisInstalled && !BisInstalled)
4945
4994
  return -1;
4946
4995
  if (!AisInstalled && BisInstalled)
@@ -7241,15 +7290,15 @@ const ConnectorsContainer = styled.div `
7241
7290
  const ConnectorList = () => {
7242
7291
  const context = usePayContext();
7243
7292
  const { isMobile } = useIsMobile();
7244
- const wallets = useWallets();
7293
+ const wallets = useWallets(isMobile);
7245
7294
  const { lastConnectorId } = useLastConnector();
7246
7295
  const walletsToDisplay = context.options?.hideRecentBadge || lastConnectorId === "walletConnect" // do not hoist walletconnect to top of list
7247
7296
  ? wallets
7248
7297
  : [
7249
7298
  // move last used wallet to top of list
7250
7299
  // using .filter and spread to avoid mutating original array order with .sort
7251
- ...wallets.filter((wallet) => lastConnectorId === wallet.connector.id),
7252
- ...wallets.filter((wallet) => lastConnectorId !== wallet.connector.id),
7300
+ ...wallets.filter((wallet) => lastConnectorId === wallet.connector?.id),
7301
+ ...wallets.filter((wallet) => lastConnectorId !== wallet.connector?.id),
7253
7302
  ];
7254
7303
  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))) }))] }));
7255
7304
  };
@@ -7266,7 +7315,7 @@ const ConnectorItem = ({ wallet, isRecent, }) => {
7266
7315
  const redirectToMoreWallets = isMobile && isWalletConnectConnector(wallet.id);
7267
7316
  // Safari requires opening popup on user gesture, so we connect immediately here
7268
7317
  const shouldConnectImmediately = (detectBrowser() === "safari" || detectBrowser() === "ios") &&
7269
- isCoinbaseWalletConnector(wallet.connector.id);
7318
+ isCoinbaseWalletConnector(wallet.connector?.id);
7270
7319
  if (redirectToMoreWallets || shouldConnectImmediately)
7271
7320
  deeplink = undefined; // mobile redirects to more wallets page
7272
7321
  return (jsxs(ConnectorButton, { type: "button", as: deeplink ? "a" : undefined, href: deeplink ? deeplink : undefined, disabled: context.route !== ROUTES.CONNECTORS, onClick: deeplink
@@ -8220,7 +8269,7 @@ const MobileConnectors = () => {
8220
8269
  // filter out installed wallets
8221
8270
  const walletsIdsToDisplay = Object.keys(walletConfigs).filter((walletId) => {
8222
8271
  const wallet = walletConfigs[walletId];
8223
- if (wallets.find((w) => w.connector.id === walletId))
8272
+ if (wallets.find((w) => w.connector?.id === walletId))
8224
8273
  return false;
8225
8274
  if (!wallet.getWalletConnectDeeplink)
8226
8275
  return false;
@@ -9352,7 +9401,7 @@ const ConnectWithInjector = ({ switchConnectMethod, forceState }) => {
9352
9401
  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." }) })] }) }));
9353
9402
  }
9354
9403
  // TODO: Make this more generic
9355
- if (isWalletConnectConnector(wallet?.connector.id)) {
9404
+ if (isWalletConnectConnector(wallet?.connector?.id)) {
9356
9405
  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." }) })] }) }));
9357
9406
  }
9358
9407
  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 &&
@@ -9364,9 +9413,9 @@ const ConnectWithInjector = ({ switchConnectMethod, forceState }) => {
9364
9413
  transform: "scale(1.14)",
9365
9414
  position: "relative",
9366
9415
  width: "100%",
9367
- }, 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"
9416
+ }, 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"
9368
9417
  ? locales.injectionScreen_connecting_injected_h1
9369
- : locales.injectionScreen_connecting_h1 }), jsx(ModalBody, { children: wallet.connector.id === "injected"
9418
+ : locales.injectionScreen_connecting_h1 }), jsx(ModalBody, { children: wallet.connector?.id === "injected"
9370
9419
  ? locales.injectionScreen_connecting_injected_p
9371
9420
  : 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))] }) })] }) }));
9372
9421
  };
@@ -9454,7 +9503,7 @@ const ConnectUsing = () => {
9454
9503
  useEffect(() => {
9455
9504
  // if no provider, change to qrcode
9456
9505
  const checkProvider = async () => {
9457
- const res = await wallet?.connector.getProvider();
9506
+ const res = await wallet?.connector?.getProvider();
9458
9507
  if (!res) {
9459
9508
  setStatus(states.QRCODE);
9460
9509
  setTimeout(context.triggerResize, 10); // delay required here for modal to resize
@@ -9756,8 +9805,8 @@ const ChainLogoContainer = styled(motion.div) `
9756
9805
  }
9757
9806
  `;
9758
9807
 
9759
- const TokenLogoSpinner = ({ token, showSpinner = true, }) => {
9760
- 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 }, token.logoURI), loading: showSpinner, unavailable: false }, "CircleSpinner")] }) }) }));
9808
+ const TokenLogoSpinner = ({ token }) => {
9809
+ 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")] }) }) }));
9761
9810
  };
9762
9811
 
9763
9812
  var PayState$1;
@@ -9864,12 +9913,12 @@ const PayWithToken = () => {
9864
9913
  if (selectedTokenOption == null) {
9865
9914
  return jsx(PageContent, {});
9866
9915
  }
9867
- 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
9916
+ 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
9868
9917
  ? () => handleTransfer(selectedTokenOption)
9869
9918
  : undefined, href: wcWallet.isWcMobileConnector
9870
9919
  ? undefined
9871
9920
  : wcWallet.walletDeepLink ||
9872
- wcWallet.getWalletConnectDeeplink?.(""), children: ["Pay with ", wcWallet.name] })), payState === PayState$1.RequestCancelled && (jsx(Button, { onClick: () => handleTransfer(selectedTokenOption), children: "Retry Payment" }))] })] }));
9921
+ wcWallet.getWalletConnectDeeplink?.(""), children: "Tap Here to Pay" })), payState === PayState$1.RequestCancelled && (jsx(Button, { onClick: () => handleTransfer(selectedTokenOption), children: "Retry Payment" }))] })] }));
9873
9922
  };
9874
9923
 
9875
9924
  /**
@@ -10082,7 +10131,7 @@ const MultiCurrencySelectAmount = ({ selectedTokenOption, setSelectedTokenOption
10082
10131
  tokenSymbol: balanceToken.symbol,
10083
10132
  });
10084
10133
  };
10085
- 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
10134
+ 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
10086
10135
  ? `${tokenValue} ${balanceToken.symbol}`
10087
10136
  : `$${usdValue}` }) }) })), message && jsx(ModalBody, { children: message }), jsx(Button, { onClick: handleContinue, disabled: continueDisabled, children: "Continue" })] })] }));
10088
10137
  };
@@ -10130,13 +10179,13 @@ const SelectAmount = () => {
10130
10179
  return (jsx(MultiCurrencySelectAmount, { selectedTokenOption: selectedTokenOption, setSelectedTokenOption: setSelectedTokenOption, nextPage: ROUTES.PAY_WITH_TOKEN }));
10131
10180
  };
10132
10181
 
10133
- const ExternalPaymentSpinner = ({ logoURI, logoShape, showSpinner = true, }) => {
10182
+ const ExternalPaymentSpinner = ({ logoURI, logoShape, }) => {
10134
10183
  const optionSpinner = (() => {
10135
10184
  if (logoShape === "circle") {
10136
- return (jsx(CircleSpinner, { logo: jsx("img", { src: logoURI }), loading: showSpinner, unavailable: false }));
10185
+ return (jsx(CircleSpinner, { logo: jsx("img", { src: logoURI }), loading: false, unavailable: false }));
10137
10186
  }
10138
10187
  else {
10139
- return (jsx(SquircleSpinner, { logo: jsx("img", { src: logoURI }), loading: showSpinner }));
10188
+ return jsx(SquircleSpinner, { logo: jsx("img", { src: logoURI }), loading: false });
10140
10189
  }
10141
10190
  })();
10142
10191
  return (jsx(LoadingContainer$2, { children: jsx(AnimationContainer$1, { "$circle": logoShape === "circle", children: jsx(AnimatePresence, { children: optionSpinner }) }) }));
@@ -10182,7 +10231,7 @@ const SelectDepositAddressAmount = () => {
10182
10231
  paymentState.setChosenUsd(amountUsd);
10183
10232
  setRoute(ROUTES.WAITING_DEPOSIT_ADDRESS, { amountUsd });
10184
10233
  };
10185
- 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" })] })] }));
10234
+ 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" })] })] }));
10186
10235
  };
10187
10236
  const AmountInputContainer$1 = styled.div `
10188
10237
  display: flex;
@@ -10444,7 +10493,7 @@ const IconStackItem = styled(motion.div) `
10444
10493
  border-radius: 22.5%;
10445
10494
  `;
10446
10495
 
10447
- const OptionsContainer = styled$1.div `
10496
+ const OptionsContainer = styled.div `
10448
10497
  width: 100%;
10449
10498
  margin-top: 1rem;
10450
10499
  `;
@@ -10593,7 +10642,7 @@ const SelectExternalAmount = () => {
10593
10642
  paymentState.setChosenUsd(amountUsd);
10594
10643
  setRoute(ROUTES.WAITING_EXTERNAL, { amountUsd });
10595
10644
  };
10596
- 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" })] })] }));
10645
+ 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" })] })] }));
10597
10646
  };
10598
10647
  const AmountInputContainer = styled.div `
10599
10648
  display: flex;
@@ -10625,7 +10674,7 @@ function SelectMethod() {
10625
10674
  const { connected: isSolanaConnected, wallet: solanaWallet, publicKey, } = useWallet$1();
10626
10675
  const { setRoute, paymentState, wcWallet, log } = usePayContext();
10627
10676
  const { disconnectAsync } = useDisconnect();
10628
- const { daimoPayOrder, setSelectedExternalOption, externalPaymentOptions, depositAddressOptions, senderEnsName, } = paymentState;
10677
+ const { daimoPayOrder, setSelectedExternalOption, externalPaymentOptions, showSolanaPaymentMethod, depositAddressOptions, senderEnsName, } = paymentState;
10629
10678
  const paymentOptions = daimoPayOrder?.metadata.payer?.paymentOptions;
10630
10679
  const getConnectedWalletOptions = () => {
10631
10680
  const showChainLogo = isEthConnected && isSolanaConnected;
@@ -10660,7 +10709,7 @@ function SelectMethod() {
10660
10709
  };
10661
10710
  connectedOptions.push(connectedEthWalletOption);
10662
10711
  }
10663
- if (isSolanaConnected && includeSolana) {
10712
+ if (isSolanaConnected && showSolanaPaymentMethod) {
10664
10713
  const solWalletDisplayName = getAddressContraction(publicKey?.toBase58() ?? "");
10665
10714
  const connectedSolWalletOption = {
10666
10715
  id: "connectedSolanaWallet",
@@ -10685,13 +10734,9 @@ function SelectMethod() {
10685
10734
  }
10686
10735
  return connectedOptions;
10687
10736
  };
10688
- // Solana payment option
10689
- // Include by default if paymentOptions not provided
10690
- const includeSolana = paymentOptions == null ||
10691
- paymentOptions.includes(ExternalPaymentOptions.Solana);
10692
10737
  // Deposit address options, e.g. Bitcoin, Tron, Zcash, etc.
10693
10738
  // Include by default if paymentOptions not provided
10694
- const includeDepositAddressOption = paymentOptions == null ||
10739
+ const showDepositAddressMethod = paymentOptions == null ||
10695
10740
  paymentOptions.includes(ExternalPaymentOptions.ExternalChains);
10696
10741
  const connectedWalletOptions = getConnectedWalletOptions();
10697
10742
  const unconnectedWalletOption = {
@@ -10709,7 +10754,7 @@ function SelectMethod() {
10709
10754
  options.push(...connectedWalletOptions);
10710
10755
  options.push(unconnectedWalletOption);
10711
10756
  log(`[SELECT_METHOD] loading: ${externalPaymentOptions.loading}, options: ${JSON.stringify(externalPaymentOptions.options)}`);
10712
- if (includeSolana) {
10757
+ if (showSolanaPaymentMethod) {
10713
10758
  const solanaOption = getSolanaOption(isIOS);
10714
10759
  if (solanaOption) {
10715
10760
  options.push(solanaOption);
@@ -10733,7 +10778,7 @@ function SelectMethod() {
10733
10778
  disabled: option.disabled,
10734
10779
  subtitle: option.message,
10735
10780
  })));
10736
- if (includeDepositAddressOption) {
10781
+ if (showDepositAddressMethod) {
10737
10782
  const depositAddressOption = getDepositAddressOption(depositAddressOptions);
10738
10783
  options.push(depositAddressOption);
10739
10784
  }
@@ -10993,7 +11038,7 @@ var PayState;
10993
11038
  })(PayState || (PayState = {}));
10994
11039
  const PayWithSolanaToken = () => {
10995
11040
  const { triggerResize, paymentState, setRoute } = usePayContext();
10996
- const { payParams, generatePreviewOrder, selectedSolanaTokenOption, payWithSolanaToken, } = paymentState;
11041
+ const { selectedSolanaTokenOption, payWithSolanaToken } = paymentState;
10997
11042
  const [payState, setPayState] = useState(PayState.RequestingPayment);
10998
11043
  const handleTransfer = async () => {
10999
11044
  try {
@@ -11130,6 +11175,7 @@ const WaitingExternal = () => {
11130
11175
  const context = usePayContext();
11131
11176
  const { triggerResize, paymentState, setRoute } = context;
11132
11177
  const trpc = context.trpc;
11178
+ const { isMobile } = useIsMobile();
11133
11179
  const { selectedExternalOption, payWithExternal, paymentWaitingMessage, daimoPayOrder, } = paymentState;
11134
11180
  const [externalURL, setExternalURL] = useState(null);
11135
11181
  useEffect(() => {
@@ -11155,17 +11201,19 @@ const WaitingExternal = () => {
11155
11201
  });
11156
11202
  }, [selectedExternalOption]);
11157
11203
  const openExternalWindow = (url) => {
11158
- if (selectedExternalOption?.id === "Coinbase") {
11159
- //opening Coinbase onramp in a popup window in portrait mode in the center of the screen
11204
+ if (isMobile) {
11205
+ // on mobile: open in a new tab
11206
+ window.open(url, "_blank");
11207
+ }
11208
+ else {
11209
+ // on desktop: open in a popup window in
11210
+ // portrait mode in the center of the screen
11160
11211
  const width = 500;
11161
11212
  const height = 700;
11162
11213
  const left = Math.max(0, Math.floor((window.innerWidth - width) / 2) + window.screenX);
11163
11214
  const top = Math.max(0, Math.floor((window.innerHeight - height) / 2) + window.screenY);
11164
11215
  window.open(url, "popupWindow", `width=${width},height=${height},left=${left},top=${top},scrollbars=yes`);
11165
11216
  }
11166
- else {
11167
- window.open(url, "_blank");
11168
- }
11169
11217
  };
11170
11218
  const waitingMessageLength = paymentWaitingMessage?.length;
11171
11219
  useEffect(() => {
@@ -11185,14 +11233,14 @@ const customThemeDefault = {};
11185
11233
  const DaimoPayModal = ({ mode = "auto", theme = "auto", customTheme = customThemeDefault, lang = "en-US", }) => {
11186
11234
  const context = usePayContext();
11187
11235
  const paymentState = context.paymentState;
11188
- const { payParams, generatePreviewOrder, isDepositFlow, setPaymentWaitingMessage, setSelectedExternalOption, setSelectedTokenOption, setSelectedSolanaTokenOption, setSelectedDepositAddressOption, } = paymentState;
11236
+ const { payParams, generatePreviewOrder, isDepositFlow, showSolanaPaymentMethod, setPaymentWaitingMessage, setSelectedExternalOption, setSelectedTokenOption, setSelectedSolanaTokenOption, setSelectedDepositAddressOption, } = paymentState;
11189
11237
  const { isConnected: isEthConnected, connector, chain, address, } = useAccount();
11190
11238
  const { connected: isSolanaConnected } = useWallet$1();
11191
11239
  const { daimoPayOrder } = paymentState;
11192
11240
  const paymentOptions = daimoPayOrder?.metadata.payer?.paymentOptions;
11193
11241
  // Solana payment option
11194
11242
  // Include by default if paymentOptions not provided
11195
- const includeSolana = paymentOptions == null ||
11243
+ paymentOptions == null ||
11196
11244
  paymentOptions.includes(ExternalPaymentOptions.Solana);
11197
11245
  const chainIsSupported = useChainIsSupported(chain?.id);
11198
11246
  //if chain is unsupported we enforce a "switch chain" prompt
@@ -11312,34 +11360,42 @@ const DaimoPayModal = ({ mode = "auto", theme = "auto", customTheme = customThem
11312
11360
  }
11313
11361
  context.setOpen(false, { event: "click-close" });
11314
11362
  }
11315
- // Separate effect for initial route when modal opens
11363
+ // If the user has a wallet already connected upon opening the modal, go
11364
+ // straight to the select token screen
11316
11365
  useEffect(() => {
11317
- if (context.open &&
11318
- !isSolanaConnected &&
11319
- (context.wcWallet || isEthConnected)) {
11320
- // Only set initial route if we're at SELECT_METHOD when opening
11321
- if (context.route === ROUTES.SELECT_METHOD) {
11322
- context.setRoute(ROUTES.SELECT_TOKEN, {
11323
- event: "eth_connected_on_open",
11324
- walletId: connector?.id,
11325
- chainId: chain?.id,
11326
- address,
11327
- });
11328
- }
11366
+ if (!context.open)
11367
+ return;
11368
+ if (context.route !== ROUTES.SELECT_METHOD)
11369
+ return;
11370
+ const ethMethodAvailable = context.wcWallet != null || isEthConnected;
11371
+ const solanaMethodAvailable = isSolanaConnected && showSolanaPaymentMethod;
11372
+ // Skip to token selection if exactly one wallet is connected. If both
11373
+ // wallets are connected, stay on the SELECT_METHOD screen to allow the
11374
+ // user to select which wallet to use
11375
+ if (ethMethodAvailable && !solanaMethodAvailable) {
11376
+ context.setRoute(ROUTES.SELECT_TOKEN, {
11377
+ event: "eth_connected_on_open",
11378
+ walletId: connector?.id,
11379
+ chainId: chain?.id,
11380
+ address,
11381
+ });
11329
11382
  }
11330
- else if (context.open &&
11331
- isSolanaConnected &&
11332
- !isEthConnected &&
11333
- context.wcWallet === undefined &&
11334
- includeSolana) {
11335
- if (context.route === ROUTES.SELECT_METHOD) {
11336
- context.setRoute(ROUTES.SOLANA_SELECT_TOKEN, {
11337
- event: "solana_connected_on_open",
11338
- });
11339
- }
11383
+ else if (solanaMethodAvailable && !ethMethodAvailable) {
11384
+ context.setRoute(ROUTES.SOLANA_SELECT_TOKEN, {
11385
+ event: "solana_connected_on_open",
11386
+ });
11340
11387
  }
11341
- // If both are connected, do nothing and stay on SELECT_METHOD
11342
- }, [context.open]);
11388
+ // Don't include context.route in the dependency array otherwise the user
11389
+ // can't go back from the select token screen to the select method screen
11390
+ }, [
11391
+ context.open,
11392
+ context.wcWallet,
11393
+ isEthConnected,
11394
+ isSolanaConnected,
11395
+ showSolanaPaymentMethod,
11396
+ ]);
11397
+ // If we're on the connect page and the user successfully connects their
11398
+ // wallet, go to the select token page
11343
11399
  useEffect(() => {
11344
11400
  if (context.route === ROUTES.CONNECT ||
11345
11401
  context.route === ROUTES.CONNECTORS ||
@@ -11353,7 +11409,7 @@ const DaimoPayModal = ({ mode = "auto", theme = "auto", customTheme = customThem
11353
11409
  });
11354
11410
  }
11355
11411
  }
11356
- }, [isEthConnected, context.route]);
11412
+ }, [isEthConnected, context.route, connector?.id, chain?.id, address]);
11357
11413
  useEffect(() => context.setMode(mode), [mode]);
11358
11414
  useEffect(() => context.setTheme(theme), [theme]);
11359
11415
  useEffect(() => context.setCustomTheme(customTheme), [customTheme]);
@@ -11452,18 +11508,43 @@ const DaimoPayProviderWithoutSolana = ({ children, theme = "auto", mode = "auto"
11452
11508
  const [ckMode, setMode] = useState(mode);
11453
11509
  const [ckCustomTheme, setCustomTheme] = useState(customTheme ?? {});
11454
11510
  const [ckLang, setLang] = useState("en-US");
11511
+ const onOpenRef = useRef();
11512
+ const onCloseRef = useRef();
11513
+ const setOnOpen = useCallback((fn) => {
11514
+ onOpenRef.current = fn;
11515
+ }, []);
11516
+ const setOnClose = useCallback((fn) => {
11517
+ onCloseRef.current = fn;
11518
+ }, []);
11455
11519
  const [open, setOpenState] = useState(false);
11456
- const setOpen = (open, meta) => {
11520
+ const [route, setRouteState] = useState(ROUTES.SELECT_METHOD);
11521
+ // Daimo Pay context
11522
+ const [daimoPayOrder, setDaimoPayOrderInner] = useState();
11523
+ const [pendingConnectorId, setPendingConnectorId] = useState(undefined);
11524
+ // Track sessions. Each generates separate intent IDs unless using externalId.
11525
+ const [sessionId] = useState(() => crypto.randomUUID().replaceAll("-", ""));
11526
+ const [solanaConnector, setSolanaConnector] = useState();
11527
+ // Other configuration
11528
+ const [errorMessage, setErrorMessage] = useState("");
11529
+ const [confirmationMessage, setConfirmationMessage] = useState(undefined);
11530
+ const [redirectReturnUrl, setRedirectReturnUrl] = useState(undefined);
11531
+ const log = debugMode ? console.log : () => { };
11532
+ // Connect to the Daimo Pay TRPC API
11533
+ const trpc = useMemo(() => createTrpcClient(payApiUrl, sessionId), [payApiUrl]);
11534
+ const [resize, onResize] = useState(0);
11535
+ const setOpen = useCallback((open, meta) => {
11457
11536
  setOpenState(open);
11458
11537
  trpc.nav.mutate({
11459
11538
  action: open ? "navOpenPay" : "navClosePay",
11460
11539
  orderId: daimoPayOrder?.id?.toString(),
11461
11540
  data: meta ?? {},
11462
11541
  });
11463
- };
11464
- const [solanaConnector, setSolanaConnector] = useState();
11465
- const [route, setRouteState] = useState(ROUTES.SELECT_METHOD);
11466
- const setRoute = (route, data) => {
11542
+ if (open)
11543
+ onOpenRef.current?.();
11544
+ else
11545
+ onCloseRef.current?.();
11546
+ }, [trpc, daimoPayOrder?.id]);
11547
+ const setRoute = useCallback((route, data) => {
11467
11548
  const action = route.replace("daimoPay", "");
11468
11549
  log(`[SET ROUTE] ${action} ${daimoPayOrder?.id} ${debugJson(data ?? {})}`);
11469
11550
  trpc.nav.mutate({
@@ -11472,15 +11553,10 @@ const DaimoPayProviderWithoutSolana = ({ children, theme = "auto", mode = "auto"
11472
11553
  data: data ?? {},
11473
11554
  });
11474
11555
  setRouteState(route);
11475
- };
11476
- const [errorMessage, setErrorMessage] = useState("");
11477
- const [confirmationMessage, setConfirmationMessage] = useState(undefined);
11478
- const [redirectReturnUrl, setRedirectReturnUrl] = useState(undefined);
11479
- const [resize, onResize] = useState(0);
11480
- const [pendingConnectorId, setPendingConnectorId] = useState(undefined);
11556
+ }, [trpc, daimoPayOrder?.id, log]);
11481
11557
  // Include Google Font that is needed for a themes
11482
11558
  if (opts.embedGoogleFonts)
11483
- useThemeFont(theme);
11559
+ useThemeFont(ckTheme);
11484
11560
  // Other Configuration
11485
11561
  useEffect(() => setTheme(theme), [theme]);
11486
11562
  useEffect(() => setLang(opts.language || "en-US"), [opts.language]);
@@ -11491,31 +11567,30 @@ const DaimoPayProviderWithoutSolana = ({ children, theme = "auto", mode = "auto"
11491
11567
  useEffect(() => {
11492
11568
  if (isConnected && opts.enforceSupportedChains && !isChainSupported) {
11493
11569
  setOpen(true);
11494
- setRoute(ROUTES.SWITCHNETWORKS);
11570
+ if (route !== ROUTES.SWITCHNETWORKS)
11571
+ setRoute(ROUTES.SWITCHNETWORKS);
11495
11572
  }
11496
11573
  }, [isConnected, isChainSupported, chain, route, open]);
11497
- const log = debugMode ? console.log : () => { };
11498
- // Track sessions. Each generates separate intent IDs unless using externalId.
11499
- const [sessionId] = useState(() => crypto.randomUUID().replaceAll("-", ""));
11500
11574
  // Single source of truth for the currently-connected wallet is the connector
11501
11575
  // exposed by wagmi. See useAccount(). We watch this connector and use it to
11502
11576
  // extract the current WalletConnect wallet, if any.
11503
11577
  const wcWallet = useExtractWcWallet({ connector, log });
11504
- // Connect to the Daimo Pay TRPC API
11505
- const trpc = useMemo(() => createTrpcClient(payApiUrl, sessionId), [payApiUrl]);
11506
11578
  // PaymentInfo is a second, inner context object containing a DaimoPayOrder
11507
11579
  // plus all associated status and callbacks. In order for useContext() and
11508
11580
  // downstream hooks like useDaimoPayStatus() to work correctly, we must set
11509
11581
  // set refresh context when payment status changes; done via setDaimoPayOrder.
11510
- const [daimoPayOrder, setDaimoPayOrderInner] = useState();
11511
11582
  const setDaimoPayOrder = useCallback((order) => {
11512
11583
  setDaimoPayOrderInner(order);
11584
+ if (order == null) {
11585
+ log(`[PAY] setDaimoPayOrder: reset`);
11586
+ return;
11587
+ }
11513
11588
  let extra = `> $${order.destFinalCallTokenAmount.usd.toFixed(2)} to ${order.destFinalCallTokenAmount.token.chainId} ${order.destFinalCall.to}`;
11514
11589
  if (order.mode === DaimoPayOrderMode.HYDRATED) {
11515
11590
  extra += ` via ${order.intentAddr} ${order.sourceStatus} ${order.intentStatus}`;
11516
11591
  }
11517
11592
  log(`[PAY] setDaimoPayOrder: ${order.id} ${extra}`);
11518
- }, [setDaimoPayOrderInner]);
11593
+ }, [log]);
11519
11594
  const paymentState = usePaymentState({
11520
11595
  trpc,
11521
11596
  daimoPayOrder,
@@ -11546,7 +11621,6 @@ const DaimoPayProviderWithoutSolana = ({ children, theme = "auto", mode = "auto"
11546
11621
  return () => clearTimeout(timeout);
11547
11622
  }, [daimoPayOrder]);
11548
11623
  const showPayment = async (modalOptions) => {
11549
- const { daimoPayOrder } = paymentState;
11550
11624
  const id = daimoPayOrder?.id;
11551
11625
  log(`[PAY] showing payment ${debugJson({ id, modalOptions })}`);
11552
11626
  paymentState.setModalOptions(modalOptions);
@@ -11572,6 +11646,8 @@ const DaimoPayProviderWithoutSolana = ({ children, theme = "auto", mode = "auto"
11572
11646
  setCustomTheme,
11573
11647
  lang: ckLang,
11574
11648
  setLang,
11649
+ setOnOpen,
11650
+ setOnClose,
11575
11651
  open,
11576
11652
  setOpen,
11577
11653
  route,
@@ -11857,10 +11933,12 @@ function DaimoPayButtonCustom(props) {
11857
11933
  evmChains: props.evmChains,
11858
11934
  externalId: props.externalId,
11859
11935
  metadata: props.metadata,
11936
+ refundAddress: props.refundAddress,
11860
11937
  }
11861
11938
  : null;
11862
11939
  let payId = "payId" in props ? props.payId : null;
11863
11940
  const { paymentState } = context;
11941
+ // Set the payId or payParams
11864
11942
  useEffect(() => {
11865
11943
  if (payId != null) {
11866
11944
  paymentState.setPayId(payId);
@@ -11869,18 +11947,30 @@ function DaimoPayButtonCustom(props) {
11869
11947
  paymentState.setPayParams(payParams);
11870
11948
  }
11871
11949
  }, [payId, JSON.stringify(payParams || {})]);
11950
+ // Set the confirmation message
11872
11951
  const { setConfirmationMessage } = context;
11873
11952
  useEffect(() => {
11874
11953
  if (props.confirmationMessage) {
11875
11954
  setConfirmationMessage(props.confirmationMessage);
11876
11955
  }
11877
11956
  }, [props.confirmationMessage, setConfirmationMessage]);
11957
+ // Set the redirect return url
11878
11958
  const { setRedirectReturnUrl } = context;
11879
11959
  useEffect(() => {
11880
11960
  if (props.redirectReturnUrl) {
11881
11961
  setRedirectReturnUrl(props.redirectReturnUrl);
11882
11962
  }
11883
11963
  }, [props.redirectReturnUrl, setRedirectReturnUrl]);
11964
+ // Set the onOpen and onClose callbacks
11965
+ const { setOnOpen, setOnClose } = context;
11966
+ useEffect(() => {
11967
+ setOnOpen(props.onOpen);
11968
+ return () => setOnOpen(undefined);
11969
+ }, [props.onOpen, setOnOpen]);
11970
+ useEffect(() => {
11971
+ setOnClose(props.onClose);
11972
+ return () => setOnClose(undefined);
11973
+ }, [props.onClose, setOnClose]);
11884
11974
  // Payment events: call these three event handlers.
11885
11975
  const { onPaymentStarted, onPaymentCompleted, onPaymentBounced } = props;
11886
11976
  const order = paymentState.daimoPayOrder;
@@ -11896,21 +11986,23 @@ function DaimoPayButtonCustom(props) {
11896
11986
  };
11897
11987
  const hide = () => context.setOpen(false);
11898
11988
  // Emit event handlers when payment status changes
11989
+ const sentStart = useRef(false);
11899
11990
  useEffect(() => {
11900
11991
  if (hydOrder == null)
11901
11992
  return;
11902
11993
  if (intentStatus === DaimoPayIntentStatus.UNPAID)
11903
11994
  return;
11904
- if (intentStatus === DaimoPayIntentStatus.STARTED) {
11995
+ if (!sentStart.current && hydOrder.sourceTokenAmount) {
11996
+ sentStart.current = true;
11905
11997
  onPaymentStarted?.({
11906
11998
  type: DaimoPayIntentStatus.STARTED,
11907
11999
  paymentId: writeDaimoPayOrderID(hydOrder.id),
11908
- chainId: hydOrder.destFinalCallTokenAmount.token.chainId,
11909
- 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,
11910
12002
  payment: getDaimoPayOrderView(hydOrder),
11911
12003
  });
11912
12004
  }
11913
- else if (intentStatus === DaimoPayIntentStatus.COMPLETED ||
12005
+ if (intentStatus === DaimoPayIntentStatus.COMPLETED ||
11914
12006
  intentStatus === DaimoPayIntentStatus.BOUNCED) {
11915
12007
  const event = {
11916
12008
  type: intentStatus,
@@ -11926,12 +12018,13 @@ function DaimoPayButtonCustom(props) {
11926
12018
  onPaymentBounced?.(event);
11927
12019
  }
11928
12020
  }
11929
- }, [hydOrder?.id, intentStatus]);
12021
+ }, [hydOrder?.id, intentStatus, hydOrder?.sourceTokenAmount?.token.chainId]);
12022
+ // Open the modal by default if the defaultOpen prop is true
11930
12023
  useEffect(() => {
11931
- if (props.defaultOpen) {
12024
+ if (props.defaultOpen && order != null) {
11932
12025
  show();
11933
12026
  }
11934
- }, [order != null]);
12027
+ }, [order != null, props.defaultOpen]);
11935
12028
  // Validation
11936
12029
  if ((payId == null) == (payParams == null)) {
11937
12030
  throw new Error("Must specify either payId or appId, not both");
@@ -11986,11 +12079,12 @@ function DaimoPayButtonInner({ disabled }) {
11986
12079
  */
11987
12080
  function useDaimoPayStatus() {
11988
12081
  const { paymentState } = usePayContext();
12082
+ const reset = paymentState.resetOrder;
11989
12083
  if (!paymentState || !paymentState.daimoPayOrder)
11990
- return undefined;
12084
+ return { reset };
11991
12085
  const order = paymentState.daimoPayOrder;
11992
12086
  const paymentId = writeDaimoPayOrderID(order.id);
11993
- return { paymentId, status: order.intentStatus };
12087
+ return { paymentId, status: order.intentStatus, reset };
11994
12088
  }
11995
12089
 
11996
12090
  function addressToNumber(address) {