From 3fabe33d5fc71bb76b52f82581162ccaa736333b Mon Sep 17 00:00:00 2001 From: Aaron Quirk Date: Tue, 12 Nov 2024 11:49:11 -0500 Subject: [PATCH] Integrated swap UX using Swing.xyz (#229) * Retrieve quotes for swap * fix lint problems * refactor to expected swing quote API * lint fixes * implement basic swap Tx signing * refactor for lint * token amount selector * show transaction ID when available * refine the completion UX * add i18n definitions * add feature flag for swap * add production swap pair definitions * add progress description at bottom of swap modal * fix fund wallet CTA * bubble up fund wallet CTA * use QR icon for receive button * text spacing * mobile breakpoint * introduce compact token rendering * use swing v1 platform API * add slider for source token amount * only show USD for swaps * add user context to feature flag * imporove failed swap UX * update copy * fix lint * updates for extension styling * bump package version --- packages/config/package.json | 2 +- packages/config/src/env/default.ts | 128 ++ packages/config/src/env/production.ts | 125 ++ packages/config/src/env/types.ts | 23 + .../src/launchdarkly/defaultFlagValues.ts | 1 + packages/config/src/launchdarkly/keys.ts | 3 +- packages/ui-components/package.json | 3 +- .../src/actions/swingActionsV1.ts | 69 ++ .../src/actions/swingActionsV2.ts | 117 ++ .../src/components/Manage/Tabs/Crypto.tsx | 4 +- .../src/components/Wallet/AddressInput.tsx | 3 +- .../src/components/Wallet/AmountInput.tsx | 4 +- .../src/components/Wallet/Buy.tsx | 3 +- .../src/components/Wallet/Client.tsx | 66 +- .../src/components/Wallet/FundWalletModal.tsx | 4 +- .../src/components/Wallet/Receive.tsx | 3 +- .../src/components/Wallet/SelectAsset.tsx | 64 +- .../src/components/Wallet/Send.tsx | 3 +- .../src/components/Wallet/SendConfirm.tsx | 4 +- .../components/Wallet/SubmitTransaction.tsx | 4 +- .../src/components/Wallet/Swap.tsx | 1070 +++++++++++++++++ .../src/components/Wallet/Token.tsx | 61 +- .../src/components/Wallet/TokensPortfolio.tsx | 3 +- .../src/hooks/useSubmitTransaction.ts | 4 +- .../ui-components/src/lib/types/swingXyzV1.ts | 153 +++ .../ui-components/src/lib/types/swingXyzV2.ts | 172 +++ .../ui-components/src/lib/types/wallet.ts | 20 + .../ui-components/src/lib/wallet/asset.ts | 2 +- .../ui-components/src/lib/wallet/evm/token.ts | 68 +- .../ui-components/src/lib/wallet/tokens.ts | 5 +- packages/ui-components/src/locales/en.json | 25 +- .../ui-components/src/tests/mocks/wallet.ts | 2 +- yarn.lock | 27 + 33 files changed, 2110 insertions(+), 135 deletions(-) create mode 100644 packages/ui-components/src/actions/swingActionsV1.ts create mode 100644 packages/ui-components/src/actions/swingActionsV2.ts create mode 100644 packages/ui-components/src/components/Wallet/Swap.tsx create mode 100644 packages/ui-components/src/lib/types/swingXyzV1.ts create mode 100644 packages/ui-components/src/lib/types/swingXyzV2.ts diff --git a/packages/config/package.json b/packages/config/package.json index b7c248f3..55444d18 100644 --- a/packages/config/package.json +++ b/packages/config/package.json @@ -1,6 +1,6 @@ { "name": "@unstoppabledomains/config", - "version": "0.0.17", + "version": "0.0.18", "private": true, "description": "Configuration variables for Unstoppable Domains environments", "homepage": "https://github.com/unstoppabledomains/domain-profiles#readme", diff --git a/packages/config/src/env/default.ts b/packages/config/src/env/default.ts index a06378ae..1e74f301 100644 --- a/packages/config/src/env/default.ts +++ b/packages/config/src/env/default.ts @@ -122,6 +122,134 @@ export default function getDefaultConfig(): Config { SEND: ['BTC/BTC', 'MATIC/MATIC', 'SOL/SOL', 'BASE/ETH', 'ETH/ETH'], DOMAINS: ['ETH', 'MATIC', 'BASE'], }, + SWAP: { + EXCHANGE_HOST_URL: 'https://swap.prod.swing.xyz/v0', + PLATFORM_HOST_URL: + 'https://platform.swing.xyz/api/v1/projects/unstoppable-domains-staging', + API_KEY: 'swing-8faf57ed-6f1d-4dbc-94a0-00c28bd1a277', + ENVIRONMENT: 'testnet', + PROJECT_ID: 'unstoppable-domains-staging', + FEE_BPS: 85, // 0.85% + MIN_BALANCE_USD: 5, + SUPPORTED_TOKENS: { + SOURCE: [ + { + swing: { + chain: 'sepolia', + symbol: 'ETH', + type: 'native', + }, + walletType: 'ETH', + chainName: 'Ethereum', + chainSymbol: 'ETH', + tokenSymbol: 'ETH', + imageUrl: + 'https://images.unstoppabledomains.com/images/icons/ETH/icon.svg', + environment: 'staging', + }, + { + swing: { + chain: 'polygon-amoy', + symbol: 'POL', + type: 'native', + }, + walletType: 'MATIC', + chainName: 'Polygon', + chainSymbol: 'MATIC', + tokenSymbol: 'MATIC', + imageUrl: + 'https://images.unstoppabledomains.com/images/icons/MATIC/icon.svg', + environment: 'staging', + }, + { + swing: { + chain: 'polygon-amoy', + symbol: 'USDC', + type: 'erc20', + }, + walletType: 'MATIC', + chainName: 'Polygon', + chainSymbol: 'MATIC', + tokenSymbol: 'USDC', + imageUrl: + 'https://images.unstoppabledomains.com/images/icons/USDC/icon.svg', + environment: 'staging', + }, + ], + DESTINATION: [ + { + swing: { + chain: 'sepolia', + symbol: 'ETH', + type: 'native', + }, + walletType: 'ETH', + chainName: 'Ethereum', + chainSymbol: 'ETH', + tokenSymbol: 'ETH', + imageUrl: + 'https://images.unstoppabledomains.com/images/icons/ETH/icon.svg', + environment: 'staging', + }, + { + swing: { + chain: 'arbitrum-sepolia', + symbol: 'ETH', + type: 'native', + }, + walletType: 'ETH', + chainName: 'Arbitrum', + chainSymbol: 'ARB', + tokenSymbol: 'ETH', + imageUrl: + 'https://images.unstoppabledomains.com/images/icons/ARB/icon.svg', + environment: 'staging', + }, + { + swing: { + chain: 'solana-dev', + symbol: 'SOL', + type: 'native', + }, + walletType: 'SOL', + chainName: 'Solana', + chainSymbol: 'SOL', + tokenSymbol: 'SOL', + imageUrl: + 'https://images.unstoppabledomains.com/images/icons/SOL/icon.svg', + environment: 'staging', + }, + { + swing: { + chain: 'bitcoin-testnet', + symbol: 'BTC', + type: 'native', + }, + walletType: 'BTC', + chainName: 'Bitcoin', + chainSymbol: 'BTC', + tokenSymbol: 'BTC', + imageUrl: + 'https://images.unstoppabledomains.com/images/icons/BTC/icon.svg', + environment: 'staging', + }, + { + swing: { + chain: 'base-sepolia', + symbol: 'ETH', + type: 'native', + }, + walletType: 'BASE', + chainName: 'Base', + chainSymbol: 'BASE', + tokenSymbol: 'ETH', + imageUrl: + 'https://images.unstoppabledomains.com/images/icons/BASE/icon.svg', + environment: 'staging', + }, + ], + }, + }, MOBILE: { ANDROID_URL: 'https://play.google.com/store/apps/details?id=com.unstoppabledomains.manager', diff --git a/packages/config/src/env/production.ts b/packages/config/src/env/production.ts index 49a64f10..8b943e9b 100644 --- a/packages/config/src/env/production.ts +++ b/packages/config/src/env/production.ts @@ -60,6 +60,131 @@ export default function getProductionConfig(): ConfigOverride { HOST_URL: 'https://api.unstoppabledomains.com/wallet/v1', GET_WALLET_URL: 'https://unstoppabledomains.com/cart?product=unstoppable-wallet', + SWAP: { + PLATFORM_HOST_URL: + 'https://platform.swing.xyz/api/v1/projects/unstoppable-domains', + API_KEY: 'swing-37b05c4c-0b6d-43a8-ad54-7bd7168af0ee', + ENVIRONMENT: 'production', + PROJECT_ID: 'unstoppable-domains', + SUPPORTED_TOKENS: { + SOURCE: [ + { + swing: { + chain: 'ethereum', + symbol: 'ETH', + type: 'native', + }, + walletType: 'ETH', + chainName: 'Ethereum', + chainSymbol: 'ETH', + tokenSymbol: 'ETH', + imageUrl: + 'https://images.unstoppabledomains.com/images/icons/ETH/icon.svg', + environment: 'production', + }, + { + swing: { + chain: 'polygon', + symbol: 'POL', + type: 'native', + }, + walletType: 'MATIC', + chainName: 'Polygon', + chainSymbol: 'MATIC', + tokenSymbol: 'MATIC', + imageUrl: + 'https://images.unstoppabledomains.com/images/icons/MATIC/icon.svg', + environment: 'production', + }, + { + swing: { + chain: 'base', + symbol: 'ETH', + type: 'native', + }, + walletType: 'BASE', + chainName: 'Base', + chainSymbol: 'BASE', + tokenSymbol: 'ETH', + imageUrl: + 'https://images.unstoppabledomains.com/images/icons/BASE/icon.svg', + environment: 'production', + }, + ], + DESTINATION: [ + { + swing: { + chain: 'ethereum', + symbol: 'ETH', + type: 'native', + }, + walletType: 'ETH', + chainName: 'Ethereum', + chainSymbol: 'ETH', + tokenSymbol: 'ETH', + imageUrl: + 'https://images.unstoppabledomains.com/images/icons/ETH/icon.svg', + environment: 'production', + }, + { + swing: { + chain: 'solana', + symbol: 'SOL', + type: 'native', + }, + walletType: 'SOL', + chainName: 'Solana', + chainSymbol: 'SOL', + tokenSymbol: 'SOL', + imageUrl: + 'https://images.unstoppabledomains.com/images/icons/SOL/icon.svg', + environment: 'production', + }, + { + swing: { + chain: 'bitcoin', + symbol: 'BTC', + type: 'native', + }, + walletType: 'BTC', + chainName: 'Bitcoin', + chainSymbol: 'BTC', + tokenSymbol: 'BTC', + imageUrl: + 'https://images.unstoppabledomains.com/images/icons/BTC/icon.svg', + environment: 'production', + }, + { + swing: { + chain: 'polygon', + symbol: 'POL', + type: 'native', + }, + walletType: 'MATIC', + chainName: 'Polygon', + chainSymbol: 'MATIC', + tokenSymbol: 'MATIC', + imageUrl: + 'https://images.unstoppabledomains.com/images/icons/MATIC/icon.svg', + environment: 'production', + }, + { + swing: { + chain: 'base', + symbol: 'ETH', + type: 'native', + }, + walletType: 'BASE', + chainName: 'Base', + chainSymbol: 'BASE', + tokenSymbol: 'ETH', + imageUrl: + 'https://images.unstoppabledomains.com/images/icons/BASE/icon.svg', + environment: 'production', + }, + ], + }, + }, }, XMTP: { ENVIRONMENT: 'production', diff --git a/packages/config/src/env/types.ts b/packages/config/src/env/types.ts index 3f56eee7..8fef3c8c 100644 --- a/packages/config/src/env/types.ts +++ b/packages/config/src/env/types.ts @@ -59,6 +59,16 @@ export type BaseChainBlockchainConfig = BaseBlockchainConfig & { BLOCK_EXPLORER_TX_URL: 'https://www.oklink.com/base/tx/'; }; +export interface SwapConfig { + swing: {chain: string; symbol: string; type: 'erc20' | 'native'}; + walletType: string; + chainName: string; + chainSymbol: string; + tokenSymbol: string; + imageUrl: string; + environment: string; +} + export type Config = { APP_ENV: AppEnv; APP_VERSION: string; @@ -110,6 +120,19 @@ export type Config = { SEND: string[]; DOMAINS: string[]; }; + SWAP: { + PLATFORM_HOST_URL: string; + EXCHANGE_HOST_URL: string; + API_KEY: string; + ENVIRONMENT: string; + PROJECT_ID: string; + FEE_BPS?: number; + MIN_BALANCE_USD: number; + SUPPORTED_TOKENS: { + SOURCE: SwapConfig[]; + DESTINATION: SwapConfig[]; + }; + }; SIGNATURE_SYMBOL: string; MOBILE: { ANDROID_URL: string; diff --git a/packages/config/src/launchdarkly/defaultFlagValues.ts b/packages/config/src/launchdarkly/defaultFlagValues.ts index 217e7442..0e9cb776 100644 --- a/packages/config/src/launchdarkly/defaultFlagValues.ts +++ b/packages/config/src/launchdarkly/defaultFlagValues.ts @@ -12,6 +12,7 @@ export const defaultFlagValues: LaunchDarklyFlagSet = { 'ud-me-service-domains-enable-social-verification': false, 'ud-me-service-domains-enable-management': false, 'ud-me-service-domains-enable-fireblocks': false, + 'ud-me-service-enable-swap': false, 'ecommerce-service-users-enable-chat-community-udBlue': false, 'ecommerce-service-wallets-disable-badges': [], 'example-number': 0, diff --git a/packages/config/src/launchdarkly/keys.ts b/packages/config/src/launchdarkly/keys.ts index 10071768..46db5807 100644 --- a/packages/config/src/launchdarkly/keys.ts +++ b/packages/config/src/launchdarkly/keys.ts @@ -10,7 +10,8 @@ export type LaunchDarklyBooleanKey = | 'profile-service-enable-wallet-send-to-email' | 'ud-me-service-domains-enable-social-verification' | 'ud-me-service-domains-enable-fireblocks' - | 'ud-me-service-domains-enable-management'; + | 'ud-me-service-domains-enable-management' + | 'ud-me-service-enable-swap'; export type LaunchDarklyNumberKey = 'example-number'; diff --git a/packages/ui-components/package.json b/packages/ui-components/package.json index 6b40b993..3b34f503 100644 --- a/packages/ui-components/package.json +++ b/packages/ui-components/package.json @@ -1,6 +1,6 @@ { "name": "@unstoppabledomains/ui-components", - "version": "0.0.52", + "version": "0.0.52-browser-extension.1", "private": true, "description": "An open and reusable suite of Unstoppable Domains management components", "keywords": [ @@ -91,6 +91,7 @@ "qs": "^6.11.2", "ramda": "^0.27.1", "react": "^17.0.0", + "react-canvas-confetti": "^2.0.7", "react-chartjs-2": "^5.2.0", "react-cookies": "^0.1.1", "react-dom": "^17.0.0", diff --git a/packages/ui-components/src/actions/swingActionsV1.ts b/packages/ui-components/src/actions/swingActionsV1.ts new file mode 100644 index 00000000..e68e1409 --- /dev/null +++ b/packages/ui-components/src/actions/swingActionsV1.ts @@ -0,0 +1,69 @@ +import qs from 'qs'; + +import config from '@unstoppabledomains/config'; + +import {notifyEvent} from '../lib'; +import {fetchApi} from '../lib/fetchApi'; +import type { + RouteQuote, + SwingQuoteRequest, + SwingQuoteResponse, + SwingTransactionResponse, +} from '../lib/types/swingXyzV1'; + +export const getSwapQuote = async (opts: SwingQuoteRequest) => { + try { + // inject project ID and fee from config + opts.projectId = config.WALLETS.SWAP.PROJECT_ID; + opts.fee = config.WALLETS.SWAP.FEE_BPS; + + // request the quote + return await fetchApi( + `/transfer/quote?${qs.stringify(opts)}`, + { + mode: 'cors', + host: config.WALLETS.SWAP.EXCHANGE_HOST_URL, + headers: { + Accept: 'application/json', + }, + }, + ); + } catch (e) { + notifyEvent(e, 'warning', 'Wallet', 'Transaction', { + msg: 'error fetching swap quote', + }); + } + return undefined; +}; + +export const getSwapTransaction = async ( + opts: SwingQuoteRequest, + quote: RouteQuote, +) => { + try { + // inject project ID and fee from config + opts.projectId = config.WALLETS.SWAP.PROJECT_ID; + opts.fee = config.WALLETS.SWAP.FEE_BPS; + + // request the quote + return await fetchApi(`/transfer/send`, { + method: 'POST', + mode: 'cors', + acceptStatusCodes: [200, 201, 400], + host: config.WALLETS.SWAP.EXCHANGE_HOST_URL, + headers: { + Accept: 'application/json', + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + ...opts, + route: quote.route, + }), + }); + } catch (e) { + notifyEvent(e, 'warning', 'Wallet', 'Transaction', { + msg: 'error fetching swap transaction', + }); + } + return undefined; +}; diff --git a/packages/ui-components/src/actions/swingActionsV2.ts b/packages/ui-components/src/actions/swingActionsV2.ts new file mode 100644 index 00000000..61675377 --- /dev/null +++ b/packages/ui-components/src/actions/swingActionsV2.ts @@ -0,0 +1,117 @@ +import qs from 'qs'; + +import config from '@unstoppabledomains/config'; + +import {notifyEvent} from '../lib'; +import {fetchApi} from '../lib/fetchApi'; +import type { + SwingV2QuoteRequest, + SwingV2QuoteResponse, + SwingV2SendRequest, + SwingV2SendResponse, + SwingV2SwapStatus, + SwingV2Token, +} from '../lib/types/swingXyzV2'; + +export const getSwapQuoteV2 = async (opts: SwingV2QuoteRequest) => { + try { + // inject project ID and fee from config + opts.projectId = config.WALLETS.SWAP.PROJECT_ID; + opts.fee = config.WALLETS.SWAP.FEE_BPS; + + // request the quote + return await fetchApi( + `/quote?${qs.stringify(opts)}`, + { + mode: 'cors', + host: config.WALLETS.SWAP.PLATFORM_HOST_URL, + headers: { + Accept: 'application/json', + 'x-swing-environment': config.WALLETS.SWAP.ENVIRONMENT, + }, + }, + ); + } catch (e) { + notifyEvent(e, 'warning', 'Wallet', 'Transaction', { + msg: 'error fetching swap quote', + meta: opts, + }); + } + return undefined; +}; + +export const getSwapStatusV2 = async (id: number, txHash: string) => { + try { + // request the status + return await fetchApi( + `/transactions/${id}?txHash=${txHash}`, + { + mode: 'cors', + host: config.WALLETS.SWAP.PLATFORM_HOST_URL, + headers: { + Accept: 'application/json', + 'Content-Type': 'application/json', + 'x-swing-environment': config.WALLETS.SWAP.ENVIRONMENT, + }, + }, + ); + } catch (e) { + notifyEvent(e, 'warning', 'Wallet', 'Transaction', { + msg: 'error fetching swap status', + meta: {id, txHash}, + }); + } + return undefined; +}; + +export const getSwapTokenV2 = async (chain: string, token: string) => { + try { + // request the tokens + const tokens = await fetchApi(`/tokens?chain=${chain}`, { + mode: 'cors', + host: config.WALLETS.SWAP.PLATFORM_HOST_URL, + headers: { + Accept: 'application/json', + 'x-swing-environment': config.WALLETS.SWAP.ENVIRONMENT, + }, + }); + // find the specified token + return tokens.find( + t => t.chain === chain && (t.symbol === token || t.address === token), + ); + } catch (e) { + notifyEvent(e, 'warning', 'Wallet', 'Transaction', { + msg: 'error fetching tokens', + meta: {chain, token}, + }); + } + return undefined; +}; + +export const getSwapTransactionV2 = async (opts: SwingV2SendRequest) => { + try { + // inject project ID and fee from config + opts.projectId = config.WALLETS.SWAP.PROJECT_ID; + opts.fee = config.WALLETS.SWAP.FEE_BPS; + + // request the transaction + return await fetchApi(`/send`, { + method: 'POST', + mode: 'cors', + acceptStatusCodes: [200, 201, 400], + host: config.WALLETS.SWAP.PLATFORM_HOST_URL, + headers: { + Accept: 'application/json', + 'Content-Type': 'application/json', + 'x-swing-environment': config.WALLETS.SWAP.ENVIRONMENT, + }, + body: JSON.stringify(opts), + }); + } catch (e) { + notifyEvent(e, 'warning', 'Wallet', 'Transaction', { + msg: 'error fetching swap transaction', + meta: opts, + }); + } + return undefined; +}; diff --git a/packages/ui-components/src/components/Manage/Tabs/Crypto.tsx b/packages/ui-components/src/components/Manage/Tabs/Crypto.tsx index 8c054312..8a5620a4 100644 --- a/packages/ui-components/src/components/Manage/Tabs/Crypto.tsx +++ b/packages/ui-components/src/components/Manage/Tabs/Crypto.tsx @@ -1,5 +1,5 @@ -import AddIcon from '@mui/icons-material/Add'; import MonetizationOnOutlinedIcon from '@mui/icons-material/MonetizationOnOutlined'; +import QrCodeIcon from '@mui/icons-material/QrCode'; import UpdateOutlinedIcon from '@mui/icons-material/UpdateOutlined'; import LoadingButton from '@mui/lab/LoadingButton'; import Box from '@mui/material/Box'; @@ -133,7 +133,7 @@ export const Crypto: React.FC = ({ onClick={handleOpenModal} disabled={isPendingTx} className={classes.button} - startIcon={} + startIcon={} loading={isModalOpened} fullWidth > diff --git a/packages/ui-components/src/components/Wallet/AddressInput.tsx b/packages/ui-components/src/components/Wallet/AddressInput.tsx index 881bf586..54b8f7c6 100644 --- a/packages/ui-components/src/components/Wallet/AddressInput.tsx +++ b/packages/ui-components/src/components/Wallet/AddressInput.tsx @@ -10,6 +10,8 @@ import {makeStyles} from '@unstoppabledomains/ui-kit/styles'; import {getProfileData} from '../../actions'; import useResolverKeys from '../../hooks/useResolverKeys'; +import type { + TokenEntry} from '../../lib'; import { DomainFieldTypes, isDomainValidForManagement, @@ -22,7 +24,6 @@ import {getAddressMetadata} from '../Chat/protocol/resolution'; import ManageInput from '../Manage/common/ManageInput'; import {isValidMappedResolverKeyValue} from '../Manage/common/currencyRecords'; import {getRecordKeys} from '../Manage/common/verification/types'; -import type {TokenEntry} from './Token'; const useStyles = makeStyles()((theme: Theme) => ({ loader: { diff --git a/packages/ui-components/src/components/Wallet/AmountInput.tsx b/packages/ui-components/src/components/Wallet/AmountInput.tsx index c0d5ba18..a33caa93 100644 --- a/packages/ui-components/src/components/Wallet/AmountInput.tsx +++ b/packages/ui-components/src/components/Wallet/AmountInput.tsx @@ -7,10 +7,10 @@ import React, {useState} from 'react'; import {makeStyles} from '@unstoppabledomains/ui-kit/styles'; -import {useTranslationContext} from '../../lib'; +import type {TokenEntry} from '../../lib'; +import { useTranslationContext} from '../../lib'; import ManageInput from '../Manage/common/ManageInput'; import {getBlockchainDisplaySymbol} from '../Manage/common/verification/types'; -import type {TokenEntry} from './Token'; const useStyles = makeStyles()((theme: Theme) => ({ container: { diff --git a/packages/ui-components/src/components/Wallet/Buy.tsx b/packages/ui-components/src/components/Wallet/Buy.tsx index db2c8c13..13ac227a 100644 --- a/packages/ui-components/src/components/Wallet/Buy.tsx +++ b/packages/ui-components/src/components/Wallet/Buy.tsx @@ -6,10 +6,9 @@ import React from 'react'; import config from '@unstoppabledomains/config'; import {makeStyles} from '@unstoppabledomains/ui-kit/styles'; -import type {SerializedWalletBalance} from '../../lib'; +import type {SerializedWalletBalance, TokenEntry} from '../../lib'; import {useTranslationContext} from '../../lib'; import {SelectAsset} from './SelectAsset'; -import type {TokenEntry} from './Token'; const useStyles = makeStyles()((theme: Theme) => ({ flexColCenterAligned: { diff --git a/packages/ui-components/src/components/Wallet/Client.tsx b/packages/ui-components/src/components/Wallet/Client.tsx index f3d33383..93b7b57d 100644 --- a/packages/ui-components/src/components/Wallet/Client.tsx +++ b/packages/ui-components/src/components/Wallet/Client.tsx @@ -1,9 +1,10 @@ -import AddOutlinedIcon from '@mui/icons-material/AddOutlined'; import AttachMoneyIcon from '@mui/icons-material/AttachMoney'; import HistoryIcon from '@mui/icons-material/History'; import ListOutlinedIcon from '@mui/icons-material/ListOutlined'; import PaidOutlinedIcon from '@mui/icons-material/PaidOutlined'; +import QrCodeIcon from '@mui/icons-material/QrCode'; import SendIcon from '@mui/icons-material/Send'; +import SwapHorizIcon from '@mui/icons-material/SwapHoriz'; import TabContext from '@mui/lab/TabContext'; import TabList from '@mui/lab/TabList'; import TabPanel from '@mui/lab/TabPanel'; @@ -25,6 +26,7 @@ import { DOMAIN_LIST_PAGE_SIZE, getOwnerDomains, getProfileData, + useFeatureFlags, } from '../../actions'; import {useWeb3Context} from '../../hooks'; import useFireblocksState from '../../hooks/useFireblocksState'; @@ -47,6 +49,7 @@ import Buy from './Buy'; import Receive from './Receive'; import ReceiveDomainModal from './ReceiveDomainModal'; import Send from './Send'; +import Swap from './Swap'; import {TokensPortfolio} from './TokensPortfolio'; const useStyles = makeStyles<{isMobile: boolean}>()( @@ -85,10 +88,12 @@ const useStyles = makeStyles<{isMobile: boolean}>()( padding: theme.spacing(1), borderRadius: theme.shape.borderRadius, marginRight: theme.spacing(2), - width: '100px', + width: '85px', + height: '85px', cursor: 'pointer', [theme.breakpoints.down('sm')]: { width: '70px', + height: '70px', }, }, domainListContainer: { @@ -135,15 +140,19 @@ const useStyles = makeStyles<{isMobile: boolean}>()( }, actionIcon: { color: theme.palette.primary.main, - width: '50px', - height: '50px', + width: '40px', + height: '40px', [theme.breakpoints.down('sm')]: { width: '35px', height: '35px', }, }, actionText: { + marginTop: theme.spacing(0.5), color: theme.palette.primary.main, + [theme.breakpoints.down('sm')]: { + marginTop: theme.spacing(0), + }, }, tabList: { marginTop: theme.spacing(-3), @@ -195,6 +204,10 @@ export const Client: React.FC = ({ const {classes} = useStyles({isMobile}); const [t] = useTranslationContext(); const {enqueueSnackbar} = useSnackbar(); + const {data: featureFlags} = useFeatureFlags( + false, + wallets?.find(w => isEthAddress(w.address))?.address, + ); // wallet state variables const [state, saveState] = useFireblocksState(); @@ -208,6 +221,7 @@ export const Client: React.FC = ({ const [isSend, setIsSend] = useState(false); const [isReceive, setIsReceive] = useState(false); const [isBuy, setIsBuy] = useState(false); + const [isSwap, setIsSwap] = useState(false); const [tabValue, setTabValue] = useState(ClientTabType.Portfolio); // domain list state @@ -378,18 +392,28 @@ export const Client: React.FC = ({ setIsSend(true); setIsReceive(false); setIsBuy(false); + setIsSwap(false); + }; + + const handleClickedSwap = () => { + setIsSwap(true); + setIsBuy(false); + setIsSend(false); + setIsReceive(false); }; const handleClickedBuy = () => { setIsBuy(true); setIsSend(false); setIsReceive(false); + setIsSwap(false); }; const handleClickedReceive = () => { setIsReceive(true); setIsSend(false); setIsBuy(false); + setIsSwap(false); }; const handleCancel = async () => { @@ -397,6 +421,7 @@ export const Client: React.FC = ({ setIsSend(false); setIsReceive(false); setIsBuy(false); + setIsSwap(false); // refresh portfolio data await onRefresh(); @@ -416,6 +441,17 @@ export const Client: React.FC = ({ wallets={wallets} /> + ) : isSwap ? ( + + + ) : isReceive ? ( @@ -454,7 +490,7 @@ export const Client: React.FC = ({ > {t('common.send')} @@ -464,14 +500,28 @@ export const Client: React.FC = ({ className={classes.actionContainer} onClick={handleClickedReceive} > - + {t('common.receive')} + {featureFlags?.variations?.udMeServiceEnableSwap && ( + + + + {t('swap.title')} + + + )} = ({ > {t(isSellEnabled ? 'common.buySell' : 'common.buy')} diff --git a/packages/ui-components/src/components/Wallet/FundWalletModal.tsx b/packages/ui-components/src/components/Wallet/FundWalletModal.tsx index 40c67a32..c9b67cf7 100644 --- a/packages/ui-components/src/components/Wallet/FundWalletModal.tsx +++ b/packages/ui-components/src/components/Wallet/FundWalletModal.tsx @@ -1,5 +1,5 @@ -import AddOutlinedIcon from '@mui/icons-material/AddOutlined'; import AttachMoneyIcon from '@mui/icons-material/AttachMoney'; +import QrCodeIcon from '@mui/icons-material/QrCode'; import RocketLaunchOutlined from '@mui/icons-material/RocketLaunchOutlined'; import Alert from '@mui/lab/Alert'; import Box from '@mui/material/Box'; @@ -86,7 +86,7 @@ const FundWalletModal: React.FC = ({onReceiveClicked, onBuyClicked}) => { + + )} + {isTxComplete && } + {errorMessage && ( + + + {t('common.refresh')} + + ) : undefined + } + > + {errorMessage} + + + )} + {isGettingQuote && sourceToken && destinationToken && ( + + + {t('swap.gettingQuote', { + source: sourceToken.swing.symbol, + destination: destinationToken.swing.symbol, + })} + + + )} + {isSwapping && !txId && sourceToken && destinationToken && quotes && ( + + + {t('swap.swapping', { + source: sourceToken.swing.symbol, + destination: destinationToken.swing.symbol, + minutes: quotes[0].duration, + s: quotes[0].duration > 1 ? 's' : '', + })} + + + )} + {!isButtonHidden && ( + + + + {`${ + sourceToken.swing.symbol !== destinationToken.swing.symbol + ? t('swap.swap') + : t('swap.bridge') + } ${sourceTokenAmountUsd + .toLocaleString('en-US', { + style: 'currency', + currency: 'USD', + }) + .replace('.00', '')} ${getBlockchainDisplaySymbol( + sourceToken.swing.symbol, + )}${ + sourceToken.swing.symbol !== destinationToken.swing.symbol + ? ` ${t( + 'common.to', + ).toLowerCase()} ${getBlockchainDisplaySymbol( + destinationToken.swing.symbol, + )}` + : '' + }`} + + {quotes && ( + + {getQuoteDescription(quotes[0])} + + )} + + + )} + + + ); +}; + +export default Swap; diff --git a/packages/ui-components/src/components/Wallet/Token.tsx b/packages/ui-components/src/components/Wallet/Token.tsx index fa07b929..54775279 100644 --- a/packages/ui-components/src/components/Wallet/Token.tsx +++ b/packages/ui-components/src/components/Wallet/Token.tsx @@ -11,11 +11,7 @@ import {Line} from 'react-chartjs-2'; import {makeStyles} from '@unstoppabledomains/ui-kit/styles'; -import type { - CurrenciesType, - SerializedPriceHistory, - WalletPalette, -} from '../../lib'; +import type {CurrenciesType, TokenEntry, WalletPalette} from '../../lib'; import {TokenType, WalletPaletteOwner, WalletPalettePublic} from '../../lib'; import {CryptoIcon} from '../Image'; import {getBlockchainDisplaySymbol} from '../Manage/common/verification/types'; @@ -87,30 +83,17 @@ const useStyles = makeStyles()((theme: Theme, {palette}) => ({ }, })); -export type TokenEntry = { - address?: string; - type: TokenType; - symbol: string; - name: string; - ticker: string; - value: number; - tokenConversionUsd: number; - balance: number; - pctChange?: number; - imageUrl?: string; - history?: SerializedPriceHistory[]; - walletAddress: string; - walletBlockChainLink: string; - walletName: string; - walletType?: string; -}; - type Props = { token: TokenEntry; - onClick: () => void; + onClick?: () => void; isOwner?: boolean; showGraph?: boolean; hideBalance?: boolean; + iconWidth?: number; + graphWidth?: number; + balanceWidth?: number; + descriptionWidth?: number; + compact?: boolean; }; const Token: React.FC = ({ @@ -119,6 +102,11 @@ const Token: React.FC = ({ isOwner, showGraph, hideBalance, + iconWidth, + descriptionWidth, + graphWidth, + balanceWidth, + compact, }) => { const {classes, cx} = useStyles({ palette: isOwner ? WalletPaletteOwner : WalletPalettePublic, @@ -132,7 +120,7 @@ const Token: React.FC = ({ className={classes.txLink} data-testid={`token-${token.symbol}`} > - + = ({ - + - {token.name} + {compact + ? token.type === TokenType.Nft + ? `NFT${token.balance === 1 ? '' : 's'}` + : getBlockchainDisplaySymbol(token.ticker) + : token.name} + {compact && numeral(token.value).format('($0.00a)')} {!hideBalance && numeral(token.balance).format('0.[000000]')}{' '} - {token.type === TokenType.Nft + {compact + ? '' + : token.type === TokenType.Nft ? `NFT${token.balance === 1 ? '' : 's'}` : getBlockchainDisplaySymbol(token.ticker)} - + {showGraph && token.history && ( = ({ )} - - {!hideBalance && ( + {!hideBalance && ( + = ({ : `---`} - )} - + + )} ); }; diff --git a/packages/ui-components/src/components/Wallet/TokensPortfolio.tsx b/packages/ui-components/src/components/Wallet/TokensPortfolio.tsx index ee7f844a..e8a5daf6 100644 --- a/packages/ui-components/src/components/Wallet/TokensPortfolio.tsx +++ b/packages/ui-components/src/components/Wallet/TokensPortfolio.tsx @@ -14,7 +14,7 @@ import React, {useEffect, useState} from 'react'; import {makeStyles} from '@unstoppabledomains/ui-kit/styles'; -import type {CurrenciesType, WalletPalette} from '../../lib'; +import type {CurrenciesType, TokenEntry, WalletPalette} from '../../lib'; import { WALLET_CARD_HEIGHT, WalletPaletteOwner, @@ -25,7 +25,6 @@ import { import type {SerializedWalletBalance} from '../../lib/types/domain'; import CopyToClipboard from '../CopyToClipboard'; import {CryptoIcon} from '../Image'; -import type {TokenEntry} from './Token'; import Token from './Token'; Chart.register(CategoryScale); diff --git a/packages/ui-components/src/hooks/useSubmitTransaction.ts b/packages/ui-components/src/hooks/useSubmitTransaction.ts index 5080fedd..8596bb08 100644 --- a/packages/ui-components/src/hooks/useSubmitTransaction.ts +++ b/packages/ui-components/src/hooks/useSubmitTransaction.ts @@ -9,8 +9,8 @@ import { getTransferOperationResponse, } from '../actions/fireBlocksActions'; import {getRecordKeys} from '../components/Manage/common/verification/types'; -import type {TokenEntry} from '../components/Wallet/Token'; -import {TokenType} from '../lib'; +import type {TokenEntry} from '../lib'; +import { TokenType} from '../lib'; import {notifyEvent} from '../lib/error'; import {FB_MAX_RETRY, FB_WAIT_TIME_MS} from '../lib/fireBlocks/client'; import {isEmailValid} from '../lib/isEmailValid'; diff --git a/packages/ui-components/src/lib/types/swingXyzV1.ts b/packages/ui-components/src/lib/types/swingXyzV1.ts new file mode 100644 index 00000000..ec4cbd1b --- /dev/null +++ b/packages/ui-components/src/lib/types/swingXyzV1.ts @@ -0,0 +1,153 @@ +interface FromChain { + chainId: number; + name: string; + slug: string; + protocolType: string; +} + +interface FromToken { + address: string; + symbol: string; + name: string; + decimals: number; + logoURI: string; +} + +export interface RouteQuote { + duration: number; + gas: string; + quote: Quote; + route: RouteStep[]; + distribution: {[key: string]: number}; + gasUSD: string; +} + +export interface RouteTransaction { + bridge: string; + bridgeTokenAddress: string; + steps: string[]; + name: string; + part: number; +} + +interface Quote { + integration: string; + type: string; + bridgeFee: string; + bridgeFeeInNativeToken: string; + amount: string; + decimals: number; + amountUSD: string; + bridgeFeeUSD: string; + bridgeFeeInNativeTokenUSD: string; + fees: Fee[]; + priceImpact?: string; +} + +interface Fee { + type: string; + amount: string; + amountUSD: string; + chainSlug: string; + tokenSymbol: string; + tokenAddress: string; + decimals: number; + deductedFromSourceToken: boolean; +} + +interface RouteStep { + bridge: string; + bridgeTokenAddress: string; + steps: string[]; + name: string; + part: number; +} + +interface Token { + address: string; + symbol: string; + name: string; + decimals: number; + logoURI: string; +} + +interface Chain { + chainId: number; + name: string; + slug: string; + protocolType: string; +} + +export interface SwingQuoteRequest { + // information about source token + fromChain: string; + fromChainDecimal: number; + fromTokenAddress?: string; + fromUserAddress: string; + tokenSymbol: string; + tokenAmount: string; + + // information about destination token + toChain: string; + toChainDecimal: number; + toTokenSymbol: string; + toTokenAddress?: string; + toUserAddress: string; + + // information about the project configuration + projectId?: string; + fee?: number; +} + +export interface SwingQuoteResponse { + routes: RouteQuote[]; + fromToken: Token; + fromChain: Chain; + toToken: Token; + toChain: Chain; +} + +export interface SwingToken { + symbol: string; + address: string; + chain: string; + decimals: number; + logo: string; + price: number; +} + +export interface SwingTransactionResponse { + id: number; + fromToken: FromToken; + toToken: ToToken; + fromChain: FromChain; + toChain: ToChain; + route: RouteTransaction[]; + message: string; + error: string; + statusCode: number; + tx: SwingTx; +} + +export interface SwingTx { + from: string; + to: string; + data: string; + value: string; + txId: string; +} + +interface ToChain { + chainId: number; + name: string; + slug: string; + protocolType: string; +} + +interface ToToken { + address: string; + symbol: string; + name: string; + decimals: number; + logoURI: string; +} diff --git a/packages/ui-components/src/lib/types/swingXyzV2.ts b/packages/ui-components/src/lib/types/swingXyzV2.ts new file mode 100644 index 00000000..a8306f1f --- /dev/null +++ b/packages/ui-components/src/lib/types/swingXyzV2.ts @@ -0,0 +1,172 @@ +export interface Destination { + address: string; + chain: string; + token: string; + amount: string; + amountUSD: string; + hash?: string; +} + +export interface Distribution { + BaseSwapV3?: number; + odos?: number; + bebop?: number; + UniswapV3?: number; +} + +export interface Fee { + type: string; + amount: string; + amountUSD: string; + chainSlug: string; + tokenSymbol: string; + tokenAddress: string; + decimals: number; + deductedFromSourceToken: boolean; +} + +export interface FromChain { + chainId: number; + name: string; + slug: string; + protocolType: string; +} + +export interface FromToken { + address: string; + symbol: string; + name: string; + decimals: number; + logoURI: string; +} + +export interface Quote { + integration: string; + type: string; + bridgeFee: string; + bridgeFeeInNativeToken: string; + amount: string; + decimals: number; + amountUSD: string; + bridgeFeeUSD: string; + bridgeFeeInNativeTokenUSD: string; + fees: Fee[]; + priceImpact: string; +} + +export interface Route { + bridge: string; + bridgeTokenAddress: string; + steps: string[]; + name: string; + part: number; +} + +export interface RouteQuote { + duration: number; + gas: string; + quote: Quote; + route: Route[]; + distribution: Distribution; + gasUSD: string; +} + +export interface Source { + address: string; + chain: string; + token: string; + amount: string; + amountUSD: string; + hash?: string; +} + +export interface SwingV2QuoteRequest { + // information about source token + fromChain: string; + fromTokenAddress?: string; + fromUserAddress: string; + tokenSymbol: string; + tokenAmount: string; + + // information about destination token + toChain: string; + toTokenSymbol: string; + toTokenAddress?: string; + toUserAddress: string; + + // information about project configuration + projectId?: string; + fee?: number; +} + +export interface SwingV2QuoteResponse { + routes: RouteQuote[]; + fromToken: FromToken; + fromChain: FromChain; + toToken: ToToken; + toChain: ToChain; +} + +export type SwingV2SendRequest = SwingV2QuoteRequest & { + toTokenAmount: string; + route: Route[]; + type: string; + integration: string; +}; + +export interface SwingV2SendResponse { + id: number; + fromToken: FromToken; + toToken: ToToken; + fromChain: FromChain; + toChain: ToChain; + route: Route[]; + tx: Tx; + error?: string; + message?: string; +} + +export interface SwingV2SwapStatus { + id: string; + type: string; + status: string; + reason: string; + createdAt: string; + updatedAt: string; + finishedAt?: string; + integration: string; + source: Source; + destination: Destination; +} + +export interface SwingV2Token { + symbol: string; + address: string; + chain: string; + decimals: number; + logo: string; + price: number; +} + +export interface ToChain { + chainId: number; + name: string; + slug: string; + protocolType: string; +} + +export interface ToToken { + address: string; + symbol: string; + name: string; + decimals: number; + logoURI: string; +} + +export interface Tx { + data: string; + to: string; + from: string; + gas: string; + value: string; +} diff --git a/packages/ui-components/src/lib/types/wallet.ts b/packages/ui-components/src/lib/types/wallet.ts index 88b57ca7..ebfa0e55 100644 --- a/packages/ui-components/src/lib/types/wallet.ts +++ b/packages/ui-components/src/lib/types/wallet.ts @@ -1,3 +1,5 @@ +import type {SerializedPriceHistory, TokenType} from './domain'; + export interface LoginResult { address: string; domain: string; @@ -5,6 +7,24 @@ export interface LoginResult { export const SUPPORTED_SIGNING_SYMBOLS = ['ETH', 'MATIC', 'POL', 'SOL', 'BTC']; +export type TokenEntry = { + address?: string; + type: TokenType; + symbol: string; + name: string; + ticker: string; + value: number; + tokenConversionUsd: number; + balance: number; + pctChange?: number; + imageUrl?: string; + history?: SerializedPriceHistory[]; + walletAddress: string; + walletBlockChainLink: string; + walletName: string; + walletType?: string; +}; + export const WALLET_CARD_HEIGHT = 275; export type WagmiConnectorType = diff --git a/packages/ui-components/src/lib/wallet/asset.ts b/packages/ui-components/src/lib/wallet/asset.ts index a6fe42a0..8bf46d38 100644 --- a/packages/ui-components/src/lib/wallet/asset.ts +++ b/packages/ui-components/src/lib/wallet/asset.ts @@ -1,9 +1,9 @@ import config from '@unstoppabledomains/config'; -import type {TokenEntry} from '../../components/Wallet/Token'; import {notifyEvent} from '../error'; import {TokenType} from '../types'; import type {AccountAsset} from '../types/fireBlocks'; +import type { TokenEntry} from '../types/wallet'; import {SUPPORTED_SIGNING_SYMBOLS} from '../types/wallet'; export const getAsset = ( diff --git a/packages/ui-components/src/lib/wallet/evm/token.ts b/packages/ui-components/src/lib/wallet/evm/token.ts index 13082d1a..8c72cc02 100644 --- a/packages/ui-components/src/lib/wallet/evm/token.ts +++ b/packages/ui-components/src/lib/wallet/evm/token.ts @@ -1,4 +1,7 @@ -import type {CreateTransaction} from '../../types'; +import type {SerializedWalletBalance} from '../../types/domain'; +import {TokenType} from '../../types/domain'; +import type {CreateTransaction} from '../../types/fireBlocks'; +import type {TokenEntry} from '../../types/wallet'; import {getContract, getContractDecimals} from './web3'; export const createErc20TransferTx = async (opts: { @@ -33,3 +36,66 @@ export const createErc20TransferTx = async (opts: { value: '0', }; }; + +export const getAllTokens = (wallets: SerializedWalletBalance[]) => { + const allTokens: TokenEntry[] = [ + ...wallets.flatMap(serializeNativeTokens), + ...(wallets || []).flatMap(wallet => + (wallet?.tokens || []).map(walletToken => { + return { + address: walletToken.address, + type: walletToken.type, + name: walletToken.name, + value: walletToken.value?.walletUsdAmt || 0, + balance: walletToken.balanceAmt || 0, + pctChange: walletToken.value?.marketPctChange24Hr, + tokenConversionUsd: walletToken.value?.marketUsdAmt || 0, + history: walletToken.value?.history?.sort( + (a, b) => + new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime(), + ), + ticker: walletToken.symbol, + symbol: wallet.symbol, + walletAddress: wallet.address, + walletBlockChainLink: wallet.blockchainScanUrl, + walletName: wallet.name, + walletType: wallet.walletType, + imageUrl: walletToken.logoUrl, + }; + }), + ), + ]; + return allTokens; +}; + +export const serializeNativeTokens = (wallet: SerializedWalletBalance) => { + if ( + wallet.value?.history && + wallet.value.history.length > 0 && + wallet.value.history[wallet.value.history.length - 1].value !== + wallet.value.marketUsdAmt + ) { + wallet.value.history.push({ + timestamp: new Date(), + value: wallet.value.marketUsdAmt || 0, + }); + } + return { + type: TokenType.Native, + name: wallet.name, + value: wallet.value?.walletUsdAmt || 0, + tokenConversionUsd: wallet.value?.marketUsdAmt || 0, + balance: wallet.balanceAmt || 0, + pctChange: wallet.value?.marketPctChange24Hr, + history: wallet.value?.history?.sort( + (a, b) => + new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime(), + ), + symbol: wallet.symbol, + ticker: wallet.gasCurrency, + walletAddress: wallet.address, + walletBlockChainLink: wallet.blockchainScanUrl, + walletName: wallet.name, + imageUrl: wallet.logoUrl, + }; +}; diff --git a/packages/ui-components/src/lib/wallet/tokens.ts b/packages/ui-components/src/lib/wallet/tokens.ts index dbeb6d7b..f0a1f611 100644 --- a/packages/ui-components/src/lib/wallet/tokens.ts +++ b/packages/ui-components/src/lib/wallet/tokens.ts @@ -1,6 +1,5 @@ -import type {TokenEntry} from '../../components/Wallet/Token'; -import type {SerializedWalletBalance} from '../types'; -import { TokenType} from '../types'; +import type {SerializedWalletBalance, TokenEntry} from '../types'; +import {TokenType} from '../types'; export const getSortedTokens = ( wallets: SerializedWalletBalance[], diff --git a/packages/ui-components/src/locales/en.json b/packages/ui-components/src/locales/en.json index 871c6eb9..6d6dca6a 100644 --- a/packages/ui-components/src/locales/en.json +++ b/packages/ui-components/src/locales/en.json @@ -145,7 +145,7 @@ "blockchain": "Blockchain", "bulkSave": "Bulk Save", "buy": "Buy", - "buySell": "Buy / Sell", + "buySell": "In / Out", "cancel": "Cancel", "cart": "Cart", "claim": "Claim", @@ -200,6 +200,7 @@ "merch": "Merch", "messenger": "Messenger", "mint": "Mint", + "minute": "minute", "more": "More", "multichain": "multichain", "needHelp": "Need help?", @@ -828,6 +829,24 @@ "searchResultsFor": "Profiles matching \"{{searchTerm}}\"", "tryAnotherSearch": "Try another domain or wallet address" }, + "swap": { + "balance": "Balance", + "bridge": "Bridge", + "description": "Swap your crypto", + "errorSwappingTokens": "Failed to complete swap, but your funds are safe. Refresh the quote or choose a different amount.", + "gettingQuote": "Getting a quote to swap your {{source}} \u2192 {{destination}}", + "introContent": "Choose a token from your wallet and the amount you want to swap. A quote will be created to preview your new tokens.", + "introTitle": "Convert tokens with swap", + "noQuoteAvailable": "No quotes available for {{source}} \u2192 {{destination}} at this time. Select new tokens or try a different amount.", + "pairNotSupported": "Swapping {{source}} \u2192 {{destination}} is not supported at this time. Choose another token and try again.", + "pay": "Pay", + "payWithToken": "Token you have", + "receive": "Receive", + "receiveToken": "Token you want", + "swap": "Swap", + "swapping": "Swapping your {{source}} \u2192 {{destination}}. The estimated processing time is {{minutes}} minute{{s}}. Leave this window open until complete.", + "title": "Swap" + }, "tokensPortfolio": { "all": "All", "crypto": "Crypto", @@ -947,8 +966,8 @@ "resolutionError": "Could not find recipient wallet address", "resolvedDomain": "Wallet found for {{resolvedDomain}}", "retrievingGasPrice": "Checking {{blockchain}} network health...", - "selectAssetToBuy": "Select crypto to buy", - "selectAssetToBuySell": "Select crypto to buy or sell", + "selectAssetToBuy": "Select crypto to buy using a bank account", + "selectAssetToBuySell": "Select crypto to buy or sell with a bank account", "selectAssetToReceive": "Select crypto to receive", "selectAssetToSend": "Select crypto to send", "sendByEmail": "Send to email address", diff --git a/packages/ui-components/src/tests/mocks/wallet.ts b/packages/ui-components/src/tests/mocks/wallet.ts index 1e57df22..a6254989 100644 --- a/packages/ui-components/src/tests/mocks/wallet.ts +++ b/packages/ui-components/src/tests/mocks/wallet.ts @@ -1,6 +1,6 @@ import type {IFireblocksNCW} from '@fireblocks/ncw-js-sdk'; -import type {TokenEntry} from '../../components/Wallet/Token'; +import type {TokenEntry} from '../../lib/types'; import type { SerializedPublicDomainProfileData, SerializedWalletBalance, diff --git a/yarn.lock b/yarn.lock index 6a7b1809..c8b386d1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4289,6 +4289,13 @@ __metadata: languageName: node linkType: hard +"@types/canvas-confetti@npm:^1.6.4": + version: 1.6.4 + resolution: "@types/canvas-confetti@npm:1.6.4" + checksum: 0452e1f381e5d59471578be1b83f2908f0906ba0fddfe16f941a07e070f4ad91ceaf1a68bd84d84bb5e81214c95ef00137677d9d61d81119051425ca5ce307ba + languageName: node + linkType: hard + "@types/chrome@npm:^0.0.268": version: 0.0.268 resolution: "@types/chrome@npm:0.0.268" @@ -5425,6 +5432,7 @@ __metadata: qs: ^6.11.2 ramda: ^0.27.1 react: ^17.0.0 + react-canvas-confetti: ^2.0.7 react-chartjs-2: ^5.2.0 react-cookies: ^0.1.1 react-dom: ^17.0.0 @@ -8015,6 +8023,13 @@ __metadata: languageName: node linkType: hard +"canvas-confetti@npm:^1.9.2": + version: 1.9.3 + resolution: "canvas-confetti@npm:1.9.3" + checksum: db044a9c9ca0e58eafd115f7dfc2f9ecc377be34d8a5dd75901dae0dafd4fb0b75fbbf8edd48bbefad2468653c5838348f0e768b79727a924259a9ada343ea30 + languageName: node + linkType: hard + "carbites@npm:^1.0.6": version: 1.0.6 resolution: "carbites@npm:1.0.6" @@ -18129,6 +18144,18 @@ __metadata: languageName: node linkType: hard +"react-canvas-confetti@npm:^2.0.7": + version: 2.0.7 + resolution: "react-canvas-confetti@npm:2.0.7" + dependencies: + "@types/canvas-confetti": ^1.6.4 + canvas-confetti: ^1.9.2 + peerDependencies: + react: "*" + checksum: 256f0e6e01b35535d7f26ba43c85d4dad488b3ea77b3e36c6849c8ef76a9354a4d85d8908445cc3820e7553e77a1a7dfa1a86904cc9a74746d5ec12a66cdcd5f + languageName: node + linkType: hard + "react-chartjs-2@npm:^5.2.0": version: 5.2.0 resolution: "react-chartjs-2@npm:5.2.0"