Skip to content

Commit

Permalink
feat(swap): Allow cancellation of an open order (#2683)
Browse files Browse the repository at this point in the history
  • Loading branch information
stackchain authored Sep 29, 2023
2 parents c26f570 + 4aad1c2 commit 27fb1e4
Show file tree
Hide file tree
Showing 40 changed files with 1,731 additions and 647 deletions.
1 change: 1 addition & 0 deletions apps/wallet-mobile/.storybook/storybook.requires.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 0 additions & 1 deletion apps/wallet-mobile/__mocks__/@emurgo/csl-mobile-bridge.js

This file was deleted.

10 changes: 5 additions & 5 deletions apps/wallet-mobile/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -105,12 +105,12 @@
"@cardano-foundation/ledgerjs-hw-app-cardano": "^6.0.0",
"@emurgo/cip14-js": "^3.0.1",
"@emurgo/cip4-js": "1.0.7",
"@emurgo/cross-csl-core": "^2.9.0",
"@emurgo/cross-csl-mobile": "^2.9.0",
"@emurgo/cross-csl-core": "^3.1.0",
"@emurgo/cross-csl-mobile": "^3.1.0",
"@emurgo/csl-mobile-bridge": "^5.1.2",
"@emurgo/react-native-blockies-svg": "^0.0.2",
"@emurgo/react-native-hid": "^5.15.6",
"@emurgo/yoroi-lib": "^0.8.1",
"@emurgo/yoroi-lib": "^0.9.0",
"@formatjs/intl-datetimeformat": "^6.7.0",
"@formatjs/intl-getcanonicallocales": "^2.1.0",
"@formatjs/intl-locale": "^3.2.1",
Expand Down Expand Up @@ -139,7 +139,7 @@
"add": "2.0.6",
"assert": "^2.0.0",
"base-64": "^1.0.0",
"bignumber.js": "^9.0.0",
"bignumber.js": "^9.0.1",
"bip39": "2.5.0",
"blake2b": "2.1.3",
"bs58": "^4.0.1",
Expand Down Expand Up @@ -212,7 +212,7 @@
"@babel/runtime": "^7.20.0",
"@config-plugins/detox": "^5.0.1",
"@emurgo/cardano-serialization-lib-nodejs": "^9.1.4",
"@emurgo/cross-csl-nodejs": "^2.7.0",
"@emurgo/cross-csl-nodejs": "^3.1.0",
"@formatjs/cli": "^6.1.0",
"@formatjs/ts-transformer": "^3.13.0",
"@react-navigation/devtools": "^6.0.13",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ export const BottomSheetModal = ({
<BottomSheet
ref={bottomSheetRef}
index={isOpen ? 1 : 0}
contentHeight={900}
snapPoints={snapPoints}
backdropComponent={(props) =>
showBackdropComp === false ? (
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import {Resolution} from '@unstoppabledomains/resolution'
import _ from 'lodash'
import React from 'react'
import {Text, View, ViewProps} from 'react-native'
import {useQuery, UseQueryOptions} from 'react-query'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,8 +94,8 @@ export const ManageCollateralScreen = () => {
return
}

const primaryTokenBalance = BigNumber(Amounts.getAmount(balances, wallet.primaryToken.identifier).quantity)
const lockedBalance = Quantities.isZero(lockedAmount) ? BigNumber(0) : BigNumber(lockedAmount)
const primaryTokenBalance = new BigNumber(Amounts.getAmount(balances, wallet.primaryToken.identifier).quantity)
const lockedBalance = Quantities.isZero(lockedAmount) ? new BigNumber(0) : new BigNumber(lockedAmount)

if (primaryTokenBalance.minus(lockedBalance).isLessThan(collateralConfig.minLovelace)) {
Alert.alert(
Expand Down
1 change: 1 addition & 0 deletions apps/wallet-mobile/src/features/Swap/SwapNavigator.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {ListOrders} from './useCases/StartSwapScreen/ListOrders/ListOrders'
const Tab = createMaterialTopTabNavigator<SwapTabRoutes>()
export const SwapTabNavigator = () => {
const strings = useStrings()

useHideBottomTabBar()

return (
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import {storiesOf} from '@storybook/react-native'
import React from 'react'
import {StyleSheet, View} from 'react-native'

import {ConfirmWithSpendingPassword} from './ConfirmWithSpendingPassword'

storiesOf('ConfirmWithSpendingPassword', module)
.addDecorator((story) => <View style={styles.container}>{story()}</View>)
.add('Initial', () => <ConfirmWithSpendingPassword />)
.add('Loading', () => <ConfirmWithSpendingPassword isLoading />)
.add('Error', () => <ConfirmWithSpendingPassword error={new Error('Example error')} />)

const styles = StyleSheet.create({
container: {
flex: 1,
padding: 16,
},
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import React from 'react'
import {ActivityIndicator, StyleSheet, TextInput as RNTextInput, View} from 'react-native'

import {Button, Spacer, Text, TextInput} from '../../../../components'
import {debugWalletInfo, features} from '../../../../features'
import {COLORS} from '../../../../theme'
import {WrongPassword} from '../../../../yoroi-wallets/cardano/errors'
import {useStrings} from '../../common/strings'

type Props = {
onSubmit?: (spendingPassword: string) => void
isLoading?: boolean
error?: Error
}

export const ConfirmWithSpendingPassword = ({onSubmit, isLoading, error}: Props) => {
const spendingPasswordRef = React.useRef<RNTextInput>(null)
const [spendingPassword, setSpendingPassword] = React.useState(
features.prefillWalletInfo ? debugWalletInfo.PASSWORD : '',
)
const strings = useStrings()

return (
<>
<Text style={styles.modalText}>{strings.enterSpendingPassword}</Text>

<TextInput
secureTextEntry
ref={spendingPasswordRef}
enablesReturnKeyAutomatically
placeholder={strings.spendingPassword}
value={spendingPassword}
onChangeText={setSpendingPassword}
autoComplete="off"
/>

{error != null && (
<View>
<Text style={styles.errorMessage} numberOfLines={3}>
{getErrorMessage(error, strings)}
</Text>
</View>
)}

<Spacer fill />

<Button testID="swapButton" shelleyTheme title={strings.sign} onPress={() => onSubmit?.(spendingPassword)} />

{isLoading && (
<View style={styles.loading}>
<ActivityIndicator size="large" color="black" />
</View>
)}
</>
)
}

const getErrorMessage = (error: unknown, strings: Record<'wrongPasswordMessage' | 'error', string>) => {
if (error instanceof WrongPassword) {
return strings.wrongPasswordMessage
}
if (error instanceof Error) {
return error.message
}

return strings.error
}

const styles = StyleSheet.create({
modalText: {
paddingHorizontal: 70,
textAlign: 'center',
paddingBottom: 8,
},
errorMessage: {
color: COLORS.ERROR_TEXT_COLOR,
},
loading: {
position: 'absolute',
height: '100%',
width: '100%',
alignItems: 'center',
justifyContent: 'center',
},
})
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export {ConfirmWithSpendingPassword} from './ConfirmWithSpendingPassword'
20 changes: 20 additions & 0 deletions apps/wallet-mobile/src/features/Swap/common/strings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,10 @@ export const useStrings = () => {
transactionDisplay: intl.formatMessage(messages.transactionDisplay),
seeOnExplorer: intl.formatMessage(messages.seeOnExplorer),
goToOrders: intl.formatMessage(messages.goToOrders),
wrongPasswordMessage: intl.formatMessage(messages.wrongPasswordMessage),
assignCollateral: intl.formatMessage(messages.assignCollateral),
collateralNotFound: intl.formatMessage(messages.collateralNotFound),
noActiveCollateral: intl.formatMessage(messages.noActiveCollateral),
failedTxTitle: intl.formatMessage(messages.failedTxTitle),
failedTxText: intl.formatMessage(messages.failedTxText),
failedTxButton: intl.formatMessage(messages.failedTxButton),
Expand All @@ -110,6 +114,10 @@ export const useStrings = () => {
export const amountInputErrorMessages = defineMessages({})

export const messages = defineMessages({
wrongPasswordMessage: {
id: 'global.actions.dialogs.incorrectPassword.title',
defaultMessage: '!!!Incorrect password.',
},
swapTitle: {
id: 'swap.swapScreen.swapTitle',
defaultMessage: '!!!Swap',
Expand Down Expand Up @@ -462,6 +470,18 @@ export const messages = defineMessages({
id: 'components.send.confirmscreen.confirmButton',
defaultMessage: '!!!Confirm',
},
assignCollateral: {
id: 'components.send.confirmscreen.assignCollateral',
defaultMessage: '!!!Assign collateral',
},
collateralNotFound: {
id: 'components.send.confirmscreen.collateralNotFound',
defaultMessage: '!!!Collateral not found',
},
noActiveCollateral: {
id: 'components.send.confirmscreen.noActiveCollateral',
defaultMessage: "!!!You don't have an active collateral utxo",
},
failedTxTitle: {
id: 'components.send.sendscreen.failedTxTitle',
defaultMessage: '!!!Transaction failed',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,11 @@ import {SafeAreaView} from 'react-native-safe-area-context'

import {BottomSheet, BottomSheetRef, Button, Spacer} from '../../../../components'
import {LoadingOverlay} from '../../../../components/LoadingOverlay'
import {useMetrics} from '../../../../metrics/metricsManager'
import {useSelectedWallet} from '../../../../SelectedWallet'
import {COLORS} from '../../../../theme'
import {useAuthOsWithEasyConfirmation} from '../../../../yoroi-wallets/auth'
import {useSignAndSubmitTx} from '../../../../yoroi-wallets/hooks'
import {useSignAndSubmitTx, useTokenInfo} from '../../../../yoroi-wallets/hooks'
import {useNavigateTo} from '../../common/navigation'
import {useStrings} from '../../common/strings'
import {ConfirmTx} from './ConfirmTx'
Expand All @@ -27,8 +28,17 @@ export const ConfirmTxScreen = () => {
const strings = useStrings()
const wallet = useSelectedWallet()
const navigate = useNavigateTo()
const {track} = useMetrics()

const {unsignedTx} = useSwap()
const {unsignedTx, createOrder} = useSwap()
const sellTokenInfo = useTokenInfo({
wallet,
tokenId: createOrder.amounts.sell.tokenId,
})
const buyTokenInfo = useTokenInfo({
wallet,
tokenId: createOrder.amounts.buy.tokenId,
})

const {authWithOs, isLoading: authenticating} = useAuthOsWithEasyConfirmation(
{id: wallet.id},
Expand All @@ -41,6 +51,22 @@ export const ConfirmTxScreen = () => {
signTx: {useErrorBoundary: true},
submitTx: {
onSuccess: () => {
if (!createOrder.selectedPool) return
track.swapOrderSubmitted({
from_asset: [
{asset_name: sellTokenInfo.name, asset_ticker: sellTokenInfo.ticker, policy_id: sellTokenInfo.group},
],
to_asset: [
{asset_name: buyTokenInfo.name, asset_ticker: buyTokenInfo.ticker, policy_id: buyTokenInfo.group},
],
order_type: createOrder.type,
slippage_tolerance: createOrder.slippage,
from_amount: createOrder.amounts.sell.quantity,
to_amount: createOrder.amounts.buy.quantity,
pool_source: createOrder.selectedPool.provider,
swap_fees: Number(createOrder.selectedPool.fee),
})

navigate.submittedTx()
},
onError: () => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,10 @@
import React from 'react'
import {ActivityIndicator, StyleSheet, TextInput as RNTextInput, View} from 'react-native'

import {Button, Spacer, Text, TextInput} from '../../../../components'
import {debugWalletInfo, features} from '../../../../features'
import {COLORS} from '../../../../theme'
import {isEmptyString} from '../../../../utils'
import {WrongPassword} from '../../../../yoroi-wallets/cardano/errors'
import {YoroiWallet} from '../../../../yoroi-wallets/cardano/types'
import {useSignWithPasswordAndSubmitTx} from '../../../../yoroi-wallets/hooks'
import {YoroiUnsignedTx} from '../../../../yoroi-wallets/types'
import {ConfirmWithSpendingPassword} from '../../common/ConfirmWithSpendingPassword'
import {useStrings} from '../../common/strings'

type ErrorData = {
Expand All @@ -24,20 +20,12 @@ type Props = {
}

export const ConfirmTxWithPassword = ({wallet, onSuccess, unsignedTx}: Props) => {
const spendingPasswordRef = React.useRef<RNTextInput>(null)
const [spendingPassword, setSpendingPassword] = React.useState(
features.prefillWalletInfo ? debugWalletInfo.PASSWORD : '',
)
const [errorData, setErrorData] = React.useState<ErrorData>({
errorMessage: '',
errorLogs: '',
})
const strings = useStrings()

React.useEffect(() => {
setErrorData({errorMessage: '', errorLogs: ''})
}, [spendingPassword])

const {signAndSubmitTx, isLoading} = useSignWithPasswordAndSubmitTx(
{wallet}, //
{submitTx: {onSuccess}},
Expand All @@ -50,11 +38,11 @@ export const ConfirmTxWithPassword = ({wallet, onSuccess, unsignedTx}: Props) =>
})
}

const onConfirm = async () => {
const onConfirm = async (password: string) => {
try {
const rootKey = await wallet.encryptedStorage.rootKey.read(spendingPassword)
const rootKey = await wallet.encryptedStorage.rootKey.read(password)
if (rootKey !== undefined) {
signAndSubmitTx({unsignedTx, password: spendingPassword})
signAndSubmitTx({unsignedTx, password})
}
} catch (err) {
if (err instanceof WrongPassword) {
Expand All @@ -71,58 +59,11 @@ export const ConfirmTxWithPassword = ({wallet, onSuccess, unsignedTx}: Props) =>
}
}

const isConfirmationDisabled = !wallet.isEasyConfirmationEnabled && isEmptyString(spendingPassword) && !wallet.isHW

return (
<>
<Text style={styles.modalText}>{strings.enterSpendingPassword}</Text>

<TextInput
secureTextEntry
ref={spendingPasswordRef}
enablesReturnKeyAutomatically
placeholder={strings.spendingPassword}
value={spendingPassword}
onChangeText={setSpendingPassword}
autoComplete="off"
/>

<Text style={styles.error}>{errorData.errorMessage}</Text>

<Spacer fill />

<Button
testID="swapButton"
shelleyTheme
title={strings.sign}
onPress={onConfirm}
disabled={isConfirmationDisabled || isLoading}
/>

{isLoading && (
<View style={styles.loading}>
<ActivityIndicator size="large" color="black" />
</View>
)}
</>
<ConfirmWithSpendingPassword
onSubmit={onConfirm}
isLoading={isLoading}
error={errorData.errorMessage.length > 0 ? new Error(errorData.errorMessage) : undefined}
/>
)
}

const styles = StyleSheet.create({
modalText: {
paddingHorizontal: 70,
textAlign: 'center',
paddingBottom: 8,
},
loading: {
position: 'absolute',
height: '100%',
width: '100%',
alignItems: 'center',
justifyContent: 'center',
},
error: {
color: COLORS.ERROR_TEXT_COLOR,
textAlign: 'center',
},
})
Loading

0 comments on commit 27fb1e4

Please sign in to comment.