diff --git a/apps/wallet-mobile/.storybook/storybook.requires.js b/apps/wallet-mobile/.storybook/storybook.requires.js
index 7046d09d9b..a41e78585f 100644
--- a/apps/wallet-mobile/.storybook/storybook.requires.js
+++ b/apps/wallet-mobile/.storybook/storybook.requires.js
@@ -173,7 +173,6 @@ const getStories = () => {
"./src/features/Scan/useCases/ShowCameraPermissionDeniedScreen/OpenDeviceAppSettingsButton.stories.tsx": require("../src/features/Scan/useCases/ShowCameraPermissionDeniedScreen/OpenDeviceAppSettingsButton.stories.tsx"),
"./src/features/Scan/useCases/ShowCameraPermissionDeniedScreen/ShowCameraPermissionDeniedScreen.stories.tsx": require("../src/features/Scan/useCases/ShowCameraPermissionDeniedScreen/ShowCameraPermissionDeniedScreen.stories.tsx"),
"./src/features/Send/common/ButtonGroup/ButtonGroup.stories.tsx": require("../src/features/Send/common/ButtonGroup/ButtonGroup.stories.tsx"),
- "./src/features/Send/useCases/ConfirmTx/ConfirmTxScreen.stories.tsx": require("../src/features/Send/useCases/ConfirmTx/ConfirmTxScreen.stories.tsx"),
"./src/features/Send/useCases/ConfirmTx/FailedTx/FailedTxScreen.stories.tsx": require("../src/features/Send/useCases/ConfirmTx/FailedTx/FailedTxScreen.stories.tsx"),
"./src/features/Send/useCases/ConfirmTx/SubmittedTx/SubmittedTxScreen.stories.tsx": require("../src/features/Send/useCases/ConfirmTx/SubmittedTx/SubmittedTxScreen.stories.tsx"),
"./src/features/Send/useCases/ListAmountsToSend/AddToken/AddToken.stories.tsx": require("../src/features/Send/useCases/ListAmountsToSend/AddToken/AddToken.stories.tsx"),
diff --git a/apps/wallet-mobile/src/WalletNavigator.tsx b/apps/wallet-mobile/src/WalletNavigator.tsx
index b67c47d16a..c1d3857a7f 100644
--- a/apps/wallet-mobile/src/WalletNavigator.tsx
+++ b/apps/wallet-mobile/src/WalletNavigator.tsx
@@ -24,6 +24,7 @@ import {useLinksShowActionResult} from './features/Links/common/useLinksShowActi
import {MenuNavigator} from './features/Menu/Menu'
import {PortfolioNavigator} from './features/Portfolio/PortfolioNavigator'
import {CatalystNavigator} from './features/RegisterCatalyst/CatalystNavigator'
+import {ReviewTxNavigator} from './features/ReviewTx/ReviewTxNavigator'
import {SearchProvider} from './features/Search/SearchContext'
import {SettingsScreenNavigator} from './features/Settings'
import {NetworkTag} from './features/Settings/ChangeNetwork/NetworkTag'
@@ -258,6 +259,8 @@ export const WalletNavigator = () => {
+
+
{
divider: color.gray_200,
}
- return {colors, styles}
+ return {colors, styles} as const
}
const messages = defineMessages({
diff --git a/apps/wallet-mobile/src/components/Icon/Direction.tsx b/apps/wallet-mobile/src/components/Icon/Direction.tsx
index 3f803c624d..7d1e4f714b 100644
--- a/apps/wallet-mobile/src/components/Icon/Direction.tsx
+++ b/apps/wallet-mobile/src/components/Icon/Direction.tsx
@@ -1,6 +1,6 @@
import {ThemedPalette, useTheme} from '@yoroi/theme'
import React from 'react'
-import {StyleSheet, View} from 'react-native'
+import {StyleSheet, View, ViewStyle} from 'react-native'
import {TransactionDirection, TransactionInfo} from '../../yoroi-wallets/types/other'
import {Received} from '../Icon/Received'
@@ -11,9 +11,10 @@ import {MultiParty} from './MultiParty'
type Props = {
transaction: TransactionInfo
size?: number
+ containerStyle?: ViewStyle
}
-export const Direction = ({transaction, size = defaultSize}: Props) => {
+export const Direction = ({transaction, size = defaultSize, containerStyle}: Props) => {
const {color} = useTheme()
const {direction} = transaction
@@ -21,7 +22,7 @@ export const Direction = ({transaction, size = defaultSize}: Props) => {
const IconComponent = iconMap[direction]
return (
-
+
)
diff --git a/apps/wallet-mobile/src/components/Info/Info.tsx b/apps/wallet-mobile/src/components/Info/Info.tsx
new file mode 100644
index 0000000000..b4272cc14e
--- /dev/null
+++ b/apps/wallet-mobile/src/components/Info/Info.tsx
@@ -0,0 +1,47 @@
+import {useTheme} from '@yoroi/theme'
+import React, {ReactNode} from 'react'
+import {StyleSheet, Text, View} from 'react-native'
+
+import {Icon} from '../Icon'
+import {Space} from '../Space/Space'
+
+type Props = {
+ content: ReactNode
+ iconSize?: number
+}
+
+export const Info = ({content, iconSize = 30}: Props) => {
+ const {styles, colors} = useStyles()
+
+ return (
+
+
+
+
+
+ {content}
+
+ )
+}
+
+const useStyles = () => {
+ const {color, atoms} = useTheme()
+ const styles = StyleSheet.create({
+ notice: {
+ backgroundColor: color.sys_cyan_100,
+ ...atoms.p_md,
+ ...atoms.rounded_sm,
+ },
+ text: {
+ ...atoms.body_2_md_regular,
+ color: color.text_gray_max,
+ },
+ })
+
+ const colors = {
+ yellow: color.sys_orange_500,
+ blue: color.primary_500,
+ }
+
+ return {colors, styles} as const
+}
diff --git a/apps/wallet-mobile/src/components/Warning/Warning.tsx b/apps/wallet-mobile/src/components/Warning/Warning.tsx
index 320ee76732..ce1a959985 100644
--- a/apps/wallet-mobile/src/components/Warning/Warning.tsx
+++ b/apps/wallet-mobile/src/components/Warning/Warning.tsx
@@ -5,7 +5,10 @@ import {StyleSheet, Text, View} from 'react-native'
import {Icon} from '../Icon'
import {Space} from '../Space/Space'
-type Props = {content: ReactNode; iconSize?: number}
+type Props = {
+ content: ReactNode
+ iconSize?: number
+}
export const Warning = ({content, iconSize = 30}: Props) => {
const {styles, colors} = useStyles()
@@ -26,8 +29,8 @@ const useStyles = () => {
const styles = StyleSheet.create({
notice: {
backgroundColor: color.sys_yellow_100,
- padding: 12,
- borderRadius: 8,
+ ...atoms.p_md,
+ ...atoms.rounded_sm,
},
text: {
...atoms.body_2_md_regular,
@@ -39,5 +42,5 @@ const useStyles = () => {
yellow: color.sys_orange_500,
}
- return {colors, styles}
+ return {colors, styles} as const
}
diff --git a/apps/wallet-mobile/src/features/Discover/useCases/ReviewTransaction/ReviewTransaction.tsx b/apps/wallet-mobile/src/features/Discover/useCases/ReviewTransaction/ReviewTransaction.tsx
index bfbf280cd5..2bd93f55f2 100644
--- a/apps/wallet-mobile/src/features/Discover/useCases/ReviewTransaction/ReviewTransaction.tsx
+++ b/apps/wallet-mobile/src/features/Discover/useCases/ReviewTransaction/ReviewTransaction.tsx
@@ -447,7 +447,7 @@ const useConnectorPromptRootKey = () => {
}, [promptRootKey])
}
-const useSignTxWithHW = () => {
+export const useSignTxWithHW = () => {
const {confirmHWConnection, closeModal} = useConfirmHWConnectionModal()
const {wallet, meta} = useSelectedWallet()
diff --git a/apps/wallet-mobile/src/features/ReviewTx/ReviewTxNavigator.tsx b/apps/wallet-mobile/src/features/ReviewTx/ReviewTxNavigator.tsx
new file mode 100644
index 0000000000..4f28322aff
--- /dev/null
+++ b/apps/wallet-mobile/src/features/ReviewTx/ReviewTxNavigator.tsx
@@ -0,0 +1,36 @@
+import {createStackNavigator} from '@react-navigation/stack'
+import {Atoms, ThemedPalette, useTheme} from '@yoroi/theme'
+import React from 'react'
+
+import {Boundary} from '../../components/Boundary/Boundary'
+import {defaultStackNavigationOptions, ReviewTxRoutes} from '../../kernel/navigation'
+import {useStrings} from './common/hooks/useStrings'
+import {ReviewTxScreen} from './useCases/ReviewTxScreen/ReviewTxScreen'
+
+export const Stack = createStackNavigator()
+
+export const ReviewTxNavigator = () => {
+ const {atoms, color} = useTheme()
+ const strings = useStrings()
+
+ return (
+
+
+ {() => (
+
+
+
+ )}
+
+
+ )
+}
+
+const screenOptions = (atoms: Atoms, color: ThemedPalette) => ({
+ ...defaultStackNavigationOptions(atoms, color),
+ gestureEnabled: true,
+})
diff --git a/apps/wallet-mobile/src/features/ReviewTx/common/Address.tsx b/apps/wallet-mobile/src/features/ReviewTx/common/Address.tsx
new file mode 100644
index 0000000000..5279b07df9
--- /dev/null
+++ b/apps/wallet-mobile/src/features/ReviewTx/common/Address.tsx
@@ -0,0 +1,73 @@
+import {useTheme} from '@yoroi/theme'
+import * as React from 'react'
+import {StyleSheet, Text, TextStyle, TouchableOpacity, View} from 'react-native'
+
+import {Icon} from '../../../components/Icon'
+import {Space} from '../../../components/Space/Space'
+import {useCopy} from '../../../hooks/useCopy'
+
+export const Address = ({
+ address,
+ index,
+ textStyle,
+ multiline = false,
+}: {
+ address: string
+ index?: number
+ textStyle?: TextStyle
+ multiline?: boolean
+}) => {
+ const {styles, colors} = useStyles()
+ const [, copy] = useCopy()
+
+ return (
+
+
+ {address}
+
+
+ {index !== undefined && (
+ <>
+
+
+ {`#${index}`}
+
+
+ >
+ )}
+
+ copy(address)} activeOpacity={0.5}>
+
+
+
+ )
+}
+
+const useStyles = () => {
+ const {atoms, color} = useTheme()
+ const styles = StyleSheet.create({
+ address: {
+ ...atoms.flex_row,
+ ...atoms.justify_between,
+ },
+ addressText: {
+ ...atoms.flex_1,
+ ...atoms.body_2_md_regular,
+ color: color.text_gray_medium,
+ },
+ index: {
+ ...atoms.body_2_md_medium,
+ color: color.text_gray_medium,
+ },
+ })
+
+ const colors = {
+ copy: color.gray_900,
+ }
+
+ return {styles, colors} as const
+}
diff --git a/apps/wallet-mobile/src/features/ReviewTx/common/CollapsibleSection.tsx b/apps/wallet-mobile/src/features/ReviewTx/common/CollapsibleSection.tsx
new file mode 100644
index 0000000000..048ac41d0e
--- /dev/null
+++ b/apps/wallet-mobile/src/features/ReviewTx/common/CollapsibleSection.tsx
@@ -0,0 +1,71 @@
+import {useTheme} from '@yoroi/theme'
+import * as React from 'react'
+import {Animated, LayoutAnimation, StyleSheet, Text, TouchableOpacity, View} from 'react-native'
+
+import {Icon} from '../../../components/Icon'
+
+export const CollapsibleSection = ({label, children}: {label: string; children: React.ReactNode}) => {
+ const {styles, colors} = useStyles()
+ const [isOpen, setIsOpen] = React.useState(false)
+ const animatedHeight = React.useRef(new Animated.Value(0)).current
+
+ const toggleSection = () => {
+ LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut)
+ setIsOpen(!isOpen)
+ Animated.timing(animatedHeight, {
+ toValue: isOpen ? 0 : 1,
+ duration: 300,
+ useNativeDriver: false,
+ }).start()
+ }
+
+ return (
+ <>
+
+ {label}
+
+
+
+
+
+
+
+ {children}
+
+ >
+ )
+}
+
+const useStyles = () => {
+ const {atoms, color} = useTheme()
+ const styles = StyleSheet.create({
+ sectionHeader: {
+ ...atoms.flex_row,
+ ...atoms.justify_between,
+ },
+ sectionHeaderText: {
+ ...atoms.body_1_lg_medium,
+ color: color.text_gray_medium,
+ },
+ childrenContainer: {
+ overflow: 'hidden',
+ },
+ })
+
+ const colors = {
+ chevron: color.gray_900,
+ }
+
+ return {styles, colors} as const
+}
diff --git a/apps/wallet-mobile/src/features/ReviewTx/common/Divider.tsx b/apps/wallet-mobile/src/features/ReviewTx/common/Divider.tsx
new file mode 100644
index 0000000000..1733930fda
--- /dev/null
+++ b/apps/wallet-mobile/src/features/ReviewTx/common/Divider.tsx
@@ -0,0 +1,31 @@
+import {SpacingSize, useTheme} from '@yoroi/theme'
+import * as React from 'react'
+import {StyleSheet, View} from 'react-native'
+
+import {Space} from '../../../components/Space/Space'
+
+export const Divider = ({verticalSpace = 'none'}: {verticalSpace?: SpacingSize}) => {
+ const {styles} = useStyles()
+ return (
+ <>
+
+
+
+
+
+ >
+ )
+}
+
+const useStyles = () => {
+ const {atoms, color} = useTheme()
+ const styles = StyleSheet.create({
+ divider: {
+ height: 1,
+ ...atoms.align_stretch,
+ backgroundColor: color.gray_200,
+ },
+ })
+
+ return {styles} as const
+}
diff --git a/apps/wallet-mobile/src/features/ReviewTx/common/TokenItem.tsx b/apps/wallet-mobile/src/features/ReviewTx/common/TokenItem.tsx
new file mode 100644
index 0000000000..751a3bbd56
--- /dev/null
+++ b/apps/wallet-mobile/src/features/ReviewTx/common/TokenItem.tsx
@@ -0,0 +1,76 @@
+import {useTheme} from '@yoroi/theme'
+import * as React from 'react'
+import {StyleSheet, Text, View} from 'react-native'
+
+export const TokenItem = ({
+ isPrimaryToken = true,
+ isSent = true,
+ label,
+}: {
+ isPrimaryToken?: boolean
+ isSent?: boolean
+ label: string
+}) => {
+ const {styles} = useStyles()
+
+ if (!isSent)
+ return (
+
+
+ {label}
+
+
+ )
+
+ return (
+
+ {label}
+
+ )
+}
+
+const useStyles = () => {
+ const {atoms, color} = useTheme()
+ const styles = StyleSheet.create({
+ sentTokenItem: {
+ ...atoms.flex,
+ ...atoms.flex_row,
+ ...atoms.align_center,
+ ...atoms.py_xs,
+ ...atoms.px_md,
+ borderRadius: 8,
+ backgroundColor: color.primary_500,
+ },
+ receivedTokenItem: {
+ ...atoms.flex,
+ ...atoms.flex_row,
+ ...atoms.align_center,
+ ...atoms.py_xs,
+ ...atoms.px_md,
+ borderRadius: 8,
+ backgroundColor: color.secondary_300,
+ },
+ tokenSentItemText: {
+ ...atoms.body_2_md_regular,
+ color: color.white_static,
+ },
+ tokenReceivedItemText: {
+ ...atoms.body_2_md_regular,
+ color: color.text_gray_max,
+ },
+ notPrimarySentTokenItem: {
+ backgroundColor: color.primary_100,
+ },
+ notPrimaryReceivedTokenItem: {
+ backgroundColor: color.secondary_100,
+ },
+ notPrimarySentTokenItemText: {
+ color: color.text_primary_medium,
+ },
+ notPrimaryReceivedTokenItemText: {
+ color: color.secondary_700,
+ },
+ })
+
+ return {styles} as const
+}
diff --git a/apps/wallet-mobile/src/features/ReviewTx/common/hooks/useAddressType.tsx b/apps/wallet-mobile/src/features/ReviewTx/common/hooks/useAddressType.tsx
new file mode 100644
index 0000000000..6e054df944
--- /dev/null
+++ b/apps/wallet-mobile/src/features/ReviewTx/common/hooks/useAddressType.tsx
@@ -0,0 +1,12 @@
+import {useQuery} from 'react-query'
+
+import {getAddressType} from '../../../../yoroi-wallets/cardano/utils'
+
+export const useAddressType = (address: string) => {
+ const query = useQuery(['useAddressType', address], () => getAddressType(address), {
+ suspense: true,
+ })
+
+ if (query.data === undefined) throw new Error('invalid address type')
+ return query.data
+}
diff --git a/apps/wallet-mobile/src/features/ReviewTx/common/hooks/useFormattedTx.tsx b/apps/wallet-mobile/src/features/ReviewTx/common/hooks/useFormattedTx.tsx
new file mode 100644
index 0000000000..08a8d1ebfe
--- /dev/null
+++ b/apps/wallet-mobile/src/features/ReviewTx/common/hooks/useFormattedTx.tsx
@@ -0,0 +1,226 @@
+import {invalid, isNonNullable} from '@yoroi/common'
+import {infoExtractName} from '@yoroi/portfolio'
+import {Portfolio} from '@yoroi/types'
+import * as _ from 'lodash'
+import {useQuery} from 'react-query'
+
+import {YoroiWallet} from '../../../../yoroi-wallets/cardano/types'
+import {wrappedCsl} from '../../../../yoroi-wallets/cardano/wrappedCsl'
+import {formatTokenWithText} from '../../../../yoroi-wallets/utils/format'
+import {asQuantity} from '../../../../yoroi-wallets/utils/utils'
+import {usePortfolioTokenInfos} from '../../../Portfolio/common/hooks/usePortfolioTokenInfos'
+import {useSelectedWallet} from '../../../WalletManager/common/hooks/useSelectedWallet'
+import {
+ FormattedFee,
+ FormattedInputs,
+ FormattedOutputs,
+ TransactionBody,
+ TransactionInputs,
+ TransactionOutputs,
+} from '../types'
+
+export type FormattedTx = ReturnType
+export const useFormattedTx = (data: TransactionBody) => {
+ const {wallet} = useSelectedWallet()
+
+ const inputs = data?.inputs ?? []
+ const outputs = data?.outputs ?? []
+
+ const inputTokenIds = inputs.flatMap((i) => {
+ const receiveUTxO = getUtxoByTxIdAndIndex(wallet, i.transaction_id, i.index)
+ return receiveUTxO?.assets.map((a) => `${a.policyId}.${a.assetId}` as Portfolio.Token.Id) ?? []
+ })
+
+ const outputTokenIds = outputs.flatMap((o) => {
+ if (!o.amount.multiasset) return []
+ const policyIds = Object.keys(o.amount.multiasset)
+ const tokenIds = policyIds.flatMap((policyId) => {
+ const assetIds = Object.keys(o.amount.multiasset?.[policyId] ?? {})
+ return assetIds.map((assetId) => `${policyId}.${assetId}` as Portfolio.Token.Id)
+ })
+ return tokenIds
+ })
+
+ const tokenIds = _.uniq([...inputTokenIds, ...outputTokenIds])
+ const portfolioTokenInfos = usePortfolioTokenInfos({wallet, tokenIds}, {suspense: true})
+
+ const formattedInputs = useFormattedInputs(wallet, inputs, portfolioTokenInfos)
+ const formattedOutputs = useFormattedOutputs(wallet, outputs, portfolioTokenInfos)
+ const formattedFee = formatFee(wallet, data)
+
+ return {inputs: formattedInputs, outputs: formattedOutputs, fee: formattedFee}
+}
+
+export const useFormattedInputs = (
+ wallet: YoroiWallet,
+ inputs: TransactionInputs,
+ tokenInfosResult: ReturnType,
+) => {
+ const query = useQuery(
+ ['useFormattedInputs', inputs],
+ async () => formatInputs(wallet, inputs, tokenInfosResult),
+ {
+ suspense: true,
+ },
+ )
+
+ if (!query.data) throw new Error('invalid formatted inputs')
+ return query.data
+}
+
+export const useFormattedOutputs = (
+ wallet: YoroiWallet,
+ outputs: TransactionOutputs,
+ portfolioTokenInfos: ReturnType,
+) => {
+ const query = useQuery(
+ ['useFormattedOutputs', outputs],
+ () => formatOutputs(wallet, outputs, portfolioTokenInfos),
+ {
+ suspense: true,
+ },
+ )
+
+ if (!query.data) throw new Error('invalid formatted outputs')
+ return query.data
+}
+
+const formatInputs = async (
+ wallet: YoroiWallet,
+ inputs: TransactionInputs,
+ portfolioTokenInfos: ReturnType,
+): Promise => {
+ return Promise.all(
+ inputs.map(async (input) => {
+ const receiveUTxO = getUtxoByTxIdAndIndex(wallet, input.transaction_id, input.index)
+ const address = receiveUTxO?.receiver
+ const rewardAddress =
+ address !== undefined ? await deriveRewardAddressFromAddress(address, wallet.networkManager.chainId) : null
+ const coin = receiveUTxO?.amount != null ? asQuantity(receiveUTxO.amount) : null
+
+ const primaryAssets =
+ coin != null
+ ? [
+ {
+ name: wallet.portfolioPrimaryTokenInfo.name,
+ label: formatTokenWithText(coin, wallet.portfolioPrimaryTokenInfo),
+ quantity: coin,
+ isPrimary: true,
+ },
+ ]
+ : []
+
+ const multiAssets =
+ receiveUTxO?.assets
+ .map((a) => {
+ const tokenInfo = portfolioTokenInfos.tokenInfos?.get(a.assetId as Portfolio.Token.Id)
+ if (!tokenInfo) return null
+ const quantity = asQuantity(a.amount)
+
+ return {
+ name: infoExtractName(tokenInfo),
+ label: formatTokenWithText(quantity, tokenInfo),
+ quantity: quantity,
+ isPrimary: false,
+ }
+ })
+ .filter(Boolean) ?? []
+
+ return {
+ assets: [...primaryAssets, ...multiAssets].filter(isNonNullable),
+ address,
+ rewardAddress,
+ ownAddress: address != null && isOwnedAddress(wallet, address),
+ txIndex: input.index,
+ txHash: input.transaction_id,
+ }
+ }),
+ )
+}
+
+const formatOutputs = async (
+ wallet: YoroiWallet,
+ outputs: TransactionOutputs,
+ portfolioTokenInfos: ReturnType,
+): Promise => {
+ return Promise.all(
+ outputs.map(async (output) => {
+ const address = output.address
+ const rewardAddress = await deriveRewardAddressFromAddress(address, wallet.networkManager.chainId)
+ const coin = asQuantity(output.amount.coin)
+
+ const primaryAssets = [
+ {
+ name: wallet.portfolioPrimaryTokenInfo.name,
+ label: formatTokenWithText(coin, wallet.portfolioPrimaryTokenInfo),
+ quantity: coin,
+ isPrimary: true,
+ },
+ ]
+
+ const multiAssets = output.amount.multiasset
+ ? Object.entries(output.amount.multiasset).flatMap(([policyId, assets]) => {
+ return Object.entries(assets).map(([assetId, amount]) => {
+ const tokenInfo = portfolioTokenInfos.tokenInfos?.get(`${policyId}.${assetId}`)
+ if (tokenInfo == null) return null
+ const quantity = asQuantity(amount)
+
+ return {
+ name: infoExtractName(tokenInfo),
+ label: formatTokenWithText(quantity, tokenInfo),
+ quantity,
+ isPrimary: false,
+ }
+ })
+ })
+ : []
+
+ const assets = [...primaryAssets, ...multiAssets].filter(isNonNullable)
+
+ return {
+ assets,
+ address,
+ rewardAddress,
+ ownAddress: isOwnedAddress(wallet, address),
+ }
+ }),
+ )
+}
+
+export const formatFee = (wallet: YoroiWallet, data: TransactionBody): FormattedFee => {
+ const fee = asQuantity(data?.fee ?? '0')
+
+ return {
+ name: wallet.portfolioPrimaryTokenInfo.name,
+ label: formatTokenWithText(fee, wallet.portfolioPrimaryTokenInfo),
+ quantity: fee,
+ isPrimary: true,
+ }
+}
+
+export const deriveRewardAddressFromAddress = async (address: string, chainId: number): Promise => {
+ const {csl, release} = wrappedCsl()
+
+ try {
+ const result = await csl.Address.fromBech32(address)
+ .then((address) => csl.BaseAddress.fromAddress(address))
+ .then((baseAddress) => baseAddress?.stakeCred() ?? invalid('invalid base address'))
+ .then((stakeCredential) => csl.RewardAddress.new(chainId, stakeCredential))
+ .then((rewardAddress) => rewardAddress.toAddress())
+ .then((rewardAddrAsAddress) => rewardAddrAsAddress.toBech32(undefined))
+ .catch((error) => error)
+
+ if (typeof result !== 'string') throw new Error('Its not possible to derive reward address')
+ return result
+ } finally {
+ release()
+ }
+}
+
+const getUtxoByTxIdAndIndex = (wallet: YoroiWallet, txId: string, index: number) => {
+ return wallet.utxos.find((u) => u.tx_hash === txId && u.tx_index === index)
+}
+
+const isOwnedAddress = (wallet: YoroiWallet, bech32Address: string) => {
+ return wallet.internalAddresses.includes(bech32Address) || wallet.externalAddresses.includes(bech32Address)
+}
diff --git a/apps/wallet-mobile/src/features/ReviewTx/common/hooks/useOnConfirm.tsx b/apps/wallet-mobile/src/features/ReviewTx/common/hooks/useOnConfirm.tsx
new file mode 100644
index 0000000000..dfb076eab5
--- /dev/null
+++ b/apps/wallet-mobile/src/features/ReviewTx/common/hooks/useOnConfirm.tsx
@@ -0,0 +1,71 @@
+import * as React from 'react'
+
+import {ConfirmTxWithHwModal} from '../../../../components/ConfirmTxWithHwModal/ConfirmTxWithHwModal'
+import {ConfirmTxWithOsModal} from '../../../../components/ConfirmTxWithOsModal/ConfirmTxWithOsModal'
+import {ConfirmTxWithSpendingPasswordModal} from '../../../../components/ConfirmTxWithSpendingPasswordModal/ConfirmTxWithSpendingPasswordModal'
+import {useModal} from '../../../../components/Modal/ModalContext'
+import {YoroiSignedTx, YoroiUnsignedTx} from '../../../../yoroi-wallets/types/yoroi'
+import {useSelectedWallet} from '../../../WalletManager/common/hooks/useSelectedWallet'
+import {useStrings} from './useStrings'
+
+// TODO: make it compatible with CBOR signing
+export const useOnConfirm = ({
+ unsignedTx,
+ onSuccess,
+ onError,
+ onNotSupportedCIP1694,
+}: {
+ onSuccess: (txId: YoroiSignedTx) => void
+ onError: () => void
+ cbor?: string
+ unsignedTx?: YoroiUnsignedTx
+ onNotSupportedCIP1694?: () => void
+}) => {
+ if (unsignedTx === undefined) throw new Error('useOnConfirm: unsignedTx missing')
+
+ const {meta} = useSelectedWallet()
+ const {openModal, closeModal} = useModal()
+ const strings = useStrings()
+
+ const onConfirm = () => {
+ if (meta.isHW) {
+ openModal(
+ strings.signTransaction,
+ onSuccess(signedTx)}
+ onNotSupportedCIP1694={onNotSupportedCIP1694}
+ />,
+ 400,
+ )
+ return
+ }
+
+ if (!meta.isHW && !meta.isEasyConfirmationEnabled) {
+ openModal(
+ strings.signTransaction,
+ onSuccess(signedTx)}
+ onError={onError}
+ />,
+ )
+ return
+ }
+
+ if (!meta.isHW && meta.isEasyConfirmationEnabled) {
+ openModal(
+ strings.signTransaction,
+ onSuccess(signedTx)}
+ onError={onError}
+ />,
+ )
+ return
+ }
+ }
+
+ return {onConfirm}
+}
diff --git a/apps/wallet-mobile/src/features/ReviewTx/common/hooks/useStrings.tsx b/apps/wallet-mobile/src/features/ReviewTx/common/hooks/useStrings.tsx
new file mode 100644
index 0000000000..54d8581849
--- /dev/null
+++ b/apps/wallet-mobile/src/features/ReviewTx/common/hooks/useStrings.tsx
@@ -0,0 +1,84 @@
+import {defineMessages, useIntl} from 'react-intl'
+
+import {txLabels} from '../../../../kernel/i18n/global-messages'
+
+export const useStrings = () => {
+ const intl = useIntl()
+
+ return {
+ signTransaction: intl.formatMessage(txLabels.signingTx),
+ confirm: intl.formatMessage(messages.confirm),
+ title: intl.formatMessage(messages.title),
+ utxosTab: intl.formatMessage(messages.utxosTab),
+ overviewTab: intl.formatMessage(messages.overviewTab),
+ walletLabel: intl.formatMessage(messages.walletLabel),
+ feeLabel: intl.formatMessage(messages.feeLabel),
+ myWalletLabel: intl.formatMessage(messages.myWalletLabel),
+ sendLabel: intl.formatMessage(messages.sendLabel),
+ receiveToLabel: intl.formatMessage(messages.receiveToLabel),
+ receiveToScriptLabel: intl.formatMessage(messages.receiveToScriptLabel),
+ utxosInputsLabel: intl.formatMessage(messages.utxosInputsLabel),
+ utxosOutputsLabel: intl.formatMessage(messages.utxosOutputsLabel),
+ utxosYourAddressLabel: intl.formatMessage(messages.utxosYourAddressLabel),
+ utxosForeignAddressLabel: intl.formatMessage(messages.utxosForeignAddressLabel),
+ }
+}
+
+const messages = defineMessages({
+ confirm: {
+ id: 'txReview.confirm',
+ defaultMessage: '!!!Confirm',
+ },
+ title: {
+ id: 'txReview.title',
+ defaultMessage: '!!!UTxOs',
+ },
+ utxosTab: {
+ id: 'txReview.tabLabel.utxos',
+ defaultMessage: '!!!UTxOs',
+ },
+ overviewTab: {
+ id: 'txReview.tabLabel.overview',
+ defaultMessage: '!!!Overview',
+ },
+ walletLabel: {
+ id: 'txReview.overview.wallet',
+ defaultMessage: '!!!Wallet',
+ },
+ feeLabel: {
+ id: 'txReview.fee',
+ defaultMessage: '!!!Fee',
+ },
+ myWalletLabel: {
+ id: 'txReview.overview.myWalletLabel',
+ defaultMessage: '!!!Your Wallet',
+ },
+ sendLabel: {
+ id: 'txReview.overview.sendLabel',
+ defaultMessage: '!!!Send',
+ },
+ receiveToLabel: {
+ id: 'txReview.overview.receiveToLabel',
+ defaultMessage: '!!!receiveToLabel',
+ },
+ receiveToScriptLabel: {
+ id: 'txReview.overview.receiveToScriptLabel',
+ defaultMessage: '!!!To script',
+ },
+ utxosInputsLabel: {
+ id: 'txReview.utxos.utxosInputsLabel',
+ defaultMessage: '!!!Inputs',
+ },
+ utxosOutputsLabel: {
+ id: 'txReview.utxos.utxosOutputsLabel',
+ defaultMessage: '!!!Outputs',
+ },
+ utxosYourAddressLabel: {
+ id: 'txReview.utxos.utxosYourAddressLabel',
+ defaultMessage: '!!!Your address',
+ },
+ utxosForeignAddressLabel: {
+ id: 'txReview.utxos.utxosForeignAddressLabel',
+ defaultMessage: '!!!Foreign address',
+ },
+})
diff --git a/apps/wallet-mobile/src/features/ReviewTx/common/hooks/useTxBody.tsx b/apps/wallet-mobile/src/features/ReviewTx/common/hooks/useTxBody.tsx
new file mode 100644
index 0000000000..9de33bb075
--- /dev/null
+++ b/apps/wallet-mobile/src/features/ReviewTx/common/hooks/useTxBody.tsx
@@ -0,0 +1,44 @@
+import {useQuery} from 'react-query'
+
+import {wrappedCsl} from '../../../../yoroi-wallets/cardano/wrappedCsl'
+import {YoroiUnsignedTx} from '../../../../yoroi-wallets/types/yoroi'
+
+export const useTxBody = ({cbor, unsignedTx}: {cbor?: string; unsignedTx?: YoroiUnsignedTx}) => {
+ const query = useQuery(
+ ['useTxBody', cbor, unsignedTx],
+ async () => {
+ if (cbor !== undefined) {
+ return getCborTxBody(cbor)
+ } else if (unsignedTx !== undefined) {
+ return getUnsignedTxTxBody(unsignedTx)
+ } else {
+ throw new Error('useTxBody: missing cbor and unsignedTx')
+ }
+ },
+ {
+ useErrorBoundary: true,
+ suspense: true,
+ },
+ )
+
+ if (query.data === undefined) throw new Error('useTxBody: cannot extract txBody')
+ return query.data
+}
+const getCborTxBody = async (cbor: string) => {
+ const {csl, release} = wrappedCsl()
+ try {
+ const tx = await csl.Transaction.fromHex(cbor)
+ const jsonString = await tx.toJson()
+ return JSON.parse(jsonString).body
+ } finally {
+ release()
+ }
+}
+
+const getUnsignedTxTxBody = async (unsignedTx: YoroiUnsignedTx) => {
+ const {
+ unsignedTx: {txBody},
+ } = unsignedTx
+ const txBodyjson = await txBody.toJson()
+ return JSON.parse(txBodyjson)
+}
diff --git a/apps/wallet-mobile/src/features/ReviewTx/common/mocks.ts b/apps/wallet-mobile/src/features/ReviewTx/common/mocks.ts
new file mode 100644
index 0000000000..12fbe9274a
--- /dev/null
+++ b/apps/wallet-mobile/src/features/ReviewTx/common/mocks.ts
@@ -0,0 +1,148 @@
+import {TransactionBody} from './types'
+
+export const adaTransactionSingleReceiver: TransactionBody = {
+ inputs: [
+ {
+ transaction_id: '46fe71d85a733d970fe7bb8e6586624823803936d18c7e14601713d05b5b287a',
+ index: 0,
+ },
+ {
+ transaction_id: '9638640d421875f068d10a0125023601bbd7e83e7f17b721c9c06c97cc29ff66',
+ index: 1,
+ },
+ ],
+ outputs: [
+ {
+ address:
+ 'addr1qyf4x8lvcyrwcxzkyz3lykyzfu7s7x307dlafgsu89qzge8lfl229ahk888cgakug24y86qtduvn065c3gw7dg5002cqdskm74',
+ amount: {
+ coin: '12000000',
+ multiasset: null,
+ },
+ plutus_data: null,
+ script_ref: null,
+ },
+ {
+ address:
+ 'addr1q9xy5p0cz2zsjrzpg4tl59mltjmfh07yc28alchxjlanygk0ppwv8x4ylafdu84xqmh9sx4vrk4czekksv884xmvanwqde82xg',
+ amount: {
+ coin: '23464562',
+ multiasset: null,
+ },
+ plutus_data: null,
+ script_ref: null,
+ },
+ ],
+ fee: '174345',
+ ttl: '220373661',
+ certs: null,
+ withdrawals: null,
+ update: null,
+ auxiliary_data_hash: null,
+ validity_start_interval: null,
+ mint: null,
+ script_data_hash: null,
+ collateral: null,
+ required_signers: null,
+ network_id: null,
+ collateral_return: null,
+ total_collateral: null,
+ reference_inputs: null,
+ voting_procedures: null,
+ voting_proposals: null,
+ donation: null,
+ current_treasury_value: null,
+}
+
+export const multiAssetsTransactionOneReceiver: TransactionBody = {
+ inputs: [
+ {
+ transaction_id: '46fe71d85a733d970fe7bb8e6586624823803936d18c7e14601713d05b5b287a',
+ index: 0,
+ },
+ {
+ transaction_id: 'bddd3e0b43b9b93f6d49190a9d4d55c3cd28e3d270b0f1bbc0f83b8ecc3e373a',
+ index: 1,
+ },
+ ],
+ outputs: [
+ {
+ address:
+ 'addr1qyf4x8lvcyrwcxzkyz3lykyzfu7s7x307dlafgsu89qzge8lfl229ahk888cgakug24y86qtduvn065c3gw7dg5002cqdskm74',
+ amount: {
+ coin: '10000000',
+ multiasset: {
+ cdaaee586376139ee8c3cc4061623968810d177ca5c300afb890b48a: {
+ '43415354': '5',
+ },
+ f0ff48bbb7bbe9d59a40f1ce90e9e9d0ff5002ec48f232b49ca0fb9a: {
+ '000de1406a6176696275656e6f': '1',
+ },
+ },
+ },
+ plutus_data: null,
+ script_ref: null,
+ },
+ {
+ address:
+ 'addr1q9xy5p0cz2zsjrzpg4tl59mltjmfh07yc28alchxjlanygk0ppwv8x4ylafdu84xqmh9sx4vrk4czekksv884xmvanwqde82xg',
+ amount: {
+ coin: '2228270',
+ multiasset: {
+ '2441ab3351c3b80213a98f4e09ddcf7dabe4879c3c94cc4e7205cb63': {
+ '46495245': '2531',
+ },
+ '279c909f348e533da5808898f87f9a14bb2c3dfbbacccd631d927a3f': {
+ '534e454b': '204',
+ },
+ '4cb48d60d1f7823d1307c61b9ecf472ff78cf22d1ccc5786d59461f8': {
+ '4144414d4f4f4e': '4983996',
+ },
+ a0028f350aaabe0545fdcb56b039bfb08e4bb4d8c4d7c3c7d481c235: {
+ '484f534b59': '115930085',
+ },
+ cdaaee586376139ee8c3cc4061623968810d177ca5c300afb890b48a: {
+ '43415354': '4503',
+ },
+ e0c4c2d7c4a0ed2cf786753fd845dee82c45512cee03e92adfd3fb8d: {
+ '6a6176696275656e6f2e616461': '1',
+ },
+ fc411f546d01e88a822200243769bbc1e1fbdde8fa0f6c5179934edb: {
+ '6a6176696275656e6f': '1',
+ },
+ },
+ },
+ plutus_data: null,
+ script_ref: null,
+ },
+ {
+ address:
+ 'addr1q9xy5p0cz2zsjrzpg4tl59mltjmfh07yc28alchxjlanygk0ppwv8x4ylafdu84xqmh9sx4vrk4czekksv884xmvanwqde82xg',
+ amount: {
+ coin: '2300311',
+ multiasset: null,
+ },
+ plutus_data: null,
+ script_ref: null,
+ },
+ ],
+ fee: '189349',
+ ttl: '93045',
+ certs: null,
+ withdrawals: null,
+ update: null,
+ auxiliary_data_hash: null,
+ validity_start_interval: null,
+ mint: null,
+ script_data_hash: null,
+ collateral: null,
+ required_signers: null,
+ network_id: null,
+ collateral_return: null,
+ total_collateral: null,
+ reference_inputs: null,
+ voting_procedures: null,
+ voting_proposals: null,
+ donation: null,
+ current_treasury_value: null,
+}
diff --git a/apps/wallet-mobile/src/features/ReviewTx/common/types.ts b/apps/wallet-mobile/src/features/ReviewTx/common/types.ts
new file mode 100644
index 0000000000..935f333b56
--- /dev/null
+++ b/apps/wallet-mobile/src/features/ReviewTx/common/types.ts
@@ -0,0 +1,891 @@
+import {Balance} from '@yoroi/types'
+
+export type TransactionDetails = {
+ id: string
+ walletPlate: React.ReactNode
+ walletName: string
+ createdBy: string | null
+ fee: string
+ txBody: TransactionBody
+}
+
+export type Address = string
+export type URL = string
+
+export interface Anchor {
+ anchor_data_hash: string
+ anchor_url: URL
+}
+export type AnchorDataHash = string
+export type AssetName = string
+export type AssetNames = string[]
+export interface Assets {
+ [k: string]: string
+}
+export type NativeScript =
+ | {
+ ScriptPubkey: ScriptPubkey
+ }
+ | {
+ ScriptAll: ScriptAll
+ }
+ | {
+ ScriptAny: ScriptAny
+ }
+ | {
+ ScriptNOfK: ScriptNOfK
+ }
+ | {
+ TimelockStart: TimelockStart
+ }
+ | {
+ TimelockExpiry: TimelockExpiry
+ }
+export type NativeScripts = NativeScript[]
+export type PlutusScripts = string[]
+
+export interface AuxiliaryData {
+ metadata?: {
+ [k: string]: string
+ } | null
+ native_scripts?: NativeScripts | null
+ plutus_scripts?: PlutusScripts | null
+ prefer_alonzo_format: boolean
+}
+export interface ScriptPubkey {
+ addr_keyhash: string
+}
+export interface ScriptAll {
+ native_scripts: NativeScripts
+}
+export interface ScriptAny {
+ native_scripts: NativeScripts
+}
+export interface ScriptNOfK {
+ n: number
+ native_scripts: NativeScripts
+}
+export interface TimelockStart {
+ slot: string
+}
+export interface TimelockExpiry {
+ slot: string
+}
+export type AuxiliaryDataHash = string
+export interface AuxiliaryDataSet {
+ [k: string]: AuxiliaryData
+}
+export type BigInt = string
+export type BigNum = string
+export type Vkey = string
+export type HeaderLeaderCertEnum =
+ | {
+ /**
+ * @minItems 2
+ * @maxItems 2
+ */
+ NonceAndLeader: [VRFCert, VRFCert]
+ }
+ | {
+ VrfResult: VRFCert
+ }
+export type Certificate =
+ | {
+ StakeRegistration: StakeRegistration
+ }
+ | {
+ StakeDeregistration: StakeDeregistration
+ }
+ | {
+ StakeDelegation: StakeDelegation
+ }
+ | {
+ PoolRegistration: PoolRegistration
+ }
+ | {
+ PoolRetirement: PoolRetirement
+ }
+ | {
+ GenesisKeyDelegation: GenesisKeyDelegation
+ }
+ | {
+ MoveInstantaneousRewardsCert: MoveInstantaneousRewardsCert
+ }
+ | {
+ CommitteeHotAuth: CommitteeHotAuth
+ }
+ | {
+ CommitteeColdResign: CommitteeColdResign
+ }
+ | {
+ DRepDeregistration: DRepDeregistration
+ }
+ | {
+ DRepRegistration: DRepRegistration
+ }
+ | {
+ DRepUpdate: DRepUpdate
+ }
+ | {
+ StakeAndVoteDelegation: StakeAndVoteDelegation
+ }
+ | {
+ StakeRegistrationAndDelegation: StakeRegistrationAndDelegation
+ }
+ | {
+ StakeVoteRegistrationAndDelegation: StakeVoteRegistrationAndDelegation
+ }
+ | {
+ VoteDelegation: VoteDelegation
+ }
+ | {
+ VoteRegistrationAndDelegation: VoteRegistrationAndDelegation
+ }
+export type CredType =
+ | {
+ Key: string
+ }
+ | {
+ Script: string
+ }
+export type Relay =
+ | {
+ SingleHostAddr: SingleHostAddr
+ }
+ | {
+ SingleHostName: SingleHostName
+ }
+ | {
+ MultiHostName: MultiHostName
+ }
+/**
+ * @minItems 4
+ * @maxItems 4
+ */
+export type Ipv4 = [number, number, number, number]
+/**
+ * @minItems 16
+ * @maxItems 16
+ */
+export type Ipv6 = [
+ number,
+ number,
+ number,
+ number,
+ number,
+ number,
+ number,
+ number,
+ number,
+ number,
+ number,
+ number,
+ number,
+ number,
+ number,
+ number,
+]
+export type DNSRecordAorAAAA = string
+export type DNSRecordSRV = string
+export type Relays = Relay[]
+export type MIRPot = 'Reserves' | 'Treasury'
+export type MIREnum =
+ | {
+ ToOtherPot: string
+ }
+ | {
+ ToStakeCredentials: StakeToCoin[]
+ }
+export type DRep =
+ | ('AlwaysAbstain' | 'AlwaysNoConfidence')
+ | {
+ KeyHash: string
+ }
+ | {
+ ScriptHash: string
+ }
+export type DataOption =
+ | {
+ DataHash: string
+ }
+ | {
+ Data: string
+ }
+export type ScriptRef =
+ | {
+ NativeScript: NativeScript
+ }
+ | {
+ PlutusScript: string
+ }
+export type Mint = [string, MintAssets][]
+export type NetworkId = 'Testnet' | 'Mainnet'
+export type TransactionOutputs = TransactionOutput[]
+export type CostModel = string[]
+export type Voter =
+ | {
+ ConstitutionalCommitteeHotCred: CredType
+ }
+ | {
+ DRep: CredType
+ }
+ | {
+ StakingPool: string
+ }
+export type VoteKind = 'No' | 'Yes' | 'Abstain'
+export type GovernanceAction =
+ | {
+ ParameterChangeAction: ParameterChangeAction
+ }
+ | {
+ HardForkInitiationAction: HardForkInitiationAction
+ }
+ | {
+ TreasuryWithdrawalsAction: TreasuryWithdrawalsAction
+ }
+ | {
+ NoConfidenceAction: NoConfidenceAction
+ }
+ | {
+ UpdateCommitteeAction: UpdateCommitteeAction
+ }
+ | {
+ NewConstitutionAction: NewConstitutionAction
+ }
+ | {
+ InfoAction: InfoAction
+ }
+/**
+ * @minItems 0
+ * @maxItems 0
+ */
+export type InfoAction = []
+export type TransactionBodies = TransactionBody[]
+export type RedeemerTag = 'Spend' | 'Mint' | 'Cert' | 'Reward' | 'Vote' | 'VotingProposal'
+export type TransactionWitnessSets = TransactionWitnessSet[]
+
+export interface Block {
+ auxiliary_data_set: {
+ [k: string]: AuxiliaryData
+ }
+ header: Header
+ invalid_transactions: number[]
+ transaction_bodies: TransactionBodies
+ transaction_witness_sets: TransactionWitnessSets
+}
+export interface Header {
+ body_signature: string
+ header_body: HeaderBody
+}
+export interface HeaderBody {
+ block_body_hash: string
+ block_body_size: number
+ block_number: number
+ issuer_vkey: Vkey
+ leader_cert: HeaderLeaderCertEnum
+ operational_cert: OperationalCert
+ prev_hash?: string | null
+ protocol_version: ProtocolVersion
+ slot: string
+ vrf_vkey: string
+}
+export interface VRFCert {
+ output: number[]
+ proof: number[]
+}
+export interface OperationalCert {
+ hot_vkey: string
+ kes_period: number
+ sequence_number: number
+ sigma: string
+}
+export interface ProtocolVersion {
+ major: number
+ minor: number
+}
+export interface TransactionBody {
+ auxiliary_data_hash?: string | null
+ certs?: Certificate[] | null
+ collateral?: TransactionInput[] | null
+ collateral_return?: TransactionOutput | null
+ current_treasury_value?: string | null
+ donation?: string | null
+ fee: string
+ inputs: TransactionInput[]
+ mint?: Mint | null
+ network_id?: NetworkId | null
+ outputs: TransactionOutputs
+ reference_inputs?: TransactionInput[] | null
+ required_signers?: string[] | null
+ script_data_hash?: string | null
+ total_collateral?: string | null
+ ttl?: string | null
+ update?: Update | null
+ validity_start_interval?: string | null
+ voting_procedures?: VoterVotes[] | null
+ voting_proposals?: VotingProposal[] | null
+ withdrawals?: {
+ [k: string]: string
+ } | null
+}
+export interface StakeRegistration {
+ coin?: string | null
+ stake_credential: CredType
+}
+export interface StakeDeregistration {
+ coin?: string | null
+ stake_credential: CredType
+}
+export interface StakeDelegation {
+ pool_keyhash: string
+ stake_credential: CredType
+}
+export interface PoolRegistration {
+ pool_params: PoolParams
+}
+export interface PoolParams {
+ cost: string
+ margin: UnitInterval
+ operator: string
+ pledge: string
+ pool_metadata?: PoolMetadata | null
+ pool_owners: string[]
+ relays: Relays
+ reward_account: string
+ vrf_keyhash: string
+}
+export interface UnitInterval {
+ denominator: string
+ numerator: string
+}
+export interface PoolMetadata {
+ pool_metadata_hash: string
+ url: URL
+}
+export interface SingleHostAddr {
+ ipv4?: Ipv4 | null
+ ipv6?: Ipv6 | null
+ port?: number | null
+}
+export interface SingleHostName {
+ dns_name: DNSRecordAorAAAA
+ port?: number | null
+}
+export interface MultiHostName {
+ dns_name: DNSRecordSRV
+}
+export interface PoolRetirement {
+ epoch: number
+ pool_keyhash: string
+}
+export interface GenesisKeyDelegation {
+ genesis_delegate_hash: string
+ genesishash: string
+ vrf_keyhash: string
+}
+export interface MoveInstantaneousRewardsCert {
+ move_instantaneous_reward: MoveInstantaneousReward
+}
+export interface MoveInstantaneousReward {
+ pot: MIRPot
+ variant: MIREnum
+}
+export interface StakeToCoin {
+ amount: string
+ stake_cred: CredType
+}
+export interface CommitteeHotAuth {
+ committee_cold_credential: CredType
+ committee_hot_credential: CredType
+}
+export interface CommitteeColdResign {
+ anchor?: Anchor | null
+ committee_cold_credential: CredType
+}
+export interface DRepDeregistration {
+ coin: string
+ voting_credential: CredType
+}
+export interface DRepRegistration {
+ anchor?: Anchor | null
+ coin: string
+ voting_credential: CredType
+}
+export interface DRepUpdate {
+ anchor?: Anchor | null
+ voting_credential: CredType
+}
+export interface StakeAndVoteDelegation {
+ drep: DRep
+ pool_keyhash: string
+ stake_credential: CredType
+}
+export interface StakeRegistrationAndDelegation {
+ coin: string
+ pool_keyhash: string
+ stake_credential: CredType
+}
+export interface StakeVoteRegistrationAndDelegation {
+ coin: string
+ drep: DRep
+ pool_keyhash: string
+ stake_credential: CredType
+}
+export interface VoteDelegation {
+ drep: DRep
+ stake_credential: CredType
+}
+export interface VoteRegistrationAndDelegation {
+ coin: string
+ drep: DRep
+ stake_credential: CredType
+}
+export interface TransactionInput {
+ index: number
+ transaction_id: string
+}
+export interface TransactionOutput {
+ address: string
+ amount: Value
+ plutus_data?: DataOption | null
+ script_ref?: ScriptRef | null
+}
+export interface Value {
+ coin: string
+ multiasset?: MultiAsset | null
+}
+export interface MultiAsset {
+ [k: string]: Assets
+}
+export interface MintAssets {
+ [k: string]: string
+}
+export interface Update {
+ epoch: number
+ proposed_protocol_parameter_updates: {
+ [k: string]: ProtocolParamUpdate
+ }
+}
+export interface ProtocolParamUpdate {
+ ada_per_utxo_byte?: string | null
+ collateral_percentage?: number | null
+ committee_term_limit?: number | null
+ cost_models?: Costmdls | null
+ d?: UnitInterval | null
+ drep_deposit?: string | null
+ drep_inactivity_period?: number | null
+ drep_voting_thresholds?: DRepVotingThresholds | null
+ execution_costs?: ExUnitPrices | null
+ expansion_rate?: UnitInterval | null
+ extra_entropy?: Nonce | null
+ governance_action_deposit?: string | null
+ governance_action_validity_period?: number | null
+ key_deposit?: string | null
+ max_block_body_size?: number | null
+ max_block_ex_units?: ExUnits | null
+ max_block_header_size?: number | null
+ max_collateral_inputs?: number | null
+ max_epoch?: number | null
+ max_tx_ex_units?: ExUnits | null
+ max_tx_size?: number | null
+ max_value_size?: number | null
+ min_committee_size?: number | null
+ min_pool_cost?: string | null
+ minfee_a?: string | null
+ minfee_b?: string | null
+ n_opt?: number | null
+ pool_deposit?: string | null
+ pool_pledge_influence?: UnitInterval | null
+ pool_voting_thresholds?: PoolVotingThresholds | null
+ protocol_version?: ProtocolVersion | null
+ ref_script_coins_per_byte?: UnitInterval | null
+ treasury_growth_rate?: UnitInterval | null
+}
+export interface Costmdls {
+ [k: string]: CostModel
+}
+export interface DRepVotingThresholds {
+ committee_no_confidence: UnitInterval
+ committee_normal: UnitInterval
+ hard_fork_initiation: UnitInterval
+ motion_no_confidence: UnitInterval
+ pp_economic_group: UnitInterval
+ pp_governance_group: UnitInterval
+ pp_network_group: UnitInterval
+ pp_technical_group: UnitInterval
+ treasury_withdrawal: UnitInterval
+ update_constitution: UnitInterval
+}
+export interface ExUnitPrices {
+ mem_price: UnitInterval
+ step_price: UnitInterval
+}
+export interface Nonce {
+ /**
+ * @minItems 32
+ * @maxItems 32
+ */
+ hash?:
+ | [
+ number,
+ number,
+ number,
+ number,
+ number,
+ number,
+ number,
+ number,
+ number,
+ number,
+ number,
+ number,
+ number,
+ number,
+ number,
+ number,
+ number,
+ number,
+ number,
+ number,
+ number,
+ number,
+ number,
+ number,
+ number,
+ number,
+ number,
+ number,
+ number,
+ number,
+ number,
+ number,
+ ]
+ | null
+}
+export interface ExUnits {
+ mem: string
+ steps: string
+}
+export interface PoolVotingThresholds {
+ committee_no_confidence: UnitInterval
+ committee_normal: UnitInterval
+ hard_fork_initiation: UnitInterval
+ motion_no_confidence: UnitInterval
+ security_relevant_threshold: UnitInterval
+}
+export interface VoterVotes {
+ voter: Voter
+ votes: Vote[]
+}
+export interface Vote {
+ action_id: GovernanceActionId
+ voting_procedure: VotingProcedure
+}
+export interface GovernanceActionId {
+ index: number
+ transaction_id: string
+}
+export interface VotingProcedure {
+ anchor?: Anchor | null
+ vote: VoteKind
+}
+export interface VotingProposal {
+ anchor: Anchor
+ deposit: string
+ governance_action: GovernanceAction
+ reward_account: string
+}
+export interface ParameterChangeAction {
+ gov_action_id?: GovernanceActionId | null
+ policy_hash?: string | null
+ protocol_param_updates: ProtocolParamUpdate
+}
+export interface HardForkInitiationAction {
+ gov_action_id?: GovernanceActionId | null
+ protocol_version: ProtocolVersion
+}
+export interface TreasuryWithdrawalsAction {
+ policy_hash?: string | null
+ withdrawals: TreasuryWithdrawals
+}
+export interface TreasuryWithdrawals {
+ [k: string]: string
+}
+export interface NoConfidenceAction {
+ gov_action_id?: GovernanceActionId | null
+}
+export interface UpdateCommitteeAction {
+ committee: Committee
+ gov_action_id?: GovernanceActionId | null
+ members_to_remove: CredType[]
+}
+export interface Committee {
+ members: CommitteeMember[]
+ quorum_threshold: UnitInterval
+}
+export interface CommitteeMember {
+ stake_credential: CredType
+ term_limit: number
+}
+export interface NewConstitutionAction {
+ constitution: Constitution
+ gov_action_id?: GovernanceActionId | null
+}
+export interface Constitution {
+ anchor: Anchor
+ script_hash?: string | null
+}
+export interface TransactionWitnessSet {
+ bootstraps?: BootstrapWitness[] | null
+ native_scripts?: NativeScripts | null
+ plutus_data?: PlutusList | null
+ plutus_scripts?: PlutusScripts | null
+ redeemers?: Redeemer[] | null
+ vkeys?: Vkeywitness[] | null
+}
+export interface BootstrapWitness {
+ attributes: number[]
+ chain_code: number[]
+ signature: string
+ vkey: Vkey
+}
+export interface PlutusList {
+ definite_encoding?: boolean | null
+ elems: string[]
+}
+export interface Redeemer {
+ data: string
+ ex_units: ExUnits
+ index: string
+ tag: RedeemerTag
+}
+export interface Vkeywitness {
+ signature: string
+ vkey: Vkey
+}
+export type BlockHash = string
+export type BootstrapWitnesses = BootstrapWitness[]
+
+export type CertificateEnum =
+ | {
+ StakeRegistration: StakeRegistration
+ }
+ | {
+ StakeDeregistration: StakeDeregistration
+ }
+ | {
+ StakeDelegation: StakeDelegation
+ }
+ | {
+ PoolRegistration: PoolRegistration
+ }
+ | {
+ PoolRetirement: PoolRetirement
+ }
+ | {
+ GenesisKeyDelegation: GenesisKeyDelegation
+ }
+ | {
+ MoveInstantaneousRewardsCert: MoveInstantaneousRewardsCert
+ }
+ | {
+ CommitteeHotAuth: CommitteeHotAuth
+ }
+ | {
+ CommitteeColdResign: CommitteeColdResign
+ }
+ | {
+ DRepDeregistration: DRepDeregistration
+ }
+ | {
+ DRepRegistration: DRepRegistration
+ }
+ | {
+ DRepUpdate: DRepUpdate
+ }
+ | {
+ StakeAndVoteDelegation: StakeAndVoteDelegation
+ }
+ | {
+ StakeRegistrationAndDelegation: StakeRegistrationAndDelegation
+ }
+ | {
+ StakeVoteRegistrationAndDelegation: StakeVoteRegistrationAndDelegation
+ }
+ | {
+ VoteDelegation: VoteDelegation
+ }
+ | {
+ VoteRegistrationAndDelegation: VoteRegistrationAndDelegation
+ }
+export type Certificates = Certificate[]
+
+export type Credential = CredType
+export type Credentials = CredType[]
+export type DRepEnum =
+ | ('AlwaysAbstain' | 'AlwaysNoConfidence')
+ | {
+ KeyHash: string
+ }
+ | {
+ ScriptHash: string
+ }
+export type DataHash = string
+export type Ed25519KeyHash = string
+export type Ed25519KeyHashes = string[]
+export type Ed25519Signature = string
+export interface GeneralTransactionMetadata {
+ [k: string]: string
+}
+export type GenesisDelegateHash = string
+export type GenesisHash = string
+export type GenesisHashes = string[]
+export type GovernanceActionEnum =
+ | {
+ ParameterChangeAction: ParameterChangeAction
+ }
+ | {
+ HardForkInitiationAction: HardForkInitiationAction
+ }
+ | {
+ TreasuryWithdrawalsAction: TreasuryWithdrawalsAction
+ }
+ | {
+ NoConfidenceAction: NoConfidenceAction
+ }
+ | {
+ UpdateCommitteeAction: UpdateCommitteeAction
+ }
+ | {
+ NewConstitutionAction: NewConstitutionAction
+ }
+ | {
+ InfoAction: InfoAction
+ }
+export type GovernanceActionIds = GovernanceActionId[]
+
+export type Int = string
+/**
+ * @minItems 4
+ * @maxItems 4
+ */
+export type KESVKey = string
+export type Language = LanguageKind
+export type LanguageKind = 'PlutusV1' | 'PlutusV2' | 'PlutusV3'
+export type Languages = Language[]
+export type MIRToStakeCredentials = StakeToCoin[]
+
+export type MintsAssets = MintAssets[]
+
+export type NetworkIdKind = 'Testnet' | 'Mainnet'
+export type PlutusScript = string
+export type PoolMetadataHash = string
+export interface ProposedProtocolParameterUpdates {
+ [k: string]: ProtocolParamUpdate
+}
+export type PublicKey = string
+export type RedeemerTagKind = 'Spend' | 'Mint' | 'Cert' | 'Reward' | 'Vote' | 'VotingProposal'
+export type Redeemers = Redeemer[]
+
+export type RelayEnum =
+ | {
+ SingleHostAddr: SingleHostAddr
+ }
+ | {
+ SingleHostName: SingleHostName
+ }
+ | {
+ MultiHostName: MultiHostName
+ }
+/**
+ * @minItems 4
+ * @maxItems 4
+ */
+export type RewardAddress = string
+export type RewardAddresses = string[]
+export type ScriptDataHash = string
+export type ScriptHash = string
+export type ScriptHashes = string[]
+export type ScriptRefEnum =
+ | {
+ NativeScript: NativeScript
+ }
+ | {
+ PlutusScript: string
+ }
+export interface Transaction {
+ auxiliary_data?: AuxiliaryData | null
+ body: TransactionBody
+ is_valid: boolean
+ witness_set: TransactionWitnessSet
+}
+export type TransactionHash = string
+export type TransactionInputs = TransactionInput[]
+
+export type TransactionMetadatum = string
+export interface TransactionUnspentOutput {
+ input: TransactionInput
+ output: TransactionOutput
+}
+export type TransactionUnspentOutputs = TransactionUnspentOutput[]
+
+export type VRFKeyHash = string
+export type VRFVKey = string
+export interface VersionedBlock {
+ block: Block
+ era_code: number
+}
+export type Vkeywitnesses = Vkeywitness[]
+
+export type VoterEnum =
+ | {
+ ConstitutionalCommitteeHotCred: CredType
+ }
+ | {
+ DRep: CredType
+ }
+ | {
+ StakingPool: string
+ }
+export type Voters = Voter[]
+export type VotingProcedures = VoterVotes[]
+
+export type VotingProposals = VotingProposal[]
+
+export interface Withdrawals {
+ [k: string]: string
+}
+
+export type FormattedInput = {
+ assets: Array<{
+ name: string
+ label: string
+ quantity: Balance.Quantity
+ isPrimary: boolean
+ }>
+ address: string | undefined
+ rewardAddress: string | null
+ ownAddress: boolean
+ txIndex: number
+ txHash: string
+}
+
+export type FormattedInputs = Array
+export type FormattedOutput = {
+ assets: Array<{
+ name: string
+ label: string
+ quantity: Balance.Quantity
+ isPrimary: boolean
+ }>
+ address: string
+ rewardAddress: string | null
+ ownAddress: boolean
+}
+export type FormattedOutputs = Array
+export type FormattedFee = {
+ name: string
+ label: string
+ quantity: Balance.Quantity
+ isPrimary: boolean
+}
diff --git a/apps/wallet-mobile/src/features/ReviewTx/useCases/ReviewTxScreen/Overview/OverviewTab.tsx b/apps/wallet-mobile/src/features/ReviewTx/useCases/ReviewTxScreen/Overview/OverviewTab.tsx
new file mode 100644
index 0000000000..eed42fd24a
--- /dev/null
+++ b/apps/wallet-mobile/src/features/ReviewTx/useCases/ReviewTxScreen/Overview/OverviewTab.tsx
@@ -0,0 +1,355 @@
+// 🚧 TODO: grouping by staking address 🚧
+
+import {Blockies} from '@yoroi/identicon'
+import {useTheme} from '@yoroi/theme'
+import * as React from 'react'
+import {Linking, StyleSheet, Text, TouchableOpacity, View} from 'react-native'
+
+import {Icon} from '../../../../../components/Icon'
+import {Info} from '../../../../../components/Info/Info'
+import {Space} from '../../../../../components/Space/Space'
+import {formatTokenWithText} from '../../../../../yoroi-wallets/utils/format'
+import {Quantities} from '../../../../../yoroi-wallets/utils/utils'
+import {useSelectedWallet} from '../../../../WalletManager/common/hooks/useSelectedWallet'
+import {useWalletManager} from '../../../../WalletManager/context/WalletManagerProvider'
+import {Address} from '../../../common/Address'
+import {CollapsibleSection} from '../../../common/CollapsibleSection'
+import {Divider} from '../../../common/Divider'
+import {useAddressType} from '../../../common/hooks/useAddressType'
+import {FormattedTx} from '../../../common/hooks/useFormattedTx'
+import {useStrings} from '../../../common/hooks/useStrings'
+import {TokenItem} from '../../../common/TokenItem'
+import {FormattedOutputs} from '../../../common/types'
+
+export const OverviewTab = ({tx}: {tx: FormattedTx}) => {
+ const {styles} = useStyles()
+
+ const notOwnedOutputs = React.useMemo(() => tx.outputs.filter((output) => !output.ownAddress), [tx.outputs])
+ const ownedOutputs = React.useMemo(() => tx.outputs.filter((output) => output.ownAddress), [tx.outputs])
+
+ return (
+
+
+
+
+
+
+
+
+
+ )
+}
+
+const WalletInfoSection = ({tx}: {tx: FormattedTx}) => {
+ const {styles} = useStyles()
+ const strings = useStrings()
+ const {wallet, meta} = useSelectedWallet()
+ const {walletManager} = useWalletManager()
+ const {plate, seed} = walletManager.checksum(wallet.publicKeyHex)
+ const seedImage = new Blockies({seed}).asBase64()
+
+ return (
+ <>
+
+ {strings.walletLabel}
+
+
+
+
+
+
+ {`${plate} | ${meta.name}`}
+
+
+
+
+
+
+ >
+ )
+}
+
+const FeeInfoItem = ({fee}: {fee: string}) => {
+ const {styles} = useStyles()
+ const strings = useStrings()
+
+ return (
+
+ {strings.feeLabel}
+
+ {fee}
+
+ )
+}
+
+const SenderSection = ({
+ tx,
+ notOwnedOutputs,
+ ownedOutputs,
+}: {
+ tx: FormattedTx
+ notOwnedOutputs: FormattedOutputs
+ ownedOutputs: FormattedOutputs
+}) => {
+ const strings = useStrings()
+ const address = ownedOutputs[0]?.rewardAddress ?? ownedOutputs[0]?.address
+
+ return (
+
+
+
+
+
+
+
+
+
+ {notOwnedOutputs.length === 1 && }
+
+ )
+}
+
+// 🚧 TODO: ADD MULTIRECEIVER SUPPORT 🚧
+const SenderTokens = ({tx, notOwnedOutputs}: {tx: FormattedTx; notOwnedOutputs: FormattedOutputs}) => {
+ const {styles} = useStyles()
+
+ const {wallet} = useSelectedWallet()
+
+ const totalPrimaryTokenSent = React.useMemo(
+ () =>
+ notOwnedOutputs
+ .flatMap((output) => output.assets.filter((asset) => asset.isPrimary))
+ .reduce((previous, current) => Quantities.sum([previous, current.quantity]), Quantities.zero),
+ [notOwnedOutputs],
+ )
+ const totalPrimaryTokenSpent = React.useMemo(
+ () => Quantities.sum([totalPrimaryTokenSent, tx.fee.quantity]),
+ [totalPrimaryTokenSent, tx.fee.quantity],
+ )
+ const totalPrimaryTokenSpentLabel = formatTokenWithText(totalPrimaryTokenSpent, wallet.portfolioPrimaryTokenInfo)
+
+ const notPrimaryTokenSent = React.useMemo(
+ () => notOwnedOutputs.flatMap((output) => output.assets.filter((asset) => !asset.isPrimary)),
+ [notOwnedOutputs],
+ )
+
+ return (
+
+
+
+
+
+
+
+
+ {notPrimaryTokenSent.map((token) => (
+
+ ))}
+
+
+ )
+}
+
+const SenderSectionLabel = () => {
+ const {styles, colors} = useStyles()
+ const strings = useStrings()
+
+ return (
+
+
+
+
+
+ {strings.sendLabel}
+
+ )
+}
+
+const ReceiverSection = ({notOwnedOutputs}: {notOwnedOutputs: FormattedOutputs}) => {
+ const address = notOwnedOutputs[0]?.rewardAddress ?? notOwnedOutputs[0]?.address
+ const {styles} = useStyles()
+ const strings = useStrings()
+ const addressType = useAddressType(address)
+ const isScriptAddress = addressType === 'script'
+
+ return (
+ <>
+
+
+
+ {isScriptAddress ? strings.receiveToScriptLabel : strings.receiveToLabel}:
+
+
+
+ >
+ )
+}
+
+const useStyles = () => {
+ const {atoms, color} = useTheme()
+ const styles = StyleSheet.create({
+ root: {
+ ...atoms.flex_1,
+ ...atoms.px_lg,
+ backgroundColor: color.bg_color_max,
+ },
+ infoItem: {
+ ...atoms.flex_row,
+ ...atoms.justify_between,
+ },
+ infoLabel: {
+ ...atoms.body_2_md_regular,
+ color: color.gray_600,
+ },
+ walletInfoText: {
+ ...atoms.body_2_md_medium,
+ color: color.text_primary_medium,
+ },
+ plate: {
+ ...atoms.flex_row,
+ ...atoms.align_center,
+ },
+ fee: {
+ color: color.gray_900,
+ ...atoms.body_2_md_regular,
+ },
+ link: {
+ color: color.text_primary_medium,
+ ...atoms.body_2_md_medium,
+ },
+ receiverAddress: {
+ ...atoms.flex_row,
+ ...atoms.align_center,
+ ...atoms.flex_row,
+ ...atoms.justify_between,
+ },
+ tokenSectionLabel: {
+ ...atoms.body_2_md_regular,
+ color: color.gray_900,
+ },
+ senderTokenItems: {
+ ...atoms.flex_wrap,
+ ...atoms.flex_row,
+ ...atoms.justify_end,
+ ...atoms.flex_1,
+ ...atoms.gap_sm,
+ },
+ tokensSection: {
+ ...atoms.flex_row,
+ ...atoms.justify_between,
+ },
+ tokensSectionLabel: {
+ ...atoms.flex_row,
+ ...atoms.align_center,
+ },
+ walletChecksum: {
+ width: 24,
+ height: 24,
+ },
+ receiverSectionAddress: {
+ maxWidth: 260,
+ },
+ })
+
+ const colors = {
+ send: color.primary_500,
+ received: color.green_static,
+ }
+
+ return {styles, colors} as const
+}
+
+// 🚧 WORK IN PROGRESS BELOW 🚧
+
+// 🚧 TODO: WIP 🚧
+// eslint-disable-next-line @typescript-eslint/no-unused-vars
+const CreatedByInfoItem = () => {
+ const {styles} = useStyles()
+
+ return (
+
+ Created By
+
+
+ {/* */}
+
+
+
+ Linking.openURL('https://google.com')}>
+ dapp.org
+
+
+
+ )
+}
+
+// 🚧 TODO: WIP 🚧
+// eslint-disable-next-line @typescript-eslint/no-unused-vars
+const ReceiverTokensSectionMultiReceiver = () => {
+ const {styles} = useStyles()
+
+ return (
+ <>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ >
+ )
+}
+
+// 🚧 TODO: WIP 🚧
+const ReceiverSectionLabel = () => {
+ const {styles, colors} = useStyles()
+
+ return (
+
+
+
+
+
+ Receive
+
+ )
+}
diff --git a/apps/wallet-mobile/src/features/ReviewTx/useCases/ReviewTxScreen/ReviewTxScreen.tsx b/apps/wallet-mobile/src/features/ReviewTx/useCases/ReviewTxScreen/ReviewTxScreen.tsx
new file mode 100644
index 0000000000..6216f4c12f
--- /dev/null
+++ b/apps/wallet-mobile/src/features/ReviewTx/useCases/ReviewTxScreen/ReviewTxScreen.tsx
@@ -0,0 +1,143 @@
+import {createMaterialTopTabNavigator, MaterialTopTabBarProps} from '@react-navigation/material-top-tabs'
+import {useTheme} from '@yoroi/theme'
+import * as React from 'react'
+import {FlatList, StyleSheet, Text, TouchableOpacity, TouchableOpacityProps, View} from 'react-native'
+
+import {Button} from '../../../../components/Button/Button'
+import {SafeArea} from '../../../../components/SafeArea'
+import {ReviewTxRoutes, useUnsafeParams} from '../../../../kernel/navigation'
+import {useFormattedTx} from '../../common/hooks/useFormattedTx'
+import {useOnConfirm} from '../../common/hooks/useOnConfirm'
+import {useStrings} from '../../common/hooks/useStrings'
+import {useTxBody} from '../../common/hooks/useTxBody'
+import {OverviewTab} from './Overview/OverviewTab'
+import {UTxOsTab} from './UTxOs/UTxOsTab'
+
+const MaterialTab = createMaterialTopTabNavigator()
+
+export const ReviewTxScreen = () => {
+ const {styles} = useStyles()
+ const strings = useStrings()
+
+ // TODO: move this to a context
+ const params = useUnsafeParams()
+ const {onConfirm} = useOnConfirm({
+ unsignedTx: params.unsignedTx,
+ onSuccess: params.onSuccess,
+ onError: params.onError,
+ })
+
+ // TODO: add cbor arguments
+ const txBody = useTxBody({unsignedTx: params.unsignedTx})
+ const formatedTx = useFormattedTx(txBody)
+
+ const OverViewTabMemo = React.memo(() => )
+ const UTxOsTabMemo = React.memo(() => )
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+ )
+}
+
+const TabBar = ({navigation, state}: MaterialTopTabBarProps) => {
+ const {styles} = useStyles()
+ const strings = useStrings()
+
+ return (
+ (
+ navigation.navigate(key)} />
+ )}
+ bounces={false}
+ horizontal
+ />
+ )
+}
+
+export const Tab = ({
+ onPress,
+ active,
+ label,
+ testID,
+ style,
+}: TouchableOpacityProps & {active: boolean; label: string}) => {
+ const {styles} = useStyles()
+
+ return (
+
+
+ {label}
+
+
+ {active && }
+
+ )
+}
+
+const Actions = ({children}: {children: React.ReactNode}) => {
+ const {styles} = useStyles()
+ return {children}
+}
+
+const useStyles = () => {
+ const {atoms, color} = useTheme()
+ const styles = StyleSheet.create({
+ root: {
+ ...atoms.flex_1,
+ backgroundColor: color.bg_color_max,
+ },
+ actions: {
+ ...atoms.p_lg,
+ },
+ tabBar: {
+ marginHorizontal: 16, // to include the border
+ maxHeight: 50,
+ borderBottomWidth: 1,
+ borderBottomColor: color.gray_200,
+ },
+ tab: {
+ ...atoms.align_center,
+ ...atoms.justify_center,
+ ...atoms.py_md,
+ },
+ tabContainer: {
+ ...atoms.align_center,
+ ...atoms.justify_center,
+ ...atoms.px_lg,
+ },
+ tabText: {
+ ...atoms.body_1_lg_medium,
+ },
+ tabTextActive: {
+ color: color.primary_600,
+ },
+ tabTextInactive: {
+ color: color.gray_600,
+ },
+ indicator: {
+ ...atoms.absolute,
+ bottom: 0,
+ height: 2,
+ width: '100%',
+ backgroundColor: color.primary_500,
+ },
+ })
+
+ return {styles} as const
+}
diff --git a/apps/wallet-mobile/src/features/ReviewTx/useCases/ReviewTxScreen/UTxOs/UTxOsTab.tsx b/apps/wallet-mobile/src/features/ReviewTx/useCases/ReviewTxScreen/UTxOs/UTxOsTab.tsx
new file mode 100644
index 0000000000..bbf23f8cec
--- /dev/null
+++ b/apps/wallet-mobile/src/features/ReviewTx/useCases/ReviewTxScreen/UTxOs/UTxOsTab.tsx
@@ -0,0 +1,185 @@
+import {useTheme} from '@yoroi/theme'
+import * as React from 'react'
+import {StyleSheet, Text, View} from 'react-native'
+
+import {Space} from '../../../../../components/Space/Space'
+import {Address} from '../../../common/Address'
+import {CollapsibleSection} from '../../../common/CollapsibleSection'
+import {Divider} from '../../../common/Divider'
+import {FormattedTx} from '../../../common/hooks/useFormattedTx'
+import {useStrings} from '../../../common/hooks/useStrings'
+import {TokenItem} from '../../../common/TokenItem'
+import {FormattedInput, FormattedInputs, FormattedOutput, FormattedOutputs} from '../../../common/types'
+
+export const UTxOsTab = ({tx}: {tx: FormattedTx}) => {
+ const {styles} = useStyles()
+ const strings = useStrings()
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+ )
+}
+
+const Inputs = ({inputs}: {inputs: FormattedInputs}) => {
+ return inputs.map((input) => )
+}
+
+const Input = ({input}: {input: FormattedInput}) => {
+ const {styles} = useStyles()
+ if (input?.address === undefined) throw new Error('UTxOsTab: input invalid address')
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {input.assets.map((asset) => (
+
+ ))}
+
+
+ )
+}
+const Outputs = ({outputs}: {outputs: FormattedOutputs}) => {
+ return outputs.map((output) => )
+}
+
+const Output = ({output}: {output: FormattedOutput}) => {
+ const {styles} = useStyles()
+ if (output?.address === undefined) throw new Error('UTxOsTab: input invalid address')
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {output.assets.map((asset) => (
+
+ ))}
+
+
+ )
+}
+
+const Fee = ({fee}: {fee: string}) => {
+ const {styles} = useStyles()
+ const strings = useStrings()
+
+ return (
+
+
+
+
+ {strings.feeLabel}
+
+ {`-${fee}`}
+
+
+
+
+ )
+}
+
+const UtxoTitle = ({isInput, isOwnAdddress}: {isOwnAdddress: boolean; isInput: boolean}) => {
+ const {styles} = useStyles()
+ const strings = useStrings()
+
+ const label = isOwnAdddress ? strings.utxosYourAddressLabel : strings.utxosForeignAddressLabel
+
+ return (
+
+
+
+
+
+ {label}
+
+ )
+}
+
+const useStyles = () => {
+ const {atoms, color} = useTheme()
+ const styles = StyleSheet.create({
+ root: {
+ ...atoms.flex_1,
+ ...atoms.px_lg,
+ backgroundColor: color.bg_color_max,
+ },
+ utxoTitle: {
+ ...atoms.flex_row,
+ ...atoms.align_center,
+ },
+ utxoTitleCircleInput: {
+ width: 12,
+ height: 12,
+ backgroundColor: color.primary_500,
+ ...atoms.rounded_full,
+ },
+ utxoTitleCircleOutput: {
+ width: 12,
+ height: 12,
+ backgroundColor: color.green_static,
+ ...atoms.rounded_full,
+ },
+ utxoTitleText: {
+ ...atoms.body_2_md_medium,
+ color: color.text_gray_medium,
+ },
+ tokenItems: {
+ ...atoms.flex_row,
+ ...atoms.justify_end,
+ ...atoms.flex_wrap,
+ ...atoms.gap_sm,
+ },
+ fee: {
+ ...atoms.flex_row,
+ ...atoms.justify_between,
+ },
+ feeLabel: {
+ ...atoms.body_1_lg_medium,
+ color: color.text_gray_medium,
+ },
+ feeValue: {
+ ...atoms.body_2_md_regular,
+ color: color.text_gray_medium,
+ },
+ })
+
+ return {styles} as const
+}
diff --git a/apps/wallet-mobile/src/features/Send/common/navigation.ts b/apps/wallet-mobile/src/features/Send/common/navigation.ts
index aaf733d819..45a7caedaa 100644
--- a/apps/wallet-mobile/src/features/Send/common/navigation.ts
+++ b/apps/wallet-mobile/src/features/Send/common/navigation.ts
@@ -15,7 +15,6 @@ export const useNavigateTo = () => {
selectedTokens: () => navigation.navigate('send-list-amounts-to-send'),
addToken: () => navigation.navigate('send-select-token-from-list'),
startTx: () => navigation.navigate('send-start-tx'),
- confirmTx: () => navigation.navigate('send-confirm-tx'),
editAmount: () => navigation.navigate('send-edit-amount'),
reader: () => navigation.navigate('scan-start', {insideFeature: 'send'}),
submittedTx: (txId: string) => navigation.navigate('send-submitted-tx', {txId}),
diff --git a/apps/wallet-mobile/src/features/Send/useCases/ConfirmTx/ConfirmTxScreen.stories.tsx b/apps/wallet-mobile/src/features/Send/useCases/ConfirmTx/ConfirmTxScreen.stories.tsx
deleted file mode 100644
index dc5affd9a3..0000000000
--- a/apps/wallet-mobile/src/features/Send/useCases/ConfirmTx/ConfirmTxScreen.stories.tsx
+++ /dev/null
@@ -1,18 +0,0 @@
-import {storiesOf} from '@storybook/react-native'
-import {TransferProvider} from '@yoroi/transfer'
-import React from 'react'
-
-import {mocks as walletMocks} from '../../../../yoroi-wallets/mocks/wallet'
-import {WalletManagerProviderMock} from '../../../../yoroi-wallets/mocks/WalletManagerProviderMock'
-import {mocks as sendMocks} from '../../common/mocks'
-import {ConfirmTxScreen} from './ConfirmTxScreen'
-
-storiesOf('Confirm Tx', module).add('initial', () => {
- return (
-
-
-
-
-
- )
-})
diff --git a/apps/wallet-mobile/src/features/Send/useCases/ConfirmTx/ConfirmTxScreen.tsx b/apps/wallet-mobile/src/features/Send/useCases/ConfirmTx/ConfirmTxScreen.tsx
deleted file mode 100644
index ca01429bbd..0000000000
--- a/apps/wallet-mobile/src/features/Send/useCases/ConfirmTx/ConfirmTxScreen.tsx
+++ /dev/null
@@ -1,181 +0,0 @@
-import {useFocusEffect} from '@react-navigation/native'
-import {useTheme} from '@yoroi/theme'
-import {useTransfer} from '@yoroi/transfer'
-import React, {useEffect} from 'react'
-import {useIntl} from 'react-intl'
-import {ScrollView, StyleSheet, View, ViewProps} from 'react-native'
-import {SafeAreaView} from 'react-native-safe-area-context'
-
-import {ConfirmTx} from '../../../../components/ConfirmTx/ConfirmTx'
-import {KeyboardAvoidingView} from '../../../../components/KeyboardAvoidingView/KeyboardAvoidingView'
-import {Space} from '../../../../components/Space/Space'
-import {ValidatedTextInput} from '../../../../components/ValidatedTextInput'
-import {isDev} from '../../../../kernel/env'
-import {debugWalletInfo, features} from '../../../../kernel/features'
-import globalMessages, {confirmationMessages, errorMessages, txLabels} from '../../../../kernel/i18n/global-messages'
-import {assetsToSendProperties} from '../../../../kernel/metrics/helpers'
-import {useMetrics} from '../../../../kernel/metrics/metricsManager'
-import {useSaveMemo} from '../../../../yoroi-wallets/hooks'
-import {YoroiSignedTx} from '../../../../yoroi-wallets/types/yoroi'
-import {useSelectedWallet} from '../../../WalletManager/common/hooks/useSelectedWallet'
-import {useNavigateTo} from '../../common/navigation'
-import {useFlashAndScroll} from '../../common/useFlashAndScroll'
-import {BalanceAfter} from './Summary/BalanceAfter'
-import {CurrentBalance} from './Summary/CurrentBalance'
-import {Fees} from './Summary/Fees'
-import {PrimaryTotal} from './Summary/PrimaryTotal'
-import {ReceiverInfo} from './Summary/ReceiverInfo'
-import {SecondaryTotals} from './Summary/SecondaryTotals'
-
-export const ConfirmTxScreen = () => {
- const strings = useStrings()
- const styles = useStyles()
- const {wallet, meta} = useSelectedWallet()
- const navigateTo = useNavigateTo()
- const [password, setPassword] = React.useState('')
- const [useUSB, setUseUSB] = React.useState(false)
- const {track} = useMetrics()
-
- const {memo, selectedTargetIndex, unsignedTx: yoroiUnsignedTx, targets} = useTransfer()
- const {amounts} = targets[selectedTargetIndex].entry
-
- const {saveMemo} = useSaveMemo({wallet})
-
- useEffect(() => {
- if (features.prefillWalletInfo && isDev) {
- setPassword(debugWalletInfo.PASSWORD)
- }
- }, [])
-
- const sendProperties = React.useMemo(() => assetsToSendProperties({amounts}), [amounts])
-
- useFocusEffect(
- React.useCallback(() => {
- track.sendSummaryPageViewed(sendProperties)
- // eslint-disable-next-line react-hooks/exhaustive-deps
- }, [track]),
- )
-
- const onSuccess = (signedTx: YoroiSignedTx) => {
- track.sendSummarySubmitted(sendProperties)
- navigateTo.submittedTx(signedTx.signedTx.id)
-
- if (memo.length > 0) {
- saveMemo({txId: signedTx.signedTx.id, memo: memo.trim()})
- }
- }
-
- const onError = () => {
- track.sendSummarySubmitted(sendProperties)
- navigateTo.failedTx()
- }
-
- const scrollViewRef = useFlashAndScroll()
-
- if (yoroiUnsignedTx === undefined) throw new Error('Missing yoroiUnsignedTx')
-
- return (
-
-
-
-
-
-
-
-
-
-
-
-
-
- {targets.map((target, index) => (
-
- ))}
-
-
-
-
-
-
-
-
-
-
-
- {!meta.isEasyConfirmationEnabled && !meta.isHW && (
-
- )}
-
-
-
-
-
-
-
- )
-}
-
-const Actions = ({style, ...props}: ViewProps) => {
- const styles = useStyles()
- return
-}
-
-const useStyles = () => {
- const {color, atoms} = useTheme()
-
- const styles = StyleSheet.create({
- root: {
- backgroundColor: color.bg_color_max,
- },
- safeAreaView: {
- ...atoms.gap_lg,
- ...atoms.pb_lg,
- },
- scrollView: {
- ...atoms.flex_1,
- ...atoms.px_lg,
- },
- flex: {
- ...atoms.flex_1,
- },
- actions: {
- ...atoms.px_lg,
- },
- })
- return styles
-}
-
-const useStrings = () => {
- const intl = useIntl()
-
- return {
- availableFunds: intl.formatMessage(globalMessages.availableFunds),
- balanceAfterTx: intl.formatMessage(txLabels.balanceAfterTx),
- total: intl.formatMessage(globalMessages.total),
- password: intl.formatMessage(txLabels.password),
- confirmButton: intl.formatMessage(confirmationMessages.commonButtons.confirmButton),
- submittingTx: intl.formatMessage(txLabels.submittingTx),
- pleaseWait: intl.formatMessage(globalMessages.pleaseWait),
- generalTxError: {
- title: intl.formatMessage(errorMessages.generalTxError.title),
- message: intl.formatMessage(errorMessages.generalTxError.message),
- },
- }
-}
diff --git a/apps/wallet-mobile/src/features/Send/useCases/ListAmountsToSend/ListAmountsToSendScreen.tsx b/apps/wallet-mobile/src/features/Send/useCases/ListAmountsToSend/ListAmountsToSendScreen.tsx
index aebcf4ff06..ee534acc81 100644
--- a/apps/wallet-mobile/src/features/Send/useCases/ListAmountsToSend/ListAmountsToSendScreen.tsx
+++ b/apps/wallet-mobile/src/features/Send/useCases/ListAmountsToSend/ListAmountsToSendScreen.tsx
@@ -18,7 +18,9 @@ import {Spacer} from '../../../../components/Spacer/Spacer'
import globalMessages from '../../../../kernel/i18n/global-messages'
import {assetsToSendProperties} from '../../../../kernel/metrics/helpers'
import {useMetrics} from '../../../../kernel/metrics/metricsManager'
-import {YoroiEntry} from '../../../../yoroi-wallets/types/yoroi'
+import {useWalletNavigation} from '../../../../kernel/navigation'
+import {useSaveMemo} from '../../../../yoroi-wallets/hooks'
+import {YoroiEntry, YoroiSignedTx} from '../../../../yoroi-wallets/types/yoroi'
import {TokenAmountItem} from '../../../Portfolio/common/TokenAmountItem/TokenAmountItem'
import {useSearch} from '../../../Search/SearchContext'
import {useSelectedWallet} from '../../../WalletManager/common/hooks/useSelectedWallet'
@@ -30,10 +32,12 @@ import {RemoveAmountButton} from './RemoveAmount'
export const ListAmountsToSendScreen = () => {
const {styles} = useStyles()
const navigateTo = useNavigateTo()
+ const {navigateToTxReview} = useWalletNavigation()
const strings = useStrings()
const {clearSearch} = useSearch()
const navigation = useNavigation()
const {track} = useMetrics()
+ const {wallet} = useSelectedWallet()
useOverridePreviousSendTxRoute('send-start-tx')
@@ -42,16 +46,17 @@ export const ListAmountsToSendScreen = () => {
}, [navigation])
const {
+ memo,
targets,
selectedTargetIndex,
tokenSelectedChanged,
amountRemoved,
unsignedTxChanged: yoroiUnsignedTxChanged,
} = useTransfer()
+ const {saveMemo} = useSaveMemo({wallet})
const {amounts} = targets[selectedTargetIndex].entry
const selectedTokensCounter = Object.keys(amounts).length
- const {wallet} = useSelectedWallet()
const {
meta: {addressMode},
} = useSelectedWallet()
@@ -80,6 +85,23 @@ export const ListAmountsToSendScreen = () => {
}
amountRemoved(tokenId)
}
+
+ const sendProperties = React.useMemo(() => assetsToSendProperties({amounts}), [amounts])
+
+ const onSuccess = (signedTx: YoroiSignedTx) => {
+ track.sendSummarySubmitted(sendProperties)
+ navigateTo.submittedTx(signedTx.signedTx.id)
+
+ if (memo.length > 0) {
+ saveMemo({txId: signedTx.signedTx.id, memo: memo.trim()})
+ }
+ }
+
+ const onError = () => {
+ track.sendSummarySubmitted(sendProperties)
+ navigateTo.failedTx()
+ }
+
const onNext = () => {
track.sendSelectAssetSelected(assetsToSendProperties({amounts}))
// since the user can't see many targets we just send the first one
@@ -87,7 +109,7 @@ export const ListAmountsToSendScreen = () => {
createUnsignedTx([toYoroiEntry(targets[selectedTargetIndex].entry)], {
onSuccess: (yoroiUnsignedTx) => {
yoroiUnsignedTxChanged(yoroiUnsignedTx)
- navigateTo.confirmTx()
+ navigateToTxReview({unsignedTx: yoroiUnsignedTx, onSuccess, onError})
},
})
}
diff --git a/apps/wallet-mobile/src/features/Transactions/TxHistoryNavigator.tsx b/apps/wallet-mobile/src/features/Transactions/TxHistoryNavigator.tsx
index 4b31210739..5e49b3da57 100644
--- a/apps/wallet-mobile/src/features/Transactions/TxHistoryNavigator.tsx
+++ b/apps/wallet-mobile/src/features/Transactions/TxHistoryNavigator.tsx
@@ -30,7 +30,6 @@ import {ListMultipleAddressesScreen} from '../Receive/useCases/ListMultipleAddre
import {RequestSpecificAmountScreen} from '../Receive/useCases/RequestSpecificAmountScreen'
import {ScanCodeScreen} from '../Scan/useCases/ScanCodeScreen'
import {ShowCameraPermissionDeniedScreen} from '../Scan/useCases/ShowCameraPermissionDeniedScreen/ShowCameraPermissionDeniedScreen'
-import {ConfirmTxScreen} from '../Send/useCases/ConfirmTx/ConfirmTxScreen'
import {FailedTxScreen} from '../Send/useCases/ConfirmTx/FailedTx/FailedTxScreen'
import {SubmittedTxScreen} from '../Send/useCases/ConfirmTx/SubmittedTx/SubmittedTxScreen'
import {SelectTokenFromListScreen} from '../Send/useCases/ListAmountsToSend/AddToken/SelectTokenFromListScreen'
@@ -352,15 +351,6 @@ export const TxHistoryNavigator = () => {
)}
-
-
- 'nft-details-routes': NavigatorScreenParams
+ 'review-tx-routes': NavigatorScreenParams
settings: NavigatorScreenParams
'voting-registration': NavigatorScreenParams
'toggle-analytics-settings': NavigatorScreenParams
@@ -153,7 +153,6 @@ export type TxHistoryRoutes = {
'receive-specific-amount': undefined
'receive-multiple': undefined
'send-start-tx': undefined
- 'send-confirm-tx': undefined
'send-submitted-tx': {txId: string}
'send-failed-tx': undefined
'send-list-amounts-to-send': undefined
@@ -294,6 +293,20 @@ export type PortfolioRoutes = {
history: NavigatorScreenParams
}
+export type ReviewTxRoutes = {
+ 'review-tx': {
+ unsignedTx?: YoroiUnsignedTx
+ cbor?: string
+ onSuccess: (signedTx: YoroiSignedTx) => void
+ onError: () => void
+ }
+}
+
+export type PortfolioTokenListTabRoutes = {
+ 'wallet-token': undefined
+ 'dapps-token': undefined
+}
+
export type VotingRegistrationRoutes = {
'download-catalyst': undefined
'display-pin': undefined
@@ -451,6 +464,16 @@ export const useWalletNavigation = () => {
})
},
+ navigateToTxReview: (params: ReviewTxRoutes['review-tx']) => {
+ navigation.navigate('manage-wallets', {
+ screen: 'review-tx-routes',
+ params: {
+ screen: 'review-tx',
+ params,
+ },
+ })
+ },
+
resetToWalletSetupInit: () => {
navigation.reset({
index: 0,
diff --git a/apps/wallet-mobile/src/yoroi-wallets/cardano/utils.ts b/apps/wallet-mobile/src/yoroi-wallets/cardano/utils.ts
index 2dc0600e65..226323f31c 100644
--- a/apps/wallet-mobile/src/yoroi-wallets/cardano/utils.ts
+++ b/apps/wallet-mobile/src/yoroi-wallets/cardano/utils.ts
@@ -31,6 +31,47 @@ export const deriveRewardAddressHex = async (
return result
}
+export const getAddressType = async (address: string) => {
+ const isKeyAddress = await getIsAddressKeyBech32Format(address)
+ if (isKeyAddress) return 'key'
+
+ const isScriptAddress = await getIsAddressScriptBech32Format(address)
+ if (isScriptAddress) return 'script'
+
+ const isRewardAddress = await getIsRewardAddressBech32Format(address)
+ if (isRewardAddress) return 'reward'
+
+ throw new Error('invalid address format')
+}
+
+const getIsAddressKeyBech32Format = async (address: string): Promise => {
+ try {
+ await CardanoMobile.Ed25519KeyHash.fromBech32(address)
+ return true
+ } catch {
+ return false
+ }
+}
+
+const getIsAddressScriptBech32Format = async (address: string): Promise => {
+ try {
+ await CardanoMobile.ScriptHash.fromBech32(address)
+ return true
+ } catch {
+ return false
+ }
+}
+
+const getIsRewardAddressBech32Format = async (addressBech32: string): Promise => {
+ try {
+ const address = await CardanoMobile.Address.fromBech32(addressBech32)
+ await CardanoMobile.RewardAddress.fromAddress(address)
+ return true
+ } catch {
+ return false
+ }
+}
+
/**
* Multi-asset related
*/
diff --git a/apps/wallet-mobile/translations/messages/src/WalletNavigator.json b/apps/wallet-mobile/translations/messages/src/WalletNavigator.json
index 6e8179e2ac..f8e4bc086c 100644
--- a/apps/wallet-mobile/translations/messages/src/WalletNavigator.json
+++ b/apps/wallet-mobile/translations/messages/src/WalletNavigator.json
@@ -4,14 +4,14 @@
"defaultMessage": "!!!Transactions",
"file": "src/WalletNavigator.tsx",
"start": {
- "line": 303,
+ "line": 309,
"column": 22,
- "index": 10476
+ "index": 10838
},
"end": {
- "line": 306,
+ "line": 312,
"column": 3,
- "index": 10579
+ "index": 10941
}
},
{
@@ -19,14 +19,14 @@
"defaultMessage": "!!!Send",
"file": "src/WalletNavigator.tsx",
"start": {
- "line": 307,
+ "line": 313,
"column": 14,
- "index": 10595
+ "index": 10957
},
"end": {
- "line": 310,
+ "line": 316,
"column": 3,
- "index": 10694
+ "index": 11056
}
},
{
@@ -34,14 +34,14 @@
"defaultMessage": "!!!Receive",
"file": "src/WalletNavigator.tsx",
"start": {
- "line": 311,
+ "line": 317,
"column": 17,
- "index": 10713
+ "index": 11075
},
"end": {
- "line": 314,
+ "line": 320,
"column": 3,
- "index": 10818
+ "index": 11180
}
},
{
@@ -49,14 +49,14 @@
"defaultMessage": "!!!Dashboard",
"file": "src/WalletNavigator.tsx",
"start": {
- "line": 315,
+ "line": 321,
"column": 19,
- "index": 10839
+ "index": 11201
},
"end": {
- "line": 318,
+ "line": 324,
"column": 3,
- "index": 10936
+ "index": 11298
}
},
{
@@ -64,14 +64,14 @@
"defaultMessage": "!!!Delegate",
"file": "src/WalletNavigator.tsx",
"start": {
- "line": 319,
+ "line": 325,
"column": 18,
- "index": 10956
+ "index": 11318
},
"end": {
- "line": 322,
+ "line": 328,
"column": 3,
- "index": 11051
+ "index": 11413
}
},
{
@@ -79,14 +79,14 @@
"defaultMessage": "!!!Wallet",
"file": "src/WalletNavigator.tsx",
"start": {
- "line": 323,
+ "line": 329,
"column": 16,
- "index": 11069
+ "index": 11431
},
"end": {
- "line": 326,
+ "line": 332,
"column": 3,
- "index": 11167
+ "index": 11529
}
},
{
@@ -94,14 +94,14 @@
"defaultMessage": "!!!Staking",
"file": "src/WalletNavigator.tsx",
"start": {
- "line": 327,
+ "line": 333,
"column": 17,
- "index": 11186
+ "index": 11548
},
"end": {
- "line": 330,
+ "line": 336,
"column": 3,
- "index": 11251
+ "index": 11613
}
},
{
@@ -109,14 +109,14 @@
"defaultMessage": "!!!NFT Gallery",
"file": "src/WalletNavigator.tsx",
"start": {
- "line": 331,
+ "line": 337,
"column": 14,
- "index": 11267
+ "index": 11629
},
"end": {
- "line": 334,
+ "line": 340,
"column": 3,
- "index": 11361
+ "index": 11723
}
},
{
@@ -124,14 +124,14 @@
"defaultMessage": "!!!Menu",
"file": "src/WalletNavigator.tsx",
"start": {
- "line": 335,
+ "line": 341,
"column": 14,
- "index": 11377
+ "index": 11739
},
"end": {
- "line": 338,
+ "line": 344,
"column": 3,
- "index": 11429
+ "index": 11791
}
},
{
@@ -139,14 +139,14 @@
"defaultMessage": "!!!Discover",
"file": "src/WalletNavigator.tsx",
"start": {
- "line": 339,
+ "line": 345,
"column": 18,
- "index": 11449
+ "index": 11811
},
"end": {
- "line": 342,
+ "line": 348,
"column": 3,
- "index": 11538
+ "index": 11900
}
},
{
@@ -154,14 +154,14 @@
"defaultMessage": "!!!My wallets",
"file": "src/WalletNavigator.tsx",
"start": {
- "line": 343,
+ "line": 349,
"column": 31,
- "index": 11571
+ "index": 11933
},
"end": {
- "line": 346,
+ "line": 352,
"column": 3,
- "index": 11680
+ "index": 12042
}
},
{
@@ -169,14 +169,14 @@
"defaultMessage": "!!!Portfolio",
"file": "src/WalletNavigator.tsx",
"start": {
- "line": 347,
+ "line": 353,
"column": 19,
- "index": 11701
+ "index": 12063
},
"end": {
- "line": 350,
+ "line": 356,
"column": 3,
- "index": 11770
+ "index": 12132
}
}
]
\ No newline at end of file
diff --git a/apps/wallet-mobile/translations/messages/src/features/ReviewTx/common/hooks/useStrings.json b/apps/wallet-mobile/translations/messages/src/features/ReviewTx/common/hooks/useStrings.json
new file mode 100644
index 0000000000..77bbb70459
--- /dev/null
+++ b/apps/wallet-mobile/translations/messages/src/features/ReviewTx/common/hooks/useStrings.json
@@ -0,0 +1,212 @@
+[
+ {
+ "id": "txReview.confirm",
+ "defaultMessage": "!!!Confirm",
+ "file": "src/features/ReviewTx/common/hooks/useStrings.tsx",
+ "start": {
+ "line": 28,
+ "column": 11,
+ "index": 1188
+ },
+ "end": {
+ "line": 31,
+ "column": 3,
+ "index": 1255
+ }
+ },
+ {
+ "id": "txReview.title",
+ "defaultMessage": "!!!UTxOs",
+ "file": "src/features/ReviewTx/common/hooks/useStrings.tsx",
+ "start": {
+ "line": 32,
+ "column": 9,
+ "index": 1266
+ },
+ "end": {
+ "line": 35,
+ "column": 3,
+ "index": 1329
+ }
+ },
+ {
+ "id": "txReview.tabLabel.utxos",
+ "defaultMessage": "!!!UTxOs",
+ "file": "src/features/ReviewTx/common/hooks/useStrings.tsx",
+ "start": {
+ "line": 36,
+ "column": 12,
+ "index": 1343
+ },
+ "end": {
+ "line": 39,
+ "column": 3,
+ "index": 1415
+ }
+ },
+ {
+ "id": "txReview.tabLabel.overview",
+ "defaultMessage": "!!!Overview",
+ "file": "src/features/ReviewTx/common/hooks/useStrings.tsx",
+ "start": {
+ "line": 40,
+ "column": 15,
+ "index": 1432
+ },
+ "end": {
+ "line": 43,
+ "column": 3,
+ "index": 1510
+ }
+ },
+ {
+ "id": "txReview.overview.wallet",
+ "defaultMessage": "!!!Wallet",
+ "file": "src/features/ReviewTx/common/hooks/useStrings.tsx",
+ "start": {
+ "line": 44,
+ "column": 15,
+ "index": 1527
+ },
+ "end": {
+ "line": 47,
+ "column": 3,
+ "index": 1601
+ }
+ },
+ {
+ "id": "txReview.fee",
+ "defaultMessage": "!!!Fee",
+ "file": "src/features/ReviewTx/common/hooks/useStrings.tsx",
+ "start": {
+ "line": 48,
+ "column": 12,
+ "index": 1615
+ },
+ "end": {
+ "line": 51,
+ "column": 3,
+ "index": 1674
+ }
+ },
+ {
+ "id": "txReview.overview.myWalletLabel",
+ "defaultMessage": "!!!Your Wallet",
+ "file": "src/features/ReviewTx/common/hooks/useStrings.tsx",
+ "start": {
+ "line": 52,
+ "column": 17,
+ "index": 1693
+ },
+ "end": {
+ "line": 55,
+ "column": 3,
+ "index": 1779
+ }
+ },
+ {
+ "id": "txReview.overview.sendLabel",
+ "defaultMessage": "!!!Send",
+ "file": "src/features/ReviewTx/common/hooks/useStrings.tsx",
+ "start": {
+ "line": 56,
+ "column": 13,
+ "index": 1794
+ },
+ "end": {
+ "line": 59,
+ "column": 3,
+ "index": 1869
+ }
+ },
+ {
+ "id": "txReview.overview.receiveToLabel",
+ "defaultMessage": "!!!receiveToLabel",
+ "file": "src/features/ReviewTx/common/hooks/useStrings.tsx",
+ "start": {
+ "line": 60,
+ "column": 18,
+ "index": 1889
+ },
+ "end": {
+ "line": 63,
+ "column": 3,
+ "index": 1979
+ }
+ },
+ {
+ "id": "txReview.overview.receiveToScriptLabel",
+ "defaultMessage": "!!!To script",
+ "file": "src/features/ReviewTx/common/hooks/useStrings.tsx",
+ "start": {
+ "line": 64,
+ "column": 24,
+ "index": 2005
+ },
+ "end": {
+ "line": 67,
+ "column": 3,
+ "index": 2096
+ }
+ },
+ {
+ "id": "txReview.utxos.utxosInputsLabel",
+ "defaultMessage": "!!!Inputs",
+ "file": "src/features/ReviewTx/common/hooks/useStrings.tsx",
+ "start": {
+ "line": 68,
+ "column": 20,
+ "index": 2118
+ },
+ "end": {
+ "line": 71,
+ "column": 3,
+ "index": 2199
+ }
+ },
+ {
+ "id": "txReview.utxos.utxosOutputsLabel",
+ "defaultMessage": "!!!Outputs",
+ "file": "src/features/ReviewTx/common/hooks/useStrings.tsx",
+ "start": {
+ "line": 72,
+ "column": 21,
+ "index": 2222
+ },
+ "end": {
+ "line": 75,
+ "column": 3,
+ "index": 2305
+ }
+ },
+ {
+ "id": "txReview.utxos.utxosYourAddressLabel",
+ "defaultMessage": "!!!Your address",
+ "file": "src/features/ReviewTx/common/hooks/useStrings.tsx",
+ "start": {
+ "line": 76,
+ "column": 25,
+ "index": 2332
+ },
+ "end": {
+ "line": 79,
+ "column": 3,
+ "index": 2424
+ }
+ },
+ {
+ "id": "txReview.utxos.utxosForeignAddressLabel",
+ "defaultMessage": "!!!Foreign address",
+ "file": "src/features/ReviewTx/common/hooks/useStrings.tsx",
+ "start": {
+ "line": 80,
+ "column": 28,
+ "index": 2454
+ },
+ "end": {
+ "line": 83,
+ "column": 3,
+ "index": 2552
+ }
+ }
+]
\ No newline at end of file
diff --git a/apps/wallet-mobile/translations/messages/src/features/Send/useCases/ListAmountsToSend/ListAmountsToSendScreen.json b/apps/wallet-mobile/translations/messages/src/features/Send/useCases/ListAmountsToSend/ListAmountsToSendScreen.json
index 4cfe771548..0c575d3101 100644
--- a/apps/wallet-mobile/translations/messages/src/features/Send/useCases/ListAmountsToSend/ListAmountsToSendScreen.json
+++ b/apps/wallet-mobile/translations/messages/src/features/Send/useCases/ListAmountsToSend/ListAmountsToSendScreen.json
@@ -4,14 +4,14 @@
"defaultMessage": "!!!Add token",
"file": "src/features/Send/useCases/ListAmountsToSend/ListAmountsToSendScreen.tsx",
"start": {
- "line": 207,
+ "line": 229,
"column": 12,
- "index": 6589
+ "index": 7330
},
"end": {
- "line": 210,
+ "line": 232,
"column": 3,
- "index": 6666
+ "index": 7407
}
}
]
\ No newline at end of file
diff --git a/apps/wallet-mobile/translations/messages/src/features/Transactions/TxHistoryNavigator.json b/apps/wallet-mobile/translations/messages/src/features/Transactions/TxHistoryNavigator.json
index ee02d523a1..cb695ac224 100644
--- a/apps/wallet-mobile/translations/messages/src/features/Transactions/TxHistoryNavigator.json
+++ b/apps/wallet-mobile/translations/messages/src/features/Transactions/TxHistoryNavigator.json
@@ -4,14 +4,14 @@
"defaultMessage": "!!!Receive",
"file": "src/features/Transactions/TxHistoryNavigator.tsx",
"start": {
- "line": 414,
+ "line": 404,
"column": 16,
- "index": 15157
+ "index": 14797
},
"end": {
- "line": 417,
+ "line": 407,
"column": 3,
- "index": 15246
+ "index": 14886
}
},
{
@@ -19,14 +19,14 @@
"defaultMessage": "!!!Address details",
"file": "src/features/Transactions/TxHistoryNavigator.tsx",
"start": {
- "line": 418,
+ "line": 408,
"column": 32,
- "index": 15280
+ "index": 14920
},
"end": {
- "line": 421,
+ "line": 411,
"column": 3,
- "index": 15393
+ "index": 15033
}
},
{
@@ -34,14 +34,14 @@
"defaultMessage": "!!!Swap",
"file": "src/features/Transactions/TxHistoryNavigator.tsx",
"start": {
- "line": 422,
+ "line": 412,
"column": 13,
- "index": 15408
+ "index": 15048
},
"end": {
- "line": 425,
+ "line": 415,
"column": 3,
- "index": 15481
+ "index": 15121
}
},
{
@@ -49,14 +49,14 @@
"defaultMessage": "!!!Swap from",
"file": "src/features/Transactions/TxHistoryNavigator.tsx",
"start": {
- "line": 426,
+ "line": 416,
"column": 17,
- "index": 15500
+ "index": 15140
},
"end": {
- "line": 429,
+ "line": 419,
"column": 3,
- "index": 15577
+ "index": 15217
}
},
{
@@ -64,14 +64,14 @@
"defaultMessage": "!!!Swap to",
"file": "src/features/Transactions/TxHistoryNavigator.tsx",
"start": {
- "line": 430,
+ "line": 420,
"column": 15,
- "index": 15594
+ "index": 15234
},
"end": {
- "line": 433,
+ "line": 423,
"column": 3,
- "index": 15667
+ "index": 15307
}
},
{
@@ -79,14 +79,14 @@
"defaultMessage": "!!!Slippage Tolerance",
"file": "src/features/Transactions/TxHistoryNavigator.tsx",
"start": {
- "line": 434,
+ "line": 424,
"column": 21,
- "index": 15690
+ "index": 15330
},
"end": {
- "line": 437,
+ "line": 427,
"column": 3,
- "index": 15785
+ "index": 15425
}
},
{
@@ -94,14 +94,14 @@
"defaultMessage": "!!!Select pool",
"file": "src/features/Transactions/TxHistoryNavigator.tsx",
"start": {
- "line": 438,
+ "line": 428,
"column": 14,
- "index": 15801
+ "index": 15441
},
"end": {
- "line": 441,
+ "line": 431,
"column": 3,
- "index": 15882
+ "index": 15522
}
},
{
@@ -109,14 +109,14 @@
"defaultMessage": "!!!Send",
"file": "src/features/Transactions/TxHistoryNavigator.tsx",
"start": {
- "line": 442,
+ "line": 432,
"column": 13,
- "index": 15897
+ "index": 15537
},
"end": {
- "line": 445,
+ "line": 435,
"column": 3,
- "index": 15977
+ "index": 15617
}
},
{
@@ -124,14 +124,14 @@
"defaultMessage": "!!!Scan QR code address",
"file": "src/features/Transactions/TxHistoryNavigator.tsx",
"start": {
- "line": 446,
+ "line": 436,
"column": 18,
- "index": 15997
+ "index": 15637
},
"end": {
- "line": 449,
+ "line": 439,
"column": 3,
- "index": 16098
+ "index": 15738
}
},
{
@@ -139,14 +139,14 @@
"defaultMessage": "!!!Select asset",
"file": "src/features/Transactions/TxHistoryNavigator.tsx",
"start": {
- "line": 450,
+ "line": 440,
"column": 20,
- "index": 16120
+ "index": 15760
},
"end": {
- "line": 453,
+ "line": 443,
"column": 3,
- "index": 16209
+ "index": 15849
}
},
{
@@ -154,14 +154,14 @@
"defaultMessage": "!!!Assets added",
"file": "src/features/Transactions/TxHistoryNavigator.tsx",
"start": {
- "line": 454,
+ "line": 444,
"column": 26,
- "index": 16237
+ "index": 15877
},
"end": {
- "line": 457,
+ "line": 447,
"column": 3,
- "index": 16338
+ "index": 15978
}
},
{
@@ -169,14 +169,14 @@
"defaultMessage": "!!!Edit amount",
"file": "src/features/Transactions/TxHistoryNavigator.tsx",
"start": {
- "line": 458,
+ "line": 448,
"column": 19,
- "index": 16359
+ "index": 15999
},
"end": {
- "line": 461,
+ "line": 451,
"column": 3,
- "index": 16452
+ "index": 16092
}
},
{
@@ -184,14 +184,14 @@
"defaultMessage": "!!!Confirm",
"file": "src/features/Transactions/TxHistoryNavigator.tsx",
"start": {
- "line": 462,
+ "line": 452,
"column": 16,
- "index": 16470
+ "index": 16110
},
"end": {
- "line": 465,
+ "line": 455,
"column": 3,
- "index": 16556
+ "index": 16196
}
},
{
@@ -199,14 +199,14 @@
"defaultMessage": "!!!Share this address to receive payments. To protect your privacy, new addresses are generated automatically once you use them.",
"file": "src/features/Transactions/TxHistoryNavigator.tsx",
"start": {
- "line": 466,
+ "line": 456,
"column": 19,
- "index": 16577
+ "index": 16217
},
"end": {
- "line": 472,
+ "line": 462,
"column": 3,
- "index": 16815
+ "index": 16455
}
},
{
@@ -214,14 +214,14 @@
"defaultMessage": "!!!Confirm transaction",
"file": "src/features/Transactions/TxHistoryNavigator.tsx",
"start": {
- "line": 473,
+ "line": 463,
"column": 27,
- "index": 16844
+ "index": 16484
},
"end": {
- "line": 476,
+ "line": 466,
"column": 3,
- "index": 16937
+ "index": 16577
}
},
{
@@ -229,14 +229,14 @@
"defaultMessage": "!!!Please scan a QR code",
"file": "src/features/Transactions/TxHistoryNavigator.tsx",
"start": {
- "line": 477,
+ "line": 467,
"column": 13,
- "index": 16952
+ "index": 16592
},
"end": {
- "line": 480,
+ "line": 470,
"column": 3,
- "index": 17027
+ "index": 16667
}
},
{
@@ -244,14 +244,14 @@
"defaultMessage": "!!!Success",
"file": "src/features/Transactions/TxHistoryNavigator.tsx",
"start": {
- "line": 481,
+ "line": 471,
"column": 25,
- "index": 17054
+ "index": 16694
},
"end": {
- "line": 484,
+ "line": 474,
"column": 3,
- "index": 17128
+ "index": 16768
}
},
{
@@ -259,14 +259,14 @@
"defaultMessage": "!!!Request specific amount",
"file": "src/features/Transactions/TxHistoryNavigator.tsx",
"start": {
- "line": 485,
+ "line": 475,
"column": 18,
- "index": 17148
+ "index": 16788
},
"end": {
- "line": 488,
+ "line": 478,
"column": 3,
- "index": 17262
+ "index": 16902
}
},
{
@@ -274,14 +274,14 @@
"defaultMessage": "!!!Buy/Sell ADA",
"file": "src/features/Transactions/TxHistoryNavigator.tsx",
"start": {
- "line": 489,
+ "line": 479,
"column": 28,
- "index": 17292
+ "index": 16932
},
"end": {
- "line": 492,
+ "line": 482,
"column": 3,
- "index": 17388
+ "index": 17028
}
},
{
@@ -289,14 +289,14 @@
"defaultMessage": "!!!Buy provider",
"file": "src/features/Transactions/TxHistoryNavigator.tsx",
"start": {
- "line": 493,
+ "line": 483,
"column": 29,
- "index": 17419
+ "index": 17059
},
"end": {
- "line": 496,
+ "line": 486,
"column": 3,
- "index": 17527
+ "index": 17167
}
},
{
@@ -304,14 +304,14 @@
"defaultMessage": "!!!Sell provider",
"file": "src/features/Transactions/TxHistoryNavigator.tsx",
"start": {
- "line": 497,
+ "line": 487,
"column": 30,
- "index": 17559
+ "index": 17199
},
"end": {
- "line": 500,
+ "line": 490,
"column": 3,
- "index": 17669
+ "index": 17309
}
},
{
@@ -319,14 +319,14 @@
"defaultMessage": "!!!Tx Details",
"file": "src/features/Transactions/TxHistoryNavigator.tsx",
"start": {
- "line": 501,
+ "line": 491,
"column": 18,
- "index": 17689
+ "index": 17329
},
"end": {
- "line": 504,
+ "line": 494,
"column": 3,
- "index": 17783
+ "index": 17423
}
}
]
\ No newline at end of file
diff --git a/packages/theme/src/base-palettes/dark-palette.ts b/packages/theme/src/base-palettes/dark-palette.ts
index 2d5582e877..8374a43683 100644
--- a/packages/theme/src/base-palettes/dark-palette.ts
+++ b/packages/theme/src/base-palettes/dark-palette.ts
@@ -39,6 +39,7 @@ export const darkPalette: BasePalette = {
black_static: '#000000',
white_static: '#FFFFFF',
+ green_static: '#08C29D',
sys_magenta_700: '#FFC0D0',
sys_magenta_600: '#FB9CB5',
diff --git a/packages/theme/src/base-palettes/light-palette.ts b/packages/theme/src/base-palettes/light-palette.ts
index dbddef3e92..5dedefc337 100644
--- a/packages/theme/src/base-palettes/light-palette.ts
+++ b/packages/theme/src/base-palettes/light-palette.ts
@@ -36,6 +36,7 @@ export const lightPalette: BasePalette = {
black_static: '#000000',
white_static: '#FFFFFF',
+ green_static: '#08C29D',
sys_magenta_700: '#CF053A',
sys_magenta_600: '#E80742',
diff --git a/packages/theme/src/types.ts b/packages/theme/src/types.ts
index fa8554796a..00da13f171 100644
--- a/packages/theme/src/types.ts
+++ b/packages/theme/src/types.ts
@@ -59,6 +59,7 @@ export type BasePalette = {
black_static: HexColor
white_static: HexColor
+ green_static: HexColor
sys_magenta_700: HexColor
sys_magenta_600: HexColor