From ad7f8f40fab11522ee47ccaa53ab240ac72a4de3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Prohaszka?= <104785785+sprohaszka-ledger@users.noreply.github.com> Date: Fri, 2 Feb 2024 11:31:01 +0100 Subject: [PATCH] Feat/live 9560 polkadot reorg (#5882) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: move type into specific folder Signed-off-by: Stéphane Prohaszka * feat: move js files to bridge dir Signed-off-by: Stéphane Prohaszka * feat: create dedicated dir for e2e and integ tests Signed-off-by: Stéphane Prohaszka * feat: move last files to dedicated dir Signed-off-by: Stéphane Prohaszka * feat: choose export function from polkadot Signed-off-by: Stéphane Prohaszka * fix: error in import Signed-off-by: Stéphane Prohaszka * fix: restore import for polkadot Signed-off-by: Stéphane Prohaszka * fix: missing generated import Signed-off-by: Stéphane Prohaszka * fix: lighten polkadot test import Signed-off-by: Stéphane Prohaszka * fix: missing correct test import Signed-off-by: Stéphane Prohaszka * fix: unimported Signed-off-by: Stéphane Prohaszka * fix: still missing correct test import Signed-off-by: Stéphane Prohaszka * chore: remove extra path for errors access Signed-off-by: Stéphane Prohaszka * chore: rename api directory to network Signed-off-by: Stéphane Prohaszka * chore: separate logic from bridge specific WIP Signed-off-by: Stéphane Prohaszka * chore: separate formatting from serialization file Signed-off-by: Stéphane Prohaszka * fix: missing export Signed-off-by: Stéphane Prohaszka * chore: return early in serialization OperationExtra Signed-off-by: Stéphane Prohaszka * chore: remove platform-sdk file from polkadot module Signed-off-by: Stéphane Prohaszka * chore: some clean Signed-off-by: Stéphane Prohaszka * chore: format code Signed-off-by: Stéphane Prohaszka * fix: unecessary import Signed-off-by: Stéphane Prohaszka * fix: remove unused types in mobile Signed-off-by: Stéphane Prohaszka * fix: unimported Signed-off-by: Stéphane Prohaszka * chore: unimported Signed-off-by: Stéphane Prohaszka * chore: remove unused dependency Signed-off-by: Stéphane Prohaszka * fix: cosmos type error Signed-off-by: Stéphane Prohaszka * chore: review feedbacks Signed-off-by: Stéphane Prohaszka * chore: rename bridge.integration.test.ts Signed-off-by: Stéphane Prohaszka * fix: undo wrongly updated snapshot Signed-off-by: Stéphane Prohaszka * chore: feedbacks Signed-off-by: Stéphane Prohaszka * fix: revert use of inferFamilyFromAccountId Signed-off-by: Stéphane Prohaszka * fix: add missing export asked by ui Signed-off-by: Stéphane Prohaszka --------- Signed-off-by: Stéphane Prohaszka --- .../src/components/Nft/NftViewer.tsx | 4 +- .../evm/EvmCustomFees/Evm1559CustomFees.tsx | 2 +- .../src/families/evm/EvmFeesStrategy.tsx | 2 +- .../src/screens/SendFunds/01c-SelectNft.tsx | 2 +- libs/coin-algorand/.unimportedrc.json | 1 + libs/coin-algorand/src/bridge/js.ts | 9 +- .../src/{account.ts => formatters.ts} | 44 +- libs/coin-algorand/src/serialization.ts | 58 +- libs/coin-algorand/src/types.ts | 15 + libs/coin-polkadot/.unimportedrc.json | 22 +- libs/coin-polkadot/package.json | 46 +- libs/coin-polkadot/src/api/subscan.ts | 362 ------------ libs/coin-polkadot/src/api/websocket.ts | 535 ------------------ .../src/bridge/createTransaction.ts | 3 + .../{ => bridge}/deviceTransactionConfig.ts | 4 +- .../src/{account.ts => bridge/formatters.ts} | 63 +-- .../src/bridge/{js.ts => index.ts} | 63 +-- .../coin-polkadot/src/{ => bridge}/preload.ts | 37 +- .../prepareTransaction.ts} | 8 +- .../coin-polkadot/src/bridge/serialization.ts | 130 +++++ .../signOperation.ts} | 48 +- .../src/{ => bridge}/transaction.ts | 5 +- .../coin-polkadot/src/{ => common}/address.ts | 0 libs/coin-polkadot/src/common/index.ts | 1 + libs/coin-polkadot/src/index.ts | 4 + libs/coin-polkadot/src/logic.test.ts | 50 -- libs/coin-polkadot/src/logic/broadcast.ts | 22 + .../buildTransaction.test.ts} | 8 +- .../buildTransaction.ts} | 6 +- .../estimateMaxSpendable.ts} | 12 +- .../getFeesForTransaction.ts} | 13 +- .../getTransactionStatus.ts} | 12 +- libs/coin-polkadot/src/logic/index.ts | 38 ++ .../src/{ => logic}/polkadot-crypto.ts | 0 .../src/logic/signTransaction.ts | 37 ++ libs/coin-polkadot/src/logic/state.ts | 28 + .../synchronisation.ts} | 11 +- .../transaction.ts} | 6 +- libs/coin-polkadot/src/logic/utils.test.ts | 116 ++++ .../src/{logic.ts => logic/utils.ts} | 5 +- .../src/{api => network}/bisontrails.ts | 2 +- .../src/{api => network}/common.ts | 0 .../src/{api => network}/index.ts | 22 - .../src/{api => network}/sidecar.ts | 0 .../src/{api => network}/sidecar.types.ts | 0 .../src/{api => network}/sidecar.unit.test.ts | 0 libs/coin-polkadot/src/serialization.ts | 66 --- .../getAddress.ts} | 6 +- libs/coin-polkadot/src/signer/index.ts | 7 + .../bot-deviceActions.ts} | 2 +- .../src/{specs.ts => test/bot-specs.ts} | 8 +- .../bridgeDatasetTest.ts} | 76 +-- .../src/{cli-transaction.ts => test/cli.ts} | 42 +- libs/coin-polkadot/src/test/index.ts | 6 + libs/coin-polkadot/src/{ => types}/errors.ts | 0 libs/coin-polkadot/src/types/index.ts | 3 + .../src/{types.ts => types/model.ts} | 10 + libs/coin-polkadot/src/{ => types}/signer.ts | 0 .../scripts/sync-families-dispatch.mjs | 27 +- .../src/account/formatters.ts | 2 +- .../src/account/serialization.ts | 14 +- libs/ledger-live-common/src/bridge/impl.ts | 33 +- libs/ledger-live-common/src/bridge/index.ts | 30 +- .../src/families/bitcoin/account.ts | 50 +- .../src/families/bitcoin/formatters.ts | 52 ++ .../src/families/bitcoin/transaction.ts | 2 +- .../src/families/celo/account.ts | 31 - .../src/families/celo/bridge/js.ts | 9 +- .../src/families/celo/serialization.ts | 45 +- .../src/families/celo/types.ts | 8 + .../src/families/cosmos/bridge/js.ts | 9 +- .../cosmos/{account.ts => formatters.ts} | 72 +-- .../src/families/cosmos/serialization.ts | 82 ++- .../src/families/cosmos/types.ts | 28 +- .../src/families/elrond/bridge/js.ts | 9 +- .../elrond/{account.ts => formatters.ts} | 25 +- .../src/families/elrond/serialization.ts | 40 +- .../src/families/elrond/types.ts | 9 + .../polkadot/bridge.integration.test.ts | 4 +- .../src/families/polkadot/bridge/mock.ts | 2 +- .../polkadot}/platformAdapter.test.ts | 0 .../src/families/polkadot}/platformAdapter.ts | 0 .../src/families/polkadot/react.ts | 4 +- .../src/families/polkadot/setup.ts | 13 +- .../src/families/polkadot/types.ts | 2 +- .../src/families/polkadot/walletApiAdapter.ts | 4 +- .../src/families/tron/account.ts | 59 -- .../src/families/tron/bridge/js.ts | 9 +- .../src/families/tron/serialization.ts | 71 ++- .../src/families/tron/types.ts | 20 +- .../src/generated/account.ts | 12 - .../src/generated/deviceTransactionConfig.ts | 2 +- .../src/generated/formatters.ts | 13 + .../src/generated/platformAdapter.ts | 4 +- .../ledger-live-common/src/generated/types.ts | 8 +- libs/ledger-live-common/src/mock/account.ts | 2 +- .../packages/types-live/src/bridge.ts | 4 +- .../packages/types-live/src/operation.ts | 6 +- pnpm-lock.yaml | 3 - 99 files changed, 1152 insertions(+), 1749 deletions(-) rename libs/coin-algorand/src/{account.ts => formatters.ts} (58%) delete mode 100644 libs/coin-polkadot/src/api/subscan.ts delete mode 100644 libs/coin-polkadot/src/api/websocket.ts create mode 100644 libs/coin-polkadot/src/bridge/createTransaction.ts rename libs/coin-polkadot/src/{ => bridge}/deviceTransactionConfig.ts (98%) rename libs/coin-polkadot/src/{account.ts => bridge/formatters.ts} (61%) rename libs/coin-polkadot/src/bridge/{js.ts => index.ts} (60%) rename libs/coin-polkadot/src/{ => bridge}/preload.ts (79%) rename libs/coin-polkadot/src/{js-prepareTransaction.ts => bridge/prepareTransaction.ts} (69%) create mode 100644 libs/coin-polkadot/src/bridge/serialization.ts rename libs/coin-polkadot/src/{js-signOperation.ts => bridge/signOperation.ts} (76%) rename libs/coin-polkadot/src/{ => bridge}/transaction.ts (96%) rename libs/coin-polkadot/src/{ => common}/address.ts (100%) create mode 100644 libs/coin-polkadot/src/common/index.ts create mode 100644 libs/coin-polkadot/src/index.ts delete mode 100644 libs/coin-polkadot/src/logic.test.ts create mode 100644 libs/coin-polkadot/src/logic/broadcast.ts rename libs/coin-polkadot/src/{js-buildTransaction.test.ts => logic/buildTransaction.test.ts} (97%) rename libs/coin-polkadot/src/{js-buildTransaction.ts => logic/buildTransaction.ts} (97%) rename libs/coin-polkadot/src/{js-estimateMaxSpendable.ts => logic/estimateMaxSpendable.ts} (73%) rename libs/coin-polkadot/src/{js-getFeesForTransaction.ts => logic/getFeesForTransaction.ts} (74%) rename libs/coin-polkadot/src/{js-getTransactionStatus.ts => logic/getTransactionStatus.ts} (97%) create mode 100644 libs/coin-polkadot/src/logic/index.ts rename libs/coin-polkadot/src/{ => logic}/polkadot-crypto.ts (100%) create mode 100644 libs/coin-polkadot/src/logic/signTransaction.ts create mode 100644 libs/coin-polkadot/src/logic/state.ts rename libs/coin-polkadot/src/{js-synchronisation.ts => logic/synchronisation.ts} (93%) rename libs/coin-polkadot/src/{js-createTransaction.ts => logic/transaction.ts} (71%) create mode 100644 libs/coin-polkadot/src/logic/utils.test.ts rename libs/coin-polkadot/src/{logic.ts => logic/utils.ts} (98%) rename libs/coin-polkadot/src/{api => network}/bisontrails.ts (99%) rename libs/coin-polkadot/src/{api => network}/common.ts (100%) rename libs/coin-polkadot/src/{api => network}/index.ts (95%) rename libs/coin-polkadot/src/{api => network}/sidecar.ts (100%) rename libs/coin-polkadot/src/{api => network}/sidecar.types.ts (100%) rename libs/coin-polkadot/src/{api => network}/sidecar.unit.test.ts (100%) delete mode 100644 libs/coin-polkadot/src/serialization.ts rename libs/coin-polkadot/src/{hw-getAddress.ts => signer/getAddress.ts} (82%) create mode 100644 libs/coin-polkadot/src/signer/index.ts rename libs/coin-polkadot/src/{speculos-deviceActions.ts => test/bot-deviceActions.ts} (96%) rename libs/coin-polkadot/src/{specs.ts => test/bot-specs.ts} (98%) rename libs/coin-polkadot/src/{bridge.integration.test.ts => test/bridgeDatasetTest.ts} (90%) rename libs/coin-polkadot/src/{cli-transaction.ts => test/cli.ts} (84%) create mode 100644 libs/coin-polkadot/src/test/index.ts rename libs/coin-polkadot/src/{ => types}/errors.ts (100%) create mode 100644 libs/coin-polkadot/src/types/index.ts rename libs/coin-polkadot/src/{types.ts => types/model.ts} (91%) rename libs/coin-polkadot/src/{ => types}/signer.ts (100%) create mode 100644 libs/ledger-live-common/src/families/bitcoin/formatters.ts delete mode 100644 libs/ledger-live-common/src/families/celo/account.ts rename libs/ledger-live-common/src/families/cosmos/{account.ts => formatters.ts} (65%) rename libs/ledger-live-common/src/families/elrond/{account.ts => formatters.ts} (80%) rename libs/{coin-polkadot/src => ledger-live-common/src/families/polkadot}/platformAdapter.test.ts (100%) rename libs/{coin-polkadot/src => ledger-live-common/src/families/polkadot}/platformAdapter.ts (100%) delete mode 100644 libs/ledger-live-common/src/families/tron/account.ts create mode 100644 libs/ledger-live-common/src/generated/formatters.ts diff --git a/apps/ledger-live-mobile/src/components/Nft/NftViewer.tsx b/apps/ledger-live-mobile/src/components/Nft/NftViewer.tsx index 3ffd4595da7..5c1bc3cb428 100644 --- a/apps/ledger-live-mobile/src/components/Nft/NftViewer.tsx +++ b/apps/ledger-live-mobile/src/components/Nft/NftViewer.tsx @@ -65,7 +65,7 @@ import NftViewerBackground from "./NftViewerBackground"; import NftViewerScreenHeader from "./NftViewerScreenHeader"; import invariant from "invariant"; import DiscreetModeContext, { withDiscreetMode } from "~/context/DiscreetModeContext"; -import { EvmNftTransaction, Transaction } from "@ledgerhq/coin-evm/types/index"; +import { EvmNftTransaction } from "@ledgerhq/coin-evm/types/index"; type Props = CompositeScreenProps< | StackNavigatorProps @@ -207,7 +207,7 @@ const NftViewer = ({ route }: Props) => { }; const goToRecipientSelection = useCallback(() => { - const bridge = getAccountBridge(account); + const bridge = getAccountBridge(account); const defaultTransaction = bridge.createTransaction(account); const transaction = bridge.updateTransaction(defaultTransaction, { diff --git a/apps/ledger-live-mobile/src/families/evm/EvmCustomFees/Evm1559CustomFees.tsx b/apps/ledger-live-mobile/src/families/evm/EvmCustomFees/Evm1559CustomFees.tsx index 65498b1fd8d..d00970f615f 100644 --- a/apps/ledger-live-mobile/src/families/evm/EvmCustomFees/Evm1559CustomFees.tsx +++ b/apps/ledger-live-mobile/src/families/evm/EvmCustomFees/Evm1559CustomFees.tsx @@ -51,7 +51,7 @@ const Evm1559CustomFees = ({ // Creating a new transaction to simulate the bridge status response before updating // the original transaction - const bridge = getAccountBridge(account); + const bridge = getAccountBridge(account); const { transaction, setTransaction, status } = useBridgeTransaction( () => ({ diff --git a/apps/ledger-live-mobile/src/families/evm/EvmFeesStrategy.tsx b/apps/ledger-live-mobile/src/families/evm/EvmFeesStrategy.tsx index ff9da3f0a5a..c0e72bf16ee 100644 --- a/apps/ledger-live-mobile/src/families/evm/EvmFeesStrategy.tsx +++ b/apps/ledger-live-mobile/src/families/evm/EvmFeesStrategy.tsx @@ -153,7 +153,7 @@ export default function EvmFeesStrategy({ const onFeesSelected = useCallback( ({ feesStrategy }: { feesStrategy: StrategyWithCustom }) => { - const bridge = getAccountBridge(account, parentAccount); + const bridge = getAccountBridge(account, parentAccount); const patch: Partial = feesStrategy === "custom" && customStrategyTransactionPatch diff --git a/apps/ledger-live-mobile/src/screens/SendFunds/01c-SelectNft.tsx b/apps/ledger-live-mobile/src/screens/SendFunds/01c-SelectNft.tsx index 1365b54efe4..5cf53374c92 100644 --- a/apps/ledger-live-mobile/src/screens/SendFunds/01c-SelectNft.tsx +++ b/apps/ledger-live-mobile/src/screens/SendFunds/01c-SelectNft.tsx @@ -33,7 +33,7 @@ const NftRow = memo(({ account, nft }: { account: Account; nft: ProtoNFT }) => { // Only evm family handles nft as of today. If later we have other family, // we will need to rework the NFT send flow by implementing family specific // logic under their "src/families" respective folder. - const bridge = getAccountBridge(account); + const bridge = getAccountBridge(account); const defaultTransaction = bridge.createTransaction(account); diff --git a/libs/coin-algorand/.unimportedrc.json b/libs/coin-algorand/.unimportedrc.json index 33bdef16759..2692a257488 100644 --- a/libs/coin-algorand/.unimportedrc.json +++ b/libs/coin-algorand/.unimportedrc.json @@ -5,6 +5,7 @@ "src/cli-transaction.ts", "src/deviceTransactionConfig.ts", "src/errors.ts", + "src/formatters.ts", "src/hw-getAddress.ts", "src/initAccount.ts", "src/mock.ts", diff --git a/libs/coin-algorand/src/bridge/js.ts b/libs/coin-algorand/src/bridge/js.ts index 9ea08997bf4..0f7bb446be4 100644 --- a/libs/coin-algorand/src/bridge/js.ts +++ b/libs/coin-algorand/src/bridge/js.ts @@ -19,7 +19,12 @@ import { getTransactionStatus } from "../js-getTransactionStatus"; import prepareTransaction from "../js-prepareTransaction"; import { buildSignOperation } from "../js-signOperation"; import { makeGetAccountShape } from "../js-synchronization"; -import { assignFromAccountRaw, assignToAccountRaw } from "../serialization"; +import { + assignFromAccountRaw, + assignToAccountRaw, + fromOperationExtraRaw, + toOperationExtraRaw, +} from "../serialization"; import type { Transaction } from "../types"; import { AlgorandAddress, AlgorandSignature, AlgorandSigner } from "../signer"; @@ -68,6 +73,8 @@ export function buildAccountBridge( signOperation, broadcast: broadcast(algorandAPI), estimateMaxSpendable: estimateMaxSpendable(algorandAPI), + fromOperationExtraRaw, + toOperationExtraRaw, }; } diff --git a/libs/coin-algorand/src/account.ts b/libs/coin-algorand/src/formatters.ts similarity index 58% rename from libs/coin-algorand/src/account.ts rename to libs/coin-algorand/src/formatters.ts index c3e3accc8de..d38651ec19a 100644 --- a/libs/coin-algorand/src/account.ts +++ b/libs/coin-algorand/src/formatters.ts @@ -1,14 +1,8 @@ import { getAccountUnit } from "@ledgerhq/coin-framework/account/index"; import { formatCurrencyUnit } from "@ledgerhq/coin-framework/currencies/index"; import type { Unit } from "@ledgerhq/types-cryptoassets"; -import { BigNumber } from "bignumber.js"; import invariant from "invariant"; -import type { - AlgorandAccount, - AlgorandOperation, - AlgorandOperationExtra, - AlgorandOperationExtraRaw, -} from "./types"; +import type { AlgorandAccount, AlgorandOperation } from "./types"; function formatOperationSpecifics(op: AlgorandOperation, unit: Unit | null | undefined): string { const { rewards } = op.extra; @@ -43,43 +37,7 @@ function formatAccountSpecifics(account: AlgorandAccount): string { return str; } -export function fromOperationExtraRaw(extraRaw: AlgorandOperationExtraRaw) { - const extra: AlgorandOperationExtra = {}; - if (extraRaw.rewards) { - extra.rewards = new BigNumber(extraRaw.rewards); - } - - if (extraRaw.memo) { - extra.memo = extraRaw.memo; - } - - if (extraRaw.assetId) { - extra.assetId = extraRaw.assetId; - } - - return extra; -} - -export function toOperationExtraRaw(extra: AlgorandOperationExtra) { - const extraRaw: AlgorandOperationExtraRaw = {}; - if (extra.rewards) { - extraRaw.rewards = extra.rewards.toString(); - } - - if (extra.memo) { - extraRaw.memo = extra.memo; - } - - if (extra.assetId) { - extraRaw.assetId = extra.assetId; - } - - return extraRaw; -} - export default { formatAccountSpecifics, formatOperationSpecifics, - fromOperationExtraRaw, - toOperationExtraRaw, }; diff --git a/libs/coin-algorand/src/serialization.ts b/libs/coin-algorand/src/serialization.ts index 9fa17f4857a..6c315da2488 100644 --- a/libs/coin-algorand/src/serialization.ts +++ b/libs/coin-algorand/src/serialization.ts @@ -1,10 +1,14 @@ -import type { Account, AccountRaw } from "@ledgerhq/types-live"; +import type { Account, AccountRaw, OperationExtra, OperationExtraRaw } from "@ledgerhq/types-live"; import { BigNumber } from "bignumber.js"; -import type { - AlgorandAccount, - AlgorandAccountRaw, - AlgorandResources, - AlgorandResourcesRaw, +import { + isAlgorandOperationExtra, + isAlgorandOperationExtraRaw, + type AlgorandAccount, + type AlgorandAccountRaw, + type AlgorandOperationExtra, + type AlgorandOperationExtraRaw, + type AlgorandResources, + type AlgorandResourcesRaw, } from "./types"; function toResourcesRaw(r: AlgorandResources): AlgorandResourcesRaw { @@ -37,3 +41,45 @@ export function assignFromAccountRaw(accountRaw: AccountRaw, account: Account): algorandAccount.algorandResources = fromResourcesRaw(algorandResourcesRaw); } } + +export function fromOperationExtraRaw(extraRaw: OperationExtraRaw) { + const extra: AlgorandOperationExtra = {}; + if (!isAlgorandOperationExtraRaw(extraRaw)) { + return extra; + } + + if (extraRaw.rewards) { + extra.rewards = new BigNumber(extraRaw.rewards); + } + + if (extraRaw.memo) { + extra.memo = extraRaw.memo; + } + + if (extraRaw.assetId) { + extra.assetId = extraRaw.assetId; + } + + return extra; +} + +export function toOperationExtraRaw(extra: OperationExtra) { + const extraRaw: AlgorandOperationExtraRaw = {}; + if (!isAlgorandOperationExtra(extra)) { + return extraRaw; + } + + if (extra.rewards) { + extraRaw.rewards = extra.rewards.toString(); + } + + if (extra.memo) { + extraRaw.memo = extra.memo; + } + + if (extra.assetId) { + extraRaw.assetId = extra.assetId; + } + + return extraRaw; +} diff --git a/libs/coin-algorand/src/types.ts b/libs/coin-algorand/src/types.ts index 5e469cb2329..3df1257f377 100644 --- a/libs/coin-algorand/src/types.ts +++ b/libs/coin-algorand/src/types.ts @@ -2,6 +2,8 @@ import type { Account, AccountRaw, Operation, + OperationExtra, + OperationExtraRaw, OperationRaw, TransactionCommon, TransactionCommonRaw, @@ -55,11 +57,24 @@ export type AlgorandOperationExtra = { memo?: string; assetId?: string; }; +export function isAlgorandOperationExtra(op: OperationExtra): op is AlgorandOperationExtra { + return ( + op !== null && typeof op === "object" && ("rewards" in op || "memo" in op || "assetId" in op) + ); +} + export type AlgorandOperationExtraRaw = { rewards?: string; memo?: string; assetId?: string; }; +export function isAlgorandOperationExtraRaw( + op: OperationExtraRaw, +): op is AlgorandOperationExtraRaw { + return ( + op !== null && typeof op === "object" && ("rewards" in op || "memo" in op || "assetId" in op) + ); +} export type AlgorandAccount = Account & { algorandResources: AlgorandResources; diff --git a/libs/coin-polkadot/.unimportedrc.json b/libs/coin-polkadot/.unimportedrc.json index 57adf515978..19f6a5afc38 100644 --- a/libs/coin-polkadot/.unimportedrc.json +++ b/libs/coin-polkadot/.unimportedrc.json @@ -1,18 +1,14 @@ { "entry": [ - "src/account.ts", - "src/bridge/js.ts", - "src/cli-transaction.ts", - "src/deviceTransactionConfig.ts", - "src/errors.ts", - "src/hw-getAddress.ts", - "src/logic.ts", - "src/platformAdapter.ts", - "src/preload.ts", - "src/serialization.ts", - "src/specs.ts", - "src/transaction.ts" + "src/bridge/index.ts", + "src/bridge/deviceTransactionConfig.ts", + "src/bridge/formatters.ts", + "src/signer/index.ts", + "src/test/cli.ts", + "src/test/index.ts", + "src/test/specs.ts", + "src/types/errors.ts", + "src/index.ts" ], - "ignoreUnimported": ["src/api/subscan.ts", "src/api/websocket.ts"], "ignoreUnresolved": [] } diff --git a/libs/coin-polkadot/package.json b/libs/coin-polkadot/package.json index e0175fdfd53..33708278761 100644 --- a/libs/coin-polkadot/package.json +++ b/libs/coin-polkadot/package.json @@ -28,14 +28,57 @@ "lib-es/*": [ "lib-es/*" ], + "logic": [ + "lib/logic/index" + ], + "specs": [ + "lib/test/bot-specs" + ], "*": [ - "lib/*" + "lib/*", + "lib/bridge/*", + "lib/logic/*", + "lib/signer/*", + "lib/test/*", + "lib/types/*" ] } }, "exports": { "./lib/*": "./lib/*.js", "./lib-es/*": "./lib-es/*.js", + "./deviceTransactionConfig": { + "require": "./lib/bridge/deviceTransactionConfig.js", + "default": "./lib-es/bridge/deviceTransactionConfig.js" + }, + "./errors": { + "require": "./lib/types/errors.js", + "default": "./lib-es/types/errors.js" + }, + "./formatters": { + "require": "./lib/bridge/formatters.js", + "default": "./lib-es/bridge/formatters.js" + }, + "./logic": { + "require": "./lib/logic/index.js", + "default": "./lib-es/logic/index.js" + }, + "./preload": { + "require": "./lib/bridge/preload.js", + "default": "./lib-es/bridge/preload.js" + }, + "./serialization": { + "require": "./lib/bridge/serialization.js", + "default": "./lib-es/bridge/serialization.js" + }, + "./specs": { + "require": "./lib/test/bot-specs.js", + "default": "./lib-es/test/bot-specs.js" + }, + "./transaction": { + "require": "./lib/bridge/transaction.js", + "default": "./lib-es/bridge/transaction.js" + }, "./*": { "require": "./lib/*.js", "default": "./lib-es/*.js" @@ -48,7 +91,6 @@ "@ledgerhq/cryptoassets": "workspace:^", "@ledgerhq/devices": "workspace:^", "@ledgerhq/errors": "workspace:^", - "@ledgerhq/live-app-sdk": "^0.8.1", "@ledgerhq/live-env": "workspace:^", "@ledgerhq/logs": "workspace:^", "@ledgerhq/types-cryptoassets": "workspace:^", diff --git a/libs/coin-polkadot/src/api/subscan.ts b/libs/coin-polkadot/src/api/subscan.ts deleted file mode 100644 index 02417c92ede..00000000000 --- a/libs/coin-polkadot/src/api/subscan.ts +++ /dev/null @@ -1,362 +0,0 @@ -/* - -THIS FILE IS UNUSED AND PROVIDED AS EXAMPLE FOR USING SUBSCAN INDEXER. - -*/ - -/* eslint-disable */ - -/* istanbul ignore file */ -import uniqBy from "lodash/uniqBy"; -import camelCase from "lodash/camelCase"; -import type { Operation } from "@ledgerhq/types-live"; -import type { NetworkRequestCall } from "@ledgerhq/coin-framework/network"; -import { BigNumber } from "bignumber.js"; -import { getEnv } from "@ledgerhq/live-env"; -import { encodeAddress } from "@polkadot/util-crypto"; -import { getOperationType } from "./common"; -import type { PolkadotValidator } from "../types"; - -const getBaseApiUrl = () => getEnv("API_POLKADOT_INDEXER"); - -const DOT_REDOMINATION_BLOCK = 1248328; -const SUBSCAN_MULTIPLIER = 10000000000; -const SUBSCAN_ROW = 100; -const SUBSCAN_VALIDATOR_OVERSUBSCRIBED = 128; -const SUBSCAN_VALIDATOR_COMISSION_RATIO = 1000000000; - -const encodePolkadotAddr = (addr) => { - return encodeAddress( - "0x" + addr, - /* SS58FORMAT= */ - 0 - ); -}; - -const getExtra = (type, extrinsic) => { - let extra: { - transferAmount?: BigNumber; - palletMethod: string; - bondedAmount?: BigNumber; - unbondedAmount?: BigNumber; - amount?: BigNumber; - withdrawUnbondedAmount?: any; - validatorStash?: any; - validators?: any; - } = { - palletMethod: extrinsic.call_module - ? `${extrinsic.call_module}.${camelCase(extrinsic.call_module_function)}` - : `${extrinsic.module_id}.${camelCase(extrinsic.event_id)}`, - }; - const params = JSON.parse(extrinsic.params); - let valueParam; - - switch (type) { - case "OUT": - valueParam = params.find((p) => p.name === "value")?.value; - extra = { ...extra, transferAmount: new BigNumber(valueParam || 0) }; - break; - - case "BOND": - valueParam = - params.find((p) => p.name === "value")?.value || - params.find((p) => p.name === "max_additional")?.value; - extra = { ...extra, bondedAmount: new BigNumber(valueParam || 0) }; - break; - - case "UNBOND": - extra = { - ...extra, - unbondedAmount: new BigNumber( - params.find((p) => p.name === "value")?.value || 0 - ), - }; - break; - - case "SLASH": - case "REWARD_PAYOUT": - extra = { - ...extra, - validatorStash: encodePolkadotAddr( - params.find((p) => p.type === "AccountId").value - ), - amount: new BigNumber(params.find((p) => p.type === "Balance").value), - }; - break; - - case "NOMINATE": - extra = { - ...extra, - validators: params - .find((t) => t.name === "targets") - .value.reduce((old, current) => { - return current ? [...old, encodePolkadotAddr(current)] : old; - }, []), - }; - break; - } - - return extra; -}; - -const subscanAmountToPlanck = (amount, blockHeight) => { - if (blockHeight >= DOT_REDOMINATION_BLOCK) { - return new BigNumber(amount).multipliedBy(SUBSCAN_MULTIPLIER); - } - - return new BigNumber(amount) - .multipliedBy(100) - .multipliedBy(SUBSCAN_MULTIPLIER); -}; - -const mapSubscanReward = ({ accountId }, reward): Partial => { - const type = reward.event_id === "Reward" ? "REWARD_PAYOUT" : "SLASH"; - const hash = reward.extrinsic_hash || reward.event_index; // Slashes are not extrinsics - - return { - id: `${accountId}-${hash}-${type}`, - accountId, - fee: new BigNumber(0), - value: new BigNumber(reward.amount), - type, - hash: reward.extrinsic_hash, - blockHeight: reward.block_num, - date: new Date(reward.block_timestamp * 1000), - recipients: [], - senders: [], - extra: getExtra(type, reward), - }; -}; - -const mapSubscanTransfer = ( - { addr, accountId }, - transfer -): Partial | null => { - const type = transfer.from === addr ? "OUT" : "IN"; - const amount = subscanAmountToPlanck(transfer.amount, transfer.block_num); - return { - id: `${accountId}-${transfer.hash}-${type}`, - accountId, - fee: new BigNumber(transfer.fee), - value: !transfer.success ? new BigNumber(0) : amount, - type, - hash: transfer.hash, - blockHeight: transfer.block_num, - date: new Date(transfer.block_timestamp * 1000), - extra: { - palletMethod: "balances.transfer_allow_death", - // FIXME subscan plz - transferAmount: amount, - }, - senders: [transfer.from], - recipients: [transfer.to], - hasFailed: !transfer.success, - }; -}; - -const mapSubscanExtrinsic = ( - { addr, accountId }, - extrinsic -): Partial => { - const pallet = extrinsic.call_module; - const palletMethod = camelCase(extrinsic.call_module_function); - const type = getOperationType(pallet, palletMethod); - // FIXME subscan WTF - const params = JSON.parse(extrinsic.params); - const paramRecipient = params.find((p) => p.name === "dest"); - const paramAmount = params.find((p) => p.name === "value"); - const recipient = paramRecipient && encodePolkadotAddr(paramRecipient.value); - // All successful transfers, but not self transfers (which only burn fees) - const value = - type === "OUT" && extrinsic.success && recipient !== addr && paramAmount - ? new BigNumber(paramAmount.value).plus(extrinsic.fee) - : new BigNumber(extrinsic.fee); - return { - id: `${accountId}-${extrinsic.extrinsic_hash}-${type}`, - accountId, - fee: new BigNumber(extrinsic.fee), - value, - type, - hash: extrinsic.extrinsic_hash, - blockHeight: extrinsic.block_num, - date: new Date(extrinsic.block_timestamp * 1000), - senders: [addr], - recipients: recipient ? [recipient] : [], - extra: getExtra(type, extrinsic), - hasFailed: !extrinsic.success, - transactionSequenceNumber: - extrinsic.signer === addr ? extrinsic.nonce : undefined, - }; -}; - -const fetchSubscanList = (network: NetworkRequestCall) => async ( - resourceName, - url, - mapFn, - mapArgs, - startAt, - page = 0, - prevOperations = [] -) => { - let operations; - - if (prevOperations.length) { - const oldestBlockHeight = ( - prevOperations[prevOperations.length - 1] as { blockHeight: number } - ).blockHeight; - - if (oldestBlockHeight < startAt) { - return prevOperations.filter( - (o) => (o as { blockHeight: number }).blockHeight >= startAt - ); - } - } - - try { - const { data } = await network({ - method: "POST", - url: `${getBaseApiUrl()}${url}`, - data: { - address: mapArgs.addr, - row: SUBSCAN_ROW, - page, - }, - }); - - if (data.code !== 0) { - throw new Error(`SUBSCAN: ${data.message} - code ${data.code}`); - } - - const list = data.data[resourceName] || []; - const count = data.data.count; - // console.log(`${url} - ${prevOperations.length} / ${count}`); - operations = [...prevOperations, ...list.map(mapFn.bind(null, mapArgs))]; - return operations.length < count - ? fetchSubscanList(network)( - resourceName, - url, - mapFn, - mapArgs, - startAt, - page + 1, - operations - ) - : operations; - } catch (e) { - console.error(e); - throw e; - } -}; - -export const getOperations = (network: NetworkRequestCall) => async ( - accountId: string, - addr: string, - startAt: number = 0 -) => { - const mapArgs = { - addr, - accountId, - }; - const [extrinsicsOp, transfersOp, rewardsOp] = await Promise.all([ - fetchSubscanList(network)( - "extrinsics", - "/api/scan/extrinsics", - mapSubscanExtrinsic, - mapArgs, - startAt - ), - fetchSubscanList(network)( - "transfers", - "/api/scan/transfers", - mapSubscanTransfer, - mapArgs, - startAt - ), - fetchSubscanList(network)( - "list", - "/api/scan/account/reward_slash", - mapSubscanReward, - mapArgs, - startAt - ), - ]); - const incomingTransfers = transfersOp.filter((t) => t.type === "IN"); - const operations = uniqBy( - [...extrinsicsOp, ...incomingTransfers, ...rewardsOp], - (op) => op.id - ); - operations.sort((a, b) => (b.date as any) - (a.date as any)); - return operations; -}; - -const mapSubscanValidator = (validator, isElected): PolkadotValidator => { - return { - address: validator.stash_account_display.address, - identity: validator.stash_account_display.display, - nominatorsCount: validator.count_nominators, - rewardPoints: validator.reward_point, - commission: new BigNumber(validator.validator_prefs_value).dividedBy( - SUBSCAN_VALIDATOR_COMISSION_RATIO - ), - totalBonded: new BigNumber(validator.bonded_nominators).plus( - validator.bonded_owner - ), - selfBonded: new BigNumber(validator.bonded_owner), - isElected, - isOversubscribed: - validator.count_nominators >= SUBSCAN_VALIDATOR_OVERSUBSCRIBED, - }; -}; - -const fetchSubscanValidators = (network: NetworkRequestCall) => async (isElected) => { - // Cannot fetch both at the same time through subscan - const url = isElected - ? "/api/scan/staking/validators" - : "/api/scan/staking/waiting"; - - try { - const { data } = await network({ - method: "POST", - url: `${getBaseApiUrl()}${url}`, - data: {}, - }); - - if (data.code !== 0) { - throw new Error(`SUBSCAN: ${data.message} - code ${data.code}`); - } - - const validators = data.data.list.map((v) => - mapSubscanValidator(v, isElected) - ); - return validators; - } catch (e) { - console.error(e); - throw e; - } -}; - -/** - * List all validators for the current era, and their exposure. - */ -export const getValidators = (network: NetworkRequestCall) => async (stashes: string | string[] = "elected") => { - if (stashes === "elected") { - return fetchSubscanValidators(network)(true); - } else if (stashes === "waiting") { - return fetchSubscanValidators(network)(false); - } else if (stashes === "all" || Array.isArray(stashes)) { - const [elected, waiting] = await Promise.all([ - fetchSubscanValidators(network)(true), - fetchSubscanValidators(network)(false), - ]); - - if (Array.isArray(stashes)) { - return [...elected, ...waiting].filter((v) => - stashes.includes(v.address) - ); - } - - return [...elected, ...waiting]; - } - - return []; -}; diff --git a/libs/coin-polkadot/src/api/websocket.ts b/libs/coin-polkadot/src/api/websocket.ts deleted file mode 100644 index e00f8741807..00000000000 --- a/libs/coin-polkadot/src/api/websocket.ts +++ /dev/null @@ -1,535 +0,0 @@ -/* -THIS FILE IS UNUSED AND PROVIDED AS EXAMPLE FOR USING POLKADOT'S WEBSOCKET API DIRECTLY - -POLKADOT.JS API VERSION 2.5.1 -*/ - -/* eslint-disable */ - -/* istanbul ignore file */ -import uniq from "lodash/uniq"; -import compact from "lodash/compact"; -import { BigNumber } from "bignumber.js"; -import { getEnv } from "@ledgerhq/live-env"; -//@ts-expect-error -import { WsProvider, ApiPromise } from "@polkadot/api"; -import { u8aToString } from "@polkadot/util"; -import { AccountId, Registration } from "@polkadot/types/interfaces"; -import { Data, Option } from "@polkadot/types"; -import type { ITuple } from "@polkadot/types/types"; -import type { PolkadotValidator, PolkadotStakingProgress } from "../types"; -type AsyncApiFunction = (api: typeof ApiPromise) => Promise; -const VALIDATOR_COMISSION_RATIO = 1000000000; - -const getWsUrl = () => getEnv("API_POLKADOT_NODE"); - -const WEBSOCKET_DEBOUNCE_DELAY = 30000; -let api; -let pendingQueries: Array> = []; -let apiDisconnectTimeout; - -/** - * Connects to Substrate Node, executes calls then disconnects - * - * @param {*} execute - the calls to execute on api - */ -async function withApi(execute: AsyncApiFunction): Promise { - // If client is instanciated already, ensure it is connected & ready - if (api) { - try { - await api.isReadyOrError; - } catch (err) { - // definitely not connected... - api = null; - pendingQueries = []; - } - } - - if (!api) { - const wsProvider = new WsProvider( - getWsUrl(), - /* autoConnectMs = */ - false - ); - wsProvider.connect(); - api = await new ApiPromise({ - provider: wsProvider, - }).isReadyOrError; - } - - cancelDebouncedDisconnect(); - - try { - const query = execute(api); - pendingQueries.push(query.catch((err) => err)); - const res = await query; - return res; - } finally { - debouncedDisconnect(); - } -} - -/** - * Disconnects Websocket API client after all pending queries are flushed. - */ -export const disconnect = async () => { - cancelDebouncedDisconnect(); - - if (api) { - const disconnecting = api; - const pending = pendingQueries; - api = undefined; - pendingQueries = []; - await Promise.all(pending); - await disconnecting.disconnect(); - } -}; - -const cancelDebouncedDisconnect = () => { - if (apiDisconnectTimeout) { - clearTimeout(apiDisconnectTimeout); - apiDisconnectTimeout = null; - } -}; - -/** - * Disconnects Websocket client after a delay. - */ -const debouncedDisconnect = () => { - cancelDebouncedDisconnect(); - apiDisconnectTimeout = setTimeout(disconnect, WEBSOCKET_DEBOUNCE_DELAY); -}; - -/** - * Returns true if ElectionStatus is Close. If ElectionStatus is Open, some features must be disabled. - */ -export const isElectionClosed = async (): Promise => - withApi(async (api: typeof ApiPromise) => { - const status = await api.query.staking.eraElectionStatus(); - const res = status.isClose; - return !!res; - }); - -/** - * Returns true if the address is a new account with no balance - * - * @param {*} addr - */ -export const isNewAccount = async (address: string): Promise => - withApi(async (api: typeof ApiPromise) => { - const { - nonce, - data: { free }, - } = await api.query.system.account(address); - return ( - new BigNumber(0).isEqualTo(nonce) && new BigNumber(0).isEqualTo(free) - ); - }); - -/** - * Returns true if the address is a new account with no balance - * - * @param {*} addr - */ -export const isControllerAddress = async (address: string): Promise => - withApi(async (api: typeof ApiPromise) => { - const ledgetOpt = await api.query.staking.ledger(address); - return ledgetOpt.isSome; - }); - -/** - * Get all validators addresses to check for validity. - */ -const getValidatorsStashesAddresses = async (): Promise => - withApi(async (api: typeof ApiPromise) => { - const list = await api.derive.staking.stashes(); - return list.map((v) => v.toString()); - }); - -/** - * Returns all addresses that are not validators - */ -export const verifyValidatorAddresses = async ( - validators: string[] -): Promise => { - const allValidators = await getValidatorsStashesAddresses(); - return validators.filter((v) => !allValidators.includes(v)); -}; - -/** - * Get all account-related data - * - * @param {*} addr - */ -export const getAccount = async (addr: string) => - withApi(async () => { - const balances = await getBalances(addr); - const stakingInfo = await getStakingInfo(addr); - const nominations = await getNominations(addr); - return { ...balances, ...stakingInfo, nominations }; - }); - -/** - * Returns all the balances for an account - * - * @param {*} addr - the account address - */ -export const getBalances = async (addr: string) => - withApi(async (api: typeof ApiPromise) => { - const [finalizedHash, balances] = await Promise.all([ - api.rpc.chain.getFinalizedHead(), - api.derive.balances.all(addr), - ]); - const { number } = await api.rpc.chain.getHeader(finalizedHash); - return { - blockHeight: number.toNumber(), - balance: new BigNumber(balances.freeBalance), - spendableBalance: new BigNumber(balances.availableBalance), - nonce: balances.accountNonce.toNumber(), - lockedBalance: new BigNumber(balances.lockedBalance), - }; - }); - -/** - * Returns all staking-related data for an account - * - * @param {*} addr - */ -export const getStakingInfo = async (addr: string) => - withApi(async (api: typeof ApiPromise) => { - const [controlledLedgerOpt, bonded] = await Promise.all([ - api.query.staking.ledger(addr), - api.query.staking.bonded(addr), - ]); - // NOTE: controlledLedgerOpt is not the current stash ledger... - const stash = controlledLedgerOpt.isSome - ? controlledLedgerOpt.unwrap().stash.toString() - : null; - const controller = bonded.isSome ? bonded.unwrap().toString() : null; - - // If account is not a stash, no need to fetch corresponding ledger - if (!controller) { - return { - controller: null, - stash: stash || null, - unlockedBalance: new BigNumber(0), - unlockingBalance: new BigNumber(0), - unlockings: [], - }; - } - - const [activeOpt, ledgerOpt] = await Promise.all([ - api.query.staking.activeEra(), - api.query.staking.ledger(controller), - ]); - const now = new Date(); - const { index, start } = activeOpt.unwrapOrDefault(); - const activeEraIndex = index.toNumber(); - const activeEraStart = start.unwrap().toNumber(); - const blockTime = api.consts.babe.expectedBlockTime; // 6000 ms - - const epochDuration = api.consts.babe.epochDuration; // 2400 blocks - - const eraLength = api.consts.staking.sessionsPerEra // 6 sessions - .mul(epochDuration) - .mul(blockTime) - .toNumber(); - const ledger = ledgerOpt.isSome ? ledgerOpt.unwrap() : null; - const unlockings = ledger - ? ledger.unlocking.map((lock) => ({ - amount: new BigNumber(lock.value), - completionDate: new Date( - activeEraStart + (lock.era - activeEraIndex) * eraLength - ), // This is an estimation of the date of completion, since it depends on block validation speed - })) - : []; - const unlocked = unlockings.filter((lock) => lock.completionDate <= now); - const unlockingBalance = unlockings.reduce( - (sum, lock) => sum.plus(lock.amount), - new BigNumber(0) - ); - const unlockedBalance = unlocked.reduce( - (sum, lock) => sum.plus(lock.amount), - new BigNumber(0) - ); - return { - controller, - stash, - unlockedBalance, - unlockingBalance, - unlockings, - }; - }); - -/** - * Returns nominations for an account including validator address, status and associated stake. - * - * @param {*} addr - */ -export const getNominations = async (addr: string) => - withApi(async (api: typeof ApiPromise) => { - const [{ activeEra }, nominationsOpt] = await Promise.all([ - api.derive.session.indexes(), - api.query.staking.nominators(addr), - ]); - if (nominationsOpt.isNone) return []; - const targets = nominationsOpt.unwrap().targets; - const [exposures, stashes] = await Promise.all([ - api.query.staking.erasStakers.multi( - targets.map((target) => [activeEra, target]) - ), - api.derive.staking.stashes(), - ]); - const allStashes = stashes.map((stash) => stash.toString()); - return targets.map((target, index) => { - const exposure = exposures[index]; - const individualExposure = exposure.others.find( - (o) => o.who.toString() === addr - ); - const value = individualExposure - ? new BigNumber(individualExposure.value) - : new BigNumber(0); - const status = exposure.others.length - ? individualExposure - ? "active" - : "inactive" - : allStashes.includes(target.toString()) - ? "waiting" - : null; - return { - address: target.toString(), - value, - status, - }; - }); - }); - -/** - * Returns all the params from the chain to build an extrinsic (a transaction on Substrate) - */ -export const getTransactionParams = async () => - withApi(async (api: typeof ApiPromise) => { - const chainName = await api.rpc.system.chain(); - const blockHash = await api.rpc.chain.getFinalizedHead(); - const genesisHash = await api.rpc.chain.getBlockHash(0); - const { number } = await api.rpc.chain.getHeader(blockHash); - const { specName, specVersion, transactionVersion } = - await api.rpc.state.getRuntimeVersion(blockHash); - return { - blockHash, - blockNumber: number, - genesisHash, - chainName: chainName.toString(), - specName: specName.toString(), - specVersion, - transactionVersion, - }; - }); - -/** - * Broadcast the transaction to the substrate node - * - * @param {string} extrinsic - the encoded extrinsic to send - */ -export const submitExtrinsic = async (extrinsic: string) => - withApi(async (api: typeof ApiPromise) => { - const tx = api.tx(extrinsic); - const hash = await api.rpc.author.submitExtrinsic(tx); - return hash; - }); - -/** - * Retrieve the transaction fees and weights - * Note: fees on Substrate are not set by the signer, but directly by the blockchain runtime. - * - * @param {string} extrinsic - the encoded extrinsic to send with a fake signing - */ -export const paymentInfo = async (extrinsic: string) => - withApi(async (api: typeof ApiPromise) => { - const info = await api.rpc.payment.queryInfo(extrinsic); - return info; - }); - -/** - * Fetch all reward points for validators for current era - * - * @returns Map - */ -export const fetchRewardPoints = async () => - withApi(async (api: typeof ApiPromise) => { - const activeOpt = await api.query.staking.activeEra(); - const { index: activeEra } = activeOpt.unwrapOrDefault(); - const { individual } = await api.query.staking.erasRewardPoints(activeEra); - // recast BTreeMap to Map because strict equality does not work - const rewards = new Map( - [...individual.entries()].map(([k, v]) => [ - k.toString(), - new BigNumber(v.toString()), - ]) - ); - return rewards; - }); - -/** - * @source https://github.com/polkadot-js/api/blob/master/packages/api-derive/src/accounts/info.ts - */ -function dataAsString(data: Data): string { - return data.isRaw - ? u8aToString(data.asRaw.toU8a(true)).trim() - : data.isNone - ? "" - : data.toHex(); -} - -/** - * Fetch identity name of multiple addresses. - * Get parent identity if any, and concatenate parent name with child name. - * - * @param {string[]} addresses - */ -export const fetchIdentities = async (addresses: string[]) => - withApi(async (api: typeof ApiPromise) => { - const superOfOpts = await api.query.identity.superOf.multi< - Option> - >(addresses); - const withParent = superOfOpts.map((superOfOpt) => - superOfOpt?.isSome ? superOfOpt?.unwrap() : undefined - ); - const parentAddresses = uniq( - compact(withParent.map((superOf) => superOf && superOf[0].toString())) - ); - const [identities, parentIdentities] = await Promise.all([ - api.query.identity.identityOf.multi>(addresses), - api.query.identity.identityOf.multi>( - parentAddresses - ), - ]); - const map = new Map( - addresses.map((addr, index) => { - const indexOfParent = withParent[index] - ? parentAddresses.indexOf(withParent[index][0].toString()) - : -1; - const identityOpt = - indexOfParent > -1 - ? parentIdentities[indexOfParent] - : identities[index]; - - if (identityOpt.isNone) { - return [addr, ""]; - } - - const { - info: { display }, - } = identityOpt.unwrap(); - const name = withParent[index] - ? `${dataAsString(display)} / ${dataAsString(withParent[index][1])}` - : dataAsString(display); - return [addr, name]; - }) - ); - return map; - }); - -/** - * Transforms each validator into an internal Validator type. - * @param {*} rewards - map of addres sand corresponding reward - * @param {*} identities - map of address and corresponding identity - * @param {*} elected - list of elected validator addresses - * @param {*} maxNominators - constant for oversubscribed validators - * @param {*} validator - the validator details to transform. - */ -const mapValidator = ( - rewards, - identities, - elected, - maxNominators, - validator -): PolkadotValidator => { - const address = validator.accountId.toString(); - return { - address: address, - identity: identities.get(address) || "", - nominatorsCount: validator.exposure.others.length, - rewardPoints: rewards.get(address) || null, - commission: new BigNumber(validator.validatorPrefs.commission).dividedBy( - VALIDATOR_COMISSION_RATIO - ), - totalBonded: new BigNumber(validator.exposure.total), - selfBonded: new BigNumber(validator.exposure.own), - isElected: elected.includes(address), - isOversubscribed: validator.exposure.others.length >= maxNominators, - }; -}; - -/** - * List all validators for the current era, and their exposure, and identity. - */ -export const getValidators = async ( - stashes: string | string[] = "elected" -): Promise => - withApi(async (api: typeof ApiPromise) => { - const [allStashes, elected] = await Promise.all([ - api.derive.staking.stashes(), - api.query.session.validators(), - ]); - let stashIds; - const allIds = allStashes.map((s) => s.toString()); - const electedIds = elected.map((s) => s.toString()); - - if (Array.isArray(stashes)) { - stashIds = allIds.filter((s) => stashes.includes(s)); - } else if (stashes === "elected") { - stashIds = electedIds; - } else { - const waitingIds = allIds.filter((v) => !electedIds.includes(v)); - - if (stashes === "waiting") { - stashIds = waitingIds; - } else { - // Keep elected in first positions - stashIds = [...electedIds, ...waitingIds]; - } - } - - const [validators, rewards, identities] = await Promise.all([ - api.derive.staking.accounts(stashIds), - fetchRewardPoints(), - fetchIdentities(stashIds), - ]); - return validators.map( - mapValidator.bind( - null, - rewards, - identities, - electedIds, - api.consts.staking.maxNominatorRewardedPerValidator - ) - ); - }); - -/** - * Get Active Era progress - */ -export const getStakingProgress = async (): Promise => - withApi(async (api: typeof ApiPromise) => { - const [activeEraOpt, status] = await Promise.all([ - api.query.staking.activeEra(), - api.query.staking.eraElectionStatus(), - ]); - const { index: activeEra } = activeEraOpt.unwrapOrDefault(); - return { - activeEra: activeEra.toNumber(), - electionClosed: !!status.isClose, - }; - }); - -/** - * Return TypeRegistry and decorated extrinsics from client - */ -export const getRegistry = async () => - withApi(async (api: typeof ApiPromise) => { - return { - registry: api.registry, - extrinsics: api.tx, - }; - }); diff --git a/libs/coin-polkadot/src/bridge/createTransaction.ts b/libs/coin-polkadot/src/bridge/createTransaction.ts new file mode 100644 index 00000000000..66174ca0345 --- /dev/null +++ b/libs/coin-polkadot/src/bridge/createTransaction.ts @@ -0,0 +1,3 @@ +import { createTransaction } from "../logic"; + +export default createTransaction; diff --git a/libs/coin-polkadot/src/deviceTransactionConfig.ts b/libs/coin-polkadot/src/bridge/deviceTransactionConfig.ts similarity index 98% rename from libs/coin-polkadot/src/deviceTransactionConfig.ts rename to libs/coin-polkadot/src/bridge/deviceTransactionConfig.ts index dfab6e0bf0b..e7edd7e8c06 100644 --- a/libs/coin-polkadot/src/deviceTransactionConfig.ts +++ b/libs/coin-polkadot/src/bridge/deviceTransactionConfig.ts @@ -1,12 +1,12 @@ import type { AccountLike, Account } from "@ledgerhq/types-live"; -import type { PolkadotAccount, Transaction, TransactionStatus } from "./types"; +import type { PolkadotAccount, Transaction, TransactionStatus } from "../types"; import type { CommonDeviceTransactionField } from "@ledgerhq/coin-framework/transaction/common"; import { getMainAccount } from "@ledgerhq/coin-framework/account/index"; import { formatCurrencyUnit, getCryptoCurrencyById, } from "@ledgerhq/coin-framework/currencies/index"; -import { isStash } from "./logic"; +import { isStash } from "../logic"; const currency = getCryptoCurrencyById("polkadot"); export type ExtraDeviceTransactionField = { type: "polkadot.validators"; diff --git a/libs/coin-polkadot/src/account.ts b/libs/coin-polkadot/src/bridge/formatters.ts similarity index 61% rename from libs/coin-polkadot/src/account.ts rename to libs/coin-polkadot/src/bridge/formatters.ts index f7211f5cd22..001ffef9c59 100644 --- a/libs/coin-polkadot/src/account.ts +++ b/libs/coin-polkadot/src/bridge/formatters.ts @@ -1,15 +1,8 @@ import invariant from "invariant"; -import { BigNumber } from "bignumber.js"; import type { Operation } from "@ledgerhq/types-live"; import { getAccountUnit } from "@ledgerhq/coin-framework/account/index"; import { formatCurrencyUnit } from "@ledgerhq/coin-framework/currencies/index"; -import { - PolkadotAccount, - PolkadotOperation, - PolkadotOperationExtra, - PolkadotOperationExtraRaw, - PolkadotResources, -} from "./types"; +import { PolkadotAccount, PolkadotOperation, PolkadotResources } from "../types"; import type { Unit } from "@ledgerhq/types-cryptoassets"; function formatOperationSpecifics(op: Operation, unit: Unit | null | undefined): string { @@ -79,61 +72,7 @@ function formatAccountSpecifics(account: PolkadotAccount): string { return str; } -export function fromOperationExtraRaw(extraRaw: PolkadotOperationExtraRaw): PolkadotOperationExtra { - const extra: PolkadotOperationExtra = { - palletMethod: extraRaw.palletMethod, - validatorStash: extraRaw.validatorStash, - validators: extraRaw.validators, - }; - - if (extraRaw.transferAmount) { - extra.transferAmount = new BigNumber(extraRaw.transferAmount); - } - - if (extraRaw.bondedAmount) { - extra.bondedAmount = new BigNumber(extraRaw.bondedAmount); - } - - if (extraRaw.unbondedAmount) { - extra.unbondedAmount = new BigNumber(extraRaw.unbondedAmount); - } - - if (extraRaw.withdrawUnbondedAmount) { - extra.withdrawUnbondedAmount = new BigNumber(extraRaw.withdrawUnbondedAmount); - } - - return extra; -} - -export function toOperationExtraRaw(extra: PolkadotOperationExtra): PolkadotOperationExtraRaw { - const extraRaw: PolkadotOperationExtraRaw = { - palletMethod: extra.palletMethod, - validatorStash: extra.validatorStash, - validators: extra.validators, - }; - - if (extra.transferAmount) { - extraRaw.transferAmount = extra.transferAmount.toString(); - } - - if (extra.bondedAmount) { - extraRaw.bondedAmount = extra.bondedAmount.toString(); - } - - if (extra.unbondedAmount) { - extraRaw.unbondedAmount = extra.unbondedAmount.toString(); - } - - if (extra.withdrawUnbondedAmount) { - extraRaw.withdrawUnbondedAmount = extra.withdrawUnbondedAmount.toString(); - } - - return extraRaw; -} - export default { formatAccountSpecifics, formatOperationSpecifics, - fromOperationExtraRaw, - toOperationExtraRaw, }; diff --git a/libs/coin-polkadot/src/bridge/js.ts b/libs/coin-polkadot/src/bridge/index.ts similarity index 60% rename from libs/coin-polkadot/src/bridge/js.ts rename to libs/coin-polkadot/src/bridge/index.ts index 86edd6ca668..d5f60c2fbc6 100644 --- a/libs/coin-polkadot/src/bridge/js.ts +++ b/libs/coin-polkadot/src/bridge/index.ts @@ -6,44 +6,29 @@ import { makeSync, } from "@ledgerhq/coin-framework/bridge/jsHelpers"; import type { NetworkRequestCall } from "@ledgerhq/coin-framework/network"; -import { patchOperationWithHash } from "@ledgerhq/coin-framework/operation"; import { LRUCacheFn } from "@ledgerhq/coin-framework/cache"; import { SignerContext } from "@ledgerhq/coin-framework/signer"; -import type { - AccountBridge, - CurrencyBridge, - Operation, - SignedOperation, -} from "@ledgerhq/types-live"; -import { PolkadotAPI } from "../api"; -import resolver from "../hw-getAddress"; -import createTransaction from "../js-createTransaction"; -import estimateMaxSpendable from "../js-estimateMaxSpendable"; -import getTransactionStatus from "../js-getTransactionStatus"; -import prepareTransaction from "../js-prepareTransaction"; -import buildSignOperation from "../js-signOperation"; -import { makeGetAccountShape } from "../js-synchronisation"; -import { loadPolkadotCrypto } from "../polkadot-crypto"; -import { assignFromAccountRaw, assignToAccountRaw } from "../serialization"; -import { getPreloadStrategy, hydrate, preload } from "../preload"; -import type { Transaction } from "../types"; -import { PolkadotAddress, PolkadotSignature, PolkadotSigner } from "../signer"; - -/** - * Broadcast the signed transaction - * @param {signature: string, operation: string} signedOperation - */ -const broadcast = - (polkadotAPI: PolkadotAPI) => - async ({ - signedOperation: { signature, operation }, - }: { - signedOperation: SignedOperation; - }): Promise => { - await loadPolkadotCrypto(); - const hash = await polkadotAPI.submitExtrinsic(signature); - return patchOperationWithHash(operation, hash); - }; +import type { AccountBridge, CurrencyBridge } from "@ledgerhq/types-live"; +import { PolkadotAPI } from "../network"; +import signerGetAddress from "../signer"; +import createTransaction from "./createTransaction"; +import { + estimateMaxSpendable, + getTransactionStatus, + broadcast, + makeGetAccountShape, +} from "../logic"; +import prepareTransaction from "./prepareTransaction"; +import buildSignOperation from "./signOperation"; +import { + assignFromAccountRaw, + assignToAccountRaw, + fromOperationExtraRaw, + toOperationExtraRaw, +} from "./serialization"; +import { getPreloadStrategy, hydrate, preload } from "./preload"; +import type { PolkadotAddress, PolkadotSignature, Transaction } from "../types"; +import { PolkadotSigner } from "../types"; export function buildCurrencyBridge( signerContext: SignerContext, @@ -51,7 +36,7 @@ export function buildCurrencyBridge( cacheFn: LRUCacheFn, ): CurrencyBridge { const polkadotAPI = new PolkadotAPI(network, cacheFn); - const getAddress = resolver(signerContext); + const getAddress = signerGetAddress(signerContext); const getAccountShape = makeGetAccountShape(polkadotAPI); const scanAccounts = makeScanAccounts({ @@ -73,7 +58,7 @@ export function buildAccountBridge( cacheFn: LRUCacheFn, ): AccountBridge { const polkadotAPI = new PolkadotAPI(network, cacheFn); - const getAddress = resolver(signerContext); + const getAddress = signerGetAddress(signerContext); const receive = makeAccountBridgeReceive(getAddressWrapper(getAddress)); const signOperation = buildSignOperation(signerContext, polkadotAPI); @@ -92,6 +77,8 @@ export function buildAccountBridge( broadcast: broadcast(polkadotAPI), assignFromAccountRaw, assignToAccountRaw, + fromOperationExtraRaw, + toOperationExtraRaw, }; } diff --git a/libs/coin-polkadot/src/preload.ts b/libs/coin-polkadot/src/bridge/preload.ts similarity index 79% rename from libs/coin-polkadot/src/preload.ts rename to libs/coin-polkadot/src/bridge/preload.ts index 499ef1acc56..0650467ddd8 100644 --- a/libs/coin-polkadot/src/preload.ts +++ b/libs/coin-polkadot/src/bridge/preload.ts @@ -1,19 +1,13 @@ /* eslint-disable no-prototype-builtins */ import { BigNumber } from "bignumber.js"; -import { Observable, Subject } from "rxjs"; import { log } from "@ledgerhq/logs"; -import type { PolkadotPreloadData, PolkadotStakingProgress, PolkadotValidator } from "./types"; -import { loadPolkadotCrypto } from "./polkadot-crypto"; -import { PolkadotAPI } from "./api"; +import type { PolkadotPreloadData, PolkadotStakingProgress, PolkadotValidator } from "../types"; +import { PolkadotAPI } from "../network"; +import { loadPolkadotCrypto } from "../logic/polkadot-crypto"; //FIXME: Polkadot SDK should not be used in bridge +import { getCurrentPolkadotPreloadData, setPolkadotPreloadData } from "../logic"; const PRELOAD_MAX_AGE = 60 * 1000; -let currentPolkadotPreloadedData: PolkadotPreloadData = { - validators: [], - staking: undefined, - minimumBondBalance: "0", -}; - function fromHydrateValidator(validatorRaw: Record): PolkadotValidator { return { address: validatorRaw.address, @@ -69,22 +63,6 @@ function fromHydratePreloadData(data: any): PolkadotPreloadData { }; } -const updates = new Subject(); - -export function getCurrentPolkadotPreloadData(): PolkadotPreloadData { - return currentPolkadotPreloadedData; -} - -export function setPolkadotPreloadData(data: PolkadotPreloadData) { - if (data === currentPolkadotPreloadedData) return; - currentPolkadotPreloadedData = data; - updates.next(data); -} - -export function getPolkadotPreloadDataUpdates(): Observable { - return updates.asObservable(); -} - /** * load max cache time for the validators */ @@ -102,12 +80,11 @@ const shouldRefreshValidators = ( export const preload = (polkadotAPI: PolkadotAPI) => async (): Promise => { await loadPolkadotCrypto(); await polkadotAPI.getRegistry(); // ensure registry is already in cache. - const minimumBondBalance = await polkadotAPI.getMinimumBondBalance(); - const minimumBondBalanceStr = minimumBondBalance.toString(); + const minimumBondBalance = await polkadotAPI.getMinimumBondBalance().toString(); const currentStakingProgress = await polkadotAPI.getStakingProgress(); const { validators: previousValidators, staking: previousStakingProgress } = - currentPolkadotPreloadedData; + getCurrentPolkadotPreloadData(); let validators = previousValidators; if ( @@ -129,7 +106,7 @@ export const preload = (polkadotAPI: PolkadotAPI) => async (): Promise (!a || !b ? a === b : a.eq(b)); @@ -13,7 +12,6 @@ const sameFees = (a: BigNumber, b?: BigNumber | null) => (!a || !b ? a === b : a */ const prepareTransaction = (polkadotAPI: PolkadotAPI) => async (a: PolkadotAccount, t: Transaction) => { - await loadPolkadotCrypto(); let fees = t.fees; fees = await getEstimatedFees(polkadotAPI)({ a, diff --git a/libs/coin-polkadot/src/bridge/serialization.ts b/libs/coin-polkadot/src/bridge/serialization.ts new file mode 100644 index 00000000000..dcd17210084 --- /dev/null +++ b/libs/coin-polkadot/src/bridge/serialization.ts @@ -0,0 +1,130 @@ +import { BigNumber } from "bignumber.js"; +import { + type PolkadotResourcesRaw, + type PolkadotResources, + type PolkadotAccount, + type PolkadotAccountRaw, + type PolkadotOperationExtra, + type PolkadotOperationExtraRaw, + isPolkadotOperationExtraRaw, + isPolkadotOperationExtra, +} from "../types"; +import { Account, AccountRaw, OperationExtra, OperationExtraRaw } from "@ledgerhq/types-live"; + +function toPolkadotResourcesRaw(r: PolkadotResources): PolkadotResourcesRaw { + const { nonce, controller, stash } = r; + return { + controller, + stash, + nonce, + lockedBalance: r.lockedBalance.toString(), + unlockedBalance: r.unlockedBalance.toString(), + unlockingBalance: r.unlockingBalance.toString(), + unlockings: r.unlockings?.map(u => ({ + amount: u.amount.toString(), + completionDate: u.completionDate.toISOString(), + })), + nominations: r.nominations?.map(n => ({ + address: n.address, + value: n.value.toString(), + status: n.status, + })), + numSlashingSpans: r.numSlashingSpans, + }; +} +function fromPolkadotResourcesRaw(r: PolkadotResourcesRaw): PolkadotResources { + const { nonce, controller, stash } = r; + return { + controller, + stash, + nonce, + lockedBalance: new BigNumber(r.lockedBalance), + unlockedBalance: new BigNumber(r.unlockedBalance), + unlockingBalance: new BigNumber(r.unlockingBalance), + unlockings: r.unlockings?.map(u => ({ + amount: new BigNumber(u.amount), + completionDate: new Date(u.completionDate), + })), + nominations: r.nominations?.map(n => ({ + address: n.address, + value: new BigNumber(n.value), + status: n.status, + })), + numSlashingSpans: Number(r.numSlashingSpans) || 0, + }; +} + +export function assignToAccountRaw(account: Account, accountRaw: AccountRaw) { + const polkadotAccount = account as PolkadotAccount; + if (polkadotAccount.polkadotResources) { + (accountRaw as PolkadotAccountRaw).polkadotResources = toPolkadotResourcesRaw( + polkadotAccount.polkadotResources, + ); + } +} + +export function assignFromAccountRaw(accountRaw: AccountRaw, account: Account) { + const polkadotResourcesRaw = (accountRaw as PolkadotAccountRaw).polkadotResources; + if (polkadotResourcesRaw) + (account as PolkadotAccount).polkadotResources = fromPolkadotResourcesRaw(polkadotResourcesRaw); +} + +export function fromOperationExtraRaw(extraRaw: OperationExtraRaw): OperationExtra { + if (!isPolkadotOperationExtraRaw(extraRaw)) { + throw Error("Unsupported OperationExtraRaw"); + } + + const extra: PolkadotOperationExtra = { + palletMethod: extraRaw.palletMethod, + validatorStash: extraRaw.validatorStash, + validators: extraRaw.validators, + }; + + if (extraRaw.transferAmount) { + extra.transferAmount = new BigNumber(extraRaw.transferAmount); + } + + if (extraRaw.bondedAmount) { + extra.bondedAmount = new BigNumber(extraRaw.bondedAmount); + } + + if (extraRaw.unbondedAmount) { + extra.unbondedAmount = new BigNumber(extraRaw.unbondedAmount); + } + + if (extraRaw.withdrawUnbondedAmount) { + extra.withdrawUnbondedAmount = new BigNumber(extraRaw.withdrawUnbondedAmount); + } + + return extra; +} + +export function toOperationExtraRaw(extra: OperationExtra): OperationExtraRaw { + if (!isPolkadotOperationExtra(extra)) { + throw Error("Unsupported OperationExtra"); + } + + const extraRaw: PolkadotOperationExtraRaw = { + palletMethod: extra.palletMethod, + validatorStash: extra.validatorStash, + validators: extra.validators, + }; + + if (extra.transferAmount) { + extraRaw.transferAmount = extra.transferAmount.toString(); + } + + if (extra.bondedAmount) { + extraRaw.bondedAmount = extra.bondedAmount.toString(); + } + + if (extra.unbondedAmount) { + extraRaw.unbondedAmount = extra.unbondedAmount.toString(); + } + + if (extra.withdrawUnbondedAmount) { + extraRaw.withdrawUnbondedAmount = extra.withdrawUnbondedAmount.toString(); + } + + return extraRaw; +} diff --git a/libs/coin-polkadot/src/js-signOperation.ts b/libs/coin-polkadot/src/bridge/signOperation.ts similarity index 76% rename from libs/coin-polkadot/src/js-signOperation.ts rename to libs/coin-polkadot/src/bridge/signOperation.ts index 0438a863e3c..48d0e1a2044 100644 --- a/libs/coin-polkadot/src/js-signOperation.ts +++ b/libs/coin-polkadot/src/bridge/signOperation.ts @@ -1,14 +1,15 @@ import { BigNumber } from "bignumber.js"; import { Observable } from "rxjs"; -import { TypeRegistry } from "@polkadot/types"; -import { u8aConcat } from "@polkadot/util"; import { FeeNotLoaded } from "@ledgerhq/errors"; import type { PolkadotAccount, + PolkadotAddress, PolkadotOperation, PolkadotOperationExtra, + PolkadotSignature, + PolkadotSigner, Transaction, -} from "./types"; +} from "../types"; import type { Account, DeviceId, @@ -18,10 +19,8 @@ import type { } from "@ledgerhq/types-live"; import { encodeOperationId } from "@ledgerhq/coin-framework/operation"; import { SignerContext } from "@ledgerhq/coin-framework/signer"; -import { buildTransaction } from "./js-buildTransaction"; -import { calculateAmount, getNonce, isFirstBond } from "./logic"; -import { PolkadotAPI } from "./api"; -import { PolkadotAddress, PolkadotSignature, PolkadotSigner } from "./signer"; +import { PolkadotAPI } from "../network"; +import { buildTransaction, calculateAmount, getNonce, isFirstBond, signExtrinsic } from "../logic"; const MODE_TO_TYPE = { send: "OUT", @@ -113,41 +112,6 @@ const buildOptimisticOperation = ( return operation; }; -/** - * Serialize a signed transaction in a format that can be submitted over the - * Node RPC Interface from the signing payload and signature produced by the - * remote signer. - * - * @param unsigned - The JSON representing the unsigned transaction. - * @param signature - Signature of the signing payload produced by the remote signer. - * @param registry - Registry used for constructing the payload. - */ -export const signExtrinsic = async ( - unsigned: Record, - signature: any, - registry: TypeRegistry, -): Promise => { - const extrinsic = registry.createType("Extrinsic", unsigned, { - version: unsigned.version, - }); - extrinsic.addSignature(unsigned.address, signature, unsigned as any); - return extrinsic.toHex(); -}; - -/** - * Sign Extrinsic with a fake signature (for fees estimation). - * - * @param unsigned - The JSON representing the unsigned transaction. - * @param registry - Registry used for constructing the payload. - */ -export const fakeSignExtrinsic = async ( - unsigned: Record, - registry: TypeRegistry, -): Promise => { - const fakeSignature = u8aConcat(new Uint8Array([1]), new Uint8Array(64).fill(0x42)); - return signExtrinsic(unsigned, fakeSignature, registry); -}; - /** * Sign Transaction with Ledger hardware */ diff --git a/libs/coin-polkadot/src/transaction.ts b/libs/coin-polkadot/src/bridge/transaction.ts similarity index 96% rename from libs/coin-polkadot/src/transaction.ts rename to libs/coin-polkadot/src/bridge/transaction.ts index 2a8c05f756c..da900241357 100644 --- a/libs/coin-polkadot/src/transaction.ts +++ b/libs/coin-polkadot/src/bridge/transaction.ts @@ -1,4 +1,4 @@ -import type { Transaction, TransactionRaw } from "./types"; +import type { Transaction, TransactionRaw } from "../types"; import { BigNumber } from "bignumber.js"; import { formatTransactionStatusCommon as formatTransactionStatus, @@ -10,6 +10,7 @@ import { import type { Account } from "@ledgerhq/types-live"; import { getAccountUnit } from "@ledgerhq/coin-framework/account/index"; import { formatCurrencyUnit } from "@ledgerhq/coin-framework/currencies/index"; + export const formatTransaction = ( { mode, amount, recipient, validators, useAllAmount }: Transaction, account: Account, @@ -25,6 +26,7 @@ ${mode.toUpperCase()} ${ disableRounding: true, }) }${recipient ? `\nTO ${recipient}` : ""}${!validators ? "" : validators.join("\n")}`; + export const fromTransactionRaw = (tr: TransactionRaw): Transaction => { const common = fromTransactionCommonRaw(tr); return { @@ -38,6 +40,7 @@ export const fromTransactionRaw = (tr: TransactionRaw): Transaction => { numSlashingSpans: tr.numSlashingSpans, }; }; + export const toTransactionRaw = (t: Transaction): TransactionRaw => { const common = toTransactionCommonRaw(t); return { diff --git a/libs/coin-polkadot/src/address.ts b/libs/coin-polkadot/src/common/address.ts similarity index 100% rename from libs/coin-polkadot/src/address.ts rename to libs/coin-polkadot/src/common/address.ts diff --git a/libs/coin-polkadot/src/common/index.ts b/libs/coin-polkadot/src/common/index.ts new file mode 100644 index 00000000000..ce667bbedc1 --- /dev/null +++ b/libs/coin-polkadot/src/common/index.ts @@ -0,0 +1 @@ +export * from "./address"; diff --git a/libs/coin-polkadot/src/index.ts b/libs/coin-polkadot/src/index.ts new file mode 100644 index 00000000000..35f3833f41c --- /dev/null +++ b/libs/coin-polkadot/src/index.ts @@ -0,0 +1,4 @@ +export * from "./test"; +export * from "./types"; + +export { buildCurrencyBridge, buildAccountBridge, createBridges } from "./bridge/index"; diff --git a/libs/coin-polkadot/src/logic.test.ts b/libs/coin-polkadot/src/logic.test.ts deleted file mode 100644 index 3ff0abbd5b5..00000000000 --- a/libs/coin-polkadot/src/logic.test.ts +++ /dev/null @@ -1,50 +0,0 @@ -import BigNumber from "bignumber.js"; -import { isController } from "./logic"; -import { PolkadotAccount } from "./types"; -import { getCryptoCurrencyById } from "@ledgerhq/cryptoassets/currencies"; - -const polkadotAccount: PolkadotAccount = { - type: "Account", - id: "", - seedIdentifier: "", - derivationMode: "", - freshAddress: "", - freshAddressPath: "", - name: "", - starred: false, - index: 0, - freshAddresses: [], - spendableBalance: new BigNumber(0), - balance: new BigNumber(0), - used: true, - creationDate: new Date(), - blockHeight: 0, - currency: getCryptoCurrencyById("polkadot"), - unit: { name: "DOT", code: "DOT", magnitude: 16 }, - operations: [], - pendingOperations: [], - operationsCount: 0, - lastSyncDate: new Date(), - balanceHistoryCache: {} as any, - swapHistory: [], - polkadotResources: {} as any, -}; - -describe("isController", () => { - it("returns false when stash is not defined", () => { - const accountIsController = isController(polkadotAccount); - expect(accountIsController).toBe(false); - }); - - it("returns false when stash account is undefined or null", () => { - polkadotAccount.polkadotResources.stash = null; - const accountIsController = isController(polkadotAccount); - expect(accountIsController).toBe(false); - }); - - it("returns true when stash account is defined", () => { - polkadotAccount.polkadotResources.stash = "stashAddress"; - const accountIsController = isController(polkadotAccount); - expect(accountIsController).toBe(true); - }); -}); diff --git a/libs/coin-polkadot/src/logic/broadcast.ts b/libs/coin-polkadot/src/logic/broadcast.ts new file mode 100644 index 00000000000..b7f07917a6a --- /dev/null +++ b/libs/coin-polkadot/src/logic/broadcast.ts @@ -0,0 +1,22 @@ +import type { Operation, SignedOperation } from "@ledgerhq/types-live"; +import { patchOperationWithHash } from "@ledgerhq/coin-framework/operation"; +import { PolkadotAPI } from "../network"; +import { loadPolkadotCrypto } from "./polkadot-crypto"; + +/** + * Broadcast the signed transaction + * @param {signature: string, operation: string} signedOperation + */ +const broadcast = + (polkadotAPI: PolkadotAPI) => + async ({ + signedOperation: { signature, operation }, + }: { + signedOperation: SignedOperation; + }): Promise => { + await loadPolkadotCrypto(); + const hash = await polkadotAPI.submitExtrinsic(signature); + return patchOperationWithHash(operation, hash); + }; + +export default broadcast; diff --git a/libs/coin-polkadot/src/js-buildTransaction.test.ts b/libs/coin-polkadot/src/logic/buildTransaction.test.ts similarity index 97% rename from libs/coin-polkadot/src/js-buildTransaction.test.ts rename to libs/coin-polkadot/src/logic/buildTransaction.test.ts index 1f40c2decf1..115d854305b 100644 --- a/libs/coin-polkadot/src/js-buildTransaction.test.ts +++ b/libs/coin-polkadot/src/logic/buildTransaction.test.ts @@ -1,8 +1,8 @@ import BigNumber from "bignumber.js"; import { cryptocurrenciesById } from "@ledgerhq/cryptoassets"; -import { PolkadotAccount, PolkadotOperationMode, Transaction } from "./types"; -import { buildTransaction } from "./js-buildTransaction"; -import { PolkadotAPI } from "./api"; +import { PolkadotAccount, PolkadotOperationMode, Transaction } from "../types"; +import { buildTransaction } from "./buildTransaction"; +import { PolkadotAPI } from "../network"; import { TypeRegistry } from "@polkadot/types"; import { NetworkRequestCall } from "@ledgerhq/coin-framework/network"; import { makeNoCache } from "@ledgerhq/coin-framework/cache"; @@ -24,7 +24,7 @@ const transactionParams = { tip: 8, transactionVersion: 22, }; -jest.mock("./api", () => ({ +jest.mock("../network", () => ({ PolkadotAPI: jest.fn().mockImplementation(() => { return { getRegistry: () => { diff --git a/libs/coin-polkadot/src/js-buildTransaction.ts b/libs/coin-polkadot/src/logic/buildTransaction.ts similarity index 97% rename from libs/coin-polkadot/src/js-buildTransaction.ts rename to libs/coin-polkadot/src/logic/buildTransaction.ts index 1e02a10d000..270e85ec0fc 100644 --- a/libs/coin-polkadot/src/js-buildTransaction.ts +++ b/libs/coin-polkadot/src/logic/buildTransaction.ts @@ -1,8 +1,8 @@ import { stringCamelCase } from "@polkadot/util"; -import type { PolkadotAccount, Transaction } from "./types"; -import { isFirstBond, getNonce } from "./logic"; +import type { PolkadotAccount, Transaction } from "../types"; +import { isFirstBond, getNonce } from "./utils"; import { loadPolkadotCrypto } from "./polkadot-crypto"; -import { PolkadotAPI } from "./api"; +import { PolkadotAPI } from "../network"; const EXTRINSIC_VERSION = 4; // Default values for tx parameters, if the user doesn't specify any const DEFAULTS = { diff --git a/libs/coin-polkadot/src/js-estimateMaxSpendable.ts b/libs/coin-polkadot/src/logic/estimateMaxSpendable.ts similarity index 73% rename from libs/coin-polkadot/src/js-estimateMaxSpendable.ts rename to libs/coin-polkadot/src/logic/estimateMaxSpendable.ts index b7b35f4a4bb..25efac9e583 100644 --- a/libs/coin-polkadot/src/js-estimateMaxSpendable.ts +++ b/libs/coin-polkadot/src/logic/estimateMaxSpendable.ts @@ -1,12 +1,11 @@ import { BigNumber } from "bignumber.js"; import type { AccountLike, Account } from "@ledgerhq/types-live"; import { getMainAccount } from "@ledgerhq/coin-framework/account/index"; -import type { PolkadotAccount, Transaction } from "./types"; -import { calculateAmount } from "./logic"; -import getEstimatedFees from "./js-getFeesForTransaction"; -import createTransaction from "./js-createTransaction"; -import { loadPolkadotCrypto } from "./polkadot-crypto"; -import { PolkadotAPI } from "./api"; +import type { PolkadotAccount, Transaction } from "../types"; +import { calculateAmount } from "./utils"; +import getEstimatedFees from "./getFeesForTransaction"; +import { createTransaction } from "./transaction"; +import { PolkadotAPI } from "../network"; /** * Returns the maximum possible amount for transaction @@ -24,7 +23,6 @@ const estimateMaxSpendable = parentAccount: Account | null | undefined; transaction: Transaction | null | undefined; }): Promise => { - await loadPolkadotCrypto(); const a = getMainAccount(account, parentAccount) as PolkadotAccount; const t = { ...createTransaction(), ...transaction, useAllAmount: true }; const fees = await getEstimatedFees(polkadotAPI)({ diff --git a/libs/coin-polkadot/src/js-getFeesForTransaction.ts b/libs/coin-polkadot/src/logic/getFeesForTransaction.ts similarity index 74% rename from libs/coin-polkadot/src/js-getFeesForTransaction.ts rename to libs/coin-polkadot/src/logic/getFeesForTransaction.ts index 80d01596dd7..79ac0e89eba 100644 --- a/libs/coin-polkadot/src/js-getFeesForTransaction.ts +++ b/libs/coin-polkadot/src/logic/getFeesForTransaction.ts @@ -1,10 +1,11 @@ import { BigNumber } from "bignumber.js"; import { getAbandonSeedAddress } from "@ledgerhq/cryptoassets"; -import type { PolkadotAccount, Transaction } from "./types"; -import { calculateAmount } from "./logic"; -import { buildTransaction } from "./js-buildTransaction"; -import { fakeSignExtrinsic } from "./js-signOperation"; -import { PolkadotAPI } from "./api"; +import { loadPolkadotCrypto } from "./polkadot-crypto"; +import type { PolkadotAccount, Transaction } from "../types"; +import { calculateAmount } from "./utils"; +import { buildTransaction } from "./buildTransaction"; +import { fakeSignExtrinsic } from "./signTransaction"; +import { PolkadotAPI } from "../network"; /** * Fetch the transaction fees for a transaction @@ -15,6 +16,8 @@ import { PolkadotAPI } from "./api"; const getEstimatedFees = (polkadotAPI: PolkadotAPI) => async ({ a, t }: { a: PolkadotAccount; t: Transaction }): Promise => { + await loadPolkadotCrypto(); + const transaction = { ...t, recipient: getAbandonSeedAddress(a.currency.id), diff --git a/libs/coin-polkadot/src/js-getTransactionStatus.ts b/libs/coin-polkadot/src/logic/getTransactionStatus.ts similarity index 97% rename from libs/coin-polkadot/src/js-getTransactionStatus.ts rename to libs/coin-polkadot/src/logic/getTransactionStatus.ts index 1f72f4e8133..6ed2064b165 100644 --- a/libs/coin-polkadot/src/js-getTransactionStatus.ts +++ b/libs/coin-polkadot/src/logic/getTransactionStatus.ts @@ -9,7 +9,7 @@ import { FeeNotLoaded, } from "@ledgerhq/errors"; import { formatCurrencyUnit } from "@ledgerhq/coin-framework/currencies/index"; -import type { PolkadotAccount, Transaction, TransactionStatus } from "./types"; +import type { PolkadotAccount, Transaction, TransactionStatus } from "../types"; import { PolkadotUnauthorizedOperation, PolkadotElectionClosed, @@ -22,7 +22,7 @@ import { PolkadotMaxUnbonding, PolkadotValidatorsRequired, PolkadotDoMaxSendInstead, -} from "./errors"; +} from "../types"; import { EXISTENTIAL_DEPOSIT, FEES_SAFETY_BUFFER, @@ -35,11 +35,11 @@ import { getMinimumAmountToBond, getMinimumBalance, EXISTENTIAL_DEPOSIT_RECOMMENDED_MARGIN, -} from "./logic"; -import { isValidAddress } from "./address"; -import { getCurrentPolkadotPreloadData } from "./preload"; +} from "./utils"; +import { isValidAddress } from "../common"; +import { getCurrentPolkadotPreloadData } from "./state"; import { loadPolkadotCrypto } from "./polkadot-crypto"; -import { PolkadotAPI } from "./api"; +import { PolkadotAPI } from "../network"; // Should try to refacto const getSendTransactionStatus = diff --git a/libs/coin-polkadot/src/logic/index.ts b/libs/coin-polkadot/src/logic/index.ts new file mode 100644 index 00000000000..7d36afc5811 --- /dev/null +++ b/libs/coin-polkadot/src/logic/index.ts @@ -0,0 +1,38 @@ +/** + * This directory is about pure coin logic. + * Its goal is to be as much as possible versatile code, + * and therefore can be used with "any Ledger product" + */ + +export { createTransaction } from "./transaction"; +export { + calculateAmount, + canBond, + canUnbond, + canNominate, + getMinimumBalance, + getNonce, + hasExternalController, + hasExternalStash, + hasMinimumBondBalance, + hasPendingOperationType, + isElectionOpen, + isStash, + isFirstBond, + MAX_NOMINATIONS, +} from "./utils"; +export { makeGetAccountShape } from "./synchronisation"; +export { + getCurrentPolkadotPreloadData, + setPolkadotPreloadData, + getPolkadotPreloadDataUpdates, +} from "./state"; +export { buildTransaction } from "./buildTransaction"; +export { signExtrinsic } from "./signTransaction"; + +import broadcast from "./broadcast"; +import estimateMaxSpendable from "./estimateMaxSpendable"; +import getEstimatedFees from "./getFeesForTransaction"; +import getTransactionStatus from "./getTransactionStatus"; + +export { broadcast, estimateMaxSpendable, getEstimatedFees, getTransactionStatus }; diff --git a/libs/coin-polkadot/src/polkadot-crypto.ts b/libs/coin-polkadot/src/logic/polkadot-crypto.ts similarity index 100% rename from libs/coin-polkadot/src/polkadot-crypto.ts rename to libs/coin-polkadot/src/logic/polkadot-crypto.ts diff --git a/libs/coin-polkadot/src/logic/signTransaction.ts b/libs/coin-polkadot/src/logic/signTransaction.ts new file mode 100644 index 00000000000..2f2b30a4590 --- /dev/null +++ b/libs/coin-polkadot/src/logic/signTransaction.ts @@ -0,0 +1,37 @@ +import { TypeRegistry } from "@polkadot/types"; +import { u8aConcat } from "@polkadot/util"; + +/** + * Serialize a signed transaction in a format that can be submitted over the + * Node RPC Interface from the signing payload and signature produced by the + * remote signer. + * + * @param unsigned - The JSON representing the unsigned transaction. + * @param signature - Signature of the signing payload produced by the remote signer. + * @param registry - Registry used for constructing the payload. + */ +export const signExtrinsic = async ( + unsigned: Record, + signature: any, + registry: TypeRegistry, +): Promise => { + const extrinsic = registry.createType("Extrinsic", unsigned, { + version: unsigned.version, + }); + extrinsic.addSignature(unsigned.address, signature, unsigned as any); + return extrinsic.toHex(); +}; + +/** + * Sign Extrinsic with a fake signature (for fees estimation). + * + * @param unsigned - The JSON representing the unsigned transaction. + * @param registry - Registry used for constructing the payload. + */ +export const fakeSignExtrinsic = async ( + unsigned: Record, + registry: TypeRegistry, +): Promise => { + const fakeSignature = u8aConcat(new Uint8Array([1]), new Uint8Array(64).fill(0x42)); + return signExtrinsic(unsigned, fakeSignature, registry); +}; diff --git a/libs/coin-polkadot/src/logic/state.ts b/libs/coin-polkadot/src/logic/state.ts new file mode 100644 index 00000000000..1ae6d841779 --- /dev/null +++ b/libs/coin-polkadot/src/logic/state.ts @@ -0,0 +1,28 @@ +/** + * Data keeping track of current "states" of the module. + */ + +import { Observable, Subject } from "rxjs"; +import { PolkadotPreloadData } from "../types"; + +let currentPolkadotPreloadedData: PolkadotPreloadData = { + validators: [], + staking: undefined, + minimumBondBalance: "0", +}; + +const updates = new Subject(); + +export function getCurrentPolkadotPreloadData(): PolkadotPreloadData { + return currentPolkadotPreloadedData; +} + +export function setPolkadotPreloadData(data: PolkadotPreloadData) { + if (data === currentPolkadotPreloadedData) return; + currentPolkadotPreloadedData = data; + updates.next(data); +} + +export function getPolkadotPreloadDataUpdates(): Observable { + return updates.asObservable(); +} diff --git a/libs/coin-polkadot/src/js-synchronisation.ts b/libs/coin-polkadot/src/logic/synchronisation.ts similarity index 93% rename from libs/coin-polkadot/src/js-synchronisation.ts rename to libs/coin-polkadot/src/logic/synchronisation.ts index 9d676b3bb39..cd3589a31f4 100644 --- a/libs/coin-polkadot/src/js-synchronisation.ts +++ b/libs/coin-polkadot/src/logic/synchronisation.ts @@ -1,15 +1,15 @@ import { encodeAccountId } from "@ledgerhq/coin-framework/account/index"; import type { GetAccountShape } from "@ledgerhq/coin-framework/bridge/jsHelpers"; import { mergeOps } from "@ledgerhq/coin-framework/bridge/jsHelpers"; -import { PolkadotAPI } from "./api"; +import { PolkadotAPI } from "../network"; import { loadPolkadotCrypto } from "./polkadot-crypto"; export function makeGetAccountShape(polkadotAPI: PolkadotAPI): GetAccountShape { return async info => { await loadPolkadotCrypto(); const { address, initialAccount, currency, derivationMode } = info; - const oldOperations = initialAccount?.operations || []; - const startAt = oldOperations.length ? (oldOperations[0].blockHeight || 0) + 1 : 0; + + // Retrieve account info const { blockHeight, balance, @@ -24,6 +24,8 @@ export function makeGetAccountShape(polkadotAPI: PolkadotAPI): GetAccountShape { nominations, numSlashingSpans, } = await polkadotAPI.getAccount(address); + + // Retrieve operations associated const accountId = encodeAccountId({ type: "js", version: "2", @@ -31,8 +33,11 @@ export function makeGetAccountShape(polkadotAPI: PolkadotAPI): GetAccountShape { xpubOrAddress: address, derivationMode, }); + const oldOperations = initialAccount?.operations || []; + const startAt = oldOperations.length ? (oldOperations[0].blockHeight || 0) + 1 : 0; const newOperations = await polkadotAPI.getOperations(accountId, address, startAt); const operations = mergeOps(oldOperations, newOperations); + return { id: accountId, balance, diff --git a/libs/coin-polkadot/src/js-createTransaction.ts b/libs/coin-polkadot/src/logic/transaction.ts similarity index 71% rename from libs/coin-polkadot/src/js-createTransaction.ts rename to libs/coin-polkadot/src/logic/transaction.ts index 1d1b03e34a5..4522bc6f9ba 100644 --- a/libs/coin-polkadot/src/js-createTransaction.ts +++ b/libs/coin-polkadot/src/logic/transaction.ts @@ -1,12 +1,12 @@ import { BigNumber } from "bignumber.js"; -import type { Transaction } from "./types"; +import type { Transaction } from "../types"; /** * Create an empty transaction * * @returns {Transaction} */ -const createTransaction = (): Transaction => ({ +export const createTransaction = (): Transaction => ({ family: "polkadot", mode: "send", amount: new BigNumber(0), @@ -18,5 +18,3 @@ const createTransaction = (): Transaction => ({ rewardDestination: null, numSlashingSpans: Number(0), }); - -export default createTransaction; diff --git a/libs/coin-polkadot/src/logic/utils.test.ts b/libs/coin-polkadot/src/logic/utils.test.ts new file mode 100644 index 00000000000..131316d208c --- /dev/null +++ b/libs/coin-polkadot/src/logic/utils.test.ts @@ -0,0 +1,116 @@ +import { BigNumber } from "bignumber.js"; +import { getCryptoCurrencyById } from "@ledgerhq/cryptoassets/currencies"; +import { PolkadotAccount } from "../types"; +import { canUnbond, isController, MAX_UNLOCKINGS } from "./utils"; + +const polkadotAccount: PolkadotAccount = { + type: "Account", + id: "", + seedIdentifier: "", + derivationMode: "", + freshAddress: "", + freshAddressPath: "", + name: "", + starred: false, + index: 0, + freshAddresses: [], + spendableBalance: new BigNumber(0), + balance: new BigNumber(0), + used: true, + creationDate: new Date(), + blockHeight: 0, + currency: getCryptoCurrencyById("polkadot"), + unit: { name: "DOT", code: "DOT", magnitude: 16 }, + operations: [], + pendingOperations: [], + operationsCount: 0, + lastSyncDate: new Date(), + balanceHistoryCache: {} as any, + swapHistory: [], + polkadotResources: {} as any, +}; + +describe("isController", () => { + it("returns false when stash is not defined", () => { + const accountIsController = isController(polkadotAccount); + expect(accountIsController).toBe(false); + }); + + it("returns false when stash account is undefined or null", () => { + polkadotAccount.polkadotResources.stash = null; + const accountIsController = isController(polkadotAccount); + expect(accountIsController).toBe(false); + }); + + it("returns true when stash account is defined", () => { + polkadotAccount.polkadotResources.stash = "stashAddress"; + const accountIsController = isController(polkadotAccount); + expect(accountIsController).toBe(true); + }); +}); + +describe("canUnbond", () => { + test("can unbond", () => { + const account: Partial = { + polkadotResources: { + controller: "", + stash: "", + nonce: 0, + numSlashingSpans: 0, + lockedBalance: new BigNumber(10000), + unlockedBalance: new BigNumber(0), + unlockingBalance: new BigNumber(0), + nominations: [], + unlockings: [ + ...Array(MAX_UNLOCKINGS - 1).map(() => ({ + amount: new BigNumber(100000), + completionDate: new Date(), + })), + ], + }, + }; + expect(canUnbond(account as PolkadotAccount)).toBeTruthy(); + }); + test("can't unbond because unlockings is too much", () => { + const account: Partial = { + polkadotResources: { + controller: "", + stash: "", + nonce: 0, + numSlashingSpans: 0, + lockedBalance: new BigNumber(1000000), + unlockedBalance: new BigNumber(0), + unlockingBalance: new BigNumber(0), + nominations: [], + unlockings: [ + ...Array(MAX_UNLOCKINGS).map(() => ({ + amount: new BigNumber(100000), + completionDate: new Date(), + })), + ], + }, + }; + expect(canUnbond(account as PolkadotAccount)).toBeFalsy(); + }); + test("can't unbond because not enough lockedBalance", () => { + const account: Partial = { + polkadotResources: { + controller: "", + stash: "", + nonce: 0, + numSlashingSpans: 0, + lockedBalance: new BigNumber(100), + unlockedBalance: new BigNumber(0), + unlockingBalance: new BigNumber(100), + nominations: [], + unlockings: [ + ...Array(MAX_UNLOCKINGS).map(() => ({ + amount: new BigNumber(100000), + completionDate: new Date(), + })), + ], + }, + }; + expect(canUnbond(account as PolkadotAccount)).toBeFalsy(); + }); +}); diff --git a/libs/coin-polkadot/src/logic.ts b/libs/coin-polkadot/src/logic/utils.ts similarity index 98% rename from libs/coin-polkadot/src/logic.ts rename to libs/coin-polkadot/src/logic/utils.ts index 2c06cbe5adf..4089fe159a3 100644 --- a/libs/coin-polkadot/src/logic.ts +++ b/libs/coin-polkadot/src/logic/utils.ts @@ -1,7 +1,7 @@ import { BigNumber } from "bignumber.js"; import type { Account, OperationType } from "@ledgerhq/types-live"; -import type { PolkadotAccount, Transaction } from "./types"; -import { getCurrentPolkadotPreloadData } from "./preload"; +import type { PolkadotAccount, Transaction } from "../types"; +import { getCurrentPolkadotPreloadData } from "./state"; export const EXISTENTIAL_DEPOSIT = new BigNumber(10000000000); export const EXISTENTIAL_DEPOSIT_RECOMMENDED_MARGIN = new BigNumber(1000000000); // Polkadot recommended Existential Deposit error margin @@ -244,6 +244,7 @@ export const calculateAmount = ({ a, t }: { a: PolkadotAccount; t: Transaction } return amount.lt(0) ? new BigNumber(0) : amount; }; + export const getMinimumBalance = (a: Account): BigNumber => { const lockedBalance = a.balance.minus(a.spendableBalance); return lockedBalance.lte(EXISTENTIAL_DEPOSIT) diff --git a/libs/coin-polkadot/src/api/bisontrails.ts b/libs/coin-polkadot/src/network/bisontrails.ts similarity index 99% rename from libs/coin-polkadot/src/api/bisontrails.ts rename to libs/coin-polkadot/src/network/bisontrails.ts index 5fb72ea3e6e..e9c3b7e0d26 100644 --- a/libs/coin-polkadot/src/api/bisontrails.ts +++ b/libs/coin-polkadot/src/network/bisontrails.ts @@ -5,7 +5,7 @@ import { encodeOperationId } from "@ledgerhq/coin-framework/operation"; import { getEnv } from "@ledgerhq/live-env"; import { getOperationType } from "./common"; import type { OperationType } from "@ledgerhq/types-live"; -import { isValidAddress } from "../address"; +import { isValidAddress } from "../common"; import { PolkadotOperation, PolkadotOperationExtra } from "../types"; const LIMIT = 200; diff --git a/libs/coin-polkadot/src/api/common.ts b/libs/coin-polkadot/src/network/common.ts similarity index 100% rename from libs/coin-polkadot/src/api/common.ts rename to libs/coin-polkadot/src/network/common.ts diff --git a/libs/coin-polkadot/src/api/index.ts b/libs/coin-polkadot/src/network/index.ts similarity index 95% rename from libs/coin-polkadot/src/api/index.ts rename to libs/coin-polkadot/src/network/index.ts index 72aba11355d..0610a1d62d0 100644 --- a/libs/coin-polkadot/src/api/index.ts +++ b/libs/coin-polkadot/src/network/index.ts @@ -1,25 +1,3 @@ -/* - -// Alternative Implementations provided for example or future implementations - -export { getOperations } from "./subscan"; - -export { - isElectionClosed, - isNewAccount, - isControllerAddress, - verifyValidatorAddresses, - getAccount, - getTransactionParams, - submitExtrinsic, - paymentInfo, - getValidators, - getStakingProgress, - getRegistry, - disconnect, -} from "./websocket"; - -*/ import { NetworkRequestCall } from "@ledgerhq/coin-framework/network"; import { getOperations as bisonGetOperations } from "./bisontrails"; import { diff --git a/libs/coin-polkadot/src/api/sidecar.ts b/libs/coin-polkadot/src/network/sidecar.ts similarity index 100% rename from libs/coin-polkadot/src/api/sidecar.ts rename to libs/coin-polkadot/src/network/sidecar.ts diff --git a/libs/coin-polkadot/src/api/sidecar.types.ts b/libs/coin-polkadot/src/network/sidecar.types.ts similarity index 100% rename from libs/coin-polkadot/src/api/sidecar.types.ts rename to libs/coin-polkadot/src/network/sidecar.types.ts diff --git a/libs/coin-polkadot/src/api/sidecar.unit.test.ts b/libs/coin-polkadot/src/network/sidecar.unit.test.ts similarity index 100% rename from libs/coin-polkadot/src/api/sidecar.unit.test.ts rename to libs/coin-polkadot/src/network/sidecar.unit.test.ts diff --git a/libs/coin-polkadot/src/serialization.ts b/libs/coin-polkadot/src/serialization.ts deleted file mode 100644 index 237059c9262..00000000000 --- a/libs/coin-polkadot/src/serialization.ts +++ /dev/null @@ -1,66 +0,0 @@ -import { BigNumber } from "bignumber.js"; -import type { - PolkadotResourcesRaw, - PolkadotResources, - PolkadotAccount, - PolkadotAccountRaw, -} from "./types"; -import { Account, AccountRaw } from "@ledgerhq/types-live"; - -export function toPolkadotResourcesRaw(r: PolkadotResources): PolkadotResourcesRaw { - const { nonce, controller, stash } = r; - return { - controller, - stash, - nonce, - lockedBalance: r.lockedBalance.toString(), - unlockedBalance: r.unlockedBalance.toString(), - unlockingBalance: r.unlockingBalance.toString(), - unlockings: r.unlockings?.map(u => ({ - amount: u.amount.toString(), - completionDate: u.completionDate.toISOString(), - })), - nominations: r.nominations?.map(n => ({ - address: n.address, - value: n.value.toString(), - status: n.status, - })), - numSlashingSpans: r.numSlashingSpans, - }; -} -export function fromPolkadotResourcesRaw(r: PolkadotResourcesRaw): PolkadotResources { - const { nonce, controller, stash } = r; - return { - controller, - stash, - nonce, - lockedBalance: new BigNumber(r.lockedBalance), - unlockedBalance: new BigNumber(r.unlockedBalance), - unlockingBalance: new BigNumber(r.unlockingBalance), - unlockings: r.unlockings?.map(u => ({ - amount: new BigNumber(u.amount), - completionDate: new Date(u.completionDate), - })), - nominations: r.nominations?.map(n => ({ - address: n.address, - value: new BigNumber(n.value), - status: n.status, - })), - numSlashingSpans: Number(r.numSlashingSpans) || 0, - }; -} - -export function assignToAccountRaw(account: Account, accountRaw: AccountRaw) { - const polkadotAccount = account as PolkadotAccount; - if (polkadotAccount.polkadotResources) { - (accountRaw as PolkadotAccountRaw).polkadotResources = toPolkadotResourcesRaw( - polkadotAccount.polkadotResources, - ); - } -} - -export function assignFromAccountRaw(accountRaw: AccountRaw, account: Account) { - const polkadotResourcesRaw = (accountRaw as PolkadotAccountRaw).polkadotResources; - if (polkadotResourcesRaw) - (account as PolkadotAccount).polkadotResources = fromPolkadotResourcesRaw(polkadotResourcesRaw); -} diff --git a/libs/coin-polkadot/src/hw-getAddress.ts b/libs/coin-polkadot/src/signer/getAddress.ts similarity index 82% rename from libs/coin-polkadot/src/hw-getAddress.ts rename to libs/coin-polkadot/src/signer/getAddress.ts index 9a42fd205de..31bda7a896d 100644 --- a/libs/coin-polkadot/src/hw-getAddress.ts +++ b/libs/coin-polkadot/src/signer/getAddress.ts @@ -1,9 +1,9 @@ import { GetAddressFn } from "@ledgerhq/coin-framework/bridge/getAddressWrapper"; import { SignerContext } from "@ledgerhq/coin-framework/signer"; import { GetAddressOptions } from "@ledgerhq/coin-framework/derivation"; -import { PolkadotAddress, PolkadotSignature, PolkadotSigner } from "./signer"; +import type { PolkadotAddress, PolkadotSignature, PolkadotSigner } from "../types"; -const resolver = ( +const getAddress = ( signerContext: SignerContext, ): GetAddressFn => { return async (deviceId: string, { path, verify }: GetAddressOptions) => { @@ -18,4 +18,4 @@ const resolver = ( }; }; -export default resolver; +export default getAddress; diff --git a/libs/coin-polkadot/src/signer/index.ts b/libs/coin-polkadot/src/signer/index.ts new file mode 100644 index 00000000000..1f3f13571b7 --- /dev/null +++ b/libs/coin-polkadot/src/signer/index.ts @@ -0,0 +1,7 @@ +/** + * This directory is the home for all types and logic based on Ledgers signer. + */ + +import getAddress from "./getAddress"; + +export default getAddress; diff --git a/libs/coin-polkadot/src/speculos-deviceActions.ts b/libs/coin-polkadot/src/test/bot-deviceActions.ts similarity index 96% rename from libs/coin-polkadot/src/speculos-deviceActions.ts rename to libs/coin-polkadot/src/test/bot-deviceActions.ts index 9e7085d7fa6..2edc2c19bc7 100644 --- a/libs/coin-polkadot/src/speculos-deviceActions.ts +++ b/libs/coin-polkadot/src/test/bot-deviceActions.ts @@ -1,5 +1,5 @@ import type { DeviceAction } from "@ledgerhq/coin-framework/bot/types"; -import type { PolkadotAccount, Transaction } from "./types"; +import type { PolkadotAccount, Transaction } from "../types"; import { deviceActionFlow, formatDeviceAmount, diff --git a/libs/coin-polkadot/src/specs.ts b/libs/coin-polkadot/src/test/bot-specs.ts similarity index 98% rename from libs/coin-polkadot/src/specs.ts rename to libs/coin-polkadot/src/test/bot-specs.ts index cbe001ec9e6..096e20b0659 100644 --- a/libs/coin-polkadot/src/specs.ts +++ b/libs/coin-polkadot/src/test/bot-specs.ts @@ -2,8 +2,8 @@ import expect from "expect"; import invariant from "invariant"; import sampleSize from "lodash/sampleSize"; import { BigNumber } from "bignumber.js"; -import { getCurrentPolkadotPreloadData } from "./preload"; -import type { PolkadotAccount, PolkadotResources, Transaction } from "./types"; +import { getCurrentPolkadotPreloadData } from "../logic/state"; +import type { PolkadotAccount, PolkadotResources, Transaction } from "../types"; import { getCryptoCurrencyById, parseCurrencyUnit, @@ -23,9 +23,9 @@ import { isFirstBond, hasMinimumBondBalance, getMinimumBalance, -} from "./logic"; +} from "../logic"; import { DeviceModelId } from "@ledgerhq/devices"; -import { acceptTransaction } from "./speculos-deviceActions"; +import { acceptTransaction } from "./bot-deviceActions"; const maxAccounts = 32; const currency = getCryptoCurrencyById("polkadot"); diff --git a/libs/coin-polkadot/src/bridge.integration.test.ts b/libs/coin-polkadot/src/test/bridgeDatasetTest.ts similarity index 90% rename from libs/coin-polkadot/src/bridge.integration.test.ts rename to libs/coin-polkadot/src/test/bridgeDatasetTest.ts index e97da6be0b4..ce15b46ab56 100644 --- a/libs/coin-polkadot/src/bridge.integration.test.ts +++ b/libs/coin-polkadot/src/test/bridgeDatasetTest.ts @@ -1,5 +1,4 @@ import { BigNumber } from "bignumber.js"; -import { canUnbond, MAX_UNLOCKINGS } from "./logic"; import { NotEnoughBalance, RecipientRequired, @@ -8,6 +7,7 @@ import { AmountRequired, NotEnoughBalanceBecauseDestinationNotCreated, } from "@ledgerhq/errors"; +import type { CurrenciesData, DatasetTest } from "@ledgerhq/types-live"; import { PolkadotUnauthorizedOperation, PolkadotNotValidator, @@ -15,10 +15,10 @@ import { PolkadotValidatorsRequired, PolkadotAllFundsWarning, PolkadotDoMaxSendInstead, -} from "./errors"; -import type { CurrenciesData, DatasetTest } from "@ledgerhq/types-live"; -import { fromTransactionRaw } from "./transaction"; -import type { PolkadotAccount, Transaction } from "./types"; +} from "../types"; + +import { fromTransactionRaw } from "../bridge/transaction"; +import type { PolkadotAccount, Transaction } from "../types"; const ACCOUNT_SAME_STASHCONTROLLER = "12YA86tRQhHgwU3SSj56aesUKB7GKvdnZTTTXRop4vd3YgDV"; const ACCOUNT_STASH = "13jAJfhpFkRZj1TSSdFopaiFeKnof2q7g4GNdcxcg8Lvx6QN"; const ACCOUNT_CONTROLLER = "15oodc5d8DWJodZhTD6qsxxSQRYWhdkWCrwqNHajDirXRrAD"; @@ -684,69 +684,3 @@ export const dataset: DatasetTest = { polkadot, }, }; - -describe("canUnbond", () => { - test("can unbond", () => { - const account: Partial = { - polkadotResources: { - controller: "", - stash: "", - nonce: 0, - numSlashingSpans: 0, - lockedBalance: new BigNumber(10000), - unlockedBalance: new BigNumber(0), - unlockingBalance: new BigNumber(0), - nominations: [], - unlockings: [ - ...Array(MAX_UNLOCKINGS - 1).map(() => ({ - amount: new BigNumber(100000), - completionDate: new Date(), - })), - ], - }, - }; - expect(canUnbond(account as PolkadotAccount)).toBeTruthy(); - }); - test("can't unbond because unlockings is too much", () => { - const account: Partial = { - polkadotResources: { - controller: "", - stash: "", - nonce: 0, - numSlashingSpans: 0, - lockedBalance: new BigNumber(1000000), - unlockedBalance: new BigNumber(0), - unlockingBalance: new BigNumber(0), - nominations: [], - unlockings: [ - ...Array(MAX_UNLOCKINGS).map(() => ({ - amount: new BigNumber(100000), - completionDate: new Date(), - })), - ], - }, - }; - expect(canUnbond(account as PolkadotAccount)).toBeFalsy(); - }); - test("can't unbond because not enough lockedBalance", () => { - const account: Partial = { - polkadotResources: { - controller: "", - stash: "", - nonce: 0, - numSlashingSpans: 0, - lockedBalance: new BigNumber(100), - unlockedBalance: new BigNumber(0), - unlockingBalance: new BigNumber(100), - nominations: [], - unlockings: [ - ...Array(MAX_UNLOCKINGS).map(() => ({ - amount: new BigNumber(100000), - completionDate: new Date(), - })), - ], - }, - }; - expect(canUnbond(account as PolkadotAccount)).toBeFalsy(); - }); -}); diff --git a/libs/coin-polkadot/src/cli-transaction.ts b/libs/coin-polkadot/src/test/cli.ts similarity index 84% rename from libs/coin-polkadot/src/cli-transaction.ts rename to libs/coin-polkadot/src/test/cli.ts index 7d56466f785..704c475467a 100644 --- a/libs/coin-polkadot/src/cli-transaction.ts +++ b/libs/coin-polkadot/src/test/cli.ts @@ -7,13 +7,19 @@ import { getCryptoCurrencyById, formatCurrencyUnit, } from "@ledgerhq/coin-framework/currencies/index"; -import { SidecarValidatorsParamAddresses, SidecarValidatorsParamStatus } from "./api/sidecar.types"; +import { + SidecarValidatorsParamAddresses, + SidecarValidatorsParamStatus, +} from "../network/sidecar.types"; import { AccountLike } from "@ledgerhq/types-live"; -import { PolkadotAccount, PolkadotValidator, Transaction } from "./types"; -import { PolkadotAPI } from "./api"; +import { PolkadotAccount, PolkadotValidator, Transaction } from "../types"; +import { PolkadotAPI } from "../network"; import { NetworkRequestCall } from "@ledgerhq/coin-framework/network"; import { LRUCacheFn } from "@ledgerhq/coin-framework/cache"; -const options = [ +type Options = + | { name: string; type: StringConstructor; desc: string } + | { name: string; type: StringConstructor; desc: string; multiple: boolean }; +const options: Array = [ { name: "mode", type: String, @@ -91,7 +97,19 @@ const polkadotValidatorsFormatters: Record = return tableList; }, }; -function createValidators(polkadotAPI: PolkadotAPI) { +type Validators = { + args: Array; + job: ({ + format, + status, + validator, + }: Partial<{ + format: string; + status: SidecarValidatorsParamStatus | SidecarValidatorsParamAddresses; + validator: string[]; + }>) => Observable; +}; +function createValidators(polkadotAPI: PolkadotAPI): Validators { return { args: [ { @@ -166,7 +184,19 @@ function inferTransactions( ); } -export default function makeCliTools(network: NetworkRequestCall, cache: LRUCacheFn) { +export type CliTools = { + options: Array; + inferTransactions: ( + transactions: Array<{ + account: AccountLike; + transaction: Transaction; + }>, + opts: Record, + { inferAmount }: any, + ) => Transaction[]; + commands: { polkadotValidators: Validators }; +}; +export default function makeCliTools(network: NetworkRequestCall, cache: LRUCacheFn): CliTools { const polkadotAPI = new PolkadotAPI(network, cache); return { options, diff --git a/libs/coin-polkadot/src/test/index.ts b/libs/coin-polkadot/src/test/index.ts new file mode 100644 index 00000000000..b4fce0ef353 --- /dev/null +++ b/libs/coin-polkadot/src/test/index.ts @@ -0,0 +1,6 @@ +import makeCliTools from "./cli"; + +export * from "./bridgeDatasetTest"; +export { makeCliTools }; +export * from "./bot-specs"; +export * from "./bot-deviceActions"; diff --git a/libs/coin-polkadot/src/errors.ts b/libs/coin-polkadot/src/types/errors.ts similarity index 100% rename from libs/coin-polkadot/src/errors.ts rename to libs/coin-polkadot/src/types/errors.ts diff --git a/libs/coin-polkadot/src/types/index.ts b/libs/coin-polkadot/src/types/index.ts new file mode 100644 index 00000000000..96521f8d701 --- /dev/null +++ b/libs/coin-polkadot/src/types/index.ts @@ -0,0 +1,3 @@ +export * from "./errors"; +export * from "./model"; +export * from "./signer"; diff --git a/libs/coin-polkadot/src/types.ts b/libs/coin-polkadot/src/types/model.ts similarity index 91% rename from libs/coin-polkadot/src/types.ts rename to libs/coin-polkadot/src/types/model.ts index 76ba2f296d8..91898da99c2 100644 --- a/libs/coin-polkadot/src/types.ts +++ b/libs/coin-polkadot/src/types/model.ts @@ -7,6 +7,8 @@ import type { TransactionStatusCommonRaw, Operation, OperationRaw, + OperationExtra, + OperationExtraRaw, } from "@ledgerhq/types-live"; import type { BigNumber } from "bignumber.js"; @@ -127,6 +129,9 @@ export type PolkadotOperationExtra = { validatorStash?: string; validators?: string[]; }; +export function isPolkadotOperationExtra(op: OperationExtra): op is PolkadotOperationExtra { + return op !== null && typeof op === "object" && "palletMethod" in op; +} export type PolkadotOperationExtraRaw = { transferAmount?: string; palletMethod: string; @@ -136,3 +141,8 @@ export type PolkadotOperationExtraRaw = { validatorStash?: string; validators?: string[]; }; +export function isPolkadotOperationExtraRaw( + op: OperationExtraRaw, +): op is PolkadotOperationExtraRaw { + return op !== null && typeof op === "object" && "palletMethod" in op; +} diff --git a/libs/coin-polkadot/src/signer.ts b/libs/coin-polkadot/src/types/signer.ts similarity index 100% rename from libs/coin-polkadot/src/signer.ts rename to libs/coin-polkadot/src/types/signer.ts diff --git a/libs/ledger-live-common/scripts/sync-families-dispatch.mjs b/libs/ledger-live-common/scripts/sync-families-dispatch.mjs index ea40b1ee27d..7515cc299bd 100644 --- a/libs/ledger-live-common/scripts/sync-families-dispatch.mjs +++ b/libs/ledger-live-common/scripts/sync-families-dispatch.mjs @@ -13,6 +13,7 @@ const targets = [ "deviceTransactionConfig.ts", "mock.ts", "account.ts", + "formatters.ts", "exchange.ts", "platformAdapter.ts", "walletApiAdapter.ts", @@ -82,27 +83,41 @@ function genCoinFrameworkTarget(targetFile) { switch (targetFile) { case "bridge/js.ts": imports += `import { bridge as ${family} } from "../../families/${family}/setup";\n`; + exprts += `\n ${family},`; break; case "cli-transaction.ts": imports += `import { cliTools as ${family} } from "../families/${family}/setup";\n`; + exprts += `\n ${family},`; break; case "hw-getAddress.ts": imports += `import { resolver as ${family} } from "../families/${family}/setup";\n`; + exprts += `\n ${family},`; break; case "hw-signMessage.ts": if (fs.existsSync(path.join(libsDir, `coin-${family}/src`, targetFile))) { imports += `import { messageSigner as ${family} } from "../families/${family}/setup";\n`; + exprts += `\n ${family},`; + } + break; + case "specs.ts": + if ( + fs.existsSync(path.join(libsDir, `coin-${family}/src`, targetFile)) || + fs.existsSync(path.join(libsDir, `coin-${family}/src/test/bot-specs.ts`)) + ) { + imports += `import ${family} from "@ledgerhq/coin-${family}/${targetName}";\n`; + exprts += `\n ${family},`; } break; // We still use bridge/js file inside "families" directory default: - if (fs.existsSync(path.join(libsDir, `coin-${family}/src`, targetFile))) { + if ( + fs.existsSync(path.join(libsDir, `coin-${family}/src`, targetFile)) || + fs.existsSync(path.join(libsDir, `coin-${family}/src/bridge`, targetFile)) + ) { imports += `import ${family} from "${targetImportPath}";\n`; + exprts += `\n ${family},`; } } - if (fs.existsSync(path.join(libsDir, `coin-${family}/src`, targetFile))) { - exprts += `\n ${family},`; - } } return { @@ -132,8 +147,8 @@ async function getDeviceTransactionConfig(families) { const libsDir = path.join(__dirname, "../.."); const family = "polkadot"; const target = "deviceTransactionConfig.ts"; - if (fs.existsSync(path.join(libsDir, `coin-${family}/src`, target))) { - imports += `import { ExtraDeviceTransactionField as ExtraDeviceTransactionField_${family} } from "@ledgerhq/coin-${family}/deviceTransactionConfig";\n`; + if (fs.existsSync(path.join(libsDir, `coin-${family}/src/bridge`, target))) { + imports += `import { ExtraDeviceTransactionField as ExtraDeviceTransactionField_${family} } from "@ledgerhq/coin-${family}/bridge/deviceTransactionConfig";\n`; exprts += `\n | ExtraDeviceTransactionField_${family}`; } diff --git a/libs/ledger-live-common/src/account/formatters.ts b/libs/ledger-live-common/src/account/formatters.ts index 01116de2372..046277633fb 100644 --- a/libs/ledger-live-common/src/account/formatters.ts +++ b/libs/ledger-live-common/src/account/formatters.ts @@ -10,7 +10,7 @@ import { getOperationAmountNumberWithInternals } from "../operation"; import { formatCurrencyUnit } from "../currencies"; import { getOperationAmountNumber } from "../operation"; import { getTagDerivationMode } from "@ledgerhq/coin-framework/derivation"; -import byFamily from "../generated/account"; +import byFamily from "../generated/formatters"; import { nftsByCollections } from "@ledgerhq/live-nft"; import type { Unit } from "@ledgerhq/types-cryptoassets"; import type { Account, Operation, ProtoNFT } from "@ledgerhq/types-live"; diff --git a/libs/ledger-live-common/src/account/serialization.ts b/libs/ledger-live-common/src/account/serialization.ts index 16ddf1e4961..46078a05ed4 100644 --- a/libs/ledger-live-common/src/account/serialization.ts +++ b/libs/ledger-live-common/src/account/serialization.ts @@ -5,7 +5,6 @@ import { getTokenById, findTokenById, } from "../currencies"; -import familySpecific from "../generated/account"; import { isAccountEmpty } from "./helpers"; import type { SwapOperation, SwapOperationRaw } from "../exchange/swap/types"; import { @@ -37,6 +36,7 @@ import { fromOperationRaw as commonFromOperationRaw, } from "@ledgerhq/coin-framework/account/serialization"; import { getAccountBridge } from "../bridge"; +import { getAccountBridgeByFamily } from "../bridge/impl"; export function toBalanceHistoryRaw(b: BalanceHistory): BalanceHistoryRaw { return b.map(({ date, value }) => [date.toISOString(), value.toString()]); @@ -57,9 +57,9 @@ export const toOperationRaw = ( const family = inferFamilyFromAccountId(operation.accountId); if (family) { - const specific = familySpecific[family]; - if (specific && specific.toOperationExtraRaw) { - operationRaw.extra = specific.toOperationExtraRaw(operation.extra); + const bridge = getAccountBridgeByFamily(family, operation.accountId); + if (bridge.toOperationExtraRaw) { + operationRaw.extra = bridge.toOperationExtraRaw(operation.extra); } } } @@ -78,9 +78,9 @@ export const fromOperationRaw = ( const family = inferFamilyFromAccountId(operationRaw.accountId); if (family) { - const specific = familySpecific[family]; - if (specific && specific.fromOperationExtraRaw) { - operation.extra = specific.fromOperationExtraRaw(operationRaw.extra); + const bridge = getAccountBridgeByFamily(family, accountId); + if (bridge.fromOperationExtraRaw) { + operation.extra = bridge.fromOperationExtraRaw(operationRaw.extra); } } } diff --git a/libs/ledger-live-common/src/bridge/impl.ts b/libs/ledger-live-common/src/bridge/impl.ts index 18540891274..ab1ffd2d604 100644 --- a/libs/ledger-live-common/src/bridge/impl.ts +++ b/libs/ledger-live-common/src/bridge/impl.ts @@ -25,28 +25,41 @@ export const getCurrencyBridge = (currency: CryptoCurrency): CurrencyBridge => { currencyName: currency.name, }); }; + export const getAccountBridge = ( account: AccountLike, parentAccount?: Account | null, ): AccountBridge => { const mainAccount = getMainAccount(account, parentAccount); const { currency } = mainAccount; - const { family } = currency; - const { type } = decodeAccountId(mainAccount.id); const supportedError = checkAccountSupported(mainAccount); if (supportedError) { throw supportedError; } - if (type === "mock") { - const mockBridge = mockBridges[currency.family]; - if (mockBridge) return mockBridge.accountBridge; + try { + return getAccountBridgeByFamily(currency.family, mainAccount.id); + } catch { + throw new CurrencyNotSupported("currency not supported " + currency.id, { + currencyName: currency.name, + }); + } +}; + +export function getAccountBridgeByFamily(family: string, accountId?: string): AccountBridge { + if (accountId) { + const { type } = decodeAccountId(accountId); + + if (type === "mock") { + const mockBridge = mockBridges[family]; + if (mockBridge) return mockBridge.accountBridge; + } } const jsBridge = jsBridges[family]; - if (jsBridge) return jsBridge.accountBridge; - throw new CurrencyNotSupported("currency not supported " + currency.id, { - currencyName: mainAccount.currency.name, - }); -}; + if (!jsBridge) { + throw new CurrencyNotSupported("currency bridge not found " + family); + } + return jsBridge.accountBridge; +} diff --git a/libs/ledger-live-common/src/bridge/index.ts b/libs/ledger-live-common/src/bridge/index.ts index 2bfa08054d0..ada7bf0448f 100644 --- a/libs/ledger-live-common/src/bridge/index.ts +++ b/libs/ledger-live-common/src/bridge/index.ts @@ -1,33 +1,7 @@ -import type { CryptoCurrency } from "@ledgerhq/types-cryptoassets"; -import type { - Account, - AccountBridge, - AccountLike, - CurrencyBridge, - ScanAccountEvent, - ScanAccountEventRaw, - TransactionCommon, -} from "@ledgerhq/types-live"; +import type { ScanAccountEvent, ScanAccountEventRaw } from "@ledgerhq/types-live"; import { fromAccountRaw, toAccountRaw } from "../account"; -import * as impl from "./impl"; -export type Proxy = { - getAccountBridge: typeof getAccountBridge; - getCurrencyBridge: typeof getCurrencyBridge; -}; -let proxy: Proxy | null | undefined; -export const setBridgeProxy = (p: Proxy | null | undefined): void => { - if (p && p.getAccountBridge === getAccountBridge) { - throw new Error("setBridgeProxy can't be called with same bridge functions!"); - } +export { getCurrencyBridge, getAccountBridge } from "./impl"; - proxy = p; -}; -export const getCurrencyBridge = (currency: CryptoCurrency): CurrencyBridge => - (proxy || impl).getCurrencyBridge(currency); -export const getAccountBridge = ( - account: AccountLike, - parentAccount?: Account | null | undefined, -): AccountBridge => (proxy || impl).getAccountBridge(account, parentAccount); export function fromScanAccountEventRaw(raw: ScanAccountEventRaw): ScanAccountEvent { switch (raw.type) { case "discovered": diff --git a/libs/ledger-live-common/src/families/bitcoin/account.ts b/libs/ledger-live-common/src/families/bitcoin/account.ts index 9cfdafd9a15..c3e2aac4a9c 100644 --- a/libs/ledger-live-common/src/families/bitcoin/account.ts +++ b/libs/ledger-live-common/src/families/bitcoin/account.ts @@ -1,10 +1,6 @@ -import type { BitcoinOutput, BitcoinInput, BitcoinAccount } from "./types"; -import { formatCurrencyUnit } from "../../currencies"; -import { getEnv } from "@ledgerhq/live-env"; +import type { BitcoinAccount } from "./types"; import { perCoinLogic } from "./logic"; -const sortUTXO = (a, b) => b.value.minus(a.value).toNumber(); - function injectGetAddressParams(account: BitcoinAccount): any { const perCoin = perCoinLogic[account.currency.id]; @@ -13,50 +9,6 @@ function injectGetAddressParams(account: BitcoinAccount): any { } } -export function formatInput(account: BitcoinAccount, input: BitcoinInput): string { - return `${(input.value - ? formatCurrencyUnit(account.unit, input.value, { - showCode: false, - }) - : "" - ).padEnd(12)} ${input.address || ""} ${input.previousTxHash || ""}@${input.previousOutputIndex}`; -} -export function formatOutput(account: BitcoinAccount, o: BitcoinOutput): string { - return [ - formatCurrencyUnit(account.unit, o.value, { - showCode: false, - }).padEnd(12), - o.address, - o.isChange ? "(change)" : "", - o.rbf ? "rbf" : "", - o.hash, - `@${o.outputIndex} (${o.blockHeight ? account.blockHeight - o.blockHeight : 0})`, - ] - .filter(Boolean) - .join(" "); -} - -function formatAccountSpecifics(account: BitcoinAccount): string { - if (!account.bitcoinResources) return ""; - const { utxos } = account.bitcoinResources; - let str = `\n${utxos.length} UTXOs`; - const n = getEnv("DEBUG_UTXO_DISPLAY"); - const displayAll = utxos.length <= n; - str += utxos - .slice(0) - .sort(sortUTXO) - .slice(0, displayAll ? utxos.length : n) - .map(utxo => `\n${formatOutput(account, utxo)}`) - .join(""); - - if (!displayAll) { - str += "\n..."; - } - - return str; -} - export default { injectGetAddressParams, - formatAccountSpecifics, }; diff --git a/libs/ledger-live-common/src/families/bitcoin/formatters.ts b/libs/ledger-live-common/src/families/bitcoin/formatters.ts new file mode 100644 index 00000000000..2519c5fdc15 --- /dev/null +++ b/libs/ledger-live-common/src/families/bitcoin/formatters.ts @@ -0,0 +1,52 @@ +import { getEnv } from "@ledgerhq/live-env"; +import type { BitcoinAccount, BitcoinInput, BitcoinOutput } from "./types"; +import { formatCurrencyUnit } from "../../currencies"; + +const sortUTXO = (a, b) => b.value.minus(a.value).toNumber(); + +export function formatInput(account: BitcoinAccount, input: BitcoinInput): string { + return `${(input.value + ? formatCurrencyUnit(account.unit, input.value, { + showCode: false, + }) + : "" + ).padEnd(12)} ${input.address || ""} ${input.previousTxHash || ""}@${input.previousOutputIndex}`; +} +export function formatOutput(account: BitcoinAccount, o: BitcoinOutput): string { + return [ + formatCurrencyUnit(account.unit, o.value, { + showCode: false, + }).padEnd(12), + o.address, + o.isChange ? "(change)" : "", + o.rbf ? "rbf" : "", + o.hash, + `@${o.outputIndex} (${o.blockHeight ? account.blockHeight - o.blockHeight : 0})`, + ] + .filter(Boolean) + .join(" "); +} + +function formatAccountSpecifics(account: BitcoinAccount): string { + if (!account.bitcoinResources) return ""; + const { utxos } = account.bitcoinResources; + let str = `\n${utxos.length} UTXOs`; + const n = getEnv("DEBUG_UTXO_DISPLAY"); + const displayAll = utxos.length <= n; + str += utxos + .slice(0) + .sort(sortUTXO) + .slice(0, displayAll ? utxos.length : n) + .map(utxo => `\n${formatOutput(account, utxo)}`) + .join(""); + + if (!displayAll) { + str += "\n..."; + } + + return str; +} + +export default { + formatAccountSpecifics, +}; diff --git a/libs/ledger-live-common/src/families/bitcoin/transaction.ts b/libs/ledger-live-common/src/families/bitcoin/transaction.ts index 83f85018ca4..0ba45a97af4 100644 --- a/libs/ledger-live-common/src/families/bitcoin/transaction.ts +++ b/libs/ledger-live-common/src/families/bitcoin/transaction.ts @@ -26,7 +26,7 @@ import { toBitcoinInputRaw, toBitcoinOutputRaw, } from "./serialization"; -import { formatInput, formatOutput } from "./account"; +import { formatInput, formatOutput } from "./formatters"; const fromFeeItemsRaw = (fir: FeeItemsRaw): FeeItems => ({ items: fir.items.map(fi => ({ diff --git a/libs/ledger-live-common/src/families/celo/account.ts b/libs/ledger-live-common/src/families/celo/account.ts deleted file mode 100644 index 7a92b9043d3..00000000000 --- a/libs/ledger-live-common/src/families/celo/account.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { BigNumber } from "bignumber.js"; -import { CeloOperationExtra, CeloOperationExtraRaw } from "./types"; - -export function fromOperationExtraRaw(extraRaw: CeloOperationExtraRaw): CeloOperationExtra { - const extra: CeloOperationExtra = { - celoOperationValue: new BigNumber(extraRaw.celoOperationValue), - }; - - if (extraRaw.celoSourceValidator) { - extra.celoSourceValidator = extraRaw.celoSourceValidator; - } - - return extra; -} - -export function toOperationExtraRaw(extra: CeloOperationExtra): CeloOperationExtraRaw { - const extraRaw: CeloOperationExtraRaw = { - celoOperationValue: extra.celoOperationValue.toString(), - }; - - if (extra.celoSourceValidator) { - extraRaw.celoSourceValidator = extra.celoSourceValidator; - } - - return extraRaw; -} - -export default { - fromOperationExtraRaw, - toOperationExtraRaw, -}; diff --git a/libs/ledger-live-common/src/families/celo/bridge/js.ts b/libs/ledger-live-common/src/families/celo/bridge/js.ts index 6041f6328ad..860caa98a20 100644 --- a/libs/ledger-live-common/src/families/celo/bridge/js.ts +++ b/libs/ledger-live-common/src/families/celo/bridge/js.ts @@ -10,7 +10,12 @@ import broadcast from "../js-broadcast"; import estimateMaxSpendable from "../js-estimateMaxSpendable"; import prepareTransaction from "../js-prepareTransaction"; import createTransaction from "../js-createTransaction"; -import { assignFromAccountRaw, assignToAccountRaw } from "../serialization"; +import { + assignFromAccountRaw, + assignToAccountRaw, + fromOperationExtraRaw, + toOperationExtraRaw, +} from "../serialization"; const receive = makeAccountBridgeReceive(); @@ -32,6 +37,8 @@ const accountBridge: AccountBridge = { broadcast, assignFromAccountRaw, assignToAccountRaw, + fromOperationExtraRaw, + toOperationExtraRaw, }; export default { currencyBridge, diff --git a/libs/ledger-live-common/src/families/celo/serialization.ts b/libs/ledger-live-common/src/families/celo/serialization.ts index bd431564b28..46b54aeea81 100644 --- a/libs/ledger-live-common/src/families/celo/serialization.ts +++ b/libs/ledger-live-common/src/families/celo/serialization.ts @@ -1,6 +1,15 @@ -import type { CeloAccount, CeloAccountRaw, CeloResources, CeloResourcesRaw } from "./types"; +import { + isCeloOperationExtra, + isCeloOperationExtraRaw, + type CeloAccount, + type CeloAccountRaw, + type CeloOperationExtra, + type CeloOperationExtraRaw, + type CeloResources, + type CeloResourcesRaw, +} from "./types"; import { BigNumber } from "bignumber.js"; -import { Account, AccountRaw } from "@ledgerhq/types-live"; +import { Account, AccountRaw, OperationExtra, OperationExtraRaw } from "@ledgerhq/types-live"; export function toCeloResourcesRaw(r: CeloResources): CeloResourcesRaw { const { @@ -71,3 +80,35 @@ export function assignFromAccountRaw(accountRaw: AccountRaw, account: Account) { if (celoResourcesRaw) (account as CeloAccount).celoResources = fromCeloResourcesRaw(celoResourcesRaw); } + +export function fromOperationExtraRaw(extraRaw: OperationExtraRaw): OperationExtra { + if (!isCeloOperationExtraRaw(extraRaw)) { + throw new Error("Unsupported OperationExtra"); + } + + const extra: CeloOperationExtra = { + celoOperationValue: new BigNumber(extraRaw.celoOperationValue), + }; + + if (extraRaw.celoSourceValidator) { + extra.celoSourceValidator = extraRaw.celoSourceValidator; + } + + return extra; +} + +export function toOperationExtraRaw(extra: OperationExtra): OperationExtraRaw { + if (!isCeloOperationExtra(extra)) { + throw new Error("Unsupported OperationExtra"); + } + + const extraRaw: CeloOperationExtraRaw = { + celoOperationValue: extra.celoOperationValue.toString(), + }; + + if (extra.celoSourceValidator) { + extraRaw.celoSourceValidator = extra.celoSourceValidator; + } + + return extraRaw; +} diff --git a/libs/ledger-live-common/src/families/celo/types.ts b/libs/ledger-live-common/src/families/celo/types.ts index 632bf9f35b3..4b0935d8d69 100644 --- a/libs/ledger-live-common/src/families/celo/types.ts +++ b/libs/ledger-live-common/src/families/celo/types.ts @@ -2,6 +2,8 @@ import type { Account, AccountRaw, Operation, + OperationExtra, + OperationExtraRaw, OperationRaw, TransactionCommon, TransactionCommonRaw, @@ -141,7 +143,13 @@ export type CeloOperationExtra = { celoOperationValue: BigNumber; celoSourceValidator?: string; }; +export function isCeloOperationExtra(op: OperationExtra): op is CeloOperationExtra { + return op !== null && typeof op === "object" && "celoOperationValue" in op; +} export type CeloOperationExtraRaw = { celoOperationValue: string; celoSourceValidator?: string; }; +export function isCeloOperationExtraRaw(op: OperationExtraRaw): op is CeloOperationExtraRaw { + return op !== null && typeof op === "object" && "celoOperationValue" in op; +} diff --git a/libs/ledger-live-common/src/families/cosmos/bridge/js.ts b/libs/ledger-live-common/src/families/cosmos/bridge/js.ts index f708a542c20..5be81a7adcb 100644 --- a/libs/ledger-live-common/src/families/cosmos/bridge/js.ts +++ b/libs/ledger-live-common/src/families/cosmos/bridge/js.ts @@ -13,7 +13,12 @@ import { CosmosAPI } from "../api/Cosmos"; import { CryptoCurrency } from "@ledgerhq/types-cryptoassets"; import { getCryptoCurrencyById } from "@ledgerhq/cryptoassets"; import { CosmosValidatorsManager } from "../CosmosValidatorsManager"; -import { assignFromAccountRaw, assignToAccountRaw } from "../serialization"; +import { + assignFromAccountRaw, + assignToAccountRaw, + fromOperationExtraRaw, + toOperationExtraRaw, +} from "../serialization"; import { getCurrencyConfiguration } from "../../../config"; import cryptoFactory from "../chain/chain"; @@ -74,6 +79,8 @@ const accountBridge: AccountBridge = { signedOperation, }); }, + fromOperationExtraRaw, + toOperationExtraRaw, }; export default { diff --git a/libs/ledger-live-common/src/families/cosmos/account.ts b/libs/ledger-live-common/src/families/cosmos/formatters.ts similarity index 65% rename from libs/ledger-live-common/src/families/cosmos/account.ts rename to libs/ledger-live-common/src/families/cosmos/formatters.ts index 70bdb73a64b..1b13d4262dc 100644 --- a/libs/ledger-live-common/src/families/cosmos/account.ts +++ b/libs/ledger-live-common/src/families/cosmos/formatters.ts @@ -3,12 +3,7 @@ import { BigNumber } from "bignumber.js"; import { getCurrentCosmosPreloadData } from "./preloadedData"; import { getAccountUnit } from "../../account"; import { formatCurrencyUnit } from "../../currencies"; -import { - CosmosOperation, - CosmosOperationExtra, - CosmosOperationExtraRaw, - CosmosAccount, -} from "./types"; +import { CosmosOperation, CosmosAccount } from "./types"; import { mapDelegations, mapUnbondings, mapRedelegations } from "./logic"; import type { Unit } from "@ledgerhq/types-cryptoassets"; @@ -118,72 +113,7 @@ export function formatAccountSpecifics(account: CosmosAccount): string { return str; } -export function fromOperationExtraRaw(extraRaw: CosmosOperationExtraRaw): CosmosOperationExtra { - const extra: CosmosOperationExtra = {}; - if (extraRaw.validator) { - extra.validator = { - address: extraRaw.validator.address, - amount: new BigNumber(extraRaw.validator.amount), - }; - } - - if (extraRaw.validators && extraRaw.validators.length > 0) { - extra.validators = extraRaw.validators.map(validator => ({ - address: validator.address, - amount: new BigNumber(validator.amount), - })); - } - - if (extraRaw.sourceValidator) { - extra.sourceValidator = extraRaw.sourceValidator; - } - - if (extraRaw.autoClaimedRewards) { - extra.autoClaimedRewards = extraRaw.autoClaimedRewards; - } - - if (extraRaw.memo) { - extra.memo = extraRaw.memo; - } - - return extra; -} - -export function toOperationExtraRaw(extra: CosmosOperationExtra): CosmosOperationExtraRaw { - const extraRaw: CosmosOperationExtraRaw = {}; - - if (extra.validator) { - extraRaw.validator = { - address: extra.validator.address, - amount: extra.validator.amount.toString(), - }; - } - - if (extra.validators && extra.validators.length > 0) { - extraRaw.validators = extra.validators.map(validator => ({ - address: validator.address, - amount: validator.amount.toString(), - })); - } - - if (extra.sourceValidator) { - extraRaw.sourceValidator = extra.sourceValidator; - } - - if (extra.autoClaimedRewards) { - extraRaw.autoClaimedRewards = extra.autoClaimedRewards; - } - - if (extra.memo) { - extraRaw.memo = extra.memo; - } - - return extraRaw; -} - export default { formatAccountSpecifics, formatOperationSpecifics, - fromOperationExtraRaw, - toOperationExtraRaw, }; diff --git a/libs/ledger-live-common/src/families/cosmos/serialization.ts b/libs/ledger-live-common/src/families/cosmos/serialization.ts index e7131e75490..e24efed73e5 100644 --- a/libs/ledger-live-common/src/families/cosmos/serialization.ts +++ b/libs/ledger-live-common/src/families/cosmos/serialization.ts @@ -1,6 +1,14 @@ import { BigNumber } from "bignumber.js"; -import type { CosmosResourcesRaw, CosmosResources, CosmosAccountRaw, CosmosAccount } from "./types"; -import { Account, AccountRaw } from "@ledgerhq/types-live"; +import { + type CosmosResourcesRaw, + type CosmosResources, + type CosmosAccountRaw, + type CosmosAccount, + isCosmosOperationExtraRaw, + CosmosOperationExtra, + CosmosOperationExtraRaw, +} from "./types"; +import { Account, AccountRaw, OperationExtra, OperationExtraRaw } from "@ledgerhq/types-live"; export function toCosmosResourcesRaw(r: CosmosResources): CosmosResourcesRaw { const { @@ -94,3 +102,73 @@ export function assignFromAccountRaw(accountRaw: AccountRaw, account: Account) { if (cosmosResourcesRaw) (account as CosmosAccount).cosmosResources = fromCosmosResourcesRaw(cosmosResourcesRaw); } + +export function fromOperationExtraRaw(extraRaw: OperationExtraRaw): OperationExtra { + const extra: CosmosOperationExtra = {}; + if (!isCosmosOperationExtraRaw(extraRaw)) { + return extra; + } + + if (extraRaw.validator) { + extra.validator = { + address: extraRaw.validator.address, + amount: new BigNumber(extraRaw.validator.amount), + }; + } + + if (extraRaw.validators && extraRaw.validators.length > 0) { + extra.validators = extraRaw.validators.map(validator => ({ + address: validator.address, + amount: new BigNumber(validator.amount), + })); + } + + if (extraRaw.sourceValidator) { + extra.sourceValidator = extraRaw.sourceValidator; + } + + if (extraRaw.autoClaimedRewards) { + extra.autoClaimedRewards = extraRaw.autoClaimedRewards; + } + + if (extraRaw.memo) { + extra.memo = extraRaw.memo; + } + + return extra; +} + +export function toOperationExtraRaw(extra: OperationExtra): OperationExtraRaw { + const extraRaw: CosmosOperationExtraRaw = {}; + if (!isCosmosOperationExtraRaw(extra)) { + return extraRaw; + } + + if (extra.validator) { + extraRaw.validator = { + address: extra.validator.address, + amount: extra.validator.amount.toString(), + }; + } + + if (extra.validators && extra.validators.length > 0) { + extraRaw.validators = extra.validators.map(validator => ({ + address: validator.address, + amount: validator.amount.toString(), + })); + } + + if (extra.sourceValidator) { + extraRaw.sourceValidator = extra.sourceValidator; + } + + if (extra.autoClaimedRewards) { + extraRaw.autoClaimedRewards = extra.autoClaimedRewards; + } + + if (extra.memo) { + extraRaw.memo = extra.memo; + } + + return extraRaw; +} diff --git a/libs/ledger-live-common/src/families/cosmos/types.ts b/libs/ledger-live-common/src/families/cosmos/types.ts index 53cba23580c..1bc6a1ea2c9 100644 --- a/libs/ledger-live-common/src/families/cosmos/types.ts +++ b/libs/ledger-live-common/src/families/cosmos/types.ts @@ -3,6 +3,8 @@ import { Account, AccountRaw, Operation, + OperationExtra, + OperationExtraRaw, OperationRaw, TransactionCommon, TransactionCommonRaw, @@ -135,21 +137,43 @@ export type NetworkInfoRaw = CosmosLikeNetworkInfoRaw & { export type CosmosOperation = Operation; export type CosmosOperationRaw = OperationRaw; -export type CosmosOperationExtra = { +export type CosmosOperationExtra = OperationExtra & { validators?: CosmosDelegationInfo[]; validator?: CosmosDelegationInfo; sourceValidator?: string; autoClaimedRewards?: string; // this is experimental to better represent auto claimed rewards memo?: string; }; +export function isCosmosOperationExtra(op: OperationExtra): op is CosmosOperationExtra { + return ( + op !== null && + typeof op === "object" && + ("validators" in op || + "validator" in op || + "sourceValidator" in op || + "autoClaimedRewards" in op || + "memo" in op) + ); +} -export type CosmosOperationExtraRaw = { +export type CosmosOperationExtraRaw = OperationExtraRaw & { validators?: CosmosDelegationInfoRaw[]; validator?: CosmosDelegationInfoRaw; sourceValidator?: string; autoClaimedRewards?: string; // this is experimental to better represent auto claimed rewards memo?: string; }; +export function isCosmosOperationExtraRaw(op: OperationExtraRaw): op is CosmosOperationExtraRaw { + return ( + op !== null && + typeof op === "object" && + ("validators" in op || + "validator" in op || + "sourceValidator" in op || + "autoClaimedRewards" in op || + "memo" in op) + ); +} export type CosmosDelegationInfo = { address: string; diff --git a/libs/ledger-live-common/src/families/elrond/bridge/js.ts b/libs/ledger-live-common/src/families/elrond/bridge/js.ts index 87992577f00..d90562f5b82 100644 --- a/libs/ledger-live-common/src/families/elrond/bridge/js.ts +++ b/libs/ledger-live-common/src/families/elrond/bridge/js.ts @@ -9,7 +9,12 @@ import getTransactionStatus from "../js-getTransactionStatus"; import estimateMaxSpendable from "../js-estimateMaxSpendable"; import signOperation from "../js-signOperation"; import broadcast from "../js-broadcast"; -import { assignFromAccountRaw, assignToAccountRaw } from "../serialization"; +import { + assignFromAccountRaw, + assignToAccountRaw, + fromOperationExtraRaw, + toOperationExtraRaw, +} from "../serialization"; const receive = makeAccountBridgeReceive(); const currencyBridge: CurrencyBridge = { @@ -30,6 +35,8 @@ const accountBridge: AccountBridge = { broadcast, assignFromAccountRaw, assignToAccountRaw, + fromOperationExtraRaw, + toOperationExtraRaw, }; export default { currencyBridge, diff --git a/libs/ledger-live-common/src/families/elrond/account.ts b/libs/ledger-live-common/src/families/elrond/formatters.ts similarity index 80% rename from libs/ledger-live-common/src/families/elrond/account.ts rename to libs/ledger-live-common/src/families/elrond/formatters.ts index 96bd9a04a93..510611c7266 100644 --- a/libs/ledger-live-common/src/families/elrond/account.ts +++ b/libs/ledger-live-common/src/families/elrond/formatters.ts @@ -3,12 +3,7 @@ import type { Operation } from "@ledgerhq/types-live"; import { getAccountUnit } from "../../account"; import { formatCurrencyUnit } from "../../currencies"; import type { Unit } from "@ledgerhq/types-cryptoassets"; -import type { - ElrondAccount, - ElrondOperation, - ElrondOperationExtra, - ElrondOperationExtraRaw, -} from "./types"; +import type { ElrondAccount, ElrondOperation } from "./types"; import BigNumber from "bignumber.js"; function formatAccountSpecifics(account: ElrondAccount): string { @@ -75,25 +70,7 @@ function formatOperationSpecifics(op: Operation, unit: Unit | null | undefined): : ""; } -export function fromOperationExtraRaw(extraRaw: ElrondOperationExtraRaw) { - const extra: ElrondOperationExtra = {}; - if (extraRaw.amount) { - extra.amount = new BigNumber(extraRaw.amount); - } - return extra; -} - -export function toOperationExtraRaw(extra: ElrondOperationExtra) { - const extraRaw: ElrondOperationExtraRaw = {}; - if (extra.amount) { - extraRaw.amount = extra.amount.toString(); - } - return extraRaw; -} - export default { formatAccountSpecifics, formatOperationSpecifics, - fromOperationExtraRaw, - toOperationExtraRaw, }; diff --git a/libs/ledger-live-common/src/families/elrond/serialization.ts b/libs/ledger-live-common/src/families/elrond/serialization.ts index c688c3aa4c9..428a46b6803 100644 --- a/libs/ledger-live-common/src/families/elrond/serialization.ts +++ b/libs/ledger-live-common/src/families/elrond/serialization.ts @@ -1,5 +1,15 @@ -import type { ElrondResourcesRaw, ElrondResources, ElrondAccountRaw, ElrondAccount } from "./types"; -import type { Account, AccountRaw } from "@ledgerhq/types-live"; +import BigNumber from "bignumber.js"; +import { + type ElrondResourcesRaw, + type ElrondResources, + type ElrondAccountRaw, + type ElrondAccount, + type ElrondOperationExtraRaw, + type ElrondOperationExtra, + isElrondOperationExtraRaw, + isElrondOperationExtra, +} from "./types"; +import type { Account, AccountRaw, OperationExtra, OperationExtraRaw } from "@ledgerhq/types-live"; export function toElrondResourcesRaw(r: ElrondResources): ElrondResourcesRaw { const { nonce, delegations, isGuarded } = r; @@ -32,3 +42,29 @@ export function assignFromAccountRaw(accountRaw: AccountRaw, account: Account) { if (elrondResourcesRaw) (account as ElrondAccount).elrondResources = fromElrondResourcesRaw(elrondResourcesRaw); } + +export function fromOperationExtraRaw(extraRaw: OperationExtraRaw) { + const extra: ElrondOperationExtra = {}; + if (!isElrondOperationExtraRaw(extraRaw)) { + // All fields might be undefined + return extra; + } + + if (extraRaw.amount) { + extra.amount = new BigNumber(extraRaw.amount); + } + return extra; +} + +export function toOperationExtraRaw(extra: OperationExtra) { + const extraRaw: ElrondOperationExtraRaw = {}; + if (!isElrondOperationExtra(extra)) { + // All fields might be undefined + return extraRaw; + } + + if (extra.amount) { + extraRaw.amount = extra.amount.toString(); + } + return extraRaw; +} diff --git a/libs/ledger-live-common/src/families/elrond/types.ts b/libs/ledger-live-common/src/families/elrond/types.ts index d384f9666b8..56499dd6874 100644 --- a/libs/ledger-live-common/src/families/elrond/types.ts +++ b/libs/ledger-live-common/src/families/elrond/types.ts @@ -2,6 +2,8 @@ import type { Account, AccountRaw, Operation, + OperationExtra, + OperationExtraRaw, OperationRaw, TransactionCommon, TransactionCommonRaw, @@ -218,6 +220,13 @@ export type ElrondOperationRaw = OperationRaw; export type ElrondOperationExtra = { amount?: BigNumber; }; +export function isElrondOperationExtra(op: OperationExtra): op is ElrondOperationExtra { + return op !== null && typeof op === "object" && "amount" in op; +} + export type ElrondOperationExtraRaw = { amount?: string; }; +export function isElrondOperationExtraRaw(op: OperationExtraRaw): op is ElrondOperationExtraRaw { + return op !== null && typeof op === "object" && "amount" in op; +} diff --git a/libs/ledger-live-common/src/families/polkadot/bridge.integration.test.ts b/libs/ledger-live-common/src/families/polkadot/bridge.integration.test.ts index cb990c0202e..d64c3d04609 100644 --- a/libs/ledger-live-common/src/families/polkadot/bridge.integration.test.ts +++ b/libs/ledger-live-common/src/families/polkadot/bridge.integration.test.ts @@ -1,7 +1,5 @@ import "../../__tests__/test-helpers/setup"; import { testBridge } from "../../__tests__/test-helpers/bridge"; -import { dataset } from "@ledgerhq/coin-polkadot/bridge.integration.test"; - -import "@ledgerhq/coin-polkadot/bridge.integration.test"; +import { dataset } from "@ledgerhq/coin-polkadot/test/index"; testBridge(dataset); diff --git a/libs/ledger-live-common/src/families/polkadot/bridge/mock.ts b/libs/ledger-live-common/src/families/polkadot/bridge/mock.ts index 4ad28eafbf5..01f89f2a892 100644 --- a/libs/ledger-live-common/src/families/polkadot/bridge/mock.ts +++ b/libs/ledger-live-common/src/families/polkadot/bridge/mock.ts @@ -2,7 +2,7 @@ import { BigNumber } from "bignumber.js"; import { NotEnoughBalance, RecipientRequired, InvalidAddress, FeeTooHigh } from "@ledgerhq/errors"; -import type { PolkadotAccount, Transaction } from "@ledgerhq/coin-polkadot/types"; +import type { PolkadotAccount, Transaction } from "@ledgerhq/coin-polkadot/types/index"; import type { AccountBridge, CurrencyBridge } from "@ledgerhq/types-live"; import { makeAccountBridgeReceive, diff --git a/libs/coin-polkadot/src/platformAdapter.test.ts b/libs/ledger-live-common/src/families/polkadot/platformAdapter.test.ts similarity index 100% rename from libs/coin-polkadot/src/platformAdapter.test.ts rename to libs/ledger-live-common/src/families/polkadot/platformAdapter.test.ts diff --git a/libs/coin-polkadot/src/platformAdapter.ts b/libs/ledger-live-common/src/families/polkadot/platformAdapter.ts similarity index 100% rename from libs/coin-polkadot/src/platformAdapter.ts rename to libs/ledger-live-common/src/families/polkadot/platformAdapter.ts diff --git a/libs/ledger-live-common/src/families/polkadot/react.ts b/libs/ledger-live-common/src/families/polkadot/react.ts index 5965b8b5c68..cc97ca06679 100644 --- a/libs/ledger-live-common/src/families/polkadot/react.ts +++ b/libs/ledger-live-common/src/families/polkadot/react.ts @@ -4,13 +4,13 @@ import { useBridgeSync } from "../../bridge/react"; import { getCurrentPolkadotPreloadData, getPolkadotPreloadDataUpdates, -} from "@ledgerhq/coin-polkadot/preload"; +} from "@ledgerhq/coin-polkadot/logic"; import type { PolkadotValidator, PolkadotNomination, PolkadotSearchFilter, PolkadotAccount, -} from "@ledgerhq/coin-polkadot/types"; +} from "@ledgerhq/coin-polkadot/types/index"; const SYNC_REFRESH_RATE = 6000; // 6s - block time export function usePolkadotPreloadData() { diff --git a/libs/ledger-live-common/src/families/polkadot/setup.ts b/libs/ledger-live-common/src/families/polkadot/setup.ts index 3a8f28a5853..aae05d7b631 100644 --- a/libs/ledger-live-common/src/families/polkadot/setup.ts +++ b/libs/ledger-live-common/src/families/polkadot/setup.ts @@ -1,14 +1,15 @@ // Goal of this file is to inject all necessary device/signer dependency to coin-modules -import { createBridges } from "@ledgerhq/coin-polkadot/bridge/js"; -import makeCliTools from "@ledgerhq/coin-polkadot/cli-transaction"; -import polkadotResolver from "@ledgerhq/coin-polkadot/hw-getAddress"; -import { Transaction } from "@ledgerhq/coin-polkadot/types"; +import { createBridges } from "@ledgerhq/coin-polkadot/bridge/index"; +import type { Transaction } from "@ledgerhq/coin-polkadot/types/index"; +import makeCliTools from "@ledgerhq/coin-polkadot/test/cli"; +import type { CliTools } from "@ledgerhq/coin-polkadot/test/cli"; +import polkadotResolver from "@ledgerhq/coin-polkadot/signer/index"; import Polkadot from "@ledgerhq/hw-app-polkadot"; import Transport from "@ledgerhq/hw-transport"; import { makeLRUCache } from "@ledgerhq/live-network/cache"; import network from "@ledgerhq/live-network/network"; -import { Bridge } from "@ledgerhq/types-live"; +import type { Bridge } from "@ledgerhq/types-live"; import { CreateSigner, createResolver, executeWithSigner } from "../../bridge/setup"; import { Resolver } from "../../hw/getAddress/types"; @@ -24,6 +25,6 @@ const bridge: Bridge = createBridges( const resolver: Resolver = createResolver(createSigner, polkadotResolver); -const cliTools = makeCliTools(network, makeLRUCache); +const cliTools: CliTools = makeCliTools(network, makeLRUCache); export { bridge, cliTools, resolver }; diff --git a/libs/ledger-live-common/src/families/polkadot/types.ts b/libs/ledger-live-common/src/families/polkadot/types.ts index cf0edfc66e2..4669e29e872 100644 --- a/libs/ledger-live-common/src/families/polkadot/types.ts +++ b/libs/ledger-live-common/src/families/polkadot/types.ts @@ -1,2 +1,2 @@ // Encapsulate for LLD et LLM -export * from "@ledgerhq/coin-polkadot/types"; +export * from "@ledgerhq/coin-polkadot/types/index"; diff --git a/libs/ledger-live-common/src/families/polkadot/walletApiAdapter.ts b/libs/ledger-live-common/src/families/polkadot/walletApiAdapter.ts index 31740e853ab..bc12fa891c7 100644 --- a/libs/ledger-live-common/src/families/polkadot/walletApiAdapter.ts +++ b/libs/ledger-live-common/src/families/polkadot/walletApiAdapter.ts @@ -1,5 +1,5 @@ -import createTransaction from "@ledgerhq/coin-polkadot/js-createTransaction"; -import { Transaction } from "@ledgerhq/coin-polkadot/types"; +import createTransaction from "@ledgerhq/coin-polkadot/bridge/createTransaction"; +import { Transaction } from "@ledgerhq/coin-polkadot/types/index"; import { PolkadotTransaction as WalletAPIPolkadotTransaction } from "@ledgerhq/wallet-api-core"; import { AreFeesProvided, diff --git a/libs/ledger-live-common/src/families/tron/account.ts b/libs/ledger-live-common/src/families/tron/account.ts deleted file mode 100644 index 1b2d023045c..00000000000 --- a/libs/ledger-live-common/src/families/tron/account.ts +++ /dev/null @@ -1,59 +0,0 @@ -import { BigNumber } from "bignumber.js"; -import { TrongridExtraTxInfo, TrongridExtraTxInfoRaw } from "./types"; - -export function fromOperationExtraRaw(extraRaw: TrongridExtraTxInfoRaw): TrongridExtraTxInfo { - const extra: TrongridExtraTxInfo = {}; - - if (extraRaw.frozenAmount) { - extra.frozenAmount = new BigNumber(extraRaw.frozenAmount); - } - - if (extraRaw.unfreezeAmount) { - extra.unfreezeAmount = new BigNumber(extraRaw.unfreezeAmount); - } - - if (extraRaw.votes) { - extra.votes = extraRaw.votes; - } - - if (extraRaw.unDelegatedAmount) { - extra.unDelegatedAmount = new BigNumber(extraRaw.unDelegatedAmount); - } - - if (extraRaw.receiverAddress) { - extra.receiverAddress = extraRaw.receiverAddress; - } - - return extra; -} - -export function toOperationExtraRaw(extra: TrongridExtraTxInfo): TrongridExtraTxInfoRaw { - const extraRaw: TrongridExtraTxInfoRaw = {}; - - if (extra.frozenAmount) { - extraRaw.frozenAmount = extra.frozenAmount.toString(); - } - - if (extra.unfreezeAmount) { - extraRaw.unfreezeAmount = extra.unfreezeAmount.toString(); - } - - if (extra.votes) { - extraRaw.votes = extra.votes; - } - - if (extra.unDelegatedAmount) { - extraRaw.unDelegatedAmount = extra.unDelegatedAmount.toString(); - } - - if (extra.receiverAddress) { - extraRaw.receiverAddress = extra.receiverAddress; - } - - return extraRaw; -} - -export default { - fromOperationExtraRaw, - toOperationExtraRaw, -}; diff --git a/libs/ledger-live-common/src/families/tron/bridge/js.ts b/libs/ledger-live-common/src/families/tron/bridge/js.ts index d888a9188b7..957ebeb022c 100644 --- a/libs/ledger-live-common/src/families/tron/bridge/js.ts +++ b/libs/ledger-live-common/src/families/tron/bridge/js.ts @@ -94,7 +94,12 @@ import { import { activationFees, oneTrx } from "../constants"; import { makeAccountBridgeReceive } from "../../../bridge/jsHelpers"; import type { AccountShapeInfo } from "../../../bridge/jsHelpers"; -import { assignFromAccountRaw, assignToAccountRaw } from "../serialization"; +import { + assignFromAccountRaw, + assignToAccountRaw, + fromOperationExtraRaw, + toOperationExtraRaw, +} from "../serialization"; import { encodeOperationId } from "@ledgerhq/coin-framework/operation"; const receive = makeAccountBridgeReceive(); @@ -867,6 +872,8 @@ const accountBridge: AccountBridge = { broadcast, assignFromAccountRaw, assignToAccountRaw, + fromOperationExtraRaw, + toOperationExtraRaw, }; export default { diff --git a/libs/ledger-live-common/src/families/tron/serialization.ts b/libs/ledger-live-common/src/families/tron/serialization.ts index 6fbed942001..737998f7940 100644 --- a/libs/ledger-live-common/src/families/tron/serialization.ts +++ b/libs/ledger-live-common/src/families/tron/serialization.ts @@ -1,6 +1,15 @@ import { BigNumber } from "bignumber.js"; -import type { TronAccount, TronAccountRaw, TronResources, TronResourcesRaw } from "./types"; -import { Account, AccountRaw } from "@ledgerhq/types-live"; +import { + isTrongridExtraTxInfo, + isTrongridExtraTxInfoRaw, + type TronAccount, + type TronAccountRaw, + type TronResources, + type TronResourcesRaw, + type TrongridExtraTxInfo, + type TrongridExtraTxInfoRaw, +} from "./types"; +import { Account, AccountRaw, OperationExtra, OperationExtraRaw } from "@ledgerhq/types-live"; export const toTronResourcesRaw = ({ frozen, @@ -217,3 +226,61 @@ export function assignFromAccountRaw(accountRaw: AccountRaw, account: Account) { if (tronResourcesRaw) (account as TronAccount).tronResources = fromTronResourcesRaw(tronResourcesRaw); } + +export function fromOperationExtraRaw(extraRaw: OperationExtraRaw): TrongridExtraTxInfo { + const extra: TrongridExtraTxInfo = {}; + if (!isTrongridExtraTxInfoRaw(extraRaw)) { + return extra; + } + + if (extraRaw.frozenAmount) { + extra.frozenAmount = new BigNumber(extraRaw.frozenAmount); + } + + if (extraRaw.unfreezeAmount) { + extra.unfreezeAmount = new BigNumber(extraRaw.unfreezeAmount); + } + + if (extraRaw.votes) { + extra.votes = extraRaw.votes; + } + + if (extraRaw.unDelegatedAmount) { + extra.unDelegatedAmount = new BigNumber(extraRaw.unDelegatedAmount); + } + + if (extraRaw.receiverAddress) { + extra.receiverAddress = extraRaw.receiverAddress; + } + + return extra; +} + +export function toOperationExtraRaw(extra: OperationExtra): TrongridExtraTxInfoRaw { + const extraRaw: TrongridExtraTxInfoRaw = {}; + if (!isTrongridExtraTxInfo(extra)) { + return extraRaw; + } + + if (extra.frozenAmount) { + extraRaw.frozenAmount = extra.frozenAmount.toString(); + } + + if (extra.unfreezeAmount) { + extraRaw.unfreezeAmount = extra.unfreezeAmount.toString(); + } + + if (extra.votes) { + extraRaw.votes = extra.votes; + } + + if (extra.unDelegatedAmount) { + extraRaw.unDelegatedAmount = extra.unDelegatedAmount.toString(); + } + + if (extra.receiverAddress) { + extraRaw.receiverAddress = extra.receiverAddress; + } + + return extraRaw; +} diff --git a/libs/ledger-live-common/src/families/tron/types.ts b/libs/ledger-live-common/src/families/tron/types.ts index 206e46cc054..a634a1aaff7 100644 --- a/libs/ledger-live-common/src/families/tron/types.ts +++ b/libs/ledger-live-common/src/families/tron/types.ts @@ -2,6 +2,8 @@ import { Account, AccountRaw, Operation, + OperationExtra, + OperationExtraRaw, OperationRaw, TransactionCommon, TransactionCommonRaw, @@ -87,20 +89,34 @@ export type TrongridTxInfo = { export type TronOperation = Operation; export type TronOperationRaw = OperationRaw; -export type TrongridExtraTxInfo = { +export type TrongridExtraTxInfo = OperationExtra & { frozenAmount?: BigNumber; unfreezeAmount?: BigNumber; votes?: Vote[]; unDelegatedAmount?: BigNumber; receiverAddress?: string; }; -export type TrongridExtraTxInfoRaw = { +export function isTrongridExtraTxInfo(op: OperationExtra): op is TrongridExtraTxInfo { + return ( + op !== null && + typeof op === "object" && + ("frozenAmount" in op || "unfreezeAmount" in op || "votes" in op) + ); +} +export type TrongridExtraTxInfoRaw = OperationExtraRaw & { frozenAmount?: string; unfreezeAmount?: string; votes?: Vote[]; unDelegatedAmount?: string; receiverAddress?: string; }; +export function isTrongridExtraTxInfoRaw(op: OperationExtraRaw): op is TrongridExtraTxInfoRaw { + return ( + op !== null && + typeof op === "object" && + ("frozenAmount" in op || "unfreezeAmount" in op || "votes" in op) + ); +} /** Payload types to send to trongrid */ export type SendTransactionData = { diff --git a/libs/ledger-live-common/src/generated/account.ts b/libs/ledger-live-common/src/generated/account.ts index de6ac305721..6075474c08e 100644 --- a/libs/ledger-live-common/src/generated/account.ts +++ b/libs/ledger-live-common/src/generated/account.ts @@ -1,25 +1,13 @@ import bitcoin from "../families/bitcoin/account"; import cardano from "../families/cardano/account"; -import celo from "../families/celo/account"; -import cosmos from "../families/cosmos/account"; import crypto_org from "../families/crypto_org/account"; -import elrond from "../families/elrond/account"; import near from "../families/near/account"; -import tron from "../families/tron/account"; import vechain from "../families/vechain/account"; -import algorand from "@ledgerhq/coin-algorand/account"; -import polkadot from "@ledgerhq/coin-polkadot/account"; export default { bitcoin, cardano, - celo, - cosmos, crypto_org, - elrond, near, - tron, vechain, - algorand, - polkadot, }; diff --git a/libs/ledger-live-common/src/generated/deviceTransactionConfig.ts b/libs/ledger-live-common/src/generated/deviceTransactionConfig.ts index 3f0a3f2b45e..e1f054ab8e2 100644 --- a/libs/ledger-live-common/src/generated/deviceTransactionConfig.ts +++ b/libs/ledger-live-common/src/generated/deviceTransactionConfig.ts @@ -48,7 +48,7 @@ import { ExtraDeviceTransactionField as ExtraDeviceTransactionField_stacks } fro import { ExtraDeviceTransactionField as ExtraDeviceTransactionField_stellar } from "../families/stellar/deviceTransactionConfig"; import { ExtraDeviceTransactionField as ExtraDeviceTransactionField_tezos } from "../families/tezos/deviceTransactionConfig"; import { ExtraDeviceTransactionField as ExtraDeviceTransactionField_tron } from "../families/tron/deviceTransactionConfig"; -import { ExtraDeviceTransactionField as ExtraDeviceTransactionField_polkadot } from "@ledgerhq/coin-polkadot/deviceTransactionConfig"; +import { ExtraDeviceTransactionField as ExtraDeviceTransactionField_polkadot } from "@ledgerhq/coin-polkadot/bridge/deviceTransactionConfig"; export type ExtraDeviceTransactionField = | ExtraDeviceTransactionField_casper diff --git a/libs/ledger-live-common/src/generated/formatters.ts b/libs/ledger-live-common/src/generated/formatters.ts new file mode 100644 index 00000000000..086c147ef39 --- /dev/null +++ b/libs/ledger-live-common/src/generated/formatters.ts @@ -0,0 +1,13 @@ +import bitcoin from "../families/bitcoin/formatters"; +import cosmos from "../families/cosmos/formatters"; +import elrond from "../families/elrond/formatters"; +import algorand from "@ledgerhq/coin-algorand/formatters"; +import polkadot from "@ledgerhq/coin-polkadot/formatters"; + +export default { + bitcoin, + cosmos, + elrond, + algorand, + polkadot, +}; diff --git a/libs/ledger-live-common/src/generated/platformAdapter.ts b/libs/ledger-live-common/src/generated/platformAdapter.ts index 70b96731d19..bb316e1eb09 100644 --- a/libs/ledger-live-common/src/generated/platformAdapter.ts +++ b/libs/ledger-live-common/src/generated/platformAdapter.ts @@ -1,11 +1,11 @@ import bitcoin from "../families/bitcoin/platformAdapter"; import evm from "../families/evm/platformAdapter"; +import polkadot from "../families/polkadot/platformAdapter"; import ripple from "../families/ripple/platformAdapter"; -import polkadot from "@ledgerhq/coin-polkadot/platformAdapter"; export default { bitcoin, evm, - ripple, polkadot, + ripple, }; diff --git a/libs/ledger-live-common/src/generated/types.ts b/libs/ledger-live-common/src/generated/types.ts index d50db0c6dfc..cb42ab450b0 100644 --- a/libs/ledger-live-common/src/generated/types.ts +++ b/libs/ledger-live-common/src/generated/types.ts @@ -50,10 +50,10 @@ import { Transaction as nearTransaction } from "../families/near/types"; import { TransactionRaw as nearTransactionRaw } from "../families/near/types"; import { TransactionStatus as nearTransactionStatus } from "../families/near/types"; import { TransactionStatusRaw as nearTransactionStatusRaw } from "../families/near/types"; -import { Transaction as polkadotTransaction } from "@ledgerhq/coin-polkadot/types"; -import { TransactionRaw as polkadotTransactionRaw } from "@ledgerhq/coin-polkadot/types"; -import { TransactionStatus as polkadotTransactionStatus } from "@ledgerhq/coin-polkadot/types"; -import { TransactionStatusRaw as polkadotTransactionStatusRaw } from "@ledgerhq/coin-polkadot/types"; +import { Transaction as polkadotTransaction } from "@ledgerhq/coin-polkadot/types/index"; +import { TransactionRaw as polkadotTransactionRaw } from "@ledgerhq/coin-polkadot/types/index"; +import { TransactionStatus as polkadotTransactionStatus } from "@ledgerhq/coin-polkadot/types/index"; +import { TransactionStatusRaw as polkadotTransactionStatusRaw } from "@ledgerhq/coin-polkadot/types/index"; import { Transaction as rippleTransaction } from "../families/ripple/types"; import { TransactionRaw as rippleTransactionRaw } from "../families/ripple/types"; import { TransactionStatus as rippleTransactionStatus } from "../families/ripple/types"; diff --git a/libs/ledger-live-common/src/mock/account.ts b/libs/ledger-live-common/src/mock/account.ts index 66671b5bdb8..b95e7d262f1 100644 --- a/libs/ledger-live-common/src/mock/account.ts +++ b/libs/ledger-live-common/src/mock/account.ts @@ -12,7 +12,7 @@ import { getAccountBridge } from "../bridge"; import perFamilyMock from "../generated/mock"; import { CosmosAccount } from "../families/cosmos/types"; import { BitcoinAccount } from "../families/bitcoin/types"; -import { PolkadotAccount } from "@ledgerhq/coin-polkadot/types"; +import { PolkadotAccount } from "@ledgerhq/coin-polkadot/types/index"; import { TezosAccount } from "../families/tezos/types"; import { TronAccount } from "../families/tron/types"; import { CardanoAccount, PaymentChain } from "../families/cardano/types"; diff --git a/libs/ledgerjs/packages/types-live/src/bridge.ts b/libs/ledgerjs/packages/types-live/src/bridge.ts index b9b4abad748..8c7fc15fc20 100644 --- a/libs/ledgerjs/packages/types-live/src/bridge.ts +++ b/libs/ledgerjs/packages/types-live/src/bridge.ts @@ -13,7 +13,7 @@ import type { TransactionCommon, TransactionStatusCommon, } from "./transaction"; -import type { Operation } from "./operation"; +import type { Operation, OperationExtra, OperationExtraRaw } from "./operation"; import type { DerivationMode } from "./derivation"; import type { SyncConfig } from "./pagination"; import { @@ -196,6 +196,8 @@ export interface AccountBridge { // broadcasting a signed transaction to network // returns an optimistic Operation that this transaction is likely to create in the future broadcast: BroadcastFnSignature; + fromOperationExtraRaw?: (extraRaw: OperationExtraRaw) => OperationExtra; + toOperationExtraRaw?: (extra: OperationExtra) => OperationExtraRaw; } type ExpectFn = (...args: Array) => any; diff --git a/libs/ledgerjs/packages/types-live/src/operation.ts b/libs/ledgerjs/packages/types-live/src/operation.ts index 3d43b7ec68a..c17bdd05ab7 100644 --- a/libs/ledgerjs/packages/types-live/src/operation.ts +++ b/libs/ledgerjs/packages/types-live/src/operation.ts @@ -53,10 +53,11 @@ export type OperationType = | "UNSTAKE" | "WITHDRAW_UNSTAKED"; +export type OperationExtra = unknown; /** * An Operation is the Ledger Live abstraction of a transaction for any blockchain */ -export type Operation = { +export type Operation = { // unique identifier (usually hash) id: string; // transaction hash @@ -110,7 +111,8 @@ export type Operation = { extra: Extra; }; -export type OperationRaw = { +export type OperationExtraRaw = unknown; +export type OperationRaw = { id: string; hash: string; type: OperationType; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a6d5a4934b3..7c459b72fb5 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1793,9 +1793,6 @@ importers: '@ledgerhq/errors': specifier: workspace:^ version: link:../ledgerjs/packages/errors - '@ledgerhq/live-app-sdk': - specifier: ^0.8.1 - version: 0.8.1 '@ledgerhq/live-env': specifier: workspace:^ version: link:../env