Skip to content

Commit

Permalink
feature(wallet-mobile): new tx review dapps info (#3735)
Browse files Browse the repository at this point in the history
  • Loading branch information
banklesss authored Nov 14, 2024
1 parent 2ed6d66 commit eb750c6
Show file tree
Hide file tree
Showing 10 changed files with 416 additions and 345 deletions.
12 changes: 6 additions & 6 deletions apps/wallet-mobile/src/features/Discover/common/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,9 +64,9 @@ type CreateDappConnectorOptions = {
wallet: YoroiWallet
meta: Wallet.Meta
confirmConnection: (origin: string, manager: DappConnector) => Promise<boolean>
signTx: (cbor: string) => Promise<string>
signTx: (options: {cbor: string; manager: DappConnector}) => Promise<string>
signData: (address: string, payload: string) => Promise<string>
signTxWithHW: (cbor: string, partial?: boolean) => Promise<Transaction>
signTxWithHW: (options: {cbor: string; partial?: boolean; manager: DappConnector}) => Promise<Transaction>
signDataWithHW: (address: string, payload: string) => Promise<{signature: string; key: string}>
}

Expand Down Expand Up @@ -115,17 +115,17 @@ export const createDappConnector = (options: CreateDappConnectorOptions) => {
},
signTx: async (cbor: string, partial?: boolean) => {
if (meta.isHW) {
const tx = await options.signTxWithHW(cbor, partial)
const tx = await options.signTxWithHW({cbor, partial, manager})
return tx.witnessSet()
}

const rootKey = await signTx(cbor)
const rootKey = await signTx({cbor, manager})
return cip30.signTx(rootKey, cbor, partial)
},
sendReorganisationTx: async (value?: string) => {
const cbor = await cip30.buildReorganisationTx(value)
if (meta.isHW) {
const signedTx = await options.signTxWithHW(cbor, false)
const signedTx = await options.signTxWithHW({cbor, partial: false, manager})
const base64 = Buffer.from(await signedTx.toBytes()).toString('base64')
await wallet.submitTransaction(base64)
return getTransactionUnspentOutput({
Expand All @@ -135,7 +135,7 @@ export const createDappConnector = (options: CreateDappConnectorOptions) => {
})
}

const rootKey = await signTx(cbor)
const rootKey = await signTx({cbor, manager})
const witnesses = await cip30.signTx(rootKey, cbor, false)
return cip30.sendReorganisationTx(cbor, witnesses)
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@ import {logger} from '../../kernel/logger/logger'
import {useWalletNavigation} from '../../kernel/navigation'
import {cip30LedgerExtensionMaker} from '../../yoroi-wallets/cardano/cip30/cip30-ledger'
import {useReviewTx} from '../ReviewTx/common/ReviewTxProvider'
import {CreatedByInfoItem} from '../ReviewTx/useCases/ReviewTxScreen/ReviewTx/Overview/OverviewTab'
import {useSelectedWallet} from '../WalletManager/common/hooks/useSelectedWallet'
import {useBrowser} from './common/BrowserProvider'
import {useOpenConfirmConnectionModal} from './common/ConfirmConnectionModal'
import {useConfirmHWConnectionModal} from './common/ConfirmHWConnectionModal'
import {isUserRejectedError, userRejectedError} from './common/errors'
Expand All @@ -26,6 +28,10 @@ export const useDappConnectorManager = () => {
const {wallet, meta} = useSelectedWallet()
const {navigateToTxReview} = useWalletNavigation()
const {cborChanged} = useReviewTx()
const {tabs, tabActiveIndex} = useBrowser()
const activeTab = tabs[tabActiveIndex]
const activeTabUrl = activeTab?.url
const activeTabOrigin = activeTabUrl === undefined ? null : new URL(activeTabUrl).origin

const confirmConnection = useConfirmConnection()

Expand All @@ -35,77 +41,85 @@ export const useDappConnectorManager = () => {
const promptRootKey = useConnectorPromptRootKey()
const {sign: signTxWithHW} = useSignTxWithHW()

const handleSignTx = React.useCallback(
({cbor, manager}: {cbor: string; manager: DappConnector}) => {
return new Promise<string>((resolve, reject) => {
let shouldResolve = true
cborChanged(cbor)
return manager.getDAppList().then(({dapps}) => {
const matchingDapp =
activeTabOrigin != null ? dapps.find((dapp) => dapp.origins.includes(activeTabOrigin)) : null
navigateToTxReview({
createdBy: matchingDapp != null && <CreatedByInfoItem logo={matchingDapp.logo} url={matchingDapp.uri} />,
onConfirm: async () => {
if (!shouldResolve) return
shouldResolve = false
const rootKey = await promptRootKey()
resolve(rootKey)
navigateTo.browseDapp()
},
onCancel: () => {
if (!shouldResolve) return
shouldResolve = false
reject(userRejectedError())
},
})
})
})
},
[activeTabOrigin, cborChanged, navigateToTxReview, promptRootKey, navigateTo],
)

const handleSignTxWithHW = React.useCallback(
({cbor, partial, manager}: {cbor: string; partial?: boolean; manager: DappConnector}) => {
return new Promise<Transaction>((resolve, reject) => {
let shouldResolve = true
cborChanged(cbor)
return manager.getDAppList().then(({dapps}) => {
const matchingDapp =
activeTabOrigin != null ? dapps.find((dapp) => dapp.origins.includes(activeTabOrigin)) : null
navigateToTxReview({
createdBy: matchingDapp != null && <CreatedByInfoItem logo={matchingDapp.logo} url={matchingDapp.uri} />,
onConfirm: () => {
if (!shouldResolve) return
shouldResolve = false
signTxWithHW(
{cbor, partial},
{
onSuccess: (signature) => resolve(signature),
onError: (error) => {
logger.error('ReviewTransaction::handleOnConfirm', {error})
reject(error)
},
},
)
navigateTo.browseDapp()
},
onCancel: () => {
if (!shouldResolve) return
shouldResolve = false
reject(userRejectedError())
},
})
})
})
},
[activeTabOrigin, cborChanged, navigateToTxReview, navigateTo, signTxWithHW],
)

return React.useMemo(
() =>
createDappConnector({
appStorage,
wallet,
confirmConnection,
signTx: (cbor) => {
return new Promise<string>((resolve, reject) => {
let shouldResolve = true
cborChanged(cbor)
navigateToTxReview({
onConfirm: async () => {
if (!shouldResolve) return
shouldResolve = false
const rootKey = await promptRootKey()
resolve(rootKey)
navigateTo.browseDapp()
},
onCancel: () => {
if (!shouldResolve) return
shouldResolve = false
reject(userRejectedError())
},
})
})
},
signTx: handleSignTx,
signData,
meta,
signTxWithHW: (cbor, partial) => {
return new Promise<Transaction>((resolve, reject) => {
let shouldResolve = true
cborChanged(cbor)
navigateToTxReview({
onConfirm: () => {
if (!shouldResolve) return
shouldResolve = false
signTxWithHW(
{cbor, partial},
{
onSuccess: (signature) => resolve(signature),
onError: (error) => {
logger.error('ReviewTransaction::handleOnConfirm', {error})
reject(error)
},
},
)
navigateTo.browseDapp()
},
onCancel: () => {
if (!shouldResolve) return
shouldResolve = false
reject(userRejectedError())
},
})
})
},
signTxWithHW: handleSignTxWithHW,
signDataWithHW,
}),
[
appStorage,
wallet,
confirmConnection,
signData,
meta,
signDataWithHW,
cborChanged,
navigateToTxReview,
promptRootKey,
navigateTo,
signTxWithHW,
],
[appStorage, wallet, confirmConnection, handleSignTx, signData, meta, handleSignTxWithHW, signDataWithHW],
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ export const useStrings = () => {
receiveLabel: intl.formatMessage(messages.receiveLabel),
operationsLabel: intl.formatMessage(messages.operationsLabel),
policyIdLabel: intl.formatMessage(messages.policyIdLabel),
createdBy: intl.formatMessage(messages.createdBy),
}
}

Expand Down Expand Up @@ -337,4 +338,8 @@ const messages = defineMessages({
id: 'txReview.policyIdLabel',
defaultMessage: '!!!Policy ID',
},
createdBy: {
id: 'txReview.createdBy',
defaultMessage: '!!!Created by',
},
})
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@ import {CredKind} from '@emurgo/cross-csl-core'
import {Blockies} from '@yoroi/identicon'
import {useTheme} from '@yoroi/theme'
import {Balance} from '@yoroi/types'
import {Image} from 'expo-image'
import * as React from 'react'
import {StyleSheet, Text, TouchableOpacity, useWindowDimensions, View} from 'react-native'
import {Linking, StyleSheet, Text, TouchableOpacity, useWindowDimensions, View} from 'react-native'

import {Divider} from '../../../../../../components/Divider/Divider'
import {Icon} from '../../../../../../components/Icon'
Expand All @@ -27,11 +28,13 @@ export const OverviewTab = ({
extraOperations,
receiverCustomTitle,
details,
createdBy,
}: {
tx: FormattedTx
extraOperations?: Array<React.ReactNode>
receiverCustomTitle?: React.ReactNode
details?: {title: string; component: React.ReactNode}
createdBy?: React.ReactNode
}) => {
const {styles} = useStyles()
const operations = useOperations(tx.certificates)
Expand All @@ -43,7 +46,7 @@ export const OverviewTab = ({
<View style={styles.root}>
<Space height="lg" />

<WalletInfoSection tx={tx} />
<WalletInfoSection tx={tx} createdBy={createdBy} />

<Divider verticalSpace="lg" />

Expand All @@ -68,7 +71,7 @@ export const OverviewTab = ({
)
}

const WalletInfoSection = ({tx}: {tx: FormattedTx}) => {
const WalletInfoSection = ({tx, createdBy}: {tx: FormattedTx; createdBy?: React.ReactNode}) => {
const {styles} = useStyles()
const strings = useStrings()
const {wallet, meta} = useSelectedWallet()
Expand Down Expand Up @@ -104,6 +107,14 @@ const WalletInfoSection = ({tx}: {tx: FormattedTx}) => {

<Space height="sm" />

{createdBy != null && (
<>
{createdBy}

<Space height="sm" />
</>
)}

<FeeInfoItem fee={tx.fee.label} />
</>
)
Expand Down Expand Up @@ -394,6 +405,27 @@ const Details = ({details}: {details?: {title: string; component: React.ReactNod
)
}

export const CreatedByInfoItem = ({logo, url}: {logo?: string; url: string}) => {
const {styles} = useStyles()
const strings = useStrings()

return (
<View style={styles.infoItem}>
<Text style={styles.infoLabel}>{strings.createdBy}</Text>

<View style={styles.plate}>
{logo != null && <Image source={{uri: logo}} style={styles.logo} />}

<Space width="xs" />

<TouchableOpacity onPress={() => Linking.openURL(url)}>
<Text style={styles.link}>{url.replace(/^https?:\/\//, '').replace(/\/+$/, '')}</Text>
</TouchableOpacity>
</View>
</View>
)
}

const useStyles = () => {
const {atoms, color} = useTheme()
const styles = StyleSheet.create({
Expand Down Expand Up @@ -475,6 +507,14 @@ const useStyles = () => {
...atoms.body_2_md_medium,
color: color.text_primary_medium,
},
link: {
color: color.text_primary_medium,
...atoms.body_2_md_medium,
},
logo: {
width: 24,
height: 24,
},
})

const colors = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,5 @@ storiesOf('Review Tx Screen', module)
.add('Multi Asset Tx / Multi Receiver', () => <Component formattedTx={mocks.formattedTxs.multiAssetMultiReceiver} />)

const Component = ({formattedTx}: {formattedTx: FormattedTx}) => {
return (
<ReviewTx
formattedTx={formattedTx}
formattedMetadata={undefined}
operations={undefined}
details={undefined}
receiverCustomTitle={undefined}
onConfirm={action('onConfirm')}
/>
)
return <ReviewTx formattedTx={formattedTx} onConfirm={action('onConfirm')} />
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,13 +32,15 @@ export const ReviewTx = ({
operations,
details,
receiverCustomTitle,
createdBy,
onConfirm,
}: {
formattedTx: FormattedTx
formattedMetadata: FormattedMetadata | undefined
operations: Array<React.ReactNode> | undefined
details: {title: string; component: React.ReactNode} | undefined
receiverCustomTitle: React.ReactNode | undefined
formattedMetadata?: FormattedMetadata
operations?: Array<React.ReactNode>
details?: {title: string; component: React.ReactNode}
receiverCustomTitle?: React.ReactNode
createdBy?: React.ReactNode
onConfirm: () => void
}) => {
const {styles} = useStyles()
Expand Down Expand Up @@ -67,6 +69,7 @@ export const ReviewTx = ({
tx={formattedTx}
extraOperations={operations}
details={details}
createdBy={createdBy}
receiverCustomTitle={receiverCustomTitle}
/>
</ScrollView>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ export const ReviewTxScreen = () => {
operations={params?.operations}
details={params?.details}
receiverCustomTitle={params?.receiverCustomTitle}
createdBy={params?.createdBy}
onConfirm={handleOnConfirm}
/>
)
Expand Down
3 changes: 2 additions & 1 deletion apps/wallet-mobile/src/kernel/i18n/locales/en-US.json
Original file line number Diff line number Diff line change
Expand Up @@ -1283,5 +1283,6 @@
"txReview.receiveLabel": "Receive",
"txReview.operationsLabel": "Operations",
"txReview.policyIdLabel": "Policy ID",
"txReview.tabLabel.referenceInputs": "Reference inputs"
"txReview.tabLabel.referenceInputs": "Reference inputs",
"txReview.createdBy": "Created by"
}
1 change: 1 addition & 0 deletions apps/wallet-mobile/src/kernel/navigation.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -278,6 +278,7 @@ export type ReviewTxRoutes = {
operations?: Array<React.ReactNode>
receiverCustomTitle?: React.ReactNode
details?: {title: string; component: React.ReactNode}
createdBy?: React.ReactNode
onConfirm?: () => void
onCancel?: () => void
onSuccess?: (signedTx: YoroiSignedTx) => void
Expand Down
Loading

0 comments on commit eb750c6

Please sign in to comment.