From 61cec00c1e4513388037dd01543515fa9d690eb1 Mon Sep 17 00:00:00 2001 From: v-almonacid Date: Fri, 17 Jan 2020 12:00:19 -0300 Subject: [PATCH] fix tx submission and error handling --- src/actions.js | 11 +++++-- src/api/api.js | 2 +- .../RestoreWallet/WalletCredentialsScreen.js | 32 ++++++++++--------- src/crypto/chain.js | 3 +- .../shelley/transactions/utxoTransactions.js | 1 - .../shelley/transactions/yoroiTransfer.js | 11 +++++-- src/crypto/shelley/util.test.js | 11 +++---- src/crypto/wallet.js | 18 +++++++---- src/i18n/global-messages.js | 10 ++++++ src/i18n/locales/en-US.json | 2 ++ 10 files changed, 64 insertions(+), 37 deletions(-) diff --git a/src/actions.js b/src/actions.js index f338f038f8..99f60fb18a 100644 --- a/src/actions.js +++ b/src/actions.js @@ -454,9 +454,13 @@ export const setSystemAuth = (enable: boolean) => async ( } } -export const handleGeneralError = async (message: string, e: Error) => { +export const handleGeneralError = async ( + message: string, + e: Error, + intl: ?intlShape, +) => { Logger.error(`${message}: ${e.message}`, e) - await showErrorDialog(errorMessages.generalError, null, {message}) + await showErrorDialog(errorMessages.generalError, intl, {message}) crashReporting.crash() } @@ -471,5 +475,6 @@ export const submitTransaction = ( } export const submitShelleyTransferTx = async (encodedTx: Uint8Array) => { - await walletManager.submitTransaction(encodedTx) + const signedTx64 = Buffer.from(encodedTx).toString('base64') + await walletManager.submitTransaction(signedTx64) } diff --git a/src/api/api.js b/src/api/api.js index 944b8e2420..823d71a003 100644 --- a/src/api/api.js +++ b/src/api/api.js @@ -127,7 +127,7 @@ export const bulkFetchUTXOsForAddresses = async ( return _.flatten(responses) } -export const submitTransaction = (signedTx: string | Uint8Array) => { +export const submitTransaction = (signedTx: string) => { return _fetch('txs/signed', {signedTx}) } diff --git a/src/components/WalletInit/RestoreWallet/WalletCredentialsScreen.js b/src/components/WalletInit/RestoreWallet/WalletCredentialsScreen.js index c2d59a610b..5df33c3a9b 100644 --- a/src/components/WalletInit/RestoreWallet/WalletCredentialsScreen.js +++ b/src/components/WalletInit/RestoreWallet/WalletCredentialsScreen.js @@ -31,6 +31,7 @@ import { import {generateTransferTxFromMnemonic} from '../../../crypto/shelley/transactions/yoroiTransfer' import {CARDANO_CONFIG} from '../../../config' import {NetworkError, ApiError} from '../../../api/errors' +import {InsufficientFunds} from '../../../crypto/errors' import {errorMessages} from '../../../i18n/global-messages' import type {Navigation} from '../../../types/navigation' @@ -84,7 +85,7 @@ const handleApiError = async ( } else if (error instanceof ApiError) { await showErrorDialog(errorMessages.apiError, intl) } else { - await handleGeneralError(fallbackMsg, error) + await handleGeneralError(fallbackMsg, error, intl) } } @@ -123,17 +124,14 @@ class WalletCredentialsScreen extends React.Component { transferTx: null, } - navigateToWallet = ignoreConcurrentAsync( - async (): Promise => { - const {name, password} = this.state - const {navigation, createWallet} = this.props - const phrase = navigation.getParam('phrase') - const isShelleyWallet = !!navigation.getParam('isShelleyWallet') - await createWallet(name, phrase, password, isShelleyWallet) - navigation.navigate(ROOT_ROUTES.WALLET) - }, - 1000, - ) + navigateToWallet = ignoreConcurrentAsync(async (): Promise => { + const {name, password} = this.state + const {navigation, createWallet} = this.props + const phrase = navigation.getParam('phrase') + const isShelleyWallet = !!navigation.getParam('isShelleyWallet') + await createWallet(name, phrase, password, isShelleyWallet) + navigation.navigate(ROOT_ROUTES.WALLET) + }, 1000) onSubmitWalletCredentials = ({name, password}) => { // TODO: make sure this call is executed only once @@ -248,7 +246,11 @@ class WalletCredentialsScreen extends React.Component { }) } catch (e) { this.setState({currentDialogStep: RESTORATION_DIALOG_STEPS.CLOSED}) - handleApiError(e, intl, messages.walletCheckError) + if (e instanceof InsufficientFunds) { + await showErrorDialog(errorMessages.insufficientBalance, intl) + } else { + handleApiError(e, intl, 'Could not check wallet') + } } finally { this.setState({isProcessing: false}) } @@ -268,9 +270,9 @@ class WalletCredentialsScreen extends React.Component { } await submitShelleyTransferTx(tx.encodedTx) this.setState({isProcessing: false}) - this.navigateToWallet() + navigation.navigate(ROOT_ROUTES.WALLET) } catch (e) { - handleApiError(e, intl, 'could not upgrade wallet') + handleApiError(e, intl, 'Could not upgrade wallet') } finally { this.setState({isProcessing: false}) } diff --git a/src/crypto/chain.js b/src/crypto/chain.js index dafc04b0c3..5db3b44958 100644 --- a/src/crypto/chain.js +++ b/src/crypto/chain.js @@ -45,6 +45,7 @@ export class AddressGenerator { } } +// TODO: make ShelleyAddressGenerator a subclass of AddressGenerator export class ShelleyAddressGenerator { addressChain: Bip32PublicKey // stakingKey: PublicKey @@ -114,7 +115,7 @@ export class AddressChain { ) constructor( - addressGenerator: AddressGenerator, + addressGenerator: AddressGenerator | ShelleyAddressGenerator, blockSize: number = CONFIG.WALLET.DISCOVERY_BLOCK_SIZE, gapLimit: number = CONFIG.WALLET.DISCOVERY_GAP_SIZE, ) { diff --git a/src/crypto/shelley/transactions/utxoTransactions.js b/src/crypto/shelley/transactions/utxoTransactions.js index edb752fa90..b64210ff52 100644 --- a/src/crypto/shelley/transactions/utxoTransactions.js +++ b/src/crypto/shelley/transactions/utxoTransactions.js @@ -352,7 +352,6 @@ export const sendAllUnsignedTxFromUtxo = async ( )).to_str() fee = new BigNumber(feeValue) } - // create a new transaction subtracing the fee from your total UTXO if (totalBalance.isLessThan(fee)) { throw new InsufficientFunds() diff --git a/src/crypto/shelley/transactions/yoroiTransfer.js b/src/crypto/shelley/transactions/yoroiTransfer.js index cbdb9d68fe..92812e41ba 100644 --- a/src/crypto/shelley/transactions/yoroiTransfer.js +++ b/src/crypto/shelley/transactions/yoroiTransfer.js @@ -2,6 +2,8 @@ import {BigNumber} from 'bignumber.js' import {isEmpty} from 'lodash' import {Address, Bip32PrivateKey} from 'react-native-chain-libs' + +import {InsufficientFunds} from '../../errors' import type {AddressedUtxo, Addressing} from '../../../types/HistoryTransaction' import {signTransaction, sendAllUnsignedTx} from './utxoTransactions' import {getShelleyTxFee} from './utils' @@ -66,8 +68,13 @@ export const buildYoroiTransferTx = async (payload: {| )).to_string(CONFIG.BECH32_PREFIX.ADDRESS), } } catch (error) { - Logger.error(`transfer::buildYoroiTransferTx: ${error.message}`) - throw new Error(`buildYoroiTransferTx: ${error.message}`) + if (error instanceof InsufficientFunds) { + // handle error at UI-level + throw error + } else { + Logger.error(`transfer::buildYoroiTransferTx: ${error.message}`) + throw new Error(`buildYoroiTransferTx: ${error.message}`) + } } } diff --git a/src/crypto/shelley/util.test.js b/src/crypto/shelley/util.test.js index b7bc2891e2..08f2cceea5 100644 --- a/src/crypto/shelley/util.test.js +++ b/src/crypto/shelley/util.test.js @@ -7,9 +7,7 @@ import { // getFirstInternalAddr, getGroupAddressesFromMnemonics, } from './util' -import { - getMasterKeyFromMnemonic, -} from '../byron/util' +import {getMasterKeyFromMnemonic} from '../byron/util' import {NUMBERS} from '../../config' jestSetup.setup() @@ -23,11 +21,10 @@ const mnemonic = [ test('Can create master key', async () => { const masterKeyV2 = await getMasterKeyFromMnemonic(mnemonic) - const masterKeyV3 = await generateWalletRootKey( - mnemonic, - ) + const masterKeyV3 = await generateWalletRootKey(mnemonic) expect(masterKeyV2).toEqual( - Buffer.from(await masterKeyV3.as_bytes()).toString('hex')) + Buffer.from(await masterKeyV3.as_bytes()).toString('hex'), + ) }) describe('group addresses', () => { diff --git a/src/crypto/wallet.js b/src/crypto/wallet.js index 36329c90c3..58e2e8a93b 100644 --- a/src/crypto/wallet.js +++ b/src/crypto/wallet.js @@ -203,8 +203,12 @@ export class Wallet { )).derive(0 + NUMBERS.HARD_DERIVATION_START) const accountPublic = await accountKey.to_public() - const privateChainKey = await accountPublic.derive(NUMBERS.CHAIN_DERIVATIONS.INTERNAL) - const publicChainKey = await accountPublic.derive(NUMBERS.CHAIN_DERIVATIONS.EXTERNAL) + const privateChainKey = await accountPublic.derive( + NUMBERS.CHAIN_DERIVATIONS.INTERNAL, + ) + const publicChainKey = await accountPublic.derive( + NUMBERS.CHAIN_DERIVATIONS.EXTERNAL, + ) this._transactionCache = new TransactionCache() @@ -441,7 +445,7 @@ export class Wallet { return Buffer.from(signedTxData.cbor_encoded_tx, 'hex').toString('base64') } - async submitTransaction(signedTx: string | Uint8Array) { + async submitTransaction(signedTx: string) { const response = await api.submitTransaction(signedTx) Logger.info(response) return response @@ -671,7 +675,7 @@ class WalletManager { ) } - async submitTransaction(signedTx: string | Uint8Array) { + async submitTransaction(signedTx: string) { if (!this._wallet) throw new WalletClosed() return await this.abortWhenWalletCloses( this._wallet.submitTransaction(signedTx), @@ -686,9 +690,9 @@ class WalletManager { ): Promise { // Ignore id & name for now const wallet = new Wallet() - const id = isShelleyWallet ? - await wallet._createShelleyWallet(mnemonic, password) : - await wallet._create(mnemonic, password) + const id = isShelleyWallet + ? await wallet._createShelleyWallet(mnemonic, password) + : await wallet._create(mnemonic, password) this._id = id this._wallets = { diff --git a/src/i18n/global-messages.js b/src/i18n/global-messages.js index ecce4307a9..97564433a3 100644 --- a/src/i18n/global-messages.js +++ b/src/i18n/global-messages.js @@ -127,6 +127,16 @@ export const errorMessages = { 'Please try again later or check our Twitter account (https://twitter.com/YoroiWallet)', }, }), + insufficientBalance: defineMessages({ + title: { + id: 'global.actions.dialogs.insufficientBalance.title', + defaultMessage: '!!!Transaction error', + }, + message: { + id: 'global.actions.dialogs.insufficientBalance.message', + defaultMessage: '!!Not enough money to make this transaction', + }, + }), disableEasyConfirmationFirst: defineMessages({ title: { id: 'global.actions.dialogs.disableEasyConfirmationFirst.title', diff --git a/src/i18n/locales/en-US.json b/src/i18n/locales/en-US.json index f8c5917ccc..5cf0537716 100644 --- a/src/i18n/locales/en-US.json +++ b/src/i18n/locales/en-US.json @@ -252,6 +252,8 @@ "global.actions.dialogs.logout.yesButton": "Yes", "global.actions.dialogs.apiError.title": "API error", "global.actions.dialogs.apiError.message": "Error received from api method call while sending transaction. Please try again later or check our Twitter account (https://twitter.com/YoroiWallet)", + "global.actions.dialogs.insufficientBalance.title": "Transaction error", + "global.actions.dialogs.insufficientBalance.message": "Not enough money to make this transaction", "global.actions.dialogs.networkError.message": "Error connecting to the server. Please check your internet connection", "global.actions.dialogs.networkError.title": "Network error", "global.actions.dialogs.pinMismatch.message": "PINs do not match.",