Skip to content

Commit

Permalink
[DDW-699] Introduce a fix for Trezor transaction native tokens groupi…
Browse files Browse the repository at this point in the history
…ng issue
  • Loading branch information
tomislavhoracek committed Jun 8, 2021
1 parent 0b94682 commit 5205fbc
Show file tree
Hide file tree
Showing 4 changed files with 64 additions and 52 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Expand Up @@ -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
Expand Down
48 changes: 41 additions & 7 deletions 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
Expand Down Expand Up @@ -32,7 +34,7 @@ export const KEY_PREFIXES = {
// [1852H, 1815H, 0H] => m/1852'/1815'/0'
export const derivationPathToString = (derivationPath: Array<string>) => {
let constructedPath = 'm';
map(derivationPath, (chunk) => {
_.map(derivationPath, (chunk) => {
constructedPath = `${constructedPath}/${chunk.replace('H', "'")}`;
});
return constructedPath;
Expand All @@ -47,15 +49,15 @@ export const derivationPathToAddressPath = (derivationPath: Array<string>) => {

// [1852H, 1815H, 0H] => [2147485500, 2147485463, 2147483648]
export const derivationPathToLedgerPath = (derivationPath: Array<string>) => {
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<string>) => {
const pathParams = takeRight(derivationPath, 2);
const pathParams = _.takeRight(derivationPath, 2);
return {
role: pathParams[0],
index: pathParams[1],
Expand All @@ -65,15 +67,15 @@ export const getParamsFromPath = (derivationPath: Array<string>) => {

// [2147485500, 2147485463, 2147483648] => 1852'/1815'/0'
export const hardenedPathToString = (hardendedPath: Array<number>) => {
const path = map(hardendedPath, (chunk) => `${chunk - HARDENED}H`);
const path = _.map(hardendedPath, (chunk) => `${chunk - HARDENED}H`);
return derivationPathToString(path).replace('m/', '');
};

// [2147485500, 2147485463, 2147483648] => [1852H, 1815H, 0H, 0, 1]
export const hardenedPathToDerivationPath = (hardendedPath: Array<number>) => {
const derivationPath = [];
const constructedDerivationPath = ['m'];
map(hardendedPath, (chunk, index) => {
_.map(hardendedPath, (chunk, index) => {
let pathChunk = chunk.toString();
let constructedPathChunk = chunk.toString();
if (index <= 2) {
Expand Down Expand Up @@ -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;
};
33 changes: 1 addition & 32 deletions source/renderer/app/utils/shelleyLedger.js
Expand Up @@ -10,6 +10,7 @@ import _ from 'lodash';
import {
derivationPathToLedgerPath,
CERTIFICATE_TYPE,
groupTokensByPolicyId,
} from './hardwareWalletUtils';
import { deriveXpubChannel } from '../ipc/getHardwareWalletChannel';
import { AddressStyles } from '../domains/WalletAddress';
Expand Down Expand Up @@ -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<Buffer, Map<Buffer, number>>();

Expand Down
34 changes: 21 additions & 13 deletions source/renderer/app/utils/shelleyTrezor.js
@@ -1,16 +1,18 @@
// @flow
import { utils } from '@cardano-foundation/ledgerjs-hw-app-cardano';
import { map } from 'lodash';
import _ from 'lodash';
import {
derivationPathToString,
CERTIFICATE_TYPE,
groupTokensByPolicyId,
} from './hardwareWalletUtils';

import type {
CoinSelectionInput,
CoinSelectionOutput,
CoinSelectionCertificate,
CoinSelectionWithdrawal,
CoinSelectionAssetsType,
} from '../api/transactions/types';

export const prepareTrezorInput = (input: CoinSelectionInput) => {
Expand All @@ -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),
Expand All @@ -37,7 +44,7 @@ export const prepareTrezorOutput = (output: CoinSelectionOutput) => {
return {
address: output.address,
amount: output.amount.quantity.toString(),
tokenBundle: _getTokenBundle(output.assets),
tokenBundle,
};
};

Expand Down Expand Up @@ -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;
};

0 comments on commit 5205fbc

Please sign in to comment.