Skip to content

Commit 92b7970

Browse files
committed
feature(wallet-mobile): new tx review for yoroi governance
2 parents 713e411 + 6fbf24a commit 92b7970

File tree

32 files changed

+699
-702
lines changed

32 files changed

+699
-702
lines changed

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

-1
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/TokenItem.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ export const TokenItem = ({
4848
style={[styles.sentTokenItem, !isPrimaryToken && styles.notPrimarySentTokenItem]}
4949
disabled={isPrimaryToken}
5050
>
51-
<Text style={[styles.tokenSentItemText, !isPrimaryToken && styles.notPrimarySentTokenItemText]}>{label}</Text>
51+
<Text style={[styles.tokenSentItemText, !isPrimaryToken && styles.notPrimarySentTokenItemText]}>-{label}</Text>
5252
</TouchableOpacity>
5353
)
5454
}

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

+6-3
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import {ConfirmTxWithOsModal} from '../../../../components/ConfirmTxWithOsModal/
55
import {ConfirmTxWithSpendingPasswordModal} from '../../../../components/ConfirmTxWithSpendingPasswordModal/ConfirmTxWithSpendingPasswordModal'
66
import {useModal} from '../../../../components/Modal/ModalContext'
77
import {YoroiSignedTx, YoroiUnsignedTx} from '../../../../yoroi-wallets/types/yoroi'
8+
import {useNavigateTo} from '../../../Staking/Governance/common/navigation'
89
import {useSelectedWallet} from '../../../WalletManager/common/hooks/useSelectedWallet'
910
import {useStrings} from './useStrings'
1011

@@ -13,19 +14,18 @@ export const useOnConfirm = ({
1314
unsignedTx,
1415
onSuccess,
1516
onError,
16-
onNotSupportedCIP1694,
1717
}: {
1818
onSuccess?: ((txId: YoroiSignedTx) => void) | null
1919
onError?: (() => void) | null
2020
cbor?: string
2121
unsignedTx?: YoroiUnsignedTx
22-
onNotSupportedCIP1694?: () => void
2322
}) => {
2423
if (unsignedTx === undefined) throw new Error('useOnConfirm: unsignedTx missing')
2524

2625
const {meta} = useSelectedWallet()
2726
const {openModal, closeModal} = useModal()
2827
const strings = useStrings()
28+
const navigateTo = useNavigateTo()
2929

3030
const onConfirm = () => {
3131
if (meta.isHW) {
@@ -35,7 +35,10 @@ export const useOnConfirm = ({
3535
onCancel={closeModal}
3636
unsignedTx={unsignedTx}
3737
onSuccess={(signedTx) => onSuccess?.(signedTx)}
38-
onNotSupportedCIP1694={onNotSupportedCIP1694}
38+
onNotSupportedCIP1694={() => {
39+
closeModal()
40+
navigateTo.notSupportedVersion()
41+
}}
3942
/>,
4043
400,
4144
)

apps/wallet-mobile/src/features/Staking/Governance/GovernanceNavigator.tsx

-7
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ import {useGovernanceManagerMaker} from './common/helpers'
99
import {NavigationStack} from './common/navigation'
1010
import {useStrings} from './common/strings'
1111
import {ChangeVoteScreen} from './useCases/ChangeVote/ChangeVoteScreen'
12-
import {ConfirmTxScreen} from './useCases/ConfirmTx/ConfirmTxScreen'
1312
import {FailedTxScreen} from './useCases/FailedTx/FailedTxScreen'
1413
import {HomeScreen} from './useCases/Home/HomeScreen'
1514
import {NoFundsScreen} from './useCases/NoFunds/NoFundsScreen'
@@ -44,12 +43,6 @@ export const GovernanceNavigator = () => {
4443
options={{title: strings.governanceCentreTitle}}
4544
/>
4645

47-
<Stack.Screen
48-
name="staking-gov-confirm-tx"
49-
component={ConfirmTxScreen}
50-
options={{title: strings.confirmTxTitle}}
51-
/>
52-
5346
<Stack.Screen name="staking-gov-tx-success" component={SuccessTxScreen} options={txStatusOptions} />
5447

5548
<Stack.Screen name="staking-gov-tx-failed" component={FailedTxScreen} options={txStatusOptions} />

apps/wallet-mobile/src/features/Staking/Governance/common/helpers.ts

-60
This file was deleted.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,245 @@
1+
import {useAsyncStorage} from '@yoroi/common'
2+
import {
3+
type StakingKeyState,
4+
governanceApiMaker,
5+
governanceManagerMaker,
6+
GovernanceProvider,
7+
useBech32DRepID,
8+
useGovernance,
9+
useStakingKeyState,
10+
useUpdateLatestGovernanceAction,
11+
} from '@yoroi/staking'
12+
import {useTheme} from '@yoroi/theme'
13+
import * as React from 'react'
14+
import {StyleSheet, Text, View} from 'react-native'
15+
16+
import {Space} from '../../../../components/Space/Space'
17+
import {governaceAfterBlock} from '../../../../kernel/config'
18+
import {useWalletNavigation} from '../../../../kernel/navigation'
19+
import {YoroiWallet} from '../../../../yoroi-wallets/cardano/types'
20+
import {useStakingKey} from '../../../../yoroi-wallets/hooks'
21+
import {YoroiUnsignedTx} from '../../../../yoroi-wallets/types/yoroi'
22+
import {CardanoMobile} from '../../../../yoroi-wallets/wallets'
23+
import {useReviewTx} from '../../../ReviewTx/common/ReviewTxProvider'
24+
import {useBestBlock} from '../../../WalletManager/common/hooks/useBestBlock'
25+
import {useSelectedWallet} from '../../../WalletManager/common/hooks/useSelectedWallet'
26+
import {GovernanceVote} from '../types'
27+
import {useNavigateTo} from './navigation'
28+
import {useStrings} from './strings'
29+
30+
export const useIsParticipatingInGovernance = (wallet: YoroiWallet) => {
31+
const stakingKeyHash = useStakingKey(wallet)
32+
const {data: stakingStatus} = useStakingKeyState(stakingKeyHash, {
33+
suspense: true,
34+
useErrorBoundary: false,
35+
retry: false,
36+
})
37+
return stakingStatus ? mapStakingKeyStateToGovernanceAction(stakingStatus) !== null : false
38+
}
39+
40+
export const mapStakingKeyStateToGovernanceAction = (state: StakingKeyState): GovernanceVote | null => {
41+
if (!state.drepDelegation) return null
42+
const vote = state.drepDelegation
43+
return vote.action === 'abstain'
44+
? {kind: 'abstain'}
45+
: vote.action === 'no-confidence'
46+
? {kind: 'no-confidence'}
47+
: {kind: 'delegate', drepID: vote.drepID}
48+
}
49+
50+
export const useIsGovernanceFeatureEnabled = (wallet: YoroiWallet) => {
51+
const bestBlock = useBestBlock({options: {suspense: true}})
52+
return bestBlock.height >= governaceAfterBlock[wallet.networkManager.network]
53+
}
54+
55+
export const useGovernanceManagerMaker = () => {
56+
const {
57+
wallet: {
58+
networkManager: {network},
59+
id: walletId,
60+
},
61+
} = useSelectedWallet()
62+
63+
const storage = useAsyncStorage()
64+
const governanceStorage = storage.join(`wallet/${walletId}/staking-governance/`)
65+
66+
return React.useMemo(
67+
() =>
68+
governanceManagerMaker({
69+
walletId,
70+
network,
71+
api: governanceApiMaker({network}),
72+
cardano: CardanoMobile,
73+
storage: governanceStorage,
74+
}),
75+
[governanceStorage, network, walletId],
76+
)
77+
}
78+
79+
export const useGovernanceActions = () => {
80+
const {manager} = useGovernance()
81+
const {wallet} = useSelectedWallet()
82+
const navigateTo = useNavigateTo()
83+
const {unsignedTxChanged, onSuccessChanged, onErrorChanged, operationsChanged} = useReviewTx()
84+
const {updateLatestGovernanceAction} = useUpdateLatestGovernanceAction(wallet.id)
85+
const {navigateToTxReview} = useWalletNavigation()
86+
87+
const handleDelegateAction = ({
88+
drepID,
89+
unsignedTx,
90+
hasStakeCert = false,
91+
navigateToStakingOnSuccess = false,
92+
}: {
93+
drepID: string
94+
unsignedTx: YoroiUnsignedTx
95+
hasStakeCert?: boolean
96+
navigateToStakingOnSuccess?: boolean
97+
}) => {
98+
let operations = [
99+
<GovernanceProvider key="0" manager={manager}>
100+
<DelegateOperation drepID={drepID} />
101+
</GovernanceProvider>,
102+
]
103+
104+
if (hasStakeCert) operations = [<RegisterStakingKeyOperation key="-1" />, ...operations]
105+
106+
operationsChanged(operations)
107+
onSuccessChanged((signedTx) => {
108+
updateLatestGovernanceAction({kind: 'delegate-to-drep', drepID, txID: signedTx.signedTx.id})
109+
navigateTo.txSuccess({navigateToStaking: navigateToStakingOnSuccess ?? false, kind: 'delegate'})
110+
})
111+
onErrorChanged(() => navigateTo.txFailed())
112+
unsignedTxChanged(unsignedTx)
113+
114+
navigateToTxReview()
115+
}
116+
117+
const handleAbstainAction = ({
118+
unsignedTx,
119+
hasStakeCert = false,
120+
navigateToStakingOnSuccess = false,
121+
}: {
122+
unsignedTx: YoroiUnsignedTx
123+
hasStakeCert?: boolean
124+
navigateToStakingOnSuccess?: boolean
125+
}) => {
126+
let operations = [<AbstainOperation key="0" />]
127+
if (hasStakeCert) operations = [<RegisterStakingKeyOperation key="-1" />, ...operations]
128+
129+
operationsChanged(operations)
130+
onSuccessChanged((signedTx) => {
131+
updateLatestGovernanceAction({kind: 'vote', vote: 'abstain', txID: signedTx.signedTx.id})
132+
navigateTo.txSuccess({navigateToStaking: navigateToStakingOnSuccess ?? false, kind: 'abstain'})
133+
})
134+
onErrorChanged(() => navigateTo.txFailed())
135+
unsignedTxChanged(unsignedTx)
136+
137+
navigateToTxReview()
138+
}
139+
140+
const handleNoConfidenceAction = ({
141+
unsignedTx,
142+
hasStakeCert = false,
143+
navigateToStakingOnSuccess = false,
144+
}: {
145+
unsignedTx: YoroiUnsignedTx
146+
hasStakeCert?: boolean
147+
navigateToStakingOnSuccess?: boolean
148+
}) => {
149+
let operations = [<NoConfidenceOperation key="0" />]
150+
if (hasStakeCert) operations = [<RegisterStakingKeyOperation key="-1" />, ...operations]
151+
152+
operationsChanged(operations)
153+
onSuccessChanged((signedTx) => {
154+
updateLatestGovernanceAction({kind: 'vote', vote: 'no-confidence', txID: signedTx.signedTx.id})
155+
navigateTo.txSuccess({
156+
navigateToStaking: navigateToStakingOnSuccess ?? false,
157+
kind: 'no-confidence',
158+
})
159+
})
160+
onErrorChanged(() => navigateTo.txFailed())
161+
unsignedTxChanged(unsignedTx)
162+
163+
navigateToTxReview()
164+
}
165+
166+
return {handleDelegateAction, handleAbstainAction, handleNoConfidenceAction} as const
167+
}
168+
169+
const RegisterStakingKeyOperation = () => {
170+
const {styles} = useStyles()
171+
const strings = useStrings()
172+
173+
return (
174+
<View style={styles.operation}>
175+
<Text style={styles.operationLabel}>{strings.registerStakingKey}</Text>
176+
177+
<Space width="lg" />
178+
179+
<Text style={styles.operationValue}>2 ADA</Text>
180+
</View>
181+
)
182+
}
183+
184+
const AbstainOperation = () => {
185+
const {styles} = useStyles()
186+
const strings = useStrings()
187+
188+
return (
189+
<View style={styles.operation}>
190+
<Text style={styles.operationLabel}>{strings.selectAbstain}</Text>
191+
</View>
192+
)
193+
}
194+
195+
const NoConfidenceOperation = () => {
196+
const {styles} = useStyles()
197+
const strings = useStrings()
198+
199+
return (
200+
<View style={styles.operation}>
201+
<Text style={styles.operationLabel}>{strings.selectNoConfidence}</Text>
202+
</View>
203+
)
204+
}
205+
206+
const DelegateOperation = ({drepID}: {drepID: string}) => {
207+
const {styles} = useStyles()
208+
const strings = useStrings()
209+
210+
const {data: bech32DrepId} = useBech32DRepID(drepID)
211+
212+
return (
213+
<View style={styles.operation}>
214+
<Text style={styles.operationLabel}>{strings.delegateVotingToDRep}</Text>
215+
216+
<Space width="lg" />
217+
218+
<Text style={styles.operationValue}>{bech32DrepId ?? drepID}</Text>
219+
</View>
220+
)
221+
}
222+
223+
const useStyles = () => {
224+
const {color, atoms} = useTheme()
225+
226+
const styles = StyleSheet.create({
227+
operation: {
228+
...atoms.flex_row,
229+
...atoms.justify_between,
230+
...atoms.align_start,
231+
},
232+
operationLabel: {
233+
...atoms.body_2_md_regular,
234+
color: color.text_gray_low,
235+
},
236+
operationValue: {
237+
...atoms.flex_1,
238+
...atoms.text_right,
239+
...atoms.body_2_md_regular,
240+
color: color.text_gray_medium,
241+
},
242+
})
243+
244+
return {styles} as const
245+
}

0 commit comments

Comments
 (0)