From bba1eeaa927bf19a0a885093454329861e8ea0fe Mon Sep 17 00:00:00 2001 From: Fabian Date: Fri, 20 Mar 2020 15:50:36 -0500 Subject: [PATCH] Fabo/fix tx parsing (#179) * changelog * preload network capabilities * tests fixed * add slug to store and mutations * lunie version changed * Aleksei/fixing path on extension (#173) * changelog * fixing redirect path after account creation in extension * refactor isExtension getter using lunie config Co-authored-by: Bitcoinera * correct submodule version * added missing webpack resolution * force checkout * change to working commit * make updates async * remove package lock again * use working commit * first steps to parsing to v2 txs * clone parsing from API * linted * move parsetx to store * delete consolelog * fix tests * fix actions tests * lint * just in case add ledger_app like in fe * working with #3736 in fe * stay clean * fix validators names in claim rewards * fix tests * lint * working with #3736 now for real * add claimable rewards * lint. kill now unused functions * changelog * refactor property names * linted Co-authored-by: Aleksei Rudometov Co-authored-by: Bitcoinera --- pending/fabo_fix-tx-parsing | 1 + src/components/SessionApprove.vue | 52 +-- src/messageHandlers.js | 8 +- src/requests.js | 9 +- src/scripts/parsers.js | 322 +++++++++++++++++- src/store/actions.js | 91 ++--- .../__snapshots__/SessionApprove.spec.js.snap | 2 +- test/unit/scripts/parsers.spec.js | 131 +++---- test/unit/store/actions.spec.js | 75 ++-- 9 files changed, 463 insertions(+), 228 deletions(-) create mode 100644 pending/fabo_fix-tx-parsing diff --git a/pending/fabo_fix-tx-parsing b/pending/fabo_fix-tx-parsing new file mode 100644 index 0000000000..ceb044defc --- /dev/null +++ b/pending/fabo_fix-tx-parsing @@ -0,0 +1 @@ +[Fixed] Parse transactions in new transaction format @faboweb \ No newline at end of file diff --git a/src/components/SessionApprove.vue b/src/components/SessionApprove.vue index 71119d52eb..8baeb56f51 100644 --- a/src/components/SessionApprove.vue +++ b/src/components/SessionApprove.vue @@ -8,9 +8,9 @@ { - const withdrawMessages = messages.filter(({ type }) => { - const messageSuffix = type.split('/')[1] - return messageSuffix === 'MsgWithdrawDelegationReward' - }) - - // strange syntax where we actually return the whole message instead of just the validator - return withdrawMessages -} - -const getLunieTransaction = tx => { - const lunieTransactions = flattenTransactionMsgs([], tx) - const pickFirst = lunieTransactions[0] // obviously error prone as we ignore the rest - pickFirst.withdrawValidators = JSON.stringify( - getWithdrawValidators(lunieTransactions) - ) - - return pickFirst -} +const parseSignMessageTx = actions({}).parseSignMessageTx export default { name: `session-approve`, @@ -125,25 +102,28 @@ export default { computed: { ...mapGetters(['signRequest']), tx() { - return this.signRequest ? parseTx(this.signRequest.signMessage) : null + return parseSignMessageTx( + this.signRequest.signMessage, + this.displayedProperties + ) }, network() { return this.signRequest ? this.signRequest.network : null }, - transaction() { - return getLunieTransaction(this.tx) - }, fees() { - return this.tx ? atoms(parseFee(this.tx.tx)) : null + return this.tx ? this.tx.fees[0].amount : null }, senderAddress() { return this.signRequest ? this.signRequest.senderAddress : null }, + displayedProperties() { + return this.signRequest ? this.signRequest.displayedProperties : null + }, amountCoin() { - return this.tx ? parseValueObj(this.tx.tx) : null + return this.tx ? this.tx.details.amount : null }, amount() { - return this.amountCoin ? atoms(Number(this.amountCoin.amount)) : 0 + return this.amountCoin ? this.amountCoin.amount : 0 }, bondDenom() { return this.amountCoin ? this.amountCoin.denom : '' @@ -151,7 +131,7 @@ export default { validatorsAddressMap() { const names = {} this.validators.forEach(item => { - names[item.operator_address] = item + names[item.operatorAddress] = item }) return names } @@ -162,7 +142,7 @@ export default { } }, async mounted() { - const validatorsObject = await getValidatorsData(this.tx.tx, this.network) + const validatorsObject = await getValidatorsData(this.tx, this.network) this.validators = validatorsObject }, methods: { diff --git a/src/messageHandlers.js b/src/messageHandlers.js index 92a04d226a..0db8889597 100644 --- a/src/messageHandlers.js +++ b/src/messageHandlers.js @@ -30,7 +30,12 @@ export function signMessageHandler( break } case 'LUNIE_SIGN_REQUEST': { - const { signMessage, senderAddress, network } = message.payload + const { + signMessage, + senderAddress, + network, + displayedProperties + } = message.payload const wallet = getWalletFromIndex(getWalletIndex(), senderAddress) if (!wallet) { throw new Error('No wallet found matching the sender address.') @@ -39,6 +44,7 @@ export function signMessageHandler( signMessage, senderAddress, network, + displayedProperties, tabID: sender.tab.id }) break diff --git a/src/requests.js b/src/requests.js index e36c621a98..55c06bde0d 100644 --- a/src/requests.js +++ b/src/requests.js @@ -4,11 +4,18 @@ export default class SignRequestQueue { this.unqueueSignRequest('') // to reset the icon in the beginning } - queueSignRequest({ signMessage, senderAddress, tabID, network }) { + queueSignRequest({ + signMessage, + senderAddress, + network, + displayedProperties, + tabID + }) { this.queue.push({ signMessage, senderAddress, network, + displayedProperties, id: Date.now(), tabID }) diff --git a/src/scripts/parsers.js b/src/scripts/parsers.js index 495c1c2f97..fa0175a261 100644 --- a/src/scripts/parsers.js +++ b/src/scripts/parsers.js @@ -1,9 +1,11 @@ 'use strict' -export const parseTx = signMessage => { +import BigNumber from 'bignumber.js' + +export const parseTx = (signMessage, displayedProperties) => { const { msgs, fee, memo } = JSON.parse(signMessage) - return { + const tx = { tx: { type: 'auth/StdTx', value: { @@ -13,22 +15,314 @@ export const parseTx = signMessage => { } } } + + return transactionReducerV2( + tx, + displayedProperties, + { coinReducer, rewardCoinReducer }, + 'STAKE' + )[0] // TODO get staking denom (apollo/networks) } -export const parseFee = stdTx => { - const { - value: { fee } - } = stdTx - return Number(fee.amount.length > 0 ? fee.amount[0].amount : 0) +// delegations rewards in Tendermint are located in events as strings with this form: +// amount: {"15000umuon"}, or in multidenom networks they look like this: +// amount: {"15000ungm,100000uchf,110000ueur,2000000ujpy"} +// That is why we need this separate function to extract those amounts in this format +function rewardCoinReducer(reward, stakingDenom) { + const numBit = reward.match(/[0-9]+/gi) + const stringBit = reward.match(/[a-z]+/gi) + const multiDenomRewardsArray = reward.split(`,`) + if (multiDenomRewardsArray.length > 1) { + const mappedMultiDenomRewardsArray = multiDenomRewardsArray.map(reward => + rewardCoinReducer(reward) + ) + let stakingDenomRewards = mappedMultiDenomRewardsArray.find( + ({ denom }) => denom === denomLookup(stakingDenom) + ) + // if there is no staking denom reward we will display the first alt-token reward + return ( + stakingDenomRewards || + mappedMultiDenomRewardsArray.find(({ amount }) => amount > 0) + ) + } + return { + denom: denomLookup(stringBit), + amount: BigNumber(numBit).div(1000000) + } +} + +export const lunieMessageTypes = { + SEND: `SendTx`, + STAKE: `StakeTx`, + RESTAKE: `RestakeTx`, + UNSTAKE: `UnstakeTx`, + VOTE: `VoteTx`, + DEPOSIT: `DepositTx`, + CLAIM_REWARDS: `ClaimRewardsTx`, + SUBMIT_PROPOSAL: `SubmitProposalTx`, + UNKNOWN: `UnknownTx` +} + +// map Cosmos SDK message types to Lunie message types +function getMessageType(type) { + // different networks use different prefixes for the transaction types like cosmos/MsgSend vs core/MsgSend in Terra + const transactionTypeSuffix = type.split('/')[1] + switch (transactionTypeSuffix) { + case 'MsgSend': + return lunieMessageTypes.SEND + case 'MsgDelegate': + return lunieMessageTypes.STAKE + case 'MsgBeginRedelegate': + return lunieMessageTypes.RESTAKE + case 'MsgUndelegate': + return lunieMessageTypes.UNSTAKE + case 'MsgWithdrawDelegationReward': + return lunieMessageTypes.CLAIM_REWARDS + case 'MsgSubmitProposal': + return lunieMessageTypes.SUBMIT_PROPOSAL + case 'MsgVote': + return lunieMessageTypes.VOTE + case 'MsgDeposit': + return lunieMessageTypes.DEPOSIT + default: + return lunieMessageTypes.UNKNOWN + } +} + +function denomLookup(denom) { + const lookup = { + uatom: 'ATOM', + umuon: 'MUON', + uluna: 'LUNA', + ukrw: 'KRT', + umnt: 'MNT', + usdr: 'SDT', + uusd: 'UST', + seed: 'TREE', + ungm: 'NGM', + eeur: 'eEUR', + echf: 'eCHF', + ejpy: 'eJPY', + eusd: 'eUSD' + } + return lookup[denom] ? lookup[denom] : denom.toUpperCase() +} + +function coinReducer(coin) { + if (!coin) { + return { + amount: 0, + denom: '' + } + } + + // we want to show only atoms as this is what users know + const denom = denomLookup(coin.denom) + return { + denom: denom, + amount: BigNumber(coin.amount) + .div(1000000) + .toNumber() // Danger: this might not be the case for all future tokens + } } -export const parseValueObj = stdTx => { - const { - value: { msg } - } = stdTx - if (msg[0].type === 'cosmos-sdk/MsgSend') { - return msg[0].value.amount[0] +function transactionReducerV2( + transaction, + displayedProperties, + reducers, + stakingDenom +) { + // TODO check if this is anywhere not an array + let fees + if (Array.isArray(transaction.tx.value.fee.amount)) { + fees = transaction.tx.value.fee.amount.map(coinReducer) } else { - return msg[0].value.amount + fees = [coinReducer(transaction.tx.value.fee.amount)] + } + // We do display only the transactions we support in Lunie + const filteredMessages = transaction.tx.value.msg.filter( + ({ type }) => getMessageType(type) !== 'Unknown' + ) + const { claimMessages, otherMessages } = filteredMessages.reduce( + ({ claimMessages, otherMessages }, message) => { + // we need to aggregate all withdraws as we display them together in one transaction + if (getMessageType(message.type) === lunieMessageTypes.CLAIM_REWARDS) { + claimMessages.push(message) + } else { + otherMessages.push(message) + } + return { claimMessages, otherMessages } + }, + { claimMessages: [], otherMessages: [] } + ) + + // we need to aggregate claim rewards messages in one single one to avoid transaction repetition + const claimMessage = + claimMessages.length > 0 + ? claimRewardsMessagesAggregator(claimMessages) + : undefined + const allMessages = claimMessage + ? otherMessages.concat(claimMessage) // add aggregated claim message + : otherMessages + const returnedMessages = allMessages.map(({ value, type }, index) => ({ + type: getMessageType(type), + hash: transaction.txhash, + height: transaction.height, + details: transactionDetailsReducer( + getMessageType(type), + value, + displayedProperties, + reducers, + transaction, + stakingDenom + ), + timestamp: transaction.timestamp, + memo: transaction.tx.value.memo, + fees, + success: transaction.logs ? transaction.logs[index].success : false + })) + return returnedMessages +} + +// function to map cosmos messages to our details format +function transactionDetailsReducer( + type, + message, + displayedProperties, + reducers, + transaction, + stakingDenom +) { + let details + switch (type) { + case lunieMessageTypes.SEND: + details = sendDetailsReducer(message, reducers) + break + case lunieMessageTypes.STAKE: + details = stakeDetailsReducer(message, reducers) + break + case lunieMessageTypes.RESTAKE: + details = restakeDetailsReducer(message, reducers) + break + case lunieMessageTypes.UNSTAKE: + details = unstakeDetailsReducer(message, reducers) + break + case lunieMessageTypes.CLAIM_REWARDS: + details = claimRewardsDetailsReducer( + message, + displayedProperties, + reducers, + transaction, + stakingDenom + ) + break + case lunieMessageTypes.SUBMIT_PROPOSAL: + details = submitProposalDetailsReducer(message, reducers) + break + case lunieMessageTypes.VOTE: + details = voteProposalDetailsReducer(message, reducers) + break + case lunieMessageTypes.DEPOSIT: + details = depositDetailsReducer(message, reducers) + break + default: + details = {} + } + + return { + type, + ...details + } +} + +function claimRewardsMessagesAggregator(claimMessages) { + // reduce all withdraw messages to one one collecting the validators from all the messages + const onlyValidatorsAddressesArray = claimMessages.map( + msg => msg.value.validator_address + ) + return { + type: `type/MsgWithdrawDelegationReward`, + value: { + validators: onlyValidatorsAddressesArray + } + } +} + +function sendDetailsReducer(message, reducers) { + return { + from: [message.from_address], + to: [message.to_address], + amount: reducers.coinReducer(message.amount[0]) + } +} + +function stakeDetailsReducer(message, reducers) { + return { + to: [message.validator_address], + amount: reducers.coinReducer(message.amount) + } +} + +function restakeDetailsReducer(message, reducers) { + return { + from: [message.validator_src_address], + to: [message.validator_dst_address], + amount: reducers.coinReducer(message.amount) + } +} + +function unstakeDetailsReducer(message, reducers) { + return { + from: [message.validator_address], + amount: reducers.coinReducer(message.amount) + } +} + +function claimRewardsDetailsReducer( + message, + displayedProperties + // reducers, + // transaction, + // stakingDenom +) { + return { + from: message.validators, + amount: { + amount: parseFloat(displayedProperties.claimableRewards[0].amount), + denom: displayedProperties.claimableRewards[0].denom + } + } +} + +// function claimRewardsAmountReducer(transaction, reducers, stakingDenom) { +// return reducers.rewardCoinReducer( +// transaction.events +// .find(event => event.type === `transfer`) +// .attributes.find(attribute => attribute.key === `amount`).value, +// stakingDenom +// ) +// } + +function submitProposalDetailsReducer(message, reducers) { + return { + proposalType: message.content.type, + proposalTitle: message.content.value.title, + proposalDescription: message.content.value.description, + initialDeposit: reducers.coinReducer(message.initial_deposit[0]) + } +} + +function voteProposalDetailsReducer(message) { + return { + proposalId: message.proposal_id, + voteOption: message.option + } +} + +// TO TEST! +function depositDetailsReducer(message, reducers) { + return { + proposalId: message.proposal_id, + amount: reducers.coinReducer(message.amount) } } diff --git a/src/store/actions.js b/src/store/actions.js index b092487cbd..9727336cce 100644 --- a/src/store/actions.js +++ b/src/store/actions.js @@ -2,6 +2,8 @@ import config from '../../config.js' import { getNewWalletFromSeed } from '@lunie/cosmos-keys' import gql from 'graphql-tag' import { NetworksAll } from '../popup/gql' +import { lunieMessageTypes } from '../scripts/parsers' +import { parseTx } from '../scripts/parsers.js' export default ({ apollo }) => { const createSeed = () => { @@ -110,70 +112,34 @@ export default ({ apollo }) => { }) } - const getValidatorsData = async (validatorAddress, network) => { - const txMessage = validatorAddress.value.msg[0] + const getValidatorsData = async (lunieTx, network) => { + let validators = [] if ( - txMessage.type.indexOf('/MsgDelegate') !== -1 || - txMessage.type.indexOf('/MsgUndelegate') !== -1 + lunieTx.type === lunieMessageTypes.STAKE || + lunieTx.type === lunieMessageTypes.RESTAKE ) { - const validatorAddress = txMessage.value.validator_address - const { name: validatorToMoniker, picture } = await fetchValidatorData( - validatorAddress, - network - ) - return [ - { - operator_address: validatorAddress, + validators.push(...lunieTx.details.to) + } + if ( + lunieTx.type === lunieMessageTypes.UNSTAKE || + lunieTx.type === lunieMessageTypes.RESTAKE || + lunieTx.type === lunieMessageTypes.CLAIM_REWARDS + ) { + validators.push(...lunieTx.details.from) + } + return await Promise.all( + validators.map(async validatorAddress => { + const { name: validatorToMoniker, picture } = await fetchValidatorData( + validatorAddress, + network + ) + return { + operatorAddress: validatorAddress, name: validatorToMoniker, picture } - ] - } - if (txMessage.type.indexOf('/MsgBeginRedelegate') !== -1) { - const validator_src_address = txMessage.value.validator_src_address - const { - name: validator_src_moniker, - picture: validator_src_picture - } = await fetchValidatorData(validator_src_address, network) - const validator_dst_address = txMessage.value.validator_dst_address - const { - name: validator_dst_moniker, - picture: validator_dst_picture - } = await fetchValidatorData(validator_dst_address, network) - - return [ - { - operator_address: validator_src_address, - name: validator_src_moniker, - picture: validator_src_picture - }, - { - operator_address: validator_dst_address, - name: validator_dst_moniker, - picture: validator_dst_picture - } - ] - } - if (txMessage.type.indexOf('/MsgWithdrawDelegationReward') !== -1) { - let validators = [] - await Promise.all( - validatorAddress.value.msg.map(async ({ value }) => { - const validator_src_address = value.validator_address - const { - name: validator_src_moniker, - picture - } = await fetchValidatorData(validator_src_address, network) - validators.push({ - operator_address: validator_src_address, - operatorAddress: validator_src_address, // looks carzy, needed to be fix in lunie\src\components\transactions\message-view\WithdrawDelegationRewardMessageDetails.vue - name: validator_src_moniker, - picture - }) - }) - ) - return validators - } - return [] + }) + ) } const fetchValidatorData = async (validatorAddress, network) => { @@ -273,6 +239,10 @@ export default ({ apollo }) => { return wallet.cosmosAddress } + const parseSignMessageTx = (signRequest, displayedProperties) => { + return signRequest ? parseTx(signRequest, displayedProperties) : null + } + return { createSeed, createKey, @@ -288,6 +258,7 @@ export default ({ apollo }) => { resetRecoverData, getAddressFromSeed, setNetwork, - preloadNetworkCapabilities + preloadNetworkCapabilities, + parseSignMessageTx } } diff --git a/test/unit/components/__snapshots__/SessionApprove.spec.js.snap b/test/unit/components/__snapshots__/SessionApprove.spec.js.snap index a7afda3002..ff0a168d4b 100644 --- a/test/unit/components/__snapshots__/SessionApprove.spec.js.snap +++ b/test/unit/components/__snapshots__/SessionApprove.spec.js.snap @@ -31,7 +31,7 @@ exports[`SessionApprove shows the approval modal with the transaction and an inv diff --git a/test/unit/scripts/parsers.spec.js b/test/unit/scripts/parsers.spec.js index b665eb52ca..9e4cdeca8e 100644 --- a/test/unit/scripts/parsers.spec.js +++ b/test/unit/scripts/parsers.spec.js @@ -1,109 +1,60 @@ -import { parseTx, parseFee, parseValueObj } from '../../../src/scripts/parsers' +import { parseTx } from '../../../src/scripts/parsers' const signedMessage = { - type: 'auth/StdTx', - value: { - msg: [ - { - type: 'cosmos-sdk/MsgSend', - value: { - amount: [ - { - amount: '10000000', - denom: 'stake' - } - ], - from_address: 'cosmos1ek9cd8ewgxg9w5xllq9um0uf4aaxaruvcw4v9e', - to_address: 'cosmos1324vt5j3wzx0xsc32mjhkrvy5gn5ef2hrwcg29' - } + msgs: [ + { + type: 'cosmos-sdk/MsgSend', + value: { + amount: [ + { + amount: '10000000', + denom: 'stake' + } + ], + from_address: 'cosmos1ek9cd8ewgxg9w5xllq9um0uf4aaxaruvcw4v9e', + to_address: 'cosmos1324vt5j3wzx0xsc32mjhkrvy5gn5ef2hrwcg29' } - ], - fee: { - amount: [ - { - amount: '40', - denom: 'stake' - } - ], - gas: '39953' - }, - memo: '(Sent via Lunie)' - } -} - -const signedActionMessage = { - type: 'auth/StdTx', - value: { - msg: [ + } + ], + fee: { + amount: [ { - type: 'cosmos-sdk/MsgDelegate', - value: { - amount: [ - { - amount: '10000000', - denom: 'stake' - } - ], - from_address: 'cosmos1ek9cd8ewgxg9w5xllq9um0uf4aaxaruvcw4v9e', - to_address: 'cosmos1324vt5j3wzx0xsc32mjhkrvy5gn5ef2hrwcg29' - } + amount: '40', + denom: 'stake' } ], - fee: { - amount: [ - { - amount: '40', - denom: 'stake' - } - ], - gas: '39953' - }, - memo: '(Sent via Lunie)' - } + gas: '39953' + }, + memo: '(Sent via Lunie)' } describe(`parsers helper`, () => { it(`should parse a signedmessaged ParseTx`, () => { - const shortMessage = { - tx: { - type: 'auth/StdTx', - value: { - msg: 'some message', - fee: 0.01, - memo: 'Sent from Lunie' - } - } + const parsedTx = { + type: 'SendTx', + hash: undefined, + height: undefined, + details: { + type: 'SendTx', + from: ['cosmos1ek9cd8ewgxg9w5xllq9um0uf4aaxaruvcw4v9e'], + to: ['cosmos1324vt5j3wzx0xsc32mjhkrvy5gn5ef2hrwcg29'], + amount: { denom: 'STAKE', amount: 10 } + }, + timestamp: undefined, + memo: '(Sent via Lunie)', + fees: [{ denom: 'STAKE', amount: 0.00004 }], + success: false } - expect( - parseTx(`{"msgs":"some message","fee":0.01,"memo":"Sent from Lunie"}`) - ).toMatchObject(shortMessage) + expect(parseTx(JSON.stringify(signedMessage))).toEqual(parsedTx) }) it(`should parse a signedmessaged parseFee`, () => { - expect(parseFee(signedMessage)).toBe(40) + expect(parseTx(JSON.stringify(signedMessage)).fees[0].amount).toBe(0.00004) }) it(`should parse a signedmessaged parseFee if there are no fees`, () => { - const noFeesSignedMessage = JSON.parse(JSON.stringify(signedMessage)) - noFeesSignedMessage.value.fee.amount = [] - expect(parseFee(noFeesSignedMessage)).toBe(0) - }) - - it(`should parse a signedmessaged parseValueObj`, () => { - const parsedValueObj = { - amount: '10000000', - denom: 'stake' - } - expect(parseValueObj(signedMessage)).toMatchObject(parsedValueObj) - }) - - it(`should parse a signedActionMessage parseValueObj`, () => { - const parsedValueObj = [ - { - amount: '10000000', - denom: 'stake' - } - ] - expect(parseValueObj(signedActionMessage)).toMatchObject(parsedValueObj) + const noFeesSignedMessage = signedMessage + noFeesSignedMessage.fee.amount = { amount: 0, denom: 'stake' } + expect(parseTx(JSON.stringify(noFeesSignedMessage)).fees[0].amount).toBe(0) }) }) diff --git a/test/unit/store/actions.spec.js b/test/unit/store/actions.spec.js index 5bf3a223e4..bbcbb91d22 100644 --- a/test/unit/store/actions.spec.js +++ b/test/unit/store/actions.spec.js @@ -17,7 +17,8 @@ const { getSignRequest, approveSignRequest, rejectSignRequest, - getValidatorsData + getValidatorsData, + parseSignMessageTx } = actions({ apollo: mockApollo }) @@ -200,30 +201,40 @@ describe('actions', () => { window.fetch = jest.fn(() => mockFetchPromise) const v1 = { - value: { - msg: [ - { - type: 'cosmos-sdk/MsgDelegate', - value: { - validator_address: 'address1' - } + msgs: [ + { + type: 'cosmos-sdk/MsgDelegate', + value: { + validator_address: 'address1' } - ] + } + ], + fee: { + amount: { + amount: 1, + denom: 'stake' + } } } - await expect(getValidatorsData(v1)).resolves.toEqual([ - { name: 'name1', operator_address: 'address1' } + await expect( + getValidatorsData(parseSignMessageTx(JSON.stringify(v1))) + ).resolves.toEqual([ + { + name: 'name1', + operatorAddress: 'address1', + picture: undefined + } ]) }) it('Get Validators Name when Redelegating', async () => { const mockFetchPromise = Promise.resolve({ - json: () => Promise.resolve({ data: { validator: { name: 'src' } } }) + json: () => Promise.resolve({ data: { validator: { name: 'dst' } } }) }) const mockFetchPromise2 = Promise.resolve({ - json: () => Promise.resolve({ data: { validator: { name: 'dst' } } }) + json: () => Promise.resolve({ data: { validator: { name: 'src' } } }) }) window.fetch = jest @@ -232,22 +243,36 @@ describe('actions', () => { .mockImplementationOnce(() => mockFetchPromise2) const validatorAddress = { - value: { - msg: [ - { - type: 'cosmos-sdk/MsgBeginRedelegate', - value: { - validator_src_address: 'srcaddress1', - validator_dst_address: 'dstaddress1' - } + msgs: [ + { + type: 'cosmos-sdk/MsgBeginRedelegate', + value: { + validator_src_address: 'srcaddress1', + validator_dst_address: 'dstaddress1' } - ] + } + ], + fee: { + amount: { + amount: 1, + denom: 'stake' + } } } - await expect(getValidatorsData(validatorAddress)).resolves.toEqual([ - { operator_address: 'srcaddress1', name: 'src' }, - { operator_address: 'dstaddress1', name: 'dst' } + await expect( + getValidatorsData(parseSignMessageTx(JSON.stringify(validatorAddress))) + ).resolves.toEqual([ + { + operatorAddress: 'dstaddress1', + name: 'dst', + picture: undefined + }, + { + operatorAddress: 'srcaddress1', + name: 'src', + picture: undefined + } ]) })