From b33f44880b4108d565837edaa2c14febeb76949a Mon Sep 17 00:00:00 2001 From: guillermo dieguez Date: Thu, 23 Jul 2026 12:44:51 -0300 Subject: [PATCH 01/20] =?UTF-8?q?=E2=9A=B0=EF=B8=8F=20app:=20remove=20expi?= =?UTF-8?q?red=20crypto=20on-ramps?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/add-funds/AddFunds.tsx | 65 +-------------------------- 1 file changed, 2 insertions(+), 63 deletions(-) diff --git a/src/components/add-funds/AddFunds.tsx b/src/components/add-funds/AddFunds.tsx index db6f37991d..85941837b3 100644 --- a/src/components/add-funds/AddFunds.tsx +++ b/src/components/add-funds/AddFunds.tsx @@ -1,4 +1,4 @@ -import React, { useMemo } from "react"; +import React from "react"; import { useTranslation } from "react-i18next"; import { useLocalSearchParams, useRouter } from "expo-router"; @@ -8,7 +8,6 @@ import { useToastController } from "@tamagui/toast"; import { ScrollView, XStack, YStack } from "tamagui"; import { useQuery } from "@tanstack/react-query"; -import { isAfter, parseISO } from "date-fns"; import { isAddress } from "viem"; import { base } from "viem/chains"; @@ -74,42 +73,6 @@ export default function AddFunds() { Object.values(providers).some((p) => p.onramp.currencies.some((item) => typeof item === "object" && "network" in item), ); - const past = useMemo(() => isAfter(new Date(), parseISO("2026-07-01")), []); - - function renderProviders(filter: "crypto" | "fiat") { - if (countryCode && isPending) { - return ( - - - - ); - } - if (!providers) return null; - return ( - - {Object.entries(providers).flatMap(([providerKey, provider]) => - provider.onramp.currencies - .filter((item) => (filter === "crypto") === (typeof item === "object")) - .map((item) => { - const isCrypto = typeof item === "object"; - const currency = isCrypto ? item.currency : item; - const network = isCrypto ? item.network : undefined; - return ( - - ); - }), - )} - - ); - } - return ( @@ -195,35 +158,13 @@ export default function AddFunds() { <> {hasCrypto && ( { openBrowser("https://x.com/exa_app/status/2071690658339770622").catch(reportError); }} /> )} - {!past && !isKYCApproved && chain.id !== base.id && ( - { - beginKYC.mutate(undefined, { - onError(error) { - toast.show(t("Error verifying identity"), { - duration: 1000, - burntOptions: { haptic: "error", preset: "error" }, - }); - reportError(error); - }, - }); - }} - loading={beginKYC.isPending} - /> - )} {method === "siwe" && ( } @@ -245,8 +186,6 @@ export default function AddFunds() { router.push("/add-funds/add-crypto"); }} /> - - {!past && renderProviders("crypto")} )} {type === "fiat" && countryCode && isPending && ( From f42305d5525ed09d1a06fc032d4299559cb4c998 Mon Sep 17 00:00:00 2001 From: guillermo dieguez Date: Thu, 23 Jul 2026 12:50:50 -0300 Subject: [PATCH 02/20] =?UTF-8?q?=E2=9C=A8=20app:=20restructure=20add=20fu?= =?UTF-8?q?nds=20root=20menu?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .changeset/brave-otters-wave.md | 5 ++++ src/components/add-funds/AddFunds.tsx | 36 ++++++++++++++++----------- src/i18n/es.json | 5 ++-- src/i18n/pt.json | 5 ++-- 4 files changed, 32 insertions(+), 19 deletions(-) create mode 100644 .changeset/brave-otters-wave.md diff --git a/.changeset/brave-otters-wave.md b/.changeset/brave-otters-wave.md new file mode 100644 index 0000000000..bc5b479d5f --- /dev/null +++ b/.changeset/brave-otters-wave.md @@ -0,0 +1,5 @@ +--- +"@exactly/mobile": patch +--- + +✨ restructure add funds root menu diff --git a/src/components/add-funds/AddFunds.tsx b/src/components/add-funds/AddFunds.tsx index 85941837b3..07e6f02b46 100644 --- a/src/components/add-funds/AddFunds.tsx +++ b/src/components/add-funds/AddFunds.tsx @@ -22,6 +22,7 @@ import queryClient, { type AuthMethod } from "../../utils/queryClient"; import reportError from "../../utils/reportError"; import { getKYCStatus, getRampProviders } from "../../utils/server"; import useBeginKYC from "../../utils/useBeginKYC"; +import useMarkets from "../../utils/useMarkets"; import RampButton from "../ramp/RampButton"; import ChainLogo from "../shared/ChainLogo"; import IconButton from "../shared/IconButton"; @@ -43,6 +44,7 @@ export default function AddFunds() { const ownerAccount = credential && isAddress(credential.credentialId) ? credential.credentialId : undefined; const { data: method } = useQuery({ queryKey: ["method"] }); + const { supportedAssets } = useMarkets(); const { data: kycStatus } = useQuery({ queryKey: ["kyc", "status"] }); const beginKYC = useBeginKYC(); const isKYCApproved = @@ -109,10 +111,27 @@ export default function AddFunds() { {type !== "crypto" && type !== "fiat" && ( <> + {method === "siwe" && ( + } + title={t("With connected wallet")} + subtitle={ + // TODO add support for ens resolution + ownerAccount ? shortenHex(ownerAccount, 4, 6) : "" + } + onPress={() => { + router.push("/add-funds/bridge"); + }} + /> + )} } title={t("Cryptocurrencies")} - subtitle={t("Multiple networks and wallets")} + subtitle={ + supportedAssets.length > 3 + ? t("{{assets}} and more", { assets: supportedAssets.slice(0, 3).join(", ") }) + : supportedAssets.join(", ") + } onPress={() => { router.push({ pathname: "/add-funds", params: { type: "crypto" } }); }} @@ -121,7 +140,7 @@ export default function AddFunds() { } title={t("Bank transfers")} - subtitle={t("From a bank account")} + subtitle={t("Pesos, dollars, or euros")} disabled={(isKYCApproved && !hasFiat) || beginKYC.isPending} loading={beginKYC.isPending} onPress={() => { @@ -165,19 +184,6 @@ export default function AddFunds() { }} /> )} - {method === "siwe" && ( - } - title={t("From connected wallet")} - subtitle={ - // TODO add support for ens resolution - ownerAccount ? shortenHex(ownerAccount, 4, 6) : "" - } - onPress={() => { - router.push("/add-funds/bridge"); - }} - /> - )} } title={t("From another wallet")} diff --git a/src/i18n/es.json b/src/i18n/es.json index 43e7d9ce84..e9b1621f55 100644 --- a/src/i18n/es.json +++ b/src/i18n/es.json @@ -1,5 +1,6 @@ { "{{amount}} left": "{{amount}} restante", + "{{assets}} and more": "{{assets}} y más", "{{count}} installments of_one": "{{count}} cuota de", "{{count}} installments of_other": "{{count}} cuotas de", "{{count}} installments_one": "{{count}} cuota", @@ -320,12 +321,10 @@ "FREE": "GRATIS", "Freeze card": "Congelar tarjeta", "Freeze your card?": "¿Congelar tu tarjeta?", - "From a bank account": "Desde una cuenta bancaria", "From another wallet": "Desde otra billetera", "From any account in your name": "Desde cualquier cuenta a tu nombre", "From any account": "Desde cualquier cuenta", "From any Argentine bank account in your name": "Desde cualquier cuenta bancaria argentina a tu nombre", - "From connected wallet": "Desde la billetera conectada", "frozen card": "tarjeta bloqueada", "Full repayment selected.": "Pago total seleccionado.", "Funding failed": "El financiamiento falló", @@ -529,6 +528,7 @@ "Pending requests": "Solicitudes pendientes", "Performance is variable, not guaranteed, and powered by Exactly Protocol. Yields depend on protocol performance and network activity. Past performance does not guarantee future results.": "El rendimiento es variable, no está garantizado y es impulsado por Exactly Protocol. Los rendimientos dependen del desempeño del protocolo y de la actividad de la red. El rendimiento pasado no garantiza resultados futuros.", "Pesos": "Pesos", + "Pesos, dollars, or euros": "Pesos, dólares o euros", "PIX key": "Clave PIX", "PIX Key": "Clave PIX", "Please check your internet connection and try again in a moment. If the problem persists, reinstalling the app may help.": "Revisa tu conexión a internet e inténtalo de nuevo en unos momentos. Si el problema persiste, reinstalar la aplicación puede ayudar.", @@ -799,6 +799,7 @@ "When you make a purchase using an installment plan, you must pay each installment manually before the due date. Otherwise, a daily penalty of {{rate}} is added while the payment is late.": "Cuando haces una compra usando un plan de cuotas, debes pagar cada cuota manualmente antes de la fecha de vencimiento. De lo contrario, se agrega una penalidad diaria de {{rate}} mientras el pago esté atrasado.", "WHEN": "CUÁNDO", "with": "con", + "With connected wallet": "Con billetera conectada", "Withdrawal": "Retiro", "Yield": "Rendimiento", "You are accessing a decentralized protocol using your crypto as collateral. The Exa App does not issue funding or provide credit. No credit checks or intermediaries are involved.": "Estás accediendo a un protocolo descentralizado usando tu cripto como garantía. La Exa App no emite financiamiento ni proporciona crédito. No se realizan verificaciones de crédito ni hay intermediarios involucrados.", diff --git a/src/i18n/pt.json b/src/i18n/pt.json index 0f583a0435..0337cbae5e 100644 --- a/src/i18n/pt.json +++ b/src/i18n/pt.json @@ -1,5 +1,6 @@ { "{{amount}} left": "{{amount}} restante", + "{{assets}} and more": "{{assets}} e mais", "{{count}} installments of_one": "{{count}} parcela de", "{{count}} installments of_other": "{{count}} parcelas de", "{{count}} installments_one": "{{count}} parcela", @@ -320,12 +321,10 @@ "FREE": "GRÁTIS", "Freeze card": "Congelar cartão", "Freeze your card?": "Congelar seu cartão?", - "From a bank account": "De uma conta bancária", "From another wallet": "De outra carteira", "From any account in your name": "De qualquer conta em seu nome", "From any account": "De qualquer conta", "From any Argentine bank account in your name": "De qualquer conta bancária argentina em seu nome", - "From connected wallet": "Da carteira conectada", "frozen card": "cartão bloqueado", "Full repayment selected.": "Pagamento total selecionado.", "Funding failed": "O financiamento falhou", @@ -529,6 +528,7 @@ "Pending requests": "Solicitações pendentes", "Performance is variable, not guaranteed, and powered by Exactly Protocol. Yields depend on protocol performance and network activity. Past performance does not guarantee future results.": "O rendimento é variável, não é garantido e é impulsionado pelo Exactly Protocol. Os rendimentos dependem do desempenho do protocolo e da atividade da rede. O desempenho passado não garante resultados futuros.", "Pesos": "Pesos", + "Pesos, dollars, or euros": "Pesos, dólares ou euros", "PIX key": "Chave PIX", "PIX Key": "Chave PIX", "Please check your internet connection and try again in a moment. If the problem persists, reinstalling the app may help.": "Verifique sua conexão com a internet e tente novamente em instantes. Se o problema persistir, reinstalar o aplicativo pode ajudar.", @@ -799,6 +799,7 @@ "When you make a purchase using an installment plan, you must pay each installment manually before the due date. Otherwise, a daily penalty of {{rate}} is added while the payment is late.": "Quando você faz uma compra usando um plano de parcelamento, você deve pagar cada parcela manualmente antes da data de vencimento. Caso contrário, uma penalidade diária de {{rate}} é adicionada enquanto o pagamento estiver atrasado.", "WHEN": "QUANDO", "with": "com", + "With connected wallet": "Com carteira conectada", "Withdrawal": "Saque", "Yield": "Rendimento", "You are accessing a decentralized protocol using your crypto as collateral. The Exa App does not issue funding or provide credit. No credit checks or intermediaries are involved.": "Você está acessando um protocolo descentralizado usando sua cripto como garantia. O Exa App não emite financiamento nem fornece crédito. Não há verificações de crédito nem intermediários envolvidos.", From f071aae981688586fef658818488fbe9aa6b2eb9 Mon Sep 17 00:00:00 2001 From: guillermo dieguez Date: Thu, 23 Jul 2026 12:54:23 -0300 Subject: [PATCH 03/20] =?UTF-8?q?=E2=9C=A8=20app:=20resolve=20ens=20name?= =?UTF-8?q?=20for=20owner=20address?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .changeset/calm-badgers-greet.md | 5 +++++ src/components/add-funds/AddFunds.tsx | 14 +++++++++++--- 2 files changed, 16 insertions(+), 3 deletions(-) create mode 100644 .changeset/calm-badgers-greet.md diff --git a/.changeset/calm-badgers-greet.md b/.changeset/calm-badgers-greet.md new file mode 100644 index 0000000000..fdde20c744 --- /dev/null +++ b/.changeset/calm-badgers-greet.md @@ -0,0 +1,5 @@ +--- +"@exactly/mobile": patch +--- + +✨ resolve ens name for owner address diff --git a/src/components/add-funds/AddFunds.tsx b/src/components/add-funds/AddFunds.tsx index 07e6f02b46..fac4b770f5 100644 --- a/src/components/add-funds/AddFunds.tsx +++ b/src/components/add-funds/AddFunds.tsx @@ -9,7 +9,8 @@ import { ScrollView, XStack, YStack } from "tamagui"; import { useQuery } from "@tanstack/react-query"; import { isAddress } from "viem"; -import { base } from "viem/chains"; +import { base, mainnet } from "viem/chains"; +import { useEnsName } from "wagmi"; import domain from "@exactly/common/domain"; import chain from "@exactly/common/generated/chain"; @@ -23,6 +24,7 @@ import reportError from "../../utils/reportError"; import { getKYCStatus, getRampProviders } from "../../utils/server"; import useBeginKYC from "../../utils/useBeginKYC"; import useMarkets from "../../utils/useMarkets"; +import ownerConfig from "../../utils/wagmi/owner"; import RampButton from "../ramp/RampButton"; import ChainLogo from "../shared/ChainLogo"; import IconButton from "../shared/IconButton"; @@ -42,6 +44,12 @@ export default function AddFunds() { const { t } = useTranslation(); const { data: credential } = useQuery({ queryKey: ["credential"] }); const ownerAccount = credential && isAddress(credential.credentialId) ? credential.credentialId : undefined; + const { data: ensName } = useEnsName({ + config: ownerConfig, + chainId: mainnet.id, + address: ownerAccount, + query: { staleTime: 86_400_000, retry: false, meta: { dropError: () => true } }, + }); const { data: method } = useQuery({ queryKey: ["method"] }); const { supportedAssets } = useMarkets(); @@ -116,8 +124,8 @@ export default function AddFunds() { icon={} title={t("With connected wallet")} subtitle={ - // TODO add support for ens resolution - ownerAccount ? shortenHex(ownerAccount, 4, 6) : "" + ownerAccount && + (ensName ? `${ensName} | ${shortenHex(ownerAccount, 4, 6)}` : shortenHex(ownerAccount, 4, 6)) } onPress={() => { router.push("/add-funds/bridge"); From 668dbac6022872eb3e9f4041a3f112a5e2c0ef4f Mon Sep 17 00:00:00 2001 From: guillermo dieguez Date: Thu, 23 Jul 2026 13:01:26 -0300 Subject: [PATCH 04/20] =?UTF-8?q?=E2=9C=A8=20app:=20redesign=20crypto=20re?= =?UTF-8?q?ceive=20screen?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .changeset/proud-lions-glow.md | 5 + src/components/add-funds/AddCrypto.tsx | 358 ++++++++++++------ src/components/add-funds/EducationSheet.tsx | 65 ++++ .../add-funds/SupportedAssetsSheet.tsx | 117 ------ src/components/shared/SendWarning.tsx | 13 + src/i18n/es-AR.json | 1 - src/i18n/es.json | 4 +- src/i18n/pt.json | 4 +- 8 files changed, 327 insertions(+), 240 deletions(-) create mode 100644 .changeset/proud-lions-glow.md create mode 100644 src/components/add-funds/EducationSheet.tsx delete mode 100644 src/components/add-funds/SupportedAssetsSheet.tsx create mode 100644 src/components/shared/SendWarning.tsx diff --git a/.changeset/proud-lions-glow.md b/.changeset/proud-lions-glow.md new file mode 100644 index 0000000000..4e9fe236c8 --- /dev/null +++ b/.changeset/proud-lions-glow.md @@ -0,0 +1,5 @@ +--- +"@exactly/mobile": patch +--- + +✨ redesign crypto receive screen diff --git a/src/components/add-funds/AddCrypto.tsx b/src/components/add-funds/AddCrypto.tsx index d5100bad3d..ad0bf07851 100644 --- a/src/components/add-funds/AddCrypto.tsx +++ b/src/components/add-funds/AddCrypto.tsx @@ -1,11 +1,13 @@ import React, { useCallback, useState } from "react"; import { useTranslation } from "react-i18next"; import { PixelRatio, Pressable, Share } from "react-native"; +import QRCode from "react-native-qrcode-styled"; import { setStringAsync } from "expo-clipboard"; +import { selectionAsync } from "expo-haptics"; import { useLocalSearchParams, useRouter } from "expo-router"; -import { AlertTriangle, ArrowLeft, Copy, RefreshCw, Share as ShareIcon } from "@tamagui/lucide-icons"; +import { AlertTriangle, ArrowLeft, Copy, QrCode, RefreshCw, Share as ShareIcon } from "@tamagui/lucide-icons"; import { useToastController } from "@tamagui/toast"; import { ScrollView, XStack, YStack } from "tamagui"; @@ -14,7 +16,7 @@ import { useQuery } from "@tanstack/react-query"; import chain from "@exactly/common/generated/chain"; import BridgeDisclaimer from "./BridgeDisclaimer"; -import SupportedAssetsSheet from "./SupportedAssetsSheet"; +import EducationSheet from "./EducationSheet"; import { presentArticle } from "../../utils/intercom"; import networkLogos from "../../utils/networkLogos"; import reportError from "../../utils/reportError"; @@ -26,7 +28,9 @@ import ChainLogo from "../shared/ChainLogo"; import CopyAddressSheet from "../shared/CopyAddressSheet"; import IconButton from "../shared/IconButton"; import Image from "../shared/Image"; +import ModalSheet from "../shared/ModalSheet"; import SafeView from "../shared/SafeView"; +import SendWarning from "../shared/SendWarning"; import Skeleton from "../shared/Skeleton"; import Button from "../shared/StyledButton"; import Text from "../shared/Text"; @@ -63,10 +67,12 @@ export default function AddCrypto() { const toast = useToastController(); const [copyAddressShown, setCopyAddressShown] = useState(false); + const [qrShown, setQRShown] = useState(false); const [supportedAssetsShown, setSupportedAssetsShown] = useState(false); const copy = useCallback(() => { if (!address) return; + selectionAsync().catch(reportError); setStringAsync(address) .then(() => { setCopyAddressShown(true); @@ -109,63 +115,54 @@ export default function AddCrypto() { - - - {isBridge - ? t("{{network}} deposit address", { network: networkName }) - : t("Your {{chain}} address", { chain: networkName })} - - - {address ? ( - {address} - ) : isBridge && isError && !isFetching ? ( - {t("Failed to load deposit address.")} - ) : ( - + + + setSupportedAssetsShown(true)} + /> + + + + + {t("Wallet address")} + + + {address ? ( + + {address} + + ) : isBridge && isError && !isFetching ? ( + + {t("Failed to load deposit address.")} + + ) : ( + + )} + + {!!address && !memo && ( + setQRShown(true)}> + + + {t("Show QR")} + + + + )} - - {isBridge && isError && !isFetching ? ( - - ) : ( - - - - - )} + {!!memo && ( @@ -198,6 +195,43 @@ export default function AddCrypto() { )} + {!!address && !memo && ( + { + setQRShown(false); + }} + > + + + + {isBridge + ? t("{{network}} deposit address", { network: networkName }) + : t("Your {{chain}} address", { chain: networkName })} + + + + + { + setQRShown(false); + }} + > + + {t("Close")} + + + + + + )} { @@ -209,83 +243,60 @@ export default function AddCrypto() { assets={isBridge ? assets : undefined} /> {!isBridge && ( - { setSupportedAssetsShown(false); }} - /> - )} - - - {t("Network")} - - - {isBridge ? t("Asset") : t("Supported Assets")} - - - - - {isBridge && typeof network === "string" && network in networkLogos ? ( - - ) : ( - - )} - - {networkName} - - - setSupportedAssetsShown(true)} + title={t("Supported assets")} + article="8950805" > - {!isBridge && isPending - ? Array.from({ length: 5 }, (_, index) => ( - - - - )) - : assets.map((symbol, index) => ( - - - - ))} - - + + {isPending + ? Array.from({ length: 5 }, (_, index) => ( + + )) + : supportedAssets.map((collateral) => ( + + ))} + + + {t( + "Only {{assets}} on {{chain}} serve as collateral, earn yield while held, and increase your Exa Card credit limit.", + { assets: supportedAssets.join(", "), chain: chain.name }, + )} + + + )} - + {isBridge && } - + - - {isBridge - ? t( - "Only send {{crypto}} on {{network}}. Sending other assets or using other networks may cause permanent loss.", - { crypto: currency, network: networkName }, - ) - : t("Only send assets on {{chain}}. Sending funds from other networks may cause permanent loss.", { - chain: networkName, - })} + + { presentArticle("8950801").catch(reportError); }} @@ -296,8 +307,115 @@ export default function AddCrypto() { + {isBridge && isError && !isFetching ? ( + + ) : ( + + + + + )} ); } + +function AssetChip({ assets, isPending, onPress }: { assets: string[]; isPending: boolean; onPress?: () => void }) { + const { t } = useTranslation(); + return ( + + + {t("Asset")} + + + {isPending ? ( + + ) : assets.length === 1 ? ( + <> + + + {assets[0]} + + + ) : ( + assets.map((symbol, index) => ( + + + + )) + )} + + + ); +} + +function NetworkChip({ logoURI, name }: { logoURI?: string; name: string }) { + const { t } = useTranslation(); + return ( + + + {t("Network")} + + + {logoURI ? ( + + ) : ( + + )} + + {name} + + + + ); +} diff --git a/src/components/add-funds/EducationSheet.tsx b/src/components/add-funds/EducationSheet.tsx new file mode 100644 index 0000000000..b9ee68e153 --- /dev/null +++ b/src/components/add-funds/EducationSheet.tsx @@ -0,0 +1,65 @@ +import React from "react"; +import { useTranslation } from "react-i18next"; +import { Pressable } from "react-native"; + +import { ThumbsUp } from "@tamagui/lucide-icons"; +import { ScrollView, YStack } from "tamagui"; + +import { presentArticle } from "../../utils/intercom"; +import reportError from "../../utils/reportError"; +import ModalSheet from "../shared/ModalSheet"; +import SafeView from "../shared/SafeView"; +import Button from "../shared/StyledButton"; +import Text from "../shared/Text"; + +export default function EducationSheet({ + article, + children, + onClose, + open, + title, +}: { + article: string; + children: React.ReactNode; + onClose: () => void; + open: boolean; + title: string; +}) { + const { t } = useTranslation(); + return ( + + + + + + {title} + + {children} + + { + presentArticle(article).catch(reportError); + }} + > + + {t("Learn more")} + + + + + + + ); +} diff --git a/src/components/add-funds/SupportedAssetsSheet.tsx b/src/components/add-funds/SupportedAssetsSheet.tsx deleted file mode 100644 index 7911d11d7d..0000000000 --- a/src/components/add-funds/SupportedAssetsSheet.tsx +++ /dev/null @@ -1,117 +0,0 @@ -import React from "react"; -import { Trans, useTranslation } from "react-i18next"; - -import { AlertTriangle, X } from "@tamagui/lucide-icons"; -import { ScrollView, XStack, YStack } from "tamagui"; - -import chain from "@exactly/common/generated/chain"; - -import { presentArticle } from "../../utils/intercom"; -import reportError from "../../utils/reportError"; -import useMarkets from "../../utils/useMarkets"; -import AssetLogo from "../shared/AssetLogo"; -import ModalSheet from "../shared/ModalSheet"; -import SafeView from "../shared/SafeView"; -import Skeleton from "../shared/Skeleton"; -import Button from "../shared/StyledButton"; -import Text from "../shared/Text"; -import View from "../shared/View"; - -export default function SupportedAssetsSheet({ open, onClose }: { onClose: () => void; open: boolean }) { - const { t } = useTranslation(); - const { supportedAssets, isPending } = useMarkets(); - return ( - - - - - - - {t("Supported assets")} - - - - {isPending - ? Array.from({ length: 5 }, (_, index) => ( - - - - - )) - : supportedAssets.map((symbol) => ( - - - - {symbol} - - - ))} - - - - - - - - { - presentArticle("8950801").catch(reportError); - }} - /> - ), - }} - /> - - - - - - - - - ); -} - -function Chip({ children }: { children: React.ReactNode }) { - return ( - - {children} - - ); -} diff --git a/src/components/shared/SendWarning.tsx b/src/components/shared/SendWarning.tsx new file mode 100644 index 0000000000..5c3ee42586 --- /dev/null +++ b/src/components/shared/SendWarning.tsx @@ -0,0 +1,13 @@ +import { useTranslation } from "react-i18next"; + +export default function SendWarning({ asset, network }: { asset?: string; network: string }) { + const { t } = useTranslation(); + return asset + ? t("Only send {{crypto}} on {{network}}. Sending other assets or using other networks may cause permanent loss.", { + crypto: asset, + network, + }) + : t("Only send assets on {{chain}}. Sending funds from other networks may cause permanent loss.", { + chain: network, + }); +} diff --git a/src/i18n/es-AR.json b/src/i18n/es-AR.json index 8d2e06cec0..0b22dd8796 100644 --- a/src/i18n/es-AR.json +++ b/src/i18n/es-AR.json @@ -94,7 +94,6 @@ "Maximize earnings, effortlessly": "Maximizá tus ganancias sin esfuerzo", "Move from Visa Platinum to Visa Signature and unlock premium benefits and perks.": "Pasá de Visa Platinum a Visa Signature y desbloqueá beneficios y ventajas premium.", "Only send assets on {{chain}}. Sending funds from other networks may cause permanent loss.": "Solo enviá activos en {{chain}}. Enviar fondos desde otras redes puede causar pérdida permanente.", - "Only send assets on {{chain}}. Sending funds from other networks may cause permanent loss. Learn more about adding funds.": "Solo enviá activos en {{chain}}. Enviar fondos desde otras redes puede causar pérdida permanente. Aprendé más sobre cómo agregar fondos.", "Only send {{crypto}} on {{network}}. Sending other assets or using other networks may cause permanent loss.": "Solo enviá {{crypto}} en {{network}}. Enviar otros activos o usar otras redes puede causar una pérdida permanente.", "Open your {{provider}} virtual account": "Abrí tu cuenta virtual de {{provider}}", "PAY NOW AND SAVE {{percent}}": "PAGÁ AHORA Y AHORRÁ {{percent}}", diff --git a/src/i18n/es.json b/src/i18n/es.json index e9b1621f55..224287f399 100644 --- a/src/i18n/es.json +++ b/src/i18n/es.json @@ -480,9 +480,9 @@ "On {{chain}}": "En {{chain}}", "On chain": "On-chain", "Onchain credit is powered by Exactly Protocol and is subject to separate Terms and conditions. The Exa App does not issue or guarantee any funding.": "El crédito on-chain es ofrecido por Exactly Protocol y está sujeto a Términos y Condiciones separados. Exa App no emite ni garantiza ningún financiamiento.", + "Only {{assets}} on {{chain}} serve as collateral, earn yield while held, and increase your Exa Card credit limit.": "Solo {{assets}} en {{chain}} sirven como colateral, generan rendimiento mientras los mantienes y aumentan el límite de crédito de tu Exa Card.", "Only send {{crypto}} on {{network}}. Sending other assets or using other networks may cause permanent loss.": "Solo envía {{crypto}} en {{network}}. Enviar otros activos o usar otras redes puede causar una pérdida permanente.", "Only send assets on {{chain}}. Sending funds from other networks may cause permanent loss.": "Solo envía activos en {{chain}}. Enviar fondos desde otras redes puede causar pérdida permanente.", - "Only send assets on {{chain}}. Sending funds from other networks may cause permanent loss. Learn more about adding funds.": "Solo envía activos en {{chain}}. Enviar fondos desde otras redes puede causar pérdida permanente. Aprende más sobre cómo agregar fondos.", "Only your USDC balance counts toward your spending limit.": "Solo tu saldo en USDC cuenta para tu límite de gasto.", "Open Exa Discord": "Abrir Exa en Discord", "Open Exa on X": "Abrir Exa en X", @@ -633,6 +633,7 @@ "Share {{chain}} address": "Compartir dirección de {{chain}}", "Share": "Compartir", "Show PIN": "Mostrar PIN", + "Show QR": "Mostrar QR", "Show QR Code": "Mostrar código QR", "Show sensitive": "Mostrar sensibles", "Sign in": "Iniciar sesión", @@ -784,6 +785,7 @@ "Visa Signature benefits": "Beneficios Visa Signature", "Visa Signature Exa Card benefits": "Beneficios de la Exa Card Visa Signature", "Visa": "Visa", + "Wallet address": "Dirección de billetera", "Wallet is busy. Please complete the pending request.": "La billetera está ocupada. Completa la solicitud pendiente.", "We couldn’t complete your verification": "No pudimos completar tu verificación", "We couldn’t verify your identity": "No pudimos verificar tu identidad", diff --git a/src/i18n/pt.json b/src/i18n/pt.json index 0337cbae5e..4a8dac5a68 100644 --- a/src/i18n/pt.json +++ b/src/i18n/pt.json @@ -480,9 +480,9 @@ "On {{chain}}": "Na {{chain}}", "On chain": "On-chain", "Onchain credit is powered by Exactly Protocol and is subject to separate Terms and conditions. The Exa App does not issue or guarantee any funding.": "O crédito on-chain é oferecido pelo Exactly Protocol e está sujeito a Termos e Condições separados. O Exa App não emite nem garante nenhum financiamento.", + "Only {{assets}} on {{chain}} serve as collateral, earn yield while held, and increase your Exa Card credit limit.": "Apenas {{assets}} em {{chain}} servem como colateral, geram rendimento enquanto você os mantém e aumentam o limite de crédito do seu Exa Card.", "Only send {{crypto}} on {{network}}. Sending other assets or using other networks may cause permanent loss.": "Envie apenas {{crypto}} na rede {{network}}. Enviar outros ativos ou usar outras redes pode causar perda permanente.", "Only send assets on {{chain}}. Sending funds from other networks may cause permanent loss.": "Envie ativos apenas na {{chain}}. Enviar fundos de outras redes pode causar perda permanente.", - "Only send assets on {{chain}}. Sending funds from other networks may cause permanent loss. Learn more about adding funds.": "Envie ativos apenas na {{chain}}. Enviar fundos de outras redes pode causar perda permanente. Saiba mais sobre como adicionar fundos.", "Only your USDC balance counts toward your spending limit.": "Apenas seu saldo em USDC conta para seu limite de gastos.", "Open Exa Discord": "Abrir Exa no Discord", "Open Exa on X": "Abrir Exa no X", @@ -633,6 +633,7 @@ "Share {{chain}} address": "Compartilhar endereço de {{chain}}", "Share": "Compartilhar", "Show PIN": "Mostrar PIN", + "Show QR": "Mostrar QR", "Show QR Code": "Mostrar código QR", "Show sensitive": "Mostrar sensíveis", "Sign in": "Entrar", @@ -784,6 +785,7 @@ "Visa Signature benefits": "Benefícios Visa Signature", "Visa Signature Exa Card benefits": "Benefícios do Exa Card Visa Signature", "Visa": "Visa", + "Wallet address": "Endereço da carteira", "Wallet is busy. Please complete the pending request.": "A carteira está ocupada. Conclua a solicitação pendente.", "We couldn’t complete your verification": "Não foi possível concluir sua verificação", "We couldn’t verify your identity": "Não foi possível verificar sua identidade", From f4a08d6e63d1185144ccc9513d58c027c0bc5219 Mon Sep 17 00:00:00 2001 From: guillermo dieguez Date: Thu, 23 Jul 2026 13:33:03 -0300 Subject: [PATCH 05/20] =?UTF-8?q?=E2=9C=A8=20app:=20add=20cryptocurrencies?= =?UTF-8?q?=20asset=20list?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .changeset/eager-owls-list.md | 5 + src/app/(main)/add-funds/_layout.tsx | 1 + src/app/(main)/add-funds/assets.tsx | 1 + src/components/add-funds/AddCrypto.tsx | 20 ++-- src/components/add-funds/AddFunds.tsx | 38 +------- src/components/add-funds/Assets.tsx | 128 +++++++++++++++++++++++++ src/i18n/es.json | 4 - src/i18n/pt.json | 4 - 8 files changed, 153 insertions(+), 48 deletions(-) create mode 100644 .changeset/eager-owls-list.md create mode 100644 src/app/(main)/add-funds/assets.tsx create mode 100644 src/components/add-funds/Assets.tsx diff --git a/.changeset/eager-owls-list.md b/.changeset/eager-owls-list.md new file mode 100644 index 0000000000..facdf7f085 --- /dev/null +++ b/.changeset/eager-owls-list.md @@ -0,0 +1,5 @@ +--- +"@exactly/mobile": patch +--- + +✨ add cryptocurrencies asset list diff --git a/src/app/(main)/add-funds/_layout.tsx b/src/app/(main)/add-funds/_layout.tsx index 7a9d15ba86..c40710962b 100644 --- a/src/app/(main)/add-funds/_layout.tsx +++ b/src/app/(main)/add-funds/_layout.tsx @@ -10,6 +10,7 @@ export default function AddFundsLayout() { + diff --git a/src/app/(main)/add-funds/assets.tsx b/src/app/(main)/add-funds/assets.tsx new file mode 100644 index 0000000000..2baf829beb --- /dev/null +++ b/src/app/(main)/add-funds/assets.tsx @@ -0,0 +1 @@ +export { default } from "../../../components/add-funds/Assets"; diff --git a/src/components/add-funds/AddCrypto.tsx b/src/components/add-funds/AddCrypto.tsx index ad0bf07851..98060394f5 100644 --- a/src/components/add-funds/AddCrypto.tsx +++ b/src/components/add-funds/AddCrypto.tsx @@ -42,9 +42,15 @@ export default function AddCrypto() { const { address: accountAddress } = useAccount(); const { supportedAssets, isPending } = useMarkets(); const { t } = useTranslation(); - const { provider, currency: currencyParameter, network: networkParameter } = useLocalSearchParams(); + const { + provider, + currency: currencyParameter, + network: networkParameter, + asset: assetParameter, + } = useLocalSearchParams(); const currency = typeof currencyParameter === "string" ? currencyParameter : ""; const network = typeof networkParameter === "string" ? networkParameter : ""; + const asset = typeof assetParameter === "string" ? assetParameter : ""; const isBridge = provider === "bridge" && !!currency && !!network; const { data, isError, isFetching, refetch } = useQuery({ @@ -63,7 +69,7 @@ export default function AddCrypto() { const address = isBridge ? depositAddress : accountAddress; const networkName = isBridge && typeof network === "string" ? network : chain.name; - const assets = isBridge && typeof currency === "string" ? [currency] : supportedAssets; + const assets = isBridge ? [currency] : asset ? [asset] : supportedAssets; const toast = useToastController(); const [copyAddressShown, setCopyAddressShown] = useState(false); @@ -119,8 +125,8 @@ export default function AddCrypto() { setSupportedAssetsShown(true)} + isPending={!isBridge && !asset && isPending} + onPress={isBridge || asset ? undefined : () => setSupportedAssetsShown(true)} /> - {!isBridge && ( + {!isBridge && !asset && ( { @@ -292,7 +298,7 @@ export default function AddCrypto() { - + p.onramp.currencies.some((item) => typeof item === "string")); - const hasCrypto = - providers && - Object.values(providers).some((p) => - p.onramp.currencies.some((item) => typeof item === "object" && "network" in item), - ); + if (type === "crypto") return ; return ( @@ -92,7 +85,7 @@ export default function AddFunds() { icon={ArrowLeft} aria-label={t("Back")} onPress={() => { - if (type === "crypto" || type === "fiat") { + if (type === "fiat") { if (router.canGoBack()) { router.back(); } else { @@ -104,7 +97,7 @@ export default function AddFunds() { }} /> - {t(type === "crypto" ? "Cryptocurrencies" : type === "fiat" ? "Bank transfers" : "Add Funds")} + {t(type === "fiat" ? "Bank transfers" : "Add Funds")} { - router.push({ pathname: "/add-funds", params: { type: "crypto" } }); + router.push("/add-funds/assets"); }} /> {hasFiat !== false && chain.id !== base.id && ( @@ -181,27 +174,6 @@ export default function AddFunds() { )} )} - {type === "crypto" && ( - <> - {hasCrypto && ( - { - openBrowser("https://x.com/exa_app/status/2071690658339770622").catch(reportError); - }} - /> - )} - } - title={t("From another wallet")} - subtitle={t("On {{chain}}", { chain: chain.name })} - onPress={() => { - router.push("/add-funds/add-crypto"); - }} - /> - - )} {type === "fiat" && countryCode && isPending && ( diff --git a/src/components/add-funds/Assets.tsx b/src/components/add-funds/Assets.tsx new file mode 100644 index 0000000000..8755da064a --- /dev/null +++ b/src/components/add-funds/Assets.tsx @@ -0,0 +1,128 @@ +import React, { useMemo, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { Pressable } from "react-native"; + +import { useRouter } from "expo-router"; + +import { ArrowLeft, CircleHelp, Info } from "@tamagui/lucide-icons"; +import { ScrollView, XStack, YStack } from "tamagui"; + +import chain from "@exactly/common/generated/chain"; + +import AddFundsOption from "./AddFundsOption"; +import EducationSheet from "./EducationSheet"; +import { presentArticle } from "../../utils/intercom"; +import reportError from "../../utils/reportError"; +import useMarkets from "../../utils/useMarkets"; +import AssetLogo from "../shared/AssetLogo"; +import IconButton from "../shared/IconButton"; +import SafeView from "../shared/SafeView"; +import Skeleton from "../shared/Skeleton"; +import Text from "../shared/Text"; +import View from "../shared/View"; + +export default function Assets() { + const router = useRouter(); + const { t } = useTranslation(); + const { markets, supportedAssets, isPending } = useMarkets(); + const [collateralShown, setCollateralShown] = useState(false); + const assets = useMemo(() => { + if (!markets) return []; + const excluded = new Set(["USDC.e", "DAI"]); + const available = markets + .filter((market) => !excluded.has(market.symbol.slice(3))) + .map((market) => + market.symbol.slice(3) === "WETH" + ? { symbol: "ETH", name: "Ether" } + : { symbol: market.symbol.slice(3), name: market.assetName }, + ); + const pinned = ["USDC", "ETH", "WBTC", "wstETH", "OP"]; + return [ + ...pinned.flatMap((symbol) => available.find((asset) => asset.symbol === symbol) ?? []), + ...available.filter((asset) => !pinned.includes(asset.symbol)), + ]; + }, [markets]); + return ( + + + + { + if (router.canGoBack()) { + router.back(); + } else { + router.replace("/add-funds"); + } + }} + /> + + {t("Cryptocurrencies")} + + { + presentArticle("8950801").catch(reportError); + }} + /> + + + + + + {t("Supported assets")} + + setCollateralShown(true)}> + + + + + {isPending + ? Array.from({ length: 5 }, (_, index) => ) + : assets.map(({ symbol, name }) => ( + } + title={symbol} + subtitle={name} + onPress={() => { + router.push({ pathname: "/add-funds/add-crypto", params: { asset: symbol } }); + }} + /> + ))} + + + + { + setCollateralShown(false); + }} + title={t("Supported assets")} + article="8950805" + > + + {isPending + ? Array.from({ length: 5 }, (_, index) => ) + : supportedAssets.map((symbol) => )} + + + {t( + "Only {{assets}} on {{chain}} serve as collateral, earn yield while held, and increase your Exa Card credit limit.", + { assets: supportedAssets.join(", "), chain: chain.name }, + )} + + + + + ); +} diff --git a/src/i18n/es.json b/src/i18n/es.json index 224287f399..bfe3c5ddcb 100644 --- a/src/i18n/es.json +++ b/src/i18n/es.json @@ -204,8 +204,6 @@ "Credit limit info": "Información del límite de crédito", "Credit limit: {{asset}}": "Límite de crédito: {{asset}}", "Credit limit": "Límite de crédito", - "Crypto on-ramps are no longer available as of July 1st.": "Los on-ramps de cripto dejaron de estar disponibles desde el 1 de Julio.", - "Crypto on-ramps will no longer be available from July 1st.": "Los on-ramps de cripto dejarán de estar disponibles a partir del 1 de Julio.", "Cryptocurrencies": "Criptomonedas", "Current debt": "Deuda actual", "CVV": "CVV", @@ -321,7 +319,6 @@ "FREE": "GRATIS", "Freeze card": "Congelar tarjeta", "Freeze your card?": "¿Congelar tu tarjeta?", - "From another wallet": "Desde otra billetera", "From any account in your name": "Desde cualquier cuenta a tu nombre", "From any account": "Desde cualquier cuenta", "From any Argentine bank account in your name": "Desde cualquier cuenta bancaria argentina a tu nombre", @@ -477,7 +474,6 @@ "Nothing to see here for now. Once you add funds or make a payment, all your account activity will appear in this section.": "Nada que ver por ahora. Una vez que agregues fondos o realices un pago, toda la actividad de tu cuenta aparecerá en esta sección.", "Now": "Ahora", "Numbers only": "Solo números", - "On {{chain}}": "En {{chain}}", "On chain": "On-chain", "Onchain credit is powered by Exactly Protocol and is subject to separate Terms and conditions. The Exa App does not issue or guarantee any funding.": "El crédito on-chain es ofrecido por Exactly Protocol y está sujeto a Términos y Condiciones separados. Exa App no emite ni garantiza ningún financiamiento.", "Only {{assets}} on {{chain}} serve as collateral, earn yield while held, and increase your Exa Card credit limit.": "Solo {{assets}} en {{chain}} sirven como colateral, generan rendimiento mientras los mantienes y aumentan el límite de crédito de tu Exa Card.", diff --git a/src/i18n/pt.json b/src/i18n/pt.json index 4a8dac5a68..e57b3ac766 100644 --- a/src/i18n/pt.json +++ b/src/i18n/pt.json @@ -204,8 +204,6 @@ "Credit limit info": "Informações do limite de crédito", "Credit limit: {{asset}}": "Limite de crédito: {{asset}}", "Credit limit": "Limite de crédito", - "Crypto on-ramps are no longer available as of July 1st.": "Os on-ramps de cripto não estão mais disponíveis desde 1º de Julho.", - "Crypto on-ramps will no longer be available from July 1st.": "Os on-ramps de cripto não estarão mais disponíveis a partir de 1º de Julho.", "Cryptocurrencies": "Criptomoedas", "Current debt": "Dívida atual", "CVV": "CVV", @@ -321,7 +319,6 @@ "FREE": "GRÁTIS", "Freeze card": "Congelar cartão", "Freeze your card?": "Congelar seu cartão?", - "From another wallet": "De outra carteira", "From any account in your name": "De qualquer conta em seu nome", "From any account": "De qualquer conta", "From any Argentine bank account in your name": "De qualquer conta bancária argentina em seu nome", @@ -477,7 +474,6 @@ "Nothing to see here for now. Once you add funds or make a payment, all your account activity will appear in this section.": "Nada para ver por enquanto. Assim que você adicionar fundos ou fizer um pagamento, toda a atividade da sua conta aparecerá nesta seção.", "Now": "Agora", "Numbers only": "Apenas números", - "On {{chain}}": "Na {{chain}}", "On chain": "On-chain", "Onchain credit is powered by Exactly Protocol and is subject to separate Terms and conditions. The Exa App does not issue or guarantee any funding.": "O crédito on-chain é oferecido pelo Exactly Protocol e está sujeito a Termos e Condições separados. O Exa App não emite nem garante nenhum financiamento.", "Only {{assets}} on {{chain}} serve as collateral, earn yield while held, and increase your Exa Card credit limit.": "Apenas {{assets}} em {{chain}} servem como colateral, geram rendimento enquanto você os mantém e aumentam o limite de crédito do seu Exa Card.", From afac302d9f9c2b284c18df38a6640eefbecc87e8 Mon Sep 17 00:00:00 2001 From: guillermo dieguez Date: Thu, 23 Jul 2026 13:40:58 -0300 Subject: [PATCH 06/20] =?UTF-8?q?=E2=9C=A8=20app:=20add=20network=20select?= =?UTF-8?q?ion=20to=20receive=20flow?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .changeset/witty-geese-roam.md | 5 + src/app/(main)/add-funds/_layout.tsx | 1 + src/app/(main)/add-funds/network.tsx | 1 + src/components/add-funds/AddCrypto.tsx | 35 +++++- src/components/add-funds/AddFundsOption.tsx | 24 +++- src/components/add-funds/Assets.tsx | 2 +- src/components/add-funds/Network.tsx | 119 ++++++++++++++++++++ src/i18n/es.json | 5 + src/i18n/pt.json | 5 + 9 files changed, 187 insertions(+), 10 deletions(-) create mode 100644 .changeset/witty-geese-roam.md create mode 100644 src/app/(main)/add-funds/network.tsx create mode 100644 src/components/add-funds/Network.tsx diff --git a/.changeset/witty-geese-roam.md b/.changeset/witty-geese-roam.md new file mode 100644 index 0000000000..cefbfac03c --- /dev/null +++ b/.changeset/witty-geese-roam.md @@ -0,0 +1,5 @@ +--- +"@exactly/mobile": patch +--- + +✨ add network selection to receive flow diff --git a/src/app/(main)/add-funds/_layout.tsx b/src/app/(main)/add-funds/_layout.tsx index c40710962b..5d879484e4 100644 --- a/src/app/(main)/add-funds/_layout.tsx +++ b/src/app/(main)/add-funds/_layout.tsx @@ -14,6 +14,7 @@ export default function AddFundsLayout() { + diff --git a/src/app/(main)/add-funds/network.tsx b/src/app/(main)/add-funds/network.tsx new file mode 100644 index 0000000000..60ac5fc5ad --- /dev/null +++ b/src/app/(main)/add-funds/network.tsx @@ -0,0 +1 @@ +export { default } from "../../../components/add-funds/Network"; diff --git a/src/components/add-funds/AddCrypto.tsx b/src/components/add-funds/AddCrypto.tsx index 98060394f5..d5b08bd01e 100644 --- a/src/components/add-funds/AddCrypto.tsx +++ b/src/components/add-funds/AddCrypto.tsx @@ -17,7 +17,9 @@ import chain from "@exactly/common/generated/chain"; import BridgeDisclaimer from "./BridgeDisclaimer"; import EducationSheet from "./EducationSheet"; +import alchemyChainById from "../../utils/alchemyChains"; import { presentArticle } from "../../utils/intercom"; +import { lifiChainsOptions } from "../../utils/lifi"; import networkLogos from "../../utils/networkLogos"; import reportError from "../../utils/reportError"; import { getRampQuote } from "../../utils/server"; @@ -28,6 +30,7 @@ import ChainLogo from "../shared/ChainLogo"; import CopyAddressSheet from "../shared/CopyAddressSheet"; import IconButton from "../shared/IconButton"; import Image from "../shared/Image"; +import InfoAlert from "../shared/InfoAlert"; import ModalSheet from "../shared/ModalSheet"; import SafeView from "../shared/SafeView"; import SendWarning from "../shared/SendWarning"; @@ -47,10 +50,16 @@ export default function AddCrypto() { currency: currencyParameter, network: networkParameter, asset: assetParameter, + chainId: chainIdParameter, } = useLocalSearchParams(); const currency = typeof currencyParameter === "string" ? currencyParameter : ""; const network = typeof networkParameter === "string" ? networkParameter : ""; const asset = typeof assetParameter === "string" ? assetParameter : ""; + const parsed = Number(chainIdParameter); + const receiveChainId = + typeof chainIdParameter === "string" && Number.isInteger(parsed) && parsed > 0 && parsed !== chain.id + ? parsed + : undefined; const isBridge = provider === "bridge" && !!currency && !!network; const { data, isError, isFetching, refetch } = useQuery({ @@ -67,8 +76,18 @@ export default function AddCrypto() { const depositAddress = deposit && "address" in deposit ? deposit.address : undefined; const memo = deposit && "memo" in deposit ? deposit.memo : undefined; + const { data: receiveChain } = useQuery({ + ...lifiChainsOptions, + enabled: !!receiveChainId, + select: (chains) => chains.find((c) => c.id === receiveChainId), + }); + const address = isBridge ? depositAddress : accountAddress; - const networkName = isBridge && typeof network === "string" ? network : chain.name; + const networkName = isBridge + ? network + : receiveChainId + ? (receiveChain?.name ?? alchemyChainById.get(receiveChainId)?.name ?? `#${receiveChainId}`) + : chain.name; const assets = isBridge ? [currency] : asset ? [asset] : supportedAssets; const toast = useToastController(); @@ -130,6 +149,7 @@ export default function AddCrypto() { /> @@ -244,8 +264,8 @@ export default function AddCrypto() { setCopyAddressShown(false); }} address={isBridge ? depositAddress : undefined} - network={isBridge && typeof network === "string" ? network : undefined} - networkLogo={isBridge && typeof network === "string" ? networkLogos[network] : undefined} + network={isBridge ? network : receiveChainId ? networkName : undefined} + networkLogo={isBridge ? networkLogos[network] : receiveChain?.logoURI} assets={isBridge || asset ? assets : undefined} /> {!isBridge && !asset && ( @@ -285,6 +305,11 @@ export default function AddCrypto() { {isBridge && } + {!!receiveChainId && !!asset && ( + + )} ) : ( - + )} {name} diff --git a/src/components/add-funds/AddFundsOption.tsx b/src/components/add-funds/AddFundsOption.tsx index 0dba030eb5..2da2f1ed36 100644 --- a/src/components/add-funds/AddFundsOption.tsx +++ b/src/components/add-funds/AddFundsOption.tsx @@ -7,6 +7,7 @@ import Text from "../shared/Text"; import View from "../shared/View"; export default function AddFundsOption({ + badge, icon, title, subtitle, @@ -14,11 +15,12 @@ export default function AddFundsOption({ loading, onPress, }: { + badge?: string; disabled?: boolean; icon: React.ReactElement; loading?: boolean; onPress: () => void; - subtitle: string; + subtitle?: string; title: string; }) { return ( @@ -50,11 +52,25 @@ export default function AddFundsOption({ {title} - - {subtitle} - + {!!subtitle && ( + + {subtitle} + + )} + {!!badge && ( + + + {badge} + + + )} {loading ? ( diff --git a/src/components/add-funds/Assets.tsx b/src/components/add-funds/Assets.tsx index 8755da064a..df58da0f6a 100644 --- a/src/components/add-funds/Assets.tsx +++ b/src/components/add-funds/Assets.tsx @@ -88,7 +88,7 @@ export default function Assets() { title={symbol} subtitle={name} onPress={() => { - router.push({ pathname: "/add-funds/add-crypto", params: { asset: symbol } }); + router.push({ pathname: "/add-funds/network", params: { asset: symbol } }); }} /> ))} diff --git a/src/components/add-funds/Network.tsx b/src/components/add-funds/Network.tsx new file mode 100644 index 0000000000..d554e5973f --- /dev/null +++ b/src/components/add-funds/Network.tsx @@ -0,0 +1,119 @@ +import React, { useMemo, useState } from "react"; +import { useTranslation } from "react-i18next"; + +import { Redirect, useLocalSearchParams, useRouter } from "expo-router"; + +import { ArrowLeft, CircleHelp } from "@tamagui/lucide-icons"; +import { ScrollView, XStack, YStack } from "tamagui"; + +import { useQuery } from "@tanstack/react-query"; +import { arbitrum, base, bsc, mainnet, optimism, polygon } from "viem/chains"; + +import chain from "@exactly/common/generated/chain"; + +import AddFundsOption from "./AddFundsOption"; +import alchemyChainById from "../../utils/alchemyChains"; +import { presentArticle } from "../../utils/intercom"; +import { lifiChainsOptions, lifiTokensOptions } from "../../utils/lifi"; +import reportError from "../../utils/reportError"; +import ChainLogo from "../shared/ChainLogo"; +import IconButton from "../shared/IconButton"; +import SafeView from "../shared/SafeView"; +import Text from "../shared/Text"; +import View from "../shared/View"; + +export default function Network() { + const router = useRouter(); + const { t } = useTranslation(); + const { asset: assetParameter } = useLocalSearchParams(); + const asset = typeof assetParameter === "string" ? assetParameter : ""; + const { data: lifiChains } = useQuery(lifiChainsOptions); + const { data: tokens } = useQuery(lifiTokensOptions); + const sorted = useMemo(() => { + const available = new Set( + (tokens ?? []).filter((token) => token.symbol === asset).map((token) => token.chainId), + ); + const others = (lifiChains ?? []).filter( + (c) => + c.id !== chain.id && + c.mainnet && + available.has(c.id) && + alchemyChainById.has(c.id) && + !alchemyChainById.get(c.id)?.testnet, + ); + const pinned: number[] = [mainnet.id, base.id, arbitrum.id, polygon.id, bsc.id].filter((id) => id !== chain.id); + return [ + ...pinned.flatMap((id) => others.find((c) => c.id === id) ?? []), + ...others.filter((c) => !pinned.includes(c.id)).sort((a, b) => a.name.localeCompare(b.name)), + ]; + }, [tokens, lifiChains, asset]); + if (!asset) return ; + function selectNetwork(chainId: number) { + router.push({ + pathname: "/add-funds/add-crypto", + params: chainId === chain.id ? { asset } : { asset, chainId: String(chainId) }, + }); + } + return ( + + + + { + if (router.canGoBack()) { + router.back(); + } else { + router.replace("/add-funds/assets"); + } + }} + /> + + {t("Select network")} + + { + presentArticle("8950801").catch(reportError); + }} + /> + + + + + + {t("Native network")} + + } + title={chain.id === optimism.id ? "Optimism" : chain.name} + subtitle={chain.id === optimism.id ? optimism.name : undefined} + badge={t("Recommended")} + onPress={() => selectNetwork(chain.id)} + /> + + {sorted.length > 0 && ( + + + {t("Other networks")} + + + {sorted.map((c) => ( + } + title={c.name} + onPress={() => selectNetwork(c.id)} + /> + ))} + + + )} + + + + + ); +} diff --git a/src/i18n/es.json b/src/i18n/es.json index bfe3c5ddcb..b8ff6e40f6 100644 --- a/src/i18n/es.json +++ b/src/i18n/es.json @@ -453,6 +453,7 @@ "Must be at least 4 characters": "Debe tener al menos 4 caracteres", "My Exa Card": "Mi Exa Card", "N/A": "N/D", + "Native network": "Red nativa", "Network fee": "Comisión de red", "Network reminder": "Recordatorio de red", "Network": "Red", @@ -476,6 +477,7 @@ "Numbers only": "Solo números", "On chain": "On-chain", "Onchain credit is powered by Exactly Protocol and is subject to separate Terms and conditions. The Exa App does not issue or guarantee any funding.": "El crédito on-chain es ofrecido por Exactly Protocol y está sujeto a Términos y Condiciones separados. Exa App no emite ni garantiza ningún financiamiento.", + "Once received, you'll need to bridge to {{asset}} on {{chain}}.": "Una vez recibido, deberás hacer bridge a {{asset}} en {{chain}}.", "Only {{assets}} on {{chain}} serve as collateral, earn yield while held, and increase your Exa Card credit limit.": "Solo {{assets}} en {{chain}} sirven como colateral, generan rendimiento mientras los mantienes y aumentan el límite de crédito de tu Exa Card.", "Only send {{crypto}} on {{network}}. Sending other assets or using other networks may cause permanent loss.": "Solo envía {{crypto}} en {{network}}. Enviar otros activos o usar otras redes puede causar una pérdida permanente.", "Only send assets on {{chain}}. Sending funds from other networks may cause permanent loss.": "Solo envía activos en {{chain}}. Enviar fondos desde otras redes puede causar pérdida permanente.", @@ -487,6 +489,7 @@ "optional": "opcional", "Optional second line": "Segunda línea opcional", "or": "o", + "Other networks": "Otras redes", "Overdue payment {{date}}, {{amount}}": "Pago vencido {{date}}, {{amount}}", "Overdue payments": "Pagos vencidos", "Paid": "Pagado", @@ -553,6 +556,7 @@ "Received": "Recibido", "Receiving address": "Dirección de recepción", "Recent": "Recientes", + "Recommended": "Recomendada", "beneficiary": "beneficiario", "Beneficiary's account details": "Datos de la cuenta del beneficiario", "Recovery from this network is not yet available. Your funds are safe and will be recoverable once we add support.": "La recuperación desde esta red aún no está disponible. Tus fondos están seguros y podrán recuperarse cuando agreguemos soporte.", @@ -596,6 +600,7 @@ "Select asset": "Seleccionar activo", "Select country": "Seleccionar país", "Select first due date": "Selecciona la primera fecha de vencimiento", + "Select network": "Selecciona la red", "Select source asset": "Selecciona el activo de origen", "Select state": "Selecciona un estado", "Select the asset to fund": "Selecciona el activo para financiar", diff --git a/src/i18n/pt.json b/src/i18n/pt.json index e57b3ac766..2e14e8e7e7 100644 --- a/src/i18n/pt.json +++ b/src/i18n/pt.json @@ -453,6 +453,7 @@ "Must be at least 4 characters": "Deve ter pelo menos 4 caracteres", "My Exa Card": "Meu Exa Card", "N/A": "N/D", + "Native network": "Rede nativa", "Network fee": "Taxa de rede", "Network reminder": "Lembrete de rede", "Network": "Rede", @@ -476,6 +477,7 @@ "Numbers only": "Apenas números", "On chain": "On-chain", "Onchain credit is powered by Exactly Protocol and is subject to separate Terms and conditions. The Exa App does not issue or guarantee any funding.": "O crédito on-chain é oferecido pelo Exactly Protocol e está sujeito a Termos e Condições separados. O Exa App não emite nem garante nenhum financiamento.", + "Once received, you'll need to bridge to {{asset}} on {{chain}}.": "Após o recebimento, você precisará fazer bridge para {{asset}} em {{chain}}.", "Only {{assets}} on {{chain}} serve as collateral, earn yield while held, and increase your Exa Card credit limit.": "Apenas {{assets}} em {{chain}} servem como colateral, geram rendimento enquanto você os mantém e aumentam o limite de crédito do seu Exa Card.", "Only send {{crypto}} on {{network}}. Sending other assets or using other networks may cause permanent loss.": "Envie apenas {{crypto}} na rede {{network}}. Enviar outros ativos ou usar outras redes pode causar perda permanente.", "Only send assets on {{chain}}. Sending funds from other networks may cause permanent loss.": "Envie ativos apenas na {{chain}}. Enviar fundos de outras redes pode causar perda permanente.", @@ -487,6 +489,7 @@ "optional": "opcional", "Optional second line": "Segunda linha opcional", "or": "ou", + "Other networks": "Outras redes", "Overdue payment {{date}}, {{amount}}": "Pagamento atrasado {{date}}, {{amount}}", "Overdue payments": "Pagamentos atrasados", "Paid": "Pago", @@ -553,6 +556,7 @@ "Received": "Recebido", "Receiving address": "Endereço de recebimento", "Recent": "Recentes", + "Recommended": "Recomendada", "beneficiary": "beneficiário", "Beneficiary's account details": "Dados da conta do beneficiário", "Recovery from this network is not yet available. Your funds are safe and will be recoverable once we add support.": "A recuperação desta rede ainda não está disponível. Seus fundos estão seguros e poderão ser recuperados assim que adicionarmos suporte.", @@ -596,6 +600,7 @@ "Select asset": "Selecionar ativo", "Select country": "Selecionar país", "Select first due date": "Selecione a primeira data de vencimento", + "Select network": "Selecione a rede", "Select source asset": "Selecione o ativo de origem", "Select state": "Selecione um estado", "Select the asset to fund": "Selecione o ativo para financiar", From 93b88999d6196c4ff3ab4d3ea0499c71fa441306 Mon Sep 17 00:00:00 2001 From: guillermo dieguez Date: Thu, 23 Jul 2026 13:49:46 -0300 Subject: [PATCH 07/20] =?UTF-8?q?=E2=9C=A8=20app:=20add=20bridge=20educati?= =?UTF-8?q?on=20sheet?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .changeset/quiet-cranes-teach.md | 5 + .../add-funds/BridgeNeededSheet.tsx | 168 ++++++++++++++++++ src/components/add-funds/Network.tsx | 25 ++- src/i18n/es.json | 6 + src/i18n/pt.json | 6 + src/utils/queryClient.ts | 7 + 6 files changed, 216 insertions(+), 1 deletion(-) create mode 100644 .changeset/quiet-cranes-teach.md create mode 100644 src/components/add-funds/BridgeNeededSheet.tsx diff --git a/.changeset/quiet-cranes-teach.md b/.changeset/quiet-cranes-teach.md new file mode 100644 index 0000000000..7a935bad0a --- /dev/null +++ b/.changeset/quiet-cranes-teach.md @@ -0,0 +1,5 @@ +--- +"@exactly/mobile": patch +--- + +✨ add bridge education sheet diff --git a/src/components/add-funds/BridgeNeededSheet.tsx b/src/components/add-funds/BridgeNeededSheet.tsx new file mode 100644 index 0000000000..4e9ee5524b --- /dev/null +++ b/src/components/add-funds/BridgeNeededSheet.tsx @@ -0,0 +1,168 @@ +import React, { useState } from "react"; +import { Trans, useTranslation } from "react-i18next"; +import { Pressable } from "react-native"; + +import { ArrowRight, Check, Info } from "@tamagui/lucide-icons"; +import { Checkbox, ScrollView, Separator, XStack, YStack } from "tamagui"; + +import chain from "@exactly/common/generated/chain"; + +import { presentArticle } from "../../utils/intercom"; +import reportError from "../../utils/reportError"; +import AssetLogo from "../shared/AssetLogo"; +import ChainLogo from "../shared/ChainLogo"; +import ModalSheet from "../shared/ModalSheet"; +import SafeView from "../shared/SafeView"; +import Button from "../shared/StyledButton"; +import Text from "../shared/Text"; +import View from "../shared/View"; + +export default function BridgeNeededSheet({ + asset, + chainId, + network, + onClose, + onContinue, + open, +}: { + asset: string; + chainId?: number; + network: string; + onClose: () => void; + onContinue: (hide: boolean) => void; + open: boolean; +}) { + const { t } = useTranslation(); + const [hide, setHide] = useState(false); + return ( + { + setHide(false); + onClose(); + }} + disableDrag + > + + + + + {t("Bridge needed after receiving")} + + + + + + + + + + + + + + + + + + + + + + + + + + + + { + presentArticle("8950805").catch(reportError); + }} + /> + ), + }} + /> + + + + setHide(!hide)}> + + + + + + + + {t("Don't show again")} + + + + + + + + ); +} + +function Step({ index, text }: { index: number; text: string }) { + return ( + + + + {index} + + + + {text} + + + ); +} diff --git a/src/components/add-funds/Network.tsx b/src/components/add-funds/Network.tsx index d554e5973f..5013be32aa 100644 --- a/src/components/add-funds/Network.tsx +++ b/src/components/add-funds/Network.tsx @@ -12,9 +12,11 @@ import { arbitrum, base, bsc, mainnet, optimism, polygon } from "viem/chains"; import chain from "@exactly/common/generated/chain"; import AddFundsOption from "./AddFundsOption"; +import BridgeNeededSheet from "./BridgeNeededSheet"; import alchemyChainById from "../../utils/alchemyChains"; import { presentArticle } from "../../utils/intercom"; import { lifiChainsOptions, lifiTokensOptions } from "../../utils/lifi"; +import queryClient from "../../utils/queryClient"; import reportError from "../../utils/reportError"; import ChainLogo from "../shared/ChainLogo"; import IconButton from "../shared/IconButton"; @@ -27,8 +29,10 @@ export default function Network() { const { t } = useTranslation(); const { asset: assetParameter } = useLocalSearchParams(); const asset = typeof assetParameter === "string" ? assetParameter : ""; + const [pendingChainId, setPendingChainId] = useState(); const { data: lifiChains } = useQuery(lifiChainsOptions); const { data: tokens } = useQuery(lifiTokensOptions); + const { data: bridgeAcknowledged } = useQuery({ queryKey: ["settings", "bridge-needed-shown"] }); const sorted = useMemo(() => { const available = new Set( (tokens ?? []).filter((token) => token.symbol === asset).map((token) => token.chainId), @@ -48,12 +52,19 @@ export default function Network() { ]; }, [tokens, lifiChains, asset]); if (!asset) return ; - function selectNetwork(chainId: number) { + function navigate(chainId: number) { router.push({ pathname: "/add-funds/add-crypto", params: chainId === chain.id ? { asset } : { asset, chainId: String(chainId) }, }); } + function selectNetwork(chainId: number) { + if (chainId !== chain.id && !bridgeAcknowledged) { + setPendingChainId(chainId); + return; + } + navigate(chainId); + } return ( @@ -113,6 +124,18 @@ export default function Network() { )} + c.id === pendingChainId)?.name ?? ""} + onClose={() => setPendingChainId(undefined)} + onContinue={(hide) => { + if (hide) queryClient.setQueryData(["settings", "bridge-needed-shown"], true); + if (pendingChainId !== undefined) navigate(pendingChainId); + setPendingChainId(undefined); + }} + /> ); diff --git a/src/i18n/es.json b/src/i18n/es.json index b8ff6e40f6..657c25950a 100644 --- a/src/i18n/es.json +++ b/src/i18n/es.json @@ -1,5 +1,6 @@ { "{{amount}} left": "{{amount}} restante", + "{{asset}} on {{network}} isn't a supported collateral asset. To earn yield and increase your Exa Card credit limit, you'll need to bridge it to {{asset}} on {{chain}}. Learn more.": "{{asset}} en {{network}} no es un activo de colateral soportado. Para generar rendimiento y aumentar el límite de crédito de tu Exa Card, deberás hacer bridge a {{asset}} en {{chain}}. Aprende más.", "{{assets}} and more": "{{assets}} y más", "{{count}} installments of_one": "{{count}} cuota de", "{{count}} installments of_other": "{{count}} cuotas de", @@ -123,6 +124,8 @@ "Bridge {{symbol}}": "Hacer bridge de {{symbol}}", "Bridge failed. Please try again.": "El bridge falló. Inténtalo de nuevo.", "Bridge failed": "El bridge falló", + "Bridge it to {{asset}} on {{chain}}.": "Haz bridge a {{asset}} en {{chain}}.", + "Bridge needed after receiving": "Bridge necesario después de recibir", "Bridge needs a few more details before creating your account.": "Bridge necesita algunos datos más antes de crear tu cuenta.", "Bridge needs more information": "Bridge necesita más información", "Bridge provides a United States virtual account, converts your {{currency}} to USDC, and sends the funds to Exa App.": "Bridge proporciona una cuenta virtual de Estados Unidos, convierte tus {{currency}} a USDC y envía los fondos a Exa App.", @@ -235,6 +238,7 @@ "Discounts on travel insurance": "Descuentos en seguros de viaje", "Document number": "Número de documento", "Dollars": "Dólares", + "Don't show again": "No mostrar de nuevo", "Double-check your address before sending funds to avoid losing them.": "Verifica tu dirección antes de enviar fondos para evitar perderlos.", "due {{date}}": "vence {{date}}", "Due {{date}}": "Vence {{date}}", @@ -307,6 +311,7 @@ "Fees": "Comisiones", "Fees and transfer times": "Comisiones y tiempos de transferencia", "Fetching best route...": "Buscando la mejor ruta...", + "Find {{asset}} on {{network}} and select it.": "Busca {{asset}} en {{network}} y selecciónalo.", "Finished": "Finalizado", "First due date: {{date}} - then every 28 days.": "Primer vencimiento: {{date}} - luego cada 28 días.", "First installment due": "Primera cuota a pagar", @@ -477,6 +482,7 @@ "Numbers only": "Solo números", "On chain": "On-chain", "Onchain credit is powered by Exactly Protocol and is subject to separate Terms and conditions. The Exa App does not issue or guarantee any funding.": "El crédito on-chain es ofrecido por Exactly Protocol y está sujeto a Términos y Condiciones separados. Exa App no emite ni garantiza ningún financiamiento.", + "Once received, go to your Portfolio.": "Una vez recibido, ve a tu Cartera.", "Once received, you'll need to bridge to {{asset}} on {{chain}}.": "Una vez recibido, deberás hacer bridge a {{asset}} en {{chain}}.", "Only {{assets}} on {{chain}} serve as collateral, earn yield while held, and increase your Exa Card credit limit.": "Solo {{assets}} en {{chain}} sirven como colateral, generan rendimiento mientras los mantienes y aumentan el límite de crédito de tu Exa Card.", "Only send {{crypto}} on {{network}}. Sending other assets or using other networks may cause permanent loss.": "Solo envía {{crypto}} en {{network}}. Enviar otros activos o usar otras redes puede causar una pérdida permanente.", diff --git a/src/i18n/pt.json b/src/i18n/pt.json index 2e14e8e7e7..3ba587cae1 100644 --- a/src/i18n/pt.json +++ b/src/i18n/pt.json @@ -1,5 +1,6 @@ { "{{amount}} left": "{{amount}} restante", + "{{asset}} on {{network}} isn't a supported collateral asset. To earn yield and increase your Exa Card credit limit, you'll need to bridge it to {{asset}} on {{chain}}. Learn more.": "{{asset}} em {{network}} não é um ativo de colateral suportado. Para gerar rendimento e aumentar o limite de crédito do seu Exa Card, você precisará fazer bridge para {{asset}} em {{chain}}. Saiba mais.", "{{assets}} and more": "{{assets}} e mais", "{{count}} installments of_one": "{{count}} parcela de", "{{count}} installments of_other": "{{count}} parcelas de", @@ -123,6 +124,8 @@ "Bridge {{symbol}}": "Fazer bridge de {{symbol}}", "Bridge failed. Please try again.": "O bridge falhou. Tente novamente.", "Bridge failed": "O bridge falhou", + "Bridge it to {{asset}} on {{chain}}.": "Faça bridge para {{asset}} em {{chain}}.", + "Bridge needed after receiving": "Bridge necessário após o recebimento", "Bridge needs a few more details before creating your account.": "Bridge precisa de mais alguns dados antes de criar sua conta.", "Bridge needs more information": "Bridge precisa de mais informações", "Bridge provides a United States virtual account, converts your {{currency}} to USDC, and sends the funds to Exa App.": "A Bridge fornece uma conta virtual dos Estados Unidos, converte seus {{currency}} em USDC e envia os fundos para o Exa App.", @@ -235,6 +238,7 @@ "Discounts on travel insurance": "Descontos em seguros de viagem", "Document number": "Número do documento", "Dollars": "Dólares", + "Don't show again": "Não mostrar novamente", "Double-check your address before sending funds to avoid losing them.": "Verifique seu endereço antes de enviar fundos para evitar perdê-los.", "due {{date}}": "vence {{date}}", "Due {{date}}": "Vence {{date}}", @@ -307,6 +311,7 @@ "Fees": "Taxas", "Fees and transfer times": "Taxas e tempos de transferência", "Fetching best route...": "Buscando a melhor rota...", + "Find {{asset}} on {{network}} and select it.": "Encontre {{asset}} em {{network}} e selecione-o.", "Finished": "Finalizado", "First due date: {{date}} - then every 28 days.": "Primeiro vencimento: {{date}} - depois a cada 28 dias.", "First installment due": "Primeira parcela a pagar", @@ -477,6 +482,7 @@ "Numbers only": "Apenas números", "On chain": "On-chain", "Onchain credit is powered by Exactly Protocol and is subject to separate Terms and conditions. The Exa App does not issue or guarantee any funding.": "O crédito on-chain é oferecido pelo Exactly Protocol e está sujeito a Termos e Condições separados. O Exa App não emite nem garante nenhum financiamento.", + "Once received, go to your Portfolio.": "Após o recebimento, vá para o seu Portfólio.", "Once received, you'll need to bridge to {{asset}} on {{chain}}.": "Após o recebimento, você precisará fazer bridge para {{asset}} em {{chain}}.", "Only {{assets}} on {{chain}} serve as collateral, earn yield while held, and increase your Exa Card credit limit.": "Apenas {{assets}} em {{chain}} servem como colateral, geram rendimento enquanto você os mantém e aumentam o limite de crédito do seu Exa Card.", "Only send {{crypto}} on {{network}}. Sending other assets or using other networks may cause permanent loss.": "Envie apenas {{crypto}} na rede {{network}}. Enviar outros ativos ou usar outras redes pode causar perda permanente.", diff --git a/src/utils/queryClient.ts b/src/utils/queryClient.ts index 6018f709e5..6dbecec471 100644 --- a/src/utils/queryClient.ts +++ b/src/utils/queryClient.ts @@ -227,6 +227,13 @@ queryClient.setQueryDefaults(["settings", "defi-intro-shown"], { gcTime: Infinity, queryFn: () => queryClient.getQueryData(["settings", "defi-intro-shown"]), }); +queryClient.setQueryDefaults(["settings", "bridge-needed-shown"], { + initialData: false, + retry: false, + staleTime: Infinity, + gcTime: Infinity, + queryFn: () => queryClient.getQueryData(["settings", "bridge-needed-shown"]), +}); queryClient.setQueryDefaults(["defi", "usdc-funding-connected"], { initialData: false, retry: false, From 0a33527927f77a7b940c36a7ac7e28ae9d1ea0d7 Mon Sep 17 00:00:00 2001 From: guillermo dieguez Date: Thu, 23 Jul 2026 14:00:59 -0300 Subject: [PATCH 08/20] =?UTF-8?q?=E2=9C=A8=20app:=20add=20network=20filter?= =?UTF-8?q?=20to=20asset=20select=20sheet?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .changeset/sly-foxes-sift.md | 5 + src/components/add-funds/AssetSelectSheet.tsx | 150 ++++++++++++++---- src/i18n/es.json | 2 + src/i18n/pt.json | 2 + 4 files changed, 128 insertions(+), 31 deletions(-) create mode 100644 .changeset/sly-foxes-sift.md diff --git a/.changeset/sly-foxes-sift.md b/.changeset/sly-foxes-sift.md new file mode 100644 index 0000000000..755c7dc91a --- /dev/null +++ b/.changeset/sly-foxes-sift.md @@ -0,0 +1,5 @@ +--- +"@exactly/mobile": patch +--- + +✨ add network filter to asset select sheet diff --git a/src/components/add-funds/AssetSelectSheet.tsx b/src/components/add-funds/AssetSelectSheet.tsx index 3d62c8162b..f8d76eb00a 100644 --- a/src/components/add-funds/AssetSelectSheet.tsx +++ b/src/components/add-funds/AssetSelectSheet.tsx @@ -2,12 +2,13 @@ import React, { useCallback, useMemo, useState } from "react"; import { useTranslation } from "react-i18next"; import { Pressable } from "react-native"; -import { Search } from "@tamagui/lucide-icons"; +import { Check, ChevronDown, Network, Search } from "@tamagui/lucide-icons"; import { ScrollView, XStack, YStack } from "tamagui"; import { formatUnits } from "viem"; import AssetLogo from "../shared/AssetLogo"; +import ChainLogo from "../shared/ChainLogo"; import Input from "../shared/Input"; import ModalSheet from "../shared/ModalSheet"; import SafeView from "../shared/SafeView"; @@ -41,11 +42,15 @@ export default function AssetSelectSheet({ i18n: { language }, } = useTranslation(); const [searchQuery, setSearchQuery] = useState(""); + const [chainFilter, setChainFilter] = useState(); + const [filterOpen, setFilterOpen] = useState(false); const displayLabel = label ?? t("Select asset"); const filteredGroups = useMemo(() => { const normalizedQuery = searchQuery.trim().toLowerCase(); + const activeChain = groups.some((group) => group.chain.id === chainFilter) ? chainFilter : undefined; return groups + .filter((group) => activeChain === undefined || group.chain.id === activeChain) .map((group) => { if (!normalizedQuery) return group; const assets = group.assets.filter(({ token }) => { @@ -59,10 +64,12 @@ export default function AssetSelectSheet({ return { ...group, assets }; }) .filter((group) => group.assets.length > 0); - }, [groups, searchQuery]); + }, [groups, searchQuery, chainFilter]); const handleClose = useCallback(() => { setSearchQuery(""); + setChainFilter(undefined); + setFilterOpen(false); onClose(); }, [onClose]); @@ -73,35 +80,87 @@ export default function AssetSelectSheet({ {displayLabel} - - - - + + + + + + + {groups.length > 1 && ( + setFilterOpen(!filterOpen)}> + + {chainFilter === undefined ? ( + + ) : ( + + )} + + + + )} - + {filterOpen && ( + + + } + label={t("All networks")} + onPress={() => { + setChainFilter(undefined); + setFilterOpen(false); + }} + /> + {groups.map((group) => ( + } + label={group.chain.name} + onPress={() => { + setChainFilter(group.chain.id); + setFilterOpen(false); + }} + /> + ))} + + + )} + {filteredGroups.map((group) => ( @@ -171,7 +230,7 @@ export default function AssetSelectSheet({ {filteredGroups.length === 0 && ( - {searchQuery + {searchQuery || chainFilter !== undefined ? t("No assets match your filters.") : t("No assets with balance available to bridge.")} @@ -184,3 +243,32 @@ export default function AssetSelectSheet({ ); } + +function FilterRow({ + active, + icon, + label, + onPress, +}: { + active: boolean; + icon: React.ReactElement; + label: string; + onPress: () => void; +}) { + return ( + + + {icon} + + {label} + + {active && } + + + ); +} diff --git a/src/i18n/es.json b/src/i18n/es.json index 657c25950a..94c8e5946f 100644 --- a/src/i18n/es.json +++ b/src/i18n/es.json @@ -67,6 +67,7 @@ "Airalo": "Airalo", "All Activity": "Actividad", "All deposits must be from bank accounts under your name.": "Todos los depósitos deben ser desde cuentas bancarias a tu nombre.", + "All networks": "Todas las redes", "All supported assets count toward your spending limit.": "Todos los activos compatibles cuentan para tu límite de gasto.", "Almost there! Activate your Exa Card to start spending your onchain assets instantly.": "¡Ya casi! Activa tu Exa Card para empezar a gastar tus activos on-chain al instante.", "Almost there!": "¡Ya casi!", @@ -311,6 +312,7 @@ "Fees": "Comisiones", "Fees and transfer times": "Comisiones y tiempos de transferencia", "Fetching best route...": "Buscando la mejor ruta...", + "Filter by network": "Filtrar por red", "Find {{asset}} on {{network}} and select it.": "Busca {{asset}} en {{network}} y selecciónalo.", "Finished": "Finalizado", "First due date: {{date}} - then every 28 days.": "Primer vencimiento: {{date}} - luego cada 28 días.", diff --git a/src/i18n/pt.json b/src/i18n/pt.json index 3ba587cae1..ffad6b47c7 100644 --- a/src/i18n/pt.json +++ b/src/i18n/pt.json @@ -67,6 +67,7 @@ "Airalo": "Airalo", "All Activity": "Atividade", "All deposits must be from bank accounts under your name.": "Todos os depósitos devem ser de contas bancárias em seu nome.", + "All networks": "Todas as redes", "All supported assets count toward your spending limit.": "Todos os ativos compatíveis contam para seu limite de gastos.", "Almost there! Activate your Exa Card to start spending your onchain assets instantly.": "Quase lá! Ative seu Exa Card para começar a gastar seus ativos on-chain instantaneamente.", "Almost there!": "Quase lá!", @@ -311,6 +312,7 @@ "Fees": "Taxas", "Fees and transfer times": "Taxas e tempos de transferência", "Fetching best route...": "Buscando a melhor rota...", + "Filter by network": "Filtrar por rede", "Find {{asset}} on {{network}} and select it.": "Encontre {{asset}} em {{network}} e selecione-o.", "Finished": "Finalizado", "First due date: {{date}} - then every 28 days.": "Primeiro vencimento: {{date}} - depois a cada 28 dias.", From 8e1d771114ccc9882c828ec62db5a91c506f8361 Mon Sep 17 00:00:00 2001 From: guillermo dieguez Date: Thu, 23 Jul 2026 13:56:52 -0300 Subject: [PATCH 09/20] =?UTF-8?q?=F0=9F=92=84=20app:=20redesign=20bridge?= =?UTF-8?q?=20quote=20screen?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .changeset/round-moles-count.md | 5 + src/components/add-funds/Bridge.tsx | 349 +++++++++++++--------------- src/i18n/es.json | 17 +- src/i18n/pt.json | 17 +- src/utils/lifi.ts | 4 +- 5 files changed, 179 insertions(+), 213 deletions(-) create mode 100644 .changeset/round-moles-count.md diff --git a/.changeset/round-moles-count.md b/.changeset/round-moles-count.md new file mode 100644 index 0000000000..a4e059f959 --- /dev/null +++ b/.changeset/round-moles-count.md @@ -0,0 +1,5 @@ +--- +"@exactly/mobile": patch +--- + +💄 redesign bridge quote screen diff --git a/src/components/add-funds/Bridge.tsx b/src/components/add-funds/Bridge.tsx index 7fcdd2b044..1bfe2a2087 100644 --- a/src/components/add-funds/Bridge.tsx +++ b/src/components/add-funds/Bridge.tsx @@ -4,7 +4,7 @@ import { Pressable } from "react-native"; import { useLocalSearchParams, useRouter } from "expo-router"; -import { ArrowLeft, Check, CircleHelp, Clock, Repeat, X } from "@tamagui/lucide-icons"; +import { ArrowLeft, Check, CircleHelp, Clock, Repeat, Wallet, X } from "@tamagui/lucide-icons"; import { useToastController } from "@tamagui/toast"; import { ScrollView, Spinner, Square, XStack, YStack } from "tamagui"; @@ -22,7 +22,15 @@ import { zeroAddress, type Hex, } from "viem"; -import { useReadContract, useSendCalls, useSendTransaction, useSimulateContract, useWriteContract } from "wagmi"; +import { mainnet } from "viem/chains"; +import { + useEnsName, + useReadContract, + useSendCalls, + useSendTransaction, + useSimulateContract, + useWriteContract, +} from "wagmi"; import alchemyAPIKey from "@exactly/common/alchemyAPIKey"; import alchemyGasPolicyId from "@exactly/common/alchemyGasPolicyId"; @@ -35,6 +43,7 @@ import { callsStatus } from "../../utils/accountClient"; import alchemyChainById from "../../utils/alchemyChains"; import { balancesOptions, + bridgeSlippage, bridgeSourcesOptions, getRouteFrom, lifiTokensOptions, @@ -102,6 +111,12 @@ export default function Bridge() { const isExaSender = params.sender === "exa"; const senderConfig = isExaSender ? exaConfig : ownerConfig; const { address: senderAddress } = useAccount({ config: senderConfig }); + const { data: senderEnsName } = useEnsName({ + config: ownerConfig, + chainId: mainnet.id, + address: isExaSender ? undefined : senderAddress, + query: { staleTime: 86_400_000, retry: false, meta: { dropError: () => true } }, + }); const { mutateAsync: sendTx } = useSendTransaction({ config: senderConfig }); const { mutateAsync: sendCallsTx } = useSendCalls({ config: senderConfig }); const { mutateAsync: transfer } = useWriteContract({ config: senderConfig }); @@ -319,6 +334,10 @@ export default function Bridge() { }); const approvalRequired = canReadAllowance && (allowanceData ?? 0n) < sourceAmount; + const lifiFeeUSD = (quote?.estimate.feeCosts ?? []).reduce((sum, { amountUSD }) => sum + (Number(amountUSD) || 0), 0); + const transactionFeeUSD = (quote?.estimate.gasCosts ?? []) + .filter(({ type }) => type !== "APPROVE" || approvalRequired) + .reduce((sum, { amountUSD }) => sum + (Number(amountUSD) || 0), 0); const nativeGasReserve = useMemo(() => { if (!quote?.estimate.gasCosts || !nativeAddress) return 0n; @@ -759,9 +778,8 @@ export default function Bridge() { : undefined; if (processing) { - const isPending = isBridging || isTransferring; - const isSuccess = isBridgeSuccess || isTransferSuccess; - const isError = isBridgeError || isTransferError; + const status = + isBridgeError || isTransferError ? "error" : isBridgeSuccess || isTransferSuccess ? "success" : "pending"; const labels = { bridge: { error: t("Bridge failed"), @@ -784,83 +802,25 @@ export default function Bridge() { const price = Number(bridgePreview.sourceToken.priceUSD); const usdValue = Number.isNaN(amount) || Number.isNaN(price) ? 0 : amount * price; return ( - - - - { - if (!isPending) { - setSourceAmount(0n); - setBridgePreview(undefined); - resetBridgeMutation(); - resetTransferMutation(); - } - router.dismissTo("/activity"); - }} - /> - - - {isPending && } - {isSuccess && } - {isError && } - - - - {isError ? labels.error : isSuccess ? labels.success : labels.processing} - - - - - - {`${Number( - formatUnits(bridgePreview.sourceAmount, bridgePreview.sourceToken.decimals), - ).toLocaleString(language, { - maximumFractionDigits: Math.min(6, bridgePreview.sourceToken.decimals), - })} ${bridgePreview.sourceToken.symbol}`} - - - - {`$${usdValue.toLocaleString(language, { style: "decimal", minimumFractionDigits: 2, maximumFractionDigits: 2 })}`} - - - - - {!isPending && ( - - { - setSourceAmount(0n); - setBridgePreview(undefined); - resetBridgeMutation(); - resetTransferMutation(); - router.dismissTo("/activity"); - }} - > - - {t("Close")} - - - - )} - + { + if (status !== "pending") { + setSourceAmount(0n); + setBridgePreview(undefined); + resetBridgeMutation(); + resetTransferMutation(); + } + router.dismissTo("/activity"); + }} + /> ); } @@ -940,7 +900,11 @@ export default function Bridge() { {assetGroups.length > 0 && ( - {isTransfer ? t("Destination") : t("Destination asset")} + {t("Receive on")} {t("Exa Account")} | {shortenHex(account ?? zeroAddress, 4, 6)} @@ -1101,110 +1065,24 @@ export default function Bridge() { sourceAmount > 0n && !insufficientBalance && ( - - - {t("You send")} - - - {`${Number(formatUnits(sourceAmount, sourceToken.decimals)).toLocaleString(language, { - minimumFractionDigits: 0, - maximumFractionDigits: sourceToken.decimals, - useGrouping: false, - })} ${sourceToken.symbol}`} - - - - - {t("Source network")} - - - {selectedGroup?.chain.name ?? (source?.chain ? t("Chain {{id}}", { id: source.chain }) : "—")} - - - - - {t("Estimated arrival")} - - - {quote.estimate.toAmount - ? `≈${Number( - formatUnits(BigInt(quote.estimate.toAmount), destinationToken.decimals), - ).toLocaleString(language, { - minimumFractionDigits: 0, - maximumFractionDigits: destinationToken.decimals, - useGrouping: false, - })} ${destinationToken.symbol}` - : "—"} - - - - - {t("Destination network")} - - - {chain.name} - - {quote.estimate.toAmountMin && ( - - - {t("Minimum received")} - - - {`${Number( - formatUnits(BigInt(quote.estimate.toAmountMin), destinationToken.decimals), - ).toLocaleString(language, { - minimumFractionDigits: 0, - maximumFractionDigits: destinationToken.decimals, - useGrouping: false, - })} ${destinationToken.symbol}`} - - - )} - - - {t("Fees")} - - - 0.25% - - - - - {t("Slippage")} - - - 2% - - - {quote.estimate.executionDuration ? ( - - - {t("Estimated time")} - - - {t("~{{minutes}} min", { - minutes: Math.max(1, Math.round(quote.estimate.executionDuration / 60)), - })} - - - ) : null} - {(quote.tool ?? quote.estimate.tool) && ( - - - {t("Exchange")} - - - {quote.tool ?? quote.estimate.tool} - - + )} + + + + )} {statusMessage && ( @@ -1253,6 +1131,18 @@ export default function Bridge() { )} + {!isExaSender && (isTransfer || !!quote) && ( + + + + + + + {t("You must confirm the transactions on your external wallet.")} + + + + )} + + + {t("Select another asset")} + + + + + + + ); +} diff --git a/src/components/add-funds/Bridge.tsx b/src/components/add-funds/Bridge.tsx index 1bfe2a2087..1d3481b8e1 100644 --- a/src/components/add-funds/Bridge.tsx +++ b/src/components/add-funds/Bridge.tsx @@ -38,6 +38,7 @@ import chain from "@exactly/common/generated/chain"; import shortenHex from "@exactly/common/shortenHex"; import { WAD } from "@exactly/lib"; +import AssetMatchSheet from "./AssetMatchSheet"; import AssetSelectSheet from "./AssetSelectSheet"; import { callsStatus } from "../../utils/accountClient"; import alchemyChainById from "../../utils/alchemyChains"; @@ -89,6 +90,7 @@ export default function Bridge() { } = useTranslation(); const [assetSheetOpen, setAssetSheetOpen] = useState(false); + const [assetMatch, setAssetMatch] = useState<{ chainId: number; destinationSymbol: string; token: Token }>(); const [destinationModalOpen, setDestinationModalOpen] = useState(false); const { address: account } = useAccount(); @@ -1182,10 +1184,44 @@ export default function Bridge() { groups={assetGroups} selected={source} onSelect={(chainId, token) => { + const correlatedSymbol = + token.symbol in tokenCorrelation + ? tokenCorrelation[token.symbol as keyof typeof tokenCorrelation] + : undefined; + const correlatedToken = + correlatedSymbol && correlatedSymbol !== token.symbol && (isExaSender || chainId !== chain.id) + ? destinationTokens.find((destination) => destination.symbol === correlatedSymbol) + : undefined; + if (correlatedToken) { + setAssetMatch({ chainId, destinationSymbol: correlatedToken.symbol, token }); + return; + } setSourceAmount(0n); setSelectedSource({ chain: chainId, address: token.address.toLowerCase() }); }} /> + group.chain.id === assetMatch?.chainId)?.chain.name ?? ""} + destinationSymbol={assetMatch?.destinationSymbol ?? ""} + onClose={() => setAssetMatch(undefined)} + onConfirm={() => { + if (!assetMatch) return; + setSourceAmount(0n); + setSelectedSource({ chain: assetMatch.chainId, address: assetMatch.token.address.toLowerCase() }); + setSelectedDestinationAddress( + destinationTokens.find((token) => token.symbol === assetMatch.destinationSymbol)?.address, + ); + setAssetMatch(undefined); + }} + onSelectAnother={() => { + setAssetMatch(undefined); + setAssetSheetOpen(true); + }} + /> Date: Thu, 23 Jul 2026 14:25:12 -0300 Subject: [PATCH 11/20] =?UTF-8?q?=E2=9C=A8=20app:=20add=20request=20sent?= =?UTF-8?q?=20screen?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .changeset/plush-swans-send.md | 5 ++++ src/components/add-funds/Bridge.tsx | 38 +++++++++++++++-------------- src/i18n/es.json | 6 ++--- src/i18n/pt.json | 6 ++--- 4 files changed, 31 insertions(+), 24 deletions(-) create mode 100644 .changeset/plush-swans-send.md diff --git a/.changeset/plush-swans-send.md b/.changeset/plush-swans-send.md new file mode 100644 index 0000000000..8532de4d93 --- /dev/null +++ b/.changeset/plush-swans-send.md @@ -0,0 +1,5 @@ +--- +"@exactly/mobile": patch +--- + +✨ add request sent screen diff --git a/src/components/add-funds/Bridge.tsx b/src/components/add-funds/Bridge.tsx index 1d3481b8e1..951db8a0e0 100644 --- a/src/components/add-funds/Bridge.tsx +++ b/src/components/add-funds/Bridge.tsx @@ -4,7 +4,7 @@ import { Pressable } from "react-native"; import { useLocalSearchParams, useRouter } from "expo-router"; -import { ArrowLeft, Check, CircleHelp, Clock, Repeat, Wallet, X } from "@tamagui/lucide-icons"; +import { ArrowLeft, ArrowRight, Check, CircleHelp, Clock, Repeat, Wallet, X } from "@tamagui/lucide-icons"; import { useToastController } from "@tamagui/toast"; import { ScrollView, Spinner, Square, XStack, YStack } from "tamagui"; @@ -782,22 +782,10 @@ export default function Bridge() { if (processing) { const status = isBridgeError || isTransferError ? "error" : isBridgeSuccess || isTransferSuccess ? "success" : "pending"; - const labels = { - bridge: { - error: t("Bridge failed"), - success: t("Bridge transaction submitted"), - processing: t("Processing bridge"), - }, - swap: { - error: t("Swap failed"), - success: t("Swap transaction submitted"), - processing: t("Processing swap"), - }, - transfer: { - error: t("Transfer failed"), - success: t("Transfer transaction submitted"), - processing: t("Processing transfer"), - }, + const errorTitle = { + bridge: t("Bridge failed"), + swap: t("Swap failed"), + transfer: t("Transfer failed"), }[bridgePreview.operation]; const amount = Number(formatUnits(bridgePreview.sourceAmount, bridgePreview.sourceToken.decimals)); @@ -806,7 +794,13 @@ export default function Bridge() { return ( {!pending && ( + {status === "success" && ( + + )} {t("Close")} diff --git a/src/i18n/es.json b/src/i18n/es.json index ec7b90a842..f3c0062af9 100644 --- a/src/i18n/es.json +++ b/src/i18n/es.json @@ -52,6 +52,7 @@ "Activate your new Exa Card": "Activa tu nueva Exa Card", "Activating your new Exa Card": "Activando tu nueva Exa Card", "Activity": "Actividad", + "Add funds request sent": "Solicitud para agregar fondos enviada", "Add funds to account": "Agregar fondos a la cuenta", "Add funds to your account": "Agregar fondos a tu cuenta", "Add funds to your account to start spending with the Exa Card.": "Agrega fondos a tu cuenta para empezar a gastar con la Exa Card.", @@ -540,12 +541,10 @@ "Pounds": "Libras", "Press “Continue” to proceed or “Back” to cancel.": "Presiona “Continuar” para continuar o “Atrás” para cancelar.", "Processing swap request": "Procesando solicitud de intercambio", + "Processing add funds request": "Procesando solicitud para agregar fondos", "Processing balance {{amount}}": "Saldo en procesamiento {{amount}}", "Processing balance → {{amount}}": "Saldo en procesamiento → {{amount}}", - "Processing bridge": "Procesando bridge", "Processing rollover": "Procesando refinanciamiento", - "Processing swap": "Procesando intercambio", - "Processing transfer": "Procesando transferencia", "Processing transfer...": "Procesando transferencia...", "Processing...": "Procesando...", "Processing": "Procesando", @@ -785,6 +784,7 @@ "View pending request": "Ver solicitud pendiente", "View pending requests": "Ver solicitudes pendientes", "View PIN number": "Ver PIN", + "View requests": "Ver solicitudes", "View Statement": "Ver estado de cuenta", "View statement": "Ver resumen", "Visa Signature benefits": "Beneficios Visa Signature", diff --git a/src/i18n/pt.json b/src/i18n/pt.json index 821be5a3ac..875fb3e785 100644 --- a/src/i18n/pt.json +++ b/src/i18n/pt.json @@ -52,6 +52,7 @@ "Activate your new Exa Card": "Ative seu novo Exa Card", "Activating your new Exa Card": "Ativando seu novo Exa Card", "Activity": "Atividade", + "Add funds request sent": "Solicitação para adicionar fundos enviada", "Add funds to account": "Adicionar fundos à conta", "Add funds to your account": "Adicionar fundos à sua conta", "Add funds to your account to start spending with the Exa Card.": "Adicione fundos à sua conta para começar a gastar com o Exa Card.", @@ -540,12 +541,10 @@ "Pounds": "Libras", "Press “Continue” to proceed or “Back” to cancel.": "Pressione “Continuar” para prosseguir ou “Voltar” para cancelar.", "Processing swap request": "Processando solicitação de troca", + "Processing add funds request": "Processando solicitação para adicionar fundos", "Processing balance {{amount}}": "Saldo em processamento {{amount}}", "Processing balance → {{amount}}": "Saldo em processamento → {{amount}}", - "Processing bridge": "Processando bridge", "Processing rollover": "Processando refinanciamento", - "Processing swap": "Processando troca", - "Processing transfer": "Processando transferência", "Processing transfer...": "Processando transferência...", "Processing...": "Processando...", "Processing": "Processando", @@ -785,6 +784,7 @@ "View pending request": "Ver solicitação pendente", "View pending requests": "Ver solicitações pendentes", "View PIN number": "Ver PIN", + "View requests": "Ver solicitações", "View statement": "Ver extrato", "View Statement": "Ver extrato", "Visa Signature benefits": "Benefícios Visa Signature", From 050b007c6e82ad1dc12e51389f4e6725f0802b85 Mon Sep 17 00:00:00 2001 From: guillermo dieguez Date: Thu, 23 Jul 2026 18:46:33 -0300 Subject: [PATCH 12/20] =?UTF-8?q?=F0=9F=92=84=20app:=20toggle=20qr=20inlin?= =?UTF-8?q?e=20on=20receive=20screen?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .changeset/warm-swifts-flip.md | 5 ++ src/components/add-funds/AddCrypto.tsx | 119 ++++++++++++------------- src/i18n/es.json | 3 +- src/i18n/pt.json | 3 +- 4 files changed, 66 insertions(+), 64 deletions(-) create mode 100644 .changeset/warm-swifts-flip.md diff --git a/.changeset/warm-swifts-flip.md b/.changeset/warm-swifts-flip.md new file mode 100644 index 0000000000..5ed8e42447 --- /dev/null +++ b/.changeset/warm-swifts-flip.md @@ -0,0 +1,5 @@ +--- +"@exactly/mobile": patch +--- + +💄 toggle qr inline on receive screen diff --git a/src/components/add-funds/AddCrypto.tsx b/src/components/add-funds/AddCrypto.tsx index d5b08bd01e..36d61b31f3 100644 --- a/src/components/add-funds/AddCrypto.tsx +++ b/src/components/add-funds/AddCrypto.tsx @@ -7,7 +7,7 @@ import { setStringAsync } from "expo-clipboard"; import { selectionAsync } from "expo-haptics"; import { useLocalSearchParams, useRouter } from "expo-router"; -import { AlertTriangle, ArrowLeft, Copy, QrCode, RefreshCw, Share as ShareIcon } from "@tamagui/lucide-icons"; +import { AlertTriangle, ArrowLeft, Copy, Hash, QrCode, RefreshCw, Share as ShareIcon } from "@tamagui/lucide-icons"; import { useToastController } from "@tamagui/toast"; import { ScrollView, XStack, YStack } from "tamagui"; @@ -31,7 +31,6 @@ import CopyAddressSheet from "../shared/CopyAddressSheet"; import IconButton from "../shared/IconButton"; import Image from "../shared/Image"; import InfoAlert from "../shared/InfoAlert"; -import ModalSheet from "../shared/ModalSheet"; import SafeView from "../shared/SafeView"; import SendWarning from "../shared/SendWarning"; import Skeleton from "../shared/Skeleton"; @@ -89,6 +88,7 @@ export default function AddCrypto() { ? (receiveChain?.name ?? alchemyChainById.get(receiveChainId)?.name ?? `#${receiveChainId}`) : chain.name; const assets = isBridge ? [currency] : asset ? [asset] : supportedAssets; + const bridgeLogoURI = isBridge && network in networkLogos ? networkLogos[network] : undefined; const toast = useToastController(); const [copyAddressShown, setCopyAddressShown] = useState(false); @@ -147,11 +147,7 @@ export default function AddCrypto() { isPending={!isBridge && !asset && isPending} onPress={isBridge || asset ? undefined : () => setSupportedAssetsShown(true)} /> - + {t("Wallet address")} - - {address ? ( - - {address} - - ) : isBridge && isError && !isFetching ? ( - - {t("Failed to load deposit address.")} - - ) : ( - - )} - + {qrShown && address ? ( + + + + + {bridgeLogoURI ? ( + + ) : ( + + )} + + + + ) : ( + + {address ? ( + + {address} + + ) : isBridge && isError && !isFetching ? ( + + {t("Failed to load deposit address.")} + + ) : ( + + )} + + )} {!!address && !memo && ( - setQRShown(true)}> + setQRShown(!qrShown)}> - {t("Show QR")} + {qrShown ? t("Show wallet address") : t("Show QR")} - + {qrShown ? ( + + ) : ( + + )} )} @@ -221,43 +257,6 @@ export default function AddCrypto() { )} - {!!address && !memo && ( - { - setQRShown(false); - }} - > - - - - {isBridge - ? t("{{network}} deposit address", { network: networkName }) - : t("Your {{chain}} address", { chain: networkName })} - - - - - { - setQRShown(false); - }} - > - - {t("Close")} - - - - - - )} { diff --git a/src/i18n/es.json b/src/i18n/es.json index f3c0062af9..e558f95396 100644 --- a/src/i18n/es.json +++ b/src/i18n/es.json @@ -13,7 +13,6 @@ "{{currency}} via {{method}}": "{{currency}} vía {{method}}", "{{currency}} via {{methods}}": "{{currency}} vía {{methods}}", "{{discount}} off": "{{discount}} off", - "{{network}} deposit address": "Dirección de depósito de {{network}}", "{{percent}} OFF": "{{percent}} OFF", "{{rate}} APR": "{{rate}} TNA", "{{source}} on {{network}} matches {{destination}} on {{chain}}. You can swap and bridge between these assets or just select another supported asset.": "{{source}} en {{network}} coincide con {{destination}} en {{chain}}. Puedes hacer swap y bridge entre estos activos o simplemente seleccionar otro activo soportado.", @@ -639,6 +638,7 @@ "Show QR": "Mostrar QR", "Show QR Code": "Mostrar código QR", "Show sensitive": "Mostrar sensibles", + "Show wallet address": "Mostrar dirección de billetera", "Sign in": "Iniciar sesión", "Sign up with browser wallet": "Regístrate con tu billetera del navegador", "Sign up with Passkey": "Regístrate con llave de acceso", @@ -831,7 +831,6 @@ "You’re all set!": "¡Todo listo!", "You’re trying to borrow more than your collateral allows. Please enter a lower amount.": "Estás intentando pedir prestado más de lo que tu garantía permite. Por favor, introduce un monto menor.", "You've reached 90% of your weekly card spending limit.": "Has alcanzado el 90% de tu límite de gasto semanal.", - "Your {{chain}} address": "Tu dirección en {{chain}}", "Your address needs to be verified": "Tu dirección necesita ser verificada", "Your assets can't back your card yet. Swap them to a supported asset to start spending.": "Tus activos aún no pueden respaldar tu tarjeta. Intercámbialos por un activo compatible para empezar a gastar.", "Your card is awaiting activation. Follow the steps to enable it.": "Tu tarjeta está a la espera de activación. Sigue los pasos para habilitarla.", diff --git a/src/i18n/pt.json b/src/i18n/pt.json index 875fb3e785..6324447fe1 100644 --- a/src/i18n/pt.json +++ b/src/i18n/pt.json @@ -13,7 +13,6 @@ "{{currency}} via {{method}}": "{{currency}} via {{method}}", "{{currency}} via {{methods}}": "{{currency}} via {{methods}}", "{{discount}} off": "{{discount}} off", - "{{network}} deposit address": "Endereço de depósito {{network}}", "{{percent}} OFF": "{{percent}} OFF", "{{rate}} APR": "{{rate}} APR", "{{source}} on {{network}} matches {{destination}} on {{chain}}. You can swap and bridge between these assets or just select another supported asset.": "{{source}} em {{network}} corresponde a {{destination}} em {{chain}}. Você pode fazer swap e bridge entre esses ativos ou simplesmente selecionar outro ativo suportado.", @@ -639,6 +638,7 @@ "Show QR": "Mostrar QR", "Show QR Code": "Mostrar código QR", "Show sensitive": "Mostrar sensíveis", + "Show wallet address": "Mostrar endereço da carteira", "Sign in": "Entrar", "Sign up with browser wallet": "Cadastre-se com a carteira do navegador", "Sign up with Passkey": "Cadastre-se com chave de acesso", @@ -831,7 +831,6 @@ "You’re all set!": "Tudo pronto!", "You’re trying to borrow more than your collateral allows. Please enter a lower amount.": "Você está tentando emprestar mais do que sua garantia permite. Por favor, insira um valor menor.", "You've reached 90% of your weekly card spending limit.": "Você atingiu 90% do seu limite de gastos semanal.", - "Your {{chain}} address": "Seu endereço na {{chain}}", "Your address needs to be verified": "Seu endereço precisa ser verificado", "Your assets can't back your card yet. Swap them to a supported asset to start spending.": "Seus ativos ainda não podem servir de garantia para o seu cartão. Troque-os por um ativo compatível para começar a gastar.", "Your card is awaiting activation. Follow the steps to enable it.": "Seu cartão está aguardando ativação. Siga os passos para ativá-lo.", From dfd77759be396e5333da03a035498752879cd501 Mon Sep 17 00:00:00 2001 From: guillermo dieguez Date: Thu, 23 Jul 2026 18:47:00 -0300 Subject: [PATCH 13/20] =?UTF-8?q?=F0=9F=92=84=20app:=20redesign=20copy=20a?= =?UTF-8?q?ddress=20sheet?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .changeset/neat-cranes-copy.md | 5 ++ src/components/add-funds/AddCrypto.tsx | 3 +- src/components/shared/CopyAddressSheet.tsx | 98 ++++++++-------------- src/i18n/es.json | 1 - src/i18n/pt.json | 1 - 5 files changed, 43 insertions(+), 65 deletions(-) create mode 100644 .changeset/neat-cranes-copy.md diff --git a/.changeset/neat-cranes-copy.md b/.changeset/neat-cranes-copy.md new file mode 100644 index 0000000000..d29a5967eb --- /dev/null +++ b/.changeset/neat-cranes-copy.md @@ -0,0 +1,5 @@ +--- +"@exactly/mobile": patch +--- + +💄 redesign copy address sheet diff --git a/src/components/add-funds/AddCrypto.tsx b/src/components/add-funds/AddCrypto.tsx index 36d61b31f3..53421229ea 100644 --- a/src/components/add-funds/AddCrypto.tsx +++ b/src/components/add-funds/AddCrypto.tsx @@ -264,8 +264,7 @@ export default function AddCrypto() { }} address={isBridge ? depositAddress : undefined} network={isBridge ? network : receiveChainId ? networkName : undefined} - networkLogo={isBridge ? networkLogos[network] : receiveChain?.logoURI} - assets={isBridge || asset ? assets : undefined} + asset={isBridge ? currency : asset || undefined} /> {!isBridge && !asset && ( void; open: boolean; }) { const { address: accountAddress } = useAccount(); - const { supportedAssets, isPending } = useMarkets(); - const displayAssets = assets ?? supportedAssets; const { t } = useTranslation(); return ( - + - + {t("Address copied")} - + {t("Double-check your address before sending funds to avoid losing them.")} - + {overrideAddress ?? accountAddress} - - - - {t("Network")} - - - {displayAssets.length === 1 ? t("Asset") : t("Supported Assets")} - - - - - {networkLogo ? ( - - ) : ( - - )} - - {network ?? chain.name} + + + + + + + { + presentArticle("8950801").catch(reportError); + }} + > + {" "} + {t("Learn more about adding funds.")} - - - {!assets && isPending - ? Array.from({ length: 5 }, (_, index) => ( - - - - )) - : displayAssets.map((symbol, index) => ( - - - - ))} - + - -