From 80fa5af430da2f60afdde91d3327142d03d83384 Mon Sep 17 00:00:00 2001
From: Juliano Lazzarotto <30806844+stackchain@users.noreply.github.com>
Date: Wed, 8 Feb 2023 11:28:38 -0500
Subject: [PATCH] wip
---
.storybook/storybook.requires.js | 12 +-
src/AppNavigator.tsx | 3 +
src/Send/AddressReaderQR/index.ts | 1 -
.../AssetSelectorScreen.stories.tsx | 17 --
src/Send/AssetSelectorScreen/index.tsx | 1 -
src/Send/ConfirmScreen/index.ts | 1 -
src/Send/Context/SendContext.tsx | 144 ----------
src/Send/SendScreen/AvailableAmountBanner.tsx | 25 --
.../SendScreen/BalanceAfterTransaction.tsx | 55 ----
src/Send/SendScreen/Fee.tsx | 45 ---
src/Send/SendScreen/SendAllWarning.tsx | 50 ----
src/Send/SendScreen/SendScreen.stories.tsx | 30 --
src/Send/SendScreen/SendScreen.tsx | 263 ------------------
src/Send/SendScreen/index.ts | 1 -
src/Send/{ => shared}/AmountField.tsx | 4 +-
src/Send/{ => shared}/ScannerButton.tsx | 16 +-
src/Send/shared/SendContext.tsx | 213 ++++++++++++++
src/Send/shared/hooks.ts | 47 ++++
src/Send/{SendScreen => shared}/strings.ts | 41 ++-
src/Send/{SendScreen => shared}/utils.ts | 47 +---
.../ConfirmTx/ConfirmTxScreen.stories.tsx} | 14 +-
.../ConfirmTx/ConfirmTxScreen.tsx} | 46 +--
src/Send/useCases/ConfirmTx/index.ts | 1 +
.../AddToken/AddToken.stories.tsx | 12 +
.../ListSelectedTokens/AddToken/AddToken.tsx | 57 ++++
.../AddToken/InputSearch.tsx | 25 ++
.../SelectTokenFromListScreen.stories.tsx | 17 ++
.../AddToken/SelectTokenScreen.tsx} | 97 +++----
.../DeleteToken.stories.tsx | 47 ++++
.../ListSelectedTokens/DeleteToken.tsx | 47 ++++
.../EditAmountScreen.stories.tsx | 89 ++++++
.../ListSelectedTokens/EditAmountScreen.tsx | 158 +++++++++++
.../ListSelectedTokensScreen.stories.tsx | 26 ++
.../ListSelectedTokensScreen.tsx | 136 +++++++++
.../useCases/ListSelectedTokens/index.tsx | 2 +
.../StartTx/InputMemo.tsx} | 4 +-
.../StartTx/InputReceiver/InputReceiver.tsx | 31 +++
.../ReadQRCodeScreen.stories.tsx} | 11 +-
.../InputReceiver/ReadQRCodeScreen.tsx} | 10 +-
.../StartTx/InputReceiver/ResolveAddress.tsx | 114 ++++++++
.../StartTx/ShowErrors.tsx} | 10 +-
.../StartTx/StartTxScreen.stories.tsx | 30 ++
src/Send/useCases/StartTx/StartTxScreen.tsx | 107 +++++++
src/TxHistory/ActionsBanner.tsx | 4 +-
src/TxHistory/AssetList/AssetList.tsx | 6 +-
src/TxHistory/PairedBalance.tsx | 5 +-
src/TxHistory/TxHistory.stories.tsx | 17 +-
src/TxHistory/TxHistoryNavigator.tsx | 85 ++++--
.../AssetItem/AssetItem.stories.tsx | 8 +-
src/components/AssetItem/AssetItem.tsx | 10 +-
src/components/TextInput/TextInput.tsx | 6 +-
src/i18n/global-messages.ts | 8 +
src/i18n/locales/en-US.json | 8 +
src/navigation.ts | 10 +-
src/yoroi-wallets/cardano/api/utils.ts | 2 +-
src/yoroi-wallets/hooks/index.ts | 2 -
src/yoroi-wallets/mocks/wallet.ts | 45 +++
src/yoroi-wallets/utils/amountUtils.test.ts | 8 -
src/yoroi-wallets/utils/amountUtils.ts | 2 -
src/yoroi-wallets/utils/utils.test.ts | 65 ++++-
src/yoroi-wallets/utils/utils.ts | 24 ++
src/yoroi-wallets/utils/validators.ts | 50 +---
62 files changed, 1555 insertions(+), 917 deletions(-)
delete mode 100644 src/Send/AddressReaderQR/index.ts
delete mode 100644 src/Send/AssetSelectorScreen/AssetSelectorScreen.stories.tsx
delete mode 100644 src/Send/AssetSelectorScreen/index.tsx
delete mode 100644 src/Send/ConfirmScreen/index.ts
delete mode 100644 src/Send/Context/SendContext.tsx
delete mode 100644 src/Send/SendScreen/AvailableAmountBanner.tsx
delete mode 100644 src/Send/SendScreen/BalanceAfterTransaction.tsx
delete mode 100644 src/Send/SendScreen/Fee.tsx
delete mode 100644 src/Send/SendScreen/SendAllWarning.tsx
delete mode 100644 src/Send/SendScreen/SendScreen.stories.tsx
delete mode 100644 src/Send/SendScreen/SendScreen.tsx
delete mode 100644 src/Send/SendScreen/index.ts
rename src/Send/{ => shared}/AmountField.tsx (90%)
rename src/Send/{ => shared}/ScannerButton.tsx (56%)
create mode 100644 src/Send/shared/SendContext.tsx
create mode 100644 src/Send/shared/hooks.ts
rename src/Send/{SendScreen => shared}/strings.ts (86%)
rename src/Send/{SendScreen => shared}/utils.ts (76%)
rename src/Send/{ConfirmScreen/ConfirmScreen.stories.tsx => useCases/ConfirmTx/ConfirmTxScreen.stories.tsx} (58%)
rename src/Send/{ConfirmScreen/ConfirmScreen.tsx => useCases/ConfirmTx/ConfirmTxScreen.tsx} (86%)
create mode 100644 src/Send/useCases/ConfirmTx/index.ts
create mode 100644 src/Send/useCases/ListSelectedTokens/AddToken/AddToken.stories.tsx
create mode 100644 src/Send/useCases/ListSelectedTokens/AddToken/AddToken.tsx
create mode 100644 src/Send/useCases/ListSelectedTokens/AddToken/InputSearch.tsx
create mode 100644 src/Send/useCases/ListSelectedTokens/AddToken/SelectTokenFromListScreen.stories.tsx
rename src/Send/{AssetSelectorScreen/AssetSelectorScreen.tsx => useCases/ListSelectedTokens/AddToken/SelectTokenScreen.tsx} (56%)
create mode 100644 src/Send/useCases/ListSelectedTokens/DeleteToken.stories.tsx
create mode 100644 src/Send/useCases/ListSelectedTokens/DeleteToken.tsx
create mode 100644 src/Send/useCases/ListSelectedTokens/EditAmountScreen.stories.tsx
create mode 100644 src/Send/useCases/ListSelectedTokens/EditAmountScreen.tsx
create mode 100644 src/Send/useCases/ListSelectedTokens/ListSelectedTokensScreen.stories.tsx
create mode 100644 src/Send/useCases/ListSelectedTokens/ListSelectedTokensScreen.tsx
create mode 100644 src/Send/useCases/ListSelectedTokens/index.tsx
rename src/Send/{Memo.tsx => useCases/StartTx/InputMemo.tsx} (94%)
create mode 100644 src/Send/useCases/StartTx/InputReceiver/InputReceiver.tsx
rename src/Send/{AddressReaderQR/AddressReaderQR.stories.tsx => useCases/StartTx/InputReceiver/ReadQRCodeScreen.stories.tsx} (54%)
rename src/Send/{AddressReaderQR/AddressReaderQR.tsx => useCases/StartTx/InputReceiver/ReadQRCodeScreen.tsx} (82%)
create mode 100644 src/Send/useCases/StartTx/InputReceiver/ResolveAddress.tsx
rename src/Send/{SendScreen/ErrorBanners.tsx => useCases/StartTx/ShowErrors.tsx} (63%)
create mode 100644 src/Send/useCases/StartTx/StartTxScreen.stories.tsx
create mode 100644 src/Send/useCases/StartTx/StartTxScreen.tsx
diff --git a/.storybook/storybook.requires.js b/.storybook/storybook.requires.js
index d0b73c2708..5c3554576f 100644
--- a/.storybook/storybook.requires.js
+++ b/.storybook/storybook.requires.js
@@ -111,10 +111,14 @@ const getStories = () => {
"./src/Receive/ReceiveScreen.stories.tsx": require("../src/Receive/ReceiveScreen.stories.tsx"),
"./src/Search/SearchBar.stories.tsx": require("../src/Search/SearchBar.stories.tsx"),
"./src/SelectedWallet/WalletSelection/WalletSelectionScreen.stories.tsx": require("../src/SelectedWallet/WalletSelection/WalletSelectionScreen.stories.tsx"),
- "./src/Send/AddressReaderQR/AddressReaderQR.stories.tsx": require("../src/Send/AddressReaderQR/AddressReaderQR.stories.tsx"),
- "./src/Send/AssetSelectorScreen/AssetSelectorScreen.stories.tsx": require("../src/Send/AssetSelectorScreen/AssetSelectorScreen.stories.tsx"),
- "./src/Send/ConfirmScreen/ConfirmScreen.stories.tsx": require("../src/Send/ConfirmScreen/ConfirmScreen.stories.tsx"),
- "./src/Send/SendScreen/SendScreen.stories.tsx": require("../src/Send/SendScreen/SendScreen.stories.tsx"),
+ "./src/Send/useCases/ConfirmTx/ConfirmTxScreen.stories.tsx": require("../src/Send/useCases/ConfirmTx/ConfirmTxScreen.stories.tsx"),
+ "./src/Send/useCases/ListSelectedTokens/AddToken/AddToken.stories.tsx": require("../src/Send/useCases/ListSelectedTokens/AddToken/AddToken.stories.tsx"),
+ "./src/Send/useCases/ListSelectedTokens/AddToken/SelectTokenFromListScreen.stories.tsx": require("../src/Send/useCases/ListSelectedTokens/AddToken/SelectTokenFromListScreen.stories.tsx"),
+ "./src/Send/useCases/ListSelectedTokens/DeleteToken.stories.tsx": require("../src/Send/useCases/ListSelectedTokens/DeleteToken.stories.tsx"),
+ "./src/Send/useCases/ListSelectedTokens/EditAmountScreen.stories.tsx": require("../src/Send/useCases/ListSelectedTokens/EditAmountScreen.stories.tsx"),
+ "./src/Send/useCases/ListSelectedTokens/ListSelectedTokensScreen.stories.tsx": require("../src/Send/useCases/ListSelectedTokens/ListSelectedTokensScreen.stories.tsx"),
+ "./src/Send/useCases/StartTx/InputReceiver/ReadQRCodeScreen.stories.tsx": require("../src/Send/useCases/StartTx/InputReceiver/ReadQRCodeScreen.stories.tsx"),
+ "./src/Send/useCases/StartTx/StartTxScreen.stories.tsx": require("../src/Send/useCases/StartTx/StartTxScreen.stories.tsx"),
"./src/Settings/ApplicationSettings/ApplicationSettingsScreen.stories.tsx": require("../src/Settings/ApplicationSettings/ApplicationSettingsScreen.stories.tsx"),
"./src/Settings/ChangeLanguage/ChangeLanguageScreen.stories.tsx": require("../src/Settings/ChangeLanguage/ChangeLanguageScreen.stories.tsx"),
"./src/Settings/ChangePassword/ChangePasswordScreen.stories.tsx": require("../src/Settings/ChangePassword/ChangePasswordScreen.stories.tsx"),
diff --git a/src/AppNavigator.tsx b/src/AppNavigator.tsx
index 31e663d29a..e8dc9a1c74 100644
--- a/src/AppNavigator.tsx
+++ b/src/AppNavigator.tsx
@@ -43,6 +43,9 @@ export const AppNavigator = () => {
}
}
+ // RNBootSplash.hide({fade: true})
+ // return
+
return (
{
- return (
-
-
-
-
-
- )
-})
diff --git a/src/Send/AssetSelectorScreen/index.tsx b/src/Send/AssetSelectorScreen/index.tsx
deleted file mode 100644
index 64c8cdf31f..0000000000
--- a/src/Send/AssetSelectorScreen/index.tsx
+++ /dev/null
@@ -1 +0,0 @@
-export * from './AssetSelectorScreen'
diff --git a/src/Send/ConfirmScreen/index.ts b/src/Send/ConfirmScreen/index.ts
deleted file mode 100644
index 5d6943c31e..0000000000
--- a/src/Send/ConfirmScreen/index.ts
+++ /dev/null
@@ -1 +0,0 @@
-export * from './ConfirmScreen'
diff --git a/src/Send/Context/SendContext.tsx b/src/Send/Context/SendContext.tsx
deleted file mode 100644
index 374a5e3435..0000000000
--- a/src/Send/Context/SendContext.tsx
+++ /dev/null
@@ -1,144 +0,0 @@
-import * as React from 'react'
-
-import {YoroiWallet} from '../../yoroi-wallets'
-import {TokenId} from '../../yoroi-wallets/types'
-
-type SendState = {
- tokenId: TokenId
- receiver: string
- amount: string
- sendAll: boolean
- memo: string
-}
-
-type SendActions = {
- receiverChanged: (receiver: SendState['receiver']) => void
- amountChanged: (amount: SendState['amount']) => void
- tokenSelected: (tokenId: SendState['tokenId']) => void
- memoChanged: (memo: SendState['memo']) => void
- sendAllChanged: () => void
- allTokensSelected: () => void
- resetForm: () => void
-}
-
-const SendContext = React.createContext(undefined)
-export const SendProvider = ({
- children,
- wallet,
- ...props
-}: {
- wallet: YoroiWallet
- initialState?: Partial
- children: React.ReactNode
-}) => {
- const [state, dispatch] = React.useReducer(sendReducer, {
- ...initialState(wallet.primaryToken.identifier),
- ...props.initialState,
- })
-
- const actions = React.useRef({
- receiverChanged: (receiver) => dispatch({type: 'receiverChanged', receiver}),
- amountChanged: (amount) => dispatch({type: 'amountChanged', amount}),
- tokenSelected: (tokenId) => dispatch({type: 'tokenSelected', tokenId}),
- memoChanged: (memo) => dispatch({type: 'memoChanged', memo}),
- sendAllChanged: () => dispatch({type: 'sendAllChanged'}),
- allTokensSelected: () => dispatch({type: 'allTokensSelected', primaryTokenId: wallet.primaryTokenInfo.id}),
- resetForm: () => dispatch({type: 'resetForm', primaryTokenId: wallet.primaryTokenInfo.id}),
- }).current
-
- const context = React.useMemo(() => ({...state, ...actions}), [actions, state])
-
- return {children}
-}
-
-type SendAction =
- | {
- type: 'receiverChanged'
- receiver: string
- }
- | {
- type: 'amountChanged'
- amount: string
- }
- | {
- type: 'memoChanged'
- memo: SendState['memo']
- }
- | {
- type: 'sendAllChanged'
- }
- | {
- type: 'allTokensSelected'
- primaryTokenId: TokenId
- }
- | {
- type: 'resetForm'
- primaryTokenId: TokenId
- }
- | {
- type: 'tokenSelected'
- tokenId: SendState['tokenId']
- }
-
-const sendReducer = (state: SendState, action: SendAction) => {
- switch (action.type) {
- case 'receiverChanged':
- return {
- ...state,
- receiver: action.receiver,
- }
-
- case 'amountChanged':
- return {
- ...state,
- amount: action.amount,
- }
-
- case 'sendAllChanged':
- return {
- ...state,
- sendAll: !state.sendAll,
- }
-
- case 'tokenSelected':
- return {
- ...state,
- sendAll: false,
- tokenId: action.tokenId,
- }
-
- case 'allTokensSelected':
- return {
- ...state,
- sendAll: true,
- tokenId: action.primaryTokenId,
- }
-
- case 'memoChanged':
- return {
- ...state,
- memo: action.memo,
- }
-
- case 'resetForm':
- return initialState(action.primaryTokenId)
-
- default:
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
- throw new Error(`sendReducer: action type ${(action as any).type} not supported`)
- }
-}
-
-export const useSend = () => React.useContext(SendContext) || missingProvider()
-
-const missingProvider = () => {
- throw new Error('SendProvider is missing')
-}
-
-const initialState = (primaryTokenId: TokenId) => ({
- tokenId: primaryTokenId,
- receiver: '',
- amount: '',
- sendAll: false,
- memo: '',
-})
diff --git a/src/Send/SendScreen/AvailableAmountBanner.tsx b/src/Send/SendScreen/AvailableAmountBanner.tsx
deleted file mode 100644
index 77662c6a06..0000000000
--- a/src/Send/SendScreen/AvailableAmountBanner.tsx
+++ /dev/null
@@ -1,25 +0,0 @@
-import React from 'react'
-
-import {Banner} from '../../components'
-import {formatTokenWithText} from '../../legacy/format'
-import {useSelectedWallet} from '../../SelectedWallet'
-import {useBalances} from '../../yoroi-wallets'
-import {Amounts} from '../../yoroi-wallets/utils'
-import {useStrings} from './strings'
-
-export const AvailableAmountBanner = () => {
- const strings = useStrings()
- const wallet = useSelectedWallet()
- const balances = useBalances(wallet)
-
- return (
-
- )
-}
diff --git a/src/Send/SendScreen/BalanceAfterTransaction.tsx b/src/Send/SendScreen/BalanceAfterTransaction.tsx
deleted file mode 100644
index 2751e1cf5c..0000000000
--- a/src/Send/SendScreen/BalanceAfterTransaction.tsx
+++ /dev/null
@@ -1,55 +0,0 @@
-import React from 'react'
-import {StyleSheet} from 'react-native'
-
-import {Text} from '../../components'
-import {formatTokenWithSymbol} from '../../legacy/format'
-import {useSelectedWallet} from '../../SelectedWallet'
-import {useBalances} from '../../yoroi-wallets'
-import {YoroiUnsignedTx} from '../../yoroi-wallets/types'
-import {Amounts} from '../../yoroi-wallets/utils'
-import {useStrings} from './strings'
-
-export const BalanceAfterTransaction = ({yoroiUnsignedTx}: {yoroiUnsignedTx: YoroiUnsignedTx | null}) => {
- const strings = useStrings()
- const wallet = useSelectedWallet()
- const balances = useBalances(wallet)
-
- if (!yoroiUnsignedTx) {
- return (
-
- {strings.balanceAfterLabel}
-
- {': '}
-
- {strings.balanceAfterNotAvailable}
-
- )
- }
-
- // prettier-ignore
- const balancesAfter = Amounts.diff(
- balances,
- Amounts.sum([
- yoroiUnsignedTx.amounts,
- yoroiUnsignedTx.fee,
- ]),
- )
- const primaryAmountAfter = Amounts.getAmount(balancesAfter, wallet.primaryToken.identifier)
-
- return (
-
- {strings.balanceAfterLabel}
-
- {': '}
-
- {formatTokenWithSymbol(primaryAmountAfter.quantity, wallet.primaryToken)}
-
- )
-}
-
-const styles = StyleSheet.create({
- info: {
- fontSize: 14,
- lineHeight: 22,
- },
-})
diff --git a/src/Send/SendScreen/Fee.tsx b/src/Send/SendScreen/Fee.tsx
deleted file mode 100644
index a881ccbc2c..0000000000
--- a/src/Send/SendScreen/Fee.tsx
+++ /dev/null
@@ -1,45 +0,0 @@
-import React from 'react'
-import {StyleSheet} from 'react-native'
-
-import {Text} from '../../components'
-import {formatTokenWithSymbol} from '../../legacy/format'
-import {useSelectedWallet} from '../../SelectedWallet'
-import {YoroiUnsignedTx} from '../../yoroi-wallets/types'
-import {Amounts} from '../../yoroi-wallets/utils'
-import {useStrings} from './strings'
-
-export const Fee = ({yoroiUnsignedTx}: {yoroiUnsignedTx: YoroiUnsignedTx | null}) => {
- const strings = useStrings()
- const wallet = useSelectedWallet()
-
- if (yoroiUnsignedTx == null) {
- return (
-
- {strings.feeLabel}
-
- {': '}
-
- {strings.feeNotAvailable}
-
- )
- }
-
- const primaryAmount = Amounts.getAmount(yoroiUnsignedTx.fee, wallet.primaryToken.identifier)
-
- return (
-
- {strings.feeLabel}
-
- {': '}
-
- {formatTokenWithSymbol(primaryAmount.quantity, wallet.primaryToken)}
-
- )
-}
-
-const styles = StyleSheet.create({
- info: {
- fontSize: 14,
- lineHeight: 22,
- },
-})
diff --git a/src/Send/SendScreen/SendAllWarning.tsx b/src/Send/SendScreen/SendAllWarning.tsx
deleted file mode 100644
index f988cce167..0000000000
--- a/src/Send/SendScreen/SendAllWarning.tsx
+++ /dev/null
@@ -1,50 +0,0 @@
-import React from 'react'
-
-import {DangerousActionModal, Text} from '../../components'
-import {truncateWithEllipsis} from '../../legacy/format'
-import {useSelectedWallet} from '../../SelectedWallet'
-import {useTokenInfo} from '../../yoroi-wallets'
-import {useStrings} from './strings'
-
-type SendAllWarningProps = {
- selectedTokenIdentifier: string
- onConfirm: () => void
- onCancel: () => void
- showSendAllWarning: boolean
-}
-export const SendAllWarning = ({
- showSendAllWarning,
- selectedTokenIdentifier,
- onCancel,
- onConfirm,
-}: SendAllWarningProps) => {
- const strings = useStrings()
- const wallet = useSelectedWallet()
- const tokenInfo = useTokenInfo({wallet, tokenId: selectedTokenIdentifier})
- const isPrimaryToken = tokenInfo.id === wallet.primaryTokenInfo.id
- const assetNameOrId = truncateWithEllipsis(tokenInfo.ticker ?? tokenInfo.name ?? tokenInfo.fingerprint, 20)
- const alertBoxContent = {
- content: isPrimaryToken
- ? [strings.sendAllWarningAlert1({assetNameOrId}), strings.sendAllWarningAlert2, strings.sendAllWarningAlert3]
- : [strings.sendAllWarningAlert1({assetNameOrId})],
- }
- return (
-
- {strings.sendAllWarningText}
-
- )
-}
diff --git a/src/Send/SendScreen/SendScreen.stories.tsx b/src/Send/SendScreen/SendScreen.stories.tsx
deleted file mode 100644
index 869d584eec..0000000000
--- a/src/Send/SendScreen/SendScreen.stories.tsx
+++ /dev/null
@@ -1,30 +0,0 @@
-import {storiesOf} from '@storybook/react-native'
-import * as React from 'react'
-
-import {QueryProvider} from '../../../.storybook/decorators'
-import {Boundary} from '../../components'
-import {SelectedWalletProvider} from '../../SelectedWallet'
-import {YoroiWallet} from '../../yoroi-wallets'
-import {mocks} from '../../yoroi-wallets/mocks'
-import {SendProvider} from '../Context/SendContext'
-import {SendScreen} from './SendScreen'
-
-storiesOf('SendScreen', module)
- .add('Default', () => )
- .add('SendAll', () => )
-
-const SendScreenTest = ({isSendAll}: {isSendAll?: boolean}) => {
- const wallet: YoroiWallet = mocks.wallet
-
- return (
-
-
-
-
-
-
-
-
-
- )
-}
diff --git a/src/Send/SendScreen/SendScreen.tsx b/src/Send/SendScreen/SendScreen.tsx
deleted file mode 100644
index 0793edd592..0000000000
--- a/src/Send/SendScreen/SendScreen.tsx
+++ /dev/null
@@ -1,263 +0,0 @@
-import {useNavigation} from '@react-navigation/native'
-import _ from 'lodash'
-import React from 'react'
-import {useIntl} from 'react-intl'
-import {ActivityIndicator, Image, ScrollView, StyleSheet, View} from 'react-native'
-import {TouchableOpacity} from 'react-native-gesture-handler'
-import {SafeAreaView} from 'react-native-safe-area-context'
-
-import {Button, Checkbox, Spacer, StatusBar, Text, TextInput} from '../../components'
-import {debugWalletInfo, features} from '../../features'
-import {formatTokenAmount, truncateWithEllipsis} from '../../legacy/format'
-import {useSelectedWallet} from '../../SelectedWallet'
-import {COLORS} from '../../theme'
-import {toToken, useBalances, useHasPendingTx, useIsOnline, useTokenInfo, useUtxos} from '../../yoroi-wallets'
-import {YoroiUnsignedTx} from '../../yoroi-wallets/types'
-import {Amounts, Quantities} from '../../yoroi-wallets/utils'
-import type {
- AddressValidationErrors,
- AmountValidationErrors,
- BalanceValidationErrors,
-} from '../../yoroi-wallets/utils/validators'
-import {useSend} from '../Context/SendContext'
-import {maxMemoLength, MemoInput} from '../Memo'
-import {ScannerButton} from '../ScannerButton'
-import {AmountField} from './../AmountField'
-import {AvailableAmountBanner} from './AvailableAmountBanner'
-import {BalanceAfterTransaction} from './BalanceAfterTransaction'
-import {ErrorBanners} from './ErrorBanners'
-import {Fee} from './Fee'
-import {SendAllWarning} from './SendAllWarning'
-import {useStrings} from './strings'
-import {getAddressErrorText, getAmountErrorText, hasDomainErrors, isDomain, recomputeAll} from './utils'
-
-export const SendScreen = () => {
- const intl = useIntl()
- const strings = useStrings()
- const navigation = useNavigation()
- const wallet = useSelectedWallet()
- const balances = useBalances(wallet)
-
- const utxos = useUtxos(wallet)
- const hasPendingTx = useHasPendingTx(wallet)
- const isOnline = useIsOnline(wallet)
-
- const {
- tokenId,
- resetForm,
- receiverChanged,
- amountChanged,
- receiver,
- amount,
- sendAll,
- sendAllChanged,
- memo,
- memoChanged,
- } = useSend()
-
- const selectedAssetAvailableAmount = Amounts.getAmount(balances, tokenId).quantity
- const defaultAssetAvailableAmount = Amounts.getAmount(balances, wallet.primaryToken.identifier).quantity
-
- React.useEffect(() => {
- if (wallet.primaryToken.identifier !== tokenId && !Quantities.isGreaterThan(selectedAssetAvailableAmount, '0')) {
- resetForm()
- }
- }, [wallet.primaryToken.identifier, tokenId, resetForm, selectedAssetAvailableAmount])
-
- const [address, setAddress] = React.useState('')
- const [addressErrors, setAddressErrors] = React.useState({addressIsRequired: true})
- const [amountErrors, setAmountErrors] = React.useState({amountIsRequired: true})
- const [balanceErrors, setBalanceErrors] = React.useState({})
- const [yoroiUnsignedTx, setYoroiUnsignedTx] = React.useState(null)
- const [recomputing, setRecomputing] = React.useState(false)
- const [showSendAllWarning, setShowSendAllWarning] = React.useState(false)
-
- const tokenInfo = useTokenInfo({wallet, tokenId})
- const isPrimaryToken = tokenInfo.id === wallet.primaryTokenInfo.id
- const token = toToken({wallet, tokenInfo})
- const assetDenomination = truncateWithEllipsis(tokenInfo.ticker ?? tokenInfo.name ?? tokenInfo.fingerprint, 20)
- const amountErrorText = getAmountErrorText(intl, amountErrors, balanceErrors, wallet.primaryToken)
-
- const isValid =
- isOnline &&
- !hasPendingTx &&
- _.isEmpty(addressErrors) &&
- _.isEmpty(amountErrors) &&
- _.isEmpty(balanceErrors) &&
- !!yoroiUnsignedTx &&
- memo.length <= maxMemoLength
-
- React.useEffect(() => {
- if (features.prefillWalletInfo) {
- if (!__DEV__) throw new Error('using debug data in non-dev env')
- receiverChanged(debugWalletInfo.SEND_ADDRESS)
- amountChanged(debugWalletInfo.SEND_AMOUNT)
- }
- // eslint-disable-next-line react-hooks/exhaustive-deps
- }, [])
-
- const promiseRef = React.useRef>()
- React.useEffect(() => {
- setYoroiUnsignedTx(null)
- setBalanceErrors({})
- setAmountErrors({})
- setRecomputing(true)
-
- const promise = recomputeAll({
- wallet,
- utxos,
- addressInput: receiver,
- amount,
- sendAll,
- selectedToken: token,
- defaultAssetAvailableAmount,
- selectedAssetAvailableAmount,
- })
-
- promiseRef.current = promise
-
- promise.then((newState) => {
- if (promise !== promiseRef.current) return // abort if newer promise
-
- setAddress(newState.address)
- setAddressErrors(newState.addressErrors)
- amountChanged(newState.amount)
- setAmountErrors(newState.amountErrors)
- setBalanceErrors(newState.balanceErrors)
- setYoroiUnsignedTx(newState.yoroiUnsignedTx)
- setRecomputing(false)
- })
- // eslint-disable-next-line react-hooks/exhaustive-deps
- }, [receiver, amount, tokenId, sendAll])
-
- const onConfirm = () => {
- if (sendAll) {
- setShowSendAllWarning(true)
- return
- }
- handleConfirm()
- }
-
- const handleConfirm = () => {
- if (isValid == false || recomputing || yoroiUnsignedTx == null) return
-
- setShowSendAllWarning(false)
-
- navigation.navigate('app-root', {
- screen: 'main-wallet-routes',
- params: {
- screen: 'history',
- params: {
- screen: 'send-confirm',
- params: {yoroiUnsignedTx},
- },
- },
- })
- }
-
- return (
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- }
- multiline
- errorOnMount
- onChangeText={receiverChanged}
- label={strings.addressInputLabel}
- errorText={getAddressErrorText(intl, addressErrors)}
- autoComplete={false}
- testID="addressInput"
- />
-
- {!recomputing &&
- isDomain(receiver) &&
- !hasDomainErrors(addressErrors) &&
- !receiver.includes(address) /* HACK */ && (
-
- {`Resolves to: ${address}`}
-
- )}
-
-
-
- {
- navigation.navigate('app-root', {
- screen: 'main-wallet-routes',
- params: {
- screen: 'history',
- params: {
- screen: 'select-asset',
- },
- },
- })
- }}
- >
- }
- editable={false}
- label={strings.asset}
- value={`${assetDenomination}: ${formatTokenAmount(selectedAssetAvailableAmount, token)}`}
- autoComplete={false}
- />
-
-
-
-
-
-
- {recomputing && (
-
-
-
- )}
-
-
-
-
-
-
- setShowSendAllWarning(false)}
- selectedTokenIdentifier={tokenId}
- onConfirm={handleConfirm}
- />
-
- )
-}
-
-const styles = StyleSheet.create({
- container: {
- flex: 1,
- backgroundColor: COLORS.WHITE,
- },
- content: {
- padding: 16,
- },
- actions: {
- paddingHorizontal: 16,
- paddingBottom: 16,
- },
- indicator: {
- marginTop: 26,
- },
-})
diff --git a/src/Send/SendScreen/index.ts b/src/Send/SendScreen/index.ts
deleted file mode 100644
index d47c63916c..0000000000
--- a/src/Send/SendScreen/index.ts
+++ /dev/null
@@ -1 +0,0 @@
-export * from './SendScreen'
diff --git a/src/Send/AmountField.tsx b/src/Send/shared/AmountField.tsx
similarity index 90%
rename from src/Send/AmountField.tsx
rename to src/Send/shared/AmountField.tsx
index 583f5a371a..8055054b91 100644
--- a/src/Send/AmountField.tsx
+++ b/src/Send/shared/AmountField.tsx
@@ -1,8 +1,8 @@
import React from 'react'
import {defineMessages, useIntl} from 'react-intl'
-import {TextInput} from '../components'
-import {editedFormatter, pastedFormatter} from '../yoroi-wallets/utils/amountUtils'
+import {TextInput} from '../../components'
+import {editedFormatter, pastedFormatter} from '../../yoroi-wallets/utils/amountUtils'
type Props = {
amount: string
diff --git a/src/Send/ScannerButton.tsx b/src/Send/shared/ScannerButton.tsx
similarity index 56%
rename from src/Send/ScannerButton.tsx
rename to src/Send/shared/ScannerButton.tsx
index 1de226b35d..a68590fee2 100644
--- a/src/Send/ScannerButton.tsx
+++ b/src/Send/shared/ScannerButton.tsx
@@ -2,14 +2,20 @@ import {useNavigation} from '@react-navigation/native'
import React from 'react'
import {TouchableOpacity} from 'react-native'
-import {Icon} from '../components'
+import {Icon} from '../../components'
+import {COLORS} from '../../theme'
-export const ScannerButton = () => {
+type Props = {
+ disabled?: boolean
+}
+export const ScannerButton = ({disabled}: Props) => {
const navigateTo = useNavigateTo()
+ const color = disabled ? COLORS.TEXT_INPUT : 'black'
+
return (
-
-
+
+
)
}
@@ -24,7 +30,7 @@ const useNavigateTo = () => {
params: {
screen: 'history',
params: {
- screen: 'address-reader-qr',
+ screen: 'send-read-qr-code',
},
},
}),
diff --git a/src/Send/shared/SendContext.tsx b/src/Send/shared/SendContext.tsx
new file mode 100644
index 0000000000..5aef87a009
--- /dev/null
+++ b/src/Send/shared/SendContext.tsx
@@ -0,0 +1,213 @@
+import * as React from 'react'
+
+import {Address, Quantity, TokenId, YoroiEntry} from '../../yoroi-wallets/types'
+
+export type YoroiTarget = {
+ receiver: string
+ entry: YoroiEntry
+}
+
+type SendState = {
+ selectedTargetIndex: number
+ selectedTokenId: TokenId
+
+ memo: string
+
+ targets: Array
+}
+
+type TargetActions = {
+ amountChanged: (quantity: Quantity) => void
+ amountRemoved: (tokenId: TokenId) => void
+ receiverChanged: (receiver: string) => void
+ addressChanged: (address: Address) => void
+}
+
+type SendActions = {
+ tokenSelectedChanged: (tokenId: TokenId) => void
+ resetForm: () => void
+ memoChanged: (memo: string) => void
+}
+
+const combinedReducers = (state: SendState, action: SendAction | TargetAction) => {
+ return {
+ ...sendReducer(state, action as SendAction),
+ targets: targetsReducer(state, action as TargetAction),
+ }
+}
+
+const SendContext = React.createContext(undefined)
+export const SendProvider = ({children, ...props}: {initialState?: Partial; children: React.ReactNode}) => {
+ const [state, dispatch] = React.useReducer(combinedReducers, {
+ ...initialState,
+ ...props.initialState,
+ })
+
+ const actions = React.useRef({
+ resetForm: () => dispatch({type: 'resetForm'}),
+
+ receiverChanged: (receiver: string) => dispatch({type: 'receiverChanged', receiver}),
+ addressChanged: (address: Address) => dispatch({type: 'addressChanged', address}),
+
+ memoChanged: (memo) => dispatch({type: 'memoChanged', memo}),
+
+ tokenSelectedChanged: (tokenId: TokenId) => dispatch({type: 'tokenSelectedChanged', tokenId}),
+ amountChanged: (quantity: Quantity) => dispatch({type: 'amountChanged', quantity}),
+ amountRemoved: (tokenId: TokenId) => dispatch({type: 'amountRemoved', tokenId}),
+ }).current
+
+ const context = React.useMemo(() => ({...state, ...actions}), [actions, state])
+
+ return {children}
+}
+
+type SendAction =
+ | {
+ type: 'resetForm'
+ }
+ | {
+ type: 'memoChanged'
+ memo: SendState['memo']
+ }
+ | {
+ type: 'tokenSelectedChanged'
+ tokenId: TokenId
+ }
+
+const sendReducer = (state: SendState, action: SendAction) => {
+ switch (action.type) {
+ case 'resetForm':
+ return initialState
+
+ case 'memoChanged':
+ return {
+ ...state,
+ memo: action.memo,
+ }
+
+ case 'tokenSelectedChanged':
+ return {
+ ...state,
+ selectedTokenId: action.tokenId,
+ }
+
+ default:
+ return state
+ }
+}
+
+type TargetAction =
+ | {
+ type: 'receiverChanged'
+ receiver: string
+ }
+ | {
+ type: 'addressChanged'
+ address: Address
+ }
+ | {
+ type: 'tokenSelectedChanged'
+ tokenId: TokenId
+ }
+ | {
+ type: 'amountChanged'
+ quantity: Quantity
+ }
+ | {
+ type: 'amountRemoved'
+ tokenId: TokenId
+ }
+
+const targetsReducer = (state: SendState, action: TargetAction) => {
+ switch (action.type) {
+ case 'receiverChanged': {
+ const {receiver} = action
+ const selectedTargetIndex = state.selectedTargetIndex
+ const updatedTargets = state.targets.map((target, index) => {
+ if (index === selectedTargetIndex) {
+ return {...target, receiver}
+ }
+
+ return {...target}
+ })
+
+ return updatedTargets
+ }
+
+ case 'addressChanged': {
+ const {address} = action
+ const selectedTargetIndex = state.selectedTargetIndex
+ const updatedTargets = state.targets.map((target, index) => {
+ if (index === selectedTargetIndex) {
+ return {...target, entry: {...target.entry, address}}
+ }
+
+ return {...target}
+ })
+
+ return updatedTargets
+ }
+
+ case 'amountChanged': {
+ const {quantity} = action
+ const selectedTargetIndex = state.selectedTargetIndex
+ const selectedTokenId = state.selectedTokenId
+ const updatedTargets = state.targets.map((target, index) => {
+ if (index === selectedTargetIndex) {
+ return {...target, entry: {...target.entry, amounts: {...target.entry.amounts, [selectedTokenId]: quantity}}}
+ }
+
+ return {...target}
+ })
+
+ return updatedTargets
+ }
+
+ case 'amountRemoved': {
+ const {tokenId} = action
+ const selectedTargetIndex = state.selectedTargetIndex
+ const updatedTargets = state.targets.map((target, index) => {
+ if (index === selectedTargetIndex) {
+ const amounts = Object.keys(target.entry.amounts).reduce((acc, key) => {
+ if (key !== tokenId) {
+ acc[key] = target.entry.amounts[key]
+ }
+
+ return acc
+ }, {})
+ return {...target, entry: {...target.entry, amounts}}
+ }
+
+ return {...target}
+ })
+
+ return updatedTargets
+ }
+
+ default:
+ return state.targets
+ }
+}
+
+export const useSend = () => React.useContext(SendContext) || missingProvider()
+
+const missingProvider = () => {
+ throw new Error('SendProvider is missing')
+}
+
+const initialState: SendState = {
+ selectedTargetIndex: 0,
+ selectedTokenId: '',
+
+ memo: '',
+
+ targets: [
+ {
+ receiver: '',
+ entry: {
+ address: '',
+ amounts: {},
+ },
+ },
+ ],
+}
diff --git a/src/Send/shared/hooks.ts b/src/Send/shared/hooks.ts
new file mode 100644
index 0000000000..fa31a4c9dc
--- /dev/null
+++ b/src/Send/shared/hooks.ts
@@ -0,0 +1,47 @@
+import {useSelectedWallet} from '../../SelectedWallet'
+import {useBalance, useLockedAmount} from '../../yoroi-wallets/hooks'
+import {Quantity, TokenId} from '../../yoroi-wallets/types'
+import {Quantities} from '../../yoroi-wallets/utils'
+import {useSend, YoroiTarget} from './SendContext'
+
+export const useTokenQuantities = (tokenId: TokenId) => {
+ const wallet = useSelectedWallet()
+ const isPrimary = tokenId === wallet.primaryTokenInfo.id
+ const {targets, selectedTargetIndex} = useSend()
+ const balance = useBalance({wallet, tokenId})
+ const used = getTotalUsedByOtherTargets({targets, selectedTokenId: tokenId, selectedTargetIndex})
+ const available = Quantities.diff(balance, used)
+ const initialQuantity = targets[selectedTargetIndex].entry.amounts[tokenId] ?? ('0' as Quantity)
+ const primaryLocked = useLockedAmount({wallet})
+ const locked = isPrimary ? primaryLocked : ('0' as Quantity)
+ const spendable = Quantities.diff(available, locked)
+
+ return {
+ balance,
+ used,
+ available,
+ initialQuantity,
+ locked,
+ spendable,
+ }
+}
+
+/**
+ * @summary Returns the total amount of tokens used by other targets
+ * @returns Quantity
+ */
+const getTotalUsedByOtherTargets = ({
+ targets,
+ selectedTargetIndex,
+ selectedTokenId,
+}: {
+ targets: Array
+ selectedTargetIndex: number
+ selectedTokenId: string
+}) => {
+ return targets.reduce((acc, target, index) => {
+ if (index === selectedTargetIndex) return acc
+ const quantity = target.entry.amounts[selectedTokenId] ?? ('0' as Quantity)
+ return Quantities.sum([acc, quantity])
+ }, '0' as Quantity)
+}
diff --git a/src/Send/SendScreen/strings.ts b/src/Send/shared/strings.ts
similarity index 86%
rename from src/Send/SendScreen/strings.ts
rename to src/Send/shared/strings.ts
index 9bfd8ead34..866ecd5114 100644
--- a/src/Send/SendScreen/strings.ts
+++ b/src/Send/shared/strings.ts
@@ -6,27 +6,38 @@ export const useStrings = () => {
const intl = useIntl()
return {
- balanceAfterNotAvailable: intl.formatMessage(messages.balanceAfterNotAvailable),
- balanceAfterLabel: intl.formatMessage(messages.balanceAfterLabel),
- feeNotAvailable: intl.formatMessage(messages.feeNotAvailable),
- feeLabel: intl.formatMessage(messages.feeLabel),
+ addressInputErrorInvalidAddress: intl.formatMessage(messages.addressInputErrorInvalidAddress),
addressInputLabel: intl.formatMessage(messages.addressInputLabel),
+ apply: intl.formatMessage(globalMessages.apply),
asset: intl.formatMessage(messages.asset),
- checkboxSendAllAssets: intl.formatMessage(messages.checkboxSendAllAssets),
+ availableFunds: intl.formatMessage(globalMessages.availableFunds),
+ availableFundsBannerIsFetching: intl.formatMessage(messages.availableFundsBannerIsFetching),
+ availableFundsBannerNotAvailable: intl.formatMessage(messages.availableFundsBannerNotAvailable),
+ backButton: intl.formatMessage(confirmationMessages.commonButtons.backButton),
+ balanceAfterLabel: intl.formatMessage(messages.balanceAfterLabel),
+ balanceAfterNotAvailable: intl.formatMessage(messages.balanceAfterNotAvailable),
checkboxSendAll: (options) => intl.formatMessage(messages.checkboxSendAll, options),
+ checkboxSendAllAssets: intl.formatMessage(messages.checkboxSendAllAssets),
continueButton: intl.formatMessage(messages.continueButton),
+ domainNotRegisteredError: intl.formatMessage(messages.domainNotRegisteredError),
+ domainRecordNotFoundError: intl.formatMessage(messages.domainRecordNotFoundError),
+ domainUnsupportedError: intl.formatMessage(messages.domainUnsupportedError),
errorBannerNetworkError: intl.formatMessage(messages.errorBannerNetworkError),
errorBannerPendingOutgoingTransaction: intl.formatMessage(messages.errorBannerPendingOutgoingTransaction),
- availableFunds: intl.formatMessage(globalMessages.availableFunds),
- availableFundsBannerIsFetching: intl.formatMessage(messages.availableFundsBannerIsFetching),
- availableFundsBannerNotAvailable: intl.formatMessage(messages.availableFundsBannerNotAvailable),
+ feeLabel: intl.formatMessage(messages.feeLabel),
+ feeNotAvailable: intl.formatMessage(messages.feeNotAvailable),
+ insuficientBalance: intl.formatMessage(amountInputErrorMessages.insufficientBalance),
+ max: intl.formatMessage(globalMessages.max),
+ minPrimaryBalanceForTokens: intl.formatMessage(amountInputErrorMessages.minPrimaryBalanceForTokens),
+ next: intl.formatMessage(globalMessages.next),
+ pleaseWait: intl.formatMessage(globalMessages.pleaseWait),
+ resolvesTo: intl.formatMessage(messages.resolvesTo),
+ sendAllContinueButton: intl.formatMessage(confirmationMessages.commonButtons.continueButton),
sendAllWarningAlert1: (options) => intl.formatMessage(messages.sendAllWarningAlert1, options),
sendAllWarningAlert2: intl.formatMessage(messages.sendAllWarningAlert2),
sendAllWarningAlert3: intl.formatMessage(messages.sendAllWarningAlert3),
- sendAllWarningTitle: intl.formatMessage(messages.sendAllWarningTitle),
- backButton: intl.formatMessage(confirmationMessages.commonButtons.backButton),
- sendAllContinueButton: intl.formatMessage(confirmationMessages.commonButtons.continueButton),
sendAllWarningText: intl.formatMessage(messages.sendAllWarningText),
+ sendAllWarningTitle: intl.formatMessage(messages.sendAllWarningTitle),
}
}
@@ -63,6 +74,10 @@ export const amountInputErrorMessages = defineMessages({
id: 'components.send.sendscreen.amountInput.error.assetOverflow',
defaultMessage: '!!!!Maximum value of a token inside a UTXO exceeded (overflow).',
},
+ minPrimaryBalanceForTokens: {
+ id: 'global.info.minPrimaryBalanceForTokens',
+ defaultMessage: '!!!Keep some balance for tokens',
+ },
})
export const messages = defineMessages({
@@ -121,6 +136,10 @@ export const messages = defineMessages({
defaultMessage: '!!!Domain is not supported',
description: 'some desc',
},
+ resolvesTo: {
+ id: 'components.send.sendscreen.resolvesTo',
+ defaultMessage: '!!!Resolves to',
+ },
sendAllWarningTitle: {
id: 'components.send.sendscreen.sendAllWarningTitle',
defaultMessage: '!!!Do you really want to send all?',
diff --git a/src/Send/SendScreen/utils.ts b/src/Send/shared/utils.ts
similarity index 76%
rename from src/Send/SendScreen/utils.ts
rename to src/Send/shared/utils.ts
index 2c7c256118..f950565aa7 100644
--- a/src/Send/SendScreen/utils.ts
+++ b/src/Send/shared/utils.ts
@@ -10,9 +10,8 @@ import {DefaultAsset, Quantity, SendTokenList, Token, YoroiUnsignedTx} from '../
import {RawUtxo} from '../../yoroi-wallets/types/other'
import {Amounts, asQuantity, Quantities} from '../../yoroi-wallets/utils'
import {InvalidAssetAmount, parseAmountDecimal} from '../../yoroi-wallets/utils/parsing'
-import type {AddressValidationErrors} from '../../yoroi-wallets/utils/validators'
-import {getUnstoppableDomainAddress, isReceiverAddressValid, validateAmount} from '../../yoroi-wallets/utils/validators'
-import {amountInputErrorMessages, messages} from './strings'
+import {validateAmount} from '../../yoroi-wallets/utils/validators'
+import {amountInputErrorMessages} from './strings'
export const getTransactionData = async (
wallet: YoroiWallet,
@@ -63,22 +62,8 @@ export const recomputeAll = async ({
defaultAssetAvailableAmount: Quantity
selectedAssetAvailableAmount: Quantity
}) => {
- let addressErrors: AddressValidationErrors = {}
- let address = addressInput
let amountErrors = validateAmount(amount, selectedToken)
- if (isDomain(addressInput)) {
- try {
- address = await getUnstoppableDomainAddress(addressInput)
- } catch (e) {
- addressErrors = JSON.parse((e as Error).message)
- }
- }
-
- if (_.isEmpty(addressErrors)) {
- addressErrors = (await isReceiverAddressValid(address, wallet.networkId)) || Object.freeze({})
- }
-
let balanceErrors = Object.freeze({})
let fee: Quantity | null = null
let balanceAfter: null | Quantity = null
@@ -86,7 +71,7 @@ export const recomputeAll = async ({
let yoroiUnsignedTx: YoroiUnsignedTx | null = null
- if (_.isEmpty(addressErrors) && utxos) {
+ if (utxos) {
try {
// we'll substract minAda from ADA balance if we are sending a token
const minAda =
@@ -95,7 +80,7 @@ export const recomputeAll = async ({
: '0'
if (sendAll) {
- yoroiUnsignedTx = await getTransactionData(wallet, address, amount, sendAll, selectedToken)
+ yoroiUnsignedTx = await getTransactionData(wallet, addressInput, amount, sendAll, selectedToken)
fee = Amounts.getAmount(yoroiUnsignedTx.fee, wallet.primaryToken.identifier).quantity
@@ -119,7 +104,7 @@ export const recomputeAll = async ({
? (parseAmountDecimal(amount, selectedToken).toString() as Quantity)
: '0'
- yoroiUnsignedTx = await getTransactionData(wallet, address, amount, false, selectedToken)
+ yoroiUnsignedTx = await getTransactionData(wallet, addressInput, amount, false, selectedToken)
fee = Amounts.getAmount(yoroiUnsignedTx.fee, wallet.primaryToken.identifier).quantity
balanceAfter = Quantities.diff(defaultAssetAvailableAmount, Quantities.sum([parsedAmount, minAda, fee]))
@@ -136,10 +121,8 @@ export const recomputeAll = async ({
}
return {
- address,
amount: recomputedAmount,
amountErrors,
- addressErrors,
balanceErrors,
fee,
balanceAfter,
@@ -147,22 +130,6 @@ export const recomputeAll = async ({
}
}
-export const getAddressErrorText = (intl: IntlShape, addressErrors: AddressValidationErrors) => {
- if (addressErrors.unsupportedDomain) {
- return intl.formatMessage(messages.domainUnsupportedError)
- }
- if (addressErrors.recordNotFound) {
- return intl.formatMessage(messages.domainRecordNotFoundError)
- }
- if (addressErrors.unregisteredDomain) {
- return intl.formatMessage(messages.domainNotRegisteredError)
- }
- if (addressErrors.invalidAddress === true) {
- return intl.formatMessage(messages.addressInputErrorInvalidAddress)
- }
- return ''
-}
-
export const getAmountErrorText = (
intl: IntlShape,
amountErrors: {invalidAmount?: string | number | null},
@@ -193,7 +160,3 @@ export const getAmountErrorText = (
return null
}
-
-export const isDomain = (addressInput: string) => /.+\..+/.test(addressInput)
-export const hasDomainErrors = (addressErrors: AddressValidationErrors) =>
- addressErrors.unsupportedDomain || addressErrors.recordNotFound || addressErrors.unregisteredDomain
diff --git a/src/Send/ConfirmScreen/ConfirmScreen.stories.tsx b/src/Send/useCases/ConfirmTx/ConfirmTxScreen.stories.tsx
similarity index 58%
rename from src/Send/ConfirmScreen/ConfirmScreen.stories.tsx
rename to src/Send/useCases/ConfirmTx/ConfirmTxScreen.stories.tsx
index 22d8d1faf1..36c000ed3c 100644
--- a/src/Send/ConfirmScreen/ConfirmScreen.stories.tsx
+++ b/src/Send/useCases/ConfirmTx/ConfirmTxScreen.stories.tsx
@@ -2,12 +2,12 @@ import {NavigationRouteContext} from '@react-navigation/native'
import {storiesOf} from '@storybook/react-native'
import React from 'react'
-import {SelectedWalletProvider} from '../../SelectedWallet'
-import {mocks} from '../../yoroi-wallets/mocks'
-import {SendProvider} from '../Context/SendContext'
-import {ConfirmScreen} from './ConfirmScreen'
+import {SelectedWalletProvider} from '../../../SelectedWallet'
+import {mocks} from '../../../yoroi-wallets/mocks'
+import {SendProvider} from '../../shared/SendContext'
+import {ConfirmTxScreen} from './ConfirmTxScreen'
-storiesOf('ConfirmScreen', module).add('Default', () => {
+storiesOf('Send/ConfirmTx', module).add('Default', () => {
const route = {
key: 'key',
name: 'name',
@@ -23,8 +23,8 @@ storiesOf('ConfirmScreen', module).add('Default', () => {
return (
-
-
+
+
diff --git a/src/Send/ConfirmScreen/ConfirmScreen.tsx b/src/Send/useCases/ConfirmTx/ConfirmTxScreen.tsx
similarity index 86%
rename from src/Send/ConfirmScreen/ConfirmScreen.tsx
rename to src/Send/useCases/ConfirmTx/ConfirmTxScreen.tsx
index 4c2eca16ee..bcbb7233af 100644
--- a/src/Send/ConfirmScreen/ConfirmScreen.tsx
+++ b/src/Send/useCases/ConfirmTx/ConfirmTxScreen.tsx
@@ -4,28 +4,28 @@ import React, {useEffect, useRef} from 'react'
import {useIntl} from 'react-intl'
import {Keyboard, ScrollView, StyleSheet, View, ViewProps} from 'react-native'
-import {Boundary, KeyboardSpacer, Spacer, StatusBar, Text, ValidatedTextInput} from '../../components'
-import {ConfirmTx} from '../../components/ConfirmTx'
-import {debugWalletInfo, features} from '../../features'
-import globalMessages, {confirmationMessages, errorMessages, txLabels} from '../../i18n/global-messages'
-import {formatTokenWithSymbol, formatTokenWithText} from '../../legacy/format'
-import {TxHistoryRoutes, useWalletNavigation} from '../../navigation'
-import {useSelectedWallet} from '../../SelectedWallet'
-import {COLORS} from '../../theme'
-import {useBalances, useSaveMemo, useToken} from '../../yoroi-wallets'
-import {YoroiAmount, YoroiUnsignedTx} from '../../yoroi-wallets/types'
-import {Amounts, Quantities} from '../../yoroi-wallets/utils'
-import {useSend} from '../Context/SendContext'
-import {AvailableAmountBanner} from '../SendScreen/AvailableAmountBanner'
-
-export const ConfirmScreen = () => {
+import {Boundary, KeyboardSpacer, Spacer, StatusBar, Text, ValidatedTextInput} from '../../../components'
+import {ConfirmTx} from '../../../components/ConfirmTx'
+import {debugWalletInfo, features} from '../../../features'
+import globalMessages, {confirmationMessages, errorMessages, txLabels} from '../../../i18n/global-messages'
+import {formatTokenWithSymbol, formatTokenWithText} from '../../../legacy/format'
+import {TxHistoryRoutes, useWalletNavigation} from '../../../navigation'
+import {useSelectedWallet} from '../../../SelectedWallet'
+import {COLORS} from '../../../theme'
+import {useBalances, useSaveMemo, useToken} from '../../../yoroi-wallets/hooks'
+import {YoroiAmount, YoroiUnsignedTx} from '../../../yoroi-wallets/types'
+import {Amounts, Quantities} from '../../../yoroi-wallets/utils'
+import {useSend} from '../../shared/SendContext'
+// import {AvailableAmountBanner} from '../SendScreen/AvailableAmountBanner'
+
+export const ConfirmTxScreen = () => {
const strings = useStrings()
- const {yoroiUnsignedTx} = useRoute>().params
+ const {yoroiUnsignedTx} = useRoute>().params
const {resetToTxHistory} = useWalletNavigation()
const wallet = useSelectedWallet()
const [password, setPassword] = React.useState('')
const [useUSB, setUseUSB] = React.useState(false)
- const {memo, resetForm, receiver} = useSend()
+ const {memo, resetForm, targets} = useSend()
const {saveMemo} = useSaveMemo({wallet})
useEffect(() => {
@@ -49,7 +49,7 @@ export const ConfirmScreen = () => {
-
+ {/* */}
@@ -61,7 +61,7 @@ export const ConfirmScreen = () => {
-
+
@@ -145,11 +145,11 @@ const Receiver = ({receiver}: {receiver: string}) => {
const strings = useStrings()
return (
- <>
+
{strings.receiver}
{receiver}
- >
+
)
}
@@ -159,13 +159,13 @@ const PrimaryTotal = ({yoroiUnsignedTx}: {yoroiUnsignedTx: YoroiUnsignedTx}) =>
const primaryAmount = Amounts.getAmount(yoroiUnsignedTx.amounts, wallet.primaryToken.identifier)
return (
- <>
+
{strings.total}
{formatTokenWithSymbol(primaryAmount.quantity, wallet.primaryToken)}
- >
+
)
}
diff --git a/src/Send/useCases/ConfirmTx/index.ts b/src/Send/useCases/ConfirmTx/index.ts
new file mode 100644
index 0000000000..8e77dd2083
--- /dev/null
+++ b/src/Send/useCases/ConfirmTx/index.ts
@@ -0,0 +1 @@
+export * from './ConfirmTxScreen'
diff --git a/src/Send/useCases/ListSelectedTokens/AddToken/AddToken.stories.tsx b/src/Send/useCases/ListSelectedTokens/AddToken/AddToken.stories.tsx
new file mode 100644
index 0000000000..caeba97089
--- /dev/null
+++ b/src/Send/useCases/ListSelectedTokens/AddToken/AddToken.stories.tsx
@@ -0,0 +1,12 @@
+import {action} from '@storybook/addon-actions'
+import {storiesOf} from '@storybook/react-native'
+import React from 'react'
+import {View} from 'react-native'
+
+import {AddToken} from './AddToken'
+
+storiesOf('Send/ListSelectedTokens/AddToken', module).add('Default', () => (
+
+ action(`onPress address`)} />
+
+))
diff --git a/src/Send/useCases/ListSelectedTokens/AddToken/AddToken.tsx b/src/Send/useCases/ListSelectedTokens/AddToken/AddToken.tsx
new file mode 100644
index 0000000000..312f2aa89d
--- /dev/null
+++ b/src/Send/useCases/ListSelectedTokens/AddToken/AddToken.tsx
@@ -0,0 +1,57 @@
+import * as React from 'react'
+import {defineMessages, useIntl} from 'react-intl'
+import {StyleProp, StyleSheet, Text, TouchableOpacity, ViewStyle} from 'react-native'
+
+import {Icon, Spacer} from '../../../../components'
+import {COLORS} from '../../../../theme'
+
+type AddTokenProps = {
+ onPress(): void
+ style?: StyleProp
+}
+export const AddToken = ({onPress, style}: AddTokenProps) => {
+ const strings = useStrings()
+
+ return (
+
+
+
+
+
+ {strings.addToken.toLocaleUpperCase()}
+
+ )
+}
+
+const styles = StyleSheet.create({
+ label: {
+ color: COLORS.SHELLEY_BLUE,
+ fontFamily: 'Rubik-Medium',
+ fontWeight: '500',
+ },
+ button: {
+ borderRadius: 8,
+ flexDirection: 'row',
+ paddingHorizontal: 16,
+ paddingVertical: 10,
+ justifyContent: 'center',
+ alignItems: 'center',
+ borderColor: COLORS.SHELLEY_BLUE,
+ borderWidth: 2,
+ },
+})
+
+const messages = defineMessages({
+ addToken: {
+ id: 'components.send.addToken',
+ defaultMessage: '!!!Add token',
+ },
+})
+
+const useStrings = () => {
+ const intl = useIntl()
+
+ return {
+ addToken: intl.formatMessage(messages.addToken),
+ }
+}
diff --git a/src/Send/useCases/ListSelectedTokens/AddToken/InputSearch.tsx b/src/Send/useCases/ListSelectedTokens/AddToken/InputSearch.tsx
new file mode 100644
index 0000000000..8cd7d07bbd
--- /dev/null
+++ b/src/Send/useCases/ListSelectedTokens/AddToken/InputSearch.tsx
@@ -0,0 +1,25 @@
+import * as React from 'react'
+import {defineMessages, useIntl} from 'react-intl'
+
+import {TextInput, TextInputProps} from '../../../../components'
+
+export const InputSearch = (props: TextInputProps) => {
+ const strings = useStrings()
+
+ return
+}
+
+const useStrings = () => {
+ const intl = useIntl()
+
+ return {
+ searchLabel: intl.formatMessage(messages.searchLabel),
+ }
+}
+
+const messages = defineMessages({
+ searchLabel: {
+ id: 'components.send.assetselectorscreen.searchlabel',
+ defaultMessage: '!!!Search by name or subject',
+ },
+})
diff --git a/src/Send/useCases/ListSelectedTokens/AddToken/SelectTokenFromListScreen.stories.tsx b/src/Send/useCases/ListSelectedTokens/AddToken/SelectTokenFromListScreen.stories.tsx
new file mode 100644
index 0000000000..5d15a74895
--- /dev/null
+++ b/src/Send/useCases/ListSelectedTokens/AddToken/SelectTokenFromListScreen.stories.tsx
@@ -0,0 +1,17 @@
+import {storiesOf} from '@storybook/react-native'
+import React from 'react'
+
+import {SelectedWalletProvider} from '../../../../SelectedWallet'
+import {mocks} from '../../../../yoroi-wallets/mocks/wallet'
+import {SendProvider} from '../../../shared/SendContext'
+import {SelectTokenFromListScreen} from './SelectTokenScreen'
+
+storiesOf('Send/SelectTokenFromList', module).add('Default', () => {
+ return (
+
+
+
+
+
+ )
+})
diff --git a/src/Send/AssetSelectorScreen/AssetSelectorScreen.tsx b/src/Send/useCases/ListSelectedTokens/AddToken/SelectTokenScreen.tsx
similarity index 56%
rename from src/Send/AssetSelectorScreen/AssetSelectorScreen.tsx
rename to src/Send/useCases/ListSelectedTokens/AddToken/SelectTokenScreen.tsx
index f5511ee7ae..dd8a095c66 100644
--- a/src/Send/AssetSelectorScreen/AssetSelectorScreen.tsx
+++ b/src/Send/useCases/ListSelectedTokens/AddToken/SelectTokenScreen.tsx
@@ -5,25 +5,27 @@ import {defineMessages, useIntl} from 'react-intl'
import {LayoutAnimation, TouchableOpacity, View} from 'react-native'
import {SafeAreaView} from 'react-native-safe-area-context'
-import {Boundary, Button, Spacer, Text, TextInput} from '../../components'
-import {AssetItem, AssetItemProps} from '../../components/AssetItem'
-import globalMessages, {txLabels} from '../../i18n/global-messages'
-import {TxHistoryRouteNavigation} from '../../navigation'
-import {useSelectedWallet} from '../../SelectedWallet'
-import {COLORS} from '../../theme'
-import {sortTokenInfos} from '../../utils'
-import {useBalances, useTokenInfos} from '../../yoroi-wallets'
-import {TokenInfo} from '../../yoroi-wallets/types'
-import {Amounts} from '../../yoroi-wallets/utils'
-import {useSend} from '../Context/SendContext'
-
-export const AssetSelectorScreen = () => {
+import {Boundary, Spacer, Text} from '../../../../components'
+import {AssetItem, AssetItemProps} from '../../../../components/AssetItem'
+import globalMessages, {txLabels} from '../../../../i18n/global-messages'
+import {TxHistoryRouteNavigation} from '../../../../navigation'
+import {useSelectedWallet} from '../../../../SelectedWallet'
+import {COLORS} from '../../../../theme'
+import {sortTokenInfos} from '../../../../utils'
+import {useBalances, useTokenInfos} from '../../../../yoroi-wallets/hooks'
+import {TokenInfo} from '../../../../yoroi-wallets/types'
+import {Amounts, Quantities} from '../../../../yoroi-wallets/utils'
+import {useTokenQuantities} from '../../../shared/hooks'
+import {useSend} from '../../../shared/SendContext'
+import {InputSearch} from './InputSearch'
+
+export const SelectTokenFromListScreen = () => {
const strings = useStrings()
const wallet = useSelectedWallet()
+
const balances = useBalances(wallet)
const [matcher, setMatcher] = React.useState('')
- const navigation = useNavigation()
- const {tokenSelected, allTokensSelected} = useSend()
+
const tokenInfos = useTokenInfos({
wallet,
tokenIds: Amounts.toArray(balances).map(({tokenId}) => tokenId),
@@ -38,7 +40,7 @@ export const AssetSelectorScreen = () => {
return (
- onChangeMatcher(text)} />
+ onChangeMatcher(text)} autoComplete />
{strings.assetsLabel}
@@ -55,14 +57,7 @@ export const AssetSelectorScreen = () => {
data={assets}
renderItem={({item: tokenInfo}: {item: TokenInfo}) => (
- {
- tokenSelected(tokenInfo.id)
- navigation.navigate('send')
- }}
- balance={balances[tokenInfo.id]}
- />
+
)}
bounces={false}
@@ -71,43 +66,37 @@ export const AssetSelectorScreen = () => {
testID="assetsList"
estimatedItemSize={78}
/>
-
-
-
)
}
-type SelectableAssetItemProps = AssetItemProps & {
- onSelect(): void
-}
-const SelectableAssetItem = ({tokenInfo, balance, onSelect}: SelectableAssetItemProps) => {
+type SelectableAssetItemProps = Omit
+const SelectableAssetItem = ({tokenInfo}: SelectableAssetItemProps) => {
+ const {tokenSelectedChanged, amountChanged} = useSend()
+ const {spendable} = useTokenQuantities(tokenInfo.id)
+ const navigation = useNavigation()
+
+ const onSelect = () => {
+ tokenSelectedChanged(tokenInfo.id)
+
+ // if the token is indivisible, we don't need to ask for the amount
+ if (Quantities.isIndivisible(spendable, tokenInfo.decimals)) {
+ amountChanged(spendable)
+ navigation.navigate('send-selected-tokens')
+ } else {
+ navigation.navigate('send-edit-token-amount')
+ }
+ }
+
return (
-
+
)
}
const HR = (props) =>
-const Actions = (props) =>
-
-const SearchInput = (props) => {
- const strings = useStrings()
-
- return
-}
-
const matches = (tokenInfo: TokenInfo, matcher: string) =>
Object.values(tokenInfo)
.filter((value): value is string | number => value != null)
@@ -119,8 +108,6 @@ const useStrings = () => {
const intl = useIntl()
return {
- searchLabel: intl.formatMessage(messages.searchLabel),
- sendAllAssets: intl.formatMessage(messages.sendAllAssets),
unknownAsset: intl.formatMessage(messages.unknownAsset),
assetsLabel: intl.formatMessage(globalMessages.assetsLabel),
amount: intl.formatMessage(txLabels.amount),
@@ -128,14 +115,6 @@ const useStrings = () => {
}
const messages = defineMessages({
- searchLabel: {
- id: 'components.send.assetselectorscreen.searchlabel',
- defaultMessage: '!!!Search by name or subject',
- },
- sendAllAssets: {
- id: 'components.send.assetselectorscreen.sendallassets',
- defaultMessage: '!!!SELECT ALL ASSETS',
- },
unknownAsset: {
id: 'components.send.assetselectorscreen.unknownAsset',
defaultMessage: '!!!Unknown asset',
diff --git a/src/Send/useCases/ListSelectedTokens/DeleteToken.stories.tsx b/src/Send/useCases/ListSelectedTokens/DeleteToken.stories.tsx
new file mode 100644
index 0000000000..60f0522bf1
--- /dev/null
+++ b/src/Send/useCases/ListSelectedTokens/DeleteToken.stories.tsx
@@ -0,0 +1,47 @@
+import {action} from '@storybook/addon-actions'
+import {storiesOf} from '@storybook/react-native'
+import React from 'react'
+import {Text, View} from 'react-native'
+
+import {QueryProvider} from '../../../../.storybook/decorators'
+import {Spacer} from '../../../components'
+import {AssetItem} from '../../../components/AssetItem'
+import {SelectedWalletProvider} from '../../../SelectedWallet'
+import {mocks} from '../../../yoroi-wallets/mocks'
+import {DeleteToken} from './DeleteToken'
+
+const primaryTokenInfo = mocks.wallet.primaryTokenInfo
+const primaryBalance = mocks.balances[primaryTokenInfo.id]
+
+const tokenInfo = mocks.tokenInfos['698a6ea0ca99f315034072af31eaac6ec11fe8558d3f48e9775aab9d.7444524950']
+const tokenBalance = mocks.balances['698a6ea0ca99f315034072af31eaac6ec11fe8558d3f48e9775aab9d.7444524950']
+
+storiesOf('Send/SelectedTokens/DeleteToken', module).add('Gallery', () => (
+
+
+
+ Fungible primary token
+
+ action(`onDelete ${tokenId}`)}
+ tokenInfo={primaryTokenInfo}
+ style={{borderColor: 'lightgray', borderWidth: 1, padding: 16, borderRadius: 8}}
+ >
+
+
+
+
+
+ Fungible non-primary token
+
+ action(`onDelete ${tokenId}`)}
+ tokenInfo={tokenInfo}
+ style={{borderColor: 'lightgray', borderWidth: 1, padding: 16, borderRadius: 8}}
+ >
+
+
+
+
+
+))
diff --git a/src/Send/useCases/ListSelectedTokens/DeleteToken.tsx b/src/Send/useCases/ListSelectedTokens/DeleteToken.tsx
new file mode 100644
index 0000000000..4b660393b3
--- /dev/null
+++ b/src/Send/useCases/ListSelectedTokens/DeleteToken.tsx
@@ -0,0 +1,47 @@
+import * as React from 'react'
+import {StyleProp, StyleSheet, TouchableOpacity, View, ViewProps, ViewStyle} from 'react-native'
+
+import {Icon} from '../../../components'
+import {COLORS} from '../../../theme'
+import {TokenId, TokenInfo} from '../../../yoroi-wallets/types'
+
+export type DeleteTokenProps = {
+ tokenInfo: TokenInfo
+ children: React.ReactNode
+ onDelete(tokenId: TokenId): void
+ style?: StyleProp
+}
+export const DeleteToken = ({children, style, tokenInfo, onDelete}: DeleteTokenProps) => {
+ return (
+
+ {children}
+
+
+ onDelete(tokenInfo.id)} />
+
+
+ )
+}
+
+const Left = ({style, ...props}: ViewProps) =>
+const Right = ({style, ...props}: ViewProps) =>
+
+type DeleteButtonProps = {
+ onPress(): void
+ style?: StyleProp
+}
+const DeleteButton = ({onPress, style}: DeleteButtonProps) => {
+ return (
+
+
+
+ )
+}
+
+const styles = StyleSheet.create({
+ container: {
+ flexDirection: 'row',
+ justifyContent: 'space-between',
+ alignItems: 'center',
+ },
+})
diff --git a/src/Send/useCases/ListSelectedTokens/EditAmountScreen.stories.tsx b/src/Send/useCases/ListSelectedTokens/EditAmountScreen.stories.tsx
new file mode 100644
index 0000000000..9d91bff5d5
--- /dev/null
+++ b/src/Send/useCases/ListSelectedTokens/EditAmountScreen.stories.tsx
@@ -0,0 +1,89 @@
+/* eslint-disable @typescript-eslint/strict-boolean-expressions */
+/* eslint-disable react/jsx-curly-brace-presence */
+import {storiesOf} from '@storybook/react-native'
+import React from 'react'
+
+import {SelectedWalletProvider} from '../../../SelectedWallet'
+import {mocks} from '../../../yoroi-wallets/mocks'
+import {SendProvider} from '../../shared/SendContext'
+import {EditAmountScreen} from './EditAmountScreen'
+
+const PrimaryToken = () => {
+ return (
+
+
+
+
+
+ )
+}
+
+const NonPrimary = () => {
+ return (
+
+
+
+
+
+ )
+}
+
+const Editing = () => {
+ return (
+
+
+
+
+
+ )
+}
+
+const Adding = () => {
+ return (
+
+
+
+
+
+ )
+}
+
+const InsuficientBalance = () => {
+ return (
+
+
+
+
+
+ )
+}
+
+const OverLockedBalance = () => {
+ return (
+
+
+
+
+
+ )
+}
+
+storiesOf('Send/ListSelectedTokens/EditAmountScreen', module) //
+ .add('editing: previous amount as initial', () => {
+ return
+ })
+ .add('with error: insuficient balance', () => {
+ return
+ })
+ .add('with warning: can`t keep assets (min-PT)', () => {
+ return
+ })
+ .add('primary token: max balance', () => {
+ return
+ })
+ .add('non primary token', () => {
+ return
+ })
+ .add('adding: empty initial', () => {
+ return
+ })
diff --git a/src/Send/useCases/ListSelectedTokens/EditAmountScreen.tsx b/src/Send/useCases/ListSelectedTokens/EditAmountScreen.tsx
new file mode 100644
index 0000000000..7fc49570fd
--- /dev/null
+++ b/src/Send/useCases/ListSelectedTokens/EditAmountScreen.tsx
@@ -0,0 +1,158 @@
+import {useNavigation} from '@react-navigation/native'
+import * as React from 'react'
+import {Text, TextProps, TouchableOpacity, View, ViewProps} from 'react-native'
+
+import {Button, Spacer, TextInput} from '../../../components'
+import {AssetItem} from '../../../components/AssetItem'
+import {TxHistoryRouteNavigation} from '../../../navigation'
+import {useSelectedWallet} from '../../../SelectedWallet'
+import {COLORS} from '../../../theme'
+import {PairedBalance} from '../../../TxHistory/PairedBalance'
+import {useTokenInfo} from '../../../yoroi-wallets/hooks'
+import {Quantity} from '../../../yoroi-wallets/types'
+import {Quantities} from '../../../yoroi-wallets/utils'
+import {editedFormatter, pastedFormatter} from '../../../yoroi-wallets/utils/amountUtils'
+import {useTokenQuantities} from '../../shared/hooks'
+import {useSend} from '../../shared/SendContext'
+import {useStrings} from '../../shared/strings'
+
+export const EditAmountScreen = () => {
+ const strings = useStrings()
+ const navigation = useNavigation()
+ const {selectedTokenId, amountChanged} = useSend()
+ const {available, locked, spendable, initialQuantity} = useTokenQuantities(selectedTokenId)
+
+ const wallet = useSelectedWallet()
+ const tokenInfo = useTokenInfo({wallet, tokenId: selectedTokenId})
+ const isPrimary = tokenInfo.id === wallet.primaryTokenInfo.id
+
+ const [quantity, setQuantity] = React.useState(initialQuantity)
+ const [inputAmount, setInputAmount] = React.useState(
+ Quantities.denominated(initialQuantity, tokenInfo.decimals),
+ )
+
+ const notEnoughBalance = Quantities.isGreaterThan(quantity, spendable)
+ const cantKeepAssets = !Quantities.isZero(locked) && Quantities.isGreaterThan(quantity, spendable)
+
+ const onChangeAmount = (text: string) => {
+ setInputAmount(text)
+ setQuantity(Quantities.atomic(text, tokenInfo.decimals))
+ }
+ const onMaxBalance = () => setInputAmount(Quantities.denominated(spendable, tokenInfo.decimals))
+ const onApply = () => {
+ amountChanged(Quantities.atomic(inputAmount, tokenInfo.decimals))
+ navigation.navigate('send-selected-tokens')
+ }
+
+ return (
+
+
+
+
+
+
+
+
+ {isPrimary && }
+
+
+
+ {!isPrimary && }
+
+
+
+ {notEnoughBalance && }
+
+ {isPrimary && cantKeepAssets && !notEnoughBalance && }
+
+
+
+ onApply()}
+ title={strings.apply.toLocaleUpperCase()}
+ shelleyTheme
+ disabled={(isPrimary && cantKeepAssets) || notEnoughBalance}
+ />
+
+
+ )
+}
+
+const BalanceError = ({style, ...props}: TextProps) => {
+ const strings = useStrings()
+ return (
+
+ {strings.insuficientBalance}
+
+ )
+}
+
+const CantKeepAssetsWarning = ({style, ...props}: TextProps) => {
+ const strings = useStrings()
+ return (
+
+ {strings.minPrimaryBalanceForTokens}
+
+ )
+}
+
+const Center = ({style, ...props}: ViewProps) =>
+const Actions = ({style, ...props}: ViewProps) =>
+
+const MaxBalanceButton = ({onPress}: {onPress(): void}) => {
+ const strings = useStrings()
+
+ return (
+
+ {strings.max.toLocaleUpperCase()}
+
+ )
+}
+
+type AmountInputProps = {
+ value: string
+ onChange(value: string): void
+ ticker: string | undefined
+}
+const AmountInput = ({value, onChange, ticker}: AmountInputProps) => {
+ const onChangeText = (text: string) => {
+ const shorterStringLength = Math.min(text.length, value.length)
+ const wasPasted =
+ Math.abs(value.length - text.length) > 1 ||
+ value.substring(0, shorterStringLength) !== text.substring(0, shorterStringLength)
+
+ const formatter = wasPasted ? pastedFormatter : editedFormatter
+
+ onChange(formatter(text) as Quantity)
+ }
+
+ return (
+ }
+ style={{
+ fontSize: 24,
+ lineHeight: 32,
+ borderWidth: 0,
+ textAlign: 'right',
+ backgroundColor: 'white',
+ }}
+ underlineColor="transparent"
+ />
+ )
+}
+const Ticker = ({ticker}: {ticker?: string}) => {ticker}
+
+const ApplyButton = Button
diff --git a/src/Send/useCases/ListSelectedTokens/ListSelectedTokensScreen.stories.tsx b/src/Send/useCases/ListSelectedTokens/ListSelectedTokensScreen.stories.tsx
new file mode 100644
index 0000000000..be7bac9381
--- /dev/null
+++ b/src/Send/useCases/ListSelectedTokens/ListSelectedTokensScreen.stories.tsx
@@ -0,0 +1,26 @@
+import {NavigationRouteContext} from '@react-navigation/native'
+import {storiesOf} from '@storybook/react-native'
+import React from 'react'
+
+import {SelectedWalletProvider} from '../../../SelectedWallet'
+import {mocks} from '../../../yoroi-wallets/mocks/wallet'
+import {SendProvider} from '../../shared/SendContext'
+import {ListSelectedTokensScreen} from './ListSelectedTokensScreen'
+
+storiesOf('Send/ListSelectedTokens', module).add('Default', () => {
+ const route = {
+ key: 'key',
+ name: 'name',
+ params: {},
+ }
+
+ return (
+
+
+
+
+
+
+
+ )
+})
diff --git a/src/Send/useCases/ListSelectedTokens/ListSelectedTokensScreen.tsx b/src/Send/useCases/ListSelectedTokens/ListSelectedTokensScreen.tsx
new file mode 100644
index 0000000000..5e04e7f512
--- /dev/null
+++ b/src/Send/useCases/ListSelectedTokens/ListSelectedTokensScreen.tsx
@@ -0,0 +1,136 @@
+import {useNavigation} from '@react-navigation/native'
+import * as React from 'react'
+import {defineMessages, useIntl} from 'react-intl'
+import {StyleSheet, TouchableOpacity, View, ViewProps} from 'react-native'
+import {FlatList} from 'react-native-gesture-handler'
+import {SafeAreaView} from 'react-native-safe-area-context'
+
+import {Boundary, Button, Spacer} from '../../../components'
+import {AssetItem} from '../../../components/AssetItem'
+import globalMessages from '../../../i18n/global-messages'
+import {TxHistoryRouteNavigation} from '../../../navigation'
+import {useSelectedWallet} from '../../../SelectedWallet'
+import {COLORS} from '../../../theme'
+import {sortTokenInfos} from '../../../utils'
+import {useTokenInfo, useTokenInfos} from '../../../yoroi-wallets/hooks'
+import {TokenId, TokenInfo, YoroiAmount} from '../../../yoroi-wallets/types'
+import {Amounts} from '../../../yoroi-wallets/utils'
+import {useSend} from '../../shared/SendContext'
+import {AddToken} from './AddToken/AddToken'
+import {DeleteToken} from './DeleteToken'
+
+export const ListSelectedTokensScreen = () => {
+ const {targets, selectedTargetIndex, tokenSelectedChanged, amountRemoved} = useSend()
+ const {amounts} = targets[selectedTargetIndex].entry
+ const navigateTo = useNavigateTo()
+ const strings = useStrings()
+
+ const wallet = useSelectedWallet()
+ const tokenInfos = useTokenInfos({
+ wallet,
+ tokenIds: Amounts.toArray(amounts).map(({tokenId}) => tokenId),
+ })
+ const tokens = sortTokenInfos({wallet, tokenInfos})
+
+ const onSelect = (tokenId: TokenId) => {
+ tokenSelectedChanged(tokenId)
+ navigateTo.editToken()
+ }
+ const onDelete = (tokenId: TokenId) => {
+ amountRemoved(tokenId)
+ }
+ const onAdd = navigateTo.addToken
+ const onNext = () => console.log('build tx')
+
+ return (
+
+ (
+
+
+
+ )}
+ bounces={false}
+ keyExtractor={({id}) => id}
+ testID="selectedTokens"
+ />
+
+
+
+
+
+
+
+
+
+
+
+
+
+ )
+}
+
+type SelectableTokenProps = {
+ amount: YoroiAmount
+ onSelect(tokenId: TokenId): void
+ onDelete(tokenId: TokenId): void
+}
+const SelectableToken = ({amount: {quantity, tokenId}, onDelete, onSelect}: SelectableTokenProps) => {
+ const wallet = useSelectedWallet()
+ const tokenInfo = useTokenInfo({wallet, tokenId})
+
+ const handleDelete = () => onDelete(tokenId)
+ const handleSelect = () => onSelect(tokenId)
+
+ return (
+
+
+
+
+
+ )
+}
+
+const Actions = ({style, ...props}: ViewProps) =>
+const Row = ({style, ...props}: ViewProps) =>
+
+export const useStrings = () => {
+ const intl = useIntl()
+
+ return {
+ addToken: intl.formatMessage(messages.addToken),
+ next: intl.formatMessage(globalMessages.next),
+ }
+}
+
+const messages = defineMessages({
+ addToken: {
+ id: 'components.send.addToken',
+ defaultMessage: '!!!Add token',
+ },
+})
+
+const useNavigateTo = () => {
+ const navigation = useNavigation()
+
+ return {
+ addToken: () => navigation.navigate('send-select-token'),
+ editToken: () => navigation.navigate('send-edit-token-amount'),
+ }
+}
+
+const styles = StyleSheet.create({
+ row: {
+ flexDirection: 'row',
+ },
+ transparent: {
+ backgroundColor: 'transparent',
+ paddingVertical: 16,
+ },
+ container: {
+ flex: 1,
+ backgroundColor: COLORS.WHITE,
+ paddingHorizontal: 16,
+ },
+})
diff --git a/src/Send/useCases/ListSelectedTokens/index.tsx b/src/Send/useCases/ListSelectedTokens/index.tsx
new file mode 100644
index 0000000000..fd41e56d1a
--- /dev/null
+++ b/src/Send/useCases/ListSelectedTokens/index.tsx
@@ -0,0 +1,2 @@
+export * from './EditAmountScreen'
+export * from './ListSelectedTokensScreen'
diff --git a/src/Send/Memo.tsx b/src/Send/useCases/StartTx/InputMemo.tsx
similarity index 94%
rename from src/Send/Memo.tsx
rename to src/Send/useCases/StartTx/InputMemo.tsx
index 98c63759d5..3b93fb5e86 100644
--- a/src/Send/Memo.tsx
+++ b/src/Send/useCases/StartTx/InputMemo.tsx
@@ -2,7 +2,7 @@ import React from 'react'
import {defineMessages, useIntl} from 'react-intl'
import {StyleSheet, Text, View} from 'react-native'
-import {HelperText, TextInput} from '../components'
+import {HelperText, TextInput} from '../../../components'
export const maxMemoLength = 256
@@ -11,7 +11,7 @@ type Props = {
onChangeText: (memo: string) => void
}
-export const MemoInput = ({onChangeText, memo}: Props) => {
+export const InputMemo = ({onChangeText, memo}: Props) => {
const strings = useStrings()
const showError = memo.length > maxMemoLength
diff --git a/src/Send/useCases/StartTx/InputReceiver/InputReceiver.tsx b/src/Send/useCases/StartTx/InputReceiver/InputReceiver.tsx
new file mode 100644
index 0000000000..2b45b17a66
--- /dev/null
+++ b/src/Send/useCases/StartTx/InputReceiver/InputReceiver.tsx
@@ -0,0 +1,31 @@
+import _ from 'lodash'
+import React from 'react'
+import {StyleSheet} from 'react-native'
+
+import {TextInput, TextInputProps} from '../../../../components'
+import {ScannerButton} from '../../../shared/ScannerButton'
+import {useStrings} from '../../../shared/strings'
+
+export const InputReceiver = ({isLoading, ...props}: {isLoading: boolean} & TextInputProps) => {
+ const strings = useStrings()
+
+ return (
+ }
+ label={strings.addressInputLabel}
+ testID="receiverInput"
+ style={styles.receiver}
+ errorOnMount
+ noHelper
+ multiline
+ blurOnSubmit
+ {...props}
+ />
+ )
+}
+
+const styles = StyleSheet.create({
+ receiver: {
+ height: 100,
+ },
+})
diff --git a/src/Send/AddressReaderQR/AddressReaderQR.stories.tsx b/src/Send/useCases/StartTx/InputReceiver/ReadQRCodeScreen.stories.tsx
similarity index 54%
rename from src/Send/AddressReaderQR/AddressReaderQR.stories.tsx
rename to src/Send/useCases/StartTx/InputReceiver/ReadQRCodeScreen.stories.tsx
index 2014dd798e..4e5ce3e20b 100644
--- a/src/Send/AddressReaderQR/AddressReaderQR.stories.tsx
+++ b/src/Send/useCases/StartTx/InputReceiver/ReadQRCodeScreen.stories.tsx
@@ -2,11 +2,10 @@ import {NavigationRouteContext} from '@react-navigation/native'
import {storiesOf} from '@storybook/react-native'
import React from 'react'
-import {mocks} from '../../yoroi-wallets/mocks'
-import {SendProvider} from '../Context/SendContext'
-import {AddressReaderQR} from './AddressReaderQR'
+import {SendProvider} from '../../../shared/SendContext'
+import {ReadQRCodeScreen} from './ReadQRCodeScreen'
-storiesOf('AddressReaderQR', module).add('Default', () => {
+storiesOf('Send/ReadQRCode', module).add('Default', () => {
const route = {
key: 'key',
name: 'name',
@@ -14,8 +13,8 @@ storiesOf('AddressReaderQR', module).add('Default', () => {
return (
-
-
+
+
)
diff --git a/src/Send/AddressReaderQR/AddressReaderQR.tsx b/src/Send/useCases/StartTx/InputReceiver/ReadQRCodeScreen.tsx
similarity index 82%
rename from src/Send/AddressReaderQR/AddressReaderQR.tsx
rename to src/Send/useCases/StartTx/InputReceiver/ReadQRCodeScreen.tsx
index eb8a224778..d02e995b65 100644
--- a/src/Send/AddressReaderQR/AddressReaderQR.tsx
+++ b/src/Send/useCases/StartTx/InputReceiver/ReadQRCodeScreen.tsx
@@ -2,12 +2,12 @@ import {useNavigation} from '@react-navigation/native'
import React from 'react'
import QRCodeScanner from 'react-native-qrcode-scanner'
-import {pastedFormatter} from '../../yoroi-wallets/utils/amountUtils'
-import {useSend} from '../Context/SendContext'
+import {pastedFormatter} from '../../../../yoroi-wallets/utils'
+import {useSend} from '../../../shared/SendContext'
-export const AddressReaderQR = () => {
+export const ReadQRCodeScreen = () => {
const navigation = useNavigation()
- const {receiverChanged, amountChanged} = useSend()
+ const {receiverChanged} = useSend()
const handleOnRead = ({data: qrData}) => {
const regex = /(cardano):([a-zA-Z1-9]\w+)\??/
@@ -19,7 +19,7 @@ export const AddressReaderQR = () => {
if ('amount' in params) {
receiverChanged(address ?? '')
const amount = pastedFormatter(params?.amount ?? '')
- amountChanged(amount)
+ console.log(`amountChanged`, amount)
}
} else {
receiverChanged(address ?? '')
diff --git a/src/Send/useCases/StartTx/InputReceiver/ResolveAddress.tsx b/src/Send/useCases/StartTx/InputReceiver/ResolveAddress.tsx
new file mode 100644
index 0000000000..82668181f3
--- /dev/null
+++ b/src/Send/useCases/StartTx/InputReceiver/ResolveAddress.tsx
@@ -0,0 +1,114 @@
+/* eslint-disable @typescript-eslint/no-explicit-any */
+import {Resolution} from '@unstoppabledomains/resolution'
+import _ from 'lodash'
+import React from 'react'
+import {Text, View, ViewProps} from 'react-native'
+import {useQuery, UseQueryOptions} from 'react-query'
+
+import {getNetworkConfigById, NetworkId, normalizeToAddress, YoroiWallet} from '../../../../yoroi-wallets'
+import {useStrings} from '../../../shared/strings'
+import {InputReceiver} from './InputReceiver'
+
+type ReceiverProps = ViewProps & {
+ receiver: string
+ address: string
+ errorMessage?: string
+ isLoading: boolean
+ onChangeReceiver: (receiver: string) => void
+}
+export const ResolveAddress = ({
+ isLoading,
+ address,
+ receiver,
+ errorMessage,
+ onChangeReceiver,
+ style,
+ ...props
+}: ReceiverProps) => {
+ const strings = useStrings()
+ const isResolved = !isLoading && isDomain(receiver) && !receiver.includes(address)
+
+ return (
+
+
+
+ {isLoading ? {strings.pleaseWait} : {errorMessage}}
+
+ {isResolved && (
+
+ {`${strings.resolvesTo}: ${address}`}
+
+ )}
+
+ )
+}
+
+export const useReceiver = (
+ {wallet, receiver}: {wallet: YoroiWallet; receiver: string},
+ options?: UseQueryOptions,
+) => {
+ const query = useQuery({
+ queryKey: [receiver, 'receiver'],
+ queryFn: () => resolveAddress(receiver, wallet.networkId),
+ ...options,
+ })
+
+ return {
+ ...query,
+ address: query.data ?? '',
+ }
+}
+
+const resolveAddress = async (receiver: string, networkId: NetworkId) => {
+ let address = receiver
+ if (!isDomain(receiver)) return address
+
+ address = await getUnstoppableDomainAddress(receiver)
+ await isReceiverAddressValid(address, networkId)
+
+ return address
+}
+
+export const getAddressErrorMessage = (error: Error & {code?: string}, strings: ReturnType) => {
+ switch (error?.code) {
+ case 'UnsupportedDomain':
+ return strings.domainUnsupportedError
+ case 'RecordNotFound':
+ return strings.domainRecordNotFoundError
+ case 'UnregisteredDomain':
+ return strings.domainNotRegisteredError
+ default:
+ return strings.addressInputErrorInvalidAddress
+ }
+}
+
+const getUnstoppableDomainAddress = (receiver: string) => {
+ const resolution = new Resolution()
+ return resolution.addr(receiver, 'ADA')
+}
+
+const isReceiverAddressValid = async (resolvedAddress: string, walletNetworkId: NetworkId): Promise => {
+ if (resolvedAddress.length === 0) return Promise.resolve()
+
+ const address = await normalizeToAddress(resolvedAddress)
+ if (!address) return Promise.reject(new Error('Invalid address'))
+
+ try {
+ const networkConfig: any = getNetworkConfigById(walletNetworkId)
+ const configNetworkId = Number(networkConfig.CHAIN_NETWORK_ID)
+ const addressNetworkId = await address.networkId()
+ if (addressNetworkId !== configNetworkId && !isNaN(configNetworkId)) {
+ return Promise.reject(new Error('Invalid address'))
+ }
+ } catch (e) {
+ return Promise.reject(new Error('Should not happen'))
+ }
+}
+
+const isDomain = (receiver: string) => /.+\..+/.test(receiver)
diff --git a/src/Send/SendScreen/ErrorBanners.tsx b/src/Send/useCases/StartTx/ShowErrors.tsx
similarity index 63%
rename from src/Send/SendScreen/ErrorBanners.tsx
rename to src/Send/useCases/StartTx/ShowErrors.tsx
index 6a439da76f..a22a8ecf0c 100644
--- a/src/Send/SendScreen/ErrorBanners.tsx
+++ b/src/Send/useCases/StartTx/ShowErrors.tsx
@@ -1,11 +1,11 @@
import React from 'react'
-import {Banner, ClickableBanner} from '../../components'
-import {useSelectedWallet} from '../../SelectedWallet'
-import {useHasPendingTx, useSync} from '../../yoroi-wallets'
-import {useStrings} from './strings'
+import {Banner, ClickableBanner} from '../../../components'
+import {useSelectedWallet} from '../../../SelectedWallet'
+import {useHasPendingTx, useSync} from '../../../yoroi-wallets/hooks'
+import {useStrings} from '../../shared/strings'
-export const ErrorBanners = () => {
+export const ShowErrors = () => {
const strings = useStrings()
const wallet = useSelectedWallet()
diff --git a/src/Send/useCases/StartTx/StartTxScreen.stories.tsx b/src/Send/useCases/StartTx/StartTxScreen.stories.tsx
new file mode 100644
index 0000000000..7bd93c9547
--- /dev/null
+++ b/src/Send/useCases/StartTx/StartTxScreen.stories.tsx
@@ -0,0 +1,30 @@
+import {storiesOf} from '@storybook/react-native'
+import * as React from 'react'
+
+import {QueryProvider} from '../../../../.storybook/decorators'
+import {Boundary} from '../../../components'
+import {SelectedWalletProvider} from '../../../SelectedWallet'
+import {YoroiWallet} from '../../../yoroi-wallets'
+import {mocks} from '../../../yoroi-wallets/mocks/wallet'
+import {SendProvider} from '../../shared/SendContext'
+import {StartTxScreen} from './StartTxScreen'
+
+storiesOf('Send/StartTx', module)
+ .add('Default', () => )
+ .add('SendAll', () => )
+
+const SendScreenTest = () => {
+ const wallet: YoroiWallet = mocks.wallet
+
+ return (
+
+
+
+
+
+
+
+
+
+ )
+}
diff --git a/src/Send/useCases/StartTx/StartTxScreen.tsx b/src/Send/useCases/StartTx/StartTxScreen.tsx
new file mode 100644
index 0000000000..fde689f1ca
--- /dev/null
+++ b/src/Send/useCases/StartTx/StartTxScreen.tsx
@@ -0,0 +1,107 @@
+import {useNavigation} from '@react-navigation/native'
+import _ from 'lodash'
+import React from 'react'
+import {ScrollView, StyleSheet, View, ViewProps} from 'react-native'
+import {SafeAreaView} from 'react-native-safe-area-context'
+
+import {Button, Spacer} from '../../../components'
+import {TxHistoryRouteNavigation} from '../../../navigation'
+import {useSelectedWallet} from '../../../SelectedWallet'
+import {COLORS} from '../../../theme'
+import {useHasPendingTx, useIsOnline} from '../../../yoroi-wallets/hooks'
+import {useSend} from '../../shared/SendContext'
+import {useStrings} from '../../shared/strings'
+import {InputMemo, maxMemoLength} from './InputMemo'
+import {getAddressErrorMessage, ResolveAddress, useReceiver} from './InputReceiver/ResolveAddress'
+import {ShowErrors} from './ShowErrors'
+
+export const StartTxScreen = () => {
+ const strings = useStrings()
+ const navigateTo = useNavigateTo()
+ const wallet = useSelectedWallet()
+
+ const hasPendingTx = useHasPendingTx(wallet)
+ const isOnline = useIsOnline(wallet)
+
+ const {targets, selectedTargetIndex, receiverChanged, memo, memoChanged, addressChanged} = useSend()
+ const {address, amounts} = targets[selectedTargetIndex].entry
+ const shouldOpenSelectToken = Object.keys(amounts).length === 0
+ const receiver = targets[selectedTargetIndex].receiver
+ const {error, isLoading} = useReceiver(
+ {wallet, receiver},
+ {
+ onSettled(address, error) {
+ if (error) {
+ addressChanged('')
+ } else {
+ addressChanged(address ?? '')
+ }
+ },
+ },
+ )
+ const addressErrorMessage = error != null ? getAddressErrorMessage(error, strings) : ''
+ const isValid = isOnline && !hasPendingTx && _.isEmpty(error) && memo.length <= maxMemoLength && address.length > 0
+
+ const onNext = () => {
+ navigateTo.selectedTokens()
+ if (shouldOpenSelectToken) navigateTo.selectToken()
+ }
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ )
+}
+
+const Actions = ({style, ...props}: ViewProps) =>
+
+const useNavigateTo = () => {
+ const navigation = useNavigation()
+
+ return {
+ selectedTokens: () => navigation.navigate('send-selected-tokens'),
+ selectToken: () => navigation.navigate('send-select-token'),
+ }
+}
+
+const styles = StyleSheet.create({
+ container: {
+ flex: 1,
+ backgroundColor: COLORS.WHITE,
+ paddingHorizontal: 16,
+ },
+ actions: {
+ paddingVertical: 16,
+ },
+})
+
+const NextButton = Button
diff --git a/src/TxHistory/ActionsBanner.tsx b/src/TxHistory/ActionsBanner.tsx
index c96600e208..55092231ba 100644
--- a/src/TxHistory/ActionsBanner.tsx
+++ b/src/TxHistory/ActionsBanner.tsx
@@ -8,7 +8,7 @@ import {features} from '../features'
import {actionMessages} from '../i18n/global-messages'
import {TxHistoryRouteNavigation} from '../navigation'
import {useSelectedWallet} from '../SelectedWallet'
-import {useSend} from '../Send/Context/SendContext'
+import {useSend} from '../Send/shared/SendContext'
import {COLORS} from '../theme'
const ACTION_PROPS = {
@@ -137,7 +137,7 @@ const useNavigateTo = () => {
const strings = useStrings()
return {
- send: () => navigation.navigate('send'),
+ send: () => navigation.navigate('send-start-tx'),
receive: () => navigation.navigate('receive'),
buy: () => Alert.alert(strings.messageBuy, strings.messageBuy),
}
diff --git a/src/TxHistory/AssetList/AssetList.tsx b/src/TxHistory/AssetList/AssetList.tsx
index bd701368af..d0ded2dced 100644
--- a/src/TxHistory/AssetList/AssetList.tsx
+++ b/src/TxHistory/AssetList/AssetList.tsx
@@ -52,7 +52,7 @@ export const AssetList = (props: Props) => {
renderItem={({item: tokenInfo}) => (
Linking.openURL(config.EXPLORER_URL_FOR_TOKEN(tokenInfo.id))}
/>
)}
@@ -68,10 +68,10 @@ export const AssetList = (props: Props) => {
type ExplorableAssetItemProps = AssetItemProps & {
onPress(): void
}
-const ExplorableAssetItem = ({tokenInfo, balance, onPress}: ExplorableAssetItemProps) => {
+const ExplorableAssetItem = ({tokenInfo, quantity, onPress}: ExplorableAssetItemProps) => {
return (
-
+
)
}
diff --git a/src/TxHistory/PairedBalance.tsx b/src/TxHistory/PairedBalance.tsx
index 87b13d722e..7d0ae75a7b 100644
--- a/src/TxHistory/PairedBalance.tsx
+++ b/src/TxHistory/PairedBalance.tsx
@@ -40,9 +40,12 @@ const Balance = ({privacy, primaryAmount}: Props) => {
const {currency, config} = useCurrencyContext()
const rate = useExchangeRate({wallet, to: currency})
- // hide pairing when set to the default asset ticker
+ // hide pairing when set to the primary token
if (currency === 'ADA') return null
+ // hide pairing when is not the primary token
+ if (wallet.primaryTokenInfo.id !== primaryAmount.tokenId) return null
+
if (rate == null)
return (
diff --git a/src/TxHistory/TxHistory.stories.tsx b/src/TxHistory/TxHistory.stories.tsx
index c788d9d3bc..04c8bc754d 100644
--- a/src/TxHistory/TxHistory.stories.tsx
+++ b/src/TxHistory/TxHistory.stories.tsx
@@ -3,7 +3,7 @@ import React from 'react'
import {QueryProvider} from '../../.storybook/decorators'
import {SelectedWalletProvider} from '../SelectedWallet'
-import {SendProvider} from '../Send/Context/SendContext'
+import {SendProvider} from '../Send/shared/SendContext'
import {mocks} from '../yoroi-wallets/mocks'
import {TxHistory} from './TxHistory'
@@ -12,7 +12,7 @@ storiesOf('V2/TxHistory', module)
return (
-
+
@@ -30,14 +30,7 @@ storiesOf('V2/TxHistory', module)
},
}}
>
-
+
@@ -53,7 +46,7 @@ storiesOf('V2/TxHistory', module)
return (
-
+
@@ -69,7 +62,7 @@ storiesOf('V2/TxHistory', module)
walletImplementationId: 'haskell-byron',
}}
>
-
+
diff --git a/src/TxHistory/TxHistoryNavigator.tsx b/src/TxHistory/TxHistoryNavigator.tsx
index 321e937eb0..62e0ca7aa8 100644
--- a/src/TxHistory/TxHistoryNavigator.tsx
+++ b/src/TxHistory/TxHistoryNavigator.tsx
@@ -12,11 +12,13 @@ import {
} from '../navigation'
import {ReceiveScreen} from '../Receive/ReceiveScreen'
import {useSelectedWallet} from '../SelectedWallet'
-import {AddressReaderQR} from '../Send/AddressReaderQR'
-import {AssetSelectorScreen} from '../Send/AssetSelectorScreen'
-import {ConfirmScreen} from '../Send/ConfirmScreen'
-import {SendProvider} from '../Send/Context/SendContext'
-import {SendScreen} from '../Send/SendScreen'
+import {SendProvider} from '../Send/shared/SendContext'
+import {ConfirmTxScreen} from '../Send/useCases/ConfirmTx'
+import {SelectTokenFromListScreen} from '../Send/useCases/ListSelectedTokens/AddToken/SelectTokenScreen'
+import {EditAmountScreen} from '../Send/useCases/ListSelectedTokens/EditAmountScreen'
+import {ListSelectedTokensScreen} from '../Send/useCases/ListSelectedTokens/ListSelectedTokensScreen'
+import {ReadQRCodeScreen} from '../Send/useCases/StartTx/InputReceiver/ReadQRCodeScreen'
+import {StartTxScreen} from '../Send/useCases/StartTx/StartTxScreen'
import {COLORS} from '../theme'
import {useWalletName} from '../yoroi-wallets'
import {ModalInfo} from './ModalInfo'
@@ -34,7 +36,7 @@ export const TxHistoryNavigator = () => {
const hideModalInfo = () => setModalInfoState(false)
return (
-
+
{
/>
{() => (
-
+
+
+ )}
+
+
+
+ {() => (
+
+
+
+ )}
+
+
+
+ {() => (
+
+
)}
-
+
{() => (
-
+
)}
@@ -132,6 +175,14 @@ const messages = defineMessages({
id: 'components.send.selectasset.title',
defaultMessage: '!!!Select asset',
},
+ selectedTokensTitle: {
+ id: 'components.send.selectedtokensscreen.title',
+ defaultMessage: '!!!Selected tokens',
+ },
+ editTokenAmountTitle: {
+ id: 'components.send.edittokenamountscreen.title',
+ defaultMessage: '!!!Selected tokens',
+ },
confirmTitle: {
id: 'components.send.confirmscreen.title',
defaultMessage: '!!!Send',
@@ -155,6 +206,8 @@ const useStrings = () => {
selectAssetTitle: intl.formatMessage(messages.selectAssetTitle),
confirmTitle: intl.formatMessage(messages.confirmTitle),
receiveInfoText: intl.formatMessage(messages.receiveInfoText),
+ editTokenAmountTitle: intl.formatMessage(messages.editTokenAmountTitle),
+ selectedTokensTitle: intl.formatMessage(messages.selectedTokensTitle),
}
}
diff --git a/src/components/AssetItem/AssetItem.stories.tsx b/src/components/AssetItem/AssetItem.stories.tsx
index e0ee8411c8..e77852fad1 100644
--- a/src/components/AssetItem/AssetItem.stories.tsx
+++ b/src/components/AssetItem/AssetItem.stories.tsx
@@ -23,7 +23,7 @@ storiesOf('Components/AssetItem', module)
@@ -33,7 +33,7 @@ storiesOf('Components/AssetItem', module)
@@ -51,7 +51,7 @@ storiesOf('Components/AssetItem', module)
@@ -69,7 +69,7 @@ storiesOf('Components/AssetItem', module)
diff --git a/src/components/AssetItem/AssetItem.tsx b/src/components/AssetItem/AssetItem.tsx
index 5435694409..8906b3a798 100644
--- a/src/components/AssetItem/AssetItem.tsx
+++ b/src/components/AssetItem/AssetItem.tsx
@@ -10,16 +10,16 @@ import {Boundary, Placeholder, Text, TokenIcon} from '..'
export type AssetItemProps = {
tokenInfo: TokenInfo
- balance: Quantity
+ quantity: Quantity
style?: ViewProps['style']
}
-export const AssetItem = ({balance, style, tokenInfo}: AssetItemProps) => {
+export const AssetItem = ({quantity, style, tokenInfo}: AssetItemProps) => {
const wallet = useSelectedWallet()
const isPrimary = tokenInfo.id === wallet.primaryTokenInfo.id
const name = tokenInfo.ticker ?? tokenInfo.name ?? '-'
const detail = isPrimary ? tokenInfo.description : tokenInfo.fingerprint
- const quantity = Quantities.denominated(balance, tokenInfo.decimals)
+ const denominatedQuantity = Quantities.denominated(quantity, tokenInfo.decimals)
return (
@@ -41,10 +41,10 @@ export const AssetItem = ({balance, style, tokenInfo}: AssetItemProps) => {
- {quantity}
+ {denominatedQuantity}
- {isPrimary && }
+ {isPrimary && }
)
diff --git a/src/components/TextInput/TextInput.tsx b/src/components/TextInput/TextInput.tsx
index 5e1adc2ba7..ca69de5a01 100644
--- a/src/components/TextInput/TextInput.tsx
+++ b/src/components/TextInput/TextInput.tsx
@@ -2,7 +2,7 @@ import React, {ForwardedRef} from 'react'
import {
StyleSheet,
TextInput as RNTextInput,
- TextInputProps,
+ TextInputProps as RNTextInputProps,
TouchableOpacity,
View,
ViewProps,
@@ -14,7 +14,7 @@ import {COLORS} from '../../theme'
import {isEmptyString} from '../../utils/utils'
import {Icon} from '../Icon'
-type Props = TextInputProps &
+export type TextInputProps = RNTextInputProps &
Omit, 'theme'> & {
containerStyle?: ViewStyle
renderComponentStyle?: ViewStyle
@@ -45,7 +45,7 @@ const useDebounced = (callback, value, delay = 1000) => {
}, [callback, delay, value])
}
-export const TextInput = React.forwardRef((props: Props, ref: ForwardedRef) => {
+export const TextInput = React.forwardRef((props: TextInputProps, ref: ForwardedRef) => {
const {
value,
containerStyle,
diff --git a/src/i18n/global-messages.ts b/src/i18n/global-messages.ts
index c628bd1c16..e250c58b00 100644
--- a/src/i18n/global-messages.ts
+++ b/src/i18n/global-messages.ts
@@ -533,6 +533,14 @@ export const currencyNames = defineMessages({
})
export default defineMessages({
+ apply: {
+ id: 'global.apply',
+ defaultMessage: '!!!Apply',
+ },
+ max: {
+ id: 'global.max',
+ defaultMessage: '!!!Max',
+ },
allDone: {
id: 'components.walletinit.restorewallet.upgradeconfirmmodal.noUpgradeLabel',
defaultMessage: '!!!All done!',
diff --git a/src/i18n/locales/en-US.json b/src/i18n/locales/en-US.json
index ceb6ea2474..066b05a5ad 100644
--- a/src/i18n/locales/en-US.json
+++ b/src/i18n/locales/en-US.json
@@ -121,12 +121,14 @@
"components.receive.receivescreen.unusedAddresses": "Unused addresses",
"components.receive.receivescreen.usedAddresses": "Used addresses",
"components.receive.receivescreen.verifyAddress": "Verify address",
+ "components.send.addToken": "Add asset",
"components.send.selectasset.title": "Select Asset",
"components.send.assetselectorscreen.searchlabel": "Search by name or subject",
"components.send.assetselectorscreen.sendallassets": "SELECT ALL ASSETS",
"components.send.assetselectorscreen.unknownAsset": "Unknown Asset",
"components.send.addressreaderqr.title": "Scan QR code address",
"components.send.amountfield.label": "Amount",
+ "components.send.edittokenamountscreen.title": "Asset amount",
"components.send.memofield.label": "Memo",
"components.send.memofield.message": "(Optional) Memo is stored locally",
"components.send.memofield.error": "Memo name is too long",
@@ -149,6 +151,7 @@
"components.send.confirmscreen.receiver": "Receiver",
"components.send.confirmscreen.sendingModalTitle": "Submitting transaction",
"components.send.confirmscreen.title": "Send",
+ "components.send.selectedtokensscreen.title": "Assets added",
"components.send.sendscreen.addressInputErrorInvalidAddress": "Please enter a valid address",
"components.send.sendscreen.addressInputLabel": "Address",
"components.send.sendscreen.amountInput.error.assetOverflow": "Maximum value of a token inside a UTXO exceeded (overflow).",
@@ -174,6 +177,7 @@
"components.send.sendscreen.errorBannerPendingOutgoingTransaction": "You cannot send a new transaction while an existing one is still pending",
"components.send.sendscreen.feeLabel": "Fee",
"components.send.sendscreen.feeNotAvailable": "-",
+ "components.send.sendscreen.resolvesTo": "Resolves to",
"components.send.sendscreen.sendAllWarningText": "You have selected the send all option. Please confirm that you understand how this feature works.",
"components.send.sendscreen.sendAllWarningTitle": "Do you really want to send all?",
"components.send.sendscreen.sendAllWarningAlert1": "All your {assetNameOrId} balance will be transferred in this transaction.",
@@ -485,6 +489,7 @@
"global.actions.dialogs.walletSynchronizing": "Wallet is synchronizing",
"global.actions.dialogs.wrongPinError.message": "PIN is incorrect.",
"global.actions.dialogs.wrongPinError.title": "Invalid PIN",
+ "global.apply": "Apply",
"global.assets.assetsLabel": "Assets",
"global.assets.assetLabel": "Asset",
"global.availableFunds": "Available funds",
@@ -503,10 +508,12 @@
"global.currency.KRW": "South Korean Won",
"global.currency.USD": "US Dollar",
"global.error": "Error",
+ "global.error.insufficientBalance": "Insufficent balance",
"global.error.walletNameAlreadyTaken": "You already have a wallet with this name",
"global.error.walletNameTooLong": "Wallet name cannot exceed 40 letters",
"global.error.walletNameMustBeFilled": "Must be filled",
"global.info": "Info",
+ "global.info.minPrimaryBalanceForTokens": "Bear in mind that you need to keep some min ADA for holding your tokens and NFTs",
"global.learnMore": "Learn more",
"global.ledgerMessages.appInstalled": "Cardano ADA app is installed on your Ledger device.",
"global.ledgerMessages.appOpened": "Cardano ADA app must remain open on your Ledger device.",
@@ -529,6 +536,7 @@
"global.ledgerMessages.continueOnLedger": "Continue on Ledger",
"global.lockedDeposit": "Locked deposit",
"global.lockedDepositHint": "This amount cannot be transferred or delegated while you hold assets like tokens or NFTs",
+ "global.max": "Max",
"global.network.syncErrorBannerTextWithoutRefresh": "We are experiencing synchronization issues.",
"global.network.syncErrorBannerTextWithRefresh": "We are experiencing synchronization issues. Pull to refresh",
"global.nfts": "{qty, plural, one {NFT} other {NFTs}}",
diff --git a/src/navigation.ts b/src/navigation.ts
index 5753fabe9b..44324e80a9 100644
--- a/src/navigation.ts
+++ b/src/navigation.ts
@@ -154,10 +154,12 @@ export type TxHistoryRoutes = {
id: string
}
receive: undefined
- send: undefined
- 'select-asset': undefined
- 'address-reader-qr': undefined
- 'send-confirm': SendConfirmParams
+ 'send-start-tx': undefined
+ 'send-select-token': undefined
+ 'send-read-qr-code': undefined
+ 'send-confirm-tx': SendConfirmParams
+ 'send-selected-tokens': undefined
+ 'send-edit-token-amount': undefined
}
export type TxHistoryRouteNavigation = StackNavigationProp
diff --git a/src/yoroi-wallets/cardano/api/utils.ts b/src/yoroi-wallets/cardano/api/utils.ts
index f09df6ccfc..6d56df665f 100644
--- a/src/yoroi-wallets/cardano/api/utils.ts
+++ b/src/yoroi-wallets/cardano/api/utils.ts
@@ -109,7 +109,7 @@ export const toTokenInfo = (token: LegacyToken): TokenInfo => {
symbol: undefined,
url: undefined,
logo: undefined,
- ticker: undefined,
+ ticker: token.metadata?.ticker ?? undefined,
}
}
diff --git a/src/yoroi-wallets/hooks/index.ts b/src/yoroi-wallets/hooks/index.ts
index 80dc4075c7..b15396e596 100644
--- a/src/yoroi-wallets/hooks/index.ts
+++ b/src/yoroi-wallets/hooks/index.ts
@@ -120,8 +120,6 @@ export const useUtxos = (wallet: YoroiWallet) => {
/**
* Calculate the lovelace locked up to hold utxos with assets
- * Important `minAdaRequired` is missing `has_hash_data`
- * which could be adding 10 in size to calc the words of the utxo
*
* @summary Returns the locked amount in Lovelace
*/
diff --git a/src/yoroi-wallets/mocks/wallet.ts b/src/yoroi-wallets/mocks/wallet.ts
index 903db6970b..376835673f 100644
--- a/src/yoroi-wallets/mocks/wallet.ts
+++ b/src/yoroi-wallets/mocks/wallet.ts
@@ -631,6 +631,9 @@ const balances: YoroiAmounts = {
'1d129dc9c03f95a863489883914f05a52e13135994a32f0cbeacc65f.74484f444c52': '5',
'1ca1fc0c880d25850cb00303788dfb51bdf2f902f6dce47d1ad09d5b.44': '2463889379',
'08d91ec4e6c743a92de97d2fde5ca0d81493555c535894a3097061f7.c8b0': '148',
+ '1d129dc9c03f95a863489883914f05a52e13135994a32f0cbeacc65e.74484f444c53': '100000008',
+ '1d129dc9c03f95a863489883914f05a52e13135994a32f0cbeacc65e.74484f444c54': '1000000000012',
+ '1d129dc9c03f95a863489883914f05a52e13135994a32f0cbeacc65e.74484f444c55': '100000000000000000020',
}
const tokenInfos: Record = {
@@ -717,6 +720,48 @@ const tokenInfos: Record = {
ticker: 'WUSDC',
url: 'https://wallet-testnet.nu.fi',
},
+ '1d129dc9c03f95a863489883914f05a52e13135994a32f0cbeacc65e.74484f444c53': toTokenInfo({
+ networkId: 300,
+ identifier: '1d129dc9c03f95a863489883914f05a52e13135994a32f0cbeacc65e.74484f444c53',
+ isDefault: false,
+ metadata: {
+ type: 'Cardano',
+ policyId: '1d129dc9c03f95a863489883914f05a52e13135994a32f0cbeacc65e',
+ assetName: '74484f444c53',
+ ticker: '8DEC',
+ longName: '',
+ numberOfDecimals: 8,
+ maxSupply: null,
+ },
+ }),
+ '1d129dc9c03f95a863489883914f05a52e13135994a32f0cbeacc65e.74484f444c54': toTokenInfo({
+ networkId: 300,
+ identifier: '1d129dc9c03f95a863489883914f05a52e13135994a32f0cbeacc65e.74484f444c54',
+ isDefault: false,
+ metadata: {
+ type: 'Cardano',
+ policyId: '1d129dc9c03f95a863489883914f05a52e13135994a32f0cbeacc65e',
+ assetName: '74484f444c53',
+ ticker: '12DEC',
+ longName: '',
+ numberOfDecimals: 12,
+ maxSupply: null,
+ },
+ }),
+ '1d129dc9c03f95a863489883914f05a52e13135994a32f0cbeacc65e.74484f444c55': toTokenInfo({
+ networkId: 300,
+ identifier: '1d129dc9c03f95a863489883914f05a52e13135994a32f0cbeacc65e.74484f444c55',
+ isDefault: false,
+ metadata: {
+ type: 'Cardano',
+ policyId: '1d129dc9c03f95a863489883914f05a52e13135994a32f0cbeacc65e',
+ assetName: '74484f444c53',
+ ticker: '20DEC',
+ longName: '',
+ numberOfDecimals: 20,
+ maxSupply: null,
+ },
+ }),
}
const stakePoolId = 'af22f95915a19cd57adb14c558dcc4a175f60c6193dc23b8bd2d8beb'
diff --git a/src/yoroi-wallets/utils/amountUtils.test.ts b/src/yoroi-wallets/utils/amountUtils.test.ts
index 9998bdd72d..6a391faf75 100644
--- a/src/yoroi-wallets/utils/amountUtils.test.ts
+++ b/src/yoroi-wallets/utils/amountUtils.test.ts
@@ -75,10 +75,6 @@ describe('pastedFormatter', () => {
it('strips unwanted chars', () => {
expect(pastedFormatter('1asdd2q we@3')).toBe('123')
})
-
- it('allows max 6 decimals', () => {
- expect(pastedFormatter('123.12345678')).toBe('123.123456')
- })
})
describe('editedFormatter', () => {
@@ -89,8 +85,4 @@ describe('editedFormatter', () => {
it('does not allow non-numeric chars', () => {
expect(editedFormatter('123a')).toBe('123')
})
-
- it('allows max 6 decimals', () => {
- expect(editedFormatter('123.12345678')).toBe('123.123456')
- })
})
diff --git a/src/yoroi-wallets/utils/amountUtils.ts b/src/yoroi-wallets/utils/amountUtils.ts
index 5937c3935d..2b696f1d5d 100644
--- a/src/yoroi-wallets/utils/amountUtils.ts
+++ b/src/yoroi-wallets/utils/amountUtils.ts
@@ -39,7 +39,6 @@ export const formatMultiLangSeparator = (number: string) => number.replace(/,/g,
export const pastedFormatter = compose(
formatSeparatorWithoutDigits,
- stripExcessiveDecimals,
stripAllButLastDecimalSeparators,
stripCommas,
stripInvalidCharacters,
@@ -47,7 +46,6 @@ export const pastedFormatter = compose(
export const editedFormatter = compose(
formatSeparatorWithoutDigits,
- stripExcessiveDecimals,
stripAllButFirstDecimalSeparator,
formatMultiLangSeparator,
stripInvalidCharacters,
diff --git a/src/yoroi-wallets/utils/utils.test.ts b/src/yoroi-wallets/utils/utils.test.ts
index d8a6099a01..9b984ae1b6 100644
--- a/src/yoroi-wallets/utils/utils.test.ts
+++ b/src/yoroi-wallets/utils/utils.test.ts
@@ -1,3 +1,5 @@
+import BigNumber from 'bignumber.js'
+
import {Quantity, YoroiAmount, YoroiAmounts, YoroiEntries, YoroiEntry} from '../types'
import {RawUtxo} from '../types/other'
import {Amounts, Entries, Quantities, Utxos} from '.'
@@ -30,7 +32,7 @@ describe('Quantities', () => {
expect(Quantities.isGreaterThan('2', '2')).toBe(false)
expect(Quantities.isGreaterThan('2', '1')).toBe(true)
})
- it('toPrecision', () => {
+ it('decimalPlaces', () => {
expect(Quantities.decimalPlaces('1', 2)).toBe('1')
expect(Quantities.decimalPlaces('1.00000', 2)).toBe('1')
expect(Quantities.decimalPlaces('1.123456', 2)).toBe('1.12')
@@ -42,6 +44,36 @@ describe('Quantities', () => {
expect(Quantities.denominated('112345', 3)).toBe('112.345')
expect(Quantities.denominated('1123456', 10)).toBe('0.0001123456')
})
+ it('atomic (decimals fixed)', () => {
+ expect(Quantities.atomic('', 15)).toBe('0000000000000000')
+ expect(Quantities.atomic('', 0)).toBe('0')
+ expect(Quantities.atomic(-1, 3)).toBe('-1000')
+ expect(Quantities.atomic(2.5, 2)).toBe('250')
+ expect(Quantities.atomic('1', 2)).toBe('100')
+ expect(Quantities.atomic('1000', 2)).toBe('100000')
+ expect(Quantities.atomic('123.456', 3)).toBe('123456')
+ expect(Quantities.atomic('1.08', 10)).toBe('10800000000')
+ expect(Quantities.atomic('1.0800001', 3)).toBe('1080')
+ expect(Quantities.atomic(new BigNumber('0000000000015'), 6)).toBe('15000000')
+ expect(Quantities.atomic(new BigNumber(1.5), 6)).toBe('1500000')
+ })
+ it('isZero', () => {
+ expect(Quantities.isZero(Quantities.atomic('', 15))).toBe(true)
+ expect(Quantities.isZero(Quantities.atomic('', 0))).toBe(true)
+ expect(Quantities.isZero(Quantities.atomic('0', 2))).toBe(true)
+ expect(Quantities.isZero(Quantities.atomic('-1', 2))).toBe(false)
+ expect(Quantities.isZero(Quantities.atomic('1', 2))).toBe(false)
+ expect(Quantities.isZero(Quantities.atomic('0.00000000000001', 18))).toBe(false)
+ })
+ it('isIndivisible', () => {
+ expect(Quantities.isIndivisible('1', 0)).toBe(true)
+ expect(Quantities.isIndivisible('-1', 0)).toBe(false)
+ expect(Quantities.isIndivisible('2', 0)).toBe(false)
+ expect(Quantities.isIndivisible('001', 2)).toBe(true)
+ expect(Quantities.isIndivisible('002', 2)).toBe(false)
+ expect(Quantities.isIndivisible('0000000000000000001', 18)).toBe(true)
+ expect(Quantities.isIndivisible('00000000000000000001', 18)).toBe(false)
+ })
})
describe('Amounts', () => {
@@ -138,6 +170,37 @@ describe('Amounts', () => {
{tokenId: 'token567', quantity: '-789'},
] as Array)
})
+
+ describe('save', () => {
+ it('updating when already exists', () => {
+ const amounts: YoroiAmounts = {
+ updateToken: '456',
+ }
+ const updateAmount: YoroiAmount = {
+ tokenId: 'updateToken',
+ quantity: '321',
+ }
+
+ expect(Amounts.save(amounts, updateAmount)).toEqual({
+ updateToken: '321',
+ })
+ })
+
+ it('adding when it doesnt exist', () => {
+ const amounts: YoroiAmounts = {
+ updateToken: '456',
+ }
+ const addAmount: YoroiAmount = {
+ tokenId: 'addToken',
+ quantity: '789',
+ }
+
+ expect(Amounts.save(amounts, addAmount)).toEqual({
+ addToken: '789',
+ updateToken: '456',
+ })
+ })
+ })
})
describe('Entries', () => {
diff --git a/src/yoroi-wallets/utils/utils.ts b/src/yoroi-wallets/utils/utils.ts
index bff113e5b7..e0a3fbdb86 100644
--- a/src/yoroi-wallets/utils/utils.ts
+++ b/src/yoroi-wallets/utils/utils.ts
@@ -62,6 +62,14 @@ export const Amounts = {
quantity: amounts[tokenId] || '0',
}
},
+ save: (amounts: YoroiAmounts, amount: YoroiAmount): YoroiAmounts => {
+ const {tokenId, quantity} = amount
+
+ return {
+ ...amounts,
+ [tokenId]: quantity,
+ }
+ },
toArray: (amounts: YoroiAmounts) =>
Object.keys(amounts).reduce(
(result, current) => [...result, Amounts.getAmount(amounts, current)],
@@ -98,6 +106,22 @@ export const Quantities = {
denominated: (quantity: Quantity, denomination: number) => {
return Quantities.quotient(quantity, `${10 ** denomination}`)
},
+ atomic: (data: Quantity | BigNumber | string | number, denomination: number) => {
+ const stripped = data.toString().replace(/[^0-9.-]/g, '')
+ const value = (stripped.length > 0 && new BigNumber(stripped).isZero() !== true ? stripped : '0') as Quantity
+ return new BigNumber(value).toFixed(denomination).toString().replace(/[.,]/g, '') as Quantity
+ },
+ isZero: (quantity: Quantity) => new BigNumber(quantity).isZero(),
+ isIndivisible: (quantity: Quantity, denomination: number) => {
+ return (
+ quantity ===
+ new BigNumber(1)
+ .dividedBy(new BigNumber(10).pow(denomination))
+ .toFixed(denomination)
+ .toString()
+ .replace(/[.,]/g, '')
+ )
+ },
}
export const asQuantity = (amount: BigNumber | number | string) => new BigNumber(amount).toString() as Quantity
diff --git a/src/yoroi-wallets/utils/validators.ts b/src/yoroi-wallets/utils/validators.ts
index 36455a6378..4b5e8a8763 100644
--- a/src/yoroi-wallets/utils/validators.ts
+++ b/src/yoroi-wallets/utils/validators.ts
@@ -1,12 +1,9 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
-import {Resolution} from '@unstoppabledomains/resolution'
import assert from 'assert'
import {wordlists} from 'bip39'
import _ from 'lodash'
-import {normalizeToAddress} from '../cardano'
-import {getNetworkConfigById} from '../cardano/networks'
-import {NetworkId, Token} from '../types'
+import {Token} from '../types'
import {InvalidAssetAmount, parseAmountDecimal} from './parsing'
export type PasswordValidationErrors = {
@@ -124,51 +121,6 @@ export const getWalletNameError = (
}
}
-export const getUnstoppableDomainAddress = async (domain: string) => {
- try {
- return await new Resolution().addr(domain, 'ADA')
- } catch (e) {
- switch ((e as any).code) {
- case 'UnsupportedDomain':
- throw new Error('{"unsupportedDomain": true}')
- case 'RecordNotFound':
- throw new Error('{"recordNotFound": true}')
- case 'UnregisteredDomain':
- throw new Error('{"unregisteredDomain": true}')
- default:
- throw new Error('{"invalidAddress": true}')
- }
- } // invalid domain
-}
-
-export const isReceiverAddressValid = async (
- receiverAddress: string,
- walletNetworkId: NetworkId,
-): Promise => {
- if (!receiverAddress) {
- return {addressIsRequired: true}
- }
-
- const address = await normalizeToAddress(receiverAddress)
- if (!address) {
- return {invalidAddress: true}
- }
-
- if (walletNetworkId) {
- try {
- const networkConfig: any = getNetworkConfigById(walletNetworkId)
- const configNetworkId = networkConfig.CHAIN_NETWORK_ID && Number(networkConfig.CHAIN_NETWORK_ID)
- const addressNetworkId = await address.networkId()
- if (addressNetworkId !== configNetworkId && !isNaN(configNetworkId)) {
- return {invalidAddress: true}
- }
- } catch (e) {
- // NOTE: should not happen
- return {invalidAddress: true}
- }
- }
-}
-
export const validateAmount = (value: string, token: Token): AmountValidationErrors => {
if (!value) {
return {amountIsRequired: true}