From 55589689a8e8ff57b46b688153c21c61a7e8b10c Mon Sep 17 00:00:00 2001 From: yushi Date: Fri, 24 Jan 2025 03:10:28 +0800 Subject: [PATCH 01/32] remove unused pre-revamp code --- .../wallet/send/HWSendConfirmationDialog.js | 240 -------------- .../wallet/send/HWSendConfirmationDialog.scss | 19 -- .../send/WalletSendConfirmationDialog.js | 298 ------------------ .../send/WalletSendConfirmationDialog.scss | 218 ------------- .../app/containers/wallet/WalletSendPage.js | 187 +---------- .../WalletSendConfirmationDialogContainer.js | 93 ------ 6 files changed, 1 insertion(+), 1054 deletions(-) delete mode 100644 packages/yoroi-extension/app/components/wallet/send/HWSendConfirmationDialog.js delete mode 100644 packages/yoroi-extension/app/components/wallet/send/HWSendConfirmationDialog.scss delete mode 100644 packages/yoroi-extension/app/components/wallet/send/WalletSendConfirmationDialog.js delete mode 100644 packages/yoroi-extension/app/components/wallet/send/WalletSendConfirmationDialog.scss delete mode 100644 packages/yoroi-extension/app/containers/wallet/dialogs/WalletSendConfirmationDialogContainer.js diff --git a/packages/yoroi-extension/app/components/wallet/send/HWSendConfirmationDialog.js b/packages/yoroi-extension/app/components/wallet/send/HWSendConfirmationDialog.js deleted file mode 100644 index 1e6f49f842..0000000000 --- a/packages/yoroi-extension/app/components/wallet/send/HWSendConfirmationDialog.js +++ /dev/null @@ -1,240 +0,0 @@ -// @flow -import type { Node } from 'react'; -import React, { Component, } from 'react'; -import { observer } from 'mobx-react'; -import classnames from 'classnames'; -import { intlShape } from 'react-intl'; -import type { MessageDescriptor, $npm$ReactIntl$IntlFormat } from 'react-intl'; - -import Dialog from '../../widgets/Dialog'; -import DialogCloseButton from '../../widgets/DialogCloseButton'; -import ErrorBlock from '../../widgets/ErrorBlock'; -import WarningBox from '../../widgets/WarningBox'; - -import globalMessages from '../../../i18n/global-messages'; -import LocalizableError from '../../../i18n/LocalizableError'; - -import ExplorableHashContainer from '../../../containers/widgets/ExplorableHashContainer'; -import RawHash from '../../widgets/hashWrappers/RawHash'; - -import { SelectedExplorer } from '../../../domain/SelectedExplorer'; -import type { UnitOfAccountSettingType } from '../../../types/unitOfAccountType'; -import styles from './HWSendConfirmationDialog.scss'; -import { truncateAddress, truncateToken } from '../../../utils/formatters'; -import { - MultiToken, -} from '../../../api/common/lib/MultiToken'; -import type { - TokenLookupKey, TokenEntry, -} from '../../../api/common/lib/MultiToken'; -import { getTokenName, genFormatTokenAmount, } from '../../../stores/stateless/tokenHelpers'; -import type { TokenRow } from '../../../api/ada/lib/storage/database/primitives/tables'; - -type ExpectedMessages = {| - infoLine1: MessageDescriptor, - infoLine2: MessageDescriptor, - sendUsingHWButtonLabel: MessageDescriptor, -|}; - -type Props = {| - +staleTx: boolean, - +selectedExplorer: SelectedExplorer, - +amount: MultiToken, - +receivers: Array, - +totalAmount: MultiToken, - +transactionFee: MultiToken, - +messages: ExpectedMessages, - +isSubmitting: boolean, - +error: ?LocalizableError, - +onSubmit: void => PossiblyAsync, - +onCancel: void => void, - +getTokenInfo: $ReadOnly> => $ReadOnly, - +unitOfAccountSetting: UnitOfAccountSettingType, - +addressToDisplayString: string => string, - +getCurrentPrice: (from: string, to: string) => ?string, -|}; - -@observer -export default class HWSendConfirmationDialog extends Component { - - static contextTypes: {|intl: $npm$ReactIntl$IntlFormat|} = { - intl: intlShape.isRequired, - }; - - renderSingleAmount: TokenEntry => Node = (entry) => { - const formatValue = genFormatTokenAmount(this.props.getTokenInfo); - - return ( -
{formatValue(entry)} -  { - truncateToken(getTokenName(this.props.getTokenInfo(entry))) - } - -
- ); - } - renderTotalAmount: TokenEntry => Node = (entry) => { - const formatValue = genFormatTokenAmount(this.props.getTokenInfo); - - return ( -
{formatValue(entry)} -  { - truncateToken(getTokenName(this.props.getTokenInfo(entry))) - } - -
- ); - } - renderSingleFee: TokenEntry => Node = (entry) => { - const formatValue = genFormatTokenAmount(this.props.getTokenInfo); - - return ( -
- +{formatValue(entry)} -  { - truncateToken(getTokenName(this.props.getTokenInfo( - entry - ))) - } - -
- ); - } - - renderBundle: {| - amount: MultiToken, - render: TokenEntry => Node, - |} => Node = (request) => { - return ( - <> - {request.render(request.amount.getDefaultEntry())} - {request.amount.nonDefaultEntries().map(entry => ( - - {request.render(entry)} - - ))} - - ); - } - - render(): Node { - const { intl } = this.context; - const { - amount, - receivers, - isSubmitting, - messages, - error, - onCancel, - } = this.props; - - const staleTxWarning = ( -
- - {intl.formatMessage(globalMessages.staleTxnWarningLine1)}
- {intl.formatMessage(globalMessages.staleTxnWarningLine2)} -
-
- ); - - const infoBlock = ( -
-
    -
  • {intl.formatMessage(messages.infoLine1)}
  • -
  • {intl.formatMessage(messages.infoLine2)}
  • -
-
); - - const addressBlock = ( -
-
- {intl.formatMessage(globalMessages.walletSendConfirmationAddressToLabel)} -
- {receivers.map((receiver, i) => ( - - - - {truncateAddress(this.props.addressToDisplayString(receiver))} - - - - ))} -
); - - const amountBlock = ( -
-
-
- {intl.formatMessage(globalMessages.amountLabel)} -
- {this.renderBundle({ - amount, - render: this.renderSingleAmount, - })} -
- -
-
- {intl.formatMessage(globalMessages.walletSendConfirmationFeesLabel)} -
- {this.renderBundle({ - amount: this.props.transactionFee, - render: this.renderSingleFee, - })} -
-
); - - const totalAmountBlock = ( -
-
- {intl.formatMessage(globalMessages.walletSendConfirmationTotalLabel)} -
- {this.renderBundle({ - amount: this.props.totalAmount, - render: this.renderTotalAmount, - })} -
); - - const confirmButtonClasses = classnames([ - 'confirmButton', - isSubmitting ? styles.submitButtonSpinning : null, - ]); - const actions = [ - { - label: intl.formatMessage(globalMessages.backButtonLabel), - disabled: isSubmitting, - onClick: onCancel, - }, - { - label: intl.formatMessage(messages.sendUsingHWButtonLabel), - onClick: this.props.onSubmit, - primary: true, - className: confirmButtonClasses, - isSubmitting, - }, - ]; - - return ( - } - > - {this.props.staleTx && staleTxWarning} - {infoBlock} - {addressBlock} - {amountBlock} - {totalAmountBlock} - - ); - } -} diff --git a/packages/yoroi-extension/app/components/wallet/send/HWSendConfirmationDialog.scss b/packages/yoroi-extension/app/components/wallet/send/HWSendConfirmationDialog.scss deleted file mode 100644 index d03d9b623d..0000000000 --- a/packages/yoroi-extension/app/components/wallet/send/HWSendConfirmationDialog.scss +++ /dev/null @@ -1,19 +0,0 @@ -@import './WalletSendConfirmationDialog.scss'; // Reusing this as it's almost same - -.dialog { - .infoBlock { - background-color: var(--yoroi-palette-gray-50); - font-weight: 400; - padding: 10px; - margin-bottom: 30px; - line-height: 1.38; - font-size: 14px; - opacity: 0.7; - letter-spacing: 0.5px; - border-radius: 10px; - li { - list-style-type: disc; - margin-left: 18px; - } - } -} diff --git a/packages/yoroi-extension/app/components/wallet/send/WalletSendConfirmationDialog.js b/packages/yoroi-extension/app/components/wallet/send/WalletSendConfirmationDialog.js deleted file mode 100644 index d39cdd9a45..0000000000 --- a/packages/yoroi-extension/app/components/wallet/send/WalletSendConfirmationDialog.js +++ /dev/null @@ -1,298 +0,0 @@ -// @flow - -/* eslint react/jsx-one-expression-per-line: 0 */ // the   in the html breaks this - -import type { Node } from 'react'; -import React, { Component, } from 'react'; -import { observer } from 'mobx-react'; -import classnames from 'classnames'; -import TextField from '../../common/TextField'; -import { intlShape } from 'react-intl'; -import ReactToolboxMobxForm from '../../../utils/ReactToolboxMobxForm'; -import vjf from 'mobx-react-form/lib/validators/VJF'; -import Dialog from '../../widgets/Dialog'; -import DialogCloseButton from '../../widgets/DialogCloseButton'; -import globalMessages from '../../../i18n/global-messages'; -import LocalizableError from '../../../i18n/LocalizableError'; -import styles from './WalletSendConfirmationDialog.scss'; -import config from '../../../config'; -import ExplorableHashContainer from '../../../containers/widgets/ExplorableHashContainer'; -import RawHash from '../../widgets/hashWrappers/RawHash'; -import { SelectedExplorer } from '../../../domain/SelectedExplorer'; -import type { UnitOfAccountSettingType } from '../../../types/unitOfAccountType'; -import WarningBox from '../../widgets/WarningBox'; -import type { $npm$ReactIntl$IntlFormat } from 'react-intl'; -import { - truncateAddress, truncateToken, -} from '../../../utils/formatters'; -import { - MultiToken, -} from '../../../api/common/lib/MultiToken'; -import type { - TokenLookupKey, TokenEntry, -} from '../../../api/common/lib/MultiToken'; -import type { TokenRow } from '../../../api/ada/lib/storage/database/primitives/tables'; -import { getTokenName, genFormatTokenAmount } from '../../../stores/stateless/tokenHelpers'; -import { Box } from '@mui/system'; - -type Props = {| - +staleTx: boolean, - +selectedExplorer: SelectedExplorer, - +amount: MultiToken, - +receivers: Array, - +totalAmount: MultiToken, - +transactionFee: MultiToken, - +transactionSize: ?string, - +onSubmit: ({| password: string |}) => PossiblyAsync, - +addressToDisplayString: string => string, - +onCancel: void => void, - +isSubmitting: boolean, - +error: ?LocalizableError, - +unitOfAccountSetting: UnitOfAccountSettingType, - +getTokenInfo: $ReadOnly> => $ReadOnly, - +getCurrentPrice: (from: string, to: string) => ?string, -|}; - -@observer -export default class WalletSendConfirmationDialog extends Component { - - static contextTypes: {|intl: $npm$ReactIntl$IntlFormat|} = { - intl: intlShape.isRequired, - }; - - form: ReactToolboxMobxForm = new ReactToolboxMobxForm({ - fields: { - walletPassword: { - type: 'password', - label: this.context.intl.formatMessage(globalMessages.walletPasswordLabel), - placeholder: '', - value: '', - validators: [({ field }) => { - if (field.value === '') { - return [false, this.context.intl.formatMessage(globalMessages.fieldIsRequired)]; - } - return [true]; - }], - }, - } - }, { - options: { - validateOnChange: true, - validationDebounceWait: config.forms.FORM_VALIDATION_DEBOUNCE_WAIT, - }, - plugins: { - vjf: vjf() - }, - }); - - submit(): void { - this.form.submit({ - onSuccess: async (form) => { - const { walletPassword } = form.values(); - const transactionData = { - password: walletPassword, - }; - await this.props.onSubmit(transactionData); - }, - onError: () => {} - }); - } - - renderSingleAmount: TokenEntry => Node = (entry) => { - const formatValue = genFormatTokenAmount(this.props.getTokenInfo); - - return ( -
{formatValue(entry)} -  { - truncateToken(getTokenName(this.props.getTokenInfo(entry))) - } - -
- ); - } - renderTotalAmount: TokenEntry => Node = (entry) => { - const formatValue = genFormatTokenAmount(this.props.getTokenInfo); - - return ( -
{formatValue(entry)} -  { - truncateToken(getTokenName(this.props.getTokenInfo(entry))) - } - -
- ); - } - renderSingleFee: TokenEntry => Node = (entry) => { - const formatValue = genFormatTokenAmount(this.props.getTokenInfo); - - return ( -
- +{formatValue(entry)} -  { - truncateToken(getTokenName(this.props.getTokenInfo( - entry - ))) - } - -
- ); - } - - renderBundle: {| - amount: MultiToken, - render: TokenEntry => Node, - |} => Node = (request) => { - return ( - <> - {request.render(request.amount.getDefaultEntry())} - {request.amount.nonDefaultEntries().map(entry => ( - - {request.render(entry)} - - ))} - - ); - } - - render(): Node { - const { form } = this; - const { intl } = this.context; - const walletPasswordField = form.$('walletPassword'); - const { - onCancel, - amount, - receivers, - isSubmitting, - error, - } = this.props; - - const staleTxWarning = ( -
- - {intl.formatMessage(globalMessages.staleTxnWarningLine1)}
- {intl.formatMessage(globalMessages.staleTxnWarningLine2)} -
-
- ); - - const confirmButtonClasses = classnames([ - 'confirmButton', - isSubmitting ? styles.submitButtonSpinning : null, - ]); - - const actions = [ - { - label: intl.formatMessage(globalMessages.backButtonLabel), - disabled: isSubmitting, - onClick: onCancel, - }, - { - label: intl.formatMessage(globalMessages.sendButtonLabel), - onClick: this.submit.bind(this), - primary: true, - className: confirmButtonClasses, - isSubmitting, - disabled: !walletPasswordField.isValid, - }, - ]; - - return ( - } - > - {this.props.staleTx && staleTxWarning} - -
-
-
- {intl.formatMessage(globalMessages.walletSendConfirmationAddressToLabel)} -
- {[...new Set(receivers)].map((receiver, i) => ( - - - - - {truncateAddress(this.props.addressToDisplayString(receiver))} - - - - - ))} -
- - {this.props.transactionSize != null ? ( -
-
- {intl.formatMessage(globalMessages.walletSendConfirmationTxSizeLabel)} -
- - {this.props.transactionSize} - -
- ) : null} - -
-
-
- {intl.formatMessage(globalMessages.amountLabel)} -
- {this.renderBundle({ - amount, - render: this.renderSingleAmount, - })} -
- -
-
- {intl.formatMessage(globalMessages.walletSendConfirmationFeesLabel)} -
- {this.renderBundle({ - amount: this.props.transactionFee, - render: this.renderSingleFee, - })} -
-
- -
-
- {intl.formatMessage(globalMessages.walletSendConfirmationTotalLabel)} -
- {this.renderBundle({ - amount: this.props.totalAmount, - render: this.renderTotalAmount, - })} -
- - -
- - {error - ? ( -
- {intl.formatMessage(error, error.values)} -
- ) - : null - } - -
- ); - } -} diff --git a/packages/yoroi-extension/app/components/wallet/send/WalletSendConfirmationDialog.scss b/packages/yoroi-extension/app/components/wallet/send/WalletSendConfirmationDialog.scss deleted file mode 100644 index 59d27bc84d..0000000000 --- a/packages/yoroi-extension/app/components/wallet/send/WalletSendConfirmationDialog.scss +++ /dev/null @@ -1,218 +0,0 @@ -@import '../../../themes/mixins/loading-spinner'; -@import '../../../themes/mixins/error-message'; - -.dialog { - font-weight: 500; - - :global { - .Dialog_title { - margin-bottom: 24px; - } - } - - .warningBox { - margin-bottom: 24px; - } - - .submitButtonSpinning { - @include loading-spinner('../../../assets/images/spinner-light.svg'); - } - - .currencySymbol { - font-weight: 300; - text-transform: uppercase; - } - - .addressToLabelWrapper, - .txSizeWrapper, - .amountFeesWrapper { - margin-bottom: 20px; - } - - .addressToLabel { - font-size: 11px; - font-weight: 500; - letter-spacing: 0.5px; - line-height: 1.38; - margin-left: 10px; - text-transform: uppercase; - } - - .addressTo { - font-size: 15px; - line-height: 1.38; - margin-top: 6px; - } - - .txSize { - font-size: 13px; - line-height: 1.28; - margin-top: 6px; - color: #b0b0b0; - } - - .amountFeesWrapper { - display: flex; - flex-direction: row; - - .amountWrapper, - .feesWrapper { - width: 50%; - } - - .amountWrapper { - margin-right: 10px; - } - - .feesWrapper { - margin-left: 10px; - } - - .amountLabel, - .feesLabel { - font-size: 11px; - font-weight: 500; - letter-spacing: 0.5px; - line-height: 1.38; - margin-left: 10px; - text-transform: uppercase; - } - - .amount, - .fees { - font-size: 15px; - line-height: 1.38; - margin-top: 6px; - word-break: break-word; - } - - .amountSmall, - .feesSmall { - @extend .amount; - font-size: 12px; - } - } - - .totalAmountLabel { - font-size: 11px; - font-weight: 500; - letter-spacing: 0.5px; - line-height: 1.38; - margin-left: 10px; - text-transform: uppercase; - } - - .totalAmount { - color: var(--yoroi-send-confirmation-dialog-send-values-color); - line-height: 1.38; - margin-top: 6px; - word-break: break-word; - } - - .totalAmountSmall { - @extend .totalAmount; - font-size: 13px; - } - - .walletPassword { - margin-top: 20px; - } - - .error { - @include error-message; - text-align: center; - } -} - -:global(.YoroiClassic) .dialog { - .amountFeesWrapper { - .amount, - .fees { - color: var(--yoroi-send-confirmation-dialog-send-values-color); - } - - .fees { - opacity: 0.5; - } - } -} - -:global(.YoroiModern) .dialog { - min-width: var(--yoroi-comp-dialog-min-width-lg); - max-width: var(--yoroi-comp-dialog-min-width-lg); - font-weight: 400; - - .currencySymbol { - font-weight: 400; - } - - .addressToLabelWrapper, - .amountFeesWrapper { - margin-bottom: 34px; - } - - .addressToLabel { - font-size: 16px; - font-weight: normal; - letter-spacing: 0; - margin-left: 0; - text-transform: none; - } - - .addressTo { - font-size: 14px; - line-height: 1.57; - margin-top: 2px; - } - - .totalAmountWrapper { - margin-bottom: 24px; - } - - .walletPassword { - margin-top: 0; - } - - .amountFeesWrapper { - .amountLabel, - .feesLabel { - font-size: 16px; - font-weight: normal; - letter-spacing: 0; - margin-left: 0; - text-transform: none; - } - - .amount, - .fees { - font-size: 14px; - line-height: 1.57; - margin-top: 2px; - opacity: 0.4; - } - - .amountSmall, - .feesSmall { - @extend .amount; - font-size: 12px; - } - } - - .totalAmountLabel { - font-size: 16px; - font-weight: normal; - letter-spacing: 0; - margin-left: 0; - text-transform: none; - } - - .totalAmount { - font-weight: 500; - line-height: 1.57; - margin-top: 2px; - - .currencySymbol { - font-weight: 500; - } - } -} diff --git a/packages/yoroi-extension/app/containers/wallet/WalletSendPage.js b/packages/yoroi-extension/app/containers/wallet/WalletSendPage.js index 0306005317..5b2f944faf 100644 --- a/packages/yoroi-extension/app/containers/wallet/WalletSendPage.js +++ b/packages/yoroi-extension/app/containers/wallet/WalletSendPage.js @@ -4,14 +4,8 @@ import { Component } from 'react'; import { observer } from 'mobx-react'; import { action, observable, runInAction } from 'mobx'; import type { $npm$ReactIntl$IntlFormat } from 'react-intl'; -import { defineMessages, intlShape } from 'react-intl'; import { ROUTES } from '../../routes-config'; - import WalletSendFormRevamp from '../../components/wallet/send/WalletSendFormRevamp'; - -// Web Wallet Confirmation -import WalletSendConfirmationDialogContainer from './dialogs/WalletSendConfirmationDialogContainer'; -import WalletSendConfirmationDialog from '../../components/wallet/send/WalletSendConfirmationDialog'; import MemoNoExternalStorageDialog from '../../components/wallet/memos/MemoNoExternalStorageDialog'; import { HaskellShelleyTxSignRequest } from '../../api/ada/transactions/shelley/HaskellShelleyTxSignRequest'; import { addressToDisplayString } from '../../api/ada/lib/storage/bridge/utils'; @@ -19,9 +13,6 @@ import type { TokenRow } from '../../api/ada/lib/storage/database/primitives/tab import { genLookupOrFail } from '../../stores/stateless/tokenHelpers'; import BigNumber from 'bignumber.js'; import TransactionSuccessDialog from '../../components/wallet/send/TransactionSuccessDialog'; - -// Hardware Wallet Confirmation -import HWSendConfirmationDialog from '../../components/wallet/send/HWSendConfirmationDialog'; import globalMessages from '../../i18n/global-messages'; import AddNFTDialog from '../../components/wallet/send/WalletSendFormSteps/AddNFTDialog'; import AddTokenDialog from '../../components/wallet/send/WalletSendFormSteps/AddTokenDialog'; @@ -32,33 +23,8 @@ import VerticallyCenteredLayout from '../../components/layout/VerticallyCentered import FullscreenLayout from '../../components/layout/FullscreenLayout'; import type { StoresProps } from '../../stores'; -const messages = defineMessages({ - txConfirmationLedgerNanoLine1: { - id: 'wallet.send.ledger.confirmationDialog.info.line.1', - defaultMessage: - '!!!After connecting your Ledger device to your computer’s USB port, press the Send using Ledger button.', - }, - sendUsingLedgerNano: { - id: 'wallet.send.ledger.confirmationDialog.submit', - defaultMessage: '!!!Send using Ledger', - }, - txConfirmationTrezorTLine1: { - id: 'wallet.send.trezor.confirmationDialog.info.line.1', - defaultMessage: - '!!!After connecting your Trezor device to your computer, press the Send using Trezor button.', - }, - sendUsingTrezorT: { - id: 'wallet.send.trezor.confirmationDialog.submit', - defaultMessage: '!!!Send using Trezor', - }, -}); - @observer export default class WalletSendPage extends Component { - static contextTypes: {| intl: $npm$ReactIntl$IntlFormat |} = { - intl: intlShape.isRequired, - }; - @observable showMemo: boolean = false; @observable showSupportedAddressDomainBanner: boolean = true; @@ -147,17 +113,6 @@ export default class WalletSendPage extends Component { ); } - const { hasAnyPending } = stores.transactions; - - // disallow sending when pending tx exists - if ( - (uiDialogs.isOpen(HWSendConfirmationDialog) || - uiDialogs.isOpen(WalletSendConfirmationDialog)) && - hasAnyPending - ) { - stores.uiDialogs.closeActiveDialog(); - } - const defaultToken = stores.tokenInfoStore.getDefaultTokenInfo( selected.networkId ); @@ -183,7 +138,7 @@ export default class WalletSendPage extends Component { getTokenInfo={genLookupOrFail(stores.tokenInfoStore.tokenInfo)} onSubmit={transactionBuilderStore.updateTentativeTx} totalInput={transactionBuilderStore.totalInput} - hasAnyPending={hasAnyPending} + hasAnyPending={stores.transactions.hasAnyPending} shouldSendAll={transactionBuilderStore.shouldSendAll} updateReceiver={(address: void | string, handle: void | {| handle: string, nameServer: string |}) => transactionBuilderStore.updateReceiver({ address, handle })} @@ -235,12 +190,6 @@ export default class WalletSendPage extends Component { renderDialog: () => Node = () => { const { uiDialogs } = this.props.stores; - if (uiDialogs.isOpen(WalletSendConfirmationDialog)) { - return this.webWalletDoConfirmation(); - } - if (uiDialogs.isOpen(HWSendConfirmationDialog)) { - return this.hardwareWalletDoConfirmation(); - } if (uiDialogs.isOpen(MemoNoExternalStorageDialog)) { return this.noCloudWarningDialog(); } @@ -266,140 +215,6 @@ export default class WalletSendPage extends Component { return ''; }; - /** Web Wallet Send Confirmation dialog - * Callback that creates a container to avoid the component knowing about actions/stores */ - webWalletDoConfirmation: () => Node = () => { - const { stores } = this.props; - const { selected } = this.props.stores.wallets; - if (!selected) - throw new Error(`Active wallet required for ${nameof(this.webWalletDoConfirmation)}.`); - - const { transactionBuilderStore } = this.props.stores; - if (!transactionBuilderStore.tentativeTx) { - throw new Error(`${nameof(this.webWalletDoConfirmation)}::should never happen`); - } - const signRequest = transactionBuilderStore.tentativeTx; - - return ( - - ); - }; - - /** Hardware Wallet (Trezor or Ledger) Confirmation dialog - * Callback that creates a component to avoid the component knowing about actions/stores - * separate container is not needed, this container acts as container for Confirmation dialog */ - hardwareWalletDoConfirmation: () => Node = () => { - const { selected } = this.props.stores.wallets; - if (!selected) - throw new Error(`Active wallet required for ${nameof(this.webWalletDoConfirmation)}.`); - const { transactionBuilderStore } = this.props.stores; - - if (!transactionBuilderStore.tentativeTx) { - throw new Error(`${nameof(this.hardwareWalletDoConfirmation)}::should never happen`); - } - const signRequest = transactionBuilderStore.tentativeTx; - - const totalInput = signRequest.totalInput(); - const fee = signRequest.fee(); - const receivers = signRequest.receivers(false); - - let hwSendConfirmationDialog: Node = null; - - if (!(signRequest instanceof HaskellShelleyTxSignRequest)) { - throw new Error( - `${nameof(this.hardwareWalletDoConfirmation)} hw wallets only supported for Byron` - ); - } - const selectedExplorerForNetwork = - this.props.stores.explorers.selectedExplorer.get( - selected.networkId - ) ?? - (() => { - throw new Error('No explorer for wallet network'); - })(); - - if (selected.type === 'ledger') { - const messagesLedgerNano = { - infoLine1: messages.txConfirmationLedgerNanoLine1, - infoLine2: globalMessages.txConfirmationLedgerNanoLine2, - sendUsingHWButtonLabel: messages.sendUsingLedgerNano, - }; - const ledgerSendStore = this.props.stores.substores.ada.ledgerSend; - ledgerSendStore.init(); - hwSendConfirmationDialog = ( - ledgerSendStore.sendUsingLedgerWallet({ - params: { signRequest }, - onSuccess: this.openTransactionSuccessDialog, - wallet: selected, - }) - } - onCancel={ledgerSendStore.cancel} - unitOfAccountSetting={this.props.stores.profile.unitOfAccount} - addressToDisplayString={addr => - addressToDisplayString(addr, getNetworkById(selected.networkId)) - } - /> - ); - } else if (selected.type === 'trezor') { - const messagesTrezor = { - infoLine1: messages.txConfirmationTrezorTLine1, - infoLine2: globalMessages.txConfirmationTrezorTLine2, - sendUsingHWButtonLabel: messages.sendUsingTrezorT, - }; - const trezorSendStore = this.props.stores.substores.ada.trezorSend; - hwSendConfirmationDialog = ( - trezorSendStore.sendUsingTrezor({ - params: { signRequest }, - onSuccess: this.openTransactionSuccessDialog, - wallet: selected, - }) - } - onCancel={trezorSendStore.cancel} - unitOfAccountSetting={this.props.stores.profile.unitOfAccount} - addressToDisplayString={addr => - addressToDisplayString(addr, getNetworkById(selected.networkId)) - } - /> - ); - } else { - throw new Error('Unsupported hardware wallet found at hardwareWalletDoConfirmation.'); - } - - return hwSendConfirmationDialog; - }; - showMemoDialog: ({| continuation: void => void, dialog: any, diff --git a/packages/yoroi-extension/app/containers/wallet/dialogs/WalletSendConfirmationDialogContainer.js b/packages/yoroi-extension/app/containers/wallet/dialogs/WalletSendConfirmationDialogContainer.js deleted file mode 100644 index e70c5a5730..0000000000 --- a/packages/yoroi-extension/app/containers/wallet/dialogs/WalletSendConfirmationDialogContainer.js +++ /dev/null @@ -1,93 +0,0 @@ -// @flow -import type { Node } from 'react'; -import { Component } from 'react'; -import { observer } from 'mobx-react'; -import WalletSendConfirmationDialog from '../../../components/wallet/send/WalletSendConfirmationDialog'; -import type { UnitOfAccountSettingType } from '../../../types/unitOfAccountType'; -import { addressToDisplayString } from '../../../api/ada/lib/storage/bridge/utils'; -import type { ISignRequest } from '../../../api/common/lib/transactions/ISignRequest'; -import { genLookupOrFail } from '../../../stores/stateless/tokenHelpers'; -import { getNetworkById } from '../../../api/ada/lib/storage/database/prepackaged/networks'; -import type { StoresProps } from '../../../stores'; - -// TODO: unmagic the constants -const MAX_VALUE_BYTES = 5000; -const MAX_TX_BYTES = 16384; - -type LocalProps = {| - +signRequest: ISignRequest, - +staleTx: boolean, - +unitOfAccountSetting: UnitOfAccountSettingType, - +openTransactionSuccessDialog: () => void, -|}; - -type Props = {| ...StoresProps, ...LocalProps |}; - -@observer -export default class WalletSendConfirmationDialogContainer extends Component { - - componentWillUnmount() { - this.props.stores.wallets.sendMoneyRequest.reset(); - } - - render(): Node { - const { - signRequest, - unitOfAccountSetting, - openTransactionSuccessDialog, - } = this.props; - const { stores } = this.props; - const publicDeriver = stores.wallets.selected; - - if (publicDeriver == null) - throw new Error(`Active wallet required for ${nameof(WalletSendConfirmationDialogContainer)}`); - - const { sendMoneyRequest } = stores.wallets; - - const totalInput = signRequest.totalInput(); - const fee = signRequest.fee(); - const size = signRequest.size?.(); - const fullSize = size ? size.full : 0; - const maxOutput = size ? Math.max(...size.outputs) : 0; - const showSize = size != null && ( - size.full > (MAX_TX_BYTES - 1000) - || maxOutput > (MAX_VALUE_BYTES - 1000) - ); - const receivers = signRequest.receivers(false); - return ( - { throw new Error('No explorer for wallet network'); })() - } - getTokenInfo={genLookupOrFail(stores.tokenInfoStore.tokenInfo)} - getCurrentPrice={stores.coinPriceStore.getCurrentPrice} - amount={totalInput.joinSubtractCopy(fee)} - receivers={receivers} - totalAmount={totalInput} - transactionFee={fee} - transactionSize={showSize ? `${fullSize}/${MAX_TX_BYTES} (Biggest output: ${maxOutput}/${MAX_VALUE_BYTES})` : null} - onSubmit={async ({ password }) => { - await stores.substores.ada.mnemonicSend.sendMoney({ - signRequest, - password, - wallet: publicDeriver, - onSuccess: openTransactionSuccessDialog, - }); - }} - isSubmitting={sendMoneyRequest.isExecuting} - onCancel={() => { - stores.uiDialogs.closeActiveDialog(); - sendMoneyRequest.reset(); - }} - error={sendMoneyRequest.error} - unitOfAccountSetting={unitOfAccountSetting} - addressToDisplayString={ - addr => addressToDisplayString(addr, getNetworkById(publicDeriver.networkId)) - } - /> - ); - } -} From 56f5284c7a8edb390bc77ba9d0f9525effc11b00 Mon Sep 17 00:00:00 2001 From: yushi Date: Sat, 25 Jan 2025 23:53:53 +0800 Subject: [PATCH 02/32] refactor: unify entry point of sending tx for hardware and mnemonic wallets --- .../wallet/send/WalletSendFormRevamp.js | 28 ++----- .../WalletSendPreviewStepContainer.js | 66 ++-------------- .../containers/swap/asset-swap/SwapPage.js | 13 ++-- .../app/containers/wallet/WalletSendPage.js | 41 ++++++++-- .../wallet/staking/WithdrawRewardsDialog.js | 40 +++++----- .../ada/AdaDelegationTransactionStore.js | 49 ++---------- .../app/stores/ada/AdaWalletsStore.js | 76 ++++--------------- .../app/stores/ada/VotingStore.js | 45 ++--------- 8 files changed, 101 insertions(+), 257 deletions(-) diff --git a/packages/yoroi-extension/app/components/wallet/send/WalletSendFormRevamp.js b/packages/yoroi-extension/app/components/wallet/send/WalletSendFormRevamp.js index a6a374899a..f975da66cd 100644 --- a/packages/yoroi-extension/app/components/wallet/send/WalletSendFormRevamp.js +++ b/packages/yoroi-extension/app/components/wallet/send/WalletSendFormRevamp.js @@ -54,7 +54,6 @@ import { ampli } from '../../../../ampli/index'; import type { DomainResolverFunc, DomainResolverResponse } from '../../../stores/ada/AdaAddressesStore'; import { isResolvableDomain } from '@yoroi/resolver'; import SupportedAddressDomainsBanner from '../../../containers/wallet/SupportedAddressDomainsBanner'; -import type { SendMoneyRequest } from '../../../stores/toplevel/WalletStore'; import type { MaxSendableAmountRequest } from '../../../stores/toplevel/TransactionBuilderStore'; import type { WalletState } from '../../../../chrome/extension/background/types'; import LoadingSpinner from '../../widgets/LoadingSpinner'; @@ -167,7 +166,7 @@ type Props = {| +selectedWallet: WalletState, +selectedExplorer: Map, +hasAnyPending: boolean, - +onSubmit: void => void, + +onConfirmAmount: void => void, +totalInput: ?MultiToken, +updateReceiver: (void | string, void | {| handle: string, nameServer: string |}) => void, +updateAmount: (?BigNumber) => void, @@ -209,22 +208,10 @@ type Props = {| +calculateMaxAmount: void => Promise, +signRequest: null | ISignRequest, +staleTx: boolean, - +openTransactionSuccessDialog: void => void, - +sendMoneyRequest: SendMoneyRequest, - +sendMoney: (params: {| - password: string, - +wallet: { - publicDeriverId: number, - +plate: { TextPart: string, ... }, - ... - }, - signRequest: ISignRequest, - onSuccess?: void => void, - |}) => Promise, + +isSending: boolean, +ledgerSendError: null | LocalizableError, +trezorSendError: null | LocalizableError, - +ledgerSend: LedgerSendStore, - +trezorSend: TrezorSendStore, + +onSubmit: ({| password: string |}) => Promise, |}; const SMemoTextField = styled(MemoTextField)(({ theme }) => ({ @@ -963,20 +950,17 @@ export default class WalletSendFormRevamp extends Component { staleTx={this.props.staleTx} isDefaultIncluded={this.props.isDefaultIncluded} unitOfAccountSetting={this.props.unitOfAccountSetting} - openTransactionSuccessDialog={this.props.openTransactionSuccessDialog} minAda={this.props.minAda} plannedTxInfoMap={this.props.plannedTxInfoMap} onUpdateStep={this.onUpdateStep.bind(this)} - sendMoneyRequest={this.props.sendMoneyRequest} - sendMoney={this.props.sendMoney} + isSending={this.props.isSending} getTokenInfo={this.props.getTokenInfo} getCurrentPrice={this.props.getCurrentPrice} ledgerSendError={this.props.ledgerSendError} trezorSendError={this.props.trezorSendError} - ledgerSend={this.props.ledgerSend} - trezorSend={this.props.trezorSend} selectedExplorer={this.props.selectedExplorer} selectedWallet={this.props.selectedWallet} + onSubmit={this.props.onSubmit} /> ); default: @@ -1025,7 +1009,7 @@ export default class WalletSendFormRevamp extends Component { variant="primary" size="medium" onClick={() => { - this.props.onSubmit(); + this.props.onConfirmAmount(); this.onUpdateStep(SEND_FORM_STEP.PREVIEW); }} disabled={!this.props.fee || this.props.hasAnyPending || invalidMemo || maxSendableAmount.isExecuting} diff --git a/packages/yoroi-extension/app/components/wallet/send/WalletSendFormSteps/WalletSendPreviewStepContainer.js b/packages/yoroi-extension/app/components/wallet/send/WalletSendFormSteps/WalletSendPreviewStepContainer.js index 05350edd59..98c5852dbe 100644 --- a/packages/yoroi-extension/app/components/wallet/send/WalletSendFormSteps/WalletSendPreviewStepContainer.js +++ b/packages/yoroi-extension/app/components/wallet/send/WalletSendFormSteps/WalletSendPreviewStepContainer.js @@ -10,13 +10,9 @@ import { addressToDisplayString } from '../../../../api/ada/lib/storage/bridge/u import type { ISignRequest } from '../../../../api/common/lib/transactions/ISignRequest'; import type { TokenRow } from '../../../../api/ada/lib/storage/database/primitives/tables'; import type { MultiToken, TokenLookupKey } from '../../../../api/common/lib/MultiToken'; -import { ampli } from '../../../../../ampli/index'; -import type { SendMoneyRequest } from '../../../../stores/toplevel/WalletStore'; import { getNetworkById } from '../../../../api/ada/lib/storage/database/prepackaged/networks'; import type { WalletState } from '../../../../../chrome/extension/background/types'; import { HaskellShelleyTxSignRequest } from '../../../../api/ada/transactions/shelley/HaskellShelleyTxSignRequest'; -import LedgerSendStore from '../../../../stores/ada/send/LedgerSendStore'; -import TrezorSendStore from '../../../../stores/ada/send/TrezorSendStore'; // TODO: unmagic the constants const MAX_VALUE_BYTES = 5000; @@ -36,67 +32,16 @@ type Props = {| +onUpdateStep: (step: number) => void, +getCurrentPrice: (from: string, to: string) => ?string, +getTokenInfo: ($ReadOnly>) => $ReadOnly, - +openTransactionSuccessDialog: void => void, - +sendMoneyRequest: SendMoneyRequest, - +sendMoney: (params: {| - password: string, - +wallet: { - publicDeriverId: number, - +plate: { TextPart: string, ... }, - ... - }, - signRequest: ISignRequest, - onSuccess?: void => void, - |}) => Promise, + +isSending: boolean, +ledgerSendError: null | LocalizableError, +trezorSendError: null | LocalizableError, - +ledgerSend: LedgerSendStore, - +trezorSend: TrezorSendStore, selectedExplorer: Map, +selectedWallet: WalletState, + +onSubmit: ({| password: string |}) => Promise, |}; @observer export default class WalletSendPreviewStepContainer extends Component { - componentWillUnmount() { - this.props.sendMoneyRequest.reset(); - this.props.ledgerSend.cancel(); - this.props.trezorSend.cancel(); - } - - onSubmit: ({| password: string |}) => Promise = async ({ password }) => { - const { signRequest, openTransactionSuccessDialog } = this.props; - const { ledgerSend, trezorSend, sendMoney, selectedWallet } = this.props; - - if (signRequest == null) throw new Error('Unexpected missing active signing request'); - - ampli.sendSummarySubmitted({ - asset_count: signRequest.totalInput().nonDefaultEntries().length, - }); - - if (selectedWallet.type === 'ledger') { - await ledgerSend.sendUsingLedgerWallet({ - params: { signRequest }, - onSuccess: openTransactionSuccessDialog, - wallet: selectedWallet, - }); - } else if (selectedWallet.type === 'trezor') { - await trezorSend.sendUsingTrezor({ - params: { signRequest }, - onSuccess: openTransactionSuccessDialog, - wallet: selectedWallet, - }); - } else { - // walletType === 'mnemonic' - await sendMoney({ - signRequest, - password, - wallet: selectedWallet, - onSuccess: openTransactionSuccessDialog, - }); - } - }; - render(): Node { const { signRequest, @@ -104,9 +49,10 @@ export default class WalletSendPreviewStepContainer extends Component { onUpdateStep, selectedWallet, selectedExplorer, - sendMoneyRequest, + isSending, getTokenInfo, getCurrentPrice, + onSubmit, } = this.props; if (selectedWallet == null) @@ -150,8 +96,8 @@ export default class WalletSendPreviewStepContainer extends Component { ? `${fullSize}/${MAX_TX_BYTES} (Biggest output: ${maxOutput}/${MAX_VALUE_BYTES})` : null } - onSubmit={this.onSubmit} - isSubmitting={sendMoneyRequest.isExecuting} + onSubmit={onSubmit} + isSubmitting={isSending} unitOfAccountSetting={unitOfAccountSetting} addressToDisplayString={addr => addressToDisplayString(addr, network)} selectedNetwork={network} diff --git a/packages/yoroi-extension/app/containers/swap/asset-swap/SwapPage.js b/packages/yoroi-extension/app/containers/swap/asset-swap/SwapPage.js index 3e2dec09cd..27a6259528 100644 --- a/packages/yoroi-extension/app/containers/swap/asset-swap/SwapPage.js +++ b/packages/yoroi-extension/app/containers/swap/asset-swap/SwapPage.js @@ -255,14 +255,13 @@ function SwapPage(props: StoresProps & Intl): Node { setOpenedDialog('loadingOverlay'); const password = userPasswordState?.value; - const baseBroadcastRequest = { wallet, signRequest }; - const broadcastRequest = isHardwareWallet - ? { [walletType]: baseBroadcastRequest } - : { normal: { ...baseBroadcastRequest, password } }; try { - const refreshWallet = () => stores.wallets.refreshWalletFromRemote(wallet.publicDeriverId); - // $FlowIgnore[incompatible-call] - await stores.substores.ada.wallets.adaSendAndRefresh({ broadcastRequest, refreshWallet }); + await stores.substores.ada.wallets.adaSendAndRefresh({ + wallet, + signRequest, + password, + callback: () => stores.wallets.refreshWalletFromRemote(wallet.publicDeriverId), + }); setOrderStepValue(2); try { ampli.swapOrderSubmitted({ diff --git a/packages/yoroi-extension/app/containers/wallet/WalletSendPage.js b/packages/yoroi-extension/app/containers/wallet/WalletSendPage.js index 5b2f944faf..b8a2b9d616 100644 --- a/packages/yoroi-extension/app/containers/wallet/WalletSendPage.js +++ b/packages/yoroi-extension/app/containers/wallet/WalletSendPage.js @@ -59,6 +59,13 @@ export default class WalletSendPage extends Component { ampli.sendInitiated(); } + componentWillUnmount() { + const { stores } = this.props; + stores.wallets.sendMoneyRequest.reset(); + stores.substores.ada.ledgerSend.cancel(); + stores.substores.ada.trezorSend.cancel(); + } + @action toggleShowMemo: void => void = () => { this.showMemo = !this.showMemo; @@ -92,6 +99,31 @@ export default class WalletSendPage extends Component { return info.Metadata.numberOfDecimals; } + onSubmit: ({| password: string |}) => Promise = async ({ password }) => { + const signRequest = this.props.stores.transactionBuilderStore.tentativeTx; + if (!(signRequest instanceof HaskellShelleyTxSignRequest)) { + throw new Error('Unexpected missing active signing request'); + } + + const wallet = this.props.stores.wallets.selected; + if (wallet == null) { + throw new Error('unexpectedly missing wallet') + } + + ampli.sendSummarySubmitted({ + asset_count: signRequest.totalInput().nonDefaultEntries().length, + }); + + await this.props.stores.substores.ada.wallets.adaSendAndRefresh({ + wallet, + signRequest, + password, + callback: async () => { + this.openTransactionSuccessDialog(); + }, + }); + }; + render(): Node { const { stores } = this.props; const { selected } = stores.wallets; @@ -136,7 +168,7 @@ export default class WalletSendPage extends Component { selectedToken={transactionBuilderStore.selectedToken} defaultToken={defaultToken} getTokenInfo={genLookupOrFail(stores.tokenInfoStore.tokenInfo)} - onSubmit={transactionBuilderStore.updateTentativeTx} + onConfirmAmount={transactionBuilderStore.updateTentativeTx} totalInput={transactionBuilderStore.totalInput} hasAnyPending={stores.transactions.hasAnyPending} shouldSendAll={transactionBuilderStore.shouldSendAll} @@ -168,19 +200,16 @@ export default class WalletSendPage extends Component { openDialog={this.openDialog} closeDialog={this.props.stores.uiDialogs.closeActiveDialog} isOpen={uiDialogs.isOpen} - openTransactionSuccessDialog={this.openTransactionSuccessDialog.bind(this)} unitOfAccountSetting={stores.profile.unitOfAccount} getCurrentPrice={stores.coinPriceStore.getCurrentPrice} calculateMaxAmount={transactionBuilderStore.calculateMaxAmount} maxSendableAmount={transactionBuilderStore.maxSendableAmount} signRequest={transactionBuilderStore.tentativeTx} staleTx={transactionBuilderStore.txMismatch} - sendMoneyRequest={stores.wallets.sendMoneyRequest} - sendMoney={stores.substores.ada.mnemonicSend.sendMoney} + isSending={stores.wallets.sendMoneyRequest.isExecuting} ledgerSendError={stores.substores.ada.ledgerSend.error || null} trezorSendError={stores.substores.ada.trezorSend.error || null} - ledgerSend={stores.substores.ada.ledgerSend} - trezorSend={stores.substores.ada.trezorSend} + onSubmit={this.onSubmit.bind(this)} /> {this.renderDialog()} diff --git a/packages/yoroi-extension/app/containers/wallet/staking/WithdrawRewardsDialog.js b/packages/yoroi-extension/app/containers/wallet/staking/WithdrawRewardsDialog.js index ad27ad93fe..7b1162e028 100644 --- a/packages/yoroi-extension/app/containers/wallet/staking/WithdrawRewardsDialog.js +++ b/packages/yoroi-extension/app/containers/wallet/staking/WithdrawRewardsDialog.js @@ -29,6 +29,7 @@ import Dialog from '../../../components/widgets/Dialog'; import { getNetworkById } from '../../../api/ada/lib/storage/database/prepackaged/networks'; import HorizintallyCenteredLayout from '../../../components/layout/HorizintallyCenteredLayout'; import type { StoresProps } from '../../../stores'; +import { HaskellShelleyTxSignRequest } from '../../../api/ada/transactions/shelley/HaskellShelleyTxSignRequest'; const messages = defineMessages({ dialogTitle: { @@ -79,35 +80,32 @@ export default class WithdrawRewardsDialog extends Component<{| ...StoresProps, stores.substores.ada.trezorSend.cancel(); } - submit: void => Promise = async () => { + signAndBroadcast: (?string) => Promise = async (password) => { const { stores } = this.props; - const selected = stores.wallets.selected; - if (selected == null) throw new Error(`${nameof(WithdrawRewardsDialog)} no wallet selected`); + const wallet = stores.wallets.selected; + if (wallet == null) throw new Error(`${nameof(WithdrawRewardsDialog)} no wallet selected`); + const signRequest = stores.substores.ada.delegationTransaction.createWithdrawalTx.result; - if (signRequest == null) return; + if (!(signRequest instanceof HaskellShelleyTxSignRequest)) { + throw new Error('Unexpected missing active signing request'); + } + await this.props.stores.substores.ada.wallets.adaSendAndRefresh({ + wallet, + signRequest, + password, + callback: async () => {}, + }); + } + + submit: void => Promise = async () => { if (this.spendingPasswordForm == null) { - if (selected.type === 'trezor') { - await stores.substores.ada.trezorSend.sendUsingTrezor({ - params: { signRequest }, - wallet: selected, - }); - } - if (selected.type === 'ledger') { - await stores.substores.ada.ledgerSend.sendUsingLedgerWallet({ - params: { signRequest }, - wallet: selected, - }); - } + await this.signAndBroadcast(); } else { this.spendingPasswordForm.submit({ onSuccess: async form => { const { walletPassword } = form.values(); - await stores.substores.ada.mnemonicSend.sendMoney({ - signRequest, - password: walletPassword, - wallet: selected, - }); + await this.signAndBroadcast(walletPassword); }, onError: () => {}, }); diff --git a/packages/yoroi-extension/app/stores/ada/AdaDelegationTransactionStore.js b/packages/yoroi-extension/app/stores/ada/AdaDelegationTransactionStore.js index f1d14aa08d..8d2f1c5704 100644 --- a/packages/yoroi-extension/app/stores/ada/AdaDelegationTransactionStore.js +++ b/packages/yoroi-extension/app/stores/ada/AdaDelegationTransactionStore.js @@ -133,52 +133,19 @@ export default class AdaDelegationTransactionStore extends Store { password?: string, dialog?: any, |}) => Promise = async request => { - const result = this.createDelegationTx.result; - if (result == null) { + const signRequest = this.createDelegationTx.result?.signTxRequest; + if (signRequest == null) { throw new Error(`${nameof(this.signTransaction)} no tx to broadcast`); } - const refreshWallet = () => { - this.stores.delegation.disablePoolTransitionState(request.wallet); - return this.stores.wallets.refreshWalletFromRemote(request.wallet.publicDeriverId); - }; try { - if (request.wallet.type === 'ledger') { - await this.stores.substores.ada.wallets.adaSendAndRefresh({ - broadcastRequest: { - ledger: { - signRequest: result.signTxRequest, - wallet: request.wallet, - }, - }, - refreshWallet, - }); - return; - } - if (request.wallet.type === 'trezor') { - await this.stores.substores.ada.wallets.adaSendAndRefresh({ - broadcastRequest: { - trezor: { - signRequest: result.signTxRequest, - wallet: request.wallet, - }, - }, - refreshWallet, - }); - return; - } - // normal password-based wallet - if (request.password == null) { - throw new Error(`${nameof(this.signTransaction)} missing password for non-hardware signing`); - } await this.stores.substores.ada.wallets.adaSendAndRefresh({ - broadcastRequest: { - normal: { - wallet: request.wallet, - password: request.password, - signRequest: result.signTxRequest, - }, + wallet: request.wallet, + signRequest, + password: request.password, + callback: () => { + this.stores.delegation.disablePoolTransitionState(request.wallet); + return this.stores.wallets.refreshWalletFromRemote(request.wallet.publicDeriverId); }, - refreshWallet, }); } catch (error) { runInAction(() => { diff --git a/packages/yoroi-extension/app/stores/ada/AdaWalletsStore.js b/packages/yoroi-extension/app/stores/ada/AdaWalletsStore.js index b144aa8576..0c88b46ff3 100644 --- a/packages/yoroi-extension/app/stores/ada/AdaWalletsStore.js +++ b/packages/yoroi-extension/app/stores/ada/AdaWalletsStore.js @@ -29,76 +29,32 @@ export default class AdaWalletsStore extends Store { // =================== SEND MONEY ==================== // adaSendAndRefresh: ({| - broadcastRequest: - | {| - normal: {| - +wallet: { - publicDeriverId: number, - +plate: { TextPart: string, ... }, - ... - }, - signRequest: HaskellShelleyTxSignRequest, - password: string, - |}, - |} - | {| - trezor: {| - signRequest: HaskellShelleyTxSignRequest, - +wallet: { - publicDeriverId: number, - +plate: { TextPart: string, ... }, - publicKey: string, - pathToPublic: Array, - stakingAddressing: Addressing, - networkId: number, - hardwareWalletDeviceId: ?string, - ... - }, - |}, - |} - | {| - ledger: {| - signRequest: HaskellShelleyTxSignRequest, - +wallet: { - publicDeriverId: number, - +plate: { TextPart: string, ... }, - stakingAddressing: Addressing, - publicKey: string, - pathToPublic: Array, - networkId: number, - hardwareWalletDeviceId: ?string, - ... - } - |}, - |}, - refreshWallet: () => Promise, + wallet: WalletState, + signRequest: HaskellShelleyTxSignRequest, + password: ?string, + callback: () => Promise, |}) => Promise = async request => { - let broadcastRequest; - let publicDeriverId; - let plateTextPart; + const { wallet, signRequest, password, callback } = request; - if (request.broadcastRequest.ledger) { - const { wallet, signRequest } = request.broadcastRequest.ledger; + let broadcastRequest; + if (wallet.type === 'ledger') { broadcastRequest = async () => { return await this.stores.substores.ada.ledgerSend.signAndBroadcastFromWallet({ signRequest, wallet, }); }; - publicDeriverId = wallet.publicDeriverId; - plateTextPart = wallet.plate.TextPart; - } else if (request.broadcastRequest.trezor) { - const { wallet, signRequest } = request.broadcastRequest.trezor; + } else if (wallet.type === 'trezor') { broadcastRequest = async () => { return await this.stores.substores.ada.trezorSend.signAndBroadcastFromWallet({ signRequest, wallet, }); }; - publicDeriverId = request.broadcastRequest.trezor.wallet.publicDeriverId; - plateTextPart = request.broadcastRequest.trezor.wallet.plate.TextPart; - } else if (request.broadcastRequest.normal) { - const { wallet, signRequest, password } = request.broadcastRequest.normal; + } else if (wallet.type === 'mnemonic') { + if (!password) { + throw new Error('missing password for hardware wallet'); + } broadcastRequest = async () => { return await this.stores.substores.ada.mnemonicSend.signAndBroadcast({ signRequest, @@ -106,18 +62,16 @@ export default class AdaWalletsStore extends Store { publicDeriverId: wallet.publicDeriverId, }); }; - publicDeriverId = wallet.publicDeriverId; - plateTextPart = wallet.plate.TextPart; } else { throw new Error( `${nameof(AdaWalletsStore)}::${nameof(this.adaSendAndRefresh)} unhandled wallet type` ); }; await this.stores.wallets.sendAndRefresh({ - publicDeriverId, + publicDeriverId: wallet.publicDeriverId, broadcastRequest, - refreshWallet: request.refreshWallet, - plateTextPart, + refreshWallet: callback, + plateTextPart: wallet.plate.TextPart, }); }; diff --git a/packages/yoroi-extension/app/stores/ada/VotingStore.js b/packages/yoroi-extension/app/stores/ada/VotingStore.js index 798bb81a6e..bfd05a668b 100644 --- a/packages/yoroi-extension/app/stores/ada/VotingStore.js +++ b/packages/yoroi-extension/app/stores/ada/VotingStore.js @@ -304,48 +304,15 @@ export default class VotingStore extends Store { password?: string, wallet: WalletState, |}) => Promise = async request => { - const result = this.createVotingRegTx.result; - if (result == null) { + const signRequest = this.createVotingRegTx.result; + if (signRequest == null) { throw new Error(`${nameof(this.signTransaction)} no tx to broadcast`); } - if (request.wallet.type === 'ledger') { - await this.stores.substores.ada.wallets.adaSendAndRefresh({ - broadcastRequest: { - ledger: { - signRequest: result, - wallet: request.wallet, - }, - }, - refreshWallet: () => this.stores.wallets.refreshWalletFromRemote(request.wallet.publicDeriverId), - }); - return; - } - if (request.wallet.type === 'trezor') { - await this.stores.substores.ada.wallets.adaSendAndRefresh({ - broadcastRequest: { - trezor: { - signRequest: result, - wallet: request.wallet, - }, - }, - refreshWallet: () => this.stores.wallets.refreshWalletFromRemote(request.wallet.publicDeriverId), - }); - return; - } - - // normal password-based wallet - if (request.password == null) { - throw new Error(`${nameof(this.signTransaction)} missing password for non-hardware signing`); - } await this.stores.substores.ada.wallets.adaSendAndRefresh({ - broadcastRequest: { - normal: { - wallet: request.wallet, - password: request.password, - signRequest: result, - }, - }, - refreshWallet: () => this.stores.wallets.refreshWalletFromRemote(request.wallet.publicDeriverId), + wallet: request.wallet, + signRequest, + password: request.password, + callback: () => this.stores.wallets.refreshWalletFromRemote(request.wallet.publicDeriverId), }); }; From b1cdeb556685531ac2b419f5dbe71b69caa7f35c Mon Sep 17 00:00:00 2001 From: yushi Date: Mon, 27 Jan 2025 00:50:57 +0800 Subject: [PATCH 03/32] remove unused pre-revamp components --- packages/yoroi-extension/app/Routes.js | 9 - .../transfer/WithdrawalTxDialogContainer.js | 81 ---- .../wallet/staking/StakingDashboardPage.js | 356 ------------------ .../wallet/staking/StakingPageContent.js | 11 - packages/yoroi-extension/app/routes-config.js | 1 - .../app/stores/stateless/topbarCategories.js | 8 - 6 files changed, 466 deletions(-) delete mode 100644 packages/yoroi-extension/app/containers/transfer/WithdrawalTxDialogContainer.js delete mode 100644 packages/yoroi-extension/app/containers/wallet/staking/StakingDashboardPage.js diff --git a/packages/yoroi-extension/app/Routes.js b/packages/yoroi-extension/app/Routes.js index 4b64603c66..bbef5ac02a 100644 --- a/packages/yoroi-extension/app/Routes.js +++ b/packages/yoroi-extension/app/Routes.js @@ -112,9 +112,6 @@ const URILandingPage = React.lazy(URILandingPagePromise); const ReceivePromise = () => import('./containers/wallet/Receive'); const Receive = React.lazy(ReceivePromise); -const StakingDashboardPagePromise = () => import('./containers/wallet/staking/StakingDashboardPage'); -const StakingDashboardPage = React.lazy(StakingDashboardPagePromise); - const CardanoStakingPagePromise = () => import('./containers/wallet/staking/CardanoStakingPage'); const CardanoStakingPage = React.lazy(CardanoStakingPagePromise); @@ -173,7 +170,6 @@ export const LazyLoadPromises: Array<() => any> = [ URILandingPagePromise, WalletTransferPagePromise, ReceivePromise, - StakingDashboardPagePromise, CardanoStakingPagePromise, VotingPageContentPromise, ComplexityLevelSettingsPagePromise, @@ -280,11 +276,6 @@ const WalletsSubpages = stores => ( path={ROUTES.WALLETS.RECEIVE.ROOT} component={props => wrapReceive({ ...props, stores }, )} /> - } - /> void, -|}; - -@observer -export default class WithdrawalTxDialogContainer extends Component { - - static contextTypes: {|intl: $npm$ReactIntl$IntlFormat|} = { - intl: intlShape.isRequired, - }; - - render(): Node { - const { stores } = this.props; - const { intl } = this.context; - - if (this.props.stores.profile.selectedNetwork == null) { - throw new Error(`${nameof(WithdrawalTxDialogContainer)} no selected network`); - } - const defaultToken = this.props.stores.tokenInfoStore.getDefaultTokenInfo( - this.props.stores.profile.selectedNetwork.NetworkId - ); - - const { createWithdrawalTx } = this.props.stores.substores.ada.delegationTransaction; - return ( - {}, // nothing extra to do - label: intl.formatMessage(globalMessages.confirm), - }} - transactionRequest={{ - error: createWithdrawalTx.error, - result: createWithdrawalTx.result, - reset: createWithdrawalTx.reset, - }} - toTransferTx={tentativeTx => { - if (!(tentativeTx instanceof HaskellShelleyTxSignRequest)) { - throw new Error(`${nameof(WithdrawalTxDialogContainer)} incorrect tx type`); - } - - const deregistrations = tentativeTx.keyDeregistrations(); - const withdrawals = tentativeTx.withdrawals(); - - return { - recoveredBalance: withdrawals.reduce( - (sum, curr) => sum.joinAddCopy(curr.amount), - new MultiToken([], getDefaultEntryToken(defaultToken)) - ), - fee: tentativeTx.fee(), - senders: tentativeTx - .uniqueSenderAddresses(), - receivers: tentativeTx - .receivers(true), - withdrawals, - deregistrations, - }; - }} - /> - ); - } -} diff --git a/packages/yoroi-extension/app/containers/wallet/staking/StakingDashboardPage.js b/packages/yoroi-extension/app/containers/wallet/staking/StakingDashboardPage.js deleted file mode 100644 index 796f1491ac..0000000000 --- a/packages/yoroi-extension/app/containers/wallet/staking/StakingDashboardPage.js +++ /dev/null @@ -1,356 +0,0 @@ -// @flow -import type { Node } from 'react'; -import { Component } from 'react'; -import type { $npm$ReactIntl$IntlFormat } from 'react-intl'; -import { intlShape } from 'react-intl'; -import { observer } from 'mobx-react'; - -import StakingDashboard from '../../../components/wallet/staking/dashboard/StakingDashboard'; -import UserSummary from '../../../components/wallet/staking/dashboard/UserSummary'; -import StakePool from '../../../components/wallet/staking/dashboard/StakePool'; -import LessThanExpectedDialog from '../../../components/wallet/staking/dashboard/LessThanExpectedDialog'; -import { digestForHash } from '../../../api/ada/lib/storage/database/primitives/api/utils'; -import { handleExternalLinkClick } from '../../../utils/routing'; -import UnmangleTxDialogContainer from '../../transfer/UnmangleTxDialogContainer'; -import config from '../../../config'; -import globalMessages from '../../../i18n/global-messages'; -import { observable, runInAction } from 'mobx'; -import DeregisterDialogContainer from '../../transfer/DeregisterDialogContainer'; -import WithdrawalTxDialogContainer from '../../transfer/WithdrawalTxDialogContainer'; -import { genLookupOrFail, getTokenName } from '../../../stores/stateless/tokenHelpers'; -import { truncateToken } from '../../../utils/formatters'; -import { generateGraphData } from '../../../utils/graph'; -import { maybe, noop } from '../../../coreUtils'; -import type { WalletState } from '../../../../chrome/extension/background/types'; -import type { StoresProps } from '../../../stores'; - -@observer -export default class StakingDashboardPage extends Component { - static contextTypes: {| intl: $npm$ReactIntl$IntlFormat |} = { - intl: intlShape.isRequired, - }; - - @observable notificationElementId: string = ''; - - componentWillUnmount() { - this.props.stores.substores.ada.delegationTransaction.reset({ justTransaction: false }); - } - - render(): Node { - const { stores } = this.props; - const publicDeriver = stores.wallets.selected; - if (publicDeriver == null) { - throw new Error(`${nameof(StakingDashboardPage)} no public deriver. Should never happen`); - } - const delegationStore = stores.delegation; - const delegationRequests = delegationStore.getDelegationRequests(publicDeriver.publicDeriverId); - if (delegationRequests == null) { - throw new Error(`${nameof(StakingDashboardPage)} opened for non-reward wallet`); - } - - const isStakeRegistered = this.props.stores.delegation - .isStakeRegistered(publicDeriver.publicDeriverId); - - const errorIfPresent = maybe(delegationRequests.error, error => ({ error })); - const stakePools = errorIfPresent ?? this.getStakePools( - publicDeriver.publicDeriverId, - publicDeriver.networkId - ); - - const showRewardAmount = errorIfPresent == null - && stores.delegation.isExecutedDelegatedBalance(publicDeriver.publicDeriverId); - - const dashboard = ( - stores.delegation.setSelectedPage(page), - } - } - hasAnyPending={stores.transactions.hasAnyPending} - stakePools={stakePools} - userSummary={this._generateUserSummary({ - wallet: publicDeriver, - showRewardAmount, - })} - graphData={generateGraphData({ - delegationRequests, - networkId: publicDeriver.networkId, - defaultTokenId: publicDeriver.defaultTokenId, - currentEpoch: stores.substores.ada.time.getCurrentTimeRequests( - publicDeriver - ).currentEpoch, - shouldHideBalance: stores.profile.shouldHideBalance, - getLocalPoolInfo: stores.delegation.getLocalPoolInfo, - tokenInfo: stores.tokenInfoStore.tokenInfo, - })} - isUnregistered={!isStakeRegistered} - epochLength={this.getEpochLengthInDays(publicDeriver)} - ticker={truncateToken( - getTokenName( - stores.tokenInfoStore.getDefaultTokenInfo(publicDeriver.networkId) - ) - )} - /> - ); - - return ( - <> - {this.getDialog(publicDeriver)} - {dashboard} - - ); - } - - getEpochLengthInDays: ({ publicDeriverId: number, ... }) => ?number = (wallet) => { - const timeCalcRequests = this.props.stores.substores.ada.time.getTimeCalcRequests(wallet); - const { currentEpochLength, currentSlotLength } = timeCalcRequests.requests; - const epochLengthInSeconds = currentEpochLength() * currentSlotLength(); - return epochLengthInSeconds / (60 * 60 * 24); - }; - - getStakePools: (number, number) => {| pools: null | Array |} = (publicDeriverId, networkId) => { - const delegationStore = this.props.stores.delegation; - const delegationRequests = delegationStore.getDelegationRequests(publicDeriverId); - if (delegationRequests == null) { - throw new Error(`${nameof(StakingDashboardPage)} opened for non-reward wallet`); - } - const isStakeRegistered = this.props.stores.delegation.isStakeRegistered(publicDeriverId); - if ( - !delegationRequests.getDelegatedBalance.wasExecuted - || delegationRequests.getDelegatedBalance.isExecuting - || delegationRequests.getDelegatedBalance.result == null - || !isStakeRegistered - ) { - return { pools: null }; - } - if (delegationRequests.getDelegatedBalance.result.delegation == null) { - return { pools: [] }; - } - const currentPools = [ - [delegationRequests.getDelegatedBalance.result.delegation, 1] - ]; - const tooltipNotification = { - duration: config.wallets.ADDRESS_COPY_TOOLTIP_NOTIFICATION_DURATION, - message: globalMessages.copyTooltipMessage, - }; - - const { uiNotifications } = this.props.stores; - const { intl } = this.context; - return { - pools: currentPools.map(pool => { - const meta = this.props.stores.delegation.getLocalPoolInfo( - networkId, - pool[0] - ); - if (meta == null) { - // server hasn't returned information about the stake pool yet - return undefined; - } - const name = meta.info?.name ?? intl.formatMessage(globalMessages.unknownPoolLabel); - - const moreInfo = - meta.info?.homepage != null - ? { - openPoolPage: handleExternalLinkClick, - url: meta.info.homepage, - } - : undefined; - - // LEGACY UI - const stakePoolMeta = { - // percentage: '30', - // fullness: '18', - // margins: '12', - // created: '29/02/2019 12:42:41 PM', - // cost: '12,688.00000', - // stake: '9,688.00000', - // pledge: '85.567088', - // rewards: '81.000088', - // age: '23', - }; - - return ( - { - throw new Error('No explorer for wallet network'); - })() - } - hash={pool[0]} - moreInfo={moreInfo} - onCopyAddressTooltip={(address, elementId) => { - if (!uiNotifications.isOpen(elementId)) { - runInAction(() => { - this.notificationElementId = elementId; - }); - uiNotifications.open({ - id: elementId, - duration: tooltipNotification.duration, - message: tooltipNotification.message, - }); - } - }} - notification={ - this.notificationElementId == null - ? null - : uiNotifications.getTooltipActiveNotification(this.notificationElementId) - } - undelegate={undefined} - /> - ); - }), - }; - }; - - getDialog: (WalletState) => Node = wallet => { - const { stores } = this.props; - const uiDialogs = this.props.stores.uiDialogs; - - if (uiDialogs.isOpen(LessThanExpectedDialog)) { - return ( - this.props.stores.uiDialogs.closeActiveDialog()} - /> - ); - } - - if (uiDialogs.isOpen(UnmangleTxDialogContainer)) { - return ( - this.props.stores.uiDialogs.closeActiveDialog()} - /> - ); - } - - if (uiDialogs.isOpen(DeregisterDialogContainer)) { - return ( - { - // note: purposely don't await since the next dialog will properly render the spinner - noop(stores.substores.ada.delegationTransaction.createWithdrawalTxForWallet({ wallet })); - this.props.stores.uiDialogs.open({ dialog: WithdrawalTxDialogContainer }); - }} - /> - ); - } - if (uiDialogs.isOpen(WithdrawalTxDialogContainer)) { - return ( - { - stores.substores.ada.delegationTransaction.reset({ justTransaction: false }); - this.props.stores.uiDialogs.closeActiveDialog(); - }} - /> - ); - } - - return null; - }; - - _generateUserSummary: ({| - wallet: { - publicDeriverId: number, - networkId: number, - defaultTokenId: string, - ... - }, - showRewardAmount: boolean, - |}) => Node = request => { - - const { wallet, showRewardAmount } = request; - const { publicDeriverId, networkId, defaultTokenId } = wallet; - const { stores } = this.props; - - const balance = this.props.stores.transactions.balance; - const mangledAmounts = stores.delegation.getMangledAmountsOrZero( - publicDeriverId, - networkId, - defaultTokenId - ); - const rewardBalance = this.props.stores.delegation.getRewardBalanceOrZero( - wallet - ); - - const stakeRegistered = - this.props.stores.delegation.isStakeRegistered(publicDeriverId) === true; - - const currentlyDelegating = - this.props.stores.delegation.getDelegatedPoolId(publicDeriverId) != null; - - return ( - - this.props.stores.uiDialogs.open({ - dialog: UnmangleTxDialogContainer, - }) - } - totalSum={balance == null ? undefined : balance.joinAddCopy(rewardBalance)} - totalRewards={showRewardAmount ? rewardBalance : undefined} - openLearnMore={() => - this.props.stores.uiDialogs.open({ - dialog: LessThanExpectedDialog, - }) - } - withdrawRewards={ - stakeRegistered ? () => { - this.props.stores.uiDialogs.open({ dialog: DeregisterDialogContainer }); - } : undefined - } - unitOfAccount={_entry => { - // temporarily disabled - return undefined; - /* - const tokenRow = this.props.stores.tokenInfoStore.tokenInfo - .get(entry.networkId.toString()) - ?.get(entry.identifier); - if (tokenRow == null) return undefined; - - if (!this.props.stores.profile.unitOfAccount.enabled) return undefined; - const currency = this.props.stores.profile.unitOfAccount.currency; - - const shiftedAmount = entry.amount - .shiftedBy(-tokenRow.Metadata.numberOfDecimals); - - const coinPrice = this.props.stores.coinPriceStore.getCurrentPrice( - getTokenName(tokenRow), - currency - ); - if (coinPrice == null) return undefined; - return { - currency, - amount: calculateAndFormatValue(shiftedAmount, coinPrice), - }; - */ - }} - shouldHideBalance={this.props.stores.profile.shouldHideBalance} - isDelegated={ - showRewardAmount - && stakeRegistered - && currentlyDelegating - } - /> - ); - }; -} diff --git a/packages/yoroi-extension/app/containers/wallet/staking/StakingPageContent.js b/packages/yoroi-extension/app/containers/wallet/staking/StakingPageContent.js index 9512e44579..ee237e9855 100644 --- a/packages/yoroi-extension/app/containers/wallet/staking/StakingPageContent.js +++ b/packages/yoroi-extension/app/containers/wallet/staking/StakingPageContent.js @@ -23,7 +23,6 @@ import { generateGraphData } from '../../../utils/graph'; import { calculateAndFormatValue } from '../../../utils/unit-of-account'; import DeregisterDialogContainer from '../../transfer/DeregisterDialogContainer'; import UnmangleTxDialogContainer from '../../transfer/UnmangleTxDialogContainer'; -import WithdrawalTxDialogContainer from '../../transfer/WithdrawalTxDialogContainer'; import WalletEmptyBanner from '../WalletEmptyBanner'; import { GovernanceParticipateDialog } from '../dialogs/GovernanceParticipateDialog'; import CardanoStakingPage from './CardanoStakingPage'; @@ -309,7 +308,6 @@ export default class StakingPageContent extends Component { // note: purposely don't await since the next dialog will properly render the spinner noop(stores.substores.ada.delegationTransaction.createWithdrawalTxForWallet({ wallet })); this.props.stores.uiDialogs.open({ - // dialog: WithdrawalTxDialogContainer, dialog: WithdrawRewardsDialog, }); }} @@ -321,15 +319,6 @@ export default class StakingPageContent extends Component { {uiDialogs.isOpen(UnmangleTxDialogContainer) ? ( ) : null} - {uiDialogs.isOpen(WithdrawalTxDialogContainer) ? ( - { - stores.substores.ada.delegationTransaction.reset({ justTransaction: false }); - this.props.stores.uiDialogs.closeActiveDialog(); - }} - /> - ) : null} {uiDialogs.isOpen(WithdrawRewardsDialog) ? ( true, -}); export const SUMMARY: TopbarCategory = registerCategory({ className: 'summary', route: ROUTES.WALLETS.TRANSACTIONS, From f5982772a57a31189080ee51e5bc0fa966a2fb4d Mon Sep 17 00:00:00 2001 From: yushi Date: Mon, 27 Jan 2025 01:34:18 +0800 Subject: [PATCH 04/32] remove some unnecessary methods --- .../containers/transfer/TransferSendPage.js | 58 +++++++++---------- .../stores/ada/send/AdaMnemonicSendStore.js | 39 ------------- .../app/stores/ada/send/LedgerSendStore.js | 55 ------------------ .../app/stores/ada/send/TrezorSendStore.js | 56 ------------------ 4 files changed, 27 insertions(+), 181 deletions(-) diff --git a/packages/yoroi-extension/app/containers/transfer/TransferSendPage.js b/packages/yoroi-extension/app/containers/transfer/TransferSendPage.js index b054abf360..4038dcf534 100644 --- a/packages/yoroi-extension/app/containers/transfer/TransferSendPage.js +++ b/packages/yoroi-extension/app/containers/transfer/TransferSendPage.js @@ -25,6 +25,8 @@ import { genAddressLookup } from '../../stores/stateless/addressStores'; import { genLookupOrFail } from '../../stores/stateless/tokenHelpers'; import { getNetworkById } from '../../api/ada/lib/storage/database/prepackaged/networks'; import type { StoresProps } from '../../stores'; +import { HaskellShelleyTxSignRequest } from '../../api/ada/transactions/shelley/HaskellShelleyTxSignRequest'; +import { ROUTES } from '../../routes-config'; // populated by ConfigWebpackPlugin declare var CONFIG: ConfigType; @@ -66,49 +68,43 @@ export default class TransferSendPage extends Component<{| ...StoresProps, ...Lo } componentWillUnmount() { - const { stores } = this.props; - stores.wallets.sendMoneyRequest.reset(); this.props.transactionRequest.reset(); - stores.substores.ada.ledgerSend.cancel(); - stores.substores.ada.trezorSend.cancel(); } - submit: void => Promise = async () => { + signAndBroadcast: (?string) => Promise = async (password) => { const { stores } = this.props; - const selected = stores.wallets.selected; - if (selected == null) { + const wallet = stores.wallets.selected; + if (wallet == null) { throw new Error(`${nameof(TransferSendPage)} no wallet selected`); } + const signRequest = this.props.transactionRequest.result; - if (signRequest == null) return; + if (!(signRequest instanceof HaskellShelleyTxSignRequest)) { + throw new Error('Unexpected missing active signing request'); + } + + await this.props.stores.substores.ada.wallets.adaSendAndRefresh({ + wallet, + signRequest, + password, + callback: async () => {}, + }); + if (stores.wallets.sendMoneyRequest.error == null) { + this.props.onSubmit.trigger(); + } + stores.uiDialogs.closeActiveDialog(); + stores.wallets.sendMoneyRequest.reset(); + stores.app.goToRoute({ route: ROUTES.WALLETS.TRANSACTIONS }); + } + + submit: void => Promise = async () => { if (this.spendingPasswordForm == null) { - if (selected.type === 'trezor') { - await stores.substores.ada.trezorSend.sendUsingTrezor({ - params: { signRequest }, - wallet: selected, - }); - } - if (selected.type === 'ledger') { - await stores.substores.ada.ledgerSend.sendUsingLedgerWallet({ - params: { signRequest }, - wallet: selected, - }); - } - if (stores.wallets.sendMoneyRequest.error == null) { - this.props.onSubmit.trigger(); - } + await this.signAndBroadcast(); } else { this.spendingPasswordForm.submit({ onSuccess: async (form) => { const { walletPassword } = form.values(); - await stores.substores.ada.mnemonicSend.sendMoney({ - signRequest, - password: walletPassword, - wallet: selected, - }); - if (stores.wallets.sendMoneyRequest.error == null) { - this.props.onSubmit.trigger(); - } + await this.signAndBroadcast(walletPassword); }, onError: () => {} }); diff --git a/packages/yoroi-extension/app/stores/ada/send/AdaMnemonicSendStore.js b/packages/yoroi-extension/app/stores/ada/send/AdaMnemonicSendStore.js index 47bc1cd043..97510a2fad 100644 --- a/packages/yoroi-extension/app/stores/ada/send/AdaMnemonicSendStore.js +++ b/packages/yoroi-extension/app/stores/ada/send/AdaMnemonicSendStore.js @@ -6,49 +6,10 @@ import { fullErrStr, Logger, } from '../../../utils/logging'; -import { ROUTES } from '../../../routes-config'; -import type { ISignRequest } from '../../../api/common/lib/transactions/ISignRequest'; import type { StoresMap } from '../../index'; import { signAndBroadcastTransaction } from '../../../api/thunk'; export default class AdaMnemonicSendStore extends Store { - - /** Send money and then return to transaction screen */ - sendMoney: {| - signRequest: ISignRequest, - password: string, - +wallet: { - publicDeriverId: number, - +plate: { TextPart: string, ... }, - ... - }, - onSuccess?: void => void, - |} => Promise = async (request) => { - if (!(request.signRequest instanceof HaskellShelleyTxSignRequest)) { - throw new Error(`${nameof(this.sendMoney)} wrong tx sign request`); - } - - const { stores } = this; - await stores.substores.ada.wallets.adaSendAndRefresh({ - broadcastRequest: { - normal: { - wallet: request.wallet, - password: request.password, - signRequest: request.signRequest, - }, - }, - refreshWallet: () => stores.wallets.refreshWalletFromRemote(request.wallet.publicDeriverId), - }); - - this.stores.uiDialogs.closeActiveDialog(); - stores.wallets.sendMoneyRequest.reset(); - if (request.onSuccess) { - request.onSuccess(); - } else { - stores.app.goToRoute({ route: ROUTES.WALLETS.TRANSACTIONS }); - } - }; - signAndBroadcast: {| signRequest: HaskellShelleyTxSignRequest, password: string, diff --git a/packages/yoroi-extension/app/stores/ada/send/LedgerSendStore.js b/packages/yoroi-extension/app/stores/ada/send/LedgerSendStore.js index bee8f7deb9..908f7959a6 100644 --- a/packages/yoroi-extension/app/stores/ada/send/LedgerSendStore.js +++ b/packages/yoroi-extension/app/stores/ada/send/LedgerSendStore.js @@ -18,7 +18,6 @@ import { } from '../../../api/ada/transactions/shelley/ledgerTx'; import { LedgerConnect } from '../../../utils/hwConnectHandler'; -import { ROUTES } from '../../../routes-config'; import { RustModule } from '../../../api/ada/lib/cardanoCrypto/rustLoader'; import { HaskellShelleyTxSignRequest } from '../../../api/ada/transactions/shelley/HaskellShelleyTxSignRequest'; import type { Addressing, } from '../../../api/ada/lib/storage/models/PublicDeriver/interfaces'; @@ -83,60 +82,6 @@ export default class LedgerSendStore extends Store { }) } - sendUsingLedgerWallet: {| - params: SendUsingLedgerParams, - onSuccess?: void => void, - +wallet: { - publicDeriverId: number, - stakingAddressing: Addressing, - publicKey: string, - pathToPublic: Array, - networkId: number, - hardwareWalletDeviceId: ?string, - +plate: { TextPart: string, ... }, - ... - }, - |} => Promise = async (request) => { - try { - if (this.isActionProcessing) { - // this Error will be converted to LocalizableError() - throw new Error('Can’t send another transaction if one transaction is in progress.'); - } - if (!(request.params.signRequest instanceof HaskellShelleyTxSignRequest)) { - throw new Error(`${nameof(this.sendUsingLedgerWallet)} wrong tx sign request`); - } - const { signRequest } = request.params; - - this._setError(null); - this._setActionProcessing(true); - - const { stores } = this; - await stores.substores.ada.wallets.adaSendAndRefresh({ - broadcastRequest: { - ledger: { - signRequest, - wallet: request.wallet, - }, - }, - refreshWallet: () => stores.wallets.refreshWalletFromRemote(request.wallet.publicDeriverId), - }); - - this.stores.uiDialogs.closeActiveDialog(); - stores.wallets.sendMoneyRequest.reset(); - if (request.onSuccess) { - request.onSuccess(); - } else { - stores.app.goToRoute({ route: ROUTES.WALLETS.TRANSACTIONS }); - } - - Logger.info('SUCCESS: ADA sent using Ledger SignTx'); - } catch (e) { - this._setError(e); - } finally { - this._setActionProcessing(false); - } - } - /** Generates a payload with Ledger format and tries Send ADA using Ledger signing */ signAndBroadcastFromWallet: {| signRequest: HaskellShelleyTxSignRequest, diff --git a/packages/yoroi-extension/app/stores/ada/send/TrezorSendStore.js b/packages/yoroi-extension/app/stores/ada/send/TrezorSendStore.js index f68e712dce..e9878e6f99 100644 --- a/packages/yoroi-extension/app/stores/ada/send/TrezorSendStore.js +++ b/packages/yoroi-extension/app/stores/ada/send/TrezorSendStore.js @@ -7,7 +7,6 @@ import { wrapWithFrame } from '../../lib/TrezorWrapper'; import { Logger, stringifyData, stringifyError, } from '../../../utils/logging'; import { convertToLocalizableError } from '../../../domain/TrezorLocalizedError'; import LocalizableError from '../../../i18n/LocalizableError'; -import { ROUTES } from '../../../routes-config'; import { HaskellShelleyTxSignRequest } from '../../../api/ada/transactions/shelley/HaskellShelleyTxSignRequest'; import type { StoresMap } from '../../index'; import { @@ -44,61 +43,6 @@ export default class TrezorSendStore extends Store { this._setError(null); } - sendUsingTrezor: {| - params: SendUsingTrezorParams, - onSuccess?: void => void, - +wallet: { - publicDeriverId: number, - stakingAddressing: Addressing, - publicKey: string, - pathToPublic: Array, - networkId: number, - hardwareWalletDeviceId: ?string, - +plate: { TextPart: string, ... }, - ... - }, - |} => Promise = async (request) => { - try { - if (this.isActionProcessing) { - // this Error will be converted to LocalizableError() - throw new Error('Can’t send another transaction if one transaction is in progress.'); - } - if (!(request.params.signRequest instanceof HaskellShelleyTxSignRequest)) { - throw new Error(`${nameof(this.sendUsingTrezor)} wrong tx sign request`); - } - const { signRequest } = request.params; - - this._setError(null); - this._setActionProcessing(true); - - const { stores } = this; - await stores.substores.ada.wallets.adaSendAndRefresh({ - broadcastRequest: { - trezor: { - signRequest, - wallet: request.wallet, - }, - }, - refreshWallet: () => stores.wallets.refreshWalletFromRemote(request.wallet.publicDeriverId), - }); - - this.stores.uiDialogs.closeActiveDialog(); - stores.wallets.sendMoneyRequest.reset(); - if (request.onSuccess) { - request.onSuccess(); - } else { - stores.app.goToRoute({ route: ROUTES.WALLETS.TRANSACTIONS }); - } - this.reset(); - - Logger.info('SUCCESS: ADA sent using Trezor SignTx'); - } catch (e) { - this._setError(e); - } finally { - this._setActionProcessing(false); - } - } - signAndBroadcastFromWallet: {| signRequest: HaskellShelleyTxSignRequest, +wallet: { From ca3b0385fb126aeaec2e72f7af2db651c42681d1 Mon Sep 17 00:00:00 2001 From: yushi Date: Wed, 29 Jan 2025 02:41:35 +0800 Subject: [PATCH 05/32] deduplicate ledger transaction signing code --- packages/yoroi-extension/app/api/ada/index.js | 36 +- .../api/ada/transactions/shelley/ledgerTx.js | 413 +++--------------- .../ada/transactions/shelley/ledgerTx.test.js | 56 +-- .../app/connector/stores/ConnectorStore.js | 2 +- .../transfer/UpgradeTxDialogContainer.js | 36 +- .../app/stores/ada/AdaWalletsStore.js | 3 +- .../app/stores/ada/LedgerConnectStore.js | 1 + .../app/stores/ada/send/LedgerSendStore.js | 267 ++++------- 8 files changed, 195 insertions(+), 619 deletions(-) diff --git a/packages/yoroi-extension/app/api/ada/index.js b/packages/yoroi-extension/app/api/ada/index.js index 30e293a58a..c032548dc3 100644 --- a/packages/yoroi-extension/app/api/ada/index.js +++ b/packages/yoroi-extension/app/api/ada/index.js @@ -83,7 +83,7 @@ import { buildCoseSign1FromSignature, cip8Sign, makeCip8Key, v4PublicToV2 } from import { isValidBip39Mnemonic, } from './lib/cardanoCrypto/wallet'; import type { CardanoSignTransaction } from 'trezor-connect-flow'; import { createTrezorSignTxPayload, toTrezorSignRequest, } from './transactions/shelley/trezorTx'; -import { createLedgerSignTxPayload, toLedgerSignRequest, } from './transactions/shelley/ledgerTx'; +import { toLedgerSignRequest, } from './transactions/shelley/ledgerTx'; import { GenericApiError, IncorrectWalletPasswordError, @@ -256,6 +256,8 @@ export type CreateHWSignTxDataRequestFromRawTx = {| addressingMap: string => (void | $PropertyType), senderUtxos: Array, additionalRequiredSigners?: Array, + ledgerSupportsCip36?: boolean, + catalystData?: LedgerNanoCatalystRegistrationTxSignData, |}; // createUnsignedTx @@ -773,36 +775,6 @@ export default class AdaApi { } } - createLedgerSignTxData( - request: CreateLedgerSignTxDataRequest - ): CreateLedgerSignTxDataResponse { - try { - Logger.debug(`${nameof(AdaApi)}::${nameof(this.createLedgerSignTxData)} called`); - - const config = getCardanoHaskellBaseConfig( - request.network - ).reduce((acc, next) => Object.assign(acc, next), {}); - - const ledgerSignTxPayload = createLedgerSignTxPayload({ - signRequest: request.signRequest, - byronNetworkMagic: config.ByronNetworkId, - networkId: Number.parseInt(config.ChainNetworkId, 10), - addressingMap: request.addressingMap, - cip36: request.cip36, - }); - - Logger.debug(`${nameof(AdaApi)}::${nameof(this.createLedgerSignTxData)} success: ` + stringifyData(ledgerSignTxPayload)); - return { - ledgerSignTxPayload - }; - } catch (error) { - Logger.error(`${nameof(AdaApi)}::${nameof(this.createLedgerSignTxData)} error: ` + stringifyError(error)); - - if (error instanceof LocalizableError) throw error; - throw new GenericApiError(); - } - } - createHwSignTxDataFromRawTx( hw: 'ledger' | 'trezor', request: CreateHWSignTxDataRequestFromRawTx @@ -827,6 +799,8 @@ export default class AdaApi { addressMap, request.senderUtxos, request.additionalRequiredSigners ?? [], + request.ledgerSupportsCip36, + request.catalystData, ); Logger.debug(`${nameof(AdaApi)}::${nameof(this.createHwSignTxDataFromRawTx)} success: ` + stringifyData(ledgerSignTxPayload)); diff --git a/packages/yoroi-extension/app/api/ada/transactions/shelley/ledgerTx.js b/packages/yoroi-extension/app/api/ada/transactions/shelley/ledgerTx.js index 4a852bc165..089d6fac08 100644 --- a/packages/yoroi-extension/app/api/ada/transactions/shelley/ledgerTx.js +++ b/packages/yoroi-extension/app/api/ada/transactions/shelley/ledgerTx.js @@ -30,7 +30,7 @@ import { TxRequiredSignerType, } from '@cardano-foundation/ledgerjs-hw-app-cardano'; import type { Address, Addressing, Value, } from '../../lib/storage/models/PublicDeriver/interfaces'; -import { HaskellShelleyTxSignRequest } from './HaskellShelleyTxSignRequest'; +import type { LedgerNanoCatalystRegistrationTxSignData } from './HaskellShelleyTxSignRequest'; import { RustModule } from '../../lib/cardanoCrypto/rustLoader'; import { toHexOrBase58 } from '../../lib/storage/bridge/utils'; import { Bip44DerivationLevels, } from '../../lib/storage/database/walletTypes/bip44/api/utils'; @@ -46,173 +46,9 @@ import { maybe } from '../../../../coreUtils'; import { mergeWitnessSets } from '../utils'; +import { transactionHexToHash } from '../../lib/cardanoCrypto/utils'; // ==================== LEDGER ==================== // -/** Generate a payload for Ledger SignTx */ -export function createLedgerSignTxPayload(request: {| - signRequest: HaskellShelleyTxSignRequest, - byronNetworkMagic: number, - networkId: number, - addressingMap: string => (void | $PropertyType), - cip36: boolean, -|}): SignTransactionRequest { - - const tx = request.signRequest.unsignedTx.build_tx(); - const txBody = tx.body(); - - const tagsState = RustModule.WasmScope(Module => - Module.WalletV4.has_transaction_set_tag(tx.to_bytes())); - - if (tagsState === RustModule.WalletV4.TransactionSetsState.MixedSets) { - throw new Error('Transaction with mixed sets cannot be signed by Ledger'); - } - - const txHasSetTags = tagsState === RustModule.WalletV4.TransactionSetsState.AllSetsHaveTag; - - // Inputs - const ledgerInputs = _transformToLedgerInputs( - request.signRequest.senderUtxos - ); - - // Output - const ledgerOutputs = _transformToLedgerOutputs({ - networkId: request.networkId, - txOutputs: txBody.outputs(), - changeAddrs: request.signRequest.changeAddr, - addressingMap: request.addressingMap, - }); - - // withdrawals - const withdrawals = txBody.withdrawals(); - - const certificates = txBody.certs(); - - const ledgerWithdrawal = []; - if (withdrawals != null && withdrawals.len() > 0) { - ledgerWithdrawal.push(...formatLedgerWithdrawals( - withdrawals, - request.addressingMap, - )); - } - - const ledgerCertificates = []; - if (certificates != null && certificates.len() > 0) { - ledgerCertificates.push(...formatLedgerCertificates( - request.networkId, - certificates, - request.addressingMap, - )); - } - - let auxiliaryData = undefined; - if (request.signRequest.ledgerNanoCatalystRegistrationTxSignData) { - const { votingPublicKey, stakingKeyPath, nonce, paymentKeyPath, } = - request.signRequest.ledgerNanoCatalystRegistrationTxSignData; - - if (request.cip36) { - auxiliaryData = { - type: TxAuxiliaryDataType.CIP36_REGISTRATION, - params: { - format: CIP36VoteRegistrationFormat.CIP_36, - delegations: [ - { - type: CIP36VoteDelegationType.KEY, - voteKeyHex: votingPublicKey.replace(/^0x/, ''), - weight: 1, - }, - ], - stakingPath: stakingKeyPath, - paymentDestination: { - type: TxOutputDestinationType.DEVICE_OWNED, - params: { - type: AddressType.BASE_PAYMENT_KEY_STAKE_KEY, - params: { - spendingPath: paymentKeyPath, - stakingPath: stakingKeyPath, - }, - }, - }, - nonce, - votingPurpose: 0, - } - }; - } else { - auxiliaryData = { - type: TxAuxiliaryDataType.CIP36_REGISTRATION, - params: { - format: CIP36VoteRegistrationFormat.CIP_15, - voteKeyHex: votingPublicKey.replace(/^0x/, ''), - stakingPath: stakingKeyPath, - paymentDestination: { - type: TxOutputDestinationType.DEVICE_OWNED, - params: { - type: AddressType.BASE_PAYMENT_KEY_STAKE_KEY, - params: { - spendingPath: paymentKeyPath, - stakingPath: stakingKeyPath, - }, - }, - }, - nonce, - } - }; - } - } else if (request.signRequest.metadata != null) { - auxiliaryData = { - type: TxAuxiliaryDataType.ARBITRARY_HASH, - params: { - hashHex: RustModule.WalletV4.hash_auxiliary_data(request.signRequest.metadata).to_hex(), - }, - }; - } - - return { - signingMode: TransactionSigningMode.ORDINARY_TRANSACTION, - tx: { - inputs: ledgerInputs, - outputs: ledgerOutputs, - ttl: txBody.ttl_bignum()?.to_str() ?? null, - validityIntervalStart: txBody.validity_start_interval_bignum()?.to_str() ?? null, - fee: txBody.fee().to_str(), - network: { - networkId: request.networkId, - protocolMagic: request.byronNetworkMagic, - }, - withdrawals: ledgerWithdrawal.length === 0 ? null : ledgerWithdrawal, - certificates: ledgerCertificates.length === 0 ? null : ledgerCertificates, - auxiliaryData, - scriptDataHashHex: txBody.script_data_hash()?.to_hex() ?? null, - }, - additionalWitnessPaths: [], - options: { - tagCborSets: txHasSetTags, - } - }; -} - -/** - * Canonical inputs sorting: by tx hash and then by index - */ -function compareInputs(a: TxInput, b: TxInput): number { - if (a.txHashHex !== b.txHashHex) { - return a.txHashHex < b.txHashHex ? -1 : 1; - } - return a.outputIndex - b.outputIndex; -} - -function _transformToLedgerInputs( - inputs: Array -): Array { - for (const input of inputs) { - verifyFromDerivationRoot(input.addressing); - } - return inputs.map(input => ({ - txHashHex: input.tx_hash, - outputIndex: input.tx_index, - path: input.addressing.path, - })).sort(compareInputs); -} - function toLedgerTokenBundle( assets: ?RustModule.WalletV4.MultiAsset ): Array | null { @@ -266,54 +102,6 @@ function compareCborKey(hex1: string, hex2: string): number { return 0; } -function _transformToLedgerOutputs(request: {| - networkId: number, - txOutputs: RustModule.WalletV4.TransactionOutputs, - changeAddrs: Array<{| ...Address, ...Value, ...Addressing |}>, - addressingMap: string => (void | $PropertyType), -|}): Array { - const result = []; - - // support post-alonzo map - - for (const output of iterateLenGet(request.txOutputs)) { - const address = output.address(); - const jsAddr = toHexOrBase58(address); - const datumHashHex = output.data_hash()?.to_hex() ?? null; - - const changeAddr = request.changeAddrs.find(change => jsAddr === change.address); - if (changeAddr != null) { - verifyFromDerivationRoot(changeAddr.addressing); - const addressParams = toLedgerAddressParameters({ - networkId: request.networkId, - address, - path: changeAddr.addressing.path, - addressingMap: request.addressingMap, - }); - result.push({ - amount: output.amount().coin().to_str(), - tokenBundle: toLedgerTokenBundle(output.amount().multiasset()), - destination: { - type: TxOutputDestinationType.DEVICE_OWNED, - params: addressParams, - }, - datumHashHex, - }); - } else { - result.push({ - amount: output.amount().coin().to_str(), - tokenBundle: toLedgerTokenBundle(output.amount().multiasset()), - destination: { - type: TxOutputDestinationType.THIRD_PARTY, - params: { addressHex: address.to_hex() }, - }, - datumHashHex, - }); - } - } - return result; -} - function formatLedgerWithdrawals( withdrawals: RustModule.WalletV4.Withdrawals, addressingMap: string => (void | { +path: Array, ... }), @@ -667,135 +455,6 @@ export function toLedgerAddressParameters(request: {| throw new Error(`${nameof(toLedgerAddressParameters)} unknown address type`); } -export function buildSignedTransaction( - tx: RustModule.WalletV4.Transaction, - senderUtxos: Array, - witnesses: Array, - publicKey: {| - ...Addressing, - key: RustModule.WalletV4.Bip32PublicKey, - |}, - metadata: RustModule.WalletV4.AuxiliaryData | void -): RustModule.WalletV4.Transaction { - const isSameArray = (array1: Array, array2: Array) => ( - array1.length === array2.length && array1.every((value, index) => value === array2[index]) - ); - const findWitness = (path: Array) => { - for (const witness of witnesses) { - if (isSameArray(witness.path, path)) { - return witness.witnessSignatureHex; - } - } - throw new Error(`${nameof(buildSignedTransaction)} no witness for ${JSON.stringify(path)}`); - }; - - const keyLevel = publicKey.addressing.startLevel + publicKey.addressing.path.length - 1; - - const witSet = RustModule.WalletV4.TransactionWitnessSet.new(); - const bootstrapWitnesses: Array = []; - const vkeys: Array = []; - - // Note: Ledger removes duplicate witnesses - // but there may be a one-to-many relationship - // ex: same witness is used in both a bootstrap witness and a vkey witness - const seenVKeyWit = new Set(); - const seenBootstrapWit = new Set(); - - for (const utxo of senderUtxos) { - verifyFromDerivationRoot(utxo.addressing); - - const witness = findWitness(utxo.addressing.path); - const addressKey = derivePublicByAddressing({ - addressing: utxo.addressing, - startingFrom: { - level: keyLevel, - key: publicKey.key, - } - }); - - if (RustModule.WalletV4.ByronAddress.is_valid(utxo.receiver)) { - - const byronAddr = RustModule.WalletV4.ByronAddress.from_base58(utxo.receiver); - const bootstrapWit = RustModule.WalletV4.BootstrapWitness.new( - RustModule.WalletV4.Vkey.new(addressKey.to_raw_key()), - RustModule.WalletV4.Ed25519Signature.from_hex(witness), - addressKey.chaincode(), - byronAddr.attributes(), - ); - const asString = bootstrapWit.to_hex(); - if (seenBootstrapWit.has(asString)) { - continue; - } - seenBootstrapWit.add(asString); - bootstrapWitnesses.push(bootstrapWit); - continue; - } - - const vkeyWit = RustModule.WalletV4.Vkeywitness.new( - RustModule.WalletV4.Vkey.new(addressKey.to_raw_key()), - RustModule.WalletV4.Ed25519Signature.from_hex(witness), - ); - const asString = vkeyWit.to_hex(); - if (seenVKeyWit.has(asString)) { - continue; - } - seenVKeyWit.add(asString); - vkeys.push(vkeyWit); - } - - // add any staking key needed - for (const witness of witnesses) { - const addressing = { - path: witness.path, - startLevel: 1, - }; - verifyFromDerivationRoot(addressing); - if (witness.path[Bip44DerivationLevels.CHAIN.level - 1] === ChainDerivations.CHIMERIC_ACCOUNT) { - const stakingKey = derivePublicByAddressing({ - addressing, - startingFrom: { - level: keyLevel, - key: publicKey.key, - } - }); - const vkeyWit = RustModule.WalletV4.Vkeywitness.new( - RustModule.WalletV4.Vkey.new(stakingKey.to_raw_key()), - RustModule.WalletV4.Ed25519Signature.from_hex(witness.witnessSignatureHex), - ); - const asString = vkeyWit.to_hex(); - if (seenVKeyWit.has(asString)) { - continue; - } - seenVKeyWit.add(asString); - vkeys.push(vkeyWit); - } - } - if (bootstrapWitnesses.length > 0) { - const bootstrapWitWasm = RustModule.WalletV4.BootstrapWitnesses.new(); - for (const bootstrapWit of bootstrapWitnesses) { - bootstrapWitWasm.add(bootstrapWit); - } - witSet.set_bootstraps(bootstrapWitWasm); - } - if (vkeys.length > 0) { - const vkeyWitWasm = RustModule.WalletV4.Vkeywitnesses.new(); - for (const vkey of vkeys) { - vkeyWitWasm.add(vkey); - } - witSet.set_vkeys(vkeyWitWasm); - } - - const mergedWitnessSet = RustModule.WalletV4.TransactionWitnessSet.from_hex( - mergeWitnessSets(tx.witness_set().to_hex(), witSet.to_hex()) - ); - - return RustModule.WalletV4.Transaction.new( - tx.body(), - mergedWitnessSet, - metadata - ); -} - type AddressMap = (addressHex: string) => ?Array; // Convert connector sign tx input into request to Ledger. @@ -809,6 +468,8 @@ export function toLedgerSignRequest( ownAddressMap: AddressMap, senderUtxos: Array, additionalRequiredSigners: Array = [], + ledgerSupportsCip36?: boolean, + catalystData?: LedgerNanoCatalystRegistrationTxSignData, ): SignTransactionRequest { const tagsState = RustModule.WasmScope(Module => Module.WalletV4.has_transaction_set_tag( @@ -1063,7 +724,6 @@ export function toLedgerSignRequest( ); } - // TODO: support CIP36 aux data let formattedAuxiliaryData = null; const auxiliaryDataHash = txBody.auxiliary_data_hash(); if (auxiliaryDataHash) { @@ -1075,6 +735,62 @@ export function toLedgerSignRequest( }; } + if (catalystData) { + if (formattedAuxiliaryData) { + throw new Error('unexpected catalyst signing data'); + } + const { votingPublicKey, stakingKeyPath, nonce, paymentKeyPath, } = catalystData; + + if (ledgerSupportsCip36) { + formattedAuxiliaryData = { + type: TxAuxiliaryDataType.CIP36_REGISTRATION, + params: { + format: CIP36VoteRegistrationFormat.CIP_36, + delegations: [ + { + type: CIP36VoteDelegationType.KEY, + voteKeyHex: votingPublicKey.replace(/^0x/, ''), + weight: 1, + }, + ], + stakingPath: stakingKeyPath, + paymentDestination: { + type: TxOutputDestinationType.DEVICE_OWNED, + params: { + type: AddressType.BASE_PAYMENT_KEY_STAKE_KEY, + params: { + spendingPath: paymentKeyPath, + stakingPath: stakingKeyPath, + }, + }, + }, + nonce, + votingPurpose: 0, + } + }; + } else { + formattedAuxiliaryData = { + type: TxAuxiliaryDataType.CIP36_REGISTRATION, + params: { + format: CIP36VoteRegistrationFormat.CIP_15, + voteKeyHex: votingPublicKey.replace(/^0x/, ''), + stakingPath: stakingKeyPath, + paymentDestination: { + type: TxOutputDestinationType.DEVICE_OWNED, + params: { + type: AddressType.BASE_PAYMENT_KEY_STAKE_KEY, + params: { + spendingPath: paymentKeyPath, + stakingPath: stakingKeyPath, + }, + }, + }, + nonce, + } + }; + } + } + let formattedCollateral = null; const collateral = txBody.collateral(); if (collateral) { @@ -1138,9 +854,13 @@ export function buildConnectorSignedTransaction( ...Addressing, key: RustModule.WalletV4.Bip32PublicKey, |}, -): string { + metadata: ?RustModule.WalletV4.AuxiliaryData, +): {| txHex: string, txId: string |} { const fixedTx = RustModule.WalletV4.FixedTransaction.from_hex(rawTxHex); + if (metadata) { + fixedTx.set_auxiliary_data(metadata.to_bytes()); + } const keyLevel = publicKey.addressing.startLevel + publicKey.addressing.path.length - 1; for (const witness of witnesses) { @@ -1165,5 +885,6 @@ export function buildConnectorSignedTransaction( fixedTx.add_vkey_witness(vkeyWit); } - return fixedTx.to_hex(); + const txHex = fixedTx.to_hex(); + return { txHex, txId: transactionHexToHash(txHex)}; } diff --git a/packages/yoroi-extension/app/api/ada/transactions/shelley/ledgerTx.test.js b/packages/yoroi-extension/app/api/ada/transactions/shelley/ledgerTx.test.js index e70c1d3765..c37077f296 100644 --- a/packages/yoroi-extension/app/api/ada/transactions/shelley/ledgerTx.test.js +++ b/packages/yoroi-extension/app/api/ada/transactions/shelley/ledgerTx.test.js @@ -4,8 +4,8 @@ import '../../lib/test-config.forTests'; import BigNumber from 'bignumber.js'; import { RustModule } from '../../lib/cardanoCrypto/rustLoader'; import { - createLedgerSignTxPayload, - buildSignedTransaction, + toLedgerSignRequest, + buildConnectorSignedTransaction, toLedgerAddressParameters, } from './ledgerTx'; import { @@ -20,6 +20,7 @@ import { TransactionSigningMode, TxOutputDestinationType, CredentialParamsType, + TxOutputFormat, } from '@cardano-foundation/ledgerjs-hw-app-cardano'; import type { DeviceOwnedAddress, SignTransactionRequest } from '@cardano-foundation/ledgerjs-hw-app-cardano'; import { networks } from '../../lib/storage/database/prepackaged/networks'; @@ -260,7 +261,7 @@ test('Create Ledger transaction', async () => { RustModule.WalletV4.ByronAddress.from_base58(utxo.receiver), RustModule.WalletV4.TransactionInput.new( RustModule.WalletV4.TransactionHash.from_hex(utxo.tx_hash), - 1 + utxo.tx_index, ), RustModule.WalletV4.Value.new(RustModule.WalletV4.BigNum.from_str(utxo.amount)) ); @@ -269,7 +270,7 @@ test('Create Ledger transaction', async () => { keyHash, RustModule.WalletV4.TransactionInput.new( RustModule.WalletV4.TransactionHash.from_hex(utxo.tx_hash), - 1 + utxo.tx_index, ), RustModule.WalletV4.Value.new(RustModule.WalletV4.BigNum.from_str(utxo.amount)) ); @@ -316,39 +317,23 @@ test('Create Ledger transaction', async () => { ], }, }; - const signRequest = new HaskellShelleyTxSignRequest({ - unsignedTx: txBuilder, - changeAddr: [], - senderUtxos, - metadata: undefined, - networkSettingSnapshot: { - ChainNetworkId: Number.parseInt(baseConfig.ChainNetworkId, 10), - PoolDeposit: new BigNumber(baseConfig.PoolDeposit), - KeyDeposit: new BigNumber(baseConfig.KeyDeposit), - NetworkId: network.NetworkId, - }, - neededStakingKeyHashes: { - neededHashes: new Set([stakeCredential.to_hex()]), - wits: new Set() // not needed for this test, but something should be here - }, - }); const rewardAddressString = RustModule.WalletV4.RewardAddress.new( Number.parseInt(baseConfig.ChainNetworkId, 10), stakeCredential ).to_address().to_hex(); - const response = await createLedgerSignTxPayload({ - signRequest, - byronNetworkMagic: ByronNetworkId, - networkId: Number.parseInt(ChainNetworkId, 10), - addressingMap: (address) => { + const response = await toLedgerSignRequest( + txBuilder.build().to_hex(), + Number.parseInt(ChainNetworkId, 10), + ByronNetworkId, + (address) => { if (address === rewardAddressString) { - return stakingKeyInfo.addressing; + return stakingKeyInfo.addressing.path; } return undefined; }, - cip36: true, - }); + senderUtxos, + ); expect(response).toStrictEqual(({ options: { @@ -405,6 +390,7 @@ test('Create Ledger transaction', async () => { outputIndex: 1, }], outputs: [{ + format: TxOutputFormat.ARRAY_LEGACY, destination: { params: { addressHex: '82d818582183581c891ac9abaac999b097c81ea3c0450b0fbb693d0bd232bebc0f4a391fa0001af2ff7e21', @@ -431,15 +417,21 @@ test('Create Ledger transaction', async () => { }, type: CertificateType.STAKE_REGISTRATION, }], - auxiliaryData: undefined, + auxiliaryData: null, validityIntervalStart: null, + collateralInputs: null, + collateralOutput: undefined, + includeNetworkId: false, + mint: null, + referenceInputs: null, + requiredSigners: null, + totalCollateral: null, }, additionalWitnessPaths: [], }: SignTransactionRequest)); - buildSignedTransaction( - txBuilder.build_tx(), - signRequest.senderUtxos, + buildConnectorSignedTransaction( + txBuilder.build_tx().to_hex(), [ // this witnesses doesn't belong to the transaction / key. Just used to test wit generation { diff --git a/packages/yoroi-extension/app/connector/stores/ConnectorStore.js b/packages/yoroi-extension/app/connector/stores/ConnectorStore.js index e70731630a..ed46f5b616 100644 --- a/packages/yoroi-extension/app/connector/stores/ConnectorStore.js +++ b/packages/yoroi-extension/app/connector/stores/ConnectorStore.js @@ -1171,7 +1171,7 @@ export default class ConnectorStore extends Store { rawTxHex, ledgerSignResult.witnesses, publicKeyInfo, - ); + ).txHex; } /** diff --git a/packages/yoroi-extension/app/containers/transfer/UpgradeTxDialogContainer.js b/packages/yoroi-extension/app/containers/transfer/UpgradeTxDialogContainer.js index 317aca5465..c4a1d067f4 100644 --- a/packages/yoroi-extension/app/containers/transfer/UpgradeTxDialogContainer.js +++ b/packages/yoroi-extension/app/containers/transfer/UpgradeTxDialogContainer.js @@ -45,23 +45,6 @@ export default class UpgradeTxDialogContainer extends Component<{| ...StoresProp intl: intlShape.isRequired, }; - submit: {| - signRequest: HaskellShelleyTxSignRequest, - publicKey: {| - key: RustModule.WalletV4.Bip32PublicKey, - ...Addressing, - |}, - publicDeriverId: number, - addressingMap: string => (void | $PropertyType), - expectedSerial: string | void, - networkId: number, - |} => Promise = async (request) => { - await this.props.stores.substores.ada.ledgerSend.sendUsingLedgerKey({ - ...request, - }); - this.props.onSubmit(); - } - render(): Node { const { transferRequest } = this.props.stores.substores.ada.yoroiTransfer; @@ -156,8 +139,6 @@ export default class UpgradeTxDialogContainer extends Component<{| ...StoresProp ); - const expectedSerial = selected.hardwareWalletDeviceId || ''; - return ( await this.submit({ - publicDeriverId: selected.publicDeriverId, - addressingMap: genAddressingLookup( - selected.networkId, - this.props.stores.addresses.addressSubgroupMap - ), - ...tentativeTx, - expectedSerial, - networkId: selected.networkId, - }), + trigger: async () => { + await this.props.stores.substores.ada.ledgerSend.signAndBroadcastFromWallet({ + signRequest: tentativeTx.signRequest, + wallet: selected, + }); + this.props.onSubmit(); + }, label: intl.formatMessage(globalMessages.upgradeLabel), }} isSubmitting={this.props.stores.wallets.sendMoneyRequest.isExecuting} diff --git a/packages/yoroi-extension/app/stores/ada/AdaWalletsStore.js b/packages/yoroi-extension/app/stores/ada/AdaWalletsStore.js index 0c88b46ff3..27d209729d 100644 --- a/packages/yoroi-extension/app/stores/ada/AdaWalletsStore.js +++ b/packages/yoroi-extension/app/stores/ada/AdaWalletsStore.js @@ -146,8 +146,9 @@ export default class AdaWalletsStore extends Store { |}) => Promise<{| signedTxHex: string |}> = async request => { if (request.signRequest.ledger) { const { wallet, transactionHex } = request.signRequest.ledger; - return this.stores.substores.ada.ledgerSend + const { signedTxHex } = await this.stores.substores.ada.ledgerSend .signRawTxFromWallet({ rawTxHex: transactionHex, wallet }); + return { signedTxHex }; } if (request.signRequest.trezor) { const { wallet, transactionHex } = request.signRequest.trezor; diff --git a/packages/yoroi-extension/app/stores/ada/LedgerConnectStore.js b/packages/yoroi-extension/app/stores/ada/LedgerConnectStore.js index 51cd84f75b..28dc45c6b9 100644 --- a/packages/yoroi-extension/app/stores/ada/LedgerConnectStore.js +++ b/packages/yoroi-extension/app/stores/ada/LedgerConnectStore.js @@ -220,6 +220,7 @@ export default class LedgerConnectStore } _checkAndStoreHWDeviceInfo: void => Promise = async () => { + debugger this.stores.substores.ada.yoroiTransfer.transferRequest.reset(); const accountPath = this.getPath(); try { diff --git a/packages/yoroi-extension/app/stores/ada/send/LedgerSendStore.js b/packages/yoroi-extension/app/stores/ada/send/LedgerSendStore.js index 908f7959a6..a7c3880bb3 100644 --- a/packages/yoroi-extension/app/stores/ada/send/LedgerSendStore.js +++ b/packages/yoroi-extension/app/stores/ada/send/LedgerSendStore.js @@ -12,14 +12,14 @@ import { convertToLocalizableError } from '../../../domain/LedgerLocalizedError' import { Logger, stringifyData, stringifyError, } from '../../../utils/logging'; -import { - buildConnectorSignedTransaction, - buildSignedTransaction, -} from '../../../api/ada/transactions/shelley/ledgerTx'; +import { buildConnectorSignedTransaction } from '../../../api/ada/transactions/shelley/ledgerTx'; import { LedgerConnect } from '../../../utils/hwConnectHandler'; import { RustModule } from '../../../api/ada/lib/cardanoCrypto/rustLoader'; -import { HaskellShelleyTxSignRequest } from '../../../api/ada/transactions/shelley/HaskellShelleyTxSignRequest'; +import type { + HaskellShelleyTxSignRequest, + LedgerNanoCatalystRegistrationTxSignData, +} from '../../../api/ada/transactions/shelley/HaskellShelleyTxSignRequest'; import type { Addressing, } from '../../../api/ada/lib/storage/models/PublicDeriver/interfaces'; import { genAddressingLookup } from '../../stateless/addressStores'; import type { StoresMap } from '../../index'; @@ -29,7 +29,7 @@ import { } from '../../../api/ada/lib/cardanoCrypto/catalyst'; import { getNetworkById } from '../../../api/ada/lib/storage/database/prepackaged/networks.js'; import { broadcastTransaction } from '../../../api/thunk'; -import { transactionHexToBodyHex, transactionHexToHash } from '../../../api/ada/lib/cardanoCrypto/utils'; +import { transactionHexToBodyHex } from '../../../api/ada/lib/cardanoCrypto/utils'; import { fail } from '../../../coreUtils'; import type { ISignRequest } from '../../../api/common/lib/transactions/ISignRequest'; @@ -63,25 +63,6 @@ export default class LedgerSendStore extends Store { } } - sendUsingLedgerKey: {| - signRequest: HaskellShelleyTxSignRequest, - publicKey: {| - key: RustModule.WalletV4.Bip32PublicKey, - ...Addressing, - |}, - publicDeriverId: number, - addressingMap: string => (void | $PropertyType), - expectedSerial: string | void, - networkId: number, - |} => Promise = async (request) => { - await this.stores.wallets.sendAndRefresh({ - publicDeriverId: undefined, - plateTextPart: undefined, - broadcastRequest: async () => await this.signAndBroadcast(request), - refreshWallet: async () => {} - }) - } - /** Generates a payload with Ledger format and tries Send ADA using Ledger signing */ signAndBroadcastFromWallet: {| signRequest: HaskellShelleyTxSignRequest, @@ -97,6 +78,47 @@ export default class LedgerSendStore extends Store { try { Logger.debug(`${nameof(LedgerSendStore)}::${nameof(this.signAndBroadcastFromWallet)} called: ` + stringifyData(request)); + const { signedTxHex, txId, metadata } = await this.signRawTxFromWallet({ + rawTxHex: request.signRequest.self().build_tx().to_hex(), + wallet: request.wallet, + catalystData: request.signRequest.ledgerNanoCatalystRegistrationTxSignData + }); + + if (metadata) { + request.signRequest.self().set_auxiliary_data(metadata); + } + + await broadcastTransaction({ + publicDeriverId: request.wallet.publicDeriverId, + signedTxHex, + }); + + return { txId }; + } catch (error) { + Logger.error(`${nameof(LedgerSendStore)}::${nameof(this.signAndBroadcastFromWallet)} error: ` + stringifyError(error)); + throw new convertToLocalizableError(error); + } + }; + + signRawTxFromWallet: {| + rawTxHex: string, + +wallet: { + publicDeriverId: number, + publicKey: string, + pathToPublic: Array, + networkId: number, + hardwareWalletDeviceId: ?string, + ... + }, + catalystData?: LedgerNanoCatalystRegistrationTxSignData, + |} => Promise<{| + signedTxHex: string, + txId: string, + metadata: ?RustModule.WalletV4.AuxiliaryData + |}> = async (request) => { + try { + Logger.debug(`${nameof(LedgerSendStore)}::${nameof(this.signRawTxFromWallet)} called: ` + stringifyData(request)); + const publicKeyInfo = { key: RustModule.WalletV4.Bip32PublicKey.from_hex(request.wallet.publicKey), addressing: { @@ -107,65 +129,79 @@ export default class LedgerSendStore extends Store { const expectedSerial = request.wallet.hardwareWalletDeviceId || ''; - const signRequest = request.signRequest; - const addressingMap = genAddressingLookup( request.wallet.networkId, - this.stores.addresses.addressSubgroupMap + this.stores.addresses.addressSubgroupMap, ); - return this.signAndBroadcast({ - signRequest, + return this.signRawTx({ + rawTxHex: request.rawTxHex, publicKey: publicKeyInfo, - publicDeriverId: request.wallet.publicDeriverId, addressingMap, expectedSerial, networkId: request.wallet.networkId, + catalystData: request.catalystData, }); } catch (error) { - Logger.error(`${nameof(LedgerSendStore)}::${nameof(this.signAndBroadcastFromWallet)} error: ` + stringifyError(error)); + Logger.error(`${nameof(LedgerSendStore)}::${nameof(this.signRawTxFromWallet)} error: ` + stringifyError(error)); throw new convertToLocalizableError(error); } - }; + } - signAndBroadcast: {| - signRequest: HaskellShelleyTxSignRequest, + signRawTx: {| + rawTxHex: string, publicKey: {| key: RustModule.WalletV4.Bip32PublicKey, ...Addressing, |}, addressingMap: string => (void | $PropertyType), - publicDeriverId: number, networkId: number, expectedSerial: string | void, - |} => Promise<{| txId: string |}> = async (request) => { + catalystData?: LedgerNanoCatalystRegistrationTxSignData, + |} => Promise<{| + signedTxHex: string, + txId: string, + metadata: ?RustModule.WalletV4.AuxiliaryData + |}> = async (request) => { + let ledgerConnect: ?LedgerConnect; try { - Logger.debug(`${nameof(LedgerSendStore)}::${nameof(this.signAndBroadcast)} called: ` + stringifyData(request)); + Logger.debug(`${nameof(LedgerSendStore)}::${nameof(this.signRawTx)} called: ` + stringifyData(request)); ledgerConnect = new LedgerConnect({ locale: this.stores.profile.currentLocale, }); - let cip36: boolean = false; - if (request.signRequest.ledgerNanoCatalystRegistrationTxSignData) { + let ledgerSupportsCip36: boolean = false; + if (request.catalystData) { const getVersionResponse = await ledgerConnect.getVersion({ serial: request.expectedSerial, dontCloseTab: true, }); - cip36 = getVersionResponse.compatibility.supportsCIP36Vote === true; + ledgerSupportsCip36 = getVersionResponse.compatibility.supportsCIP36Vote === true; } + const { rawTxHex } = request; + const network = getNetworkById(request.networkId); - const { ledgerSignTxPayload } = this.api.ada.createLedgerSignTxData({ - signRequest: request.signRequest, + const txBodyHex = transactionHexToBodyHex(rawTxHex); + + const addressedUtxos = await this.stores.wallets.getAddressedUtxos(); + + const response = this.api.ada.createHwSignTxDataFromRawTx('ledger', { + txBodyHex, network, addressingMap: request.addressingMap, - cip36, + senderUtxos: addressedUtxos, + ledgerSupportsCip36, + catalystData: request.catalystData, }); + const ledgerSignTxPayload = response.hw === 'ledger' ? response.result.ledgerSignTxPayload + : fail('Unecpected response type from `createHwSignTxDataFromRawTx` for ledger: ' + JSON.stringify(response)); + const ledgerSignTxResp: LedgerSignTxResponse = await ledgerConnect.signTransaction({ serial: request.expectedSerial, @@ -180,26 +216,25 @@ export default class LedgerSendStore extends Store { ledgerConnect.dispose(); let metadata; - - if (request.signRequest.ledgerNanoCatalystRegistrationTxSignData) { + if (request.catalystData) { const { votingPublicKey, stakingKey, paymentAddress, nonce, - } = request.signRequest.ledgerNanoCatalystRegistrationTxSignData; + } = request.catalystData; if ( !ledgerSignTxResp.auxiliaryDataSupplement || (ledgerSignTxResp.auxiliaryDataSupplement.type !== TxAuxiliaryDataSupplementType.CIP36_REGISTRATION) ) { - throw new Error(`${nameof(LedgerSendStore)}::${nameof(this.signAndBroadcast)} unexpected Ledger sign transaction response`); + throw new Error(`${nameof(LedgerSendStore)}::${nameof(this.signRawTx)} unexpected Ledger sign transaction response`); } const { cip36VoteRegistrationSignatureHex } = ledgerSignTxResp.auxiliaryDataSupplement; - if (cip36) { + if (ledgerSupportsCip36) { metadata = generateRegistrationMetadata( votingPublicKey, stakingKey, @@ -225,144 +260,18 @@ export default class LedgerSendStore extends Store { // blake2b(256 / 8).update(metadata.to_bytes()).digest('binary') // ).toString('hex') === // ledgerSignTxResp.auxiliaryDataSupplement.auxiliaryDataHashaHex - } else { - metadata = request.signRequest.metadata; - } - - if (metadata) { - request.signRequest.self().set_auxiliary_data(metadata); } - const tx = request.signRequest.self().build_tx(); - const txId = transactionHexToHash(tx.to_hex()); - - const signedTx = buildSignedTransaction( - tx, - request.signRequest.senderUtxos, - ledgerSignTxResp.witnesses, - request.publicKey, - metadata, - ); - - await broadcastTransaction({ - publicDeriverId: request.publicDeriverId, - signedTxHex: signedTx.to_hex(), - }); - - return { txId }; - } catch (error) { - Logger.error(`${nameof(LedgerSendStore)}::${nameof(this.signAndBroadcast)} error: ` + stringifyError(error)); - throw new convertToLocalizableError(error); - } finally { - if (ledgerConnect != null) { - ledgerConnect.dispose(); - } - } - }; - - - signRawTxFromWallet: {| - rawTxHex: string, - +wallet: { - publicDeriverId: number, - publicKey: string, - pathToPublic: Array, - networkId: number, - hardwareWalletDeviceId: ?string, - ... - }, - |} => Promise<{| signedTxHex: string |}> = async (request) => { - try { - Logger.debug(`${nameof(LedgerSendStore)}::${nameof(this.signRawTxFromWallet)} called: ` + stringifyData(request)); - - const publicKeyInfo = { - key: RustModule.WalletV4.Bip32PublicKey.from_hex(request.wallet.publicKey), - addressing: { - startLevel: 1, - path: request.wallet.pathToPublic, - }, - }; - - const expectedSerial = request.wallet.hardwareWalletDeviceId || ''; - - const addressingMap = genAddressingLookup( - request.wallet.networkId, - this.stores.addresses.addressSubgroupMap, - ); - - return this.signRawTx({ - rawTxHex: request.rawTxHex, - publicKey: publicKeyInfo, - addressingMap, - expectedSerial, - networkId: request.wallet.networkId, - }); - - } catch (error) { - Logger.error(`${nameof(LedgerSendStore)}::${nameof(this.signRawTxFromWallet)} error: ` + stringifyError(error)); - throw new convertToLocalizableError(error); - } - } - - signRawTx: {| - rawTxHex: string, - publicKey: {| - key: RustModule.WalletV4.Bip32PublicKey, - ...Addressing, - |}, - addressingMap: string => (void | $PropertyType), - networkId: number, - expectedSerial: string | void, - |} => Promise<{| signedTxHex: string |}> = async (request) => { - - let ledgerConnect: ?LedgerConnect; - try { - Logger.debug(`${nameof(LedgerSendStore)}::${nameof(this.signAndBroadcast)} called: ` + stringifyData(request)); - - ledgerConnect = new LedgerConnect({ - locale: this.stores.profile.currentLocale, - }); - - const { rawTxHex } = request; - - const network = getNetworkById(request.networkId); - - const txBodyHex = transactionHexToBodyHex(rawTxHex); - - const addressedUtxos = await this.stores.wallets.getAddressedUtxos(); - - const response = this.api.ada.createHwSignTxDataFromRawTx('ledger', { - txBodyHex, - network, - addressingMap: request.addressingMap, - senderUtxos: addressedUtxos, - }); - - const ledgerSignTxPayload = response.hw === 'ledger' ? response.result.ledgerSignTxPayload - : fail('Unecpected response type from `createHwSignTxDataFromRawTx` for ledger: ' + JSON.stringify(response)); - - const ledgerSignTxResp: LedgerSignTxResponse = - await ledgerConnect.signTransaction({ - serial: request.expectedSerial, - params: ledgerSignTxPayload, - useOpenTab: true, - }); - - // There is no need of ledgerConnect after this line. - // UI was getting blocked for few seconds - // because _prepareAndBroadcastSignedTx takes time. - // Disposing here will fix the UI issue. - ledgerConnect.dispose(); - - const signedTxHex = buildConnectorSignedTransaction( + const { txHex, txId } = buildConnectorSignedTransaction( rawTxHex, ledgerSignTxResp.witnesses, request.publicKey, + metadata, ); - return { signedTxHex }; + return { signedTxHex: txHex, txId, metadata }; } catch (error) { - Logger.error(`${nameof(LedgerSendStore)}::${nameof(this.signAndBroadcast)} error: ` + stringifyError(error)); + Logger.error(`${nameof(LedgerSendStore)}::${nameof(this.signRawTx)} error: ` + stringifyError(error)); throw new convertToLocalizableError(error); } finally { if (ledgerConnect != null) { From f50e6e9b4c958f3aed2e5b563bf5cf93e9e8ea85 Mon Sep 17 00:00:00 2001 From: yushi Date: Thu, 30 Jan 2025 04:01:30 +0800 Subject: [PATCH 06/32] deduplicate trezor transaction signing code --- packages/yoroi-extension/app/api/ada/index.js | 30 +- .../api/ada/transactions/shelley/ledgerTx.js | 1 - .../api/ada/transactions/shelley/trezorTx.js | 371 ++---------------- .../ada/transactions/shelley/trezorTx.test.js | 53 +-- .../app/connector/stores/ConnectorStore.js | 2 +- .../app/stores/ada/AdaWalletsStore.js | 3 +- .../app/stores/ada/send/TrezorSendStore.js | 207 ++++------ 7 files changed, 133 insertions(+), 534 deletions(-) diff --git a/packages/yoroi-extension/app/api/ada/index.js b/packages/yoroi-extension/app/api/ada/index.js index c032548dc3..3d19ca8796 100644 --- a/packages/yoroi-extension/app/api/ada/index.js +++ b/packages/yoroi-extension/app/api/ada/index.js @@ -82,7 +82,7 @@ import { generateAdaMnemonic, generateWalletRootKey, } from './lib/cardanoCrypto import { buildCoseSign1FromSignature, cip8Sign, makeCip8Key, v4PublicToV2 } from './lib/cardanoCrypto/utils'; import { isValidBip39Mnemonic, } from './lib/cardanoCrypto/wallet'; import type { CardanoSignTransaction } from 'trezor-connect-flow'; -import { createTrezorSignTxPayload, toTrezorSignRequest, } from './transactions/shelley/trezorTx'; +import { toTrezorSignRequest, } from './transactions/shelley/trezorTx'; import { toLedgerSignRequest, } from './transactions/shelley/ledgerTx'; import { GenericApiError, @@ -748,33 +748,6 @@ export default class AdaApi { } } - createTrezorSignTxData(request: {| - signRequest: HaskellShelleyTxSignRequest, - network: $ReadOnly, - |}): CreateTrezorSignTxDataResponse { - try { - Logger.debug(`${nameof(AdaApi)}::${nameof(this.createTrezorSignTxData)} called`); - - const config = getCardanoHaskellBaseConfig( - request.network - ).reduce((acc, next) => Object.assign(acc, next), {}); - - const trezorSignTxPayload = createTrezorSignTxPayload( - request.signRequest, - config.ByronNetworkId, - Number.parseInt(config.ChainNetworkId, 10), - ); - Logger.debug(`${nameof(AdaApi)}::${nameof(this.createTrezorSignTxData)} success: ` + stringifyData(trezorSignTxPayload)); - return { - trezorSignTxPayload, - }; - } catch (error) { - Logger.error(`${nameof(AdaApi)}::${nameof(this.createTrezorSignTxData)} error: ` + stringifyError(error)); - if (error instanceof LocalizableError) throw error; - throw new GenericApiError(); - } - } - createHwSignTxDataFromRawTx( hw: 'ledger' | 'trezor', request: CreateHWSignTxDataRequestFromRawTx @@ -814,6 +787,7 @@ export default class AdaApi { protocolMagic, addressMap, request.senderUtxos, + request.catalystData, ); Logger.debug(`${nameof(AdaApi)}::${nameof(this.createHwSignTxDataFromRawTx)} success: ` + stringifyData(trezorSignTxPayload)); return { hw, result: { trezorSignTxPayload } }; diff --git a/packages/yoroi-extension/app/api/ada/transactions/shelley/ledgerTx.js b/packages/yoroi-extension/app/api/ada/transactions/shelley/ledgerTx.js index 089d6fac08..b8b8a596e3 100644 --- a/packages/yoroi-extension/app/api/ada/transactions/shelley/ledgerTx.js +++ b/packages/yoroi-extension/app/api/ada/transactions/shelley/ledgerTx.js @@ -45,7 +45,6 @@ import { iterateLenGetMap, maybe } from '../../../../coreUtils'; -import { mergeWitnessSets } from '../utils'; import { transactionHexToHash } from '../../lib/cardanoCrypto/utils'; // ==================== LEDGER ==================== // diff --git a/packages/yoroi-extension/app/api/ada/transactions/shelley/trezorTx.js b/packages/yoroi-extension/app/api/ada/transactions/shelley/trezorTx.js index 0e4dad9362..cd29eafb85 100644 --- a/packages/yoroi-extension/app/api/ada/transactions/shelley/trezorTx.js +++ b/packages/yoroi-extension/app/api/ada/transactions/shelley/trezorTx.js @@ -23,7 +23,10 @@ import { CardanoDRepType, } from 'trezor-connect-flow'; import type { Address, Addressing, Value, } from '../../lib/storage/models/PublicDeriver/interfaces'; -import { HaskellShelleyTxSignRequest } from './HaskellShelleyTxSignRequest'; +import type { + HaskellShelleyTxSignRequest, + TrezorTCatalystRegistrationTxSignData, +} from './HaskellShelleyTxSignRequest'; import { Bip44DerivationLevels, } from '../../lib/storage/database/walletTypes/bip44/api/utils'; import { ChainDerivations, } from '../../../../config/numbersConfig'; @@ -32,136 +35,9 @@ import { toHexOrBase58 } from '../../lib/storage/bridge/utils'; import blake2b from 'blake2b'; import { derivePublicByAddressing } from '../../lib/cardanoCrypto/deriveByAddressing'; import { bytesToHex, iterateLenGet, iterateLenGetMap, maybe, forceNonNull, hexToBytes } from '../../../../coreUtils'; -import { mergeWitnessSets } from '../utils'; +import { transactionHexToHash } from '../../lib/cardanoCrypto/utils'; // ==================== TREZOR ==================== // -/** Generate a payload for Trezor SignTx */ -export function createTrezorSignTxPayload( - signRequest: HaskellShelleyTxSignRequest, - byronNetworkMagic: number, - networkId: number, -): $Exact { - const stakingKeyPath = (() => { - // TODO: this entire block is super hacky - // need to instead pass in a mapping from wallet addresses to addressing - // or add something similar to the sign request - - // assume the withdrawal is the same path as the UTXOs being spent - // so just take the first UTXO arbitrarily and change it to the staking key path - const firstUtxo = signRequest.senderUtxos[0]; - if (firstUtxo.addressing.startLevel !== Bip44DerivationLevels.PURPOSE.level) { - throw new Error(`${nameof(createTrezorSignTxPayload)} unexpected addressing start level`); - } - const result = [...firstUtxo.addressing.path]; - result[Bip44DerivationLevels.CHAIN.level - 1] = ChainDerivations.CHIMERIC_ACCOUNT; - result[Bip44DerivationLevels.ADDRESS.level - 1] = 0; - return result; - })(); - - const tx = signRequest.unsignedTx.build_tx(); - const txBody = tx.body(); - - const tagsState = RustModule.WasmScope(Module => - Module.WalletV4.has_transaction_set_tag(tx.to_bytes())); - - if (tagsState === RustModule.WalletV4.TransactionSetsState.MixedSets) { - throw new Error('Transaction with mixed sets cannot be signed by Ledger'); - } - - const txHasSetTags = tagsState === RustModule.WalletV4.TransactionSetsState.AllSetsHaveTag; - - // Inputs - const trezorInputs = _transformToTrezorInputs( - signRequest.senderUtxos - ); - - // Output - const trezorOutputs = _generateTrezorOutputs( - txBody.outputs(), - signRequest.changeAddr, - stakingKeyPath, - ); - - let request = { - signingMode: CardanoTxSigningMode.ORDINARY_TRANSACTION, - inputs: trezorInputs, - outputs: trezorOutputs, - fee: txBody.fee().to_str(), - ttl: txBody.ttl_bignum()?.to_str(), - validityIntervalStart: txBody.validity_start_interval_bignum()?.to_str(), - scriptDataHash: txBody.script_data_hash()?.to_hex(), - protocolMagic: byronNetworkMagic, - networkId, - }; - - // withdrawals - const withdrawals = txBody.withdrawals(); - request = withdrawals == null - ? request - : { - ...request, - withdrawals: formatTrezorWithdrawals( - withdrawals, - [stakingKeyPath], - ) - }; - - // certificates - const certificates = txBody.certs(); - request = certificates == null - ? request - : { - ...request, - certificates: formatTrezorCertificates( - certificates, - (_) => stakingKeyPath, - ) - }; - - if (signRequest.trezorTCatalystRegistrationTxSignData) { - const { votingPublicKey, nonce, paymentKeyPath } = - signRequest.trezorTCatalystRegistrationTxSignData; - request = { - ...request, - auxiliaryData: { - cVoteRegistrationParameters: { - delegations: [ - { - votePublicKey: votingPublicKey.replace(/^0x/, ''), - weight: 1, - } - ], - stakingPath: stakingKeyPath, - paymentAddressParameters: { - addressType: CardanoAddressType.BASE, - path: paymentKeyPath, - stakingPath: stakingKeyPath, - }, - nonce: String(nonce), - format: CardanoGovernanceRegistrationFormat.CIP36, - votingPurpose: 0, - }, - } - }; - } else { - const metadata = signRequest.metadata; - request = metadata === undefined - ? request - : { - ...request, - auxiliaryData: { - hash: blake2b(256 / 8).update(metadata.to_bytes()).digest('hex') - } - }; - } - if (txHasSetTags) { - request = { - ...request, - tagCborSets: true, - }; - } - return request; -} function formatTrezorWithdrawals( withdrawals: RustModule.WalletV4.Withdrawals, @@ -252,29 +128,6 @@ function formatTrezorCertificates( return result; } -/** - * Canonical inputs sorting: by tx hash and then by index - */ -function compareInputs(a: CardanoInput, b: CardanoInput): number { - if (a.prev_hash !== b.prev_hash) { - return a.prev_hash < b.prev_hash ? -1 : 1; - } - return a.prev_index - b.prev_index; -} - -function _transformToTrezorInputs( - inputs: Array -): Array { - for (const input of inputs) { - verifyFromDerivationRoot(input.addressing); - } - return inputs.map(input => ({ - prev_hash: input.tx_hash, - prev_index: input.tx_index, - path: toDerivationPathString(input.addressing.path), - })).sort(compareInputs); -} - function toTrezorTokenBundle( assets: ?RustModule.WalletV4.MultiAsset ): {| @@ -304,61 +157,6 @@ function toTrezorTokenBundle( return { tokenBundle }; } -function _generateTrezorOutputs( - txOutputs: RustModule.WalletV4.TransactionOutputs, - changeAddrs: Array<{| ...Address, ...Value, ...Addressing |}>, - stakingKeyPath: Array, -): Array { - const result = []; - for (const output of iterateLenGet(txOutputs)) { - const address = output.address(); - const jsAddr = toHexOrBase58(output.address()); - - // support post-alonzo map - - const tokenBundle = toTrezorTokenBundle(output.amount().multiasset()); - const dataHash = maybe(output.data_hash()?.to_hex(), datumHash => ({ datumHash })) ?? {}; - - const changeAddr = changeAddrs.find(change => jsAddr === change.address); - if (changeAddr != null) { - verifyFromDerivationRoot(changeAddr.addressing); - if (RustModule.WalletV4.BaseAddress.from_address(address)) { - result.push({ - addressParameters: { - addressType: CardanoAddressType.BASE, - path: changeAddr.addressing.path, - stakingPath: stakingKeyPath, - }, - amount: output.amount().coin().to_str(), - ...tokenBundle, - ...dataHash, - }); - } else if (RustModule.WalletV4.ByronAddress.from_address(address)) { - result.push({ - addressParameters: { - addressType: CardanoAddressType.BYRON, - path: changeAddr.addressing.path, - }, - amount: output.amount().coin().to_str(), - ...dataHash, - }); - } else { - throw new Error('unexpected change address type'); - } - } else { - const byronWasm = RustModule.WalletV4.ByronAddress.from_address(address); - result.push({ - address: byronWasm == null - ? address.to_bech32() - : byronWasm.to_base58(), - amount: output.amount().coin().to_str(), - ...tokenBundle, - ...dataHash, - }); - } - } - return result; -} export function toTrezorAddressParameters( address: RustModule.WalletV4.Address, @@ -426,128 +224,6 @@ export function toTrezorAddressParameters( throw new Error(`${nameof(toTrezorAddressParameters)} unknown address type`); } -export function buildSignedTransaction( - tx: RustModule.WalletV4.Transaction, - senderUtxos: Array, - witnesses: Array, - publicKey: {| - ...Addressing, - key: RustModule.WalletV4.Bip32PublicKey, - |}, - stakingKey: ?RustModule.WalletV4.Bip32PublicKey, - metadata: RustModule.WalletV4.AuxiliaryData | void, -): RustModule.WalletV4.Transaction { - const findWitness = (pubKey: string) => { - for (const witness of witnesses) { - if (witness.pubKey === pubKey) { - return witness.signature; - } - } - throw new Error(`${nameof(buildSignedTransaction)} no witness for ${pubKey}`); - }; - - const keyLevel = publicKey.addressing.startLevel + publicKey.addressing.path.length - 1; - - const witSet = RustModule.WalletV4.TransactionWitnessSet.new(); - const bootstrapWitnesses: Array = []; - const vkeys: Array = []; - - const seenVKeyWit = new Set(); - const seenBootstrapWit = new Set(); - - for (const utxo of senderUtxos) { - verifyFromDerivationRoot(utxo.addressing); - - const addressKey = derivePublicByAddressing({ - addressing: utxo.addressing, - startingFrom: { - level: keyLevel, - key: publicKey.key, - } - }); - const pubKey = addressKey.to_raw_key().to_hex(); - - const witness = findWitness(pubKey); - - if (RustModule.WalletV4.ByronAddress.is_valid(utxo.receiver)) { - - const byronAddr = RustModule.WalletV4.ByronAddress.from_base58(utxo.receiver); - const bootstrapWit = RustModule.WalletV4.BootstrapWitness.new( - RustModule.WalletV4.Vkey.new(addressKey.to_raw_key()), - RustModule.WalletV4.Ed25519Signature.from_hex(witness), - addressKey.chaincode(), - byronAddr.attributes(), - ); - const asString = bootstrapWit.to_hex(); - if (seenBootstrapWit.has(asString)) { - continue; - } - seenBootstrapWit.add(asString); - bootstrapWitnesses.push(bootstrapWit); - continue; - } - - const vkeyWit = RustModule.WalletV4.Vkeywitness.new( - RustModule.WalletV4.Vkey.new(addressKey.to_raw_key()), - RustModule.WalletV4.Ed25519Signature.from_hex(witness), - ); - const asString = vkeyWit.to_hex(); - if (seenVKeyWit.has(asString)) { - continue; - } - seenVKeyWit.add(asString); - vkeys.push(vkeyWit); - } - - // add any staking key needed - const stakingPubKey = stakingKey - ? bytesToHex(stakingKey.to_raw_key().as_bytes()) - : null; - - for (const witness of witnesses) { - if (witness.pubKey === stakingPubKey) { - if (stakingKey == null) { - throw new Error('unexpected nullish staking key'); - } - const vkeyWit = RustModule.WalletV4.Vkeywitness.new( - RustModule.WalletV4.Vkey.new(stakingKey.to_raw_key()), - RustModule.WalletV4.Ed25519Signature.from_hex(witness.signature), - ); - const asString = vkeyWit.to_hex(); - if (seenVKeyWit.has(asString)) { - continue; - } - seenVKeyWit.add(asString); - vkeys.push(vkeyWit); - } - } - - if (bootstrapWitnesses.length > 0) { - const bootstrapWitWasm = RustModule.WalletV4.BootstrapWitnesses.new(); - for (const bootstrapWit of bootstrapWitnesses) { - bootstrapWitWasm.add(bootstrapWit); - } - witSet.set_bootstraps(bootstrapWitWasm); - } - if (vkeys.length > 0) { - const vkeyWitWasm = RustModule.WalletV4.Vkeywitnesses.new(); - for (const vkey of vkeys) { - vkeyWitWasm.add(vkey); - } - witSet.set_vkeys(vkeyWitWasm); - } - - const mergedWitnessSet = RustModule.WalletV4.TransactionWitnessSet.from_hex( - mergeWitnessSets(tx.witness_set().to_hex(), witSet.to_hex()), - ); - - return RustModule.WalletV4.Transaction.new( - tx.body(), - mergedWitnessSet, - metadata - ); -} - type AddressMap = (addressHex: string) => ?Array; // Convert connector sign tx input into request to Trezor. @@ -560,6 +236,7 @@ export function toTrezorSignRequest( protocolMagic: number, ownAddressMap: AddressMap, senderUtxos: Array, + catalystData?: TrezorTCatalystRegistrationTxSignData, ): $Exact { const tagsState = RustModule.WasmScope(Module => Module.WalletV4.has_transaction_set_tag( @@ -797,7 +474,6 @@ export function toTrezorSignRequest( formattedWithdrawals = result; } - // TODO: support CIP36 aux data let formattedAuxiliaryData = null; const auxiliaryDataHash = txBody.auxiliary_data_hash(); if (auxiliaryDataHash) { @@ -806,6 +482,32 @@ export function toTrezorSignRequest( }; } + if (catalystData) { + if (formattedAuxiliaryData) { + throw new Error('unexpected catalyst signing data'); + } + const { votingPublicKey, nonce, paymentKeyPath, stakingKeyPath } = catalystData; + formattedAuxiliaryData = { + cVoteRegistrationParameters: { + delegations: [ + { + votePublicKey: votingPublicKey.replace(/^0x/, ''), + weight: 1, + } + ], + stakingPath: stakingKeyPath, + paymentAddressParameters: { + addressType: CardanoAddressType.BASE, + path: paymentKeyPath, + stakingPath: stakingKeyPath, + }, + nonce: String(nonce), + format: CardanoGovernanceRegistrationFormat.CIP36, + votingPurpose: 0, + }, + }; + } + let formattedCollateral = null; const collateral = txBody.collateral(); if (collateral) { @@ -896,9 +598,13 @@ export function toTrezorSignRequest( export function buildConnectorSignedTransaction( rawTxHex: string, witnesses: Array, -): string { + metadata: ?RustModule.WalletV4.AuxiliaryData, +): {| txHex: string, txId: string |} { const fixedTx = RustModule.WalletV4.FixedTransaction.from_hex(rawTxHex); + if (metadata) { + fixedTx.set_auxiliary_data(metadata.to_bytes()); + } for (const witness of witnesses) { if (witness.type === CardanoTxWitnessType.BYRON_WITNESS) { @@ -919,5 +625,6 @@ export function buildConnectorSignedTransaction( } - return fixedTx.to_hex(); + const txHex = fixedTx.to_hex(); + return { txHex, txId: transactionHexToHash(txHex)}; } diff --git a/packages/yoroi-extension/app/api/ada/transactions/shelley/trezorTx.test.js b/packages/yoroi-extension/app/api/ada/transactions/shelley/trezorTx.test.js index 3ce0911960..c863818e1b 100644 --- a/packages/yoroi-extension/app/api/ada/transactions/shelley/trezorTx.test.js +++ b/packages/yoroi-extension/app/api/ada/transactions/shelley/trezorTx.test.js @@ -3,17 +3,10 @@ import '../../lib/test-config.forTests'; import BigNumber from 'bignumber.js'; import { RustModule } from '../../lib/cardanoCrypto/rustLoader'; -import { - createTrezorSignTxPayload, - toTrezorAddressParameters, -} from './trezorTx'; +import { toTrezorSignRequest, toTrezorAddressParameters } from './trezorTx'; import { networks } from '../../lib/storage/database/prepackaged/networks'; import { HaskellShelleyTxSignRequest } from './HaskellShelleyTxSignRequest'; -import { - byronAddrToHex, - getCardanoSpendingKeyHash, - normalizeToAddress, -} from '../../lib/storage/bridge/utils'; +import { getCardanoSpendingKeyHash, normalizeToAddress } from '../../lib/storage/bridge/utils'; import { CardanoCertificateType, CardanoAddressType, @@ -172,7 +165,7 @@ test('Create Trezor transaction', async () => { RustModule.WalletV4.ByronAddress.from_base58(utxo.receiver), RustModule.WalletV4.TransactionInput.new( RustModule.WalletV4.TransactionHash.from_hex(utxo.tx_hash), - 1 + utxo.tx_index, ), RustModule.WalletV4.Value.new(RustModule.WalletV4.BigNum.from_str(utxo.amount)) ); @@ -181,7 +174,7 @@ test('Create Trezor transaction', async () => { keyHash, RustModule.WalletV4.TransactionInput.new( RustModule.WalletV4.TransactionHash.from_hex(utxo.tx_hash), - 1 + utxo.tx_index, ), RustModule.WalletV4.Value.new(RustModule.WalletV4.BigNum.from_str(utxo.amount)) ); @@ -189,7 +182,7 @@ test('Create Trezor transaction', async () => { } txBuilder.add_output( RustModule.WalletV4.TransactionOutput.new( - RustModule.WalletV4.Address.from_hex(byronAddrToHex('Ae2tdPwUPEZAVDjkPPpwDhXMSAjH53CDmd2xMwuR9tZMAZWxLhFphrHKHXe')), + RustModule.WalletV4.Address.from_bech32('addr1stvpskppsdvpezg6ex464jvekztus84rcpzskramdy7sh53jh67q7j3er7sqqxhjlalzzsk0pgc'), RustModule.WalletV4.Value.new(RustModule.WalletV4.BigNum.from_str('6323634')) ) ); @@ -214,52 +207,38 @@ test('Create Trezor transaction', async () => { .reduce((acc, next) => Object.assign(acc, next), {}); const { ByronNetworkId, ChainNetworkId } = baseConfig; - const response = await createTrezorSignTxPayload( - new HaskellShelleyTxSignRequest({ - unsignedTx: txBuilder, - changeAddr: [], - senderUtxos, - metadata: undefined, - networkSettingSnapshot: { - ChainNetworkId: Number.parseInt(baseConfig.ChainNetworkId, 10), - PoolDeposit: new BigNumber(baseConfig.PoolDeposit), - KeyDeposit: new BigNumber(baseConfig.KeyDeposit), - NetworkId: network.NetworkId, - }, - neededStakingKeyHashes: { - neededHashes: new Set([stakeCredential.to_hex()]), - wits: new Set() // not needed for this test, but something should be here - }, - }), + const response = toTrezorSignRequest( + txBuilder.build().to_hex(), + Number.parseInt(baseConfig.ChainNetworkId, 10), ByronNetworkId, - Number.parseInt(ChainNetworkId, 10), + _address => [2147483692, 2147485463, 2147483648, 2, 0], + senderUtxos, ); expect(response).toStrictEqual({ fee: '2000', ttl: '500', networkId: 1, protocolMagic: 764824073, - scriptDataHash: undefined, - validityIntervalStart: undefined, + includeNetworkId: false, inputs: [{ - path: `m/44'/1815'/0'/1/1`, + path: [2147483692, 2147485463, 2147483648, 1, 1], prev_hash: '058405892f66075d83abd1b7fe341d2d5bfd2f6122b2f874700039e5078e0dd5', prev_index: 1, }, { - path: `m/44'/1815'/0'/0/7`, + path: [2147483692, 2147485463, 2147483648, 0, 7], prev_hash: '1029eef5bb0f06979ab0b9530a62bac11e180797d08cab980fe39389d42b3657', prev_index: 0, }, { - path: `m/44'/1815'/0'/0/7`, + path: [2147483692, 2147485463, 2147483648, 0, 7], prev_hash: '2029eef5bb0f06979ab0b9530a62bac11e180797d08cab980fe39389d42b3658', prev_index: 0, }, { - path: `m/44'/1815'/0'/1/2`, + path: [2147483692, 2147485463, 2147483648, 1, 2], prev_hash: '3677e75c7ba699bfdc6cd57d42f246f86f69aefd76025006ac78313fad2bba20', prev_index: 1, }], outputs: [{ - address: 'Ae2tdPwUPEZAVDjkPPpwDhXMSAjH53CDmd2xMwuR9tZMAZWxLhFphrHKHXe', + address: 'addr1stvpskppsdvpezg6ex464jvekztus84rcpzskramdy7sh53jh67q7j3er7sqqxhjlalzzsk0pgc', amount: `6323634` }], certificates: [{ diff --git a/packages/yoroi-extension/app/connector/stores/ConnectorStore.js b/packages/yoroi-extension/app/connector/stores/ConnectorStore.js index ed46f5b616..1123405f01 100644 --- a/packages/yoroi-extension/app/connector/stores/ConnectorStore.js +++ b/packages/yoroi-extension/app/connector/stores/ConnectorStore.js @@ -1082,7 +1082,7 @@ export default class ConnectorStore extends Store { throw new Error('hash mismatch'); } - return buildSignedTrezorTransaction(rawTxHex, trezorSignTxResp.witnesses); + return buildSignedTrezorTransaction(rawTxHex, trezorSignTxResp.witnesses).txHex; } async ledgerSignTx( diff --git a/packages/yoroi-extension/app/stores/ada/AdaWalletsStore.js b/packages/yoroi-extension/app/stores/ada/AdaWalletsStore.js index 27d209729d..7025a876c2 100644 --- a/packages/yoroi-extension/app/stores/ada/AdaWalletsStore.js +++ b/packages/yoroi-extension/app/stores/ada/AdaWalletsStore.js @@ -152,8 +152,9 @@ export default class AdaWalletsStore extends Store { } if (request.signRequest.trezor) { const { wallet, transactionHex } = request.signRequest.trezor; - return this.stores.substores.ada.trezorSend + const { signedTxHex } = await this.stores.substores.ada.trezorSend .signRawTxFromWallet({ rawTxHex: transactionHex, wallet }); + return { signedTxHex }; } if (request.signRequest.normal) { const { wallet, transactionHex, password } = request.signRequest.normal; diff --git a/packages/yoroi-extension/app/stores/ada/send/TrezorSendStore.js b/packages/yoroi-extension/app/stores/ada/send/TrezorSendStore.js index e9878e6f99..05da14cdf9 100644 --- a/packages/yoroi-extension/app/stores/ada/send/TrezorSendStore.js +++ b/packages/yoroi-extension/app/stores/ada/send/TrezorSendStore.js @@ -7,12 +7,12 @@ import { wrapWithFrame } from '../../lib/TrezorWrapper'; import { Logger, stringifyData, stringifyError, } from '../../../utils/logging'; import { convertToLocalizableError } from '../../../domain/TrezorLocalizedError'; import LocalizableError from '../../../i18n/LocalizableError'; -import { HaskellShelleyTxSignRequest } from '../../../api/ada/transactions/shelley/HaskellShelleyTxSignRequest'; +import type { + HaskellShelleyTxSignRequest, + TrezorTCatalystRegistrationTxSignData, +} from '../../../api/ada/transactions/shelley/HaskellShelleyTxSignRequest'; import type { StoresMap } from '../../index'; -import { - buildConnectorSignedTransaction, - buildSignedTransaction -} from '../../../api/ada/transactions/shelley/trezorTx'; +import { buildConnectorSignedTransaction } from '../../../api/ada/transactions/shelley/trezorTx'; import { RustModule } from '../../../api/ada/lib/cardanoCrypto/rustLoader'; import { generateRegistrationMetadata } from '../../../api/ada/lib/cardanoCrypto/catalyst'; import { derivePublicByAddressing } from '../../../api/ada/lib/cardanoCrypto/deriveByAddressing'; @@ -57,138 +57,28 @@ export default class TrezorSendStore extends Store { try { Logger.debug(`${nameof(TrezorSendStore)}::${nameof(this.signAndBroadcastFromWallet)} called: ` + stringifyData(request)); - const { signRequest, wallet } = request; - - return this.signAndBroadcast({ - signRequest, - wallet, - }); - - } catch (error) { - Logger.error(`${nameof(TrezorSendStore)}::${nameof(this.signAndBroadcastFromWallet)} error: ` + stringifyError(error)); - throw new convertToLocalizableError(error); - } - } - - signAndBroadcast: {| - signRequest: HaskellShelleyTxSignRequest, - +wallet: { - publicDeriverId: number, - networkId: number, - publicKey: string, - pathToPublic: Array, - stakingAddressing: Addressing, - ... - }, - |} => Promise<{| txId: string |}> = async (request) => { - const { signRequest } = request; - try { - const network = getNetworkById(request.wallet.networkId); - const trezorSignTxDataResp = this.api.ada.createTrezorSignTxData({ - signRequest, - network, + const { signedTxHex, txId, metadata } = await this.signRawTxFromWallet({ + rawTxHex: request.signRequest.self().build_tx().to_hex(), + wallet: request.wallet, + catalystData: request.signRequest.trezorTCatalystRegistrationTxSignData }); - const trezorSignTxResp = await wrapWithFrame(trezor => { - return trezor.cardanoSignTransaction( - JSON.parse(JSON.stringify({ ...trezorSignTxDataResp.trezorSignTxPayload })) - ); - }); - - if (trezorSignTxResp && trezorSignTxResp.payload && trezorSignTxResp.payload.error != null) { - // this Error will be converted to LocalizableError() - // noinspection ExceptionCaughtLocallyJS - throw new Error(trezorSignTxResp.payload.error); - } - if (!trezorSignTxResp.success) { - // noinspection ExceptionCaughtLocallyJS - throw new Error(`${nameof(TrezorSendStore)}::${nameof(this.signAndBroadcast)} should never happen`); - } - - const publicKeyInfo = { - key: RustModule.WalletV4.Bip32PublicKey.from_hex(request.wallet.publicKey), - addressing: { - startLevel: 1, - path: request.wallet.pathToPublic, - }, - }; - - const stakingKey = derivePublicByAddressing({ - addressing: request.wallet.stakingAddressing.addressing, - startingFrom: { - level: publicKeyInfo.addressing.startLevel + publicKeyInfo.addressing.path.length - 1, - key: publicKeyInfo.key, - } - }); - - let metadata; - - if (signRequest.trezorTCatalystRegistrationTxSignData) { - const { - votingPublicKey, - stakingKey: stakingKeyHex, - paymentAddress, - nonce, - } = signRequest.trezorTCatalystRegistrationTxSignData; - - const auxDataSupplement = trezorSignTxResp.payload.auxiliaryDataSupplement; - if ( - !auxDataSupplement - || auxDataSupplement.type !== 1 - || auxDataSupplement.governanceSignature == null - ) { - // noinspection ExceptionCaughtLocallyJS - throw new Error(`${nameof(TrezorSendStore)}::${nameof(this.signAndBroadcast)} unexpected Trezor sign transaction response`); - } - const catalystSignature = auxDataSupplement.governanceSignature; - - metadata = generateRegistrationMetadata( - votingPublicKey, - stakingKeyHex, - paymentAddress, - nonce, - (_hashedMetadata) => { - return catalystSignature; - }, - ); - // We can verify that - // Buffer.from( - // blake2b(256 / 8).update(metadata.to_bytes()).digest('binary') - // ).toString('hex') === - // trezorSignTxResp.payload.auxiliaryDataSupplement.auxiliaryDataHash - } else { - metadata = signRequest.metadata; - } - if (metadata) { - signRequest.self().set_auxiliary_data(metadata); + request.signRequest.self().set_auxiliary_data(metadata); } - const tx = signRequest.self().build_tx(); - - const signedTx = buildSignedTransaction( - tx, - signRequest.senderUtxos, - trezorSignTxResp.payload.witnesses, - publicKeyInfo, - stakingKey, - metadata, - ); - - const txId = RustModule.WalletV4.FixedTransaction.from_hex(tx.to_hex()).transaction_hash().to_hex(); - await broadcastTransaction({ publicDeriverId: request.wallet.publicDeriverId, - signedTxHex: signedTx.to_hex(), + signedTxHex, }); + return { txId }; } catch (error) { - Logger.error(`${nameof(TrezorSendStore)}::${nameof(this.signAndBroadcast)} error: ` + stringifyError(error)); + Logger.error(`${nameof(TrezorSendStore)}::${nameof(this.signAndBroadcastFromWallet)} error: ` + stringifyError(error)); throw new convertToLocalizableError(error); } } - signRawTxFromWallet: {| rawTxHex: string, +wallet: { @@ -199,14 +89,19 @@ export default class TrezorSendStore extends Store { stakingAddressing: Addressing, ... }, - |} => Promise<{| signedTxHex: string |}> = async (request) => { + catalystData?: TrezorTCatalystRegistrationTxSignData, + |} => Promise<{| + signedTxHex: string, + txId: string, + metadata: ?RustModule.WalletV4.AuxiliaryData + |}> = async (request) => { try { Logger.debug(`${nameof(TrezorSendStore)}::${nameof(this.signRawTxFromWallet)} called: ` + stringifyData(request)); - const { rawTxHex, wallet } = request; + const { rawTxHex, wallet, catalystData } = request; const addressingMap = genAddressingLookup( - request.wallet.networkId, + wallet.networkId, this.stores.addresses.addressSubgroupMap, ); @@ -214,6 +109,7 @@ export default class TrezorSendStore extends Store { rawTxHex, addressingMap, networkId: wallet.networkId, + catalystData, }); } catch (error) { @@ -226,12 +122,16 @@ export default class TrezorSendStore extends Store { rawTxHex: string, addressingMap: string => (void | $PropertyType), networkId: number, - |} => Promise<{| signedTxHex: string |}> = async (request) => { - const { rawTxHex } = request; + catalystData?: TrezorTCatalystRegistrationTxSignData, + |} => Promise<{| + signedTxHex: string, + txId: string, + metadata: ?RustModule.WalletV4.AuxiliaryData + |}> = async (request) => { try { const network = getNetworkById(request.networkId); - const txBodyHex = transactionHexToBodyHex(rawTxHex); + const txBodyHex = transactionHexToBodyHex(request.rawTxHex); const addressedUtxos = await this.stores.wallets.getAddressedUtxos(); @@ -240,6 +140,7 @@ export default class TrezorSendStore extends Store { network, addressingMap: request.addressingMap, senderUtxos: addressedUtxos, + catalystData: request.catalystData, }); const trezorSignTxPayload = response.hw === 'trezor' ? response.result.trezorSignTxPayload @@ -258,17 +159,55 @@ export default class TrezorSendStore extends Store { } if (!trezorSignTxResp.success) { // noinspection ExceptionCaughtLocallyJS - throw new Error(`${nameof(TrezorSendStore)}::${nameof(this.signAndBroadcast)} should never happen`); + throw new Error(`${nameof(TrezorSendStore)}::${nameof(this.signRawTx)} should never happen`); } - const signedTxHex = buildConnectorSignedTransaction( - rawTxHex, + let metadata; + + if (request.catalystData) { + const { + votingPublicKey, + stakingKey: stakingKeyHex, + paymentAddress, + nonce, + } = request.catalystData; + + const auxDataSupplement = trezorSignTxResp.payload.auxiliaryDataSupplement; + if ( + !auxDataSupplement + || auxDataSupplement.type !== 1 + || auxDataSupplement.governanceSignature == null + ) { + // noinspection ExceptionCaughtLocallyJS + throw new Error(`${nameof(TrezorSendStore)}::${nameof(this.signRawTx)} unexpected Trezor sign transaction response`); + } + const catalystSignature = auxDataSupplement.governanceSignature; + + metadata = generateRegistrationMetadata( + votingPublicKey, + stakingKeyHex, + paymentAddress, + nonce, + (_hashedMetadata) => { + return catalystSignature; + }, + ); + // We can verify that + // Buffer.from( + // blake2b(256 / 8).update(metadata.to_bytes()).digest('binary') + // ).toString('hex') === + // trezorSignTxResp.payload.auxiliaryDataSupplement.auxiliaryDataHash + } + + const { txHex, txId } = buildConnectorSignedTransaction( + request.rawTxHex, trezorSignTxResp.payload.witnesses, + metadata, ); - return { signedTxHex }; + return { signedTxHex: txHex, txId, metadata }; } catch (error) { - Logger.error(`${nameof(TrezorSendStore)}::${nameof(this.signAndBroadcast)} error: ` + stringifyError(error)); + Logger.error(`${nameof(TrezorSendStore)}::${nameof(this.signRawTx)} error: ` + stringifyError(error)); throw new convertToLocalizableError(error); } } From 18189673e6a4fd693b1539c780d89334267e2d01 Mon Sep 17 00:00:00 2001 From: yushi Date: Thu, 30 Jan 2025 19:52:37 +0800 Subject: [PATCH 07/32] lint --- .../api/ada/transactions/shelley/ledgerTx.js | 5 +-- .../ada/transactions/shelley/ledgerTx.test.js | 2 -- .../api/ada/transactions/shelley/trezorTx.js | 32 ++----------------- .../ada/transactions/shelley/trezorTx.test.js | 4 +-- .../wallet/send/WalletSendFormRevamp.js | 2 -- .../transfer/UpgradeTxDialogContainer.js | 2 +- .../app/containers/wallet/WalletSendPage.js | 3 -- .../app/stores/ada/LedgerConnectStore.js | 1 - .../app/stores/ada/send/TrezorSendStore.js | 1 - .../app/stores/stateless/topbarCategories.js | 1 - 10 files changed, 6 insertions(+), 47 deletions(-) diff --git a/packages/yoroi-extension/app/api/ada/transactions/shelley/ledgerTx.js b/packages/yoroi-extension/app/api/ada/transactions/shelley/ledgerTx.js index b8b8a596e3..2efc8f6ec2 100644 --- a/packages/yoroi-extension/app/api/ada/transactions/shelley/ledgerTx.js +++ b/packages/yoroi-extension/app/api/ada/transactions/shelley/ledgerTx.js @@ -29,12 +29,9 @@ import { TxOutputFormat, TxRequiredSignerType, } from '@cardano-foundation/ledgerjs-hw-app-cardano'; -import type { Address, Addressing, Value, } from '../../lib/storage/models/PublicDeriver/interfaces'; +import type { Addressing } from '../../lib/storage/models/PublicDeriver/interfaces'; import type { LedgerNanoCatalystRegistrationTxSignData } from './HaskellShelleyTxSignRequest'; import { RustModule } from '../../lib/cardanoCrypto/rustLoader'; -import { toHexOrBase58 } from '../../lib/storage/bridge/utils'; -import { Bip44DerivationLevels, } from '../../lib/storage/database/walletTypes/bip44/api/utils'; -import { ChainDerivations, } from '../../../../config/numbersConfig'; import { derivePublicByAddressing } from '../../lib/cardanoCrypto/deriveByAddressing'; import { bytesToHex, diff --git a/packages/yoroi-extension/app/api/ada/transactions/shelley/ledgerTx.test.js b/packages/yoroi-extension/app/api/ada/transactions/shelley/ledgerTx.test.js index c37077f296..93a001a3b3 100644 --- a/packages/yoroi-extension/app/api/ada/transactions/shelley/ledgerTx.test.js +++ b/packages/yoroi-extension/app/api/ada/transactions/shelley/ledgerTx.test.js @@ -1,7 +1,6 @@ // @flow import '../../lib/test-config.forTests'; -import BigNumber from 'bignumber.js'; import { RustModule } from '../../lib/cardanoCrypto/rustLoader'; import { toLedgerSignRequest, @@ -13,7 +12,6 @@ import { getCardanoSpendingKeyHash, normalizeToAddress, } from '../../lib/storage/bridge/utils'; -import { HaskellShelleyTxSignRequest } from './HaskellShelleyTxSignRequest'; import { AddressType, CertificateType, diff --git a/packages/yoroi-extension/app/api/ada/transactions/shelley/trezorTx.js b/packages/yoroi-extension/app/api/ada/transactions/shelley/trezorTx.js index cd29eafb85..070df46a60 100644 --- a/packages/yoroi-extension/app/api/ada/transactions/shelley/trezorTx.js +++ b/packages/yoroi-extension/app/api/ada/transactions/shelley/trezorTx.js @@ -1,6 +1,5 @@ -// // @flow +// @flow import type { CardanoAddressedUtxo, } from '../types'; -import { verifyFromDerivationRoot } from '../../lib/storage/models/utils'; import { toDerivationPathString } from '../../lib/cardanoCrypto/keys/path'; import type { CardanoAddressParameters, @@ -22,38 +21,13 @@ import { CardanoTxWitnessType, CardanoDRepType, } from 'trezor-connect-flow'; -import type { Address, Addressing, Value, } from '../../lib/storage/models/PublicDeriver/interfaces'; -import type { - HaskellShelleyTxSignRequest, - TrezorTCatalystRegistrationTxSignData, -} from './HaskellShelleyTxSignRequest'; -import { Bip44DerivationLevels, } from '../../lib/storage/database/walletTypes/bip44/api/utils'; -import { ChainDerivations, } from '../../../../config/numbersConfig'; - +import type { TrezorTCatalystRegistrationTxSignData } from './HaskellShelleyTxSignRequest'; import { RustModule } from '../../lib/cardanoCrypto/rustLoader'; -import { toHexOrBase58 } from '../../lib/storage/bridge/utils'; -import blake2b from 'blake2b'; -import { derivePublicByAddressing } from '../../lib/cardanoCrypto/deriveByAddressing'; -import { bytesToHex, iterateLenGet, iterateLenGetMap, maybe, forceNonNull, hexToBytes } from '../../../../coreUtils'; +import { bytesToHex, iterateLenGet, iterateLenGetMap, forceNonNull, hexToBytes } from '../../../../coreUtils'; import { transactionHexToHash } from '../../lib/cardanoCrypto/utils'; // ==================== TREZOR ==================== // -function formatTrezorWithdrawals( - withdrawals: RustModule.WalletV4.Withdrawals, - paths: Array>, -): Array { - return iterateLenGetMap(withdrawals) - .values() - .nonNull() - .zip(paths) - .map(([withdrawalAmount, path]) => ({ - amount: withdrawalAmount.to_str(), - path, - })) - .toArray(); -} - function formatTrezorCertificates( certificates: RustModule.WalletV4.Certificates, getPath: (stakeCredential: RustModule.WalletV4.Credential) => Array, diff --git a/packages/yoroi-extension/app/api/ada/transactions/shelley/trezorTx.test.js b/packages/yoroi-extension/app/api/ada/transactions/shelley/trezorTx.test.js index c863818e1b..c5823da497 100644 --- a/packages/yoroi-extension/app/api/ada/transactions/shelley/trezorTx.test.js +++ b/packages/yoroi-extension/app/api/ada/transactions/shelley/trezorTx.test.js @@ -1,11 +1,9 @@ // @flow import '../../lib/test-config.forTests'; -import BigNumber from 'bignumber.js'; import { RustModule } from '../../lib/cardanoCrypto/rustLoader'; import { toTrezorSignRequest, toTrezorAddressParameters } from './trezorTx'; import { networks } from '../../lib/storage/database/prepackaged/networks'; -import { HaskellShelleyTxSignRequest } from './HaskellShelleyTxSignRequest'; import { getCardanoSpendingKeyHash, normalizeToAddress } from '../../lib/storage/bridge/utils'; import { CardanoCertificateType, @@ -209,7 +207,7 @@ test('Create Trezor transaction', async () => { const response = toTrezorSignRequest( txBuilder.build().to_hex(), - Number.parseInt(baseConfig.ChainNetworkId, 10), + Number.parseInt(ChainNetworkId, 10), ByronNetworkId, _address => [2147483692, 2147485463, 2147483648, 2, 0], senderUtxos, diff --git a/packages/yoroi-extension/app/components/wallet/send/WalletSendFormRevamp.js b/packages/yoroi-extension/app/components/wallet/send/WalletSendFormRevamp.js index f975da66cd..dd289eb587 100644 --- a/packages/yoroi-extension/app/components/wallet/send/WalletSendFormRevamp.js +++ b/packages/yoroi-extension/app/components/wallet/send/WalletSendFormRevamp.js @@ -57,8 +57,6 @@ import SupportedAddressDomainsBanner from '../../../containers/wallet/SupportedA import type { MaxSendableAmountRequest } from '../../../stores/toplevel/TransactionBuilderStore'; import type { WalletState } from '../../../../chrome/extension/background/types'; import LoadingSpinner from '../../widgets/LoadingSpinner'; -import LedgerSendStore from '../../../stores/ada/send/LedgerSendStore'; -import TrezorSendStore from '../../../stores/ada/send/TrezorSendStore'; const messages = defineMessages({ receiverLabel: { diff --git a/packages/yoroi-extension/app/containers/transfer/UpgradeTxDialogContainer.js b/packages/yoroi-extension/app/containers/transfer/UpgradeTxDialogContainer.js index c4a1d067f4..3000879112 100644 --- a/packages/yoroi-extension/app/containers/transfer/UpgradeTxDialogContainer.js +++ b/packages/yoroi-extension/app/containers/transfer/UpgradeTxDialogContainer.js @@ -10,7 +10,7 @@ import { addressToDisplayString, } from '../../api/ada/lib/storage/bridge/utils' import type { TransferTx, } from '../../types/TransferTypes'; -import { genAddressLookup, genAddressingLookup, allAddressSubgroups } from '../../stores/stateless/addressStores'; +import { genAddressingLookup, allAddressSubgroups } from '../../stores/stateless/addressStores'; import TransferSummaryPage from '../../components/transfer/TransferSummaryPage'; import Dialog from '../../components/widgets/Dialog'; import LegacyTransferLayout from '../../components/transfer/LegacyTransferLayout'; diff --git a/packages/yoroi-extension/app/containers/wallet/WalletSendPage.js b/packages/yoroi-extension/app/containers/wallet/WalletSendPage.js index b8a2b9d616..3f6aa8bada 100644 --- a/packages/yoroi-extension/app/containers/wallet/WalletSendPage.js +++ b/packages/yoroi-extension/app/containers/wallet/WalletSendPage.js @@ -3,17 +3,14 @@ import type { Node } from 'react'; import { Component } from 'react'; import { observer } from 'mobx-react'; import { action, observable, runInAction } from 'mobx'; -import type { $npm$ReactIntl$IntlFormat } from 'react-intl'; import { ROUTES } from '../../routes-config'; import WalletSendFormRevamp from '../../components/wallet/send/WalletSendFormRevamp'; import MemoNoExternalStorageDialog from '../../components/wallet/memos/MemoNoExternalStorageDialog'; import { HaskellShelleyTxSignRequest } from '../../api/ada/transactions/shelley/HaskellShelleyTxSignRequest'; -import { addressToDisplayString } from '../../api/ada/lib/storage/bridge/utils'; import type { TokenRow } from '../../api/ada/lib/storage/database/primitives/tables'; import { genLookupOrFail } from '../../stores/stateless/tokenHelpers'; import BigNumber from 'bignumber.js'; import TransactionSuccessDialog from '../../components/wallet/send/TransactionSuccessDialog'; -import globalMessages from '../../i18n/global-messages'; import AddNFTDialog from '../../components/wallet/send/WalletSendFormSteps/AddNFTDialog'; import AddTokenDialog from '../../components/wallet/send/WalletSendFormSteps/AddTokenDialog'; import { ampli } from '../../../ampli/index'; diff --git a/packages/yoroi-extension/app/stores/ada/LedgerConnectStore.js b/packages/yoroi-extension/app/stores/ada/LedgerConnectStore.js index 28dc45c6b9..51cd84f75b 100644 --- a/packages/yoroi-extension/app/stores/ada/LedgerConnectStore.js +++ b/packages/yoroi-extension/app/stores/ada/LedgerConnectStore.js @@ -220,7 +220,6 @@ export default class LedgerConnectStore } _checkAndStoreHWDeviceInfo: void => Promise = async () => { - debugger this.stores.substores.ada.yoroiTransfer.transferRequest.reset(); const accountPath = this.getPath(); try { diff --git a/packages/yoroi-extension/app/stores/ada/send/TrezorSendStore.js b/packages/yoroi-extension/app/stores/ada/send/TrezorSendStore.js index 05da14cdf9..b9425279a7 100644 --- a/packages/yoroi-extension/app/stores/ada/send/TrezorSendStore.js +++ b/packages/yoroi-extension/app/stores/ada/send/TrezorSendStore.js @@ -15,7 +15,6 @@ import type { StoresMap } from '../../index'; import { buildConnectorSignedTransaction } from '../../../api/ada/transactions/shelley/trezorTx'; import { RustModule } from '../../../api/ada/lib/cardanoCrypto/rustLoader'; import { generateRegistrationMetadata } from '../../../api/ada/lib/cardanoCrypto/catalyst'; -import { derivePublicByAddressing } from '../../../api/ada/lib/cardanoCrypto/deriveByAddressing'; import type { Addressing } from '../../../api/ada/lib/storage/models/PublicDeriver/interfaces'; import { getNetworkById } from '../../../api/ada/lib/storage/database/prepackaged/networks.js'; import { broadcastTransaction } from '../../../api/thunk'; diff --git a/packages/yoroi-extension/app/stores/stateless/topbarCategories.js b/packages/yoroi-extension/app/stores/stateless/topbarCategories.js index 9eca74f7c0..8230539433 100644 --- a/packages/yoroi-extension/app/stores/stateless/topbarCategories.js +++ b/packages/yoroi-extension/app/stores/stateless/topbarCategories.js @@ -7,7 +7,6 @@ import { networks } from '../../api/ada/lib/storage/database/prepackaged/network import { ReactComponent as transactionsIcon } from '../../assets/images/wallet-nav/tab-transactions.inline.svg'; import { ReactComponent as sendIcon } from '../../assets/images/wallet-nav/tab-send.inline.svg'; import { ReactComponent as receiveIcon } from '../../assets/images/wallet-nav/tab-receive.inline.svg'; -import { ReactComponent as dashboardIcon } from '../../assets/images/wallet-nav/tab-dashboard.inline.svg'; import { ReactComponent as delegationListIcon } from '../../assets/images/wallet-nav/tab-delegation_list.inline.svg'; import { ReactComponent as votingIcon } from '../../assets/images/wallet-nav/voting.inline.svg'; import { ReactComponent as assetsIcon } from '../../assets/images/assets-page/assets.inline.svg'; From d012f241fcde58ab0f23e77b36441b42c927e7cd Mon Sep 17 00:00:00 2001 From: yushi Date: Thu, 30 Jan 2025 19:56:23 +0800 Subject: [PATCH 08/32] lint --- .../app/api/ada/transactions/shelley/trezorTx.js | 1 - .../app/containers/transfer/UpgradeTxDialogContainer.js | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/yoroi-extension/app/api/ada/transactions/shelley/trezorTx.js b/packages/yoroi-extension/app/api/ada/transactions/shelley/trezorTx.js index 070df46a60..086486d894 100644 --- a/packages/yoroi-extension/app/api/ada/transactions/shelley/trezorTx.js +++ b/packages/yoroi-extension/app/api/ada/transactions/shelley/trezorTx.js @@ -10,7 +10,6 @@ import type { CardanoSignedTxWitness, CardanoSignTransaction, CardanoToken, - CardanoWithdrawal, } from 'trezor-connect-flow/index'; import { CardanoAddressType, diff --git a/packages/yoroi-extension/app/containers/transfer/UpgradeTxDialogContainer.js b/packages/yoroi-extension/app/containers/transfer/UpgradeTxDialogContainer.js index 3000879112..5caccaeccb 100644 --- a/packages/yoroi-extension/app/containers/transfer/UpgradeTxDialogContainer.js +++ b/packages/yoroi-extension/app/containers/transfer/UpgradeTxDialogContainer.js @@ -10,7 +10,7 @@ import { addressToDisplayString, } from '../../api/ada/lib/storage/bridge/utils' import type { TransferTx, } from '../../types/TransferTypes'; -import { genAddressingLookup, allAddressSubgroups } from '../../stores/stateless/addressStores'; +import { allAddressSubgroups } from '../../stores/stateless/addressStores'; import TransferSummaryPage from '../../components/transfer/TransferSummaryPage'; import Dialog from '../../components/widgets/Dialog'; import LegacyTransferLayout from '../../components/transfer/LegacyTransferLayout'; From 77696bc3687a92ef12ebb33ee667af79e6773ff7 Mon Sep 17 00:00:00 2001 From: yushi Date: Thu, 30 Jan 2025 20:03:41 +0800 Subject: [PATCH 09/32] format --- .../app/containers/transfer/UpgradeTxDialogContainer.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/yoroi-extension/app/containers/transfer/UpgradeTxDialogContainer.js b/packages/yoroi-extension/app/containers/transfer/UpgradeTxDialogContainer.js index 5caccaeccb..3f2a328c44 100644 --- a/packages/yoroi-extension/app/containers/transfer/UpgradeTxDialogContainer.js +++ b/packages/yoroi-extension/app/containers/transfer/UpgradeTxDialogContainer.js @@ -7,9 +7,7 @@ import { HaskellShelleyTxSignRequest } from '../../api/ada/transactions/shelley/ import globalMessages from '../../i18n/global-messages'; import type { $npm$ReactIntl$IntlFormat } from 'react-intl'; import { addressToDisplayString, } from '../../api/ada/lib/storage/bridge/utils'; -import type { - TransferTx, -} from '../../types/TransferTypes'; +import type { TransferTx } from '../../types/TransferTypes'; import { allAddressSubgroups } from '../../stores/stateless/addressStores'; import TransferSummaryPage from '../../components/transfer/TransferSummaryPage'; import Dialog from '../../components/widgets/Dialog'; From 7ade897c7fa98f949207f6e11a304bf0694142ff Mon Sep 17 00:00:00 2001 From: yushi Date: Sat, 1 Feb 2025 20:39:53 +0800 Subject: [PATCH 10/32] fix --- .../app/containers/transfer/UpgradeTxDialogContainer.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/yoroi-extension/app/containers/transfer/UpgradeTxDialogContainer.js b/packages/yoroi-extension/app/containers/transfer/UpgradeTxDialogContainer.js index 3f2a328c44..4f0e490dd5 100644 --- a/packages/yoroi-extension/app/containers/transfer/UpgradeTxDialogContainer.js +++ b/packages/yoroi-extension/app/containers/transfer/UpgradeTxDialogContainer.js @@ -8,7 +8,7 @@ import globalMessages from '../../i18n/global-messages'; import type { $npm$ReactIntl$IntlFormat } from 'react-intl'; import { addressToDisplayString, } from '../../api/ada/lib/storage/bridge/utils'; import type { TransferTx } from '../../types/TransferTypes'; -import { allAddressSubgroups } from '../../stores/stateless/addressStores'; +import { genAddressLookup, allAddressSubgroups } from '../../stores/stateless/addressStores'; import TransferSummaryPage from '../../components/transfer/TransferSummaryPage'; import Dialog from '../../components/widgets/Dialog'; import LegacyTransferLayout from '../../components/transfer/LegacyTransferLayout'; From 37d3a906ec84e0448873e70d001111eb809c9c60 Mon Sep 17 00:00:00 2001 From: yushi Date: Sat, 1 Feb 2025 23:44:08 +0800 Subject: [PATCH 11/32] fix send error handling --- .../wallet/send/WalletSendFormRevamp.js | 4 - .../WalletSendPreviewStep.js | 97 ++++++++----------- .../WalletSendPreviewStepContainer.js | 4 - .../app/containers/wallet/WalletSendPage.js | 2 - .../app/stores/ada/send/LedgerSendStore.js | 6 -- .../app/stores/ada/send/TrezorSendStore.js | 6 -- 6 files changed, 41 insertions(+), 78 deletions(-) diff --git a/packages/yoroi-extension/app/components/wallet/send/WalletSendFormRevamp.js b/packages/yoroi-extension/app/components/wallet/send/WalletSendFormRevamp.js index dd289eb587..3ced892e1a 100644 --- a/packages/yoroi-extension/app/components/wallet/send/WalletSendFormRevamp.js +++ b/packages/yoroi-extension/app/components/wallet/send/WalletSendFormRevamp.js @@ -207,8 +207,6 @@ type Props = {| +signRequest: null | ISignRequest, +staleTx: boolean, +isSending: boolean, - +ledgerSendError: null | LocalizableError, - +trezorSendError: null | LocalizableError, +onSubmit: ({| password: string |}) => Promise, |}; @@ -954,8 +952,6 @@ export default class WalletSendFormRevamp extends Component { isSending={this.props.isSending} getTokenInfo={this.props.getTokenInfo} getCurrentPrice={this.props.getCurrentPrice} - ledgerSendError={this.props.ledgerSendError} - trezorSendError={this.props.trezorSendError} selectedExplorer={this.props.selectedExplorer} selectedWallet={this.props.selectedWallet} onSubmit={this.props.onSubmit} diff --git a/packages/yoroi-extension/app/components/wallet/send/WalletSendFormSteps/WalletSendPreviewStep.js b/packages/yoroi-extension/app/components/wallet/send/WalletSendFormSteps/WalletSendPreviewStep.js index 5803c9e088..3ffdb1e8c8 100644 --- a/packages/yoroi-extension/app/components/wallet/send/WalletSendFormSteps/WalletSendPreviewStep.js +++ b/packages/yoroi-extension/app/components/wallet/send/WalletSendFormSteps/WalletSendPreviewStep.js @@ -7,7 +7,7 @@ import type { UnitOfAccountSettingType } from '../../../../types/unitOfAccountTy import type { $npm$ReactIntl$IntlFormat, $npm$ReactIntl$MessageDescriptor } from 'react-intl'; import type { TokenLookupKey, TokenEntry } from '../../../../api/common/lib/MultiToken'; import type { TokenRow, NetworkRow } from '../../../../api/ada/lib/storage/database/primitives/tables'; -import type LocalizableError from '../../../../i18n/LocalizableError'; +import LocalizableError from '../../../../i18n/LocalizableError'; import { observer } from 'mobx-react'; import { defineMessages, intlShape, FormattedMessage } from 'react-intl'; import { SelectedExplorer } from '../../../../domain/SelectedExplorer'; @@ -62,14 +62,12 @@ type Props = {| |}>, +selectedNetwork: $ReadOnly, +walletType: 'trezor' | 'ledger' | 'mnemonic', - +ledgerSendError: ?LocalizableError, - +trezorSendError: ?LocalizableError, +onUpdateStep: (step: number) => void, |}; type State = {| passwordError: string | null, - txError: string | null, + txError: Error | null, |}; const messages = defineMessages({ @@ -152,30 +150,30 @@ export default class WalletSendPreviewStep extends Component { } ); + async _signAndBroadcastTx(password: string): Promise { + try { + await this.props.onSubmit({ password }); + } catch (error) { + if (error instanceof IncorrectWalletPasswordError) { + this.setState({ passwordError: this.context.intl.formatMessage(error, error.values) }); + } else { + this.setState({ txError: error }); + } + } + } + submit(): void { if (this.props.walletType === 'mnemonic') { this.form.submit({ onSuccess: async form => { const { walletPassword } = form.values(); - const transactionData = { - password: walletPassword, - }; - try { - await this.props.onSubmit(transactionData); - } catch (error) { - const errorMessage = this.context.intl.formatMessage(error, error.values); - if (error instanceof IncorrectWalletPasswordError) { - this.setState({ passwordError: errorMessage }); - } else { - this.setState({ txError: errorMessage }); - } - } + this._signAndBroadcastTx(walletPassword); }, onError: () => {}, }); } else { // hw wallets are not using passwords - this.props.onSubmit({ password: '' }); + this._signAndBroadcastTx(''); } } @@ -385,47 +383,34 @@ export default class WalletSendPreviewStep extends Component { }; renderError(): Node { - const { walletType } = this.props; - const { intl } = this.context; - if (walletType === 'mnemonic') { - const { txError } = this.state; - if (txError !== null) { - return this.renderErrorBanner( - 'Transaction error', -
- The transaction cannot be done due to technical reasons. Try again or - - Ask our support team - -
- ); - } + const { txError } = this.state; + if (txError == null) { return null; } - if (walletType === 'trezor') { - const { trezorSendError } = this.props; - if (trezorSendError !== null) { - return this.renderErrorBanner('Transaction error', intl.formatMessage(trezorSendError)); - } - return null; - } - if (walletType === 'ledger') { - const { ledgerSendError } = this.props; - if (ledgerSendError !== null) { - return this.renderErrorBanner('Transaction error', intl.formatMessage(ledgerSendError)); - } - return null; + + const { intl } = this.context; + + if (txError instanceof LocalizableError) { + return this.renderErrorBanner('Transaction error', intl.formatMessage(txError, txError.values)); } - throw new Error('unexpected wallet type'); + return this.renderErrorBanner( + 'Transaction error', +
+ The transaction cannot be done due to technical reasons. Try again or + + Ask our support team + +
+ ); } render(): Node { diff --git a/packages/yoroi-extension/app/components/wallet/send/WalletSendFormSteps/WalletSendPreviewStepContainer.js b/packages/yoroi-extension/app/components/wallet/send/WalletSendFormSteps/WalletSendPreviewStepContainer.js index 98c5852dbe..c5df4f4b46 100644 --- a/packages/yoroi-extension/app/components/wallet/send/WalletSendFormSteps/WalletSendPreviewStepContainer.js +++ b/packages/yoroi-extension/app/components/wallet/send/WalletSendFormSteps/WalletSendPreviewStepContainer.js @@ -33,8 +33,6 @@ type Props = {| +getCurrentPrice: (from: string, to: string) => ?string, +getTokenInfo: ($ReadOnly>) => $ReadOnly, +isSending: boolean, - +ledgerSendError: null | LocalizableError, - +trezorSendError: null | LocalizableError, selectedExplorer: Map, +selectedWallet: WalletState, +onSubmit: ({| password: string |}) => Promise, @@ -105,8 +103,6 @@ export default class WalletSendPreviewStepContainer extends Component { plannedTxInfoMap={this.props.plannedTxInfoMap} minAda={this.props.minAda} walletType={selectedWallet.type} - ledgerSendError={this.props.ledgerSendError} - trezorSendError={this.props.trezorSendError} onUpdateStep={onUpdateStep} /> ); diff --git a/packages/yoroi-extension/app/containers/wallet/WalletSendPage.js b/packages/yoroi-extension/app/containers/wallet/WalletSendPage.js index 3f6aa8bada..df50623181 100644 --- a/packages/yoroi-extension/app/containers/wallet/WalletSendPage.js +++ b/packages/yoroi-extension/app/containers/wallet/WalletSendPage.js @@ -204,8 +204,6 @@ export default class WalletSendPage extends Component { signRequest={transactionBuilderStore.tentativeTx} staleTx={transactionBuilderStore.txMismatch} isSending={stores.wallets.sendMoneyRequest.isExecuting} - ledgerSendError={stores.substores.ada.ledgerSend.error || null} - trezorSendError={stores.substores.ada.trezorSend.error || null} onSubmit={this.onSubmit.bind(this)} /> {this.renderDialog()} diff --git a/packages/yoroi-extension/app/stores/ada/send/LedgerSendStore.js b/packages/yoroi-extension/app/stores/ada/send/LedgerSendStore.js index a7c3880bb3..9504d11c3c 100644 --- a/packages/yoroi-extension/app/stores/ada/send/LedgerSendStore.js +++ b/packages/yoroi-extension/app/stores/ada/send/LedgerSendStore.js @@ -42,7 +42,6 @@ export default class LedgerSendStore extends Store { // =================== VIEW RELATED =================== // // TODO: consider getting rid of both of these @observable isActionProcessing: boolean = false; - @observable error: ?LocalizableError; // =================== VIEW RELATED =================== // /** setup() is called when stores are being created @@ -53,7 +52,6 @@ export default class LedgerSendStore extends Store { _reset(): void { this._setActionProcessing(false); - this._setError(null); } _preSendValidation: void => void = () => { @@ -290,8 +288,4 @@ export default class LedgerSendStore extends Store { @action _setActionProcessing: boolean => void = (processing) => { this.isActionProcessing = processing; } - - @action _setError: ?LocalizableError => void = (error) => { - this.error = error; - } } diff --git a/packages/yoroi-extension/app/stores/ada/send/TrezorSendStore.js b/packages/yoroi-extension/app/stores/ada/send/TrezorSendStore.js index b9425279a7..bead7edc5e 100644 --- a/packages/yoroi-extension/app/stores/ada/send/TrezorSendStore.js +++ b/packages/yoroi-extension/app/stores/ada/send/TrezorSendStore.js @@ -32,14 +32,12 @@ export default class TrezorSendStore extends Store { // =================== VIEW RELATED =================== // // TODO: consider getting rid of both of these @observable isActionProcessing: boolean = false; - @observable error: ?LocalizableError; // =================== VIEW RELATED =================== // // =================== API RELATED =================== // reset: void => void = () => { this._setActionProcessing(false); - this._setError(null); } signAndBroadcastFromWallet: {| @@ -221,8 +219,4 @@ export default class TrezorSendStore extends Store { @action _setActionProcessing: boolean => void = (processing) => { this.isActionProcessing = processing; } - - @action _setError: (?LocalizableError) => void = (error) => { - this.error = error; - } } From 3097d0c774d4fa14ad855964b18fc45ebcf6bf49 Mon Sep 17 00:00:00 2001 From: yushi Date: Mon, 3 Feb 2025 16:28:43 +0800 Subject: [PATCH 12/32] fix change address path --- packages/yoroi-extension/app/api/ada/index.js | 3 +++ .../api/ada/transactions/shelley/ledgerTx.js | 8 ++++++-- .../ada/transactions/shelley/ledgerTx.test.js | 1 + .../api/ada/transactions/shelley/trezorTx.js | 7 ++++++- .../ada/transactions/shelley/trezorTx.test.js | 1 + .../app/connector/stores/ConnectorStore.js | 2 ++ .../app/stores/ada/AdaWalletsStore.js | 18 ++++++++++++++---- .../app/stores/ada/send/LedgerSendStore.js | 9 +++++++-- .../app/stores/ada/send/TrezorSendStore.js | 9 +++++++-- 9 files changed, 47 insertions(+), 11 deletions(-) diff --git a/packages/yoroi-extension/app/api/ada/index.js b/packages/yoroi-extension/app/api/ada/index.js index 3d19ca8796..8166cb8128 100644 --- a/packages/yoroi-extension/app/api/ada/index.js +++ b/packages/yoroi-extension/app/api/ada/index.js @@ -254,6 +254,7 @@ export type CreateHWSignTxDataRequestFromRawTx = {| txBodyHex: string, network: $ReadOnly, addressingMap: string => (void | $PropertyType), + changeAddrs: Array<{| ...Address, ...Value, ...Addressing |}>, senderUtxos: Array, additionalRequiredSigners?: Array, ledgerSupportsCip36?: boolean, @@ -770,6 +771,7 @@ export default class AdaApi { Number(config.ChainNetworkId), protocolMagic, addressMap, + request.changeAddrs ?? [], request.senderUtxos, request.additionalRequiredSigners ?? [], request.ledgerSupportsCip36, @@ -786,6 +788,7 @@ export default class AdaApi { Number(config.ChainNetworkId), protocolMagic, addressMap, + request.changeAddrs ?? [], request.senderUtxos, request.catalystData, ); diff --git a/packages/yoroi-extension/app/api/ada/transactions/shelley/ledgerTx.js b/packages/yoroi-extension/app/api/ada/transactions/shelley/ledgerTx.js index 2efc8f6ec2..7439809b13 100644 --- a/packages/yoroi-extension/app/api/ada/transactions/shelley/ledgerTx.js +++ b/packages/yoroi-extension/app/api/ada/transactions/shelley/ledgerTx.js @@ -29,7 +29,7 @@ import { TxOutputFormat, TxRequiredSignerType, } from '@cardano-foundation/ledgerjs-hw-app-cardano'; -import type { Addressing } from '../../lib/storage/models/PublicDeriver/interfaces'; +import type { Addressing, Address, Value } from '../../lib/storage/models/PublicDeriver/interfaces'; import type { LedgerNanoCatalystRegistrationTxSignData } from './HaskellShelleyTxSignRequest'; import { RustModule } from '../../lib/cardanoCrypto/rustLoader'; import { derivePublicByAddressing } from '../../lib/cardanoCrypto/deriveByAddressing'; @@ -462,6 +462,9 @@ export function toLedgerSignRequest( networkId: number, protocolMagic: number, ownAddressMap: AddressMap, + // when sending money, `ownAddressMap` doesn't contain the change address, so we need to + // pass it in explicitly + changeAddrs: Array<{| ...Address, ...Value, ...Addressing |}>, senderUtxos: Array, additionalRequiredSigners: Array = [], ledgerSupportsCip36?: boolean, @@ -541,7 +544,8 @@ export function toLedgerSignRequest( networkId, baseAddr.payment_cred() ).to_address().to_hex(); - const ownPaymentPath = ownAddressMap(paymentAddress); + const ownPaymentPath = ownAddressMap(paymentAddress) || + changeAddrs.find(({ address }) => address === addr.to_hex())?.addressing.path; if (ownPaymentPath) { const stake = baseAddr.stake_cred(); const stakeAddr = RustModule.WalletV4.RewardAddress.new( diff --git a/packages/yoroi-extension/app/api/ada/transactions/shelley/ledgerTx.test.js b/packages/yoroi-extension/app/api/ada/transactions/shelley/ledgerTx.test.js index 93a001a3b3..ec6e7b11bd 100644 --- a/packages/yoroi-extension/app/api/ada/transactions/shelley/ledgerTx.test.js +++ b/packages/yoroi-extension/app/api/ada/transactions/shelley/ledgerTx.test.js @@ -330,6 +330,7 @@ test('Create Ledger transaction', async () => { } return undefined; }, + [], senderUtxos, ); diff --git a/packages/yoroi-extension/app/api/ada/transactions/shelley/trezorTx.js b/packages/yoroi-extension/app/api/ada/transactions/shelley/trezorTx.js index 086486d894..ad31f37f5b 100644 --- a/packages/yoroi-extension/app/api/ada/transactions/shelley/trezorTx.js +++ b/packages/yoroi-extension/app/api/ada/transactions/shelley/trezorTx.js @@ -20,6 +20,7 @@ import { CardanoTxWitnessType, CardanoDRepType, } from 'trezor-connect-flow'; +import type { Addressing, Address, Value } from '../../lib/storage/models/PublicDeriver/interfaces'; import type { TrezorTCatalystRegistrationTxSignData } from './HaskellShelleyTxSignRequest'; import { RustModule } from '../../lib/cardanoCrypto/rustLoader'; import { bytesToHex, iterateLenGet, iterateLenGetMap, forceNonNull, hexToBytes } from '../../../../coreUtils'; @@ -208,6 +209,9 @@ export function toTrezorSignRequest( networkId: number, protocolMagic: number, ownAddressMap: AddressMap, + // when sending money, `ownAddressMap` doesn't contain the change address, so we need to + // pass it in explicitly + changeAddrs: Array<{| ...Address, ...Value, ...Addressing |}>, senderUtxos: Array, catalystData?: TrezorTCatalystRegistrationTxSignData, ): $Exact { @@ -293,7 +297,8 @@ export function toTrezorSignRequest( networkId, baseAddr.payment_cred() ).to_address().to_hex(); - const ownPaymentPath = ownAddressMap(paymentAddress); + const ownPaymentPath = ownAddressMap(paymentAddress) || + changeAddrs.find(({ address }) => address === addr.to_hex())?.addressing.path; if (ownPaymentPath) { const stake = baseAddr.stake_cred(); const stakeAddr = RustModule.WalletV4.RewardAddress.new( diff --git a/packages/yoroi-extension/app/api/ada/transactions/shelley/trezorTx.test.js b/packages/yoroi-extension/app/api/ada/transactions/shelley/trezorTx.test.js index c5823da497..748b70cee2 100644 --- a/packages/yoroi-extension/app/api/ada/transactions/shelley/trezorTx.test.js +++ b/packages/yoroi-extension/app/api/ada/transactions/shelley/trezorTx.test.js @@ -210,6 +210,7 @@ test('Create Trezor transaction', async () => { Number.parseInt(ChainNetworkId, 10), ByronNetworkId, _address => [2147483692, 2147485463, 2147483648, 2, 0], + [], senderUtxos, ); expect(response).toStrictEqual({ diff --git a/packages/yoroi-extension/app/connector/stores/ConnectorStore.js b/packages/yoroi-extension/app/connector/stores/ConnectorStore.js index 1123405f01..4584aefc47 100644 --- a/packages/yoroi-extension/app/connector/stores/ConnectorStore.js +++ b/packages/yoroi-extension/app/connector/stores/ConnectorStore.js @@ -1034,6 +1034,7 @@ export default class ConnectorStore extends Store { Number(config.ChainNetworkId), config.ByronNetworkId, s => ownAddressMap[s], + [], addressedUtxos, ); } catch (e) { @@ -1113,6 +1114,7 @@ export default class ConnectorStore extends Store { Number(config.ChainNetworkId), config.ByronNetworkId, s => ownAddressMap[s], + [], addressedUtxos, additionalRequiredSigners, ); diff --git a/packages/yoroi-extension/app/stores/ada/AdaWalletsStore.js b/packages/yoroi-extension/app/stores/ada/AdaWalletsStore.js index 7025a876c2..8abd4d495b 100644 --- a/packages/yoroi-extension/app/stores/ada/AdaWalletsStore.js +++ b/packages/yoroi-extension/app/stores/ada/AdaWalletsStore.js @@ -146,14 +146,24 @@ export default class AdaWalletsStore extends Store { |}) => Promise<{| signedTxHex: string |}> = async request => { if (request.signRequest.ledger) { const { wallet, transactionHex } = request.signRequest.ledger; - const { signedTxHex } = await this.stores.substores.ada.ledgerSend - .signRawTxFromWallet({ rawTxHex: transactionHex, wallet }); + const { signedTxHex } = await this.stores.substores.ada.ledgerSend.signRawTxFromWallet({ + rawTxHex: transactionHex, + wallet, + // by happenstance the use case of this function is not to send + // money while getting the change so there is no change address + changeAddrs: [], + }); return { signedTxHex }; } if (request.signRequest.trezor) { const { wallet, transactionHex } = request.signRequest.trezor; - const { signedTxHex } = await this.stores.substores.ada.trezorSend - .signRawTxFromWallet({ rawTxHex: transactionHex, wallet }); + const { signedTxHex } = await this.stores.substores.ada.trezorSend.signRawTxFromWallet({ + rawTxHex: transactionHex, + wallet, + // by happenstance the use case of this function is not to send + // money while getting the change so there is no change address + changeAddrs: [], + }); return { signedTxHex }; } if (request.signRequest.normal) { diff --git a/packages/yoroi-extension/app/stores/ada/send/LedgerSendStore.js b/packages/yoroi-extension/app/stores/ada/send/LedgerSendStore.js index 9504d11c3c..c166648e71 100644 --- a/packages/yoroi-extension/app/stores/ada/send/LedgerSendStore.js +++ b/packages/yoroi-extension/app/stores/ada/send/LedgerSendStore.js @@ -20,7 +20,7 @@ import type { HaskellShelleyTxSignRequest, LedgerNanoCatalystRegistrationTxSignData, } from '../../../api/ada/transactions/shelley/HaskellShelleyTxSignRequest'; -import type { Addressing, } from '../../../api/ada/lib/storage/models/PublicDeriver/interfaces'; +import type { Addressing, Address, Value } from '../../../api/ada/lib/storage/models/PublicDeriver/interfaces'; import { genAddressingLookup } from '../../stateless/addressStores'; import type { StoresMap } from '../../index'; import { @@ -79,7 +79,8 @@ export default class LedgerSendStore extends Store { const { signedTxHex, txId, metadata } = await this.signRawTxFromWallet({ rawTxHex: request.signRequest.self().build_tx().to_hex(), wallet: request.wallet, - catalystData: request.signRequest.ledgerNanoCatalystRegistrationTxSignData + catalystData: request.signRequest.ledgerNanoCatalystRegistrationTxSignData, + changeAddrs: request.signRequest.changeAddr, }); if (metadata) { @@ -108,6 +109,7 @@ export default class LedgerSendStore extends Store { hardwareWalletDeviceId: ?string, ... }, + changeAddrs: Array<{| ...Address, ...Value, ...Addressing |}>, catalystData?: LedgerNanoCatalystRegistrationTxSignData, |} => Promise<{| signedTxHex: string, @@ -136,6 +138,7 @@ export default class LedgerSendStore extends Store { rawTxHex: request.rawTxHex, publicKey: publicKeyInfo, addressingMap, + changeAddrs: request.changeAddrs, expectedSerial, networkId: request.wallet.networkId, catalystData: request.catalystData, @@ -154,6 +157,7 @@ export default class LedgerSendStore extends Store { ...Addressing, |}, addressingMap: string => (void | $PropertyType), + changeAddrs: Array<{| ...Address, ...Value, ...Addressing |}>, networkId: number, expectedSerial: string | void, catalystData?: LedgerNanoCatalystRegistrationTxSignData, @@ -195,6 +199,7 @@ export default class LedgerSendStore extends Store { senderUtxos: addressedUtxos, ledgerSupportsCip36, catalystData: request.catalystData, + changeAddrs: request.changeAddrs, }); const ledgerSignTxPayload = response.hw === 'ledger' ? response.result.ledgerSignTxPayload diff --git a/packages/yoroi-extension/app/stores/ada/send/TrezorSendStore.js b/packages/yoroi-extension/app/stores/ada/send/TrezorSendStore.js index bead7edc5e..fbde1919fb 100644 --- a/packages/yoroi-extension/app/stores/ada/send/TrezorSendStore.js +++ b/packages/yoroi-extension/app/stores/ada/send/TrezorSendStore.js @@ -15,7 +15,7 @@ import type { StoresMap } from '../../index'; import { buildConnectorSignedTransaction } from '../../../api/ada/transactions/shelley/trezorTx'; import { RustModule } from '../../../api/ada/lib/cardanoCrypto/rustLoader'; import { generateRegistrationMetadata } from '../../../api/ada/lib/cardanoCrypto/catalyst'; -import type { Addressing } from '../../../api/ada/lib/storage/models/PublicDeriver/interfaces'; +import type { Addressing, Address, Value } from '../../../api/ada/lib/storage/models/PublicDeriver/interfaces'; import { getNetworkById } from '../../../api/ada/lib/storage/database/prepackaged/networks.js'; import { broadcastTransaction } from '../../../api/thunk'; import { transactionHexToBodyHex } from '../../../api/ada/lib/cardanoCrypto/utils'; @@ -57,7 +57,8 @@ export default class TrezorSendStore extends Store { const { signedTxHex, txId, metadata } = await this.signRawTxFromWallet({ rawTxHex: request.signRequest.self().build_tx().to_hex(), wallet: request.wallet, - catalystData: request.signRequest.trezorTCatalystRegistrationTxSignData + catalystData: request.signRequest.trezorTCatalystRegistrationTxSignData, + changeAddrs: request.signRequest.changeAddr, }); if (metadata) { @@ -86,6 +87,7 @@ export default class TrezorSendStore extends Store { stakingAddressing: Addressing, ... }, + changeAddrs: Array<{| ...Address, ...Value, ...Addressing |}>, catalystData?: TrezorTCatalystRegistrationTxSignData, |} => Promise<{| signedTxHex: string, @@ -105,6 +107,7 @@ export default class TrezorSendStore extends Store { return this.signRawTx({ rawTxHex, addressingMap, + changeAddrs: request.changeAddrs, networkId: wallet.networkId, catalystData, }); @@ -118,6 +121,7 @@ export default class TrezorSendStore extends Store { signRawTx: {| rawTxHex: string, addressingMap: string => (void | $PropertyType), + changeAddrs: Array<{| ...Address, ...Value, ...Addressing |}>, networkId: number, catalystData?: TrezorTCatalystRegistrationTxSignData, |} => Promise<{| @@ -138,6 +142,7 @@ export default class TrezorSendStore extends Store { addressingMap: request.addressingMap, senderUtxos: addressedUtxos, catalystData: request.catalystData, + changeAddrs: request.changeAddrs, }); const trezorSignTxPayload = response.hw === 'trezor' ? response.result.trezorSignTxPayload From 3d1afd10a7cd900f841c290bf543e81dc88ebce7 Mon Sep 17 00:00:00 2001 From: yushi Date: Mon, 3 Feb 2025 23:44:17 +0800 Subject: [PATCH 13/32] fix: withdraw dialog not closed after tx submission --- .../app/containers/wallet/staking/WithdrawRewardsDialog.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/yoroi-extension/app/containers/wallet/staking/WithdrawRewardsDialog.js b/packages/yoroi-extension/app/containers/wallet/staking/WithdrawRewardsDialog.js index 7b1162e028..b0ab8c9b40 100644 --- a/packages/yoroi-extension/app/containers/wallet/staking/WithdrawRewardsDialog.js +++ b/packages/yoroi-extension/app/containers/wallet/staking/WithdrawRewardsDialog.js @@ -94,7 +94,9 @@ export default class WithdrawRewardsDialog extends Component<{| ...StoresProps, wallet, signRequest, password, - callback: async () => {}, + callback: async () => { + stores.uiDialogs.closeActiveDialog(); + }, }); } From 586eea6adcd15d54e31ad8741e1496dca93250ec Mon Sep 17 00:00:00 2001 From: yushi Date: Mon, 3 Feb 2025 23:51:26 +0800 Subject: [PATCH 14/32] close transfer dialog after transaction submission --- .../app/containers/transfer/TransferSendPage.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/yoroi-extension/app/containers/transfer/TransferSendPage.js b/packages/yoroi-extension/app/containers/transfer/TransferSendPage.js index 4038dcf534..5cf4308e43 100644 --- a/packages/yoroi-extension/app/containers/transfer/TransferSendPage.js +++ b/packages/yoroi-extension/app/containers/transfer/TransferSendPage.js @@ -87,7 +87,9 @@ export default class TransferSendPage extends Component<{| ...StoresProps, ...Lo wallet, signRequest, password, - callback: async () => {}, + callback: async () => { + stores.uiDialogs.closeActiveDialog(); + }, }); if (stores.wallets.sendMoneyRequest.error == null) { this.props.onSubmit.trigger(); From 77caf3541d726aa86034b2919dc83da005d99651 Mon Sep 17 00:00:00 2001 From: yushi Date: Tue, 4 Feb 2025 00:08:40 +0800 Subject: [PATCH 15/32] fix catalyst registration --- .../app/api/ada/transactions/shelley/ledgerTx.js | 5 ++--- .../app/api/ada/transactions/shelley/trezorTx.js | 5 ++--- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/packages/yoroi-extension/app/api/ada/transactions/shelley/ledgerTx.js b/packages/yoroi-extension/app/api/ada/transactions/shelley/ledgerTx.js index 7439809b13..07fd578188 100644 --- a/packages/yoroi-extension/app/api/ada/transactions/shelley/ledgerTx.js +++ b/packages/yoroi-extension/app/api/ada/transactions/shelley/ledgerTx.js @@ -735,10 +735,9 @@ export function toLedgerSignRequest( }; } + // note: we know that `catelystData` is only used for voting in the extension and there + // should be no other auxiliary data in this scenario so we just overwrite the auxiliary data if (catalystData) { - if (formattedAuxiliaryData) { - throw new Error('unexpected catalyst signing data'); - } const { votingPublicKey, stakingKeyPath, nonce, paymentKeyPath, } = catalystData; if (ledgerSupportsCip36) { diff --git a/packages/yoroi-extension/app/api/ada/transactions/shelley/trezorTx.js b/packages/yoroi-extension/app/api/ada/transactions/shelley/trezorTx.js index ad31f37f5b..6bc1b01b9b 100644 --- a/packages/yoroi-extension/app/api/ada/transactions/shelley/trezorTx.js +++ b/packages/yoroi-extension/app/api/ada/transactions/shelley/trezorTx.js @@ -460,10 +460,9 @@ export function toTrezorSignRequest( }; } + // note: we know that `catelystData` is only used for voting in the extension and there + // should be no other auxiliary data in this scenario so we just overwrite the auxiliary data if (catalystData) { - if (formattedAuxiliaryData) { - throw new Error('unexpected catalyst signing data'); - } const { votingPublicKey, nonce, paymentKeyPath, stakingKeyPath } = catalystData; formattedAuxiliaryData = { cVoteRegistrationParameters: { From 7e6dce4408f567c4259bcb356f13ff889c375bc0 Mon Sep 17 00:00:00 2001 From: yushi Date: Tue, 4 Feb 2025 19:11:35 +0800 Subject: [PATCH 16/32] fix "reference inputs not allowed in TransactionSigningMode.ORDINARY_TRANSACTION" --- .../app/api/ada/transactions/shelley/ledgerTx.js | 2 +- .../app/api/ada/transactions/shelley/trezorTx.js | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/yoroi-extension/app/api/ada/transactions/shelley/ledgerTx.js b/packages/yoroi-extension/app/api/ada/transactions/shelley/ledgerTx.js index 07fd578188..89347c74c7 100644 --- a/packages/yoroi-extension/app/api/ada/transactions/shelley/ledgerTx.js +++ b/packages/yoroi-extension/app/api/ada/transactions/shelley/ledgerTx.js @@ -805,7 +805,7 @@ export function toLedgerSignRequest( } let signingMode = TransactionSigningMode.ORDINARY_TRANSACTION; - if (formattedCollateral) { + if (formattedCollateral || formattedReferenceInputs) { signingMode = TransactionSigningMode.PLUTUS_TRANSACTION; } diff --git a/packages/yoroi-extension/app/api/ada/transactions/shelley/trezorTx.js b/packages/yoroi-extension/app/api/ada/transactions/shelley/trezorTx.js index 6bc1b01b9b..dceb1a1256 100644 --- a/packages/yoroi-extension/app/api/ada/transactions/shelley/trezorTx.js +++ b/packages/yoroi-extension/app/api/ada/transactions/shelley/trezorTx.js @@ -538,7 +538,6 @@ export function toTrezorSignRequest( } if (formattedCollateral) { result.collateralInputs = formattedCollateral; - result.signingMode = CardanoTxSigningMode.PLUTUS_TRANSACTION; } if (requiredSigners) { result.requiredSigners = formattedRequiredSigners; @@ -569,6 +568,9 @@ export function toTrezorSignRequest( result.tagCborSets = true; } + if (formattedCollateral || referenceInputs) { + result.signingMode = CardanoTxSigningMode.PLUTUS_TRANSACTION; + } return result; } From 7c62d6a9b600515cec907eb6a4d7fadd5acf2100 Mon Sep 17 00:00:00 2001 From: yushi Date: Wed, 5 Feb 2025 11:20:42 +0800 Subject: [PATCH 17/32] fix catalyst voting transaction --- .../app/api/ada/transactions/shelley/ledgerTx.js | 5 ++++- .../app/api/ada/transactions/shelley/trezorTx.js | 3 +++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/packages/yoroi-extension/app/api/ada/transactions/shelley/ledgerTx.js b/packages/yoroi-extension/app/api/ada/transactions/shelley/ledgerTx.js index 89347c74c7..265f0edb62 100644 --- a/packages/yoroi-extension/app/api/ada/transactions/shelley/ledgerTx.js +++ b/packages/yoroi-extension/app/api/ada/transactions/shelley/ledgerTx.js @@ -859,6 +859,9 @@ export function buildConnectorSignedTransaction( const fixedTx = RustModule.WalletV4.FixedTransaction.from_hex(rawTxHex); if (metadata) { fixedTx.set_auxiliary_data(metadata.to_bytes()); + const body = fixedTx.body(); + body.set_auxiliary_data_hash(RustModule.WalletV4.hash_auxiliary_data(metadata)); + fixedTx.set_body(body.to_bytes()); } const keyLevel = publicKey.addressing.startLevel + publicKey.addressing.path.length - 1; @@ -883,7 +886,7 @@ export function buildConnectorSignedTransaction( fixedTx.add_vkey_witness(vkeyWit); } - + debugger const txHex = fixedTx.to_hex(); return { txHex, txId: transactionHexToHash(txHex)}; } diff --git a/packages/yoroi-extension/app/api/ada/transactions/shelley/trezorTx.js b/packages/yoroi-extension/app/api/ada/transactions/shelley/trezorTx.js index dceb1a1256..51ebc8dd4c 100644 --- a/packages/yoroi-extension/app/api/ada/transactions/shelley/trezorTx.js +++ b/packages/yoroi-extension/app/api/ada/transactions/shelley/trezorTx.js @@ -583,6 +583,9 @@ export function buildConnectorSignedTransaction( const fixedTx = RustModule.WalletV4.FixedTransaction.from_hex(rawTxHex); if (metadata) { fixedTx.set_auxiliary_data(metadata.to_bytes()); + const body = fixedTx.body(); + body.set_auxiliary_data_hash(RustModule.WalletV4.hash_auxiliary_data(metadata)); + fixedTx.set_body(body.to_bytes()); } for (const witness of witnesses) { From 73e5f88d031349bcebc7f7357c74cb9d6ccbcec2 Mon Sep 17 00:00:00 2001 From: yushi Date: Wed, 5 Feb 2025 12:04:31 +0800 Subject: [PATCH 18/32] lint --- .../app/api/ada/transactions/shelley/ledgerTx.js | 1 - .../WalletSendFormSteps/WalletSendPreviewStepContainer.js | 1 - .../app/stores/ada/send/LedgerSendStore.js | 8 -------- .../app/stores/ada/send/TrezorSendStore.js | 3 --- 4 files changed, 13 deletions(-) diff --git a/packages/yoroi-extension/app/api/ada/transactions/shelley/ledgerTx.js b/packages/yoroi-extension/app/api/ada/transactions/shelley/ledgerTx.js index 265f0edb62..7a2ddc55ca 100644 --- a/packages/yoroi-extension/app/api/ada/transactions/shelley/ledgerTx.js +++ b/packages/yoroi-extension/app/api/ada/transactions/shelley/ledgerTx.js @@ -886,7 +886,6 @@ export function buildConnectorSignedTransaction( fixedTx.add_vkey_witness(vkeyWit); } - debugger const txHex = fixedTx.to_hex(); return { txHex, txId: transactionHexToHash(txHex)}; } diff --git a/packages/yoroi-extension/app/components/wallet/send/WalletSendFormSteps/WalletSendPreviewStepContainer.js b/packages/yoroi-extension/app/components/wallet/send/WalletSendFormSteps/WalletSendPreviewStepContainer.js index c5df4f4b46..d2a33fbb8a 100644 --- a/packages/yoroi-extension/app/components/wallet/send/WalletSendFormSteps/WalletSendPreviewStepContainer.js +++ b/packages/yoroi-extension/app/components/wallet/send/WalletSendFormSteps/WalletSendPreviewStepContainer.js @@ -4,7 +4,6 @@ import { Component } from 'react'; import { observer } from 'mobx-react'; import WalletSendPreviewStep from './WalletSendPreviewStep'; import type { UnitOfAccountSettingType } from '../../../../types/unitOfAccountType'; -import LocalizableError from '../../../../i18n/LocalizableError'; import { SelectedExplorer } from '../../../../domain/SelectedExplorer'; import { addressToDisplayString } from '../../../../api/ada/lib/storage/bridge/utils'; import type { ISignRequest } from '../../../../api/common/lib/transactions/ISignRequest'; diff --git a/packages/yoroi-extension/app/stores/ada/send/LedgerSendStore.js b/packages/yoroi-extension/app/stores/ada/send/LedgerSendStore.js index c166648e71..18557709a0 100644 --- a/packages/yoroi-extension/app/stores/ada/send/LedgerSendStore.js +++ b/packages/yoroi-extension/app/stores/ada/send/LedgerSendStore.js @@ -1,19 +1,11 @@ // @flow import { action, observable } from 'mobx'; - import type { SignTransactionResponse as LedgerSignTxResponse } from '@cardano-foundation/ledgerjs-hw-app-cardano'; import { TxAuxiliaryDataSupplementType } from '@cardano-foundation/ledgerjs-hw-app-cardano'; - import Store from '../../base/Store'; - -import LocalizableError from '../../../i18n/LocalizableError'; - import { convertToLocalizableError } from '../../../domain/LedgerLocalizedError'; - import { Logger, stringifyData, stringifyError, } from '../../../utils/logging'; - import { buildConnectorSignedTransaction } from '../../../api/ada/transactions/shelley/ledgerTx'; - import { LedgerConnect } from '../../../utils/hwConnectHandler'; import { RustModule } from '../../../api/ada/lib/cardanoCrypto/rustLoader'; import type { diff --git a/packages/yoroi-extension/app/stores/ada/send/TrezorSendStore.js b/packages/yoroi-extension/app/stores/ada/send/TrezorSendStore.js index fbde1919fb..abbc649f44 100644 --- a/packages/yoroi-extension/app/stores/ada/send/TrezorSendStore.js +++ b/packages/yoroi-extension/app/stores/ada/send/TrezorSendStore.js @@ -1,12 +1,9 @@ // @flow import { action, observable } from 'mobx'; - import Store from '../../base/Store'; - import { wrapWithFrame } from '../../lib/TrezorWrapper'; import { Logger, stringifyData, stringifyError, } from '../../../utils/logging'; import { convertToLocalizableError } from '../../../domain/TrezorLocalizedError'; -import LocalizableError from '../../../i18n/LocalizableError'; import type { HaskellShelleyTxSignRequest, TrezorTCatalystRegistrationTxSignData, From 16a339afe768a0b9567d40274168104198fa3a72 Mon Sep 17 00:00:00 2001 From: yushi Date: Thu, 20 Feb 2025 14:58:01 +0800 Subject: [PATCH 19/32] fix Byron transfer for ledger --- .../api/ada/transactions/shelley/ledgerTx.js | 30 +++++++++++++++---- .../transfer/UpgradeTxDialogContainer.js | 8 ++++- .../app/stores/ada/send/LedgerSendStore.js | 15 +++++++++- 3 files changed, 46 insertions(+), 7 deletions(-) diff --git a/packages/yoroi-extension/app/api/ada/transactions/shelley/ledgerTx.js b/packages/yoroi-extension/app/api/ada/transactions/shelley/ledgerTx.js index 7a2ddc55ca..deb83f1824 100644 --- a/packages/yoroi-extension/app/api/ada/transactions/shelley/ledgerTx.js +++ b/packages/yoroi-extension/app/api/ada/transactions/shelley/ledgerTx.js @@ -43,6 +43,7 @@ import { maybe } from '../../../../coreUtils'; import { transactionHexToHash } from '../../lib/cardanoCrypto/utils'; +import { WalletTypePurpose } from '../../../../config/numbersConfig'; // ==================== LEDGER ==================== // function toLedgerTokenBundle( @@ -854,6 +855,8 @@ export function buildConnectorSignedTransaction( key: RustModule.WalletV4.Bip32PublicKey, |}, metadata: ?RustModule.WalletV4.AuxiliaryData, + // to support transfering from Byron address when initializing Ledger wallets + pathToReceiverMapping: Map = new Map(), ): {| txHex: string, txId: string |} { const fixedTx = RustModule.WalletV4.FixedTransaction.from_hex(rawTxHex); @@ -879,12 +882,29 @@ export function buildConnectorSignedTransaction( key: publicKey.key, } }); - const vkeyWit = RustModule.WalletV4.Vkeywitness.new( - RustModule.WalletV4.Vkey.new(witnessKey.to_raw_key()), - RustModule.WalletV4.Ed25519Signature.from_hex(witness.witnessSignatureHex), - ); + if (witness.path[0] === WalletTypePurpose.BIP44) { + const receiver = pathToReceiverMapping.get(witness.path.join('/')); + if (!receiver) { + throw new Error('unexpectedly missing Byron path'); + } + const bootstrapWit = RustModule.WalletV4.BootstrapWitness.new( + RustModule.WalletV4.Vkey.new(witnessKey.to_raw_key()), + RustModule.WalletV4.Ed25519Signature.from_hex(witness.witnessSignatureHex), + witnessKey.chaincode(), + RustModule.WalletV4.ByronAddress.from_base58(receiver).attributes(), + ); - fixedTx.add_vkey_witness(vkeyWit); + fixedTx.add_bootstrap_witness(bootstrapWit); + } else if (witness.path[0] === WalletTypePurpose.CIP1852) { + const vkeyWit = RustModule.WalletV4.Vkeywitness.new( + RustModule.WalletV4.Vkey.new(witnessKey.to_raw_key()), + RustModule.WalletV4.Ed25519Signature.from_hex(witness.witnessSignatureHex), + ); + + fixedTx.add_vkey_witness(vkeyWit); + } else { + throw new Error(`unexpected witness path purpose value ${witness.path[0]}`); + } } const txHex = fixedTx.to_hex(); return { txHex, txId: transactionHexToHash(txHex)}; diff --git a/packages/yoroi-extension/app/containers/transfer/UpgradeTxDialogContainer.js b/packages/yoroi-extension/app/containers/transfer/UpgradeTxDialogContainer.js index 4f0e490dd5..ea7734444d 100644 --- a/packages/yoroi-extension/app/containers/transfer/UpgradeTxDialogContainer.js +++ b/packages/yoroi-extension/app/containers/transfer/UpgradeTxDialogContainer.js @@ -150,7 +150,13 @@ export default class UpgradeTxDialogContainer extends Component<{| ...StoresProp trigger: async () => { await this.props.stores.substores.ada.ledgerSend.signAndBroadcastFromWallet({ signRequest: tentativeTx.signRequest, - wallet: selected, + wallet: { + ...selected, + // when transfering ledger wallet Byron Utxos to Shelley, we should use the + // Byron public key + publicKey: tentativeTx.publicKey.key.to_hex(), + pathToPublic: tentativeTx.publicKey.addressing.path, + }, }); this.props.onSubmit(); }, diff --git a/packages/yoroi-extension/app/stores/ada/send/LedgerSendStore.js b/packages/yoroi-extension/app/stores/ada/send/LedgerSendStore.js index 18557709a0..bcd5b85cb1 100644 --- a/packages/yoroi-extension/app/stores/ada/send/LedgerSendStore.js +++ b/packages/yoroi-extension/app/stores/ada/send/LedgerSendStore.js @@ -24,6 +24,7 @@ import { broadcastTransaction } from '../../../api/thunk'; import { transactionHexToBodyHex } from '../../../api/ada/lib/cardanoCrypto/utils'; import { fail } from '../../../coreUtils'; import type { ISignRequest } from '../../../api/common/lib/transactions/ISignRequest'; +import type { CardanoAddressedUtxo } from '../../../api/ada/transactions/types'; export type SendUsingLedgerParams = {| signRequest: ISignRequest, @@ -73,6 +74,7 @@ export default class LedgerSendStore extends Store { wallet: request.wallet, catalystData: request.signRequest.ledgerNanoCatalystRegistrationTxSignData, changeAddrs: request.signRequest.changeAddr, + additionalSenderUtxos: request.signRequest.senderUtxos, }); if (metadata) { @@ -102,6 +104,9 @@ export default class LedgerSendStore extends Store { ... }, changeAddrs: Array<{| ...Address, ...Value, ...Addressing |}>, + // The purpose of this parameter is to support transfering from Byron address when initializing + // Ledger wallets. It is needed because the wallet's utxos property no longer contains Byron UTxOs. + additionalSenderUtxos?: Array, catalystData?: LedgerNanoCatalystRegistrationTxSignData, |} => Promise<{| signedTxHex: string, @@ -134,6 +139,7 @@ export default class LedgerSendStore extends Store { expectedSerial, networkId: request.wallet.networkId, catalystData: request.catalystData, + additionalSenderUtxos: request.additionalSenderUtxos, }); } catch (error) { @@ -152,6 +158,7 @@ export default class LedgerSendStore extends Store { changeAddrs: Array<{| ...Address, ...Value, ...Addressing |}>, networkId: number, expectedSerial: string | void, + additionalSenderUtxos?: Array, catalystData?: LedgerNanoCatalystRegistrationTxSignData, |} => Promise<{| signedTxHex: string, @@ -182,7 +189,10 @@ export default class LedgerSendStore extends Store { const txBodyHex = transactionHexToBodyHex(rawTxHex); - const addressedUtxos = await this.stores.wallets.getAddressedUtxos(); + const addressedUtxos = [ + ...await this.stores.wallets.getAddressedUtxos(), + ...(request.additionalSenderUtxos || []) + ]; const response = this.api.ada.createHwSignTxDataFromRawTx('ledger', { txBodyHex, @@ -262,6 +272,9 @@ export default class LedgerSendStore extends Store { ledgerSignTxResp.witnesses, request.publicKey, metadata, + new Map((request.additionalSenderUtxos || []).map( + ({ addressing, receiver }) => [addressing.path.join('/'), receiver] + )), ); return { signedTxHex: txHex, txId, metadata }; From 1c88773c99f54989e7fdc373cded505e9b4fe15a Mon Sep 17 00:00:00 2001 From: yushi Date: Mon, 24 Feb 2025 02:36:05 +0800 Subject: [PATCH 20/32] simplify --- .../app/stores/ada/AdaWalletsStore.js | 89 ++++--------------- 1 file changed, 15 insertions(+), 74 deletions(-) diff --git a/packages/yoroi-extension/app/stores/ada/AdaWalletsStore.js b/packages/yoroi-extension/app/stores/ada/AdaWalletsStore.js index 8abd4d495b..63608cd0a5 100644 --- a/packages/yoroi-extension/app/stores/ada/AdaWalletsStore.js +++ b/packages/yoroi-extension/app/stores/ada/AdaWalletsStore.js @@ -91,93 +91,34 @@ export default class AdaWalletsStore extends Store { }, password: string, |}) => Promise<{| signedTxHex: string |}> = async ({ wallet, transactionHex, password }) => { - const walletType: string = wallet.type; - const baseSignRequest = { wallet, transactionHex }; - const signRequest = wallet.isHardware - ? { [walletType]: baseSignRequest } - : { normal: { ...baseSignRequest, password } }; - // $FlowIgnore[incompatible-call] - return this.adaSignTransactionHex({ signRequest }); - } - - adaSignTransactionHex: ({| - signRequest: - | {| - normal: {| - +wallet: { - publicDeriverId: number, - +plate: { TextPart: string, ... }, - ... - }, - transactionHex: string, - password: string, - |}, - |} - | {| - trezor: {| - transactionHex: string, - +wallet: { - publicDeriverId: number, - +plate: { TextPart: string, ... }, - publicKey: string, - pathToPublic: Array, - stakingAddressing: Addressing, - networkId: number, - hardwareWalletDeviceId: ?string, - ... - }, - |}, - |} - | {| - ledger: {| - transactionHex: string, - +wallet: { - publicDeriverId: number, - +plate: { TextPart: string, ... }, - stakingAddressing: Addressing, - publicKey: string, - pathToPublic: Array, - networkId: number, - hardwareWalletDeviceId: ?string, - ... - } - |}, - |}, - |}) => Promise<{| signedTxHex: string |}> = async request => { - if (request.signRequest.ledger) { - const { wallet, transactionHex } = request.signRequest.ledger; - const { signedTxHex } = await this.stores.substores.ada.ledgerSend.signRawTxFromWallet({ + let result; + if (wallet.type === 'mnemonic') { + const signedTxHex = await signTransaction({ + publicDeriverId: wallet.publicDeriverId, + transactionHex, + password, + }); + result = { signedTxHex }; + } else if (wallet.type === 'trezor') { + result = await this.stores.substores.ada.trezorSend.signRawTxFromWallet({ rawTxHex: transactionHex, wallet, // by happenstance the use case of this function is not to send // money while getting the change so there is no change address changeAddrs: [], }); - return { signedTxHex }; - } - if (request.signRequest.trezor) { - const { wallet, transactionHex } = request.signRequest.trezor; - const { signedTxHex } = await this.stores.substores.ada.trezorSend.signRawTxFromWallet({ + } else if (wallet.type === 'ledger') { + result = await this.stores.substores.ada.ledgerSend.signRawTxFromWallet({ rawTxHex: transactionHex, wallet, // by happenstance the use case of this function is not to send // money while getting the change so there is no change address changeAddrs: [], }); - return { signedTxHex }; - } - if (request.signRequest.normal) { - const { wallet, transactionHex, password } = request.signRequest.normal; - const signedTxHex = await signTransaction({ - publicDeriverId: wallet.publicDeriverId, - transactionHex, - password, - }); - return { signedTxHex }; + } else { + throw new Error('unexpected wallet type'); } - throw new Error( - `${nameof(AdaWalletsStore)}::${nameof(this.adaSignTransactionHex)} unhandled wallet type` - ); + return { signedTxHex: result.signedTxHex }; }; // =================== WALLET RESTORATION ==================== // From aa2929bec114b4bcfa53965c0e107ad405ae7d34 Mon Sep 17 00:00:00 2001 From: yushi Date: Thu, 20 Mar 2025 16:26:24 +0800 Subject: [PATCH 21/32] re-organize store code --- packages/yoroi-extension/app/api/ada/index.js | 18 ++- .../wallet/send/WalletSendFormRevamp.js | 2 +- .../WalletSendPreviewStepContainer.js | 2 +- .../containers/swap/asset-swap/SwapPage.js | 2 +- .../app/containers/swap/orders/OrdersPage.js | 4 +- .../containers/transfer/TransferSendPage.js | 12 +- .../transfer/UpgradeTxDialogContainer.js | 4 +- .../containers/transfer/YoroiTransferPage.js | 2 +- .../wallet/CreateWalletPageContainer.js | 4 +- .../app/containers/wallet/WalletSendPage.js | 2 +- .../dialogs/WalletBackupDialogContainer.js | 2 +- .../dialogs/WalletCreateDialogContainer.js | 2 +- .../dialogs/WalletRestoreDialogContainer.js | 2 +- .../WalletSendConfirmationDialogContainer.js | 4 +- .../voting/TransactionDialogContainer.js | 2 +- .../wallet/staking/CardanoStakingPage.js | 10 +- .../wallet/staking/WithdrawRewardsDialog.js | 6 +- .../ada/AdaDelegationTransactionStore.js | 9 +- .../app/stores/ada/LedgerConnectStore.js | 2 +- .../stores/ada/MnemonicWalletCreationStore.js | 71 ++++++++ .../app/stores/ada/SwapStore.js | 3 +- .../app/stores/ada/VotingStore.js | 8 +- .../yoroi-extension/app/stores/ada/index.js | 8 +- .../stores/ada/send/AdaMnemonicSendStore.js | 4 +- .../app/stores/ada/send/LedgerSendStore.js | 6 +- .../app/stores/ada/send/TrezorSendStore.js | 4 +- packages/yoroi-extension/app/stores/index.js | 4 + .../TransactionProcessingStore.js} | 152 +++++++----------- .../app/stores/toplevel/WalletStore.js | 51 ------ .../app/stores/toplevel/YoroiTransferStore.js | 4 +- 30 files changed, 204 insertions(+), 202 deletions(-) create mode 100644 packages/yoroi-extension/app/stores/ada/MnemonicWalletCreationStore.js rename packages/yoroi-extension/app/stores/{ada/AdaWalletsStore.js => toplevel/TransactionProcessingStore.js} (61%) diff --git a/packages/yoroi-extension/app/api/ada/index.js b/packages/yoroi-extension/app/api/ada/index.js index e54a03ec4a..fdec07c5b7 100644 --- a/packages/yoroi-extension/app/api/ada/index.js +++ b/packages/yoroi-extension/app/api/ada/index.js @@ -59,6 +59,7 @@ import type { IPublicDeriver, UsedStatus, Value, + QueriedUtxo, } from './lib/storage/models/PublicDeriver/interfaces'; import type { BaseGetTransactionsRequest, @@ -149,7 +150,7 @@ import type { DefaultTokenEntry } from '../common/lib/MultiToken'; import { MultiToken } from '../common/lib/MultiToken'; import { getReceiveAddress } from '../../stores/stateless/addressStores'; import { generateRegistrationMetadata } from './lib/cardanoCrypto/catalyst'; -import { bytesToHex, fail, hexToBytes, hexToUtf, iterateLenGet } from '../../coreUtils'; +import { bytesToHex, fail, hexToBytes, hexToUtf, iterateLenGet, first, sorted } from '../../coreUtils'; import type { PersistedSubmittedTransaction } from '../localStorage'; import type WalletTransaction from '../../domain/WalletTransaction'; import { derivePrivateByAddressing, derivePublicByAddressing } from './lib/cardanoCrypto/deriveByAddressing'; @@ -506,6 +507,7 @@ export type ForeignUtxoFetcher = (Array) => Promise Promise = async ({ wallet }) => { + const allUtxos = wallet.utxos; + if (allUtxos.length === 0) { + fail('Cannot pick a collateral utxo! No utxo available at all in the wallet!'); + } + const utxoDefaultCoinAmount = (u: QueriedUtxo): BigNumber => + new BigNumber(u.output.tokens.find(x => x.Token.Identifier === '')?.TokenList.Amount ?? 0); + const compareDefaultCoins = (a: QueriedUtxo, b: QueriedUtxo): number => + utxoDefaultCoinAmount(a).comparedTo(utxoDefaultCoinAmount(b)); + const smallPureUtxos = allUtxos + .filter(u => u.output.tokens.length === 1 && utxoDefaultCoinAmount(u).lte(MAX_PICKED_COLLATERAL_UTXO_ADA)); + return first(sorted(smallPureUtxos, compareDefaultCoins)); + } } // ========== End of class AdaApi ========= diff --git a/packages/yoroi-extension/app/components/wallet/send/WalletSendFormRevamp.js b/packages/yoroi-extension/app/components/wallet/send/WalletSendFormRevamp.js index ed8300df00..30e091351b 100644 --- a/packages/yoroi-extension/app/components/wallet/send/WalletSendFormRevamp.js +++ b/packages/yoroi-extension/app/components/wallet/send/WalletSendFormRevamp.js @@ -54,7 +54,7 @@ import { ampli } from '../../../../ampli/index'; import type { DomainResolverFunc, DomainResolverResponse } from '../../../stores/ada/AdaAddressesStore'; import { isResolvableDomain } from '@yoroi/resolver'; import SupportedAddressDomainsBanner from '../../../containers/wallet/SupportedAddressDomainsBanner'; -import type { SendMoneyRequest } from '../../../stores/toplevel/WalletStore'; +import type { SendMoneyRequest } from '../../../stores/toplevel/TransactionProcessingStore'; import type { MaxSendableAmountRequest } from '../../../stores/toplevel/TransactionBuilderStore'; import type { WalletState } from '../../../../chrome/extension/background/types'; import LoadingSpinner from '../../widgets/LoadingSpinner'; diff --git a/packages/yoroi-extension/app/components/wallet/send/WalletSendFormSteps/WalletSendPreviewStepContainer.js b/packages/yoroi-extension/app/components/wallet/send/WalletSendFormSteps/WalletSendPreviewStepContainer.js index a9ce430ffa..e4d0735cd6 100644 --- a/packages/yoroi-extension/app/components/wallet/send/WalletSendFormSteps/WalletSendPreviewStepContainer.js +++ b/packages/yoroi-extension/app/components/wallet/send/WalletSendFormSteps/WalletSendPreviewStepContainer.js @@ -11,7 +11,7 @@ import type { ISignRequest } from '../../../../api/common/lib/transactions/ISign import type { TokenRow } from '../../../../api/ada/lib/storage/database/primitives/tables'; import type { MultiToken, TokenLookupKey } from '../../../../api/common/lib/MultiToken'; import { ampli } from '../../../../../ampli/index'; -import type { SendMoneyRequest } from '../../../../stores/toplevel/WalletStore'; +import type { SendMoneyRequest } from '../../../../stores/toplevel/TransactionProcessingStore'; import { getNetworkById } from '../../../../api/ada/lib/storage/database/prepackaged/networks'; import type { WalletState } from '../../../../../chrome/extension/background/types'; import { HaskellShelleyTxSignRequest } from '../../../../api/ada/transactions/shelley/HaskellShelleyTxSignRequest'; diff --git a/packages/yoroi-extension/app/containers/swap/asset-swap/SwapPage.js b/packages/yoroi-extension/app/containers/swap/asset-swap/SwapPage.js index 46de335b11..8e7f1454c3 100644 --- a/packages/yoroi-extension/app/containers/swap/asset-swap/SwapPage.js +++ b/packages/yoroi-extension/app/containers/swap/asset-swap/SwapPage.js @@ -291,7 +291,7 @@ function SwapPage(props: StoresProps & Intl): Node { try { const refreshWallet = () => stores.wallets.refreshWalletFromRemote(wallet.publicDeriverId); // $FlowIgnore[incompatible-call] - await stores.substores.ada.wallets.adaSendAndRefresh({ broadcastRequest, refreshWallet }); + await stores.transactionProcessingStore.adaSendAndRefresh({ broadcastRequest, refreshWallet }); setOrderStepValue(2); showTxResultModal(TransactionResult.SUCCESS); diff --git a/packages/yoroi-extension/app/containers/swap/orders/OrdersPage.js b/packages/yoroi-extension/app/containers/swap/orders/OrdersPage.js index 579974d01c..341422c92b 100644 --- a/packages/yoroi-extension/app/containers/swap/orders/OrdersPage.js +++ b/packages/yoroi-extension/app/containers/swap/orders/OrdersPage.js @@ -284,7 +284,7 @@ export default function SwapOrdersPage(props: StoresProps): Node { const submitTx = async (passswordInput, cancelTxCbor, signedCollateralReorgTx, order: any) => { try { startLoadingTxReview(); - const { signedTxHex: signedCancelTx } = await props.stores.substores.ada.wallets.adaSignTransactionHexFromWallet({ + const { signedTxHex: signedCancelTx } = await props.stores.transactionProcessingStore.adaSignTransactionHexFromWallet({ wallet, transactionHex: cancelTxCbor, password: passswordInput, @@ -320,7 +320,7 @@ export default function SwapOrdersPage(props: StoresProps): Node { } try { - const { signedTxHex: signedCollateralReorgTx } = await props.stores.substores.ada.wallets.adaSignTransactionHexFromWallet({ + const { signedTxHex: signedCollateralReorgTx } = await props.stores.transactionProcessingStore.adaSignTransactionHexFromWallet({ wallet, transactionHex: collateralReorgTxObj.cbor, password: passswordInput, diff --git a/packages/yoroi-extension/app/containers/transfer/TransferSendPage.js b/packages/yoroi-extension/app/containers/transfer/TransferSendPage.js index b054abf360..fa2b5bbbf2 100644 --- a/packages/yoroi-extension/app/containers/transfer/TransferSendPage.js +++ b/packages/yoroi-extension/app/containers/transfer/TransferSendPage.js @@ -67,7 +67,7 @@ export default class TransferSendPage extends Component<{| ...StoresProps, ...Lo componentWillUnmount() { const { stores } = this.props; - stores.wallets.sendMoneyRequest.reset(); + stores.transactionProcessingStore.sendMoneyRequest.reset(); this.props.transactionRequest.reset(); stores.substores.ada.ledgerSend.cancel(); stores.substores.ada.trezorSend.cancel(); @@ -94,7 +94,7 @@ export default class TransferSendPage extends Component<{| ...StoresProps, ...Lo wallet: selected, }); } - if (stores.wallets.sendMoneyRequest.error == null) { + if (stores.transactionProcessingStore.sendMoneyRequest.error == null) { this.props.onSubmit.trigger(); } } else { @@ -106,7 +106,7 @@ export default class TransferSendPage extends Component<{| ...StoresProps, ...Lo password: walletPassword, wallet: selected, }); - if (stores.wallets.sendMoneyRequest.error == null) { + if (stores.transactionProcessingStore.sendMoneyRequest.error == null) { this.props.onSubmit.trigger(); } }, @@ -159,7 +159,7 @@ export default class TransferSendPage extends Component<{| ...StoresProps, ...Lo ? ( this.setSpendingPasswordForm(form)} - isSubmitting={this.props.stores.wallets.sendMoneyRequest.isExecuting} + isSubmitting={this.props.stores.transactionProcessingStore.sendMoneyRequest.isExecuting} /> ) : null; @@ -178,9 +178,9 @@ export default class TransferSendPage extends Component<{| ...StoresProps, ...Lo label: this.props.onSubmit.label, trigger: this.submit, }} - isSubmitting={this.props.stores.wallets.sendMoneyRequest.isExecuting} + isSubmitting={this.props.stores.transactionProcessingStore.sendMoneyRequest.isExecuting} onCancel={this.props.onClose} - error={this.props.stores.wallets.sendMoneyRequest.error} + error={this.props.stores.transactionProcessingStore.sendMoneyRequest.error} dialogTitle={intl.formatMessage(globalMessages.walletSendConfirmationDialogTitle)} getCurrentPrice={this.props.stores.coinPriceStore.getCurrentPrice} unitOfAccountSetting={this.props.stores.profile.unitOfAccount} diff --git a/packages/yoroi-extension/app/containers/transfer/UpgradeTxDialogContainer.js b/packages/yoroi-extension/app/containers/transfer/UpgradeTxDialogContainer.js index 42d9573dbc..15b178ea35 100644 --- a/packages/yoroi-extension/app/containers/transfer/UpgradeTxDialogContainer.js +++ b/packages/yoroi-extension/app/containers/transfer/UpgradeTxDialogContainer.js @@ -177,12 +177,12 @@ export default class UpgradeTxDialogContainer extends Component<{| ...StoresProp }), label: intl.formatMessage(globalMessages.upgradeLabel), }} - isSubmitting={this.props.stores.wallets.sendMoneyRequest.isExecuting} + isSubmitting={this.props.stores.transactionProcessingStore.sendMoneyRequest.isExecuting} onCancel={{ trigger: this.props.onClose, label: intl.formatMessage(globalMessages.skipLabel), }} - error={this.props.stores.wallets.sendMoneyRequest.error} + error={this.props.stores.transactionProcessingStore.sendMoneyRequest.error} dialogTitle={intl.formatMessage(globalMessages.walletSendConfirmationDialogTitle)} getCurrentPrice={this.props.stores.coinPriceStore.getCurrentPrice} unitOfAccountSetting={this.props.stores.profile.unitOfAccount} diff --git a/packages/yoroi-extension/app/containers/transfer/YoroiTransferPage.js b/packages/yoroi-extension/app/containers/transfer/YoroiTransferPage.js index bb4a354c86..93ed525454 100644 --- a/packages/yoroi-extension/app/containers/transfer/YoroiTransferPage.js +++ b/packages/yoroi-extension/app/containers/transfer/YoroiTransferPage.js @@ -156,7 +156,7 @@ export default class YoroiTransferPage extends Component { label: intl.formatMessage(globalMessages.nextButtonLabel), trigger: this.transferFunds, }} - isSubmitting={stores.wallets.sendMoneyRequest.isExecuting} + isSubmitting={stores.transactionProcessingStore.sendMoneyRequest.isExecuting} onCancel={{ label: intl.formatMessage(globalMessages.cancel), trigger: this.cancelTransferFunds diff --git a/packages/yoroi-extension/app/containers/wallet/CreateWalletPageContainer.js b/packages/yoroi-extension/app/containers/wallet/CreateWalletPageContainer.js index 523e7e5a3f..8f88e0d188 100644 --- a/packages/yoroi-extension/app/containers/wallet/CreateWalletPageContainer.js +++ b/packages/yoroi-extension/app/containers/wallet/CreateWalletPageContainer.js @@ -21,8 +21,8 @@ export default class CreateWalletPageContainer extends Component { const createWalletPageComponent = ( stores.substores.ada.wallets.createWallet(request)} + genWalletRecoveryPhrase={stores.substores.ada.mnemonicWalletCreationStore.genWalletRecoveryPhrase} + createWallet={request => stores.substores.ada.mnemonicWalletCreationStore.createWallet(request)} selectedNetwork={stores.profile.selectedNetwork} openDialog={dialog => this.props.stores.uiDialogs.open({ dialog })} closeDialog={this.props.stores.uiDialogs.closeActiveDialog} diff --git a/packages/yoroi-extension/app/containers/wallet/WalletSendPage.js b/packages/yoroi-extension/app/containers/wallet/WalletSendPage.js index 387bbb2328..25d6c9d797 100644 --- a/packages/yoroi-extension/app/containers/wallet/WalletSendPage.js +++ b/packages/yoroi-extension/app/containers/wallet/WalletSendPage.js @@ -222,7 +222,7 @@ export default class WalletSendPage extends Component { maxSendableAmount={transactionBuilderStore.maxSendableAmount} signRequest={transactionBuilderStore.tentativeTx} staleTx={transactionBuilderStore.txMismatch} - sendMoneyRequest={stores.wallets.sendMoneyRequest} + sendMoneyRequest={stores.transactionProcessingStore.sendMoneyRequest} sendMoney={stores.substores.ada.mnemonicSend.sendMoney} ledgerSendError={stores.substores.ada.ledgerSend.error || null} trezorSendError={stores.substores.ada.trezorSend.error || null} diff --git a/packages/yoroi-extension/app/containers/wallet/dialogs/WalletBackupDialogContainer.js b/packages/yoroi-extension/app/containers/wallet/dialogs/WalletBackupDialogContainer.js index 05ce2d9178..2f94d6a32f 100644 --- a/packages/yoroi-extension/app/containers/wallet/dialogs/WalletBackupDialogContainer.js +++ b/packages/yoroi-extension/app/containers/wallet/dialogs/WalletBackupDialogContainer.js @@ -58,7 +58,7 @@ export default class WalletBackupDialogContainer extends Component<{| ...Props, onAcceptTermRecovery={stores.walletBackup.acceptWalletBackupTermRecovery} onAddWord={stores.walletBackup.addWordToWalletBackupVerification} onClear={stores.walletBackup.clearEnteredRecoveryPhrase} - onFinishBackup={stores.substores.ada.wallets.finishWalletBackup} + onFinishBackup={stores.substores.ada.mnemonicWalletCreationStore.finishWalletBackup} removeWord={() => { stores.walletBackup.removeOneMnemonicWord(); }} diff --git a/packages/yoroi-extension/app/containers/wallet/dialogs/WalletCreateDialogContainer.js b/packages/yoroi-extension/app/containers/wallet/dialogs/WalletCreateDialogContainer.js index 44a3c532bd..0dd6547d63 100644 --- a/packages/yoroi-extension/app/containers/wallet/dialogs/WalletCreateDialogContainer.js +++ b/packages/yoroi-extension/app/containers/wallet/dialogs/WalletCreateDialogContainer.js @@ -14,7 +14,7 @@ export default class WalletCreateDialogContainer extends Component<{| ...StoresP render(): Node { return ( this.props.stores.substores.ada.wallets.startWalletCreation(request)} + onSubmit={request => this.props.stores.substores.ada.mnemonicWalletCreationStore.startWalletCreation(request)} onCancel={this.props.onClose} /> ); diff --git a/packages/yoroi-extension/app/containers/wallet/dialogs/WalletRestoreDialogContainer.js b/packages/yoroi-extension/app/containers/wallet/dialogs/WalletRestoreDialogContainer.js index 16d3aecca7..7c8d2d8a54 100644 --- a/packages/yoroi-extension/app/containers/wallet/dialogs/WalletRestoreDialogContainer.js +++ b/packages/yoroi-extension/app/containers/wallet/dialogs/WalletRestoreDialogContainer.js @@ -248,7 +248,7 @@ export default class WalletRestoreDialogContainer extends Component<{| ...Stores label: intl.formatMessage(globalMessages.nextButtonLabel), trigger: walletRestore.transferFromLegacy, }} - isSubmitting={this.props.stores.wallets.sendMoneyRequest.isExecuting} + isSubmitting={this.props.stores.transactionProcessingStore.sendMoneyRequest.isExecuting} onCancel={{ label: intl.formatMessage(globalMessages.cancel), trigger: this.onCancel, diff --git a/packages/yoroi-extension/app/containers/wallet/dialogs/WalletSendConfirmationDialogContainer.js b/packages/yoroi-extension/app/containers/wallet/dialogs/WalletSendConfirmationDialogContainer.js index e70c5a5730..cab7eab786 100644 --- a/packages/yoroi-extension/app/containers/wallet/dialogs/WalletSendConfirmationDialogContainer.js +++ b/packages/yoroi-extension/app/containers/wallet/dialogs/WalletSendConfirmationDialogContainer.js @@ -27,7 +27,7 @@ type Props = {| ...StoresProps, ...LocalProps |}; export default class WalletSendConfirmationDialogContainer extends Component { componentWillUnmount() { - this.props.stores.wallets.sendMoneyRequest.reset(); + this.props.stores.transactionProcessingStore.sendMoneyRequest.reset(); } render(): Node { @@ -42,7 +42,7 @@ export default class WalletSendConfirmationDialogContainer extends Component { progressInfo={votingStore.progressInfo} staleTx={votingStore.isStale} transactionFee={votingRegTx.fee()} - isSubmitting={stores.wallets.sendMoneyRequest.isExecuting} + isSubmitting={stores.transactionProcessingStore.sendMoneyRequest.isExecuting} getTokenInfo={genLookupOrFail(stores.tokenInfoStore.tokenInfo)} onCancel={cancel} goBack={goBack} diff --git a/packages/yoroi-extension/app/containers/wallet/staking/CardanoStakingPage.js b/packages/yoroi-extension/app/containers/wallet/staking/CardanoStakingPage.js index 9df7baf02d..c805db556b 100644 --- a/packages/yoroi-extension/app/containers/wallet/staking/CardanoStakingPage.js +++ b/packages/yoroi-extension/app/containers/wallet/staking/CardanoStakingPage.js @@ -290,9 +290,9 @@ export default class CardanoStakingPage extends Component { }; const showSignDialog = - stores.wallets.sendMoneyRequest.isExecuting || - !stores.wallets.sendMoneyRequest.wasExecuted || - stores.wallets.sendMoneyRequest.error != null; + stores.transactionProcessingStore.sendMoneyRequest.isExecuting || + !stores.transactionProcessingStore.sendMoneyRequest.wasExecuted || + stores.transactionProcessingStore.sendMoneyRequest.error != null; const selectedPoolInfo = this._getPoolInfo(selectedWallet); if (stores.delegation.poolInfoQuery.error != null) { @@ -342,7 +342,7 @@ export default class CardanoStakingPage extends Component { amountToDelegate={delegationTx.totalAmountToDelegate} approximateReward={approximateReward(delegationTx.totalAmountToDelegate.getDefaultEntry())} getTokenInfo={genLookupOrFail(stores.tokenInfoStore.tokenInfo)} - isSubmitting={stores.wallets.sendMoneyRequest.isExecuting} + isSubmitting={stores.transactionProcessingStore.sendMoneyRequest.isExecuting} isHardware={selectedWallet.type !== 'mnemonic'} onCancel={this.cancel} onSubmit={async ({ password }) => { @@ -356,7 +356,7 @@ export default class CardanoStakingPage extends Component { staking_pool: selectedPoolId, }); }} - error={stores.wallets.sendMoneyRequest.error} + error={stores.transactionProcessingStore.sendMoneyRequest.error} selectedExplorer={ stores.explorers.selectedExplorer.get(selectedWallet.networkId) ?? (() => { diff --git a/packages/yoroi-extension/app/containers/wallet/staking/WithdrawRewardsDialog.js b/packages/yoroi-extension/app/containers/wallet/staking/WithdrawRewardsDialog.js index ec43c78648..14991dbf4a 100644 --- a/packages/yoroi-extension/app/containers/wallet/staking/WithdrawRewardsDialog.js +++ b/packages/yoroi-extension/app/containers/wallet/staking/WithdrawRewardsDialog.js @@ -74,7 +74,7 @@ export default class WithdrawRewardsDialog extends Component<{| ...StoresProps, componentWillUnmount() { const { stores } = this.props; - stores.wallets.sendMoneyRequest.reset(); + stores.transactionProcessingStore.sendMoneyRequest.reset(); stores.substores.ada.delegationTransaction.createWithdrawalTx.reset(); stores.substores.ada.ledgerSend.cancel(); stores.substores.ada.trezorSend.cancel(); @@ -202,8 +202,8 @@ export default class WithdrawRewardsDialog extends Component<{| ...StoresProps, const txFee = tentativeTx.fee(); const finalRewards = this.getTotalBalance(recoveredBalance, txFee, deregistrations); - const isSubmitting = this.props.stores.wallets.sendMoneyRequest.isExecuting; - const error = this.props.stores.wallets.sendMoneyRequest.error; + const isSubmitting = this.props.stores.transactionProcessingStore.sendMoneyRequest.isExecuting; + const error = this.props.stores.transactionProcessingStore.sendMoneyRequest.error; const spendingPasswordForm = publicDeriver.type !== 'mnemonic' ? undefined : ( this.setSpendingPasswordForm(form)} isSubmitting={isSubmitting} /> diff --git a/packages/yoroi-extension/app/stores/ada/AdaDelegationTransactionStore.js b/packages/yoroi-extension/app/stores/ada/AdaDelegationTransactionStore.js index e4434421dc..3397d50907 100644 --- a/packages/yoroi-extension/app/stores/ada/AdaDelegationTransactionStore.js +++ b/packages/yoroi-extension/app/stores/ada/AdaDelegationTransactionStore.js @@ -137,6 +137,7 @@ export default class AdaDelegationTransactionStore extends Store { password?: string, dialog?: any, |}) => Promise = async request => { +debugger const result = this.createDelegationTx.result; console.log('resultresult', { result, request }); if (result == null) { @@ -148,7 +149,7 @@ export default class AdaDelegationTransactionStore extends Store { }; try { if (request.wallet.type === 'ledger') { - await this.stores.substores.ada.wallets.adaSendAndRefresh({ + await this.stores.transactionProcessingStore.adaSendAndRefresh({ broadcastRequest: { ledger: { signRequest: result.signTxRequest, @@ -160,7 +161,7 @@ export default class AdaDelegationTransactionStore extends Store { return; } if (request.wallet.type === 'trezor') { - await this.stores.substores.ada.wallets.adaSendAndRefresh({ + await this.stores.transactionProcessingStore.adaSendAndRefresh({ broadcastRequest: { trezor: { signRequest: result.signTxRequest, @@ -175,7 +176,7 @@ export default class AdaDelegationTransactionStore extends Store { if (request.password == null) { throw new Error(`${nameof(this.signTransaction)} missing password for non-hardware signing`); } - await this.stores.substores.ada.wallets.adaSendAndRefresh({ + await this.stores.transactionProcessingStore.adaSendAndRefresh({ broadcastRequest: { normal: { wallet: request.wallet, @@ -206,7 +207,7 @@ export default class AdaDelegationTransactionStore extends Store { @action.bound reset(request: {| justTransaction: boolean |}): void { - this.stores.wallets.sendMoneyRequest.reset(); + this.stores.transactionProcessingStore.sendMoneyRequest.reset(); this.createDelegationTx.reset(); if (!request.justTransaction) { this.isStale = false; diff --git a/packages/yoroi-extension/app/stores/ada/LedgerConnectStore.js b/packages/yoroi-extension/app/stores/ada/LedgerConnectStore.js index 9e74ade593..d16669a8d8 100644 --- a/packages/yoroi-extension/app/stores/ada/LedgerConnectStore.js +++ b/packages/yoroi-extension/app/stores/ada/LedgerConnectStore.js @@ -92,7 +92,7 @@ export default class LedgerConnectStore this.error = undefined; this.hwDeviceInfo = undefined; this.stores.substores.ada.yoroiTransfer.transferRequest.reset(); - this.stores.wallets.sendMoneyRequest.reset(); + this.stores.transactionProcessingStore.sendMoneyRequest.reset(); }; @action _openTransferDialog: void => void = () => { diff --git a/packages/yoroi-extension/app/stores/ada/MnemonicWalletCreationStore.js b/packages/yoroi-extension/app/stores/ada/MnemonicWalletCreationStore.js new file mode 100644 index 0000000000..5b5057515f --- /dev/null +++ b/packages/yoroi-extension/app/stores/ada/MnemonicWalletCreationStore.js @@ -0,0 +1,71 @@ +// @flow +import { observable } from 'mobx'; + +import Store from '../base/Store'; +import Request from '../lib/LocalizedRequest'; +import type { GenerateWalletRecoveryPhraseFunc } from '../../api/ada/index'; +import type { StoresMap } from '../index'; +import { HARD_DERIVATION_START } from '../../config/numbersConfig'; +import { createWallet } from '../../api/thunk'; + +export default class MnemonicWalletCreationStore extends Store { + // REQUESTS + + @observable + generateWalletRecoveryPhraseRequest: Request = new Request( + this.api.ada.generateWalletRecoveryPhrase + ); + + startWalletCreation: ({| + name: string, + password: string, + |}) => Promise = async params => { + const recoveryPhrase = await this.generateWalletRecoveryPhraseRequest.execute({}).promise; + if (recoveryPhrase == null) { + throw new Error(`${nameof(this.startWalletCreation)} failed to generate recovery phrase`); + } + this.stores.walletBackup.initiateWalletBackup({ + recoveryPhrase, + name: params.name, + password: params.password, + }); + }; + + genWalletRecoveryPhrase: void => Promise> = async () => { + const recoveryPhrase = await this.generateWalletRecoveryPhraseRequest.execute({}).promise; + + if (recoveryPhrase == null) { + throw new Error(`${nameof(this.startWalletCreation)} failed to generate recovery phrase`); + } + + return recoveryPhrase; + }; + + /** Create the wallet and go to wallet summary screen */ + finishWalletBackup: void => Promise = async () => { + await this.createWallet({ + recoveryPhrase: this.stores.walletBackup.recoveryPhrase, + walletPassword: this.stores.walletBackup.password, + walletName: this.stores.walletBackup.name, + }); + }; + + createWallet: {| + recoveryPhrase: Array, + walletPassword: string, + walletName: string, + |} => Promise = async (request) => { + const { selectedNetwork } = this.stores.profile; + if (selectedNetwork == null) throw new Error(`${nameof(this.finishWalletBackup)} no network selected`); + await this.stores.wallets.createWalletRequest.execute(async () => { + const wallet = await createWallet({ + walletName: request.walletName, + walletPassword: request.walletPassword, + recoveryPhrase: request.recoveryPhrase.join(' '), + networkId: selectedNetwork.NetworkId, + accountIndex: 0 + HARD_DERIVATION_START, + }); + return wallet; + }).promise; + }; +} diff --git a/packages/yoroi-extension/app/stores/ada/SwapStore.js b/packages/yoroi-extension/app/stores/ada/SwapStore.js index 6c77dbf1ef..4e81f6e041 100644 --- a/packages/yoroi-extension/app/stores/ada/SwapStore.js +++ b/packages/yoroi-extension/app/stores/ada/SwapStore.js @@ -82,8 +82,7 @@ export default class SwapStore extends Store { getCollateralUtxoHexForCancel: ({| wallet: WalletState |}) => Promise = async ({ wallet, }) => { - const utxo: ?QueriedUtxo = await this.stores.substores.ada.wallets - .pickCollateralUtxo({ wallet }); + const utxo: ?QueriedUtxo = await this.api.ada.pickCollateralUtxo({ wallet }); return maybe(utxo, u => { const [addressedUtxo] = asAddressedUtxo([u]); return cardanoUtxoHexFromRemoteFormat(cast(addressedUtxo)); diff --git a/packages/yoroi-extension/app/stores/ada/VotingStore.js b/packages/yoroi-extension/app/stores/ada/VotingStore.js index 798bb81a6e..ae703330be 100644 --- a/packages/yoroi-extension/app/stores/ada/VotingStore.js +++ b/packages/yoroi-extension/app/stores/ada/VotingStore.js @@ -309,7 +309,7 @@ export default class VotingStore extends Store { throw new Error(`${nameof(this.signTransaction)} no tx to broadcast`); } if (request.wallet.type === 'ledger') { - await this.stores.substores.ada.wallets.adaSendAndRefresh({ + await this.stores.transactionProcessingStore.adaSendAndRefresh({ broadcastRequest: { ledger: { signRequest: result, @@ -321,7 +321,7 @@ export default class VotingStore extends Store { return; } if (request.wallet.type === 'trezor') { - await this.stores.substores.ada.wallets.adaSendAndRefresh({ + await this.stores.transactionProcessingStore.adaSendAndRefresh({ broadcastRequest: { trezor: { signRequest: result, @@ -337,7 +337,7 @@ export default class VotingStore extends Store { if (request.password == null) { throw new Error(`${nameof(this.signTransaction)} missing password for non-hardware signing`); } - await this.stores.substores.ada.wallets.adaSendAndRefresh({ + await this.stores.transactionProcessingStore.adaSendAndRefresh({ broadcastRequest: { normal: { wallet: request.wallet, @@ -379,7 +379,7 @@ export default class VotingStore extends Store { stepState: StepState.LOAD, }; this.error = null; - this.stores.wallets.sendMoneyRequest.reset(); + this.stores.transactionProcessingStore.sendMoneyRequest.reset(); this.createVotingRegTx.reset(); if (!request.justTransaction) { this.isStale = false; diff --git a/packages/yoroi-extension/app/stores/ada/index.js b/packages/yoroi-extension/app/stores/ada/index.js index 7a13797446..da9156ee4f 100644 --- a/packages/yoroi-extension/app/stores/ada/index.js +++ b/packages/yoroi-extension/app/stores/ada/index.js @@ -3,7 +3,7 @@ // File is based the same pattern used for the non-ada-specific stores in our app. import { observable, action } from 'mobx'; -import AdaWalletsStore from './AdaWalletsStore'; +import MnemonicWalletCreationStore from './MnemonicWalletCreationStore'; import AdaTransactionsStore from './AdaTransactionsStore'; import AddressesStore from './AdaAddressesStore'; import AdaYoroiTransferStore from './AdaYoroiTransferStore'; @@ -24,7 +24,7 @@ import type { StoresMap } from '../index'; import BaseCardanoTimeStore from '../base/BaseCardanoTimeStore'; export const adaStoreClasses = Object.freeze({ - wallets: AdaWalletsStore, + mnemonicWalletCreationStore: MnemonicWalletCreationStore, transactions: AdaTransactionsStore, addresses: AddressesStore, yoroiTransfer: AdaYoroiTransferStore, @@ -44,7 +44,7 @@ export const adaStoreClasses = Object.freeze({ }); export type AdaStoresMap = {| - wallets: AdaWalletsStore, + mnemonicWalletCreationStore: MnemonicWalletCreationStore, transactions: AdaTransactionsStore, addresses: AddressesStore, yoroiTransfer: AdaYoroiTransferStore, @@ -64,7 +64,7 @@ export type AdaStoresMap = {| |}; const adaStores: WithNullableFields = observable({ - wallets: null, + mnemonicWalletCreationStore: null, transactions: null, addresses: null, yoroiTransfer: null, diff --git a/packages/yoroi-extension/app/stores/ada/send/AdaMnemonicSendStore.js b/packages/yoroi-extension/app/stores/ada/send/AdaMnemonicSendStore.js index 47bc1cd043..d8de0e8cc7 100644 --- a/packages/yoroi-extension/app/stores/ada/send/AdaMnemonicSendStore.js +++ b/packages/yoroi-extension/app/stores/ada/send/AdaMnemonicSendStore.js @@ -29,7 +29,7 @@ export default class AdaMnemonicSendStore extends Store { } const { stores } = this; - await stores.substores.ada.wallets.adaSendAndRefresh({ + await stores.transactionProcessingStore.adaSendAndRefresh({ broadcastRequest: { normal: { wallet: request.wallet, @@ -41,7 +41,7 @@ export default class AdaMnemonicSendStore extends Store { }); this.stores.uiDialogs.closeActiveDialog(); - stores.wallets.sendMoneyRequest.reset(); + stores.transactionProcessingStore.sendMoneyRequest.reset(); if (request.onSuccess) { request.onSuccess(); } else { diff --git a/packages/yoroi-extension/app/stores/ada/send/LedgerSendStore.js b/packages/yoroi-extension/app/stores/ada/send/LedgerSendStore.js index 0cc87dcc82..b81e1c1c2c 100644 --- a/packages/yoroi-extension/app/stores/ada/send/LedgerSendStore.js +++ b/packages/yoroi-extension/app/stores/ada/send/LedgerSendStore.js @@ -75,7 +75,7 @@ export default class LedgerSendStore extends Store { expectedSerial: string | void, networkId: number, |} => Promise = async (request) => { - await this.stores.wallets.sendAndRefresh({ + await this.stores.transactionProcessingStore.sendAndRefresh({ publicDeriverId: undefined, plateTextPart: undefined, broadcastRequest: async () => await this.signAndBroadcast(request), @@ -111,7 +111,7 @@ export default class LedgerSendStore extends Store { this._setActionProcessing(true); const { stores } = this; - await stores.substores.ada.wallets.adaSendAndRefresh({ + await stores.transactionProcessingStore.adaSendAndRefresh({ broadcastRequest: { ledger: { signRequest, @@ -122,7 +122,7 @@ export default class LedgerSendStore extends Store { }); this.stores.uiDialogs.closeActiveDialog(); - stores.wallets.sendMoneyRequest.reset(); + stores.transactionProcessingStore.sendMoneyRequest.reset(); if (request.onSuccess) { request.onSuccess(); } else { diff --git a/packages/yoroi-extension/app/stores/ada/send/TrezorSendStore.js b/packages/yoroi-extension/app/stores/ada/send/TrezorSendStore.js index f68e712dce..609fdd30ac 100644 --- a/packages/yoroi-extension/app/stores/ada/send/TrezorSendStore.js +++ b/packages/yoroi-extension/app/stores/ada/send/TrezorSendStore.js @@ -72,7 +72,7 @@ export default class TrezorSendStore extends Store { this._setActionProcessing(true); const { stores } = this; - await stores.substores.ada.wallets.adaSendAndRefresh({ + await stores.transactionProcessingStore.adaSendAndRefresh({ broadcastRequest: { trezor: { signRequest, @@ -83,7 +83,7 @@ export default class TrezorSendStore extends Store { }); this.stores.uiDialogs.closeActiveDialog(); - stores.wallets.sendMoneyRequest.reset(); + stores.transactionProcessingStore.sendMoneyRequest.reset(); if (request.onSuccess) { request.onSuccess(); } else { diff --git a/packages/yoroi-extension/app/stores/index.js b/packages/yoroi-extension/app/stores/index.js index 69946e357b..6125c5f44e 100644 --- a/packages/yoroi-extension/app/stores/index.js +++ b/packages/yoroi-extension/app/stores/index.js @@ -26,6 +26,7 @@ import ExplorerStore from './toplevel/ExplorerStore'; import ServerConnectionStore from './toplevel/ServerConnectionStore'; import ConnectorStore from './toplevel/DappConnectorStore' import ProtocolParametersStore from './toplevel/ProtocolParametersStore'; +import TransactionProcessingStore from './toplevel/TransactionProcessingStore'; /** Map of var name to class. Allows dynamic lookup of class so we can init all stores one loop */ const storeClasses = Object.freeze({ @@ -51,6 +52,7 @@ const storeClasses = Object.freeze({ explorers: ExplorerStore, connector: ConnectorStore, protocolParameters: ProtocolParametersStore, + transactionProcessingStore: TransactionProcessingStore, // note: purposely exclude substores and router }); @@ -82,6 +84,7 @@ export type StoresMap = {| // $FlowFixMe[value-as-type] router: RouterStore, protocolParameters: ProtocolParametersStore, + transactionProcessingStore: TransactionProcessingStore, |}; /** Constant that represents the stores across the lifetime of the application */ @@ -115,6 +118,7 @@ const stores: StoresMap = (observable({ router: null, connector: null, protocolParameters: null, + transactionProcessingStore: null, }): any); function initializeSubstore( diff --git a/packages/yoroi-extension/app/stores/ada/AdaWalletsStore.js b/packages/yoroi-extension/app/stores/toplevel/TransactionProcessingStore.js similarity index 61% rename from packages/yoroi-extension/app/stores/ada/AdaWalletsStore.js rename to packages/yoroi-extension/app/stores/toplevel/TransactionProcessingStore.js index b144aa8576..d2fb25078a 100644 --- a/packages/yoroi-extension/app/stores/ada/AdaWalletsStore.js +++ b/packages/yoroi-extension/app/stores/toplevel/TransactionProcessingStore.js @@ -1,32 +1,63 @@ // @flow -import { observable } from 'mobx'; - import Store from '../base/Store'; -import Request from '../lib/LocalizedRequest'; -import type { GenerateWalletRecoveryPhraseFunc } from '../../api/ada/index'; -import { HaskellShelleyTxSignRequest } from '../../api/ada/transactions/shelley/HaskellShelleyTxSignRequest'; import type { StoresMap } from '../index'; -import { HARD_DERIVATION_START } from '../../config/numbersConfig'; -import { createWallet, signTransaction } from '../../api/thunk'; -import type { - Addressing, - QueriedUtxo, -} from '../../api/ada/lib/storage/models/PublicDeriver/interfaces'; -import { fail, first, sorted } from '../../coreUtils'; -import BigNumber from 'bignumber.js'; -import type{ WalletState } from '../../../chrome/extension/background/types'; - -const MAX_PICKED_COLLATERAL_UTXO_ADA = 10_000_000; // 10 ADA +import { HaskellShelleyTxSignRequest } from '../../api/ada/transactions/shelley/HaskellShelleyTxSignRequest'; +import { signTransaction } from '../../api/thunk'; +import type { Addressing } from '../../api/ada/lib/storage/models/PublicDeriver/interfaces'; +import { observable } from 'mobx'; +import Request from '../lib/LocalizedRequest'; +import { Logger, stringifyError } from '../../utils/logging'; -export default class AdaWalletsStore extends Store { - // REQUESTS +export type SendMoneyRequest = Request>; - @observable - generateWalletRecoveryPhraseRequest: Request = new Request( - this.api.ada.generateWalletRecoveryPhrase - ); +export default class TransactionProcessingStore extends Store { + @observable sendMoneyRequest: SendMoneyRequest = new Request< + DeferredCall<{| txId: string |}> + >(request => request()); - // =================== SEND MONEY ==================== // + sendAndRefresh: ({| + publicDeriverId: void | number, + plateTextPart: void | string, + broadcastRequest: void => Promise<{| txId: string |}>, + refreshWallet: () => Promise, + |}) => Promise<{| txId: string |}> = async request => { + this.sendMoneyRequest.reset(); + const resp = await this.sendMoneyRequest.execute(async () => { + const result = await request.broadcastRequest(); + + if (request.publicDeriverId != null) { + const memo = this.stores.transactionBuilderStore.memo; + if (memo !== '' && memo !== undefined && request.plateTextPart) { + try { + await this.stores.memos.saveTxMemo({ + publicDeriverId: request.publicDeriverId, + plateTextPart: request.plateTextPart, + memo: { + Content: memo, + TransactionHash: result.txId, + LastUpdated: new Date(), + }, + }); + } catch (error) { + Logger.error( + `${nameof(TransactionProcessingStore)}::${nameof(this.sendAndRefresh)} error: ` + + stringifyError(error) + ); + throw new Error('An error has ocurred when saving the transaction memo.'); + } + } + } + try { + await request.refreshWallet(); + } catch (_e) { + // even if refreshing the wallet fails, we don't want to fail the tx + // otherwise user may try and re-send the tx + } + return result; + }).promise; + if (resp == null) throw new Error(`Should never happen`); + return resp; + }; adaSendAndRefresh: ({| broadcastRequest: @@ -110,10 +141,10 @@ export default class AdaWalletsStore extends Store { plateTextPart = wallet.plate.TextPart; } else { throw new Error( - `${nameof(AdaWalletsStore)}::${nameof(this.adaSendAndRefresh)} unhandled wallet type` + `${nameof(TransactionProcessingStore)}::${nameof(this.adaSendAndRefresh)} unhandled wallet type` ); }; - await this.stores.wallets.sendAndRefresh({ + await this.sendAndRefresh({ publicDeriverId, broadcastRequest, refreshWallet: request.refreshWallet, @@ -210,76 +241,7 @@ export default class AdaWalletsStore extends Store { return { signedTxHex }; } throw new Error( - `${nameof(AdaWalletsStore)}::${nameof(this.adaSignTransactionHex)} unhandled wallet type` + `${nameof(TransactionProcessingStore)}::${nameof(this.adaSignTransactionHex)} unhandled wallet type` ); }; - - // =================== WALLET RESTORATION ==================== // - - startWalletCreation: ({| - name: string, - password: string, - |}) => Promise = async params => { - const recoveryPhrase = await this.generateWalletRecoveryPhraseRequest.execute({}).promise; - if (recoveryPhrase == null) { - throw new Error(`${nameof(this.startWalletCreation)} failed to generate recovery phrase`); - } - this.stores.walletBackup.initiateWalletBackup({ - recoveryPhrase, - name: params.name, - password: params.password, - }); - }; - - genWalletRecoveryPhrase: void => Promise> = async () => { - const recoveryPhrase = await this.generateWalletRecoveryPhraseRequest.execute({}).promise; - - if (recoveryPhrase == null) { - throw new Error(`${nameof(this.startWalletCreation)} failed to generate recovery phrase`); - } - - return recoveryPhrase; - }; - - /** Create the wallet and go to wallet summary screen */ - finishWalletBackup: void => Promise = async () => { - await this.createWallet({ - recoveryPhrase: this.stores.walletBackup.recoveryPhrase, - walletPassword: this.stores.walletBackup.password, - walletName: this.stores.walletBackup.name, - }); - }; - - createWallet: {| - recoveryPhrase: Array, - walletPassword: string, - walletName: string, - |} => Promise = async (request) => { - const { selectedNetwork } = this.stores.profile; - if (selectedNetwork == null) throw new Error(`${nameof(this.finishWalletBackup)} no network selected`); - await this.stores.wallets.createWalletRequest.execute(async () => { - const wallet = await createWallet({ - walletName: request.walletName, - walletPassword: request.walletPassword, - recoveryPhrase: request.recoveryPhrase.join(' '), - networkId: selectedNetwork.NetworkId, - accountIndex: 0 + HARD_DERIVATION_START, - }); - return wallet; - }).promise; - }; - - pickCollateralUtxo: ({| wallet: WalletState |}) => Promise = async ({ wallet }) => { - const allUtxos = wallet.utxos; - if (allUtxos.length === 0) { - fail('Cannot pick a collateral utxo! No utxo available at all in the wallet!'); - } - const utxoDefaultCoinAmount = (u: QueriedUtxo): BigNumber => - new BigNumber(u.output.tokens.find(x => x.Token.Identifier === '')?.TokenList.Amount ?? 0); - const compareDefaultCoins = (a: QueriedUtxo, b: QueriedUtxo): number => - utxoDefaultCoinAmount(a).comparedTo(utxoDefaultCoinAmount(b)); - const smallPureUtxos = allUtxos - .filter(u => u.output.tokens.length === 1 && utxoDefaultCoinAmount(u).lte(MAX_PICKED_COLLATERAL_UTXO_ADA)); - return first(sorted(smallPureUtxos, compareDefaultCoins)); - } } diff --git a/packages/yoroi-extension/app/stores/toplevel/WalletStore.js b/packages/yoroi-extension/app/stores/toplevel/WalletStore.js index 07828cab0e..04abefa19b 100644 --- a/packages/yoroi-extension/app/stores/toplevel/WalletStore.js +++ b/packages/yoroi-extension/app/stores/toplevel/WalletStore.js @@ -8,7 +8,6 @@ import config from '../../config'; import globalMessages from '../../i18n/global-messages'; import type { Notification } from '../../types/notification.types'; import type { IGetLastSyncInfoResponse } from '../../api/ada/lib/storage/models/PublicDeriver/interfaces'; -import { Logger, stringifyError } from '../../utils/logging'; import type { WalletChecksum } from '@emurgo/cip4-js'; import { createDebugWalletDialog } from '../../containers/wallet/dialogs/DebugWalletDialogContainer'; import { createProblematicWalletDialog } from '../../containers/wallet/dialogs/ProblematicWalletDialogContainer'; @@ -29,8 +28,6 @@ import { asAddressedUtxo } from '../../api/ada/transactions/utils'; declare var chrome; */ -export type SendMoneyRequest = Request>; - /** * The base wallet store that contains the shared logic * dealing with wallets / accounts. @@ -45,10 +42,6 @@ export default class WalletStore extends Store { @observable getInitialWallets: Request = new Request(getWallets); - @observable sendMoneyRequest: SendMoneyRequest = new Request< - DeferredCall<{| txId: string |}> - >(request => request()); - @observable createWalletRequest: Request< (() => Promise) => Promise > = new Request(async create => { @@ -364,50 +357,6 @@ export default class WalletStore extends Store { } }; - sendAndRefresh: ({| - publicDeriverId: void | number, - plateTextPart: void | string, - broadcastRequest: void => Promise<{| txId: string |}>, - refreshWallet: () => Promise, - |}) => Promise<{| txId: string |}> = async request => { - this.sendMoneyRequest.reset(); - const resp = await this.sendMoneyRequest.execute(async () => { - const result = await request.broadcastRequest(); - - if (request.publicDeriverId != null) { - const memo = this.stores.transactionBuilderStore.memo; - if (memo !== '' && memo !== undefined && request.plateTextPart) { - try { - await this.stores.memos.saveTxMemo({ - publicDeriverId: request.publicDeriverId, - plateTextPart: request.plateTextPart, - memo: { - Content: memo, - TransactionHash: result.txId, - LastUpdated: new Date(), - }, - }); - } catch (error) { - Logger.error( - `${nameof(WalletStore)}::${nameof(this.sendAndRefresh)} error: ` + - stringifyError(error) - ); - throw new Error('An error has ocurred when saving the transaction memo.'); - } - } - } - try { - await request.refreshWallet(); - } catch (_e) { - // even if refreshing the wallet fails, we don't want to fail the tx - // otherwise user may try and re-send the tx - } - return result; - }).promise; - if (resp == null) throw new Error(`Should never happen`); - return resp; - }; - @action onRenameSelectedWallet: (string) => void = (newName) => { this.selectedWalletName = newName; if (this.selectedIndex != null) { diff --git a/packages/yoroi-extension/app/stores/toplevel/YoroiTransferStore.js b/packages/yoroi-extension/app/stores/toplevel/YoroiTransferStore.js index aaed7def81..3c34a95e56 100644 --- a/packages/yoroi-extension/app/stores/toplevel/YoroiTransferStore.js +++ b/packages/yoroi-extension/app/stores/toplevel/YoroiTransferStore.js @@ -194,7 +194,7 @@ export default class YoroiTransferStore extends Store { const { next } = payload; try { - await this.stores.wallets.sendAndRefresh({ + await this.stores.transactionProcessingStore.sendAndRefresh({ publicDeriverId: undefined, plateTextPart: undefined, broadcastRequest: async () => { @@ -256,7 +256,7 @@ export default class YoroiTransferStore extends Store { this.status = TransferStatus.UNINITIALIZED; this.error = null; this.transferTx = null; - this.stores.wallets.sendMoneyRequest.reset(); + this.stores.transactionProcessingStore.sendMoneyRequest.reset(); this.recoveryPhrase = ''; if (this.stores.profile.selectedNetwork != null) { From f4cdca79375c36519c62c981039877cd1c712184 Mon Sep 17 00:00:00 2001 From: yushi Date: Fri, 21 Mar 2025 01:38:01 +0800 Subject: [PATCH 22/32] remove dead code --- .../send/WalletSendConfirmationDialog.js | 257 ------------------ .../send/WalletSendConfirmationDialog.scss | 218 --------------- .../app/containers/wallet/WalletSendPage.js | 140 +--------- .../WalletSendConfirmationDialogContainer.js | 93 ------- 4 files changed, 1 insertion(+), 707 deletions(-) delete mode 100644 packages/yoroi-extension/app/components/wallet/send/WalletSendConfirmationDialog.js delete mode 100644 packages/yoroi-extension/app/components/wallet/send/WalletSendConfirmationDialog.scss delete mode 100644 packages/yoroi-extension/app/containers/wallet/dialogs/WalletSendConfirmationDialogContainer.js diff --git a/packages/yoroi-extension/app/components/wallet/send/WalletSendConfirmationDialog.js b/packages/yoroi-extension/app/components/wallet/send/WalletSendConfirmationDialog.js deleted file mode 100644 index 20af537f32..0000000000 --- a/packages/yoroi-extension/app/components/wallet/send/WalletSendConfirmationDialog.js +++ /dev/null @@ -1,257 +0,0 @@ -// @flow - -/* eslint react/jsx-one-expression-per-line: 0 */ // the   in the html breaks this - -import type { Node } from 'react'; -import React, { Component } from 'react'; -import { observer } from 'mobx-react'; -import classnames from 'classnames'; -import TextField from '../../common/TextField'; -import { intlShape } from 'react-intl'; -import ReactToolboxMobxForm from '../../../utils/ReactToolboxMobxForm'; -import vjf from 'mobx-react-form/lib/validators/VJF'; -import Dialog from '../../widgets/Dialog'; -import DialogCloseButton from '../../widgets/DialogCloseButton'; -import globalMessages from '../../../i18n/global-messages'; -import LocalizableError from '../../../i18n/LocalizableError'; -import styles from './WalletSendConfirmationDialog.scss'; -import config from '../../../config'; -import ExplorableHashContainer from '../../../containers/widgets/ExplorableHashContainer'; -import RawHash from '../../widgets/hashWrappers/RawHash'; -import { SelectedExplorer } from '../../../domain/SelectedExplorer'; -import type { UnitOfAccountSettingType } from '../../../types/unitOfAccountType'; -import WarningBox from '../../widgets/WarningBox'; -import type { $npm$ReactIntl$IntlFormat } from 'react-intl'; -import { truncateAddress, truncateToken } from '../../../utils/formatters'; -import { MultiToken } from '../../../api/common/lib/MultiToken'; -import type { TokenLookupKey, TokenEntry } from '../../../api/common/lib/MultiToken'; -import type { TokenRow } from '../../../api/ada/lib/storage/database/primitives/tables'; -import { getTokenName, genFormatTokenAmount } from '../../../stores/stateless/tokenHelpers'; -import { Box } from '@mui/system'; - -type Props = {| - +staleTx: boolean, - +selectedExplorer: SelectedExplorer, - +amount: MultiToken, - +receivers: Array, - +totalAmount: MultiToken, - +transactionFee: MultiToken, - +transactionSize: ?string, - +onSubmit: ({| password: string |}) => PossiblyAsync, - +addressToDisplayString: string => string, - +onCancel: void => void, - +isSubmitting: boolean, - +error: ?LocalizableError, - +unitOfAccountSetting: UnitOfAccountSettingType, - +getTokenInfo: ($ReadOnly>) => $ReadOnly, - +getCurrentPrice: (from: string, to: string) => ?string, -|}; - -@observer -export default class WalletSendConfirmationDialog extends Component { - static contextTypes: {| intl: $npm$ReactIntl$IntlFormat |} = { - intl: intlShape.isRequired, - }; - - form: ReactToolboxMobxForm = new ReactToolboxMobxForm( - { - fields: { - walletPassword: { - type: 'password', - label: this.context.intl.formatMessage(globalMessages.walletPasswordLabel), - placeholder: '', - value: '', - validators: [ - ({ field }) => { - if (field.value === '') { - return [false, this.context.intl.formatMessage(globalMessages.fieldIsRequired)]; - } - return [true]; - }, - ], - }, - }, - }, - { - options: { - validateOnChange: true, - validationDebounceWait: config.forms.FORM_VALIDATION_DEBOUNCE_WAIT, - }, - plugins: { - vjf: vjf(), - }, - } - ); - - submit(): void { - this.form.submit({ - onSuccess: async form => { - const { walletPassword } = form.values(); - const transactionData = { - password: walletPassword, - }; - await this.props.onSubmit(transactionData); - }, - onError: () => {}, - }); - } - - renderSingleAmount: TokenEntry => Node = entry => { - const formatValue = genFormatTokenAmount(this.props.getTokenInfo); - - return ( -
- {formatValue(entry)} -  {truncateToken(getTokenName(this.props.getTokenInfo(entry)))} -
- ); - }; - renderTotalAmount: TokenEntry => Node = entry => { - const formatValue = genFormatTokenAmount(this.props.getTokenInfo); - - return ( -
- {formatValue(entry)} -  {truncateToken(getTokenName(this.props.getTokenInfo(entry)))} -
- ); - }; - renderSingleFee: TokenEntry => Node = entry => { - const formatValue = genFormatTokenAmount(this.props.getTokenInfo); - - return ( -
- +{formatValue(entry)} -  {truncateToken(getTokenName(this.props.getTokenInfo(entry)))} -
- ); - }; - - renderBundle: ({| - amount: MultiToken, - render: TokenEntry => Node, - |}) => Node = request => { - return ( - <> - {request.render(request.amount.getDefaultEntry())} - {request.amount.nonDefaultEntries().map(entry => ( - {request.render(entry)} - ))} - - ); - }; - - render(): Node { - const { form } = this; - const { intl } = this.context; - const walletPasswordField = form.$('walletPassword'); - const { onCancel, amount, receivers, isSubmitting, error } = this.props; - - const staleTxWarning = ( -
- - {intl.formatMessage(globalMessages.staleTxnWarningLine1)} -
- {intl.formatMessage(globalMessages.staleTxnWarningLine2)} -
-
- ); - - const confirmButtonClasses = classnames(['confirmButton', isSubmitting ? styles.submitButtonSpinning : null]); - - const actions = [ - { - label: intl.formatMessage(globalMessages.backButtonLabel), - disabled: isSubmitting, - onClick: onCancel, - }, - { - label: intl.formatMessage(globalMessages.sendButtonLabel), - onClick: this.submit.bind(this), - primary: true, - className: confirmButtonClasses, - isSubmitting, - disabled: !walletPasswordField.isValid, - }, - ]; - - return ( - } - > - {this.props.staleTx && staleTxWarning} - -
-
-
- {intl.formatMessage(globalMessages.walletSendConfirmationAddressToLabel)}sdsdsdsds -
- {[...new Set(receivers)].map((receiver, i) => ( - - - - {truncateAddress(this.props.addressToDisplayString(receiver))} - - - - ))} -
- - {this.props.transactionSize != null ? ( -
-
{intl.formatMessage(globalMessages.walletSendConfirmationTxSizeLabel)}
- {this.props.transactionSize} -
- ) : null} - -
-
-
{intl.formatMessage(globalMessages.amountLabel)}
- {this.renderBundle({ - amount, - render: this.renderSingleAmount, - })} -
- -
-
{intl.formatMessage(globalMessages.walletSendConfirmationFeesLabel)}
- {this.renderBundle({ - amount: this.props.transactionFee, - render: this.renderSingleFee, - })} -
-
- -
-
{intl.formatMessage(globalMessages.walletSendConfirmationTotalLabel)}
- {this.renderBundle({ - amount: this.props.totalAmount, - render: this.renderTotalAmount, - })} -
- - -
- - {error ?
{intl.formatMessage(error, error.values)}
: null} -
- ); - } -} diff --git a/packages/yoroi-extension/app/components/wallet/send/WalletSendConfirmationDialog.scss b/packages/yoroi-extension/app/components/wallet/send/WalletSendConfirmationDialog.scss deleted file mode 100644 index 59d27bc84d..0000000000 --- a/packages/yoroi-extension/app/components/wallet/send/WalletSendConfirmationDialog.scss +++ /dev/null @@ -1,218 +0,0 @@ -@import '../../../themes/mixins/loading-spinner'; -@import '../../../themes/mixins/error-message'; - -.dialog { - font-weight: 500; - - :global { - .Dialog_title { - margin-bottom: 24px; - } - } - - .warningBox { - margin-bottom: 24px; - } - - .submitButtonSpinning { - @include loading-spinner('../../../assets/images/spinner-light.svg'); - } - - .currencySymbol { - font-weight: 300; - text-transform: uppercase; - } - - .addressToLabelWrapper, - .txSizeWrapper, - .amountFeesWrapper { - margin-bottom: 20px; - } - - .addressToLabel { - font-size: 11px; - font-weight: 500; - letter-spacing: 0.5px; - line-height: 1.38; - margin-left: 10px; - text-transform: uppercase; - } - - .addressTo { - font-size: 15px; - line-height: 1.38; - margin-top: 6px; - } - - .txSize { - font-size: 13px; - line-height: 1.28; - margin-top: 6px; - color: #b0b0b0; - } - - .amountFeesWrapper { - display: flex; - flex-direction: row; - - .amountWrapper, - .feesWrapper { - width: 50%; - } - - .amountWrapper { - margin-right: 10px; - } - - .feesWrapper { - margin-left: 10px; - } - - .amountLabel, - .feesLabel { - font-size: 11px; - font-weight: 500; - letter-spacing: 0.5px; - line-height: 1.38; - margin-left: 10px; - text-transform: uppercase; - } - - .amount, - .fees { - font-size: 15px; - line-height: 1.38; - margin-top: 6px; - word-break: break-word; - } - - .amountSmall, - .feesSmall { - @extend .amount; - font-size: 12px; - } - } - - .totalAmountLabel { - font-size: 11px; - font-weight: 500; - letter-spacing: 0.5px; - line-height: 1.38; - margin-left: 10px; - text-transform: uppercase; - } - - .totalAmount { - color: var(--yoroi-send-confirmation-dialog-send-values-color); - line-height: 1.38; - margin-top: 6px; - word-break: break-word; - } - - .totalAmountSmall { - @extend .totalAmount; - font-size: 13px; - } - - .walletPassword { - margin-top: 20px; - } - - .error { - @include error-message; - text-align: center; - } -} - -:global(.YoroiClassic) .dialog { - .amountFeesWrapper { - .amount, - .fees { - color: var(--yoroi-send-confirmation-dialog-send-values-color); - } - - .fees { - opacity: 0.5; - } - } -} - -:global(.YoroiModern) .dialog { - min-width: var(--yoroi-comp-dialog-min-width-lg); - max-width: var(--yoroi-comp-dialog-min-width-lg); - font-weight: 400; - - .currencySymbol { - font-weight: 400; - } - - .addressToLabelWrapper, - .amountFeesWrapper { - margin-bottom: 34px; - } - - .addressToLabel { - font-size: 16px; - font-weight: normal; - letter-spacing: 0; - margin-left: 0; - text-transform: none; - } - - .addressTo { - font-size: 14px; - line-height: 1.57; - margin-top: 2px; - } - - .totalAmountWrapper { - margin-bottom: 24px; - } - - .walletPassword { - margin-top: 0; - } - - .amountFeesWrapper { - .amountLabel, - .feesLabel { - font-size: 16px; - font-weight: normal; - letter-spacing: 0; - margin-left: 0; - text-transform: none; - } - - .amount, - .fees { - font-size: 14px; - line-height: 1.57; - margin-top: 2px; - opacity: 0.4; - } - - .amountSmall, - .feesSmall { - @extend .amount; - font-size: 12px; - } - } - - .totalAmountLabel { - font-size: 16px; - font-weight: normal; - letter-spacing: 0; - margin-left: 0; - text-transform: none; - } - - .totalAmount { - font-weight: 500; - line-height: 1.57; - margin-top: 2px; - - .currencySymbol { - font-weight: 500; - } - } -} diff --git a/packages/yoroi-extension/app/containers/wallet/WalletSendPage.js b/packages/yoroi-extension/app/containers/wallet/WalletSendPage.js index 25d6c9d797..e64b9629c7 100644 --- a/packages/yoroi-extension/app/containers/wallet/WalletSendPage.js +++ b/packages/yoroi-extension/app/containers/wallet/WalletSendPage.js @@ -6,12 +6,7 @@ import { action, observable, runInAction } from 'mobx'; import type { $npm$ReactIntl$IntlFormat } from 'react-intl'; import { defineMessages, intlShape } from 'react-intl'; import { ROUTES } from '../../routes-config'; - import WalletSendFormRevamp from '../../components/wallet/send/WalletSendFormRevamp'; - -// Web Wallet Confirmation -import WalletSendConfirmationDialogContainer from './dialogs/WalletSendConfirmationDialogContainer'; -import WalletSendConfirmationDialog from '../../components/wallet/send/WalletSendConfirmationDialog'; import MemoNoExternalStorageDialog from '../../components/wallet/memos/MemoNoExternalStorageDialog'; import { HaskellShelleyTxSignRequest } from '../../api/ada/transactions/shelley/HaskellShelleyTxSignRequest'; import { addressToDisplayString } from '../../api/ada/lib/storage/bridge/utils'; @@ -19,9 +14,6 @@ import type { TokenRow } from '../../api/ada/lib/storage/database/primitives/tab import { genLookupOrFail } from '../../stores/stateless/tokenHelpers'; import BigNumber from 'bignumber.js'; import TransactionSuccessDialog from '../../components/wallet/send/TransactionSuccessDialog'; - -// Hardware Wallet Confirmation -import HWSendConfirmationDialog from '../../components/wallet/send/HWSendConfirmationDialog'; import globalMessages from '../../i18n/global-messages'; import AddNFTDialog from '../../components/wallet/send/WalletSendFormSteps/AddNFTDialog'; import AddTokenDialog from '../../components/wallet/send/WalletSendFormSteps/AddTokenDialog'; @@ -151,7 +143,7 @@ export default class WalletSendPage extends Component { const { hasAnyPending } = stores.transactions; // disallow sending when pending tx exists - if ((uiDialogs.isOpen(HWSendConfirmationDialog) || uiDialogs.isOpen(WalletSendConfirmationDialog)) && hasAnyPending) { + if (hasAnyPending) { stores.uiDialogs.closeActiveDialog(); } @@ -239,12 +231,6 @@ export default class WalletSendPage extends Component { renderDialog: () => Node = () => { const { uiDialogs } = this.props.stores; - if (uiDialogs.isOpen(WalletSendConfirmationDialog)) { - return this.webWalletDoConfirmation(); - } - if (uiDialogs.isOpen(HWSendConfirmationDialog)) { - return this.hardwareWalletDoConfirmation(); - } if (uiDialogs.isOpen(MemoNoExternalStorageDialog)) { return this.noCloudWarningDialog(); } @@ -264,130 +250,6 @@ export default class WalletSendPage extends Component { return ''; }; - /** Web Wallet Send Confirmation dialog - * Callback that creates a container to avoid the component knowing about actions/stores */ - webWalletDoConfirmation: () => Node = () => { - const { stores } = this.props; - const { selected } = this.props.stores.wallets; - if (!selected) throw new Error(`Active wallet required for ${nameof(this.webWalletDoConfirmation)}.`); - - const { transactionBuilderStore } = this.props.stores; - if (!transactionBuilderStore.tentativeTx) { - throw new Error(`${nameof(this.webWalletDoConfirmation)}::should never happen`); - } - const signRequest = transactionBuilderStore.tentativeTx; - - return ( - - ); - }; - - /** Hardware Wallet (Trezor or Ledger) Confirmation dialog - * Callback that creates a component to avoid the component knowing about actions/stores - * separate container is not needed, this container acts as container for Confirmation dialog */ - hardwareWalletDoConfirmation: () => Node = () => { - const { selected } = this.props.stores.wallets; - if (!selected) throw new Error(`Active wallet required for ${nameof(this.webWalletDoConfirmation)}.`); - const { transactionBuilderStore } = this.props.stores; - - if (!transactionBuilderStore.tentativeTx) { - throw new Error(`${nameof(this.hardwareWalletDoConfirmation)}::should never happen`); - } - const signRequest = transactionBuilderStore.tentativeTx; - - const totalInput = signRequest.totalInput(); - const fee = signRequest.fee(); - const receivers = signRequest.receivers(false); - - let hwSendConfirmationDialog: Node = null; - - if (!(signRequest instanceof HaskellShelleyTxSignRequest)) { - throw new Error(`${nameof(this.hardwareWalletDoConfirmation)} hw wallets only supported for Byron`); - } - const selectedExplorerForNetwork = - this.props.stores.explorers.selectedExplorer.get(selected.networkId) ?? - (() => { - throw new Error('No explorer for wallet network'); - })(); - - if (selected.type === 'ledger') { - const messagesLedgerNano = { - infoLine1: messages.txConfirmationLedgerNanoLine1, - infoLine2: globalMessages.txConfirmationLedgerNanoLine2, - sendUsingHWButtonLabel: messages.sendUsingLedgerNano, - }; - const ledgerSendStore = this.props.stores.substores.ada.ledgerSend; - ledgerSendStore.init(); - hwSendConfirmationDialog = ( - - ledgerSendStore.sendUsingLedgerWallet({ - params: { signRequest }, - onSuccess: this.openTransactionSuccessDialog, - wallet: selected, - }) - } - onCancel={ledgerSendStore.cancel} - unitOfAccountSetting={this.props.stores.profile.unitOfAccount} - addressToDisplayString={addr => addressToDisplayString(addr, getNetworkById(selected.networkId))} - /> - ); - } else if (selected.type === 'trezor') { - const messagesTrezor = { - infoLine1: messages.txConfirmationTrezorTLine1, - infoLine2: globalMessages.txConfirmationTrezorTLine2, - sendUsingHWButtonLabel: messages.sendUsingTrezorT, - }; - const trezorSendStore = this.props.stores.substores.ada.trezorSend; - hwSendConfirmationDialog = ( - - trezorSendStore.sendUsingTrezor({ - params: { signRequest }, - onSuccess: this.openTransactionSuccessDialog, - wallet: selected, - }) - } - onCancel={trezorSendStore.cancel} - unitOfAccountSetting={this.props.stores.profile.unitOfAccount} - addressToDisplayString={addr => addressToDisplayString(addr, getNetworkById(selected.networkId))} - /> - ); - } else { - throw new Error('Unsupported hardware wallet found at hardwareWalletDoConfirmation.'); - } - - return hwSendConfirmationDialog; - }; - showMemoDialog: ({| continuation: void => void, dialog: any, diff --git a/packages/yoroi-extension/app/containers/wallet/dialogs/WalletSendConfirmationDialogContainer.js b/packages/yoroi-extension/app/containers/wallet/dialogs/WalletSendConfirmationDialogContainer.js deleted file mode 100644 index cab7eab786..0000000000 --- a/packages/yoroi-extension/app/containers/wallet/dialogs/WalletSendConfirmationDialogContainer.js +++ /dev/null @@ -1,93 +0,0 @@ -// @flow -import type { Node } from 'react'; -import { Component } from 'react'; -import { observer } from 'mobx-react'; -import WalletSendConfirmationDialog from '../../../components/wallet/send/WalletSendConfirmationDialog'; -import type { UnitOfAccountSettingType } from '../../../types/unitOfAccountType'; -import { addressToDisplayString } from '../../../api/ada/lib/storage/bridge/utils'; -import type { ISignRequest } from '../../../api/common/lib/transactions/ISignRequest'; -import { genLookupOrFail } from '../../../stores/stateless/tokenHelpers'; -import { getNetworkById } from '../../../api/ada/lib/storage/database/prepackaged/networks'; -import type { StoresProps } from '../../../stores'; - -// TODO: unmagic the constants -const MAX_VALUE_BYTES = 5000; -const MAX_TX_BYTES = 16384; - -type LocalProps = {| - +signRequest: ISignRequest, - +staleTx: boolean, - +unitOfAccountSetting: UnitOfAccountSettingType, - +openTransactionSuccessDialog: () => void, -|}; - -type Props = {| ...StoresProps, ...LocalProps |}; - -@observer -export default class WalletSendConfirmationDialogContainer extends Component { - - componentWillUnmount() { - this.props.stores.transactionProcessingStore.sendMoneyRequest.reset(); - } - - render(): Node { - const { - signRequest, - unitOfAccountSetting, - openTransactionSuccessDialog, - } = this.props; - const { stores } = this.props; - const publicDeriver = stores.wallets.selected; - - if (publicDeriver == null) - throw new Error(`Active wallet required for ${nameof(WalletSendConfirmationDialogContainer)}`); - - const { sendMoneyRequest } = stores.transactionProcessingStore; - - const totalInput = signRequest.totalInput(); - const fee = signRequest.fee(); - const size = signRequest.size?.(); - const fullSize = size ? size.full : 0; - const maxOutput = size ? Math.max(...size.outputs) : 0; - const showSize = size != null && ( - size.full > (MAX_TX_BYTES - 1000) - || maxOutput > (MAX_VALUE_BYTES - 1000) - ); - const receivers = signRequest.receivers(false); - return ( - { throw new Error('No explorer for wallet network'); })() - } - getTokenInfo={genLookupOrFail(stores.tokenInfoStore.tokenInfo)} - getCurrentPrice={stores.coinPriceStore.getCurrentPrice} - amount={totalInput.joinSubtractCopy(fee)} - receivers={receivers} - totalAmount={totalInput} - transactionFee={fee} - transactionSize={showSize ? `${fullSize}/${MAX_TX_BYTES} (Biggest output: ${maxOutput}/${MAX_VALUE_BYTES})` : null} - onSubmit={async ({ password }) => { - await stores.substores.ada.mnemonicSend.sendMoney({ - signRequest, - password, - wallet: publicDeriver, - onSuccess: openTransactionSuccessDialog, - }); - }} - isSubmitting={sendMoneyRequest.isExecuting} - onCancel={() => { - stores.uiDialogs.closeActiveDialog(); - sendMoneyRequest.reset(); - }} - error={sendMoneyRequest.error} - unitOfAccountSetting={unitOfAccountSetting} - addressToDisplayString={ - addr => addressToDisplayString(addr, getNetworkById(publicDeriver.networkId)) - } - /> - ); - } -} From d9e201dafbdd007fdb3fab38986594986b3a3cfc Mon Sep 17 00:00:00 2001 From: yushi Date: Fri, 21 Mar 2025 02:32:45 +0800 Subject: [PATCH 23/32] remove dead code --- .../wallet/send/WalletSendFormRevamp.js | 63 +- .../WalletSendPreviewStep.js | 638 ------------------ .../WalletSendPreviewStepContainer.js | 171 ----- .../app/types/WalletSendTypes.js | 1 - 4 files changed, 13 insertions(+), 860 deletions(-) delete mode 100644 packages/yoroi-extension/app/components/wallet/send/WalletSendFormSteps/WalletSendPreviewStep.js delete mode 100644 packages/yoroi-extension/app/components/wallet/send/WalletSendFormSteps/WalletSendPreviewStepContainer.js diff --git a/packages/yoroi-extension/app/components/wallet/send/WalletSendFormRevamp.js b/packages/yoroi-extension/app/components/wallet/send/WalletSendFormRevamp.js index 30e091351b..407d4b5eca 100644 --- a/packages/yoroi-extension/app/components/wallet/send/WalletSendFormRevamp.js +++ b/packages/yoroi-extension/app/components/wallet/send/WalletSendFormRevamp.js @@ -48,7 +48,6 @@ import type { UnitOfAccountSettingType } from '../../../types/unitOfAccountType' import { calculateAndFormatValue } from '../../../utils/unit-of-account'; import { CannotSendBelowMinimumValueError } from '../../../api/common/errors'; import { getImageFromTokenMetadata } from '../../../utils/nftMetadata'; -import WalletSendPreviewStepContainer from './WalletSendFormSteps/WalletSendPreviewStepContainer'; import type { ISignRequest } from '../../../api/common/lib/transactions/ISignRequest'; import { ampli } from '../../../../ampli/index'; import type { DomainResolverFunc, DomainResolverResponse } from '../../../stores/ada/AdaAddressesStore'; @@ -955,29 +954,6 @@ export default class WalletSendFormRevamp extends Component { /> ); - case SEND_FORM_STEP.PREVIEW: - return ( - - ); default: throw Error(`${step} is not a valid step`); } @@ -1063,21 +1039,19 @@ export default class WalletSendFormRevamp extends Component { > {this.renderCurrentStep(currentStep)} - {currentStep !== SEND_FORM_STEP.PREVIEW && ( - bodyRef.clientHeight ? '1px solid' : '0'} - borderColor="grayscale.200" - display="flex" - alignItems="center" - justifyContent="center" - gap="24px" - p="24px" - mx="-24px" - mt="30px" - > - {this.renderCurrentFooter(currentStep)} - - )} + bodyRef.clientHeight ? '1px solid' : '0'} + borderColor="grayscale.200" + display="flex" + alignItems="center" + justifyContent="center" + gap="24px" + p="24px" + mx="-24px" + mt="30px" + > + {this.renderCurrentFooter(currentStep)} + @@ -1091,17 +1065,6 @@ export default class WalletSendFormRevamp extends Component { this.maxStep = step; if (step === SEND_FORM_STEP.AMOUNT) { ampli.sendSelectAssetPageViewed(); - } else if (step === SEND_FORM_STEP.PREVIEW) { - const { totalInput } = this.props; - if (totalInput == null) { - throw new Error('expect totalInput'); - } - ampli.sendSelectAssetSelected({ - asset_count: totalInput.nonDefaultEntries().length, - }); - ampli.sendSummaryPageViewed({ - asset_count: totalInput.nonDefaultEntries().length, - }); } } } diff --git a/packages/yoroi-extension/app/components/wallet/send/WalletSendFormSteps/WalletSendPreviewStep.js b/packages/yoroi-extension/app/components/wallet/send/WalletSendFormSteps/WalletSendPreviewStep.js deleted file mode 100644 index 59434363f8..0000000000 --- a/packages/yoroi-extension/app/components/wallet/send/WalletSendFormSteps/WalletSendPreviewStep.js +++ /dev/null @@ -1,638 +0,0 @@ -// @flow - -/* eslint react/jsx-one-expression-per-line: 0 */ // the   in the html breaks this - -import type { Node } from 'react'; -import type { UnitOfAccountSettingType } from '../../../../types/unitOfAccountType'; -import type { $npm$ReactIntl$IntlFormat, $npm$ReactIntl$MessageDescriptor } from 'react-intl'; -import type { TokenLookupKey, TokenEntry } from '../../../../api/common/lib/MultiToken'; -import type { TokenRow, NetworkRow } from '../../../../api/ada/lib/storage/database/primitives/tables'; -import type LocalizableError from '../../../../i18n/LocalizableError'; -import { observer } from 'mobx-react'; -import { defineMessages, intlShape, FormattedMessage } from 'react-intl'; -import { SelectedExplorer } from '../../../../domain/SelectedExplorer'; -import { calculateAndFormatValue } from '../../../../utils/unit-of-account'; -import { truncateToken } from '../../../../utils/formatters'; -import { MultiToken } from '../../../../api/common/lib/MultiToken'; -import { getTokenName, genFormatTokenAmount } from '../../../../stores/stateless/tokenHelpers'; -import { Button, Link, Stack, Tooltip, Typography, styled } from '@mui/material'; -import { getNFTs, getTokens } from '../../../../utils/wallet'; -import { IncorrectWalletPasswordError } from '../../../../api/common/errors'; -import { isCardanoHaskell } from '../../../../api/ada/lib/storage/database/prepackaged/networks'; -import { Box } from '@mui/system'; -import { ReactComponent as InfoIcon } from '../../../../assets/images/attention-big-light.inline.svg'; -import React, { Component } from 'react'; -import TextField from '../../../common/TextField'; -import ReactToolboxMobxForm from '../../../../utils/ReactToolboxMobxForm'; -import vjf from 'mobx-react-form/lib/validators/VJF'; -import globalMessages from '../../../../i18n/global-messages'; -import styles from './WalletSendPreviewStep.scss'; -import config from '../../../../config'; -import WarningBox from '../../../widgets/WarningBox'; -import AssetsDropdown from './AssetsDropdown'; -import LoadingSpinner from '../../../widgets/LoadingSpinner'; -import { SEND_FORM_STEP } from '../../../../types/WalletSendTypes'; -import { ReactComponent as AttentionIcon } from '../../../../assets/images/attention-modern.inline.svg'; - -const SBox = styled(Box)(({ theme }) => ({ - backgroundImage: theme.palette.ds.bg_gradient_3, - color: 'ds.gray_min', -})); - -const SBoxHWNotes = styled(Box)(({ theme }) => ({ - flex: 1, - display: 'flex', - flexDirection: 'column', - padding: '16px', - marginTop: '16px', - backgroundImage: theme.palette.ds.bg_gradient_1, - borderRadius: '8px', -})); - -type Props = {| - +staleTx: boolean, - +selectedExplorer: SelectedExplorer, - +amount: MultiToken, - +receiver: {| address: string, handle: {| handle: string, nameServer: string |} | void |}, - +totalAmount: MultiToken, - +transactionFee: MultiToken, - +transactionSize: ?string, - +onSubmit: ({| password: string |}) => PossiblyAsync, - +addressToDisplayString: string => string, - +isSubmitting: boolean, - +unitOfAccountSetting: UnitOfAccountSettingType, - +getTokenInfo: ($ReadOnly>) => $ReadOnly, - +getCurrentPrice: (from: string, to: string) => ?string, - +isDefaultIncluded: boolean, - +minAda: ?MultiToken, - +plannedTxInfoMap: Array<{| - token: $ReadOnly, - amount?: string, - shouldSendAll?: boolean, - |}>, - +selectedNetwork: $ReadOnly, - +walletType: 'trezor' | 'ledger' | 'mnemonic', - +ledgerSendError: ?LocalizableError, - +trezorSendError: ?LocalizableError, - +onUpdateStep: (step: number) => void, -|}; - -type State = {| - passwordError: string | null, - txError: string | null, -|}; - -const messages = defineMessages({ - receiverHandleLabel: { - id: 'wallet.send.form.preview.receiverHandleLabel', - defaultMessage: '!!!Receiver', - }, - receiverLabel: { - id: 'wallet.send.form.preview.receiverLabel', - defaultMessage: '!!!Receiver wallet address', - }, - nAssets: { - id: 'wallet.send.form.preview.nAssets', - defaultMessage: '!!!{number} Assets', - }, - minAdaHelp: { - id: 'wallet.send.form.preview.minAdaHelp', - defaultMessage: '!!!Minimum ADA required to send these assets. {moreDetails}', - }, - moreDetails: { - id: 'wallet.send.form.preview.moreDetails', - defaultMessage: '!!!More details here', - }, - txConfirmationLedgerNanoLine1: { - id: 'wallet.send.ledger.confirmationDialog.info.line.1', - defaultMessage: '!!!After connecting your Ledger device to your computer’s USB port, press the Send using Ledger button.', - }, - sendUsingLedgerNano: { - id: 'wallet.send.ledger.confirmationDialog.submit', - defaultMessage: '!!!Send using Ledger', - }, - txConfirmationTrezorTLine1: { - id: 'wallet.send.trezor.confirmationDialog.info.line.1', - defaultMessage: '!!!After connecting your Trezor device to your computer, press the Send using Trezor button.', - }, - sendUsingTrezorT: { - id: 'wallet.send.trezor.confirmationDialog.submit', - defaultMessage: '!!!Send using Trezor', - }, - transactionErrorTitle: { - id: 'wallet.send.error.title.transactionError', - defaultMessage: '!!!Transaction Error', - }, - transactionErrorMnemonic: { - id: 'wallet.send.error.description.mnemonic', - defaultMessage: '!!!The transaction cannot be done due to technical reasons. Try again or ask our support team', - }, -}); - -@observer -export default class WalletSendPreviewStep extends Component { - static contextTypes: {| intl: $npm$ReactIntl$IntlFormat |} = { - intl: intlShape.isRequired, - }; - - state: State = { - passwordError: null, - txError: null, - }; - - form: ReactToolboxMobxForm = new ReactToolboxMobxForm( - { - fields: { - walletPassword: { - type: 'password', - label: this.context.intl.formatMessage(globalMessages.walletPasswordLabel), - placeholder: '', - value: '', - validators: [ - ({ field }) => { - if (field.value === '') { - return [false, this.context.intl.formatMessage(globalMessages.fieldIsRequired)]; - } - return [true]; - }, - ], - }, - }, - }, - { - options: { - validateOnChange: true, - validationDebounceWait: config.forms.FORM_VALIDATION_DEBOUNCE_WAIT, - }, - plugins: { - vjf: vjf(), - }, - } - ); - - submit(): void { - if (this.props.walletType === 'mnemonic') { - this.form.submit({ - onSuccess: async form => { - const { walletPassword } = form.values(); - const transactionData = { - password: walletPassword, - }; - try { - await this.props.onSubmit(transactionData); - } catch (error) { - const errorMessage = this.context.intl.formatMessage(error, error.values); - if (error instanceof IncorrectWalletPasswordError) { - this.setState({ passwordError: errorMessage }); - } else { - this.setState({ txError: errorMessage }); - } - } - }, - onError: () => {}, - }); - } else { - // hw wallets are not using passwords - this.props.onSubmit({ password: '' }); - } - } - - convertedToUnitOfAccount: (TokenEntry, string) => string = (token, toCurrency) => { - const tokenInfo = this.props.getTokenInfo(token); - - const shiftedAmount = token.amount.shiftedBy(-tokenInfo.Metadata.numberOfDecimals); - - const ticker = tokenInfo.Metadata.ticker; - if (ticker == null) { - throw new Error('unexpected main token type'); - } - const coinPrice = this.props.getCurrentPrice(ticker, toCurrency); - - if (coinPrice == null) return '-'; - - return calculateAndFormatValue(shiftedAmount, coinPrice); - }; - - renderDefaultTokenAmount: TokenEntry => Node = entry => { - const formatValue = genFormatTokenAmount(this.props.getTokenInfo); - return ( -
- {formatValue(entry)} -  {truncateToken(getTokenName(this.props.getTokenInfo(entry)))} -
- ); - }; - - renderTotalAmount: TokenEntry => Node = entry => { - const formatValue = genFormatTokenAmount(this.props.getTokenInfo); - - const { unitOfAccountSetting } = this.props; - return unitOfAccountSetting.enabled ? ( - - - - {formatValue(entry)} - - -  {truncateToken(getTokenName(this.props.getTokenInfo(entry)))} - - - - - {this.convertedToUnitOfAccount(entry, unitOfAccountSetting.currency)} - - -  {unitOfAccountSetting.currency} - - - - ) : ( - - - {formatValue(entry)} - - -  {truncateToken(getTokenName(this.props.getTokenInfo(entry)))} - - - ); - }; - renderSingleFee: TokenEntry => Node = entry => { - const formatValue = genFormatTokenAmount(this.props.getTokenInfo); - return ( -
- {formatValue(entry)} -  {truncateToken(getTokenName(this.props.getTokenInfo(entry)))} -
- ); - }; - - renderBundle: ({| - amount: MultiToken, - render: TokenEntry => Node, - |}) => Node = request => { - return ( - <> - {request.render(request.amount.getDefaultEntry())} - {request.amount.nonDefaultEntries().map(entry => ( - {request.render(entry)} - ))} - - ); - }; - - _amountLabel: void => Node = () => { - const { selectedNetwork, plannedTxInfoMap, minAda } = this.props; - const { intl } = this.context; - const isCardano = isCardanoHaskell(selectedNetwork); - - if (isCardano) { - const tokenInfo = plannedTxInfoMap.find(({ token }) => token.IsDefault); - if ( - (!tokenInfo || // Show Min-Ada label if the ADA is not included - // Or if included ADA less than Min-ADA - minAda?.getDefaultEntry().amount.gt(tokenInfo.amount ?? 0)) && - !tokenInfo?.shouldSendAll - ) { - const moreDetailsLink = ( - - {intl.formatMessage(messages.moreDetails)} - - ); - return ( - - {intl.formatMessage(globalMessages.minAda)} - - - - - } - > - svg': { - width: 20, - height: 20, - }, - }} - > - - - - - ); - } - } - - return {intl.formatMessage(globalMessages.amountLabel)}; - }; - - renderHWWalletInfo(): Node { - const { intl } = this.context; - const { walletType } = this.props; - if (walletType === 'mnemonic') { - return null; - } - - let infoLine1; - let infoLine2; - if (walletType === 'trezor') { - infoLine1 = messages.txConfirmationTrezorTLine1; - infoLine2 = globalMessages.txConfirmationTrezorTLine2; - } - if (walletType === 'ledger') { - infoLine1 = messages.txConfirmationLedgerNanoLine1; - infoLine2 = globalMessages.txConfirmationLedgerNanoLine2; - } - return ( - - - - {intl.formatMessage(infoLine1)} - - - - {intl.formatMessage(infoLine2)} - - - ); - } - - getSendButtonText(): $npm$ReactIntl$MessageDescriptor { - const { walletType } = this.props; - if (walletType === 'ledger') { - return messages.sendUsingLedgerNano; - } - if (walletType === 'trezor') { - return messages.sendUsingTrezorT; - } - return globalMessages.confirm; - } - - renderErrorBanner: (string, Node) => Node = (errorTitle, descriptionNode) => { - return ( - - - - - {errorTitle} - - - - {descriptionNode} - - - ); - }; - - renderError(): Node { - const { walletType } = this.props; - const { intl } = this.context; - const txErrorTitle = intl.formatMessage(messages.transactionErrorTitle); - if (walletType === 'mnemonic') { - const { txError } = this.state; - if (txError !== null) { - const re = /(.*)(.*)<\/link>(.*)/; - let m = intl.formatMessage(messages.transactionErrorMnemonic).match(re); - if (!m) { - // the translation has an error, fall back - m = [ - undefined, - 'The transaction cannot be done due to technical reasons. Try again or ', - 'ask our support team', - '' - ]; - } - return this.renderErrorBanner( - txErrorTitle, -
- {m[1]} - - {m[2]} - - {m[3]} -
- ); - } - return null; - } - if (walletType === 'trezor') { - const { trezorSendError } = this.props; - if (trezorSendError !== null) { - return this.renderErrorBanner(txErrorTitle, intl.formatMessage(trezorSendError)); - } - return null; - } - if (walletType === 'ledger') { - const { ledgerSendError } = this.props; - if (ledgerSendError !== null) { - return this.renderErrorBanner(txErrorTitle, intl.formatMessage(ledgerSendError)); - } - return null; - } - throw new Error('unexpected wallet type'); - } - - render(): Node { - const { form } = this; - const { intl } = this.context; - const walletPasswordField = form.$('walletPassword'); - const { amount, receiver, isSubmitting, walletType } = this.props; - const { passwordError } = this.state; - - const staleTxWarning = ( -
- - {intl.formatMessage(globalMessages.staleTxnWarningLine1)} -
- {intl.formatMessage(globalMessages.staleTxnWarningLine2)} -
-
- ); - - return ( -
- - - {this.renderError()} - {this.props.staleTx ?
{staleTxWarning}
: null} - {receiver.handle != null ? ( -
- - - {intl.formatMessage(messages.receiverHandleLabel)} - - - - - {receiver.handle?.nameServer}: {receiver.handle?.handle} - - -
- ) : null} -
- - - {intl.formatMessage(messages.receiverLabel)} - - - - - {this.props.addressToDisplayString(receiver.address)} - - -
- - - - {intl.formatMessage(globalMessages.walletSendConfirmationTotalLabel)} - -
- - {/* */} - {this.renderTotalAmount(this.props.totalAmount.getDefaultEntry())} - {/* */} - - {amount.nonDefaultEntries().length > 0 && ( - - {intl.formatMessage(messages.nAssets, { - number: amount.nonDefaultEntries().length, - })} - - )} -
-
- -
- - {intl.formatMessage(globalMessages.transactionFee)} - - - {this.renderBundle({ - amount: this.props.transactionFee, - render: this.renderSingleFee, - })} - -
- -
- - {this._amountLabel()} - - - {this.renderDefaultTokenAmount(amount.getDefaultEntry())} - -
- -
- {this.props.transactionSize != null ? ( -
- - {intl.formatMessage(globalMessages.walletSendConfirmationTxSizeLabel)} - - {this.props.transactionSize} -
- ) : null} - - - {amount.nonDefaultEntries().length > 0 && ( - - )} - - - {walletType === 'mnemonic' && ( - { - this.setState({ passwordError: null }); - walletPasswordField.set('value', e.target.value); - }} - error={walletPasswordField.error || passwordError} - sx={{ mt: '24px' }} - /> - )} -
- -
{this.renderHWWalletInfo()}
-
-
- - - - - - - -
- ); - } -} diff --git a/packages/yoroi-extension/app/components/wallet/send/WalletSendFormSteps/WalletSendPreviewStepContainer.js b/packages/yoroi-extension/app/components/wallet/send/WalletSendFormSteps/WalletSendPreviewStepContainer.js deleted file mode 100644 index e4d0735cd6..0000000000 --- a/packages/yoroi-extension/app/components/wallet/send/WalletSendFormSteps/WalletSendPreviewStepContainer.js +++ /dev/null @@ -1,171 +0,0 @@ -// @flow -import type { Node } from 'react'; -import { Component } from 'react'; -import { observer } from 'mobx-react'; -import WalletSendPreviewStep from './WalletSendPreviewStep'; -import type { UnitOfAccountSettingType } from '../../../../types/unitOfAccountType'; -import LocalizableError from '../../../../i18n/LocalizableError'; -import { SelectedExplorer } from '../../../../domain/SelectedExplorer'; -import { addressToDisplayString } from '../../../../api/ada/lib/storage/bridge/utils'; -import type { ISignRequest } from '../../../../api/common/lib/transactions/ISignRequest'; -import type { TokenRow } from '../../../../api/ada/lib/storage/database/primitives/tables'; -import type { MultiToken, TokenLookupKey } from '../../../../api/common/lib/MultiToken'; -import { ampli } from '../../../../../ampli/index'; -import type { SendMoneyRequest } from '../../../../stores/toplevel/TransactionProcessingStore'; -import { getNetworkById } from '../../../../api/ada/lib/storage/database/prepackaged/networks'; -import type { WalletState } from '../../../../../chrome/extension/background/types'; -import { HaskellShelleyTxSignRequest } from '../../../../api/ada/transactions/shelley/HaskellShelleyTxSignRequest'; -import LedgerSendStore from '../../../../stores/ada/send/LedgerSendStore'; -import TrezorSendStore from '../../../../stores/ada/send/TrezorSendStore'; - -// TODO: unmagic the constants -const MAX_VALUE_BYTES = 5000; -const MAX_TX_BYTES = 16384; - -type Props = {| - +signRequest: null | ISignRequest, - +staleTx: boolean, - +unitOfAccountSetting: UnitOfAccountSettingType, - +isDefaultIncluded: boolean, - +plannedTxInfoMap: Array<{| - token: $ReadOnly, - amount?: string, - shouldSendAll?: boolean, - |}>, - +minAda: ?MultiToken, - +onUpdateStep: (step: number) => void, - +getCurrentPrice: (from: string, to: string) => ?string, - +getTokenInfo: ($ReadOnly>) => $ReadOnly, - +openTransactionSuccessDialog: void => void, - +sendMoneyRequest: SendMoneyRequest, - +sendMoney: (params: {| - password: string, - +wallet: { - publicDeriverId: number, - +plate: { TextPart: string, ... }, - ... - }, - signRequest: ISignRequest, - onSuccess?: void => void, - |}) => Promise, - +ledgerSendError: null | LocalizableError, - +trezorSendError: null | LocalizableError, - +ledgerSend: LedgerSendStore, - +trezorSend: TrezorSendStore, - selectedExplorer: Map, - +selectedWallet: WalletState, -|}; - -@observer -export default class WalletSendPreviewStepContainer extends Component { - componentWillUnmount() { - this.props.sendMoneyRequest.reset(); - this.props.ledgerSend.cancel(); - this.props.trezorSend.cancel(); - } - - onSubmit: ({| password: string |}) => Promise = async ({ password }) => { - const { signRequest, openTransactionSuccessDialog } = this.props; - const { ledgerSend, trezorSend, sendMoney, selectedWallet } = this.props; - - if (signRequest == null) throw new Error('Unexpected missing active signing request'); - - const amount = signRequest.totalInput(); - const { numberOfDecimals } = this.props.getTokenInfo(amount.getDefaultEntry()).Metadata; - ampli.sendSummarySubmitted({ - ada_amount: amount.getDefault().shiftedBy(-numberOfDecimals).toNumber(), - asset_count: signRequest.totalInput().nonDefaultEntries().length, - }); - - if (selectedWallet.type === 'ledger') { - await ledgerSend.sendUsingLedgerWallet({ - params: { signRequest }, - onSuccess: openTransactionSuccessDialog, - wallet: selectedWallet, - }); - } else if (selectedWallet.type === 'trezor') { - await trezorSend.sendUsingTrezor({ - params: { signRequest }, - onSuccess: openTransactionSuccessDialog, - wallet: selectedWallet, - }); - } else { - // walletType === 'mnemonic' - await sendMoney({ - signRequest, - password, - wallet: selectedWallet, - onSuccess: openTransactionSuccessDialog, - }); - } - }; - - render(): Node { - const { - signRequest, - unitOfAccountSetting, - onUpdateStep, - selectedWallet, - selectedExplorer, - sendMoneyRequest, - getTokenInfo, - getCurrentPrice, - } = this.props; - - if (selectedWallet == null) - throw new Error(`Active wallet required for ${nameof(WalletSendPreviewStepContainer)}`); - if (signRequest == null) throw new Error('Unexpected missing active signing request'); - - const totalInput = signRequest.totalInput(); - const fee = signRequest.fee(); - const size = signRequest.size?.(); - const fullSize = size ? size.full : 0; - const maxOutput = size ? Math.max(...size.outputs) : 0; - const showSize = - size != null && (size.full > MAX_TX_BYTES - 1000 || maxOutput > MAX_VALUE_BYTES - 1000); - const network = getNetworkById(selectedWallet.networkId); - - const receiverWithHandle = signRequest instanceof HaskellShelleyTxSignRequest - ? signRequest.receiverWithHandle() - : null; - const receiver = { - address: receiverWithHandle?.address ?? signRequest.receivers(false)[0], - handle: receiverWithHandle?.handle, - } - - return ( - { - throw new Error('No explorer for wallet network'); - })() - } - getTokenInfo={getTokenInfo} - getCurrentPrice={getCurrentPrice} - amount={totalInput.joinSubtractCopy(fee)} - receiver={receiver} - totalAmount={totalInput} - transactionFee={fee} - transactionSize={ - showSize - ? `${fullSize}/${MAX_TX_BYTES} (Biggest output: ${maxOutput}/${MAX_VALUE_BYTES})` - : null - } - onSubmit={this.onSubmit} - isSubmitting={sendMoneyRequest.isExecuting} - unitOfAccountSetting={unitOfAccountSetting} - addressToDisplayString={addr => addressToDisplayString(addr, network)} - selectedNetwork={network} - isDefaultIncluded={this.props.isDefaultIncluded} - plannedTxInfoMap={this.props.plannedTxInfoMap} - minAda={this.props.minAda} - walletType={selectedWallet.type} - ledgerSendError={this.props.ledgerSendError} - trezorSendError={this.props.trezorSendError} - onUpdateStep={onUpdateStep} - /> - ); - } -} diff --git a/packages/yoroi-extension/app/types/WalletSendTypes.js b/packages/yoroi-extension/app/types/WalletSendTypes.js index 0ffcdce713..b970700abb 100644 --- a/packages/yoroi-extension/app/types/WalletSendTypes.js +++ b/packages/yoroi-extension/app/types/WalletSendTypes.js @@ -3,5 +3,4 @@ export const SEND_FORM_STEP = { RECEIVER: 1, AMOUNT: 2, - PREVIEW: 3, } From b2686530a62133966b407f47a9f2de4c6e7de752 Mon Sep 17 00:00:00 2001 From: yushi Date: Sat, 22 Mar 2025 01:36:10 +0800 Subject: [PATCH 24/32] unify transaction processing --- .../stores/ada/send/AdaMnemonicSendStore.js | 26 -- .../app/stores/ada/send/LedgerSendStore.js | 302 ------------- .../app/stores/ada/send/TrezorSendStore.js | 224 ---------- .../toplevel/TransactionProcessingStore.js | 412 +++++++++++++++++- 4 files changed, 407 insertions(+), 557 deletions(-) delete mode 100644 packages/yoroi-extension/app/stores/ada/send/AdaMnemonicSendStore.js delete mode 100644 packages/yoroi-extension/app/stores/ada/send/LedgerSendStore.js delete mode 100644 packages/yoroi-extension/app/stores/ada/send/TrezorSendStore.js diff --git a/packages/yoroi-extension/app/stores/ada/send/AdaMnemonicSendStore.js b/packages/yoroi-extension/app/stores/ada/send/AdaMnemonicSendStore.js deleted file mode 100644 index 97510a2fad..0000000000 --- a/packages/yoroi-extension/app/stores/ada/send/AdaMnemonicSendStore.js +++ /dev/null @@ -1,26 +0,0 @@ -// @flow - -import Store from '../../base/Store'; -import { HaskellShelleyTxSignRequest } from '../../../api/ada/transactions/shelley/HaskellShelleyTxSignRequest'; -import { - fullErrStr, - Logger, -} from '../../../utils/logging'; -import type { StoresMap } from '../../index'; -import { signAndBroadcastTransaction } from '../../../api/thunk'; - -export default class AdaMnemonicSendStore extends Store { - signAndBroadcast: {| - signRequest: HaskellShelleyTxSignRequest, - password: string, - publicDeriverId: number, - |} => Promise<{| txId: string |}> = async (request) => { - try { - const { txId } = await signAndBroadcastTransaction(request); - return { txId }; - } catch (error) { - Logger.error(`${nameof(AdaMnemonicSendStore)}::${nameof(this.signAndBroadcast)} error: ${fullErrStr(error)}` ); - throw error; - } - } -} diff --git a/packages/yoroi-extension/app/stores/ada/send/LedgerSendStore.js b/packages/yoroi-extension/app/stores/ada/send/LedgerSendStore.js deleted file mode 100644 index c4841ddb00..0000000000 --- a/packages/yoroi-extension/app/stores/ada/send/LedgerSendStore.js +++ /dev/null @@ -1,302 +0,0 @@ -// @flow -import { action, observable } from 'mobx'; -import type { SignTransactionResponse as LedgerSignTxResponse } from '@cardano-foundation/ledgerjs-hw-app-cardano'; -import { TxAuxiliaryDataSupplementType } from '@cardano-foundation/ledgerjs-hw-app-cardano'; -import Store from '../../base/Store'; -import { convertToLocalizableError } from '../../../domain/LedgerLocalizedError'; -import { Logger, stringifyData, stringifyError, } from '../../../utils/logging'; -import { buildConnectorSignedTransaction } from '../../../api/ada/transactions/shelley/ledgerTx'; -import { LedgerConnect } from '../../../utils/hwConnectHandler'; -import { RustModule } from '../../../api/ada/lib/cardanoCrypto/rustLoader'; -import type { - HaskellShelleyTxSignRequest, - LedgerNanoCatalystRegistrationTxSignData, -} from '../../../api/ada/transactions/shelley/HaskellShelleyTxSignRequest'; -import type { Addressing, Address, Value } from '../../../api/ada/lib/storage/models/PublicDeriver/interfaces'; -import { genAddressingLookup } from '../../stateless/addressStores'; -import type { StoresMap } from '../../index'; -import { - generateCip15RegistrationMetadata, - generateRegistrationMetadata, -} from '../../../api/ada/lib/cardanoCrypto/catalyst'; -import { getNetworkById } from '../../../api/ada/lib/storage/database/prepackaged/networks.js'; -import { broadcastTransaction } from '../../../api/thunk'; -import { transactionHexToBodyHex } from '../../../api/ada/lib/cardanoCrypto/utils'; -import { fail } from '../../../coreUtils'; -import type { ISignRequest } from '../../../api/common/lib/transactions/ISignRequest'; -import type { CardanoAddressedUtxo } from '../../../api/ada/transactions/types'; - -export type SendUsingLedgerParams = {| - signRequest: ISignRequest, -|}; - -/** Note: Handles Ledger Signing */ -export default class LedgerSendStore extends Store { - // =================== VIEW RELATED =================== // - // TODO: consider getting rid of both of these - @observable isActionProcessing: boolean = false; - // =================== VIEW RELATED =================== // - - /** setup() is called when stores are being created - * _init() is called when Confirmation dialog is about to show */ - init: void => void = () => { - Logger.debug(`${nameof(LedgerSendStore)}::${nameof(this.init)} called`); - } - - _reset(): void { - this._setActionProcessing(false); - } - - _preSendValidation: void => void = () => { - if (this.isActionProcessing) { - // this Error will be converted to LocalizableError() - throw new Error('Can\'t send another transaction if one transaction is in progress.'); - } - } - - /** Generates a payload with Ledger format and tries Send ADA using Ledger signing */ - signAndBroadcastFromWallet: {| - signRequest: HaskellShelleyTxSignRequest, - +wallet: { - publicDeriverId: number, - publicKey: string, - pathToPublic: Array, - networkId: number, - hardwareWalletDeviceId: ?string, - ... - }, - |} => Promise<{| txId: string |}> = async (request) => { - try { - Logger.debug(`${nameof(LedgerSendStore)}::${nameof(this.signAndBroadcastFromWallet)} called: ` + stringifyData(request)); - - const { signedTxHex, txId, metadata } = await this.signRawTxFromWallet({ - rawTxHex: request.signRequest.self().build_tx().to_hex(), - wallet: request.wallet, - catalystData: request.signRequest.ledgerNanoCatalystRegistrationTxSignData, - changeAddrs: request.signRequest.changeAddr, - additionalSenderUtxos: request.signRequest.senderUtxos, - }); - - if (metadata) { - request.signRequest.self().set_auxiliary_data(metadata); - } - - await broadcastTransaction({ - publicDeriverId: request.wallet.publicDeriverId, - signedTxHex, - networkId: request.networkId, - }); - - return { txId }; - } catch (error) { - Logger.error(`${nameof(LedgerSendStore)}::${nameof(this.signAndBroadcastFromWallet)} error: ` + stringifyError(error)); - throw new convertToLocalizableError(error); - } - }; - - signRawTxFromWallet: {| - rawTxHex: string, - +wallet: { - publicDeriverId: number, - publicKey: string, - pathToPublic: Array, - networkId: number, - hardwareWalletDeviceId: ?string, - ... - }, - changeAddrs: Array<{| ...Address, ...Value, ...Addressing |}>, - // The purpose of this parameter is to support transfering from Byron address when initializing - // Ledger wallets. It is needed because the wallet's utxos property no longer contains Byron UTxOs. - additionalSenderUtxos?: Array, - catalystData?: LedgerNanoCatalystRegistrationTxSignData, - |} => Promise<{| - signedTxHex: string, - txId: string, - metadata: ?RustModule.WalletV4.AuxiliaryData - |}> = async (request) => { - try { - Logger.debug(`${nameof(LedgerSendStore)}::${nameof(this.signRawTxFromWallet)} called: ` + stringifyData(request)); - - const publicKeyInfo = { - key: RustModule.WalletV4.Bip32PublicKey.from_hex(request.wallet.publicKey), - addressing: { - startLevel: 1, - path: request.wallet.pathToPublic, - }, - }; - - const expectedSerial = request.wallet.hardwareWalletDeviceId || ''; - - const addressingMap = genAddressingLookup( - request.wallet.networkId, - this.stores.addresses.addressSubgroupMap, - ); - - return this.signRawTx({ - rawTxHex: request.rawTxHex, - publicKey: publicKeyInfo, - addressingMap, - changeAddrs: request.changeAddrs, - expectedSerial, - networkId: request.wallet.networkId, - catalystData: request.catalystData, - additionalSenderUtxos: request.additionalSenderUtxos, - }); - - } catch (error) { - Logger.error(`${nameof(LedgerSendStore)}::${nameof(this.signRawTxFromWallet)} error: ` + stringifyError(error)); - throw new convertToLocalizableError(error); - } - } - - signRawTx: {| - rawTxHex: string, - publicKey: {| - key: RustModule.WalletV4.Bip32PublicKey, - ...Addressing, - |}, - addressingMap: string => (void | $PropertyType), - changeAddrs: Array<{| ...Address, ...Value, ...Addressing |}>, - networkId: number, - expectedSerial: string | void, - additionalSenderUtxos?: Array, - catalystData?: LedgerNanoCatalystRegistrationTxSignData, - |} => Promise<{| - signedTxHex: string, - txId: string, - metadata: ?RustModule.WalletV4.AuxiliaryData - |}> = async (request) => { - - let ledgerConnect: ?LedgerConnect; - try { - Logger.debug(`${nameof(LedgerSendStore)}::${nameof(this.signRawTx)} called: ` + stringifyData(request)); - - ledgerConnect = new LedgerConnect({ - locale: this.stores.profile.currentLocale, - }); - - let ledgerSupportsCip36: boolean = false; - if (request.catalystData) { - const getVersionResponse = await ledgerConnect.getVersion({ - serial: request.expectedSerial, - dontCloseTab: true, - }); - ledgerSupportsCip36 = getVersionResponse.compatibility.supportsCIP36Vote === true; - } - - const { rawTxHex } = request; - - const network = getNetworkById(request.networkId); - - const txBodyHex = transactionHexToBodyHex(rawTxHex); - - const addressedUtxos = [ - ...await this.stores.wallets.getAddressedUtxos(), - ...(request.additionalSenderUtxos || []) - ]; - - const response = this.api.ada.createHwSignTxDataFromRawTx('ledger', { - txBodyHex, - network, - addressingMap: request.addressingMap, - senderUtxos: addressedUtxos, - ledgerSupportsCip36, - catalystData: request.catalystData, - changeAddrs: request.changeAddrs, - }); - - const ledgerSignTxPayload = response.hw === 'ledger' ? response.result.ledgerSignTxPayload - : fail('Unecpected response type from `createHwSignTxDataFromRawTx` for ledger: ' + JSON.stringify(response)); - - const ledgerSignTxResp: LedgerSignTxResponse = - await ledgerConnect.signTransaction({ - serial: request.expectedSerial, - params: ledgerSignTxPayload, - useOpenTab: true, - }); - - // There is no need of ledgerConnect after this line. - // UI was getting blocked for few seconds - // because _prepareAndBroadcastSignedTx takes time. - // Disposing here will fix the UI issue. - ledgerConnect.dispose(); - - let metadata; - if (request.catalystData) { - const { - votingPublicKey, - stakingKey, - paymentAddress, - nonce, - } = request.catalystData; - - if ( - !ledgerSignTxResp.auxiliaryDataSupplement || - (ledgerSignTxResp.auxiliaryDataSupplement.type !== - TxAuxiliaryDataSupplementType.CIP36_REGISTRATION) - ) { - throw new Error(`${nameof(LedgerSendStore)}::${nameof(this.signRawTx)} unexpected Ledger sign transaction response`); - } - const { cip36VoteRegistrationSignatureHex } = - ledgerSignTxResp.auxiliaryDataSupplement; - - if (ledgerSupportsCip36) { - metadata = generateRegistrationMetadata( - votingPublicKey, - stakingKey, - paymentAddress, - nonce, - (_hashedMetadata) => { - return cip36VoteRegistrationSignatureHex; - }, - ); - } else { - metadata = generateCip15RegistrationMetadata( - votingPublicKey, - stakingKey, - paymentAddress, - nonce, - (_hashedMetadata) => { - return cip36VoteRegistrationSignatureHex; - }, - ); - } - // We can verify that - // Buffer.from( - // blake2b(256 / 8).update(metadata.to_bytes()).digest('binary') - // ).toString('hex') === - // ledgerSignTxResp.auxiliaryDataSupplement.auxiliaryDataHashaHex - } - - const { txHex, txId } = buildConnectorSignedTransaction( - rawTxHex, - ledgerSignTxResp.witnesses, - request.publicKey, - metadata, - new Map((request.additionalSenderUtxos || []).map( - ({ addressing, receiver }) => [addressing.path.join('/'), receiver] - )), - ); - - return { signedTxHex: txHex, txId, metadata }; - } catch (error) { - Logger.error(`${nameof(LedgerSendStore)}::${nameof(this.signRawTx)} error: ` + stringifyError(error)); - throw new convertToLocalizableError(error); - } finally { - if (ledgerConnect != null) { - ledgerConnect.dispose(); - } - } - }; - - cancel: void => void = () => { - if (!this.isActionProcessing) { - this.stores.uiDialogs.closeActiveDialog(); - this._reset(); - } - } - - @action _setActionProcessing: boolean => void = (processing) => { - this.isActionProcessing = processing; - } -} diff --git a/packages/yoroi-extension/app/stores/ada/send/TrezorSendStore.js b/packages/yoroi-extension/app/stores/ada/send/TrezorSendStore.js deleted file mode 100644 index abbc649f44..0000000000 --- a/packages/yoroi-extension/app/stores/ada/send/TrezorSendStore.js +++ /dev/null @@ -1,224 +0,0 @@ -// @flow -import { action, observable } from 'mobx'; -import Store from '../../base/Store'; -import { wrapWithFrame } from '../../lib/TrezorWrapper'; -import { Logger, stringifyData, stringifyError, } from '../../../utils/logging'; -import { convertToLocalizableError } from '../../../domain/TrezorLocalizedError'; -import type { - HaskellShelleyTxSignRequest, - TrezorTCatalystRegistrationTxSignData, -} from '../../../api/ada/transactions/shelley/HaskellShelleyTxSignRequest'; -import type { StoresMap } from '../../index'; -import { buildConnectorSignedTransaction } from '../../../api/ada/transactions/shelley/trezorTx'; -import { RustModule } from '../../../api/ada/lib/cardanoCrypto/rustLoader'; -import { generateRegistrationMetadata } from '../../../api/ada/lib/cardanoCrypto/catalyst'; -import type { Addressing, Address, Value } from '../../../api/ada/lib/storage/models/PublicDeriver/interfaces'; -import { getNetworkById } from '../../../api/ada/lib/storage/database/prepackaged/networks.js'; -import { broadcastTransaction } from '../../../api/thunk'; -import { transactionHexToBodyHex } from '../../../api/ada/lib/cardanoCrypto/utils'; -import { fail } from '../../../coreUtils'; -import { genAddressingLookup } from '../../stateless/addressStores'; -import type { ISignRequest } from '../../../api/common/lib/transactions/ISignRequest'; - -export type SendUsingTrezorParams = {| - signRequest: ISignRequest, -|}; - -/** Note: Handles Trezor Signing */ -export default class TrezorSendStore extends Store { - // =================== VIEW RELATED =================== // - // TODO: consider getting rid of both of these - @observable isActionProcessing: boolean = false; - // =================== VIEW RELATED =================== // - - // =================== API RELATED =================== // - - reset: void => void = () => { - this._setActionProcessing(false); - } - - signAndBroadcastFromWallet: {| - signRequest: HaskellShelleyTxSignRequest, - +wallet: { - publicDeriverId: number, - networkId: number, - publicKey: string, - pathToPublic: Array, - stakingAddressing: Addressing, - ... - }, - |} => Promise<{| txId: string |}> = async (request) => { - try { - Logger.debug(`${nameof(TrezorSendStore)}::${nameof(this.signAndBroadcastFromWallet)} called: ` + stringifyData(request)); - - const { signedTxHex, txId, metadata } = await this.signRawTxFromWallet({ - rawTxHex: request.signRequest.self().build_tx().to_hex(), - wallet: request.wallet, - catalystData: request.signRequest.trezorTCatalystRegistrationTxSignData, - changeAddrs: request.signRequest.changeAddr, - }); - - if (metadata) { - request.signRequest.self().set_auxiliary_data(metadata); - } - - await broadcastTransaction({ - publicDeriverId: request.wallet.publicDeriverId, - signedTxHex, - }); - - return { txId }; - } catch (error) { - Logger.error(`${nameof(TrezorSendStore)}::${nameof(this.signAndBroadcastFromWallet)} error: ` + stringifyError(error)); - throw new convertToLocalizableError(error); - } - } - - signRawTxFromWallet: {| - rawTxHex: string, - +wallet: { - publicDeriverId: number, - networkId: number, - publicKey: string, - pathToPublic: Array, - stakingAddressing: Addressing, - ... - }, - changeAddrs: Array<{| ...Address, ...Value, ...Addressing |}>, - catalystData?: TrezorTCatalystRegistrationTxSignData, - |} => Promise<{| - signedTxHex: string, - txId: string, - metadata: ?RustModule.WalletV4.AuxiliaryData - |}> = async (request) => { - try { - Logger.debug(`${nameof(TrezorSendStore)}::${nameof(this.signRawTxFromWallet)} called: ` + stringifyData(request)); - - const { rawTxHex, wallet, catalystData } = request; - - const addressingMap = genAddressingLookup( - wallet.networkId, - this.stores.addresses.addressSubgroupMap, - ); - - return this.signRawTx({ - rawTxHex, - addressingMap, - changeAddrs: request.changeAddrs, - networkId: wallet.networkId, - catalystData, - }); - - } catch (error) { - Logger.error(`${nameof(TrezorSendStore)}::${nameof(this.signRawTxFromWallet)} error: ` + stringifyError(error)); - throw new convertToLocalizableError(error); - } - } - - signRawTx: {| - rawTxHex: string, - addressingMap: string => (void | $PropertyType), - changeAddrs: Array<{| ...Address, ...Value, ...Addressing |}>, - networkId: number, - catalystData?: TrezorTCatalystRegistrationTxSignData, - |} => Promise<{| - signedTxHex: string, - txId: string, - metadata: ?RustModule.WalletV4.AuxiliaryData - |}> = async (request) => { - try { - const network = getNetworkById(request.networkId); - - const txBodyHex = transactionHexToBodyHex(request.rawTxHex); - - const addressedUtxos = await this.stores.wallets.getAddressedUtxos(); - - const response = this.api.ada.createHwSignTxDataFromRawTx('trezor', { - txBodyHex, - network, - addressingMap: request.addressingMap, - senderUtxos: addressedUtxos, - catalystData: request.catalystData, - changeAddrs: request.changeAddrs, - }); - - const trezorSignTxPayload = response.hw === 'trezor' ? response.result.trezorSignTxPayload - : fail('Unecpected response type from `createHwSignTxDataFromRawTx` for trezor: ' + JSON.stringify(response)); - - const trezorSignTxResp = await wrapWithFrame(trezor => { - return trezor.cardanoSignTransaction( - JSON.parse(JSON.stringify({ ...trezorSignTxPayload })) - ); - }); - - if (trezorSignTxResp && trezorSignTxResp.payload && trezorSignTxResp.payload.error != null) { - // this Error will be converted to LocalizableError() - // noinspection ExceptionCaughtLocallyJS - throw new Error(trezorSignTxResp.payload.error); - } - if (!trezorSignTxResp.success) { - // noinspection ExceptionCaughtLocallyJS - throw new Error(`${nameof(TrezorSendStore)}::${nameof(this.signRawTx)} should never happen`); - } - - let metadata; - - if (request.catalystData) { - const { - votingPublicKey, - stakingKey: stakingKeyHex, - paymentAddress, - nonce, - } = request.catalystData; - - const auxDataSupplement = trezorSignTxResp.payload.auxiliaryDataSupplement; - if ( - !auxDataSupplement - || auxDataSupplement.type !== 1 - || auxDataSupplement.governanceSignature == null - ) { - // noinspection ExceptionCaughtLocallyJS - throw new Error(`${nameof(TrezorSendStore)}::${nameof(this.signRawTx)} unexpected Trezor sign transaction response`); - } - const catalystSignature = auxDataSupplement.governanceSignature; - - metadata = generateRegistrationMetadata( - votingPublicKey, - stakingKeyHex, - paymentAddress, - nonce, - (_hashedMetadata) => { - return catalystSignature; - }, - ); - // We can verify that - // Buffer.from( - // blake2b(256 / 8).update(metadata.to_bytes()).digest('binary') - // ).toString('hex') === - // trezorSignTxResp.payload.auxiliaryDataSupplement.auxiliaryDataHash - } - - const { txHex, txId } = buildConnectorSignedTransaction( - request.rawTxHex, - trezorSignTxResp.payload.witnesses, - metadata, - ); - - return { signedTxHex: txHex, txId, metadata }; - } catch (error) { - Logger.error(`${nameof(TrezorSendStore)}::${nameof(this.signRawTx)} error: ` + stringifyError(error)); - throw new convertToLocalizableError(error); - } - } - - cancel: void => void = () => { - if (!this.isActionProcessing) { - this.stores.uiDialogs.closeActiveDialog(); - this.reset(); - } - } - - @action _setActionProcessing: boolean => void = (processing) => { - this.isActionProcessing = processing; - } -} diff --git a/packages/yoroi-extension/app/stores/toplevel/TransactionProcessingStore.js b/packages/yoroi-extension/app/stores/toplevel/TransactionProcessingStore.js index 455b2c7a7e..c7100bea45 100644 --- a/packages/yoroi-extension/app/stores/toplevel/TransactionProcessingStore.js +++ b/packages/yoroi-extension/app/stores/toplevel/TransactionProcessingStore.js @@ -1,15 +1,51 @@ // @flow import Store from '../base/Store'; import type { StoresMap } from '../index'; -import { HaskellShelleyTxSignRequest } from '../../api/ada/transactions/shelley/HaskellShelleyTxSignRequest'; -import { signTransaction } from '../../api/thunk'; -import type { Addressing } from '../../api/ada/lib/storage/models/PublicDeriver/interfaces'; +import { + HaskellShelleyTxSignRequest, + type LedgerNanoCatalystRegistrationTxSignData, + type TrezorTCatalystRegistrationTxSignData, +} from '../../api/ada/transactions/shelley/HaskellShelleyTxSignRequest'; +import { + signAndBroadcastTransaction, + signTransaction, + broadcastTransaction, +} from '../../api/thunk'; import { observable } from 'mobx'; import Request from '../lib/LocalizedRequest'; -import { Logger, stringifyError } from '../../utils/logging'; +import { Logger, stringifyError, stringifyData, fullErrStr } from '../../utils/logging'; +import { convertToLocalizableError as trezorConvertToLocalizableError } from '../../domain/TrezorLocalizedError'; +import { convertToLocalizableError as ledgerConvertToLocalizableError } from '../../domain/LedgerLocalizedError'; +import LocalizableError from '../../i18n/LocalizableError'; +import { + buildConnectorSignedTransaction as ledgerBuildConnectorSignedTransaction +} from '../../api/ada/transactions/shelley/ledgerTx'; +import { + buildConnectorSignedTransaction as trezorBuildConnectorSignedTransaction +} from '../../api/ada/transactions/shelley/trezorTx'; +import { convertToLocalizableError } from '../../domain/TrezorLocalizedError'; +import type { Addressing, Address, Value } from '../../api/ada/lib/storage/models/PublicDeriver/interfaces'; +import { + generateCip15RegistrationMetadata, + generateRegistrationMetadata, +} from '../../api/ada/lib/cardanoCrypto/catalyst'; +import { TxAuxiliaryDataSupplementType } from '@cardano-foundation/ledgerjs-hw-app-cardano'; +import { fail } from '../../coreUtils'; +import { transactionHexToBodyHex } from '../../api/ada/lib/cardanoCrypto/utils'; +import { getNetworkById } from '../../api/ada/lib/storage/database/prepackaged/networks.js'; +import { LedgerConnect } from '../../utils/hwConnectHandler'; +import { genAddressingLookup } from '../stateless/addressStores'; +import { RustModule } from '../../api/ada/lib/cardanoCrypto/rustLoader'; +import type { CardanoAddressedUtxo } from '../../api/ada/transactions/types'; +import { wrapWithFrame } from '../lib/TrezorWrapper'; +import type { WalletState } from '../../../chrome/extension/background/types'; export type SendMoneyRequest = Request>; +export type SendUsingTrezorParams = {| + signRequest: HaskellShelleyTxSignRequest, +|}; + export default class TransactionProcessingStore extends Store { @observable sendMoneyRequest: SendMoneyRequest = new Request< DeferredCall<{| txId: string |}> @@ -148,9 +184,375 @@ export default class TransactionProcessingStore extends Store { }); } else { throw new Error( - `${nameof(TransactionProcessingStore)}::${nameof(this.adaSignTransactionHex)} unhandled wallet type` + `${nameof(TransactionProcessingStore)}::${nameof(this.adaSignTransactionHexFromWallet)} unhandled wallet type` ); } return { signedTxHex: result.signedTxHex }; }; + + /* + mnemonic + */ + mnemonicWalletSignAndBroadcast: {| + signRequest: HaskellShelleyTxSignRequest, + password: string, + publicDeriverId: number, + |} => Promise<{| txId: string |}> = async (request) => { + try { + const { txId } = await signAndBroadcastTransaction(request); + return { txId }; + } catch (error) { + Logger.error(`${nameof(TransactionProcessingStore)}::${nameof(this.mnemonicWalletSignAndBroadcast)} error: ${fullErrStr(error)}` ); + throw error; + } + } + + /* + trezor + */ + trezorSignAndBroadcast: {| + signRequest: HaskellShelleyTxSignRequest, + +wallet: { + publicDeriverId: number, + networkId: number, + publicKey: string, + pathToPublic: Array, + stakingAddressing: Addressing, + ... + }, + |} => Promise<{| txId: string |}> = async (request) => { + try { + Logger.debug(`${nameof(TransactionProcessingStore)}::${nameof(this.trezorSignAndBroadcast)} called: ` + stringifyData(request)); + + const { signedTxHex, txId, metadata } = await this.trezorSignRawTx({ + rawTxHex: request.signRequest.self().build_tx().to_hex(), + wallet: request.wallet, + catalystData: request.signRequest.trezorTCatalystRegistrationTxSignData, + changeAddrs: request.signRequest.changeAddr, + }); + + if (metadata) { + request.signRequest.self().set_auxiliary_data(metadata); + } + + await broadcastTransaction({ + publicDeriverId: request.wallet.publicDeriverId, + signedTxHex, + }); + + return { txId }; + } catch (error) { + Logger.error(`${nameof(TransactionProcessingStore)}::${nameof(this.trezorSignAndBroadcast)} error: ` + stringifyError(error)); + throw new convertToLocalizableError(error); + } + } + + trezorSignRawTx: {| + rawTxHex: string, + +wallet: { + publicDeriverId: number, + networkId: number, + publicKey: string, + pathToPublic: Array, + stakingAddressing: Addressing, + ... + }, + changeAddrs: Array<{| ...Address, ...Value, ...Addressing |}>, + catalystData?: TrezorTCatalystRegistrationTxSignData, + |} => Promise<{| + signedTxHex: string, + txId: string, + metadata: ?RustModule.WalletV4.AuxiliaryData + |}> = async (request) => { + try { + Logger.debug(`${nameof(TransactionProcessingStore)}::${nameof(this.trezorSignRawTx)} called: ` + stringifyData(request)); + + const { rawTxHex, wallet, catalystData } = request; + + const addressingMap = genAddressingLookup( + wallet.networkId, + this.stores.addresses.addressSubgroupMap, + ); + + const network = getNetworkById(request.wallet.networkId); + + const txBodyHex = transactionHexToBodyHex(request.rawTxHex); + + const addressedUtxos = await this.stores.wallets.getAddressedUtxos(); + + const response = this.api.ada.createHwSignTxDataFromRawTx('trezor', { + txBodyHex, + network, + addressingMap, + senderUtxos: addressedUtxos, + catalystData: request.catalystData, + changeAddrs: request.changeAddrs, + }); + + const trezorSignTxPayload = response.hw === 'trezor' ? response.result.trezorSignTxPayload + : fail('Unecpected response type from `createHwSignTxDataFromRawTx` for trezor: ' + JSON.stringify(response)); + + const trezorSignTxResp = await wrapWithFrame(trezor => { + return trezor.cardanoSignTransaction( + JSON.parse(JSON.stringify({ ...trezorSignTxPayload })) + ); + }); + + if (trezorSignTxResp && trezorSignTxResp.payload && trezorSignTxResp.payload.error != null) { + // this Error will be converted to LocalizableError() + // noinspection ExceptionCaughtLocallyJS + throw new Error(trezorSignTxResp.payload.error); + } + if (!trezorSignTxResp.success) { + // noinspection ExceptionCaughtLocallyJS + throw new Error(`${nameof(TransactionProcessingStore)}::${nameof(this.trezorSignRawTx)} should never happen`); + } + + let metadata; + + if (request.catalystData) { + const { + votingPublicKey, + stakingKey: stakingKeyHex, + paymentAddress, + nonce, + } = request.catalystData; + + const auxDataSupplement = trezorSignTxResp.payload.auxiliaryDataSupplement; + if ( + !auxDataSupplement + || auxDataSupplement.type !== 1 + || auxDataSupplement.governanceSignature == null + ) { + // noinspection ExceptionCaughtLocallyJS + throw new Error(`${nameof(TransactionProcessingStore)}::${nameof(this.trezorSignRawTx)} unexpected Trezor sign transaction response`); + } + const catalystSignature = auxDataSupplement.governanceSignature; + + metadata = generateRegistrationMetadata( + votingPublicKey, + stakingKeyHex, + paymentAddress, + nonce, + (_hashedMetadata) => { + return catalystSignature; + }, + ); + // We can verify that + // Buffer.from( + // blake2b(256 / 8).update(metadata.to_bytes()).digest('binary') + // ).toString('hex') === + // trezorSignTxResp.payload.auxiliaryDataSupplement.auxiliaryDataHash + } + + const { txHex, txId } = trezorBuildConnectorSignedTransaction( + request.rawTxHex, + trezorSignTxResp.payload.witnesses, + metadata, + ); + + return { signedTxHex: txHex, txId, metadata }; + } catch (error) { + Logger.error(`${nameof(TransactionProcessingStore)}::${nameof(this.trezorSignRawTx)} error: ` + stringifyError(error)); + throw new convertToLocalizableError(error); + } + } + + /* + ledger + */ + ledgerWalletSignAndBroadcast: {| + signRequest: HaskellShelleyTxSignRequest, + +wallet: { + publicDeriverId: number, + publicKey: string, + pathToPublic: Array, + networkId: number, + hardwareWalletDeviceId: ?string, + ... + }, + |} => Promise<{| txId: string |}> = async (request) => { + try { + Logger.debug(`${nameof(TransactionProcessingStore)}::${nameof(this.ledgerWalletSignAndBroadcast)} called: ` + stringifyData(request)); + + const { signedTxHex, txId, metadata } = await this.ledgerWalletSignRawTx({ + rawTxHex: request.signRequest.self().build_tx().to_hex(), + wallet: request.wallet, + catalystData: request.signRequest.ledgerNanoCatalystRegistrationTxSignData, + changeAddrs: request.signRequest.changeAddr, + additionalSenderUtxos: request.signRequest.senderUtxos, + }); + + if (metadata) { + request.signRequest.self().set_auxiliary_data(metadata); + } + + await broadcastTransaction({ + publicDeriverId: request.wallet.publicDeriverId, + signedTxHex, + networkId: request.wallet.networkId, + }); + + return { txId }; + } catch (error) { + Logger.error(`${nameof(TransactionProcessingStore)}::${nameof(this.ledgerWalletSignAndBroadcast)} error: ` + stringifyError(error)); + throw new convertToLocalizableError(error); + } + }; + + ledgerWalletSignRawTx: {| + rawTxHex: string, + +wallet: { + publicDeriverId: number, + publicKey: string, + pathToPublic: Array, + networkId: number, + hardwareWalletDeviceId: ?string, + ... + }, + changeAddrs: Array<{| ...Address, ...Value, ...Addressing |}>, + // The purpose of this parameter is to support transfering from Byron address when initializing + // Ledger wallets. It is needed because the wallet's utxos property no longer contains Byron UTxOs. + additionalSenderUtxos?: Array, + catalystData?: LedgerNanoCatalystRegistrationTxSignData, + |} => Promise<{| + signedTxHex: string, + txId: string, + metadata: ?RustModule.WalletV4.AuxiliaryData + |}> = async (request) => { + try { + Logger.debug(`${nameof(TransactionProcessingStore)}::${nameof(this.ledgerWalletSignRawTx)} called: ` + stringifyData(request)); + + const publicKeyInfo = { + key: RustModule.WalletV4.Bip32PublicKey.from_hex(request.wallet.publicKey), + addressing: { + startLevel: 1, + path: request.wallet.pathToPublic, + }, + }; + + const expectedSerial = request.wallet.hardwareWalletDeviceId || ''; + + const addressingMap = genAddressingLookup( + request.wallet.networkId, + this.stores.addresses.addressSubgroupMap, + ); + + Logger.debug(`${nameof(TransactionProcessingStore)}::${nameof(this.ledgerWalletSignRawTx)} called: ` + stringifyData(request)); + + const ledgerConnect = new LedgerConnect({ + locale: this.stores.profile.currentLocale, + }); + + let ledgerSupportsCip36: boolean = false; + if (request.catalystData) { + const getVersionResponse = await ledgerConnect.getVersion({ + serial: expectedSerial, + dontCloseTab: true, + }); + ledgerSupportsCip36 = getVersionResponse.compatibility.supportsCIP36Vote === true; + } + + const { rawTxHex } = request; + + const network = getNetworkById(request.wallet.networkId); + + const txBodyHex = transactionHexToBodyHex(rawTxHex); + + const addressedUtxos = [ + ...await this.stores.wallets.getAddressedUtxos(), + ...(request.additionalSenderUtxos || []) + ]; + + const response = this.api.ada.createHwSignTxDataFromRawTx('ledger', { + txBodyHex, + network, + addressingMap, + senderUtxos: addressedUtxos, + ledgerSupportsCip36, + catalystData: request.catalystData, + changeAddrs: request.changeAddrs, + }); + + const ledgerSignTxPayload = response.hw === 'ledger' ? response.result.ledgerSignTxPayload + : fail('Unecpected response type from `createHwSignTxDataFromRawTx` for ledger: ' + JSON.stringify(response)); + + let ledgerSignTxResp; + try{ + ledgerSignTxResp = await ledgerConnect.signTransaction({ + serial: expectedSerial, + params: ledgerSignTxPayload, + useOpenTab: true, + }); + } finally { + // There is no need of ledgerConnect after this line. + // UI was getting blocked for few seconds + // because _prepareAndBroadcastSignedTx takes time. + // Disposing here will fix the UI issue. + ledgerConnect.dispose(); + } + + let metadata; + if (request.catalystData) { + const { + votingPublicKey, + stakingKey, + paymentAddress, + nonce, + } = request.catalystData; + + if ( + !ledgerSignTxResp.auxiliaryDataSupplement || + (ledgerSignTxResp.auxiliaryDataSupplement.type !== + TxAuxiliaryDataSupplementType.CIP36_REGISTRATION) + ) { + throw new Error(`${nameof(TransactionProcessingStore)}::${nameof(this.ledgerWalletSignRawTx)} unexpected Ledger sign transaction response`); + } + const { cip36VoteRegistrationSignatureHex } = + ledgerSignTxResp.auxiliaryDataSupplement; + + if (ledgerSupportsCip36) { + metadata = generateRegistrationMetadata( + votingPublicKey, + stakingKey, + paymentAddress, + nonce, + (_hashedMetadata) => { + return cip36VoteRegistrationSignatureHex; + }, + ); + } else { + metadata = generateCip15RegistrationMetadata( + votingPublicKey, + stakingKey, + paymentAddress, + nonce, + (_hashedMetadata) => { + return cip36VoteRegistrationSignatureHex; + }, + ); + } + // We can verify that + // Buffer.from( + // blake2b(256 / 8).update(metadata.to_bytes()).digest('binary') + // ).toString('hex') === + // ledgerSignTxResp.auxiliaryDataSupplement.auxiliaryDataHashaHex + } + + const { txHex, txId } = ledgerBuildConnectorSignedTransaction( + rawTxHex, + ledgerSignTxResp.witnesses, + publicKeyInfo, + metadata, + new Map((request.additionalSenderUtxos || []).map( + ({ addressing, receiver }) => [addressing.path.join('/'), receiver] + )), + ); + + return { signedTxHex: txHex, txId, metadata }; + } catch (error) { + Logger.error(`${nameof(TransactionProcessingStore)}::${nameof(this.ledgerWalletSignRawTx)} error: ` + stringifyError(error)); + throw new convertToLocalizableError(error); + } + } } From 15e2b48d3d26876f65ab2bf45062e4a518c9ee1e Mon Sep 17 00:00:00 2001 From: yushi Date: Mon, 24 Mar 2025 19:58:24 +0800 Subject: [PATCH 25/32] wip --- .../wallet/send/WalletSendFormRevamp.js | 6 --- .../containers/swap/asset-swap/SwapPage.js | 15 +++---- .../containers/transfer/TransferSendPage.js | 41 +++++++------------ .../yoroi-extension/app/stores/ada/index.js | 12 ------ .../toplevel/TransactionProcessingStore.js | 10 ++--- 5 files changed, 28 insertions(+), 56 deletions(-) diff --git a/packages/yoroi-extension/app/components/wallet/send/WalletSendFormRevamp.js b/packages/yoroi-extension/app/components/wallet/send/WalletSendFormRevamp.js index 407d4b5eca..06853bd339 100644 --- a/packages/yoroi-extension/app/components/wallet/send/WalletSendFormRevamp.js +++ b/packages/yoroi-extension/app/components/wallet/send/WalletSendFormRevamp.js @@ -57,8 +57,6 @@ import type { SendMoneyRequest } from '../../../stores/toplevel/TransactionProce import type { MaxSendableAmountRequest } from '../../../stores/toplevel/TransactionBuilderStore'; import type { WalletState } from '../../../../chrome/extension/background/types'; import LoadingSpinner from '../../widgets/LoadingSpinner'; -import LedgerSendStore from '../../../stores/ada/send/LedgerSendStore'; -import TrezorSendStore from '../../../stores/ada/send/TrezorSendStore'; import { SendTokensButton } from './SendTokensButton'; const messages = defineMessages({ @@ -222,10 +220,6 @@ type Props = {| signRequest: ISignRequest, onSuccess?: void => void, |}) => Promise, - +ledgerSendError: null | LocalizableError, - +trezorSendError: null | LocalizableError, - +ledgerSend: LedgerSendStore, - +trezorSend: TrezorSendStore, |}; const SMemoTextField = styled(MemoTextField)(({ theme }) => ({ diff --git a/packages/yoroi-extension/app/containers/swap/asset-swap/SwapPage.js b/packages/yoroi-extension/app/containers/swap/asset-swap/SwapPage.js index 8e7f1454c3..44d8b88d9b 100644 --- a/packages/yoroi-extension/app/containers/swap/asset-swap/SwapPage.js +++ b/packages/yoroi-extension/app/containers/swap/asset-swap/SwapPage.js @@ -283,15 +283,16 @@ function SwapPage(props: StoresProps & Intl): Node { validateSignRequestAndUserPassword(passswordInput); setOpenedDialog('loadingOverlay'); + const password = userPasswordState?.value; - const baseBroadcastRequest = { wallet, signRequest }; - const broadcastRequest = isHardwareWallet - ? { [walletType]: baseBroadcastRequest } - : { normal: { ...baseBroadcastRequest, password: passswordInput } }; try { - const refreshWallet = () => stores.wallets.refreshWalletFromRemote(wallet.publicDeriverId); - // $FlowIgnore[incompatible-call] - await stores.transactionProcessingStore.adaSendAndRefresh({ broadcastRequest, refreshWallet }); + await stores.transactionProcessingStore.adaSendAndRefresh({ + wallet, + signRequest, + password, + callback: () => stores.wallets.refreshWalletFromRemote(wallet.publicDeriverId), + }); + setOrderStepValue(2); showTxResultModal(TransactionResult.SUCCESS); diff --git a/packages/yoroi-extension/app/containers/transfer/TransferSendPage.js b/packages/yoroi-extension/app/containers/transfer/TransferSendPage.js index fa2b5bbbf2..63b21d7dad 100644 --- a/packages/yoroi-extension/app/containers/transfer/TransferSendPage.js +++ b/packages/yoroi-extension/app/containers/transfer/TransferSendPage.js @@ -69,8 +69,6 @@ export default class TransferSendPage extends Component<{| ...StoresProps, ...Lo const { stores } = this.props; stores.transactionProcessingStore.sendMoneyRequest.reset(); this.props.transactionRequest.reset(); - stores.substores.ada.ledgerSend.cancel(); - stores.substores.ada.trezorSend.cancel(); } submit: void => Promise = async () => { @@ -81,34 +79,25 @@ export default class TransferSendPage extends Component<{| ...StoresProps, ...Lo } const signRequest = this.props.transactionRequest.result; if (signRequest == null) return; + + const send = (password) => { + this.stores.transactionProcessingStore.adaSendAndRefresh({ + wallet: selected, + signRequest, + password, + callback: async () => { + if (stores.transactionProcessingStore.sendMoneyRequest.error == null) { + this.props.onSubmit.trigger(); + } + }, + }); + } if (this.spendingPasswordForm == null) { - if (selected.type === 'trezor') { - await stores.substores.ada.trezorSend.sendUsingTrezor({ - params: { signRequest }, - wallet: selected, - }); - } - if (selected.type === 'ledger') { - await stores.substores.ada.ledgerSend.sendUsingLedgerWallet({ - params: { signRequest }, - wallet: selected, - }); - } - if (stores.transactionProcessingStore.sendMoneyRequest.error == null) { - this.props.onSubmit.trigger(); - } + send(null); } else { this.spendingPasswordForm.submit({ onSuccess: async (form) => { - const { walletPassword } = form.values(); - await stores.substores.ada.mnemonicSend.sendMoney({ - signRequest, - password: walletPassword, - wallet: selected, - }); - if (stores.transactionProcessingStore.sendMoneyRequest.error == null) { - this.props.onSubmit.trigger(); - } + send(form.values().walletPassword); }, onError: () => {} }); diff --git a/packages/yoroi-extension/app/stores/ada/index.js b/packages/yoroi-extension/app/stores/ada/index.js index da9156ee4f..06f58ea108 100644 --- a/packages/yoroi-extension/app/stores/ada/index.js +++ b/packages/yoroi-extension/app/stores/ada/index.js @@ -8,15 +8,12 @@ import AdaTransactionsStore from './AdaTransactionsStore'; import AddressesStore from './AdaAddressesStore'; import AdaYoroiTransferStore from './AdaYoroiTransferStore'; import TrezorConnectStore from './TrezorConnectStore'; -import TrezorSendStore from './send/TrezorSendStore'; import LedgerConnectStore from './LedgerConnectStore'; -import LedgerSendStore from './send/LedgerSendStore'; import HWVerifyAddressStore from './HWVerifyAddressStore'; import AdaStateFetchStore from './AdaStateFetchStore'; import AdaWalletRestoreStore from './AdaWalletRestoreStore'; import AdaDelegationTransactionStore from './AdaDelegationTransactionStore'; import AdaDelegationStore from './AdaDelegationStore'; -import AdaMnemonicSendStore from './send/AdaMnemonicSendStore'; import VotingStore from './VotingStore'; import SwapStore from './SwapStore'; import type { Api } from '../../api/index'; @@ -29,16 +26,13 @@ export const adaStoreClasses = Object.freeze({ addresses: AddressesStore, yoroiTransfer: AdaYoroiTransferStore, trezorConnect: TrezorConnectStore, - trezorSend: TrezorSendStore, ledgerConnect: LedgerConnectStore, - ledgerSend: LedgerSendStore, hwVerifyAddress: HWVerifyAddressStore, stateFetchStore: AdaStateFetchStore, delegationTransaction: AdaDelegationTransactionStore, walletRestore: AdaWalletRestoreStore, delegation: AdaDelegationStore, time: BaseCardanoTimeStore, - mnemonicSend: AdaMnemonicSendStore, votingStore: VotingStore, swapStore: SwapStore, }); @@ -49,16 +43,13 @@ export type AdaStoresMap = {| addresses: AddressesStore, yoroiTransfer: AdaYoroiTransferStore, trezorConnect: TrezorConnectStore, - trezorSend: TrezorSendStore, ledgerConnect: LedgerConnectStore, - ledgerSend: LedgerSendStore, hwVerifyAddress: HWVerifyAddressStore, stateFetchStore: AdaStateFetchStore, delegationTransaction: AdaDelegationTransactionStore, walletRestore: AdaWalletRestoreStore, delegation: AdaDelegationStore, time: BaseCardanoTimeStore, - mnemonicSend: AdaMnemonicSendStore, votingStore: VotingStore, swapStore: SwapStore, |}; @@ -69,16 +60,13 @@ const adaStores: WithNullableFields = observable({ addresses: null, yoroiTransfer: null, trezorConnect: null, - trezorSend: null, ledgerConnect: null, - ledgerSend: null, hwVerifyAddress: null, stateFetchStore: null, delegationTransaction: null, walletRestore: null, delegation: null, time: null, - mnemonicSend: null, votingStore: null, swapStore: null, }); diff --git a/packages/yoroi-extension/app/stores/toplevel/TransactionProcessingStore.js b/packages/yoroi-extension/app/stores/toplevel/TransactionProcessingStore.js index c7100bea45..990bc0b13d 100644 --- a/packages/yoroi-extension/app/stores/toplevel/TransactionProcessingStore.js +++ b/packages/yoroi-extension/app/stores/toplevel/TransactionProcessingStore.js @@ -106,14 +106,14 @@ export default class TransactionProcessingStore extends Store { let broadcastRequest; if (wallet.type === 'ledger') { broadcastRequest = async () => { - return await this.stores.substores.ada.ledgerSend.signAndBroadcastFromWallet({ + return await this.ledgerWalletSignAndBroadcast({ signRequest, wallet, }); }; } else if (wallet.type === 'trezor') { broadcastRequest = async () => { - return await this.stores.substores.ada.trezorSend.signAndBroadcastFromWallet({ + return await this.trezorSignAndBroadcast({ signRequest, wallet, }); @@ -123,7 +123,7 @@ export default class TransactionProcessingStore extends Store { throw new Error('missing password for hardware wallet'); } broadcastRequest = async () => { - return await this.stores.substores.ada.mnemonicSend.signAndBroadcast({ + return await this.mnemonicWalletSignAndBroadcast({ signRequest, password, publicDeriverId: wallet.publicDeriverId, @@ -167,7 +167,7 @@ export default class TransactionProcessingStore extends Store { }); result = { signedTxHex }; } else if (wallet.type === 'trezor') { - result = await this.stores.substores.ada.trezorSend.signRawTxFromWallet({ + result = await this.trezorSignRawTx({ rawTxHex: transactionHex, wallet, // by happenstance the use case of this function is not to send @@ -175,7 +175,7 @@ export default class TransactionProcessingStore extends Store { changeAddrs: [], }); } else if (wallet.type === 'ledger') { - result = await this.stores.substores.ada.ledgerSend.signRawTxFromWallet({ + result = await this.ledgerWalletSignRawTx({ rawTxHex: transactionHex, wallet, // by happenstance the use case of this function is not to send From 54e1ef4200f00c2af215758288c8584926c81457 Mon Sep 17 00:00:00 2001 From: yushi Date: Mon, 24 Mar 2025 23:54:34 +0800 Subject: [PATCH 26/32] fix previous merge --- .../containers/wallet/staking/StakingPageContent.js | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/packages/yoroi-extension/app/containers/wallet/staking/StakingPageContent.js b/packages/yoroi-extension/app/containers/wallet/staking/StakingPageContent.js index 3dea916a7f..4da7206b9c 100644 --- a/packages/yoroi-extension/app/containers/wallet/staking/StakingPageContent.js +++ b/packages/yoroi-extension/app/containers/wallet/staking/StakingPageContent.js @@ -23,7 +23,6 @@ import { generateGraphData } from '../../../utils/graph'; import { calculateAndFormatValue } from '../../../utils/unit-of-account'; import DeregisterDialogContainer from '../../transfer/DeregisterDialogContainer'; import UnmangleTxDialogContainer from '../../transfer/UnmangleTxDialogContainer'; -import WithdrawalTxDialogContainer from '../../transfer/WithdrawalTxDialogContainer'; import WalletEmptyBanner from '../WalletEmptyBanner'; import { GovernanceParticipateDialog } from '../dialogs/GovernanceParticipateDialog'; import CardanoStakingPage from './CardanoStakingPage'; @@ -315,7 +314,6 @@ export default class StakingPageContent extends Component { // note: purposely don't await since the next dialog will properly render the spinner noop(stores.substores.ada.delegationTransaction.createWithdrawalTxForWallet({ wallet })); this.props.stores.uiDialogs.open({ - // dialog: WithdrawalTxDialogContainer, dialog: WithdrawRewardsDialog, }); }} @@ -327,15 +325,6 @@ export default class StakingPageContent extends Component { {uiDialogs.isOpen(UnmangleTxDialogContainer) ? ( ) : null} - {uiDialogs.isOpen(WithdrawalTxDialogContainer) ? ( - { - stores.substores.ada.delegationTransaction.reset({ justTransaction: false }); - this.props.stores.uiDialogs.closeActiveDialog(); - }} - /> - ) : null} {uiDialogs.isOpen(WithdrawRewardsDialog) ? ( Date: Tue, 25 Mar 2025 00:01:49 +0800 Subject: [PATCH 27/32] wip --- .../app/containers/transfer/TransferSendPage.js | 8 +++++++- .../app/containers/transfer/UpgradeTxDialogContainer.js | 7 ++++++- .../containers/wallet/staking/WithdrawRewardsDialog.js | 4 +--- 3 files changed, 14 insertions(+), 5 deletions(-) diff --git a/packages/yoroi-extension/app/containers/transfer/TransferSendPage.js b/packages/yoroi-extension/app/containers/transfer/TransferSendPage.js index 63b21d7dad..02f125cfa6 100644 --- a/packages/yoroi-extension/app/containers/transfer/TransferSendPage.js +++ b/packages/yoroi-extension/app/containers/transfer/TransferSendPage.js @@ -25,6 +25,7 @@ import { genAddressLookup } from '../../stores/stateless/addressStores'; import { genLookupOrFail } from '../../stores/stateless/tokenHelpers'; import { getNetworkById } from '../../api/ada/lib/storage/database/prepackaged/networks'; import type { StoresProps } from '../../stores'; +import { HaskellShelleyTxSignRequest } from '../../api/ada/transactions/shelley/HaskellShelleyTxSignRequest'; // populated by ConfigWebpackPlugin declare var CONFIG: ConfigType; @@ -81,7 +82,11 @@ export default class TransferSendPage extends Component<{| ...StoresProps, ...Lo if (signRequest == null) return; const send = (password) => { - this.stores.transactionProcessingStore.adaSendAndRefresh({ + if (!(signRequest instanceof HaskellShelleyTxSignRequest)) { + throw new Error('unexpected signRequest type'); + } + + stores.transactionProcessingStore.adaSendAndRefresh({ wallet: selected, signRequest, password, @@ -95,6 +100,7 @@ export default class TransferSendPage extends Component<{| ...StoresProps, ...Lo if (this.spendingPasswordForm == null) { send(null); } else { + // why do we have to submit the form this.spendingPasswordForm.submit({ onSuccess: async (form) => { send(form.values().walletPassword); diff --git a/packages/yoroi-extension/app/containers/transfer/UpgradeTxDialogContainer.js b/packages/yoroi-extension/app/containers/transfer/UpgradeTxDialogContainer.js index 9f1a816956..143d6d2cb9 100644 --- a/packages/yoroi-extension/app/containers/transfer/UpgradeTxDialogContainer.js +++ b/packages/yoroi-extension/app/containers/transfer/UpgradeTxDialogContainer.js @@ -145,7 +145,10 @@ export default class UpgradeTxDialogContainer extends Component<{| ...StoresProp getTokenInfo={genLookupOrFail(this.props.stores.tokenInfoStore.tokenInfo)} onSubmit={{ trigger: async () => { - await this.props.stores.substores.ada.ledgerSend.signAndBroadcastFromWallet({ + if (selected.type !== 'ledger') { + throw new Error('unexpected wallet type'); + } + await this.props.stores.transactionProcessingStore.adaSendAndRefresh({ signRequest: tentativeTx.signRequest, wallet: { ...selected, @@ -154,6 +157,8 @@ export default class UpgradeTxDialogContainer extends Component<{| ...StoresProp publicKey: tentativeTx.publicKey.key.to_hex(), pathToPublic: tentativeTx.publicKey.addressing.path, }, + password: null, + callback: async () => {}, }); this.props.onSubmit(); }, diff --git a/packages/yoroi-extension/app/containers/wallet/staking/WithdrawRewardsDialog.js b/packages/yoroi-extension/app/containers/wallet/staking/WithdrawRewardsDialog.js index a1ddfb058d..814469ffa3 100644 --- a/packages/yoroi-extension/app/containers/wallet/staking/WithdrawRewardsDialog.js +++ b/packages/yoroi-extension/app/containers/wallet/staking/WithdrawRewardsDialog.js @@ -77,8 +77,6 @@ export default class WithdrawRewardsDialog extends Component<{| ...StoresProps, const { stores } = this.props; stores.transactionProcessingStore.sendMoneyRequest.reset(); stores.substores.ada.delegationTransaction.createWithdrawalTx.reset(); - stores.substores.ada.ledgerSend.cancel(); - stores.substores.ada.trezorSend.cancel(); } signAndBroadcast: (?string) => Promise = async (password) => { @@ -91,7 +89,7 @@ export default class WithdrawRewardsDialog extends Component<{| ...StoresProps, throw new Error('Unexpected missing active signing request'); } - await this.props.stores.substores.ada.wallets.adaSendAndRefresh({ + await this.props.stores.transactionProcessingStore.adaSendAndRefresh({ wallet, signRequest, password, From 1a32debbd99b83f69f8bd40893ffb74e7ca147c8 Mon Sep 17 00:00:00 2001 From: yushi Date: Tue, 25 Mar 2025 00:08:34 +0800 Subject: [PATCH 28/32] wip --- .../components/wallet/send/WalletSendFormRevamp.js | 12 ------------ .../app/containers/wallet/WalletSendPage.js | 6 ------ .../app/stores/ada/AdaDelegationTransactionStore.js | 4 ++-- .../yoroi-extension/app/stores/ada/VotingStore.js | 4 ++-- 4 files changed, 4 insertions(+), 22 deletions(-) diff --git a/packages/yoroi-extension/app/components/wallet/send/WalletSendFormRevamp.js b/packages/yoroi-extension/app/components/wallet/send/WalletSendFormRevamp.js index 06853bd339..0dcab79d62 100644 --- a/packages/yoroi-extension/app/components/wallet/send/WalletSendFormRevamp.js +++ b/packages/yoroi-extension/app/components/wallet/send/WalletSendFormRevamp.js @@ -53,7 +53,6 @@ import { ampli } from '../../../../ampli/index'; import type { DomainResolverFunc, DomainResolverResponse } from '../../../stores/ada/AdaAddressesStore'; import { isResolvableDomain } from '@yoroi/resolver'; import SupportedAddressDomainsBanner from '../../../containers/wallet/SupportedAddressDomainsBanner'; -import type { SendMoneyRequest } from '../../../stores/toplevel/TransactionProcessingStore'; import type { MaxSendableAmountRequest } from '../../../stores/toplevel/TransactionBuilderStore'; import type { WalletState } from '../../../../chrome/extension/background/types'; import LoadingSpinner from '../../widgets/LoadingSpinner'; @@ -209,17 +208,6 @@ type Props = {| +signRequest: null | ISignRequest, +staleTx: boolean, +openTransactionSuccessDialog: void => void, - +sendMoneyRequest: SendMoneyRequest, - +sendMoney: (params: {| - password: string, - +wallet: { - publicDeriverId: number, - +plate: { TextPart: string, ... }, - ... - }, - signRequest: ISignRequest, - onSuccess?: void => void, - |}) => Promise, |}; const SMemoTextField = styled(MemoTextField)(({ theme }) => ({ diff --git a/packages/yoroi-extension/app/containers/wallet/WalletSendPage.js b/packages/yoroi-extension/app/containers/wallet/WalletSendPage.js index e64b9629c7..0fac167e96 100644 --- a/packages/yoroi-extension/app/containers/wallet/WalletSendPage.js +++ b/packages/yoroi-extension/app/containers/wallet/WalletSendPage.js @@ -214,12 +214,6 @@ export default class WalletSendPage extends Component { maxSendableAmount={transactionBuilderStore.maxSendableAmount} signRequest={transactionBuilderStore.tentativeTx} staleTx={transactionBuilderStore.txMismatch} - sendMoneyRequest={stores.transactionProcessingStore.sendMoneyRequest} - sendMoney={stores.substores.ada.mnemonicSend.sendMoney} - ledgerSendError={stores.substores.ada.ledgerSend.error || null} - trezorSendError={stores.substores.ada.trezorSend.error || null} - ledgerSend={stores.substores.ada.ledgerSend} - trezorSend={stores.substores.ada.trezorSend} /> {this.renderDialog()} diff --git a/packages/yoroi-extension/app/stores/ada/AdaDelegationTransactionStore.js b/packages/yoroi-extension/app/stores/ada/AdaDelegationTransactionStore.js index 8d2f1c5704..126e79f8cc 100644 --- a/packages/yoroi-extension/app/stores/ada/AdaDelegationTransactionStore.js +++ b/packages/yoroi-extension/app/stores/ada/AdaDelegationTransactionStore.js @@ -138,7 +138,7 @@ export default class AdaDelegationTransactionStore extends Store { throw new Error(`${nameof(this.signTransaction)} no tx to broadcast`); } try { - await this.stores.substores.ada.wallets.adaSendAndRefresh({ + await this.stores.transactionProcessingStore.adaSendAndRefresh({ wallet: request.wallet, signRequest, password: request.password, @@ -168,7 +168,7 @@ export default class AdaDelegationTransactionStore extends Store { @action.bound reset(request: {| justTransaction: boolean |}): void { - this.stores.wallets.sendMoneyRequest.reset(); + this.stores.transactionProcessingStore.sendMoneyRequest.reset(); this.createDelegationTx.reset(); if (!request.justTransaction) { this.isStale = false; diff --git a/packages/yoroi-extension/app/stores/ada/VotingStore.js b/packages/yoroi-extension/app/stores/ada/VotingStore.js index bfd05a668b..45978dafe3 100644 --- a/packages/yoroi-extension/app/stores/ada/VotingStore.js +++ b/packages/yoroi-extension/app/stores/ada/VotingStore.js @@ -308,7 +308,7 @@ export default class VotingStore extends Store { if (signRequest == null) { throw new Error(`${nameof(this.signTransaction)} no tx to broadcast`); } - await this.stores.substores.ada.wallets.adaSendAndRefresh({ + await this.stores.transactionProcessingStore.adaSendAndRefresh({ wallet: request.wallet, signRequest, password: request.password, @@ -346,7 +346,7 @@ export default class VotingStore extends Store { stepState: StepState.LOAD, }; this.error = null; - this.stores.wallets.sendMoneyRequest.reset(); + this.stores.transactionProcessingStore.sendMoneyRequest.reset(); this.createVotingRegTx.reset(); if (!request.justTransaction) { this.isStale = false; From 479d49c3e92fbd7272a28550069001a2d1cfc317 Mon Sep 17 00:00:00 2001 From: yushi Date: Tue, 25 Mar 2025 00:39:13 +0800 Subject: [PATCH 29/32] fix --- .../app/UI/components/icons/index.ts | 1 - .../wallet/send/SendTokensButton.js | 49 +++++-------------- 2 files changed, 11 insertions(+), 39 deletions(-) diff --git a/packages/yoroi-extension/app/UI/components/icons/index.ts b/packages/yoroi-extension/app/UI/components/icons/index.ts index 9c3243475f..acfbf83dd7 100644 --- a/packages/yoroi-extension/app/UI/components/icons/index.ts +++ b/packages/yoroi-extension/app/UI/components/icons/index.ts @@ -14,7 +14,6 @@ import { Info } from './Info'; import { LeftArrow } from './LeftArrow'; import { Receive } from './Receive'; import { RightArrow } from './RightArrow'; -import { Receive } from './Receive'; import { Search } from './Search'; import { Send } from './Send'; import { Staking } from './Staking'; diff --git a/packages/yoroi-extension/app/components/wallet/send/SendTokensButton.js b/packages/yoroi-extension/app/components/wallet/send/SendTokensButton.js index af12b37a96..b52060cbed 100644 --- a/packages/yoroi-extension/app/components/wallet/send/SendTokensButton.js +++ b/packages/yoroi-extension/app/components/wallet/send/SendTokensButton.js @@ -3,7 +3,7 @@ import { TransactionResult } from '../../../UI/features/transaction-review/commo import { useTxReviewModal } from '../../../UI/features/transaction-review/module/ReviewTxProvider'; export const SendTokensButton = ({ disabled, onSuccess, label, stores }) => { - const { openTxReviewModal, startLoadingTxReview, showTxResultModal, isHardwareWallet } = useTxReviewModal(); + const { openTxReviewModal, startLoadingTxReview, showTxResultModal } = useTxReviewModal(); const handleSubmit = async () => { const signTxRequest = stores.transactionBuilderStore.updateTentativeTx(); @@ -18,45 +18,18 @@ export const SendTokensButton = ({ disabled, onSuccess, label, stores }) => { }); }; - const submitTx = async (passswordInput, signTxRequest) => { - const selectedWallet = stores.wallets.selected; - + const submitTx = async (password, signRequest) => { try { startLoadingTxReview(); - if (isHardwareWallet) { - if (walletType === 'ledger') { - const ledgerSendStore = stores.substores.ada.ledgerSend; - ledgerSendStore.sendUsingLedgerWallet({ - params: { signRequest: signTxRequest }, - onSuccess: () => { - onSuccess(); - showTxResultModal(TransactionResult.SUCCESS); - }, - wallet: selectedWallet, - }); - } - if (walletType === 'trezor') { - const trezorSendStore = stores.substores.ada.trezorSend; - trezorSendStore.sendUsingTrezor({ - params: { signRequest: signTxRequest }, - onSuccess: () => { - onSuccess(); - showTxResultModal(TransactionResult.SUCCESS); - }, - wallet: selectedWallet, - }); - } - } else { - await stores.substores.ada.mnemonicSend.sendMoney({ - signRequest: signTxRequest, - password: passswordInput, - wallet: selectedWallet, - onSuccess: () => { - onSuccess(); - showTxResultModal(TransactionResult.SUCCESS); - }, - }); - } + stores.transactionProcessingStore.adaSendAndRefresh({ + wallet: stores.wallets.selected, + signRequest, + password, + callback: async () => { + onSuccess(); + showTxResultModal(TransactionResult.SUCCESS); + }, + }); } catch (error) { console.warn('Delegation error', error); showTxResultModal(TransactionResult.FAIL); From fbcc2f6ec7b962403b129c54597b4983760a0f0d Mon Sep 17 00:00:00 2001 From: yushi Date: Tue, 25 Mar 2025 01:16:29 +0800 Subject: [PATCH 30/32] lint --- .../app/containers/wallet/WalletSendPage.js | 24 +------------------ .../toplevel/TransactionProcessingStore.js | 8 +------ 2 files changed, 2 insertions(+), 30 deletions(-) diff --git a/packages/yoroi-extension/app/containers/wallet/WalletSendPage.js b/packages/yoroi-extension/app/containers/wallet/WalletSendPage.js index 8e3f01599a..5af81433ad 100644 --- a/packages/yoroi-extension/app/containers/wallet/WalletSendPage.js +++ b/packages/yoroi-extension/app/containers/wallet/WalletSendPage.js @@ -4,17 +4,14 @@ import { Component } from 'react'; import { observer } from 'mobx-react'; import { action, observable, runInAction } from 'mobx'; import type { $npm$ReactIntl$IntlFormat } from 'react-intl'; -import { defineMessages, intlShape } from 'react-intl'; +import { intlShape } from 'react-intl'; import { ROUTES } from '../../routes-config'; import WalletSendFormRevamp from '../../components/wallet/send/WalletSendFormRevamp'; import MemoNoExternalStorageDialog from '../../components/wallet/memos/MemoNoExternalStorageDialog'; -import { HaskellShelleyTxSignRequest } from '../../api/ada/transactions/shelley/HaskellShelleyTxSignRequest'; -import { addressToDisplayString } from '../../api/ada/lib/storage/bridge/utils'; import type { TokenRow } from '../../api/ada/lib/storage/database/primitives/tables'; import { genLookupOrFail } from '../../stores/stateless/tokenHelpers'; import BigNumber from 'bignumber.js'; import TransactionSuccessDialog from '../../components/wallet/send/TransactionSuccessDialog'; -import globalMessages from '../../i18n/global-messages'; import AddNFTDialog from '../../components/wallet/send/WalletSendFormSteps/AddNFTDialog'; import AddTokenDialog from '../../components/wallet/send/WalletSendFormSteps/AddTokenDialog'; import { ampli } from '../../../ampli/index'; @@ -34,25 +31,6 @@ import { ModalManager } from '../../UI/components/modals/ModalManager'; // $FlowIgnore: suppressing this error import { CurrencyProvider } from '../../UI/context/CurrencyContext'; -const messages = defineMessages({ - txConfirmationLedgerNanoLine1: { - id: 'wallet.send.ledger.confirmationDialog.info.line.1', - defaultMessage: '!!!After connecting your Ledger device to your computer’s USB port, press the Send using Ledger button.', - }, - sendUsingLedgerNano: { - id: 'wallet.send.ledger.confirmationDialog.submit', - defaultMessage: '!!!Send using Ledger', - }, - txConfirmationTrezorTLine1: { - id: 'wallet.send.trezor.confirmationDialog.info.line.1', - defaultMessage: '!!!After connecting your Trezor device to your computer, press the Send using Trezor button.', - }, - sendUsingTrezorT: { - id: 'wallet.send.trezor.confirmationDialog.submit', - defaultMessage: '!!!Send using Trezor', - }, -}); - @observer export default class WalletSendPage extends Component { static contextTypes: {| intl: $npm$ReactIntl$IntlFormat |} = { diff --git a/packages/yoroi-extension/app/stores/toplevel/TransactionProcessingStore.js b/packages/yoroi-extension/app/stores/toplevel/TransactionProcessingStore.js index 990bc0b13d..3bf25bad3e 100644 --- a/packages/yoroi-extension/app/stores/toplevel/TransactionProcessingStore.js +++ b/packages/yoroi-extension/app/stores/toplevel/TransactionProcessingStore.js @@ -14,16 +14,12 @@ import { import { observable } from 'mobx'; import Request from '../lib/LocalizedRequest'; import { Logger, stringifyError, stringifyData, fullErrStr } from '../../utils/logging'; -import { convertToLocalizableError as trezorConvertToLocalizableError } from '../../domain/TrezorLocalizedError'; -import { convertToLocalizableError as ledgerConvertToLocalizableError } from '../../domain/LedgerLocalizedError'; -import LocalizableError from '../../i18n/LocalizableError'; import { buildConnectorSignedTransaction as ledgerBuildConnectorSignedTransaction } from '../../api/ada/transactions/shelley/ledgerTx'; import { buildConnectorSignedTransaction as trezorBuildConnectorSignedTransaction } from '../../api/ada/transactions/shelley/trezorTx'; -import { convertToLocalizableError } from '../../domain/TrezorLocalizedError'; import type { Addressing, Address, Value } from '../../api/ada/lib/storage/models/PublicDeriver/interfaces'; import { generateCip15RegistrationMetadata, @@ -267,10 +263,8 @@ export default class TransactionProcessingStore extends Store { try { Logger.debug(`${nameof(TransactionProcessingStore)}::${nameof(this.trezorSignRawTx)} called: ` + stringifyData(request)); - const { rawTxHex, wallet, catalystData } = request; - const addressingMap = genAddressingLookup( - wallet.networkId, + request.wallet.networkId, this.stores.addresses.addressSubgroupMap, ); From 7d4284567ee47bb541a565991c498bc9ddf977fa Mon Sep 17 00:00:00 2001 From: yushi Date: Tue, 25 Mar 2025 01:22:16 +0800 Subject: [PATCH 31/32] fix --- .../app/stores/toplevel/TransactionProcessingStore.js | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/yoroi-extension/app/stores/toplevel/TransactionProcessingStore.js b/packages/yoroi-extension/app/stores/toplevel/TransactionProcessingStore.js index 3bf25bad3e..b94ba1a073 100644 --- a/packages/yoroi-extension/app/stores/toplevel/TransactionProcessingStore.js +++ b/packages/yoroi-extension/app/stores/toplevel/TransactionProcessingStore.js @@ -20,6 +20,7 @@ import { import { buildConnectorSignedTransaction as trezorBuildConnectorSignedTransaction } from '../../api/ada/transactions/shelley/trezorTx'; +import { convertToLocalizableError } from '../../domain/TrezorLocalizedError'; import type { Addressing, Address, Value } from '../../api/ada/lib/storage/models/PublicDeriver/interfaces'; import { generateCip15RegistrationMetadata, From 0e54fe9fbccfedc6465374420205a8c6a5293205 Mon Sep 17 00:00:00 2001 From: yushi Date: Tue, 25 Mar 2025 04:10:15 +0800 Subject: [PATCH 32/32] fix test --- .../app/api/ada/transactions/shelley/ledgerTx.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/yoroi-extension/app/api/ada/transactions/shelley/ledgerTx.js b/packages/yoroi-extension/app/api/ada/transactions/shelley/ledgerTx.js index deb83f1824..0169249b0f 100644 --- a/packages/yoroi-extension/app/api/ada/transactions/shelley/ledgerTx.js +++ b/packages/yoroi-extension/app/api/ada/transactions/shelley/ledgerTx.js @@ -885,7 +885,7 @@ export function buildConnectorSignedTransaction( if (witness.path[0] === WalletTypePurpose.BIP44) { const receiver = pathToReceiverMapping.get(witness.path.join('/')); if (!receiver) { - throw new Error('unexpectedly missing Byron path'); + continue; } const bootstrapWit = RustModule.WalletV4.BootstrapWitness.new( RustModule.WalletV4.Vkey.new(witnessKey.to_raw_key()),