Skip to content

Commit 62319a9

Browse files
committed
feature(wallet-mobile): new tx review for swap
1 parent b649d33 commit 62319a9

39 files changed

+865
-1788
lines changed

apps/wallet-mobile/.storybook/storybook.requires.js

+1-2
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

apps/wallet-mobile/src/features/ReviewTx/common/Accordion.tsx

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import {useTheme} from '@yoroi/theme'
22
import * as React from 'react'
3-
import {Animated, LayoutAnimation, StyleSheet, Text, TouchableOpacity, View} from 'react-native'
3+
import {Animated, Easing, StyleSheet, Text, TouchableOpacity, View} from 'react-native'
44

55
import {Icon} from '../../../components/Icon'
66

@@ -10,12 +10,12 @@ export const Accordion = ({label, children}: {label: string; children: React.Rea
1010
const animatedHeight = React.useRef(new Animated.Value(0)).current
1111

1212
const toggleSection = () => {
13-
LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut)
1413
setIsOpen(!isOpen)
1514
Animated.timing(animatedHeight, {
1615
toValue: isOpen ? 0 : 1,
1716
duration: 300,
1817
useNativeDriver: false,
18+
easing: Easing.ease,
1919
}).start()
2020
}
2121

apps/wallet-mobile/src/features/ReviewTx/common/CopiableText.tsx

+23-8
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,40 @@
11
import {useTheme} from '@yoroi/theme'
22
import * as React from 'react'
3-
import {StyleSheet, TouchableOpacity, View} from 'react-native'
3+
import {StyleSheet, TouchableOpacity, View, ViewStyle} from 'react-native'
44

55
import {useCopy} from '../../../components/Clipboard/ClipboardProvider'
66
import {Icon} from '../../../components/Icon'
77

8-
export const CopiableText = ({children, textToCopy}: {children: React.ReactNode; textToCopy: string}) => {
9-
const {styles, colors} = useStyles()
10-
const {copy} = useCopy()
8+
export const CopiableText = ({
9+
children,
10+
style,
11+
textToCopy,
12+
}: {
13+
children: React.ReactNode
14+
style?: ViewStyle
15+
textToCopy: string
16+
}) => {
17+
const {styles} = useStyles()
1118

1219
return (
13-
<View style={styles.text}>
20+
<View style={[styles.text, style]}>
1421
{children}
1522

16-
<TouchableOpacity onPress={() => copy({text: textToCopy})} activeOpacity={0.5}>
17-
<Icon.Copy size={24} color={colors.copy} />
18-
</TouchableOpacity>
23+
<CopyButton textToCopy={textToCopy} />
1924
</View>
2025
)
2126
}
2227

28+
export const CopyButton = ({textToCopy}: {textToCopy: string}) => {
29+
const {colors} = useStyles()
30+
const {copy} = useCopy()
31+
return (
32+
<TouchableOpacity onPress={() => copy({text: textToCopy})} activeOpacity={0.5}>
33+
<Icon.Copy size={24} color={colors.copy} />
34+
</TouchableOpacity>
35+
)
36+
}
37+
2338
const useStyles = () => {
2439
const {atoms, color} = useTheme()
2540
const styles = StyleSheet.create({

apps/wallet-mobile/src/features/ReviewTx/common/ReviewTxProvider.tsx

+29
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,9 @@ export const ReviewTxProvider = ({
2424
cborChanged: (cbor: ReviewTxState['cbor']) => dispatch({type: ReviewTxActionType.CborChanged, cbor}),
2525
operationsChanged: (operations: ReviewTxState['operations']) =>
2626
dispatch({type: ReviewTxActionType.OperationsChanged, operations}),
27+
customReceiverTitleChanged: (customReceiverTitle: ReviewTxState['customReceiverTitle']) =>
28+
dispatch({type: ReviewTxActionType.CustomReceiverTitleChanged, customReceiverTitle}),
29+
detailsChanged: (details: ReviewTxState['details']) => dispatch({type: ReviewTxActionType.DetailsChanged, details}),
2730
onSuccessChanged: (onSuccess: ReviewTxState['onSuccess']) =>
2831
dispatch({type: ReviewTxActionType.OnSuccessChanged, onSuccess}),
2932
onErrorChanged: (onError: ReviewTxState['onError']) => dispatch({type: ReviewTxActionType.OnErrorChanged, onError}),
@@ -55,6 +58,14 @@ const reviewTxReducer = (state: ReviewTxState, action: ReviewTxAction) => {
5558
draft.operations = action.operations
5659
break
5760

61+
case ReviewTxActionType.CustomReceiverTitleChanged:
62+
draft.customReceiverTitle = action.customReceiverTitle
63+
break
64+
65+
case ReviewTxActionType.DetailsChanged:
66+
draft.details = action.details
67+
break
68+
5869
case ReviewTxActionType.OnSuccessChanged:
5970
draft.onSuccess = action.onSuccess
6071
break
@@ -82,6 +93,14 @@ type ReviewTxAction =
8293
type: ReviewTxActionType.OperationsChanged
8394
operations: ReviewTxState['operations']
8495
}
96+
| {
97+
type: ReviewTxActionType.CustomReceiverTitleChanged
98+
customReceiverTitle: ReviewTxState['customReceiverTitle']
99+
}
100+
| {
101+
type: ReviewTxActionType.DetailsChanged
102+
details: ReviewTxState['details']
103+
}
85104
| {
86105
type: ReviewTxActionType.OnSuccessChanged
87106
onSuccess: ReviewTxState['onSuccess']
@@ -95,6 +114,8 @@ export type ReviewTxState = {
95114
unsignedTx: YoroiUnsignedTx | null
96115
cbor: string | null
97116
operations: Array<React.ReactNode> | null
117+
customReceiverTitle: React.ReactNode | null
118+
details: {title: string; component: React.ReactNode} | null
98119
onSuccess: ((signedTx: YoroiSignedTx) => void) | null
99120
onError: (() => void) | null
100121
}
@@ -103,6 +124,8 @@ type ReviewTxActions = {
103124
unsignedTxChanged: (unsignedTx: ReviewTxState['unsignedTx']) => void
104125
cborChanged: (cbor: ReviewTxState['cbor']) => void
105126
operationsChanged: (operations: ReviewTxState['operations']) => void
127+
customReceiverTitleChanged: (customReceiverTitle: ReviewTxState['customReceiverTitle']) => void
128+
detailsChanged: (details: ReviewTxState['details']) => void
106129
onSuccessChanged: (onSuccess: ReviewTxState['onSuccess']) => void
107130
onErrorChanged: (onError: ReviewTxState['onError']) => void
108131
}
@@ -111,6 +134,8 @@ const defaultState: ReviewTxState = Object.freeze({
111134
unsignedTx: null,
112135
cbor: null,
113136
operations: null,
137+
customReceiverTitle: null,
138+
details: null,
114139
onSuccess: null,
115140
onError: null,
116141
})
@@ -124,6 +149,8 @@ const initialReviewTxContext: ReviewTxContext = {
124149
unsignedTxChanged: missingInit,
125150
cborChanged: missingInit,
126151
operationsChanged: missingInit,
152+
customReceiverTitleChanged: missingInit,
153+
detailsChanged: missingInit,
127154
onSuccessChanged: missingInit,
128155
onErrorChanged: missingInit,
129156
}
@@ -132,6 +159,8 @@ enum ReviewTxActionType {
132159
UnsignedTxChanged = 'unsignedTxChanged',
133160
CborChanged = 'cborChanged',
134161
OperationsChanged = 'operationsChanged',
162+
CustomReceiverTitleChanged = 'customReceiverTitleChanged',
163+
DetailsChanged = 'detailsChanged',
135164
OnSuccessChanged = 'onSuccessChanged',
136165
OnErrorChanged = 'onErrorChanged',
137166
}

apps/wallet-mobile/src/features/ReviewTx/common/hooks/useAddressType.tsx

-12
This file was deleted.

apps/wallet-mobile/src/features/ReviewTx/common/hooks/useFormattedMetadata.tsx

+3-2
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,11 @@ import {TransactionBody} from '../types'
33

44
export const formatMetadata = (unsignedTx: YoroiUnsignedTx, txBody: TransactionBody) => {
55
const hash = txBody.auxiliary_data_hash ?? null
6-
const metadata = unsignedTx.metadata?.['674'] ?? null
6+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
7+
const metadata = unsignedTx.metadata?.['674']?.['msg' as any] ?? null
78

89
return {
910
hash,
10-
metadata,
11+
metadata: {msg: [JSON.parse(metadata)]},
1112
}
1213
}

apps/wallet-mobile/src/features/ReviewTx/common/hooks/useFormattedTx.tsx

+37-4
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
// import {CredKind} from '@emurgo/csl-mobile-bridge'
2+
import {CredKind} from '@emurgo/cross-csl-core'
13
import {isNonNullable} from '@yoroi/common'
24
import {infoExtractName} from '@yoroi/portfolio'
35
import {Portfolio} from '@yoroi/types'
@@ -6,6 +8,7 @@ import {useQuery} from 'react-query'
68

79
import {YoroiWallet} from '../../../../yoroi-wallets/cardano/types'
810
import {deriveRewardAddressFromAddress} from '../../../../yoroi-wallets/cardano/utils'
11+
import {wrappedCsl} from '../../../../yoroi-wallets/cardano/wrappedCsl'
912
import {formatTokenWithText} from '../../../../yoroi-wallets/utils/format'
1013
import {asQuantity} from '../../../../yoroi-wallets/utils/utils'
1114
import {usePortfolioTokenInfos} from '../../../Portfolio/common/hooks/usePortfolioTokenInfos'
@@ -14,12 +17,13 @@ import {
1417
FormattedFee,
1518
FormattedInputs,
1619
FormattedOutputs,
20+
FormattedTx,
1721
TransactionBody,
1822
TransactionInputs,
1923
TransactionOutputs,
2024
} from '../types'
2125

22-
export const useFormattedTx = (data: TransactionBody) => {
26+
export const useFormattedTx = (data: TransactionBody): FormattedTx => {
2327
const {wallet} = useSelectedWallet()
2428

2529
const inputs = data?.inputs ?? []
@@ -93,10 +97,14 @@ const formatInputs = async (
9397
inputs.map(async (input) => {
9498
const receiveUTxO = getUtxoByTxIdAndIndex(wallet, input.transaction_id, input.index)
9599
const address = receiveUTxO?.receiver
96-
const rewardAddress =
97-
address !== undefined ? await deriveRewardAddressFromAddress(address, wallet.networkManager.chainId) : null
98100
const coin = receiveUTxO?.amount != null ? asQuantity(receiveUTxO.amount) : null
99101

102+
const addressKind = address != null ? await getAddressKind(address) : null
103+
const rewardAddress =
104+
address != null && addressKind === CredKind.Key
105+
? await deriveAddress(address, wallet.networkManager.chainId)
106+
: null
107+
100108
const primaryAssets =
101109
coin != null
102110
? [
@@ -130,6 +138,7 @@ const formatInputs = async (
130138
return {
131139
assets: [...primaryAssets, ...multiAssets].filter(isNonNullable),
132140
address,
141+
addressKind: addressKind ?? null,
133142
rewardAddress,
134143
ownAddress: address != null && isOwnedAddress(wallet, address),
135144
txIndex: input.index,
@@ -147,9 +156,12 @@ const formatOutputs = async (
147156
return Promise.all(
148157
outputs.map(async (output) => {
149158
const address = output.address
150-
const rewardAddress = await deriveRewardAddressFromAddress(address, wallet.networkManager.chainId)
151159
const coin = asQuantity(output.amount.coin)
152160

161+
const addressKind = await getAddressKind(address)
162+
const rewardAddress =
163+
addressKind === CredKind.Key ? await deriveAddress(address, wallet.networkManager.chainId) : null
164+
153165
const primaryAssets = [
154166
{
155167
tokenInfo: wallet.portfolioPrimaryTokenInfo,
@@ -183,6 +195,7 @@ const formatOutputs = async (
183195
return {
184196
assets,
185197
address,
198+
addressKind,
186199
rewardAddress,
187200
ownAddress: isOwnedAddress(wallet, address),
188201
}
@@ -202,6 +215,26 @@ export const formatFee = (wallet: YoroiWallet, data: TransactionBody): Formatted
202215
}
203216
}
204217

218+
const deriveAddress = async (address: string, chainId: number) => {
219+
try {
220+
return await deriveRewardAddressFromAddress(address, chainId)
221+
} catch {
222+
return null
223+
}
224+
}
225+
226+
const getAddressKind = async (addressBech32: string): Promise<CredKind | null> => {
227+
const {csl, release} = wrappedCsl()
228+
229+
try {
230+
const address = await csl.Address.fromBech32(addressBech32)
231+
const addressKind = await (await address.paymentCred())?.kind()
232+
return addressKind ?? null
233+
} finally {
234+
release()
235+
}
236+
}
237+
205238
const getUtxoByTxIdAndIndex = (wallet: YoroiWallet, txId: string, index: number) => {
206239
return wallet.utxos.find((u) => u.tx_hash === txId && u.tx_index === index)
207240
}

apps/wallet-mobile/src/features/ReviewTx/common/hooks/useStrings.tsx

+15
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@ export const useStrings = () => {
1111
title: intl.formatMessage(messages.title),
1212
utxosTab: intl.formatMessage(messages.utxosTab),
1313
overviewTab: intl.formatMessage(messages.overviewTab),
14+
metadataTab: intl.formatMessage(messages.metadataTab),
15+
metadataHash: intl.formatMessage(messages.metadataHash),
16+
metadataJsonLabel: intl.formatMessage(messages.metadataJsonLabel),
1417
walletLabel: intl.formatMessage(messages.walletLabel),
1518
feeLabel: intl.formatMessage(messages.feeLabel),
1619
myWalletLabel: intl.formatMessage(messages.myWalletLabel),
@@ -52,6 +55,18 @@ const messages = defineMessages({
5255
id: 'txReview.tabLabel.overview',
5356
defaultMessage: '!!!Overview',
5457
},
58+
metadataTab: {
59+
id: 'txReview.tabLabel.metadataTab',
60+
defaultMessage: '!!!Metadata',
61+
},
62+
metadataHash: {
63+
id: 'txReview.metadata.metadataHash',
64+
defaultMessage: '!!!Metadata hash',
65+
},
66+
metadataJsonLabel: {
67+
id: 'txReview.metadata.metadataJsonLabel',
68+
defaultMessage: '!!!Metadata',
69+
},
5570
walletLabel: {
5671
id: 'txReview.overview.wallet',
5772
defaultMessage: '!!!Wallet',

apps/wallet-mobile/src/features/ReviewTx/common/hooks/useTxBody.tsx

+2-1
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,9 @@ import {useQuery} from 'react-query'
22

33
import {wrappedCsl} from '../../../../yoroi-wallets/cardano/wrappedCsl'
44
import {YoroiUnsignedTx} from '../../../../yoroi-wallets/types/yoroi'
5+
import {TransactionBody} from '../types'
56

6-
export const useTxBody = ({cbor, unsignedTx}: {cbor?: string; unsignedTx?: YoroiUnsignedTx}) => {
7+
export const useTxBody = ({cbor, unsignedTx}: {cbor?: string; unsignedTx?: YoroiUnsignedTx}): TransactionBody => {
78
const query = useQuery(
89
['useTxBody', cbor, unsignedTx],
910
async () => {

apps/wallet-mobile/src/features/ReviewTx/common/types.ts

+8
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import {
33
TransactionInputsJSON,
44
TransactionOutputsJSON,
55
} from '@emurgo/cardano-serialization-lib-nodejs'
6+
import {CredKind} from '@emurgo/cross-csl-core'
67
import {Balance, Portfolio} from '@yoroi/types'
78

89
export type TransactionBody = TransactionBodyJSON
@@ -18,6 +19,7 @@ export type FormattedInput = {
1819
isPrimary: boolean
1920
}>
2021
address: string | undefined
22+
addressKind: CredKind | null
2123
rewardAddress: string | null
2224
ownAddress: boolean
2325
txIndex: number
@@ -35,6 +37,7 @@ export type FormattedOutput = {
3537
isPrimary: boolean
3638
}>
3739
address: string
40+
addressKind: CredKind | null
3841
rewardAddress: string | null
3942
ownAddress: boolean
4043
}
@@ -54,3 +57,8 @@ export type FormattedTx = {
5457
outputs: FormattedOutputs
5558
fee: FormattedFee
5659
}
60+
61+
export type Metadata = {
62+
json: string | null
63+
hash: string | null
64+
}

apps/wallet-mobile/src/features/ReviewTx/useCases/ReviewTxScreen/Metadata/MetadataTab.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import {useStrings} from '../../../common/hooks/useStrings'
88

99
type Props = {
1010
hash: string | null
11-
metadata: string | null
11+
metadata: {msg: Array<string>} | null
1212
}
1313

1414
export const MetadataTab = ({metadata, hash}: Props) => {

0 commit comments

Comments
 (0)