diff --git a/CHANGELOG.md b/CHANGELOG.md index dd98a75a96..f819a5492f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ Changelog ### Fixes +- Fixed Trezor transaction native tokens grouping issue ([PR 2594](https://github.com/input-output-hk/daedalus/pull/2594)) - Fixed notes field visibility for software wallets on Share Wallet Address dialog ([PR 2582](https://github.com/input-output-hk/daedalus/pull/2582)) ## 4.1.0 diff --git a/source/renderer/app/utils/hardwareWalletUtils.js b/source/renderer/app/utils/hardwareWalletUtils.js index 2a2850ec50..ceceda2412 100644 --- a/source/renderer/app/utils/hardwareWalletUtils.js +++ b/source/renderer/app/utils/hardwareWalletUtils.js @@ -1,9 +1,11 @@ // @flow -import { map, join, takeRight } from 'lodash'; +import _ from 'lodash'; import { bech32 } from 'bech32'; import { utils } from '@cardano-foundation/ledgerjs-hw-app-cardano'; import { HARDENED } from '../config/hardwareWalletsConfig'; +import type { CoinSelectionAssetsType } from '../api/transactions/types'; + // Constants export const CERTIFICATE_TYPE = { register_reward_account: 0, // register_reward_account @@ -32,7 +34,7 @@ export const KEY_PREFIXES = { // [1852H, 1815H, 0H] => m/1852'/1815'/0' export const derivationPathToString = (derivationPath: Array) => { let constructedPath = 'm'; - map(derivationPath, (chunk) => { + _.map(derivationPath, (chunk) => { constructedPath = `${constructedPath}/${chunk.replace('H', "'")}`; }); return constructedPath; @@ -47,15 +49,15 @@ export const derivationPathToAddressPath = (derivationPath: Array) => { // [1852H, 1815H, 0H] => [2147485500, 2147485463, 2147483648] export const derivationPathToLedgerPath = (derivationPath: Array) => { - const transformedPath = map(derivationPath, (chunk) => + const transformedPath = _.map(derivationPath, (chunk) => chunk.replace('H', "'") ); - const constructedPath = join(transformedPath, '/'); + const constructedPath = _.join(transformedPath, '/'); return utils.str_to_path(constructedPath); }; export const getParamsFromPath = (derivationPath: Array) => { - const pathParams = takeRight(derivationPath, 2); + const pathParams = _.takeRight(derivationPath, 2); return { role: pathParams[0], index: pathParams[1], @@ -65,7 +67,7 @@ export const getParamsFromPath = (derivationPath: Array) => { // [2147485500, 2147485463, 2147483648] => 1852'/1815'/0' export const hardenedPathToString = (hardendedPath: Array) => { - const path = map(hardendedPath, (chunk) => `${chunk - HARDENED}H`); + const path = _.map(hardendedPath, (chunk) => `${chunk - HARDENED}H`); return derivationPathToString(path).replace('m/', ''); }; @@ -73,7 +75,7 @@ export const hardenedPathToString = (hardendedPath: Array) => { export const hardenedPathToDerivationPath = (hardendedPath: Array) => { const derivationPath = []; const constructedDerivationPath = ['m']; - map(hardendedPath, (chunk, index) => { + _.map(hardendedPath, (chunk, index) => { let pathChunk = chunk.toString(); let constructedPathChunk = chunk.toString(); if (index <= 2) { @@ -102,3 +104,35 @@ export const bech32DecodePublicKey = (data: string): Buffer => { const { words } = bech32.decode(data, 1000); return Buffer.from(bech32.fromWords(words)); }; + +export const groupTokensByPolicyId = (assets: CoinSelectionAssetsType) => { + const compareStringsCanonically = (string1: string, string2: string) => + string1.length - string2.length || string1.localeCompare(string2); + + const groupedAssets = {}; + _(assets) + .orderBy(['policyId', 'assetName'], ['asc', 'asc']) + .groupBy(({ policyId }) => policyId) + .mapValues((tokens) => + tokens.map(({ assetName, quantity, policyId }) => ({ + assetName, + quantity, + policyId, + })) + ) + .map((tokens, policyId) => ({ + policyId, + assets: tokens.sort((token1, token2) => + compareStringsCanonically(token1.assetName, token2.assetName) + ), + })) + .sort((token1, token2) => + compareStringsCanonically(token1.policyId, token2.policyId) + ) + .value() + .map((sortedAssetsGroup) => { + groupedAssets[sortedAssetsGroup.policyId] = sortedAssetsGroup.assets; + return groupedAssets; + }); + return groupedAssets; +}; diff --git a/source/renderer/app/utils/shelleyLedger.js b/source/renderer/app/utils/shelleyLedger.js index 064377526d..cb292b5720 100644 --- a/source/renderer/app/utils/shelleyLedger.js +++ b/source/renderer/app/utils/shelleyLedger.js @@ -10,6 +10,7 @@ import _ from 'lodash'; import { derivationPathToLedgerPath, CERTIFICATE_TYPE, + groupTokensByPolicyId, } from './hardwareWalletUtils'; import { deriveXpubChannel } from '../ipc/getHardwareWalletChannel'; import { AddressStyles } from '../domains/WalletAddress'; @@ -119,38 +120,6 @@ export const ShelleyTxInputFromUtxo = (utxoInput: CoinSelectionInput) => { }; }; -export const groupTokensByPolicyId = (assets: CoinSelectionAssetsType) => { - const compareStringsCanonically = (string1: string, string2: string) => - string1.length - string2.length || string1.localeCompare(string2); - - const groupedAssets = {}; - _(assets) - .orderBy(['policyId', 'assetName'], ['asc', 'asc']) - .groupBy(({ policyId }) => policyId) - .mapValues((tokens) => - tokens.map(({ assetName, quantity, policyId }) => ({ - assetName, - quantity, - policyId, - })) - ) - .map((tokens, policyId) => ({ - policyId, - assets: tokens.sort((token1, token2) => - compareStringsCanonically(token1.assetName, token2.assetName) - ), - })) - .sort((token1, token2) => - compareStringsCanonically(token1.policyId, token2.policyId) - ) - .value() - .map((sortedAssetsGroup) => { - groupedAssets[sortedAssetsGroup.policyId] = sortedAssetsGroup.assets; - return groupedAssets; - }); - return groupedAssets; -}; - export const ShelleyTxOutputAssets = (assets: CoinSelectionAssetsType) => { const policyIdMap = new Map>(); diff --git a/source/renderer/app/utils/shelleyTrezor.js b/source/renderer/app/utils/shelleyTrezor.js index b92f53fea8..ceae68b7e1 100644 --- a/source/renderer/app/utils/shelleyTrezor.js +++ b/source/renderer/app/utils/shelleyTrezor.js @@ -4,6 +4,7 @@ import { map } from 'lodash'; import { derivationPathToString, CERTIFICATE_TYPE, + groupTokensByPolicyId, } from './hardwareWalletUtils'; import type { @@ -11,6 +12,7 @@ import type { CoinSelectionOutput, CoinSelectionCertificate, CoinSelectionWithdrawal, + CoinSelectionAssetsType, } from '../api/transactions/types'; export const prepareTrezorInput = (input: CoinSelectionInput) => { @@ -22,11 +24,16 @@ export const prepareTrezorInput = (input: CoinSelectionInput) => { }; export const prepareTrezorOutput = (output: CoinSelectionOutput) => { + let tokenBundle = []; + if (output.assets) { + tokenBundle = prepareTokenBundle(output.assets); + } + if (output.derivationPath) { // Change output return { amount: output.amount.quantity.toString(), - tokenBundle: _getTokenBundle(output.assets), + tokenBundle, addressParameters: { addressType: 0, // BASE address path: derivationPathToString(output.derivationPath), @@ -37,7 +44,7 @@ export const prepareTrezorOutput = (output: CoinSelectionOutput) => { return { address: output.address, amount: output.amount.quantity.toString(), - tokenBundle: _getTokenBundle(output.assets), + tokenBundle, }; }; @@ -65,18 +72,19 @@ export const prepareTrezorWithdrawal = ( }; // Helper Methods +export const prepareTokenBundle = (assets: CoinSelectionAssetsType) => { + const tokenObject = groupTokensByPolicyId(assets); + const tokenObjectEntries = Object.entries(tokenObject); -const _getTokenBundle = (assets) => { - const constructedAssets = map(assets, (asset) => { + const tokenBundle = map(tokenObjectEntries, ([policyId, tokens]) => { + const tokenAmounts = tokens.map(({ assetName, quantity }) => ({ + assetNameBytes: assetName, + amount: quantity.toString(), + })); return { - policyId: asset.policyId, - tokenAmounts: [ - { - assetNameBytes: asset.assetName, - amount: asset.quantity.toString(), - }, - ], + policyId, + tokenAmounts, }; }); - return constructedAssets; + return tokenBundle; };