diff --git a/.changeset/light-starfishes-appear.md b/.changeset/light-starfishes-appear.md
new file mode 100644
index 000000000000..a40c17abcfe3
--- /dev/null
+++ b/.changeset/light-starfishes-appear.md
@@ -0,0 +1,10 @@
+---
+"@ledgerhq/live-cli": minor
+"ledger-live-desktop": minor
+"live-mobile": minor
+"@ledgerhq/live-common": minor
+"@ledgerhq/cryptoassets": minor
+"@ledgerhq/hw-app-icon": minor
+---
+
+Integrate Sync, Send, Receive, Create Account for Icon network
diff --git a/apps/cli/src/live-common-setup-base.ts b/apps/cli/src/live-common-setup-base.ts
index 21b779317492..faf2b4581f25 100644
--- a/apps/cli/src/live-common-setup-base.ts
+++ b/apps/cli/src/live-common-setup-base.ts
@@ -55,6 +55,8 @@ setSupportedCurrencies([
"songbird",
"flare",
"near",
+ "icon",
+ "icon_berlin_testnet",
"optimism",
"optimism_sepolia",
"arbitrum",
diff --git a/apps/ledger-live-desktop/cryptoassets.md b/apps/ledger-live-desktop/cryptoassets.md
index 89a935c2adfe..402564a70546 100644
--- a/apps/ledger-live-desktop/cryptoassets.md
+++ b/apps/ledger-live-desktop/cryptoassets.md
@@ -107,7 +107,7 @@
| Helium | HNT | NO | helium |
| High Performance Blockchain | HPB | NO | hpb |
| Hycon | HYC | NO | hycon |
-| ICON | ICX | NO | icon |
+| ICON | ICX | YES | icon |
| IOTA | MIOTA | NO | iota |
| IOV | IOV | NO | iov |
| Kin | KIN | NO | kin |
diff --git a/apps/ledger-live-desktop/src/live-common-set-supported-currencies.ts b/apps/ledger-live-desktop/src/live-common-set-supported-currencies.ts
index 32d1ce624897..4b2f92ff44b0 100644
--- a/apps/ledger-live-desktop/src/live-common-set-supported-currencies.ts
+++ b/apps/ledger-live-desktop/src/live-common-set-supported-currencies.ts
@@ -60,6 +60,8 @@ setSupportedCurrencies([
"songbird",
"flare",
"near",
+ "icon",
+ "icon_berlin_testnet",
"optimism",
"optimism_sepolia",
"arbitrum",
diff --git a/apps/ledger-live-desktop/src/renderer/families/icon/AccountBalanceSummaryFooter.tsx b/apps/ledger-live-desktop/src/renderer/families/icon/AccountBalanceSummaryFooter.tsx
new file mode 100644
index 000000000000..3babb0339880
--- /dev/null
+++ b/apps/ledger-live-desktop/src/renderer/families/icon/AccountBalanceSummaryFooter.tsx
@@ -0,0 +1,93 @@
+import React from "react";
+import styled from "styled-components";
+import { useSelector } from "react-redux";
+import { Trans } from "react-i18next";
+import { formatCurrencyUnit } from "@ledgerhq/live-common/currencies/index";
+import Discreet, { useDiscreetMode } from "~/renderer/components/Discreet";
+import { IconAccount } from "@ledgerhq/live-common/families/icon/types";
+
+import Box from "~/renderer/components/Box/Box";
+import Text from "~/renderer/components/Text";
+import InfoCircle from "~/renderer/icons/InfoCircle";
+import ToolTip from "~/renderer/components/Tooltip";
+import { localeSelector } from "~/renderer/reducers/settings";
+import { SubAccount } from "@ledgerhq/types-live";
+import { useAccountUnit } from "~/renderer/hooks/useAccountUnit";
+
+const Wrapper = styled(Box).attrs(() => ({
+ horizontal: true,
+ mt: 4,
+ p: 5,
+ pb: 0,
+}))`
+ border-top: 1px solid ${p => p.theme.colors.palette.text.shade10};
+`;
+
+const BalanceDetail = styled(Box).attrs(() => ({
+ flex: 1.25,
+ vertical: true,
+ alignItems: "start",
+}))`
+ &:nth-child(n + 3) {
+ flex: 0.75;
+ }
+`;
+
+const TitleWrapper = styled(Box).attrs(() => ({ horizontal: true, alignItems: "center", mb: 1 }))``;
+
+const Title = styled(Text).attrs(() => ({
+ fontSize: 4,
+ ff: "Inter|Medium",
+ color: "palette.text.shade60",
+}))`
+ line-height: ${p => p.theme.space[4]}px;
+ margin-right: ${p => p.theme.space[1]}px;
+`;
+
+const AmountValue = styled(Text).attrs(() => ({
+ fontSize: 6,
+ ff: "Inter|SemiBold",
+ color: "palette.text.shade100",
+}))``;
+
+type Props = {
+ account: IconAccount | SubAccount;
+};
+
+const AccountBalanceSummaryFooter = ({ account }: Props) => {
+ const discreet = useDiscreetMode();
+ const locale = useSelector(localeSelector);
+ const unit = useAccountUnit(account);
+
+ if (account.type !== "Account") return null;
+
+ const formatConfig = {
+ disableRounding: false,
+ alwaysShowSign: false,
+ showCode: true,
+ discreet,
+ locale,
+ };
+
+ const spendableBalance = formatCurrencyUnit(unit, account.spendableBalance, formatConfig);
+
+ return (
+
+
+ }>
+
+
+
+
+
+
+
+
+ {spendableBalance}
+
+
+
+ );
+};
+
+export default AccountBalanceSummaryFooter;
diff --git a/apps/ledger-live-desktop/src/renderer/families/icon/AccountSubHeader.tsx b/apps/ledger-live-desktop/src/renderer/families/icon/AccountSubHeader.tsx
new file mode 100644
index 000000000000..a77f17f4942d
--- /dev/null
+++ b/apps/ledger-live-desktop/src/renderer/families/icon/AccountSubHeader.tsx
@@ -0,0 +1,6 @@
+import React from "react";
+import AccountSubHeader from "../../components/AccountSubHeader";
+
+export default function IconAccountSubHeader() {
+ return ;
+}
diff --git a/apps/ledger-live-desktop/src/renderer/families/icon/StepSummaryNetworkFeesRow.tsx b/apps/ledger-live-desktop/src/renderer/families/icon/StepSummaryNetworkFeesRow.tsx
new file mode 100644
index 000000000000..30faaae98915
--- /dev/null
+++ b/apps/ledger-live-desktop/src/renderer/families/icon/StepSummaryNetworkFeesRow.tsx
@@ -0,0 +1,64 @@
+import React from "react";
+import { Trans } from "react-i18next";
+import Box from "~/renderer/components/Box";
+import CounterValue from "~/renderer/components/CounterValue";
+import FormattedVal from "~/renderer/components/FormattedVal";
+import Text from "~/renderer/components/Text";
+import IconExclamationCircle from "~/renderer/icons/ExclamationCircle";
+import TranslatedError from "~/renderer/components/TranslatedError";
+import { SummaryNetworkFeesRowProps } from "../types";
+
+const StepSummaryNetworkFeesRow = ({
+ feeTooHigh,
+ feesUnit,
+ estimatedFees,
+ feesCurrency,
+}: SummaryNetworkFeesRowProps) => {
+ return (
+ <>
+
+
+ {" "}
+
+
+
+
+
+
+
+
+ {feeTooHigh ? (
+
+
+
+
+
+
+ ) : null}
+ >
+ );
+};
+
+export default StepSummaryNetworkFeesRow;
diff --git a/apps/ledger-live-desktop/src/renderer/families/icon/TransactionConfirmFields.tsx b/apps/ledger-live-desktop/src/renderer/families/icon/TransactionConfirmFields.tsx
new file mode 100644
index 000000000000..eebedb28fdda
--- /dev/null
+++ b/apps/ledger-live-desktop/src/renderer/families/icon/TransactionConfirmFields.tsx
@@ -0,0 +1,101 @@
+import invariant from "invariant";
+import React from "react";
+import styled from "styled-components";
+import { Trans } from "react-i18next";
+import TransactionConfirmField from "~/renderer/components/TransactionConfirm/TransactionConfirmField";
+import WarnBox from "~/renderer/components/WarnBox";
+import Box from "~/renderer/components/Box";
+import FormattedVal from "~/renderer/components/FormattedVal";
+import Alert from "~/renderer/components/Alert";
+import { FieldComponentProps } from "../types";
+import { getMainAccount } from "@ledgerhq/live-common/account/index";
+import type {
+ IconAccount,
+ Transaction,
+ TransactionStatus,
+} from "@ledgerhq/live-common/families/icon/types";
+import { useAccountUnit } from "~/renderer/hooks/useAccountUnit";
+
+const Info = styled(Box).attrs(() => ({
+ ff: "Inter|SemiBold",
+ color: "palette.text.shade100",
+ mt: 6,
+ mb: 4,
+ px: 5,
+}))`
+ text-align: center;
+`;
+
+const IconFreesField = ({
+ account,
+ parentAccount,
+ transaction,
+ field,
+}: FieldComponentProps) => {
+ const mainAccount = getMainAccount(account, parentAccount);
+ invariant(transaction.family === "icon", "icon transaction");
+ const unit = useAccountUnit(mainAccount);
+
+ const { fees } = transaction;
+ return (
+
+
+
+
+ );
+};
+
+const Warning = ({
+ transaction,
+ recipientWording,
+}: {
+ transaction: Transaction;
+ recipientWording: string;
+}) => {
+ invariant(transaction.family === "icon", "icon transaction");
+ return (
+
+
+
+ );
+};
+
+const Title = ({ transaction }: { transaction: Transaction }) => {
+ invariant(transaction.family === "icon", "icon transaction");
+
+ return (
+
+
+
+ );
+};
+
+const Footer = ({ transaction }: { transaction: Transaction }) => {
+ invariant(transaction.family === "icon", "icon transaction");
+ return (
+
+
+
+ );
+};
+
+const fieldComponents = {
+ "icon.fees": IconFreesField,
+};
+
+export default {
+ fieldComponents,
+ warning: Warning,
+ title: Title,
+ footer: Footer,
+ disableFees: () => true,
+};
diff --git a/apps/ledger-live-desktop/src/renderer/families/icon/index.ts b/apps/ledger-live-desktop/src/renderer/families/icon/index.ts
new file mode 100644
index 000000000000..eee5a73bc862
--- /dev/null
+++ b/apps/ledger-live-desktop/src/renderer/families/icon/index.ts
@@ -0,0 +1,20 @@
+import AccountSubHeader from "./AccountSubHeader";
+import transactionConfirmFields from "./TransactionConfirmFields";
+import AccountBalanceSummaryFooter from "./AccountBalanceSummaryFooter";
+import StepSummaryNetworkFeesRow from "./StepSummaryNetworkFeesRow";
+import {
+ IconAccount,
+ Transaction,
+ TransactionStatus,
+ IconOperation,
+} from "@ledgerhq/live-common/families/icon/types";
+import { LLDCoinFamily } from "../types";
+
+const family: LLDCoinFamily = {
+ AccountSubHeader,
+ transactionConfirmFields,
+ StepSummaryNetworkFeesRow,
+ AccountBalanceSummaryFooter,
+};
+
+export default family;
diff --git a/apps/ledger-live-mobile/src/families/icon/AccountSubHeader.tsx b/apps/ledger-live-mobile/src/families/icon/AccountSubHeader.tsx
new file mode 100644
index 000000000000..b6a64cccc0f9
--- /dev/null
+++ b/apps/ledger-live-mobile/src/families/icon/AccountSubHeader.tsx
@@ -0,0 +1,8 @@
+import React from "react";
+import AccountSubHeader from "../../components/AccountSubHeader";
+
+function IconAccountSubHeader() {
+ return ;
+}
+
+export default IconAccountSubHeader;
diff --git a/apps/ledger-live-mobile/src/live-common-setup.ts b/apps/ledger-live-mobile/src/live-common-setup.ts
index 19fe976ce4b4..dad824f396de 100644
--- a/apps/ledger-live-mobile/src/live-common-setup.ts
+++ b/apps/ledger-live-mobile/src/live-common-setup.ts
@@ -93,6 +93,8 @@ setSupportedCurrencies([
"songbird",
"flare",
"near",
+ "icon",
+ "icon_berlin_testnet",
"optimism",
"optimism_sepolia",
"arbitrum",
diff --git a/libs/coin-framework/src/derivation.ts b/libs/coin-framework/src/derivation.ts
index 04ed4a73433c..ccf0ff7b10ca 100644
--- a/libs/coin-framework/src/derivation.ts
+++ b/libs/coin-framework/src/derivation.ts
@@ -175,6 +175,9 @@ const modes: Readonly>> = Object.freeze(
overridesDerivation: "44'/397'/0'/0'/'",
mandatoryEmptyAccountSkip: 1,
},
+ icon: {
+ overridesDerivation: "44'/4801368'/0'/0'/'",
+ },
vechain: {
overridesDerivation: "44'/818'/0'/0/",
},
@@ -203,6 +206,8 @@ const legacyDerivations: Partial>
cardano: ["cardano"],
cardano_testnet: ["cardano"],
near: ["nearbip44h"],
+ icon: ["icon"],
+ icon_berlin_testnet: ["icon"],
vechain: ["vechain"],
stacks: ["stacks_wallet"],
ethereum: ["ethM", "ethMM"],
@@ -342,6 +347,8 @@ const disableBIP44: Record = {
cardano: true,
cardano_testnet: true,
near: true,
+ icon: true,
+ icon_berlin_testnet: true,
vechain: true,
internet_computer: true,
casper: true,
diff --git a/libs/coin-modules/coin-icon/.eslintrc.js b/libs/coin-modules/coin-icon/.eslintrc.js
new file mode 100644
index 000000000000..8f6848a860b9
--- /dev/null
+++ b/libs/coin-modules/coin-icon/.eslintrc.js
@@ -0,0 +1,20 @@
+module.exports = {
+ env: {
+ browser: true,
+ es6: true,
+ },
+ overrides: [
+ {
+ files: ["src/**/*.test.{ts,tsx}"],
+ env: {
+ "jest/globals": true,
+ },
+ plugins: ["jest"],
+ },
+ ],
+ rules: {
+ "no-console": ["error", { allow: ["warn", "error"] }],
+ "@typescript-eslint/no-empty-function": "off",
+ "@typescript-eslint/no-explicit-any": "warn",
+ },
+};
diff --git a/libs/coin-modules/coin-icon/.unimportedrc.json b/libs/coin-modules/coin-icon/.unimportedrc.json
new file mode 100644
index 000000000000..caa4bde96225
--- /dev/null
+++ b/libs/coin-modules/coin-icon/.unimportedrc.json
@@ -0,0 +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/serialization.ts",
+ "src/specs.ts",
+ "src/transaction.ts"
+ ],
+ "ignoreUnimported": []
+}
diff --git a/libs/coin-modules/coin-icon/CHANGELOG.md b/libs/coin-modules/coin-icon/CHANGELOG.md
new file mode 100644
index 000000000000..f561e0993c1d
--- /dev/null
+++ b/libs/coin-modules/coin-icon/CHANGELOG.md
@@ -0,0 +1,483 @@
+# @ledgerhq/coin-algorand
+
+## 0.4.0
+
+### Minor Changes
+
+- [#6172](https://github.com/LedgerHQ/ledger-live/pull/6172) [`7fb3eb2`](https://github.com/LedgerHQ/ledger-live/commit/7fb3eb266acdca143c94d2fce74329809ebfbb79) Thanks [@sprohaszka-ledger](https://github.com/sprohaszka-ledger)! - Remove API injection in coin-module
+
+### Patch Changes
+
+- Updated dependencies [[`4744c31`](https://github.com/LedgerHQ/ledger-live/commit/4744c3136021f1f47ad1617f2c84f47ac0647370), [`0dd1546`](https://github.com/LedgerHQ/ledger-live/commit/0dd15467070cbf7fcbb9d9055a4535f6a25b2ad0), [`f456d69`](https://github.com/LedgerHQ/ledger-live/commit/f456d69a2f64b6a217d3c1d9c6a531f31c2817a8), [`4715e4c`](https://github.com/LedgerHQ/ledger-live/commit/4715e4c411fa2396330ebcb810aeb6bfc9892e88), [`6de15bc`](https://github.com/LedgerHQ/ledger-live/commit/6de15bc96e8b97a2a6815cf3fb1da874f7044b49), [`9c83695`](https://github.com/LedgerHQ/ledger-live/commit/9c8369580b91d82021d4ec28ad7a49dc4ba42e4f), [`2fd465e`](https://github.com/LedgerHQ/ledger-live/commit/2fd465ee730b11594d231503cfb940b984fa2f5a), [`dfac39b`](https://github.com/LedgerHQ/ledger-live/commit/dfac39b2086f0475d1bc8065032bfe27cbf424f6), [`7fb3eb2`](https://github.com/LedgerHQ/ledger-live/commit/7fb3eb266acdca143c94d2fce74329809ebfbb79), [`dfac39b`](https://github.com/LedgerHQ/ledger-live/commit/dfac39b2086f0475d1bc8065032bfe27cbf424f6), [`b34f5cd`](https://github.com/LedgerHQ/ledger-live/commit/b34f5cdda0b7bf34750d258cc8b1c91304516360), [`d870e90`](https://github.com/LedgerHQ/ledger-live/commit/d870e904a0dde5f8abf05f930f5f545828eccbc9), [`dfac39b`](https://github.com/LedgerHQ/ledger-live/commit/dfac39b2086f0475d1bc8065032bfe27cbf424f6), [`2e5185b`](https://github.com/LedgerHQ/ledger-live/commit/2e5185b3dba497c956272068128e49db72e8af2a)]:
+ - @ledgerhq/live-env@2.0.0
+ - @ledgerhq/errors@6.16.3
+ - @ledgerhq/cryptoassets@12.0.0
+ - @ledgerhq/types-live@6.45.0
+ - @ledgerhq/coin-framework@0.11.3
+ - @ledgerhq/live-network@1.2.0
+ - @ledgerhq/types-cryptoassets@7.10.0
+ - @ledgerhq/devices@8.2.2
+
+## 0.4.0-next.0
+
+### Minor Changes
+
+- [#6172](https://github.com/LedgerHQ/ledger-live/pull/6172) [`7fb3eb2`](https://github.com/LedgerHQ/ledger-live/commit/7fb3eb266acdca143c94d2fce74329809ebfbb79) Thanks [@sprohaszka-ledger](https://github.com/sprohaszka-ledger)! - Remove API injection in coin-module
+
+### Patch Changes
+
+- Updated dependencies [[`4744c31`](https://github.com/LedgerHQ/ledger-live/commit/4744c3136021f1f47ad1617f2c84f47ac0647370), [`0dd1546`](https://github.com/LedgerHQ/ledger-live/commit/0dd15467070cbf7fcbb9d9055a4535f6a25b2ad0), [`f456d69`](https://github.com/LedgerHQ/ledger-live/commit/f456d69a2f64b6a217d3c1d9c6a531f31c2817a8), [`4715e4c`](https://github.com/LedgerHQ/ledger-live/commit/4715e4c411fa2396330ebcb810aeb6bfc9892e88), [`6de15bc`](https://github.com/LedgerHQ/ledger-live/commit/6de15bc96e8b97a2a6815cf3fb1da874f7044b49), [`9c83695`](https://github.com/LedgerHQ/ledger-live/commit/9c8369580b91d82021d4ec28ad7a49dc4ba42e4f), [`2fd465e`](https://github.com/LedgerHQ/ledger-live/commit/2fd465ee730b11594d231503cfb940b984fa2f5a), [`dfac39b`](https://github.com/LedgerHQ/ledger-live/commit/dfac39b2086f0475d1bc8065032bfe27cbf424f6), [`7fb3eb2`](https://github.com/LedgerHQ/ledger-live/commit/7fb3eb266acdca143c94d2fce74329809ebfbb79), [`dfac39b`](https://github.com/LedgerHQ/ledger-live/commit/dfac39b2086f0475d1bc8065032bfe27cbf424f6), [`b34f5cd`](https://github.com/LedgerHQ/ledger-live/commit/b34f5cdda0b7bf34750d258cc8b1c91304516360), [`d870e90`](https://github.com/LedgerHQ/ledger-live/commit/d870e904a0dde5f8abf05f930f5f545828eccbc9), [`dfac39b`](https://github.com/LedgerHQ/ledger-live/commit/dfac39b2086f0475d1bc8065032bfe27cbf424f6), [`2e5185b`](https://github.com/LedgerHQ/ledger-live/commit/2e5185b3dba497c956272068128e49db72e8af2a)]:
+ - @ledgerhq/live-env@2.0.0-next.0
+ - @ledgerhq/errors@6.16.3-next.0
+ - @ledgerhq/cryptoassets@12.0.0-next.0
+ - @ledgerhq/types-live@6.45.0-next.0
+ - @ledgerhq/coin-framework@0.11.3-next.0
+ - @ledgerhq/live-network@1.2.0-next.0
+ - @ledgerhq/types-cryptoassets@7.10.0-next.0
+ - @ledgerhq/devices@8.2.2-next.0
+
+## 0.3.11
+
+### Patch Changes
+
+- Updated dependencies [[`884cfd6`](https://github.com/LedgerHQ/ledger-live/commit/884cfd64a1440d393fb983dfe361be9c78f3b81c), [`3e28615`](https://github.com/LedgerHQ/ledger-live/commit/3e28615a8d5edbec3eff1e93207bf0e9d017666a)]:
+ - @ledgerhq/cryptoassets@11.4.1
+ - @ledgerhq/live-env@1.0.1
+ - @ledgerhq/coin-framework@0.11.2
+
+## 0.3.11-hotfix.1
+
+### Patch Changes
+
+- Updated dependencies [[`884cfd6`](https://github.com/LedgerHQ/ledger-live/commit/884cfd64a1440d393fb983dfe361be9c78f3b81c)]:
+ - @ledgerhq/cryptoassets@11.4.1-hotfix.0
+ - @ledgerhq/coin-framework@0.11.2-hotfix.1
+
+## 0.3.11-hotfix.0
+
+### Patch Changes
+
+- Updated dependencies [[`3e28615`](https://github.com/LedgerHQ/ledger-live/commit/3e28615a8d5edbec3eff1e93207bf0e9d017666a)]:
+ - @ledgerhq/live-env@1.0.1-hotfix.0
+ - @ledgerhq/coin-framework@0.11.2-hotfix.0
+
+## 0.3.10
+
+### Patch Changes
+
+- Updated dependencies [[`c18a0cf`](https://github.com/LedgerHQ/ledger-live/commit/c18a0cfdce5d1e44faf8d8bd5659ebdae38533fa), [`ee88785`](https://github.com/LedgerHQ/ledger-live/commit/ee8878515671241ce1037362af5e8f7799b3673a), [`8d99b81`](https://github.com/LedgerHQ/ledger-live/commit/8d99b81feaf5e8d46e0c26f34bc70b709a7e3c14), [`43eea9e`](https://github.com/LedgerHQ/ledger-live/commit/43eea9e8076f2c9d5aeb0f8a3b0738e97b3152c8), [`1cb83b5`](https://github.com/LedgerHQ/ledger-live/commit/1cb83b5baa4603e22f391609438e349ca0ce9b0e), [`c217a6c`](https://github.com/LedgerHQ/ledger-live/commit/c217a6cfa59218278d25d2d987a06dfb33977cd9)]:
+ - @ledgerhq/live-env@1.0.0
+ - @ledgerhq/errors@6.16.2
+ - @ledgerhq/coin-framework@0.11.1
+ - @ledgerhq/types-live@6.44.1
+ - @ledgerhq/devices@8.2.1
+
+## 0.3.10-next.0
+
+### Patch Changes
+
+- Updated dependencies [[`4744c31`](https://github.com/LedgerHQ/ledger-live/commit/4744c3136021f1f47ad1617f2c84f47ac0647370), [`f456d69`](https://github.com/LedgerHQ/ledger-live/commit/f456d69a2f64b6a217d3c1d9c6a531f31c2817a8), [`9c83695`](https://github.com/LedgerHQ/ledger-live/commit/9c8369580b91d82021d4ec28ad7a49dc4ba42e4f), [`2fd465e`](https://github.com/LedgerHQ/ledger-live/commit/2fd465ee730b11594d231503cfb940b984fa2f5a), [`b34f5cd`](https://github.com/LedgerHQ/ledger-live/commit/b34f5cdda0b7bf34750d258cc8b1c91304516360), [`d870e90`](https://github.com/LedgerHQ/ledger-live/commit/d870e904a0dde5f8abf05f930f5f545828eccbc9)]:
+ - @ledgerhq/live-env@1.0.0-next.0
+ - @ledgerhq/errors@6.16.2-next.0
+ - @ledgerhq/coin-framework@0.11.1-next.0
+ - @ledgerhq/types-live@6.44.1-next.0
+ - @ledgerhq/devices@8.2.1-next.0
+
+## 0.3.9
+
+### Patch Changes
+
+- Updated dependencies [[`fc2cf04`](https://github.com/LedgerHQ/ledger-live/commit/fc2cf04c8d3cd55503ea19aeb21fd12ee55046f6), [`a4a72da`](https://github.com/LedgerHQ/ledger-live/commit/a4a72da33ddfefd5ba69ac4d9ecb33d7775583f1), [`acc0605`](https://github.com/LedgerHQ/ledger-live/commit/acc06050b622f8d4265be9f962c6c83b1fbaefd5), [`2358e87`](https://github.com/LedgerHQ/ledger-live/commit/2358e8748d9ae9398cfc05a0ec20a6b191fc7324), [`65772fb`](https://github.com/LedgerHQ/ledger-live/commit/65772fbcc1e6887d60ca585147123d356914ba56), [`69bbdce`](https://github.com/LedgerHQ/ledger-live/commit/69bbdce5c88d69248cbddb94ac4627334c1df626), [`b74faea`](https://github.com/LedgerHQ/ledger-live/commit/b74faea05f856860a253c5b94a9333810a3446ca), [`0375de1`](https://github.com/LedgerHQ/ledger-live/commit/0375de19ca909b0b013992c114b0fa2ead2a08f3)]:
+ - @ledgerhq/cryptoassets@11.4.0
+ - @ledgerhq/types-live@6.44.0
+ - @ledgerhq/coin-framework@0.11.0
+ - @ledgerhq/types-cryptoassets@7.9.0
+ - @ledgerhq/live-env@0.9.0
+
+## 0.3.9-next.0
+
+### Patch Changes
+
+- Updated dependencies [[`fc2cf04`](https://github.com/LedgerHQ/ledger-live/commit/fc2cf04c8d3cd55503ea19aeb21fd12ee55046f6), [`a4a72da`](https://github.com/LedgerHQ/ledger-live/commit/a4a72da33ddfefd5ba69ac4d9ecb33d7775583f1), [`acc0605`](https://github.com/LedgerHQ/ledger-live/commit/acc06050b622f8d4265be9f962c6c83b1fbaefd5), [`2358e87`](https://github.com/LedgerHQ/ledger-live/commit/2358e8748d9ae9398cfc05a0ec20a6b191fc7324), [`65772fb`](https://github.com/LedgerHQ/ledger-live/commit/65772fbcc1e6887d60ca585147123d356914ba56), [`69bbdce`](https://github.com/LedgerHQ/ledger-live/commit/69bbdce5c88d69248cbddb94ac4627334c1df626), [`b74faea`](https://github.com/LedgerHQ/ledger-live/commit/b74faea05f856860a253c5b94a9333810a3446ca), [`0375de1`](https://github.com/LedgerHQ/ledger-live/commit/0375de19ca909b0b013992c114b0fa2ead2a08f3)]:
+ - @ledgerhq/cryptoassets@11.4.0-next.0
+ - @ledgerhq/types-live@6.44.0-next.0
+ - @ledgerhq/coin-framework@0.11.0-next.0
+ - @ledgerhq/types-cryptoassets@7.9.0-next.0
+ - @ledgerhq/live-env@0.9.0-next.0
+
+## 0.3.8
+
+### Patch Changes
+
+- Updated dependencies [[`d49f444`](https://github.com/LedgerHQ/ledger-live/commit/d49f44417fd175affe71da589c0ca380e88fbb35)]:
+ - @ledgerhq/cryptoassets@11.3.1
+ - @ledgerhq/coin-framework@0.10.1
+
+## 0.3.8-next.0
+
+### Patch Changes
+
+- Updated dependencies [[`d49f444`](https://github.com/LedgerHQ/ledger-live/commit/d49f44417fd175affe71da589c0ca380e88fbb35)]:
+ - @ledgerhq/cryptoassets@11.3.1-next.0
+ - @ledgerhq/coin-framework@0.10.1-next.0
+
+## 0.3.7
+
+### Patch Changes
+
+- Updated dependencies [[`1cb527b`](https://github.com/LedgerHQ/ledger-live/commit/1cb527b9a0b03f23a921a446c64f71ab5c9e9013), [`b7d58b4`](https://github.com/LedgerHQ/ledger-live/commit/b7d58b4cf3aa041b7b794d9af6f0b89bbc0df633), [`44ee889`](https://github.com/LedgerHQ/ledger-live/commit/44ee88944571c73afb105349f5f28b82e8be262d), [`13d9cbe`](https://github.com/LedgerHQ/ledger-live/commit/13d9cbe9a4afbf3ccd532a33e4ada3685d9d646a), [`618307f`](https://github.com/LedgerHQ/ledger-live/commit/618307f92899af07f4c8ad97c67df483492e3d9d), [`0f5292a`](https://github.com/LedgerHQ/ledger-live/commit/0f5292af8feaa517f36ec35155d813b17c4f66e9), [`52a3732`](https://github.com/LedgerHQ/ledger-live/commit/52a373273dee3b2cb5a3e8d2d4b05f90616d71a2), [`4d1aade`](https://github.com/LedgerHQ/ledger-live/commit/4d1aade53cd33f8e7548ce340f54fbb834bdcdcb), [`eb5ac4d`](https://github.com/LedgerHQ/ledger-live/commit/eb5ac4dca430654f5f86854025fddddab4261a29), [`c8172ab`](https://github.com/LedgerHQ/ledger-live/commit/c8172abc5c052a753b93be8b6c9cfd88ce0dd64a), [`5d03bf5`](https://github.com/LedgerHQ/ledger-live/commit/5d03bf514fcf7aea91dc8beae0cd503ed6b4ab3c), [`9d35080`](https://github.com/LedgerHQ/ledger-live/commit/9d35080944a6a63c78f54a545734f4cf3cbded63), [`3adea7a`](https://github.com/LedgerHQ/ledger-live/commit/3adea7a7ad66080b5e6e4407071a976644158d04), [`2edfa53`](https://github.com/LedgerHQ/ledger-live/commit/2edfa533bccafbfd8a61aea0f5422c0db79825ea), [`be22d72`](https://github.com/LedgerHQ/ledger-live/commit/be22d724cf5499fe4958bfb3b5f763ffaf0d0446), [`52a3732`](https://github.com/LedgerHQ/ledger-live/commit/52a373273dee3b2cb5a3e8d2d4b05f90616d71a2), [`6dea540`](https://github.com/LedgerHQ/ledger-live/commit/6dea54057f67162a1f556661afae16e0422f7ac3), [`e70e345`](https://github.com/LedgerHQ/ledger-live/commit/e70e345bd21d4f5c82fbedfd4447aec0e866be5a)]:
+ - @ledgerhq/types-live@6.43.1
+ - @ledgerhq/cryptoassets@11.3.0
+ - @ledgerhq/coin-framework@0.10.0
+ - @ledgerhq/live-env@0.8.0
+ - @ledgerhq/errors@6.16.1
+ - @ledgerhq/devices@8.2.0
+
+## 0.3.7-next.0
+
+### Patch Changes
+
+- Updated dependencies [[`1cb527b`](https://github.com/LedgerHQ/ledger-live/commit/1cb527b9a0b03f23a921a446c64f71ab5c9e9013), [`b7d58b4`](https://github.com/LedgerHQ/ledger-live/commit/b7d58b4cf3aa041b7b794d9af6f0b89bbc0df633), [`44ee889`](https://github.com/LedgerHQ/ledger-live/commit/44ee88944571c73afb105349f5f28b82e8be262d), [`13d9cbe`](https://github.com/LedgerHQ/ledger-live/commit/13d9cbe9a4afbf3ccd532a33e4ada3685d9d646a), [`618307f`](https://github.com/LedgerHQ/ledger-live/commit/618307f92899af07f4c8ad97c67df483492e3d9d), [`0f5292a`](https://github.com/LedgerHQ/ledger-live/commit/0f5292af8feaa517f36ec35155d813b17c4f66e9), [`52a3732`](https://github.com/LedgerHQ/ledger-live/commit/52a373273dee3b2cb5a3e8d2d4b05f90616d71a2), [`4d1aade`](https://github.com/LedgerHQ/ledger-live/commit/4d1aade53cd33f8e7548ce340f54fbb834bdcdcb), [`eb5ac4d`](https://github.com/LedgerHQ/ledger-live/commit/eb5ac4dca430654f5f86854025fddddab4261a29), [`c8172ab`](https://github.com/LedgerHQ/ledger-live/commit/c8172abc5c052a753b93be8b6c9cfd88ce0dd64a), [`5d03bf5`](https://github.com/LedgerHQ/ledger-live/commit/5d03bf514fcf7aea91dc8beae0cd503ed6b4ab3c), [`9d35080`](https://github.com/LedgerHQ/ledger-live/commit/9d35080944a6a63c78f54a545734f4cf3cbded63), [`3adea7a`](https://github.com/LedgerHQ/ledger-live/commit/3adea7a7ad66080b5e6e4407071a976644158d04), [`2edfa53`](https://github.com/LedgerHQ/ledger-live/commit/2edfa533bccafbfd8a61aea0f5422c0db79825ea), [`be22d72`](https://github.com/LedgerHQ/ledger-live/commit/be22d724cf5499fe4958bfb3b5f763ffaf0d0446), [`52a3732`](https://github.com/LedgerHQ/ledger-live/commit/52a373273dee3b2cb5a3e8d2d4b05f90616d71a2), [`6dea540`](https://github.com/LedgerHQ/ledger-live/commit/6dea54057f67162a1f556661afae16e0422f7ac3), [`e70e345`](https://github.com/LedgerHQ/ledger-live/commit/e70e345bd21d4f5c82fbedfd4447aec0e866be5a)]:
+ - @ledgerhq/types-live@6.43.1-next.0
+ - @ledgerhq/cryptoassets@11.3.0-next.0
+ - @ledgerhq/coin-framework@0.10.0-next.0
+ - @ledgerhq/live-env@0.8.0-next.0
+ - @ledgerhq/errors@6.16.1-next.0
+ - @ledgerhq/devices@8.2.0-next.0
+
+## 0.3.6
+
+### Patch Changes
+
+- Updated dependencies [[`48487abd29`](https://github.com/LedgerHQ/ledger-live/commit/48487abd297e41629c6725bc0ac9d69bfeaa74d3), [`08dde174fd`](https://github.com/LedgerHQ/ledger-live/commit/08dde174fdeaadbce85dcd914383839f788f21dd), [`771c9d3c1d`](https://github.com/LedgerHQ/ledger-live/commit/771c9d3c1d138ddd68da2e4f9738e2c41ecaf81b), [`c5981ae341`](https://github.com/LedgerHQ/ledger-live/commit/c5981ae3411abc4c8594adf2efcb52aacddac143), [`317685e696`](https://github.com/LedgerHQ/ledger-live/commit/317685e69678e6fe1f489f0c071e7613d329d389), [`a4299c5d62`](https://github.com/LedgerHQ/ledger-live/commit/a4299c5d629cd56e6e6795adaa14978ae2b90f42), [`b4e7201b0b`](https://github.com/LedgerHQ/ledger-live/commit/b4e7201b0b70d146de7d936ff2c9e9e443164243), [`e63205b850`](https://github.com/LedgerHQ/ledger-live/commit/e63205b85071538ed2431157a12818d7a8f0ffa9), [`5964e30bed`](https://github.com/LedgerHQ/ledger-live/commit/5964e30bed11d64a3b7401c6ab51ffc1ad4c427c), [`4a283060bf`](https://github.com/LedgerHQ/ledger-live/commit/4a283060bf2e837d73c6c1cb5d89f890a4e4b931)]:
+ - @ledgerhq/types-live@6.43.0
+ - @ledgerhq/types-cryptoassets@7.8.0
+ - @ledgerhq/cryptoassets@11.2.0
+ - @ledgerhq/errors@6.16.0
+ - @ledgerhq/coin-framework@0.9.0
+ - @ledgerhq/live-env@0.7.0
+ - @ledgerhq/devices@8.1.0
+ - @ledgerhq/live-promise@0.0.3
+
+## 0.3.6-next.1
+
+### Patch Changes
+
+- Updated dependencies [[`4a283060bf`](https://github.com/LedgerHQ/ledger-live/commit/4a283060bf2e837d73c6c1cb5d89f890a4e4b931)]:
+ - @ledgerhq/cryptoassets@11.2.0-next.1
+ - @ledgerhq/coin-framework@0.9.0-next.1
+
+## 0.3.6-next.0
+
+### Patch Changes
+
+- Updated dependencies [[`48487abd29`](https://github.com/LedgerHQ/ledger-live/commit/48487abd297e41629c6725bc0ac9d69bfeaa74d3), [`08dde174fd`](https://github.com/LedgerHQ/ledger-live/commit/08dde174fdeaadbce85dcd914383839f788f21dd), [`771c9d3c1d`](https://github.com/LedgerHQ/ledger-live/commit/771c9d3c1d138ddd68da2e4f9738e2c41ecaf81b), [`c5981ae341`](https://github.com/LedgerHQ/ledger-live/commit/c5981ae3411abc4c8594adf2efcb52aacddac143), [`317685e696`](https://github.com/LedgerHQ/ledger-live/commit/317685e69678e6fe1f489f0c071e7613d329d389), [`a4299c5d62`](https://github.com/LedgerHQ/ledger-live/commit/a4299c5d629cd56e6e6795adaa14978ae2b90f42), [`b4e7201b0b`](https://github.com/LedgerHQ/ledger-live/commit/b4e7201b0b70d146de7d936ff2c9e9e443164243), [`e63205b850`](https://github.com/LedgerHQ/ledger-live/commit/e63205b85071538ed2431157a12818d7a8f0ffa9), [`5964e30bed`](https://github.com/LedgerHQ/ledger-live/commit/5964e30bed11d64a3b7401c6ab51ffc1ad4c427c)]:
+ - @ledgerhq/types-live@6.43.0-next.0
+ - @ledgerhq/types-cryptoassets@7.8.0-next.0
+ - @ledgerhq/cryptoassets@11.2.0-next.0
+ - @ledgerhq/errors@6.16.0-next.0
+ - @ledgerhq/coin-framework@0.9.0-next.0
+ - @ledgerhq/live-env@0.7.0-next.0
+ - @ledgerhq/devices@8.1.0-next.0
+ - @ledgerhq/live-promise@0.0.3-next.0
+
+## 0.3.5
+
+### Patch Changes
+
+- Updated dependencies [[`3dc4937cc0`](https://github.com/LedgerHQ/ledger-live/commit/3dc4937cc0c77f6dc40ac7c628e9ab165dfb899f), [`95cf52eb66`](https://github.com/LedgerHQ/ledger-live/commit/95cf52eb66769228feb45dd5e799c444e80c5072), [`f5a5c315ea`](https://github.com/LedgerHQ/ledger-live/commit/f5a5c315ea2200cd5b52ef3a0b377d1327b1144e), [`17ba334e47`](https://github.com/LedgerHQ/ledger-live/commit/17ba334e47b901e34fbb083396aa3f9952e5233e), [`f83e060bf4`](https://github.com/LedgerHQ/ledger-live/commit/f83e060bf474a6b6133406eff49cb054e813046f), [`9e2d32aec4`](https://github.com/LedgerHQ/ledger-live/commit/9e2d32aec4ebd8774880f94e3ef0e805ebb172ac), [`df5c9ae02a`](https://github.com/LedgerHQ/ledger-live/commit/df5c9ae02a604ddba13ddc64caf8d9ad079c303d), [`54b1d185c9`](https://github.com/LedgerHQ/ledger-live/commit/54b1d185c9df5ae84dc7e85d58249c06550df5f1), [`9b49ff233c`](https://github.com/LedgerHQ/ledger-live/commit/9b49ff233ccfad68c98d15cd648927dee12a8b0b), [`4d6fa0772e`](https://github.com/LedgerHQ/ledger-live/commit/4d6fa0772e19cdbd4b432fafa43621c42e2a5fdd), [`b259781b72`](https://github.com/LedgerHQ/ledger-live/commit/b259781b7212aa7758437640e7c48c5d17b0fa79)]:
+ - @ledgerhq/types-live@6.42.0
+ - @ledgerhq/types-cryptoassets@7.7.0
+ - @ledgerhq/cryptoassets@11.1.0
+ - @ledgerhq/coin-framework@0.8.1
+ - @ledgerhq/live-env@0.6.1
+ - @ledgerhq/errors@6.15.0
+ - @ledgerhq/devices@8.0.8
+ - @ledgerhq/live-promise@0.0.2
+
+## 0.3.5-next.0
+
+### Patch Changes
+
+- Updated dependencies [[`3dc4937cc0`](https://github.com/LedgerHQ/ledger-live/commit/3dc4937cc0c77f6dc40ac7c628e9ab165dfb899f), [`95cf52eb66`](https://github.com/LedgerHQ/ledger-live/commit/95cf52eb66769228feb45dd5e799c444e80c5072), [`f5a5c315ea`](https://github.com/LedgerHQ/ledger-live/commit/f5a5c315ea2200cd5b52ef3a0b377d1327b1144e), [`17ba334e47`](https://github.com/LedgerHQ/ledger-live/commit/17ba334e47b901e34fbb083396aa3f9952e5233e), [`f83e060bf4`](https://github.com/LedgerHQ/ledger-live/commit/f83e060bf474a6b6133406eff49cb054e813046f), [`9e2d32aec4`](https://github.com/LedgerHQ/ledger-live/commit/9e2d32aec4ebd8774880f94e3ef0e805ebb172ac), [`df5c9ae02a`](https://github.com/LedgerHQ/ledger-live/commit/df5c9ae02a604ddba13ddc64caf8d9ad079c303d), [`54b1d185c9`](https://github.com/LedgerHQ/ledger-live/commit/54b1d185c9df5ae84dc7e85d58249c06550df5f1), [`9b49ff233c`](https://github.com/LedgerHQ/ledger-live/commit/9b49ff233ccfad68c98d15cd648927dee12a8b0b), [`4d6fa0772e`](https://github.com/LedgerHQ/ledger-live/commit/4d6fa0772e19cdbd4b432fafa43621c42e2a5fdd), [`b259781b72`](https://github.com/LedgerHQ/ledger-live/commit/b259781b7212aa7758437640e7c48c5d17b0fa79)]:
+ - @ledgerhq/types-live@6.42.0-next.0
+ - @ledgerhq/types-cryptoassets@7.7.0-next.0
+ - @ledgerhq/cryptoassets@11.1.0-next.0
+ - @ledgerhq/coin-framework@0.8.1-next.0
+ - @ledgerhq/live-env@0.6.1-next.0
+ - @ledgerhq/errors@6.15.0-next.0
+ - @ledgerhq/devices@8.0.8-next.0
+ - @ledgerhq/live-promise@0.0.2-next.0
+
+## 0.3.4
+
+### Patch Changes
+
+- Updated dependencies [[`ce18546c0a`](https://github.com/LedgerHQ/ledger-live/commit/ce18546c0a0b9dd5ed78b1745cac19b7eef7b5eb), [`3b4f7501cc`](https://github.com/LedgerHQ/ledger-live/commit/3b4f7501cc5f09be94a2994f20f9998898682975), [`fbeebfe04b`](https://github.com/LedgerHQ/ledger-live/commit/fbeebfe04b297b33ec258440b694cdfb6213af24)]:
+ - @ledgerhq/types-live@6.41.1
+ - @ledgerhq/coin-framework@0.8.0
+ - @ledgerhq/cryptoassets@11.0.1
+
+## 0.3.4-hotfix.2
+
+### Patch Changes
+
+- Updated dependencies [[`fbeebfe04b`](https://github.com/LedgerHQ/ledger-live/commit/fbeebfe04b297b33ec258440b694cdfb6213af24)]:
+ - @ledgerhq/cryptoassets@11.0.1-hotfix.0
+ - @ledgerhq/coin-framework@0.8.0-hotfix.2
+
+## 0.3.4-hotfix.1
+
+### Patch Changes
+
+- Updated dependencies [[`3b4f7501cc`](https://github.com/LedgerHQ/ledger-live/commit/3b4f7501cc5f09be94a2994f20f9998898682975)]:
+ - @ledgerhq/coin-framework@0.8.0-hotfix.1
+
+## 0.3.4-hotfix.0
+
+### Patch Changes
+
+- Updated dependencies [[`ce18546c0a`](https://github.com/LedgerHQ/ledger-live/commit/ce18546c0a0b9dd5ed78b1745cac19b7eef7b5eb)]:
+ - @ledgerhq/types-live@6.41.1-hotfix.0
+ - @ledgerhq/coin-framework@0.7.1-hotfix.0
+
+## 0.3.3
+
+### Patch Changes
+
+- Updated dependencies [[`72288402ec`](https://github.com/LedgerHQ/ledger-live/commit/72288402ec70f9159022505cb3187e63b24df450), [`f527d1bb5a`](https://github.com/LedgerHQ/ledger-live/commit/f527d1bb5a2888a916f761d43d2ba5093eaa3e3f), [`a134f28e9d`](https://github.com/LedgerHQ/ledger-live/commit/a134f28e9d220d172148619ed281d4ca897d5532), [`49ea3fd98b`](https://github.com/LedgerHQ/ledger-live/commit/49ea3fd98ba1e1e0ed54d29ab5fdc71c4918183f), [`533278e2c4`](https://github.com/LedgerHQ/ledger-live/commit/533278e2c40ee764ecb87d4430fa6650f251ff0c), [`70e4277bc9`](https://github.com/LedgerHQ/ledger-live/commit/70e4277bc9dda253b894bdae5f2c8a5f43a9a64e)]:
+ - @ledgerhq/cryptoassets@11.0.0
+ - @ledgerhq/types-cryptoassets@7.6.0
+ - @ledgerhq/types-live@6.41.0
+ - @ledgerhq/coin-framework@0.7.0
+ - @ledgerhq/live-env@0.6.0
+
+## 0.3.2
+
+### Patch Changes
+
+- Updated dependencies [[`6c83521fee`](https://github.com/LedgerHQ/ledger-live/commit/6c83521fee8da656858630c1cb37a5af95df3362)]:
+ - @ledgerhq/types-cryptoassets@7.5.0
+ - @ledgerhq/cryptoassets@9.13.0
+ - @ledgerhq/types-live@6.40.0
+ - @ledgerhq/coin-framework@0.5.4
+
+## 0.3.1
+
+### Patch Changes
+
+- Updated dependencies [[`e0cc3a0841`](https://github.com/LedgerHQ/ledger-live/commit/e0cc3a08415de84b9d3ce828444248a043a9d699), [`18b4a47b48`](https://github.com/LedgerHQ/ledger-live/commit/18b4a47b4878a23695a50096b7770134883b8a2e)]:
+ - @ledgerhq/coin-framework@0.5.3
+ - @ledgerhq/cryptoassets@9.12.1
+
+## 0.3.0
+
+### Minor Changes
+
+- [#4235](https://github.com/LedgerHQ/ledger-live/pull/4235) [`8e9df43a0c`](https://github.com/LedgerHQ/ledger-live/commit/8e9df43a0cd00a2065b494439f300f96724b8eb8) Thanks [@haammar-ledger](https://github.com/haammar-ledger)! - Better typing for the Operation.extra field
+
+### Patch Changes
+
+- Updated dependencies [[`8e9df43a0c`](https://github.com/LedgerHQ/ledger-live/commit/8e9df43a0cd00a2065b494439f300f96724b8eb8), [`fde2fe79f1`](https://github.com/LedgerHQ/ledger-live/commit/fde2fe79f1df69fffe80763cd6d9792fe9de1262), [`f6f70ba0e8`](https://github.com/LedgerHQ/ledger-live/commit/f6f70ba0e85c7898cdeec19402b1eadfde6a2206), [`45be23c776`](https://github.com/LedgerHQ/ledger-live/commit/45be23c77666697dbe395f836ab592062173d5cb), [`6375c250a9`](https://github.com/LedgerHQ/ledger-live/commit/6375c250a9a58b33e3dd1d6c96a96c7e46150298), [`0d9ad3599b`](https://github.com/LedgerHQ/ledger-live/commit/0d9ad3599bce8872fde97d27c977ab24445afc3a)]:
+ - @ledgerhq/types-live@6.39.0
+ - @ledgerhq/live-env@0.5.0
+ - @ledgerhq/coin-framework@0.5.2
+ - @ledgerhq/cryptoassets@9.12.0
+
+## 0.3.0-next.0
+
+### Minor Changes
+
+- [#4235](https://github.com/LedgerHQ/ledger-live/pull/4235) [`8e9df43a0c`](https://github.com/LedgerHQ/ledger-live/commit/8e9df43a0cd00a2065b494439f300f96724b8eb8) Thanks [@haammar-ledger](https://github.com/haammar-ledger)! - Better typing for the Operation.extra field
+
+### Patch Changes
+
+- Updated dependencies [[`8e9df43a0c`](https://github.com/LedgerHQ/ledger-live/commit/8e9df43a0cd00a2065b494439f300f96724b8eb8), [`fde2fe79f1`](https://github.com/LedgerHQ/ledger-live/commit/fde2fe79f1df69fffe80763cd6d9792fe9de1262), [`f6f70ba0e8`](https://github.com/LedgerHQ/ledger-live/commit/f6f70ba0e85c7898cdeec19402b1eadfde6a2206), [`45be23c776`](https://github.com/LedgerHQ/ledger-live/commit/45be23c77666697dbe395f836ab592062173d5cb), [`6375c250a9`](https://github.com/LedgerHQ/ledger-live/commit/6375c250a9a58b33e3dd1d6c96a96c7e46150298), [`0d9ad3599b`](https://github.com/LedgerHQ/ledger-live/commit/0d9ad3599bce8872fde97d27c977ab24445afc3a)]:
+ - @ledgerhq/types-live@6.39.0-next.0
+ - @ledgerhq/live-env@0.5.0-next.0
+ - @ledgerhq/coin-framework@0.5.2-next.0
+ - @ledgerhq/cryptoassets@9.12.0-next.0
+
+## 0.2.6
+
+### Patch Changes
+
+- Updated dependencies [[`5bbcea12f9`](https://github.com/LedgerHQ/ledger-live/commit/5bbcea12f93e3cda41705a4d61d50845628a6de6), [`95088eab45`](https://github.com/LedgerHQ/ledger-live/commit/95088eab45f6af919e347a605cefefb6d7705808), [`a61a43fc47`](https://github.com/LedgerHQ/ledger-live/commit/a61a43fc47399e969fa68539de6af51bfa41e921), [`4c2539a1d5`](https://github.com/LedgerHQ/ledger-live/commit/4c2539a1d5c9c01c0f9fa7cd1daf5a5a63c02996), [`229cf62304`](https://github.com/LedgerHQ/ledger-live/commit/229cf623043b29eefed3e8e37a102325fa6e0387), [`3455944496`](https://github.com/LedgerHQ/ledger-live/commit/34559444969ce1571ff4c54f33feb7f3fb59a33a)]:
+ - @ledgerhq/types-live@6.38.1
+ - @ledgerhq/live-env@0.4.2
+ - @ledgerhq/errors@6.14.0
+ - @ledgerhq/cryptoassets@9.11.1
+ - @ledgerhq/coin-framework@0.5.1
+ - @ledgerhq/devices@8.0.7
+
+## 0.2.6-next.0
+
+### Patch Changes
+
+- Updated dependencies [[`5bbcea12f9`](https://github.com/LedgerHQ/ledger-live/commit/5bbcea12f93e3cda41705a4d61d50845628a6de6), [`95088eab45`](https://github.com/LedgerHQ/ledger-live/commit/95088eab45f6af919e347a605cefefb6d7705808), [`a61a43fc47`](https://github.com/LedgerHQ/ledger-live/commit/a61a43fc47399e969fa68539de6af51bfa41e921), [`4c2539a1d5`](https://github.com/LedgerHQ/ledger-live/commit/4c2539a1d5c9c01c0f9fa7cd1daf5a5a63c02996), [`229cf62304`](https://github.com/LedgerHQ/ledger-live/commit/229cf623043b29eefed3e8e37a102325fa6e0387), [`3455944496`](https://github.com/LedgerHQ/ledger-live/commit/34559444969ce1571ff4c54f33feb7f3fb59a33a)]:
+ - @ledgerhq/types-live@6.38.1-next.0
+ - @ledgerhq/live-env@0.4.2-next.0
+ - @ledgerhq/errors@6.14.0-next.0
+ - @ledgerhq/cryptoassets@9.11.1-next.0
+ - @ledgerhq/coin-framework@0.5.1-next.0
+ - @ledgerhq/devices@8.0.7-next.0
+
+## 0.2.5
+
+### Patch Changes
+
+- Updated dependencies [[`1263b7a9c1`](https://github.com/LedgerHQ/ledger-live/commit/1263b7a9c1916da81ad55bb2ca1e804cff5f89e2), [`770842cdbe`](https://github.com/LedgerHQ/ledger-live/commit/770842cdbe94c629b6844f93d1b5d94d381931b1), [`e5f9cc46d6`](https://github.com/LedgerHQ/ledger-live/commit/e5f9cc46d69b82ad7267296b350e9d97a47f9e86), [`6a88b7f8a6`](https://github.com/LedgerHQ/ledger-live/commit/6a88b7f8a6b7c732be0c945131b6c1d9b3937cc1), [`cfbff52724`](https://github.com/LedgerHQ/ledger-live/commit/cfbff527241534aba69bff3d86733b50a14eb4ce), [`b6e50932af`](https://github.com/LedgerHQ/ledger-live/commit/b6e50932afac6acc2d2f9fa9ed10b77a62378e03), [`6a88b7f8a6`](https://github.com/LedgerHQ/ledger-live/commit/6a88b7f8a6b7c732be0c945131b6c1d9b3937cc1), [`95ac67a5e0`](https://github.com/LedgerHQ/ledger-live/commit/95ac67a5e0359b03c7d82c34fd1f2f3b6eb7df22), [`c7c484acf0`](https://github.com/LedgerHQ/ledger-live/commit/c7c484acf01e9db8dc5a5507b62ffcb863c77ca4), [`374e339c27`](https://github.com/LedgerHQ/ledger-live/commit/374e339c27e317656d01463a822898ad3a60df85), [`66769a98e6`](https://github.com/LedgerHQ/ledger-live/commit/66769a98e69f2b8156417e464e90d9162272b470)]:
+ - @ledgerhq/cryptoassets@9.11.0
+ - @ledgerhq/live-env@0.4.1
+ - @ledgerhq/types-cryptoassets@7.4.0
+ - @ledgerhq/types-live@6.38.0
+ - @ledgerhq/coin-framework@0.5.0
+ - @ledgerhq/errors@6.13.1
+ - @ledgerhq/devices@8.0.6
+
+## 0.2.5-next.0
+
+### Patch Changes
+
+- Updated dependencies [[`1263b7a9c1`](https://github.com/LedgerHQ/ledger-live/commit/1263b7a9c1916da81ad55bb2ca1e804cff5f89e2), [`770842cdbe`](https://github.com/LedgerHQ/ledger-live/commit/770842cdbe94c629b6844f93d1b5d94d381931b1), [`e5f9cc46d6`](https://github.com/LedgerHQ/ledger-live/commit/e5f9cc46d69b82ad7267296b350e9d97a47f9e86), [`6a88b7f8a6`](https://github.com/LedgerHQ/ledger-live/commit/6a88b7f8a6b7c732be0c945131b6c1d9b3937cc1), [`cfbff52724`](https://github.com/LedgerHQ/ledger-live/commit/cfbff527241534aba69bff3d86733b50a14eb4ce), [`b6e50932af`](https://github.com/LedgerHQ/ledger-live/commit/b6e50932afac6acc2d2f9fa9ed10b77a62378e03), [`6a88b7f8a6`](https://github.com/LedgerHQ/ledger-live/commit/6a88b7f8a6b7c732be0c945131b6c1d9b3937cc1), [`95ac67a5e0`](https://github.com/LedgerHQ/ledger-live/commit/95ac67a5e0359b03c7d82c34fd1f2f3b6eb7df22), [`c7c484acf0`](https://github.com/LedgerHQ/ledger-live/commit/c7c484acf01e9db8dc5a5507b62ffcb863c77ca4), [`374e339c27`](https://github.com/LedgerHQ/ledger-live/commit/374e339c27e317656d01463a822898ad3a60df85), [`66769a98e6`](https://github.com/LedgerHQ/ledger-live/commit/66769a98e69f2b8156417e464e90d9162272b470)]:
+ - @ledgerhq/cryptoassets@9.11.0-next.0
+ - @ledgerhq/live-env@0.4.1-next.0
+ - @ledgerhq/types-cryptoassets@7.4.0-next.0
+ - @ledgerhq/types-live@6.38.0-next.0
+ - @ledgerhq/coin-framework@0.5.0-next.0
+ - @ledgerhq/errors@6.13.1-next.0
+ - @ledgerhq/devices@8.0.6-next.0
+
+## 0.2.4
+
+### Patch Changes
+
+- Updated dependencies [[`15e8abc482`](https://github.com/LedgerHQ/ledger-live/commit/15e8abc482b2b38e4808890f556097cf693359ec), [`15e8abc482`](https://github.com/LedgerHQ/ledger-live/commit/15e8abc482b2b38e4808890f556097cf693359ec), [`49182846de`](https://github.com/LedgerHQ/ledger-live/commit/49182846dee35ae9b3535c0c120e17d0eaecde70), [`15e8abc482`](https://github.com/LedgerHQ/ledger-live/commit/15e8abc482b2b38e4808890f556097cf693359ec), [`c660c4e389`](https://github.com/LedgerHQ/ledger-live/commit/c660c4e389ac200ef308cbc3882930d392375de3), [`2c28d5aab3`](https://github.com/LedgerHQ/ledger-live/commit/2c28d5aab36b8b0cf2cb2a50e02eac4c5a588e41), [`0f4293e9bf`](https://github.com/LedgerHQ/ledger-live/commit/0f4293e9bf9cac4c2a195efeb0831aab3d51933d), [`14cce73003`](https://github.com/LedgerHQ/ledger-live/commit/14cce7300333c51cbcdbd5a7e290ddc600c9f3a1), [`15e8abc482`](https://github.com/LedgerHQ/ledger-live/commit/15e8abc482b2b38e4808890f556097cf693359ec), [`bae3b64dd2`](https://github.com/LedgerHQ/ledger-live/commit/bae3b64dd2710a3743552600166be986e93d9099), [`9692adc2a6`](https://github.com/LedgerHQ/ledger-live/commit/9692adc2a6774feb4424fc7a984810918c946b1b), [`15e8abc482`](https://github.com/LedgerHQ/ledger-live/commit/15e8abc482b2b38e4808890f556097cf693359ec)]:
+ - @ledgerhq/live-env@0.4.0
+ - @ledgerhq/coin-framework@0.4.0
+ - @ledgerhq/cryptoassets@9.10.0
+ - @ledgerhq/errors@6.13.0
+ - @ledgerhq/types-live@6.37.0
+ - @ledgerhq/types-cryptoassets@7.3.1
+ - @ledgerhq/devices@8.0.5
+
+## 0.2.4-next.0
+
+### Patch Changes
+
+- Updated dependencies [[`15e8abc482`](https://github.com/LedgerHQ/ledger-live/commit/15e8abc482b2b38e4808890f556097cf693359ec), [`15e8abc482`](https://github.com/LedgerHQ/ledger-live/commit/15e8abc482b2b38e4808890f556097cf693359ec), [`49182846de`](https://github.com/LedgerHQ/ledger-live/commit/49182846dee35ae9b3535c0c120e17d0eaecde70), [`15e8abc482`](https://github.com/LedgerHQ/ledger-live/commit/15e8abc482b2b38e4808890f556097cf693359ec), [`c660c4e389`](https://github.com/LedgerHQ/ledger-live/commit/c660c4e389ac200ef308cbc3882930d392375de3), [`2c28d5aab3`](https://github.com/LedgerHQ/ledger-live/commit/2c28d5aab36b8b0cf2cb2a50e02eac4c5a588e41), [`0f4293e9bf`](https://github.com/LedgerHQ/ledger-live/commit/0f4293e9bf9cac4c2a195efeb0831aab3d51933d), [`14cce73003`](https://github.com/LedgerHQ/ledger-live/commit/14cce7300333c51cbcdbd5a7e290ddc600c9f3a1), [`15e8abc482`](https://github.com/LedgerHQ/ledger-live/commit/15e8abc482b2b38e4808890f556097cf693359ec), [`bae3b64dd2`](https://github.com/LedgerHQ/ledger-live/commit/bae3b64dd2710a3743552600166be986e93d9099), [`9692adc2a6`](https://github.com/LedgerHQ/ledger-live/commit/9692adc2a6774feb4424fc7a984810918c946b1b), [`15e8abc482`](https://github.com/LedgerHQ/ledger-live/commit/15e8abc482b2b38e4808890f556097cf693359ec)]:
+ - @ledgerhq/live-env@0.4.0-next.0
+ - @ledgerhq/coin-framework@0.4.0-next.0
+ - @ledgerhq/cryptoassets@9.10.0-next.0
+ - @ledgerhq/errors@6.13.0-next.0
+ - @ledgerhq/types-live@6.37.0-next.0
+ - @ledgerhq/types-cryptoassets@7.3.1-next.0
+ - @ledgerhq/devices@8.0.5-next.0
+
+## 0.2.3
+
+### Patch Changes
+
+- Updated dependencies [[`44192f2ab2`](https://github.com/LedgerHQ/ledger-live/commit/44192f2ab2857cbae2ef4a81ee9608d395dcd2b9), [`cb95f72c24`](https://github.com/LedgerHQ/ledger-live/commit/cb95f72c2415876ef88ca83fd2c4363a57669b92), [`be5f56b233`](https://github.com/LedgerHQ/ledger-live/commit/be5f56b2330c166323914b79fef37a3c05e0e13a), [`092cb8d317`](https://github.com/LedgerHQ/ledger-live/commit/092cb8d317fa7971e0f790b77f900ae3864d96c2), [`5af41b6fa1`](https://github.com/LedgerHQ/ledger-live/commit/5af41b6fa1e43037ccdb2df279c82e12ef3d2b1a)]:
+ - @ledgerhq/types-live@6.36.0
+ - @ledgerhq/cryptoassets@9.9.0
+ - @ledgerhq/types-cryptoassets@7.3.0
+ - @ledgerhq/live-env@0.3.1
+ - @ledgerhq/coin-framework@0.3.7
+
+## 0.2.3-next.0
+
+### Patch Changes
+
+- Updated dependencies [[`44192f2ab2`](https://github.com/LedgerHQ/ledger-live/commit/44192f2ab2857cbae2ef4a81ee9608d395dcd2b9), [`cb95f72c24`](https://github.com/LedgerHQ/ledger-live/commit/cb95f72c2415876ef88ca83fd2c4363a57669b92), [`be5f56b233`](https://github.com/LedgerHQ/ledger-live/commit/be5f56b2330c166323914b79fef37a3c05e0e13a), [`092cb8d317`](https://github.com/LedgerHQ/ledger-live/commit/092cb8d317fa7971e0f790b77f900ae3864d96c2), [`5af41b6fa1`](https://github.com/LedgerHQ/ledger-live/commit/5af41b6fa1e43037ccdb2df279c82e12ef3d2b1a)]:
+ - @ledgerhq/types-live@6.36.0-next.0
+ - @ledgerhq/cryptoassets@9.9.0-next.0
+ - @ledgerhq/types-cryptoassets@7.3.0-next.0
+ - @ledgerhq/live-env@0.3.1-next.0
+ - @ledgerhq/coin-framework@0.3.7-next.0
+
+## 0.2.2
+
+### Patch Changes
+
+- Updated dependencies [[`9adc1862dd`](https://github.com/LedgerHQ/ledger-live/commit/9adc1862dda605a722d19f3b6895bd324834c914), [`4a1454beb3`](https://github.com/LedgerHQ/ledger-live/commit/4a1454beb3f86405ba2686e07879c12a7d35ad8e), [`809065c571`](https://github.com/LedgerHQ/ledger-live/commit/809065c57198646a49adea112b9d799e35a57d25), [`d1aa522db7`](https://github.com/LedgerHQ/ledger-live/commit/d1aa522db75f7ea850efe412abaa4dc7d37af6b7), [`ebe5b07afe`](https://github.com/LedgerHQ/ledger-live/commit/ebe5b07afec441ea3e2d9103da9e1175972add47)]:
+ - @ledgerhq/errors@6.12.7
+ - @ledgerhq/cryptoassets@9.8.0
+ - @ledgerhq/types-cryptoassets@7.2.1
+ - @ledgerhq/types-live@6.35.1
+ - @ledgerhq/coin-framework@0.3.6
+ - @ledgerhq/devices@8.0.4
+ - @ledgerhq/hw-app-algorand@6.27.16
+
+## 0.2.2-next.0
+
+### Patch Changes
+
+- Updated dependencies [[`9adc1862dd`](https://github.com/LedgerHQ/ledger-live/commit/9adc1862dda605a722d19f3b6895bd324834c914), [`4a1454beb3`](https://github.com/LedgerHQ/ledger-live/commit/4a1454beb3f86405ba2686e07879c12a7d35ad8e), [`809065c571`](https://github.com/LedgerHQ/ledger-live/commit/809065c57198646a49adea112b9d799e35a57d25), [`d1aa522db7`](https://github.com/LedgerHQ/ledger-live/commit/d1aa522db75f7ea850efe412abaa4dc7d37af6b7), [`ebe5b07afe`](https://github.com/LedgerHQ/ledger-live/commit/ebe5b07afec441ea3e2d9103da9e1175972add47)]:
+ - @ledgerhq/errors@6.12.7-next.0
+ - @ledgerhq/cryptoassets@9.8.0-next.0
+ - @ledgerhq/types-cryptoassets@7.2.1-next.0
+ - @ledgerhq/types-live@6.35.1-next.0
+ - @ledgerhq/coin-framework@0.3.6-next.0
+ - @ledgerhq/devices@8.0.4-next.0
+ - @ledgerhq/hw-app-algorand@6.27.16-next.0
+
+## 0.2.1
+
+### Patch Changes
+
+- Updated dependencies [[`5cce6e3593`](https://github.com/LedgerHQ/ledger-live/commit/5cce6e359309110df53e16ef989c5b8b94492dfd), [`30bf4d92c7`](https://github.com/LedgerHQ/ledger-live/commit/30bf4d92c7d79cb81b1e4ad014857459739c33be), [`b30ead9d22`](https://github.com/LedgerHQ/ledger-live/commit/b30ead9d22a4bce5f8ee27febf0190fccd2ca25b), [`43cdd2624c`](https://github.com/LedgerHQ/ledger-live/commit/43cdd2624cd2965ddb6e346e9a77a3cc12476500)]:
+ - @ledgerhq/cryptoassets@9.7.0
+ - @ledgerhq/types-live@6.35.0
+ - @ledgerhq/coin-framework@0.3.5
+
+## 0.2.1-next.1
+
+### Patch Changes
+
+- Updated dependencies [[`30bf4d92c7`](https://github.com/LedgerHQ/ledger-live/commit/30bf4d92c7d79cb81b1e4ad014857459739c33be)]:
+ - @ledgerhq/cryptoassets@9.7.0-next.1
+ - @ledgerhq/coin-framework@0.3.5-next.1
+
+## 0.2.1-next.0
+
+### Patch Changes
+
+- Updated dependencies [[`5cce6e3593`](https://github.com/LedgerHQ/ledger-live/commit/5cce6e359309110df53e16ef989c5b8b94492dfd), [`b30ead9d22`](https://github.com/LedgerHQ/ledger-live/commit/b30ead9d22a4bce5f8ee27febf0190fccd2ca25b), [`7439b63325`](https://github.com/LedgerHQ/ledger-live/commit/7439b63325a9b0181a3af4310ba787f00faa80c9), [`43cdd2624c`](https://github.com/LedgerHQ/ledger-live/commit/43cdd2624cd2965ddb6e346e9a77a3cc12476500)]:
+ - @ledgerhq/cryptoassets@9.7.0-next.0
+ - @ledgerhq/types-live@6.35.0-next.0
+ - @ledgerhq/coin-framework@0.3.5-next.0
+
+## 0.2.0
+
+### Minor Changes
+
+- [#3306](https://github.com/LedgerHQ/ledger-live/pull/3306) [`77f990e207`](https://github.com/LedgerHQ/ledger-live/commit/77f990e2075c7c9a4be69b364e3754b449c7a546) Thanks [@chabroA](https://github.com/chabroA)! - Move algorand coin logic in own package
+
+### Patch Changes
+
+- Updated dependencies [[`77f990e207`](https://github.com/LedgerHQ/ledger-live/commit/77f990e2075c7c9a4be69b364e3754b449c7a546), [`817a8dd811`](https://github.com/LedgerHQ/ledger-live/commit/817a8dd8112ff7c4640852ab4e47ea0436df2ec1), [`77f990e207`](https://github.com/LedgerHQ/ledger-live/commit/77f990e2075c7c9a4be69b364e3754b449c7a546), [`22491091f7`](https://github.com/LedgerHQ/ledger-live/commit/22491091f7b4e06ee6a0cdf964498aa5b08d6eb0), [`745fbf2a54`](https://github.com/LedgerHQ/ledger-live/commit/745fbf2a54cdc34aea938d7fbe4c8b198dc36b54), [`7cf49e1919`](https://github.com/LedgerHQ/ledger-live/commit/7cf49e1919466836e9025693ed07b18ebf99041a)]:
+ - @ledgerhq/errors@6.12.6
+ - @ledgerhq/cryptoassets@9.6.0
+ - @ledgerhq/coin-framework@0.3.4
+ - @ledgerhq/live-env@0.3.0
+ - @ledgerhq/types-live@6.34.1
+ - @ledgerhq/devices@8.0.3
+ - @ledgerhq/hw-app-algorand@6.27.15
+
+## 0.2.0-next.0
+
+### Minor Changes
+
+- [#3306](https://github.com/LedgerHQ/ledger-live/pull/3306) [`77f990e207`](https://github.com/LedgerHQ/ledger-live/commit/77f990e2075c7c9a4be69b364e3754b449c7a546) Thanks [@chabroA](https://github.com/chabroA)! - Move algorand coin logic in own package
+
+### Patch Changes
+
+- Updated dependencies [[`77f990e207`](https://github.com/LedgerHQ/ledger-live/commit/77f990e2075c7c9a4be69b364e3754b449c7a546), [`817a8dd811`](https://github.com/LedgerHQ/ledger-live/commit/817a8dd8112ff7c4640852ab4e47ea0436df2ec1), [`77f990e207`](https://github.com/LedgerHQ/ledger-live/commit/77f990e2075c7c9a4be69b364e3754b449c7a546), [`22491091f7`](https://github.com/LedgerHQ/ledger-live/commit/22491091f7b4e06ee6a0cdf964498aa5b08d6eb0), [`745fbf2a54`](https://github.com/LedgerHQ/ledger-live/commit/745fbf2a54cdc34aea938d7fbe4c8b198dc36b54), [`7cf49e1919`](https://github.com/LedgerHQ/ledger-live/commit/7cf49e1919466836e9025693ed07b18ebf99041a)]:
+ - @ledgerhq/errors@6.12.6-next.0
+ - @ledgerhq/cryptoassets@9.6.0-next.0
+ - @ledgerhq/coin-framework@0.3.4-next.0
+ - @ledgerhq/live-env@0.3.0-next.0
+ - @ledgerhq/types-live@6.34.1-next.0
+ - @ledgerhq/devices@8.0.3-next.0
+ - @ledgerhq/hw-app-algorand@6.27.15-next.0
diff --git a/libs/coin-modules/coin-icon/jest.config.js b/libs/coin-modules/coin-icon/jest.config.js
new file mode 100644
index 000000000000..f46ddfd2b0a4
--- /dev/null
+++ b/libs/coin-modules/coin-icon/jest.config.js
@@ -0,0 +1,6 @@
+/** @type {import('ts-jest/dist/types').InitialOptionsTsJest} */
+module.exports = {
+ preset: "ts-jest",
+ testEnvironment: "node",
+ testPathIgnorePatterns: ["lib/", "lib-es/"],
+};
diff --git a/libs/coin-modules/coin-icon/package.json b/libs/coin-modules/coin-icon/package.json
new file mode 100644
index 000000000000..7e7315e8bddc
--- /dev/null
+++ b/libs/coin-modules/coin-icon/package.json
@@ -0,0 +1,83 @@
+{
+ "name": "@ledgerhq/coin-icon",
+ "version": "0.4.0",
+ "description": "Ledger Icon Coin integration",
+ "keywords": [
+ "Ledger",
+ "LedgerWallet",
+ "icx",
+ "Icon",
+ "Hardware Wallet"
+ ],
+ "repository": {
+ "type": "git",
+ "url": "https://github.com/LedgerHQ/ledger-live.git"
+ },
+ "bugs": {
+ "url": "https://github.com/LedgerHQ/ledger-live/issues"
+ },
+ "homepage": "https://github.com/LedgerHQ/ledger-live/tree/develop/libs/coin-modules/coin-icon",
+ "publishConfig": {
+ "access": "public"
+ },
+ "typesVersions": {
+ "*": {
+ "lib/*": [
+ "lib/*"
+ ],
+ "lib-es/*": [
+ "lib-es/*"
+ ],
+ "*": [
+ "lib/*"
+ ]
+ }
+ },
+ "exports": {
+ "./lib/*": "./lib/*.js",
+ "./lib-es/*": "./lib-es/*.js",
+ "./*": {
+ "require": "./lib/*.js",
+ "default": "./lib-es/*.js"
+ },
+ "./package.json": "./package.json"
+ },
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@ledgerhq/coin-framework": "workspace:^",
+ "@ledgerhq/cryptoassets": "workspace:^",
+ "@ledgerhq/devices": "workspace:^",
+ "@ledgerhq/errors": "workspace:^",
+ "@ledgerhq/live-env": "workspace:^",
+ "@ledgerhq/live-network": "workspace:^",
+ "@ledgerhq/live-promise": "workspace:^",
+ "@ledgerhq/types-cryptoassets": "workspace:^",
+ "@ledgerhq/types-live": "workspace:^",
+ "@ledgerhq/logs": "workspace:^",
+ "bignumber.js": "^9.1.2",
+ "expect": "^27.4.6",
+ "invariant": "^2.2.2",
+ "lodash": "^4.17.21",
+ "prando": "^6.0.1",
+ "icon-sdk-js":"1.5.2",
+ "rxjs": "^7.8.1"
+ },
+ "devDependencies": {
+ "@types/invariant": "^2.2.2",
+ "@types/jest": "^29.5.10",
+ "@types/lodash": "^4.14.191",
+ "jest": "^29.7.0",
+ "ts-jest": "^29.1.1"
+ },
+ "scripts": {
+ "clean": "rimraf lib lib-es",
+ "build": "tsc && tsc -m ES6 --outDir lib-es",
+ "prewatch": "pnpm build",
+ "watch": "tsc --watch",
+ "doc": "documentation readme src/** --section=API --pe ts --re ts --re d.ts",
+ "lint": "eslint ./src --no-error-on-unmatched-pattern --ext .ts,.tsx --cache",
+ "lint:fix": "pnpm lint --fix",
+ "test": "jest",
+ "unimported": "unimported"
+ }
+}
diff --git a/libs/coin-modules/coin-icon/src/account.ts b/libs/coin-modules/coin-icon/src/account.ts
new file mode 100644
index 000000000000..b6ce2844f2a8
--- /dev/null
+++ b/libs/coin-modules/coin-icon/src/account.ts
@@ -0,0 +1,30 @@
+import { getAccountCurrency } from "@ledgerhq/coin-framework/account/index";
+import { IconAccount } from "./types";
+import { formatCurrencyUnit } from "@ledgerhq/coin-framework/currencies/index";
+
+function formatAccountSpecifics(account: IconAccount): string {
+ const { iconResources } = account;
+ if (!iconResources) {
+ throw new Error("icon account expected");
+ }
+
+ const unit = getAccountCurrency(account).units[0];
+ const formatConfig = {
+ disableRounding: true,
+ alwaysShowSign: false,
+ showCode: true,
+ };
+
+ let str = " ";
+
+ str += formatCurrencyUnit(unit, account.spendableBalance, formatConfig) + " spendable. ";
+
+ if (iconResources.nonce) {
+ str += "\nonce : " + iconResources.nonce;
+ }
+
+ return str;
+}
+export default {
+ formatAccountSpecifics,
+};
diff --git a/libs/coin-modules/coin-icon/src/api/api-type.ts b/libs/coin-modules/coin-icon/src/api/api-type.ts
new file mode 100644
index 000000000000..2dfb581eabb0
--- /dev/null
+++ b/libs/coin-modules/coin-icon/src/api/api-type.ts
@@ -0,0 +1,65 @@
+import BigNumber from "bignumber.js";
+
+/**
+ * Icon Account
+ */
+export type AccountType = {
+ address: string;
+ audit_tx_hash: string;
+ balance: number; //9.998855272082496
+ code_hash: string;
+ contract_type: string;
+ contract_updated_block: number; // 0
+ created_timestamp: number; // 0
+ deploy_tx_hash: string;
+ is_contract: boolean;
+ is_nft: boolean;
+ is_prep: boolean;
+ is_token: boolean;
+ log_count: number;
+ name: string;
+ owner: string;
+ status: string;
+ symbol: string;
+ token_standard: string;
+ token_transfer_count: number;
+ transaction_count: number;
+ transaction_internal_count: number;
+ type: string;
+};
+
+/**
+ * Icon TransactionType
+ */
+export type IconTransactionType = {
+ block_number: number;
+ block_timestamp: number;
+ data: string;
+ from_address: string;
+ hash: string;
+ method: string;
+ status: string; // 010
+ to_address: string;
+ transaction_fee: string; // 0x470de4df82000"
+ transaction_type: number;
+ type: number;
+ value: string; //0x470de4df82000
+ value_decimal: number; // 4
+};
+
+/**
+ * Icon Delegation item Type
+ */
+export type IconDelegationItemType = {
+ address: string;
+ value: string;
+};
+
+/**
+ * Icon DelegationType
+ */
+export type IconDelegationType = {
+ delegations: IconDelegationItemType[];
+ totalDelegated: BigNumber;
+ votingPower: BigNumber;
+};
diff --git a/libs/coin-modules/coin-icon/src/api/index.ts b/libs/coin-modules/coin-icon/src/api/index.ts
new file mode 100644
index 000000000000..15f7b6f09f0b
--- /dev/null
+++ b/libs/coin-modules/coin-icon/src/api/index.ts
@@ -0,0 +1,160 @@
+import network from "@ledgerhq/live-network/network";
+import { BigNumber } from "bignumber.js";
+import type { Operation, OperationType } from "@ledgerhq/types-live";
+import { encodeOperationId } from "@ledgerhq/coin-framework/operation";
+import { CryptoCurrency } from "@ledgerhq/types-cryptoassets";
+import { LIMIT } from "../constants";
+import { isTestnet } from "../logic";
+import { AccountType, IconTransactionType } from "./api-type";
+import { log } from "@ledgerhq/logs";
+import { IconOperation } from "../types";
+import querystring from "querystring";
+import { getCoinConfig } from "../config";
+
+/**
+ * Returns Testnet API URL if the current network is testnet
+ *
+ * @param {network} network
+ */
+function getApiUrl(network: CryptoCurrency): string {
+ const currencyConfig = getCoinConfig();
+ let apiUrl = currencyConfig.infra.indexer;
+ if (isTestnet(network)) {
+ apiUrl = currencyConfig.infra.indexer_testnet;
+ }
+ return apiUrl;
+}
+
+async function fetch(url: string) {
+ const { data } = await network({
+ method: "GET",
+ url,
+ });
+
+ if (data.Error) {
+ log("icon-error", data.Error, {
+ url,
+ });
+ throw new Error(data.Error);
+ }
+
+ return data;
+}
+
+export const getAccount = async (addr: string, network: CryptoCurrency): Promise => {
+ const query = querystring.stringify({
+ address: addr,
+ });
+ const data = await fetch(`${getApiUrl(network)}/addresses/details/${addr}?${query}`);
+ return data;
+};
+
+export const getCurrentBlockHeight = async (
+ network: CryptoCurrency,
+): Promise => {
+ const data = await fetch(`${getApiUrl(network)}/blocks`);
+ return data?.[0].number;
+};
+
+/**
+ * Returns true if account is the signer
+ */
+function isSender(transaction: IconTransactionType, addr: string): boolean {
+ return transaction.from_address === addr;
+}
+
+/**
+ * Map transaction to an Operation Type
+ */
+function getOperationType(transaction: IconTransactionType, addr: string): OperationType {
+ return isSender(transaction, addr) ? "OUT" : "IN";
+}
+
+/**
+ * Map transaction to a correct Operation Value (affecting account balance)
+ */
+function getOperationValue(transaction: IconTransactionType, addr: string): BigNumber {
+ if (isSender(transaction, addr)) {
+ return transaction.value
+ ? new BigNumber(transaction.value).plus(transaction.transaction_fee)
+ : new BigNumber(0);
+ } else {
+ return transaction.value ? new BigNumber(transaction.value) : new BigNumber(0);
+ }
+}
+
+/**
+ * Map the ICON history transaction to a Ledger Live Operation
+ */
+function txToOperation(
+ accountId: string,
+ addr: string,
+ transaction: IconTransactionType,
+): IconOperation {
+ const type = getOperationType(transaction, addr);
+
+ return {
+ id: encodeOperationId(accountId, transaction.hash, type),
+ accountId,
+ fee: new BigNumber(transaction.transaction_fee),
+ value: getOperationValue(transaction, addr),
+ type,
+ hash: transaction.hash,
+ blockHash: null,
+ blockHeight: transaction.block_number,
+ date: new Date(transaction.block_timestamp / 1000),
+ senders: [transaction.from_address],
+ recipients: [transaction.to_address],
+ extra: {},
+ hasFailed: transaction.status !== "0x1",
+ };
+}
+
+/**
+ * Fetch operation list
+ */
+export const getOperations = async (
+ accountId: string,
+ addr: string,
+ skip: number,
+ network: CryptoCurrency,
+ maxLength: number,
+): Promise => {
+ return await fetchOperationList(accountId, addr, skip, network, maxLength);
+};
+
+export const fetchOperationList = async (
+ accountId: string,
+ addr: string,
+ skip: number,
+ network: CryptoCurrency,
+ maxLength: number,
+ prevOperations: IconOperation[] = [],
+): Promise => {
+ const data = await getTxHistory(addr, skip, network);
+ const operations = data.map((transaction: IconTransactionType) =>
+ txToOperation(accountId, addr, transaction),
+ );
+
+ const mergedOp = [...prevOperations, ...operations];
+ if (operations.length < LIMIT || operations.length >= maxLength) {
+ return mergedOp;
+ }
+ return await fetchOperationList(accountId, addr, skip + LIMIT, network, maxLength, mergedOp);
+};
+
+export const getTxHistory = async (
+ addr: string,
+ skip: number,
+ network: CryptoCurrency,
+ limit: number = LIMIT,
+): Promise => {
+ const query = querystring.stringify({
+ address: addr,
+ skip: skip,
+ limit: limit,
+ });
+
+ const data = await fetch(`${getApiUrl(network)}/transactions/address/${addr}?${query}`);
+ return data;
+};
diff --git a/libs/coin-modules/coin-icon/src/api/node.ts b/libs/coin-modules/coin-icon/src/api/node.ts
new file mode 100644
index 000000000000..af94031ccda4
--- /dev/null
+++ b/libs/coin-modules/coin-icon/src/api/node.ts
@@ -0,0 +1,115 @@
+import { BigNumber } from "bignumber.js";
+import IconService from "icon-sdk-js";
+import type { IcxTransaction, SignedTransaction } from "icon-sdk-js";
+import { CryptoCurrency } from "@ledgerhq/types-cryptoassets";
+import { isTestnet } from "../logic";
+import { GOVERNANCE_SCORE_ADDRESS, IISS_SCORE_ADDRESS } from "../constants";
+import { IconAccount } from "../types";
+import { SignedOperation } from "@ledgerhq/types-live";
+import { IconDelegationType } from "./api-type";
+import { getCoinConfig } from "../config";
+
+const { HttpProvider } = IconService;
+const { IconBuilder } = IconService;
+
+/**
+ * Returns Testnet RPC URL if the current currency is testnet
+ * @param {currency} currency
+ */
+export function getRpcUrl(currency: CryptoCurrency): string {
+ const currencyConfig = getCoinConfig();
+ let rpcUrl = currencyConfig.infra.node_endpoint;
+ if (isTestnet(currency)) {
+ rpcUrl = currencyConfig.infra.node_testnet_endpoint;
+ }
+ return rpcUrl;
+}
+
+export function getDebugRpcUrl(currency: CryptoCurrency): string {
+ const currencyConfig = getCoinConfig();
+ let rpcUrl = currencyConfig.infra.debug_endpoint;
+ if (isTestnet(currency)) {
+ rpcUrl = currencyConfig.infra.debug_testnet_endpoint;
+ }
+ return rpcUrl;
+}
+
+/**
+ * Broadcast blob to blockchain
+ */
+export const broadcastTransaction = async (
+ signedOperation: SignedOperation,
+ currency: CryptoCurrency,
+) => {
+ const { hash } = await submit(signedOperation, currency);
+ return { hash };
+};
+
+export const submit = async (signedOperation: SignedOperation, currency: CryptoCurrency) => {
+ const rpcURL = getRpcUrl(currency);
+
+ const httpProvider = new HttpProvider(rpcURL);
+ const iconService = new IconService(httpProvider);
+
+ const signedTransaction = {
+ getProperties: () => signedOperation.rawData,
+ getSignature: () => signedOperation.signature,
+ };
+
+ const response = await iconService
+ .sendTransaction(signedTransaction as SignedTransaction)
+ .execute();
+ return {
+ hash: response,
+ };
+};
+
+/**
+ * Obtain fees from blockchain
+ */
+export const getFees = async (
+ unsigned: IcxTransaction,
+ account: IconAccount,
+): Promise => {
+ const debugRpcUrl = getDebugRpcUrl(account.currency);
+ const httpProvider = new HttpProvider(debugRpcUrl);
+ const iconService = new IconService(httpProvider);
+ const res = await iconService.estimateStep(unsigned).execute();
+ return new BigNumber(res);
+};
+
+/**
+ * Get step price from governance contract
+ */
+export const getStepPrice = async (account: IconAccount): Promise => {
+ const rpcURL = getRpcUrl(account.currency);
+ const httpProvider = new HttpProvider(rpcURL);
+ const iconService = new IconService(httpProvider);
+ const txBuilder = new IconBuilder.CallBuilder();
+ const stepPriceTx = txBuilder.to(GOVERNANCE_SCORE_ADDRESS).method("getStepPrice").build();
+ const res = await iconService.call(stepPriceTx).execute();
+ return new BigNumber(res);
+};
+
+export const getDelegation = async (
+ address: string,
+ currency: CryptoCurrency,
+): Promise => {
+ const rpcURL = getRpcUrl(currency);
+ const httpProvider = new HttpProvider(rpcURL);
+ const iconService = new IconService(httpProvider);
+ const delegationTx = new IconBuilder.CallBuilder()
+ .to(IISS_SCORE_ADDRESS)
+ .method("getDelegation")
+ .params({
+ address,
+ })
+ .build();
+
+ const res = await iconService.call(delegationTx).execute();
+ return {
+ delegations: res.delegations,
+ totalDelegated: new BigNumber(res.totalDelegated),
+ votingPower: new BigNumber(res.votingPower),
+ };
+};
diff --git a/libs/coin-modules/coin-icon/src/bridge.integration.test.ts b/libs/coin-modules/coin-icon/src/bridge.integration.test.ts
new file mode 100644
index 000000000000..5f3ba21ad683
--- /dev/null
+++ b/libs/coin-modules/coin-icon/src/bridge.integration.test.ts
@@ -0,0 +1,105 @@
+import type { DatasetTest, CurrenciesData } from "@ledgerhq/types-live";
+import type { Transaction } from "./types";
+import { fromTransactionRaw } from "./transaction";
+import BigNumber from "bignumber.js";
+import {
+ InvalidAddressBecauseDestinationIsAlsoSource,
+ InvalidAddress,
+ NotEnoughBalance,
+} from "@ledgerhq/errors";
+
+const TEST_ADDRESS = "hxe52720d9125586e64c745bf3c2c1917dbb46f9ba";
+
+const icon: CurrenciesData = {
+ scanAccounts: [
+ {
+ name: "icon seed 1",
+ apdus: `
+ => e00200010d038000002c8049435880000000
+ <= 410426dd3cdd69f3604e9c1d8f3021b20e133524f12894b9ef8e35d3f1f5244516d18b7bb0d1fee7b7c2972d5cde08190d66cf932d0addd5ac87f53198d050940ccb2a6878626232396531313463626434303939346136346462303137626363323233616661353135363966669fd5109b2b2077b50f18fee878efef03cef0cfb5c3256ea9e55f44fa0e9ae5289000
+ => e002000115058000002c80494358800000008000000080000000
+ <= 4104c1142d5b765d3026e801fd3946d9673051c7e3c3bd0eecd8ca27b6be079c9ee6b7b9f311b9eecb2995efd7e4a7ef9287a7f8878751d3227689c25a324270e8082a687863663465326339346235323631396637326161346461313333373061613663316333646164303064ff4e5e0cebe9462a469955997216658ce22953f79d272a6a1ecf3b3cdcb3549a9000
+ => e002000115058000002c80494358800000008000000080000001
+ <= 410429e40c4a50f96a32b622e218114c9204f66763c997dadb8c719a95d3a2d98fda3b00ccc9f3d491906d453fa2a63298817eb6af94c77a47f5eb4baa43cbb73a832a6878666330343962346365363063633539363834383764313830643963343236336536333138313733392ed566d1841e629d6f3040a23918ee84015fb60b99824d7dcadde65bcb01bb089000
+ `,
+ },
+ ],
+ accounts: [
+ {
+ FIXME_tests: ["balance is sum of ops"],
+ raw: {
+ id: `js:2:icon:${TEST_ADDRESS}:`,
+ seedIdentifier: `${TEST_ADDRESS}`,
+ name: "ICON 1",
+ derivationMode: "",
+ index: 0,
+ freshAddress: `${TEST_ADDRESS}`,
+ freshAddressPath: "44'/4801368'/0'/0'/0'",
+ blockHeight: 0,
+ operations: [],
+ pendingOperations: [],
+ currencyId: "icon",
+ lastSyncDate: "",
+ balance: "299569965",
+ },
+ transactions: [
+ {
+ name: "recipient and sender must not be the same",
+ transaction: fromTransactionRaw({
+ family: "icon",
+ recipient: `${TEST_ADDRESS}`,
+ amount: "100000000",
+ mode: "send",
+ fees: "0.00125",
+ }),
+ expectedStatus: {
+ amount: new BigNumber("100000000"),
+ errors: {
+ recipient: new InvalidAddressBecauseDestinationIsAlsoSource(),
+ },
+ warnings: {},
+ },
+ },
+ {
+ name: "Not a valid address",
+ transaction: fromTransactionRaw({
+ family: "icon",
+ recipient: "iconinv",
+ amount: "100000000",
+ mode: "send",
+ fees: null,
+ }),
+ expectedStatus: {
+ errors: {
+ recipient: new InvalidAddress(),
+ },
+ warnings: {},
+ },
+ },
+ {
+ name: "Not enough balance",
+ transaction: fromTransactionRaw({
+ family: "icon",
+ recipient: "hxedaf3b2027fbbc0a31f589299c0b34533cd8edac",
+ amount: "1000000000000000000000000",
+ mode: "send",
+ fees: null,
+ }),
+ expectedStatus: {
+ errors: {
+ amount: new NotEnoughBalance(),
+ },
+ warnings: {},
+ },
+ },
+ ],
+ },
+ ],
+};
+
+export const dataset: DatasetTest = {
+ implementations: ["js"],
+ currencies: {
+ icon,
+ },
+};
diff --git a/libs/coin-modules/coin-icon/src/bridge/index.ts b/libs/coin-modules/coin-icon/src/bridge/index.ts
new file mode 100644
index 000000000000..9be540c8b278
--- /dev/null
+++ b/libs/coin-modules/coin-icon/src/bridge/index.ts
@@ -0,0 +1,73 @@
+import getAddressWrapper from "@ledgerhq/coin-framework/bridge/getAddressWrapper";
+import {
+ defaultUpdateTransaction,
+ makeAccountBridgeReceive,
+ makeScanAccounts,
+ makeSync,
+} from "@ledgerhq/coin-framework/bridge/jsHelpers";
+import { SignerContext } from "@ledgerhq/coin-framework/signer";
+import { CoinConfig } from "@ledgerhq/coin-framework/config";
+
+import type { AccountBridge, CurrencyBridge } from "@ledgerhq/types-live";
+import resolver from "../hw-getAddress";
+import { initAccount } from "../initAccount";
+import { broadcast } from "../broadcast";
+import { createTransaction } from "../createTransaction";
+import { estimateMaxSpendable } from "../estimateMaxSpendable";
+import { getTransactionStatus } from "../getTransactionStatus";
+import { prepareTransaction } from "../prepareTransaction";
+import { buildSignOperation } from "../signOperation";
+import { getAccountShape } from "../synchronization";
+import { assignFromAccountRaw, assignToAccountRaw } from "../serialization";
+import type { Transaction } from "../types";
+import { IconSigner } from "../signer";
+import { IconCoinConfig, setCoinConfig } from "../config";
+
+export function buildCurrencyBridge(signerContext: SignerContext): CurrencyBridge {
+ const getAddress = resolver(signerContext);
+
+ const scanAccounts = makeScanAccounts({
+ getAccountShape,
+ getAddressFn: getAddress,
+ });
+
+ return {
+ preload: async () => Promise.resolve({}),
+ hydrate: () => {},
+ scanAccounts,
+ };
+}
+
+export function buildAccountBridge(
+ signerContext: SignerContext,
+): AccountBridge {
+ const getAddress = resolver(signerContext);
+
+ const receive = makeAccountBridgeReceive(getAddressWrapper(getAddress));
+ const signOperation = buildSignOperation(signerContext);
+ const sync = makeSync({ getAccountShape });
+
+ return {
+ createTransaction,
+ updateTransaction: defaultUpdateTransaction,
+ prepareTransaction,
+ getTransactionStatus,
+ sync,
+ receive,
+ assignToAccountRaw,
+ assignFromAccountRaw,
+ initAccount,
+ signOperation,
+ broadcast,
+ estimateMaxSpendable,
+ };
+}
+
+export function createBridges(signerContext: SignerContext, coinConfig: CoinConfig,) {
+ setCoinConfig(coinConfig);
+
+ return {
+ currencyBridge: buildCurrencyBridge(signerContext),
+ accountBridge: buildAccountBridge(signerContext),
+ };
+}
diff --git a/libs/coin-modules/coin-icon/src/broadcast.ts b/libs/coin-modules/coin-icon/src/broadcast.ts
new file mode 100644
index 000000000000..afffaf6ffa03
--- /dev/null
+++ b/libs/coin-modules/coin-icon/src/broadcast.ts
@@ -0,0 +1,18 @@
+import { Account, Operation, SignedOperation } from "@ledgerhq/types-live";
+import { patchOperationWithHash } from "@ledgerhq/coin-framework/operation";
+
+import { broadcastTransaction } from "./api/node";
+
+/**
+ * Broadcast the signed transaction
+ */
+export async function broadcast({
+ account,
+ signedOperation,
+}: {
+ account: Account;
+ signedOperation: SignedOperation;
+}): Promise {
+ const { hash } = await broadcastTransaction(signedOperation, account.currency);
+ return patchOperationWithHash(signedOperation.operation, hash);
+}
diff --git a/libs/coin-modules/coin-icon/src/buildTransaction.ts b/libs/coin-modules/coin-icon/src/buildTransaction.ts
new file mode 100644
index 000000000000..ccd6144110a1
--- /dev/null
+++ b/libs/coin-modules/coin-icon/src/buildTransaction.ts
@@ -0,0 +1,53 @@
+import type { IconAccount, Transaction } from "./types";
+import IconService from "icon-sdk-js";
+import type { IcxTransaction } from "icon-sdk-js";
+import { getNid, getNonce } from "./logic";
+import BigNumber from "bignumber.js";
+import { RPC_VERSION } from "./constants";
+
+const { IconBuilder, IconConverter } = IconService;
+
+const buildTransferTransaction = (
+ account: IconAccount,
+ transaction: Transaction,
+ stepLimit?: BigNumber | undefined,
+): IcxTransaction => {
+ const address = account.freshAddress;
+ const icxTransactionBuilder = new IconBuilder.IcxTransactionBuilder();
+ const icxTransferData = icxTransactionBuilder
+ .from(address)
+ .to(transaction.recipient)
+ .value(IconConverter.toHexNumber(transaction.amount))
+ .nid(IconConverter.toHexNumber(getNid(account.currency)))
+ .nonce(IconConverter.toHexNumber(getNonce(account)))
+ .timestamp(IconConverter.toHexNumber(new Date().getTime() * 1000))
+ .version(IconConverter.toHexNumber(RPC_VERSION));
+ if (stepLimit) {
+ icxTransferData.stepLimit(IconConverter.toHexNumber(stepLimit));
+ }
+
+ return icxTransferData.build();
+};
+
+/**
+ *
+ * @param {Account} account
+ * @param {Transaction} transaction
+ */
+export const buildTransaction = async (
+ account: IconAccount,
+ transaction: Transaction,
+ stepLimit?: BigNumber | undefined,
+) => {
+ let unsigned: IcxTransaction | undefined;
+ switch (transaction.mode) {
+ case "send":
+ unsigned = buildTransferTransaction(account, transaction, stepLimit);
+ break;
+ default:
+ throw new Error(`Unsupported transaction mode: ${transaction.mode}`);
+ }
+ return {
+ unsigned,
+ };
+};
diff --git a/libs/coin-modules/coin-icon/src/cli-transaction.ts b/libs/coin-modules/coin-icon/src/cli-transaction.ts
new file mode 100644
index 000000000000..25a75ae533e2
--- /dev/null
+++ b/libs/coin-modules/coin-icon/src/cli-transaction.ts
@@ -0,0 +1,49 @@
+import type { AccountLike } from "@ledgerhq/types-live";
+import invariant from "invariant";
+import flatMap from "lodash/flatMap";
+import type { IconAccount, Transaction } from "./types";
+
+const options = [
+ {
+ name: "mode",
+ type: String,
+ desc: "mode of transaction: send, stake, unstake",
+ },
+];
+
+function inferTransactions(
+ transactions: Array<{
+ account: AccountLike;
+ transaction: Transaction;
+ }>,
+ opts: Record,
+): Transaction[] {
+ return flatMap(transactions, ({ transaction, account }) => {
+ invariant(
+ transaction.family === "icon",
+ "[cli-transaction] inferTransactions expects icon family transaction",
+ );
+
+ if (account.type === "Account") {
+ invariant((account as IconAccount).iconResources, "unactivated account");
+ }
+
+ if (account.type === "TokenAccount") {
+ const isDelisted = account.token.delisted === true;
+ invariant(!isDelisted, "token is delisted");
+ }
+
+ return {
+ ...transaction,
+ family: "icon",
+ mode: opts.mode || "send",
+ } as Transaction;
+ });
+}
+
+export default function makeCliTools() {
+ return {
+ options,
+ inferTransactions,
+ };
+}
diff --git a/libs/coin-modules/coin-icon/src/config.ts b/libs/coin-modules/coin-icon/src/config.ts
new file mode 100644
index 000000000000..cae136181d14
--- /dev/null
+++ b/libs/coin-modules/coin-icon/src/config.ts
@@ -0,0 +1,29 @@
+import { CurrencyConfig, CoinConfig } from "@ledgerhq/coin-framework/config";
+import { MissingCoinConfig } from "@ledgerhq/coin-framework/errors";
+
+export type IconConfig = {
+ infra: {
+ indexer: string;
+ indexer_testnet: string;
+ node_endpoint: string;
+ node_testnet_endpoint: string;
+ debug_endpoint: string;
+ debug_testnet_endpoint: string;
+ };
+};
+
+export type IconCoinConfig = CurrencyConfig & IconConfig;
+
+let coinConfig: CoinConfig | undefined;
+
+export const setCoinConfig = (config: CoinConfig): void => {
+ coinConfig = config;
+};
+
+export const getCoinConfig = (): IconCoinConfig => {
+ if (!coinConfig) {
+ throw new MissingCoinConfig();
+ }
+
+ return coinConfig();
+};
diff --git a/libs/coin-modules/coin-icon/src/constants.ts b/libs/coin-modules/coin-icon/src/constants.ts
new file mode 100644
index 000000000000..b1f0d7e3bc91
--- /dev/null
+++ b/libs/coin-modules/coin-icon/src/constants.ts
@@ -0,0 +1,14 @@
+export const GOVERNANCE_SCORE_ADDRESS = "cx0000000000000000000000000000000000000001";
+export const IISS_SCORE_ADDRESS = "cx0000000000000000000000000000000000000000";
+export const LIMIT = 100;
+export const BERLIN_TESTNET_NID = 7;
+export const MAINNET_NID = 1;
+export const I_SCORE_UNIT = 1000;
+export const RPC_VERSION = 3;
+export const DEFAULT_STEP_LIMIT = 200000;
+
+export const PREP_TYPE = {
+ MAIN: "Main P-Rep",
+ SUB: "Sub P-Rep",
+ CANDIDATE: "Candidate",
+};
diff --git a/libs/coin-modules/coin-icon/src/createTransaction.ts b/libs/coin-modules/coin-icon/src/createTransaction.ts
new file mode 100644
index 000000000000..31287161b224
--- /dev/null
+++ b/libs/coin-modules/coin-icon/src/createTransaction.ts
@@ -0,0 +1,16 @@
+import { BigNumber } from "bignumber.js";
+import type { Transaction } from "./types";
+
+/**
+ * Create an empty transaction
+ *
+ * @returns {Transaction}
+ */
+export const createTransaction = (): Transaction => ({
+ family: "icon",
+ mode: "send",
+ amount: new BigNumber(0),
+ recipient: "",
+ useAllAmount: false,
+ fees: null,
+});
diff --git a/libs/coin-modules/coin-icon/src/deviceTransactionConfig.ts b/libs/coin-modules/coin-icon/src/deviceTransactionConfig.ts
new file mode 100644
index 000000000000..832eb5a14f42
--- /dev/null
+++ b/libs/coin-modules/coin-icon/src/deviceTransactionConfig.ts
@@ -0,0 +1,44 @@
+import type { AccountLike, Account } from "@ledgerhq/types-live";
+import type { Transaction, TransactionStatus } from "./types";
+import type { CommonDeviceTransactionField as DeviceTransactionField } from "@ledgerhq/coin-framework/transaction/common";
+import { getMainAccount } from "@ledgerhq/coin-framework/account/index";
+
+export type ExtraDeviceTransactionField = {
+ type: "icon.fees";
+ label: string;
+};
+
+function getDeviceTransactionConfig({
+ transaction,
+ account,
+ parentAccount,
+ status: { amount },
+}: {
+ account: AccountLike;
+ parentAccount?: Account;
+ transaction: Transaction;
+ status: TransactionStatus;
+}): Array {
+ const fields: Array = [];
+ const mainAccount = getMainAccount(account, parentAccount);
+ const { mode } = transaction;
+
+ if (!amount.isZero()) {
+ fields.push({
+ type: "amount",
+ label: "Amount",
+ });
+ }
+
+ if (mode !== "send") {
+ fields.push({
+ type: "address",
+ label: "From Address",
+ address: mainAccount.freshAddress,
+ });
+ }
+
+ return fields;
+}
+
+export default getDeviceTransactionConfig;
diff --git a/libs/coin-modules/coin-icon/src/errors.ts b/libs/coin-modules/coin-icon/src/errors.ts
new file mode 100644
index 000000000000..494f08ec8a2f
--- /dev/null
+++ b/libs/coin-modules/coin-icon/src/errors.ts
@@ -0,0 +1,5 @@
+import { createCustomErrorClass } from "@ledgerhq/errors";
+
+export const IconAllFundsWarning = createCustomErrorClass("IconAllFundsWarning");
+export const IconValidatorsRequired = createCustomErrorClass("IconValidatorsRequired");
+export const IconDoMaxSendInstead = createCustomErrorClass("IconDoMaxSendInstead");
diff --git a/libs/coin-modules/coin-icon/src/estimateMaxSpendable.ts b/libs/coin-modules/coin-icon/src/estimateMaxSpendable.ts
new file mode 100644
index 000000000000..5105d043818a
--- /dev/null
+++ b/libs/coin-modules/coin-icon/src/estimateMaxSpendable.ts
@@ -0,0 +1,34 @@
+import { BigNumber } from "bignumber.js";
+import type { AccountLike, Account } from "@ledgerhq/types-live";
+import { getMainAccount } from "@ledgerhq/coin-framework/account/index";
+import type { IconAccount, Transaction } from "./types";
+import { createTransaction } from "./createTransaction";
+import getEstimatedFees from "./getFeesForTransaction";
+import { calculateAmount } from "./logic";
+
+/**
+ * Returns the maximum possible amount for transaction
+ *
+ * @param {Object} param - the account, parentAccount and transaction
+ */
+export const estimateMaxSpendable = async ({
+ account,
+ parentAccount,
+ transaction,
+}: {
+ account: AccountLike;
+ parentAccount: Account | null | undefined;
+ transaction: Transaction | null | undefined;
+}): Promise => {
+ const acc = getMainAccount(account, parentAccount) as IconAccount;
+ const tx = { ...createTransaction(), ...transaction, useAllAmount: true };
+ const fees = await getEstimatedFees({
+ account: acc,
+ transaction: tx,
+ });
+
+ return calculateAmount({
+ account: acc,
+ transaction: { ...tx, fees },
+ });
+};
diff --git a/libs/coin-modules/coin-icon/src/getFeesForTransaction.ts b/libs/coin-modules/coin-icon/src/getFeesForTransaction.ts
new file mode 100644
index 000000000000..60c05148eecb
--- /dev/null
+++ b/libs/coin-modules/coin-icon/src/getFeesForTransaction.ts
@@ -0,0 +1,46 @@
+import { BigNumber } from "bignumber.js";
+import type { IconAccount, Transaction } from "./types";
+import { getFees } from "./api/node";
+import { buildTransaction } from "./buildTransaction";
+import { getStepPrice } from "./api/node";
+import { FEES_SAFETY_BUFFER, calculateAmount } from "./logic";
+import { getAbandonSeedAddress } from "@ledgerhq/cryptoassets";
+
+/**
+ * Fetch the transaction fees for a transaction
+ *
+ * @param {IconAccount} account
+ * @param {Transaction} transaction
+ */
+const getEstimatedFees = async ({
+ account,
+ transaction,
+}: {
+ account: IconAccount;
+ transaction: Transaction;
+}): Promise => {
+ const tx = {
+ ...transaction,
+ recipient: getAbandonSeedAddress(account.currency.id),
+ // Always use a fake recipient to estimate fees
+ amount: calculateAmount({
+ account,
+ transaction: {
+ ...transaction,
+ fees: new BigNumber(0),
+ },
+ }), // Remove fees if present since we are fetching fees
+ };
+ try {
+ const { unsigned } = await buildTransaction(account, tx);
+ const stepLimit = await getFees(unsigned, account);
+ transaction.stepLimit = stepLimit;
+ const stepPrice = await getStepPrice(account);
+ return stepLimit.multipliedBy(stepPrice);
+ } catch (_error) {
+ // Fix ME, the API of Icon throws an error when getting the fee with maximum balance
+ return FEES_SAFETY_BUFFER;
+ }
+};
+
+export default getEstimatedFees;
diff --git a/libs/coin-modules/coin-icon/src/getTransactionStatus.ts b/libs/coin-modules/coin-icon/src/getTransactionStatus.ts
new file mode 100644
index 000000000000..7a59c225a28e
--- /dev/null
+++ b/libs/coin-modules/coin-icon/src/getTransactionStatus.ts
@@ -0,0 +1,150 @@
+import { BigNumber } from "bignumber.js";
+import {
+ NotEnoughBalance,
+ RecipientRequired,
+ InvalidAddress,
+ FeeNotLoaded,
+ InvalidAddressBecauseDestinationIsAlsoSource,
+ AmountRequired,
+ NotEnoughBalanceBecauseDestinationNotCreated,
+} from "@ledgerhq/errors";
+
+import type { IconAccount, Transaction, TransactionStatus } from "./types";
+
+import {
+ EXISTENTIAL_DEPOSIT,
+ EXISTENTIAL_DEPOSIT_RECOMMENDED_MARGIN,
+ FEES_SAFETY_BUFFER,
+ calculateAmount,
+ getMinimumBalance,
+ isSelfTransaction,
+ isValidAddress,
+} from "./logic";
+import { formatCurrencyUnit } from "@ledgerhq/coin-framework/currencies/index";
+import { IconAllFundsWarning, IconDoMaxSendInstead } from "./errors";
+import { getAccount } from "./api";
+
+export const getSendTransactionStatus = async (
+ account: IconAccount,
+ transaction: Transaction,
+): Promise => {
+ const errors: any = {};
+ const warnings: any = {};
+
+ // Check if fees are loaded
+ if (!transaction.fees) {
+ errors.fees = new FeeNotLoaded();
+ }
+
+ // Validate recipient
+ if (!transaction.recipient) {
+ errors.recipient = new RecipientRequired();
+ } else if (isSelfTransaction(account, transaction)) {
+ errors.recipient = new InvalidAddressBecauseDestinationIsAlsoSource();
+ } else if (!isValidAddress(transaction.recipient)) {
+ errors.recipient = new InvalidAddress("", {
+ currencyName: account.currency.name,
+ });
+ }
+
+ const estimatedFees = transaction.fees || new BigNumber(0);
+ const amount = calculateAmount({ account, transaction });
+ const totalSpent = amount.plus(estimatedFees);
+
+ // Check if amount is valid
+ if (amount.lte(0) && !transaction.useAllAmount) {
+ errors.amount = new AmountRequired();
+ } else {
+ const minimumBalanceExistential = getMinimumBalance(account);
+ const leftover = account.spendableBalance.minus(totalSpent);
+ if (
+ minimumBalanceExistential.gt(0) &&
+ leftover.lt(minimumBalanceExistential) &&
+ leftover.gt(0)
+ ) {
+ errors.amount = new IconDoMaxSendInstead("", {
+ minimumBalance: formatCurrencyUnit(account.currency.units[0], EXISTENTIAL_DEPOSIT, {
+ showCode: true,
+ }),
+ });
+ } else if (
+ !errors.amount &&
+ !transaction.useAllAmount &&
+ account.spendableBalance.lte(EXISTENTIAL_DEPOSIT.plus(EXISTENTIAL_DEPOSIT_RECOMMENDED_MARGIN))
+ ) {
+ errors.amount = new NotEnoughBalance();
+ } else if (totalSpent.gt(account.spendableBalance)) {
+ errors.amount = new NotEnoughBalance();
+ }
+
+ if (
+ !errors.amount &&
+ new BigNumber(account.iconResources?.totalDelegated)
+ .plus(account.iconResources?.votingPower)
+ .gt(0) &&
+ (transaction.useAllAmount ||
+ account.spendableBalance.minus(totalSpent).lt(FEES_SAFETY_BUFFER))
+ ) {
+ warnings.amount = new IconAllFundsWarning();
+ }
+
+ if (
+ !errors.recipient &&
+ amount.lt(EXISTENTIAL_DEPOSIT) &&
+ (await getAccount(transaction.recipient, account.currency)) === null
+ ) {
+ errors.amount = new NotEnoughBalanceBecauseDestinationNotCreated("", {
+ minimalAmount: formatCurrencyUnit(account.currency.units[0], EXISTENTIAL_DEPOSIT, {
+ showCode: true,
+ }),
+ });
+ }
+
+ if (totalSpent.gt(account.spendableBalance)) {
+ errors.amount = new NotEnoughBalance();
+ }
+ }
+
+ return Promise.resolve({
+ errors,
+ warnings,
+ estimatedFees,
+ amount: amount.lt(0) ? new BigNumber(0) : amount,
+ totalSpent,
+ });
+};
+
+export const getTransactionStatus = async (
+ account: IconAccount,
+ transaction: Transaction,
+): Promise => {
+ switch (transaction.mode) {
+ case "send":
+ return await getSendTransactionStatus(account, transaction);
+ default: {
+ const errors: { amount?: Error; recipient?: Error } = {};
+ const warnings: { amount?: Error } = {};
+
+ const amount = calculateAmount({ account, transaction });
+ const estimatedFees = transaction.fees || new BigNumber(0);
+ const totalSpent = amount.plus(estimatedFees);
+
+ if (totalSpent.gt(account.spendableBalance)) {
+ errors.amount = new NotEnoughBalance();
+ }
+
+ // Validate amount
+ if (amount.lte(0) && !transaction.useAllAmount) {
+ errors.amount = new AmountRequired();
+ }
+
+ return {
+ errors,
+ warnings,
+ estimatedFees,
+ amount: amount.lt(0) ? new BigNumber(0) : amount,
+ totalSpent,
+ };
+ }
+ }
+};
diff --git a/libs/coin-modules/coin-icon/src/hw-getAddress.ts b/libs/coin-modules/coin-icon/src/hw-getAddress.ts
new file mode 100644
index 000000000000..e23bedbd77d4
--- /dev/null
+++ b/libs/coin-modules/coin-icon/src/hw-getAddress.ts
@@ -0,0 +1,19 @@
+import { GetAddressFn } from "@ledgerhq/coin-framework/bridge/getAddressWrapper";
+import { SignerContext } from "@ledgerhq/coin-framework/signer";
+import { GetAddressOptions } from "@ledgerhq/coin-framework/derivation";
+import { IconAddress, IconSigner } from "./signer";
+
+const resolver = (signerContext: SignerContext): GetAddressFn => {
+ return async (deviceId: string, { path, verify }: GetAddressOptions) => {
+ const r = (await signerContext(deviceId, signer =>
+ signer.getAddress(path, verify || false),
+ )) as IconAddress;
+ return {
+ address: r.address,
+ publicKey: r.publicKey,
+ path,
+ };
+ };
+};
+
+export default resolver;
diff --git a/libs/coin-modules/coin-icon/src/initAccount.ts b/libs/coin-modules/coin-icon/src/initAccount.ts
new file mode 100644
index 000000000000..008df0e361bf
--- /dev/null
+++ b/libs/coin-modules/coin-icon/src/initAccount.ts
@@ -0,0 +1,11 @@
+import { BigNumber } from "bignumber.js";
+import { IconAccount } from "./types";
+import { Account } from "@ledgerhq/types-live";
+
+export function initAccount(account: Account): void {
+ (account as IconAccount).iconResources = {
+ nonce: 0,
+ votingPower: BigNumber(0),
+ totalDelegated: BigNumber(0),
+ };
+}
diff --git a/libs/coin-modules/coin-icon/src/logic.ts b/libs/coin-modules/coin-icon/src/logic.ts
new file mode 100644
index 000000000000..7f8e8599ac9d
--- /dev/null
+++ b/libs/coin-modules/coin-icon/src/logic.ts
@@ -0,0 +1,132 @@
+import { getCryptoCurrencyById } from "@ledgerhq/cryptoassets";
+import { BigNumber } from "bignumber.js";
+import IconService from "icon-sdk-js";
+import type { CryptoCurrency } from "@ledgerhq/types-cryptoassets";
+import type { Account } from "@ledgerhq/types-live";
+import type { IconAccount, Transaction } from "./types";
+const { IconAmount } = IconService;
+import { BERLIN_TESTNET_NID, MAINNET_NID } from "./constants";
+
+/**
+ * @param {string|number|BigNumber} value value as loop
+ * @returns {BigNumber} value as ICX
+ */
+export const convertLoopToIcx = (value: string | number | BigNumber): BigNumber => {
+ return new BigNumber(IconAmount.fromLoop(value, IconAmount.Unit.ICX.toString()));
+};
+
+/**
+ * @param {string|number|BigNumber} value value as ICX
+ * @returns {BigNumber} value as loop
+ */
+export const convertICXtoLoop = (value: string | number | BigNumber): BigNumber => {
+ return new BigNumber(IconAmount.toLoop(value, IconAmount.Unit.ICX.toString()));
+};
+
+export const EXISTENTIAL_DEPOSIT = convertICXtoLoop(0.1);
+export const EXISTENTIAL_DEPOSIT_RECOMMENDED_MARGIN = convertICXtoLoop(0.00125);
+export const FEES_SAFETY_BUFFER = convertICXtoLoop(0.00125); // Arbitrary buffer for paying fees of next transactions
+export const MAX_AMOUNT_INPUT = convertICXtoLoop(5000);
+/**
+ * Returns true if address is a valid md5
+ *
+ * @param {string} address
+ */
+export const isValidAddress = (address: string): boolean => {
+ if (!address) return false;
+ return !!address.match(/^[a-z0-9]{42}$/);
+};
+
+/**
+ * Returns true if transaction is a self transaction
+ *
+ * @param {Account} account
+ * @param {Transaction} transaction
+ */
+export const isSelfTransaction = (account: Account, transaction: Transaction): boolean => {
+ return transaction.recipient === account.freshAddress;
+};
+
+/**
+ * Returns nonce for an account
+ *
+ * @param {Account} account
+ */
+export const getNonce = (account: IconAccount): number => {
+ const lastPendingOp = account.pendingOperations[0];
+
+ const nonce = Math.max(
+ account.iconResources?.nonce || 0,
+ lastPendingOp && typeof lastPendingOp.transactionSequenceNumber === "number"
+ ? lastPendingOp.transactionSequenceNumber + 1
+ : 0,
+ );
+
+ return nonce;
+};
+
+/**
+ * Returns true if the current currency is testnet
+ *
+ * @param {currency} CryptoCurrency
+ */
+export function isTestnet(currency: CryptoCurrency): boolean {
+ return getCryptoCurrencyById(currency.id).isTestnetFor ? true : false;
+}
+
+export function getNid(currency: CryptoCurrency): number {
+ let nid = MAINNET_NID;
+ if (isTestnet(currency)) {
+ nid = BERLIN_TESTNET_NID;
+ }
+ return nid;
+}
+
+/**
+ * Calculate the real spendable
+ *
+ * @param {*} account
+ * @param {*} transaction
+ */
+const calculateMaxSend = (account: Account, transaction: Transaction): BigNumber => {
+ const amount = account.spendableBalance.minus(transaction.fees || 0);
+ return amount.lt(0) ? new BigNumber(0) : BigNumber(amount.toFixed(5));
+};
+
+/**
+ * Calculates correct amount if useAllAmount
+ *
+ * @param {*} param
+ */
+export const calculateAmount = ({
+ account,
+ transaction,
+}: {
+ account: IconAccount;
+ transaction: Transaction;
+}): BigNumber => {
+ let amount = transaction.amount;
+
+ if (transaction.useAllAmount) {
+ switch (transaction.mode) {
+ case "send":
+ amount = calculateMaxSend(account, transaction);
+ break;
+
+ default:
+ amount = account.spendableBalance.minus(transaction.fees || 0);
+ break;
+ }
+ } else if (transaction.amount.gt(MAX_AMOUNT_INPUT)) {
+ return new BigNumber(MAX_AMOUNT_INPUT);
+ }
+
+ return amount.lt(0) ? new BigNumber(0) : amount;
+};
+
+export const getMinimumBalance = (account: Account): BigNumber => {
+ const lockedBalance = account.balance.minus(account.spendableBalance);
+ return lockedBalance.lte(EXISTENTIAL_DEPOSIT)
+ ? EXISTENTIAL_DEPOSIT.minus(lockedBalance)
+ : new BigNumber(0);
+};
diff --git a/libs/coin-modules/coin-icon/src/prepareTransaction.ts b/libs/coin-modules/coin-icon/src/prepareTransaction.ts
new file mode 100644
index 000000000000..e6d5bb0ab1dc
--- /dev/null
+++ b/libs/coin-modules/coin-icon/src/prepareTransaction.ts
@@ -0,0 +1,24 @@
+import type { IconAccount, Transaction } from "./types";
+import getEstimatedFees from "./getFeesForTransaction";
+import BigNumber from "bignumber.js";
+
+const sameFees = (a: BigNumber, b?: BigNumber | null) => (!a || !b ? a === b : a.eq(b));
+
+/**
+ * Prepare transaction before checking status
+ *
+ * @param {IconAccount} account
+ * @param {Transaction} transaction
+ */
+export const prepareTransaction = async (
+ account: IconAccount,
+ transaction: Transaction,
+): Promise => {
+ let fees = transaction.fees;
+ fees = await getEstimatedFees({ account, transaction });
+
+ if (fees && !sameFees(fees, transaction.fees)) {
+ return { ...transaction, fees };
+ }
+ return transaction;
+};
diff --git a/libs/coin-modules/coin-icon/src/serialization.ts b/libs/coin-modules/coin-icon/src/serialization.ts
new file mode 100644
index 000000000000..d48063b7cfa8
--- /dev/null
+++ b/libs/coin-modules/coin-icon/src/serialization.ts
@@ -0,0 +1,34 @@
+import { BigNumber } from "bignumber.js";
+import type { IconResourcesRaw, IconResources, IconAccount, IconAccountRaw } from "./types";
+import { AccountRaw, Account } from "@ledgerhq/types-live";
+
+export function toIconResourcesRaw(resources: IconResources): IconResourcesRaw {
+ const { nonce, votingPower, totalDelegated } = resources;
+ return {
+ nonce,
+ votingPower: votingPower.toString(),
+ totalDelegated: totalDelegated.toString(),
+ };
+}
+
+export function fromIconResourcesRaw(rawResources: IconResourcesRaw): IconResources {
+ const { nonce, votingPower, totalDelegated } = rawResources;
+ return {
+ nonce,
+ votingPower: new BigNumber(votingPower || 0),
+ totalDelegated: new BigNumber(totalDelegated || 0),
+ };
+}
+
+export function assignToAccountRaw(account: Account, accountRaw: AccountRaw): void {
+ const iconAccount = account as IconAccount;
+ if (iconAccount.iconResources) {
+ (accountRaw as IconAccountRaw).iconResources = toIconResourcesRaw(iconAccount.iconResources);
+ }
+}
+
+export function assignFromAccountRaw(accountRaw: AccountRaw, account: Account) {
+ const iconResourcesRaw = (accountRaw as IconAccountRaw).iconResources;
+ if (iconResourcesRaw)
+ (account as IconAccount).iconResources = fromIconResourcesRaw(iconResourcesRaw);
+}
diff --git a/libs/coin-modules/coin-icon/src/signOperation.ts b/libs/coin-modules/coin-icon/src/signOperation.ts
new file mode 100644
index 000000000000..37cbedd04951
--- /dev/null
+++ b/libs/coin-modules/coin-icon/src/signOperation.ts
@@ -0,0 +1,140 @@
+import { BigNumber } from "bignumber.js";
+import { Observable } from "rxjs";
+
+import type { IconAccount, Transaction } from "./types";
+import type {
+ Account,
+ AccountBridge,
+ DeviceId,
+ Operation,
+ SignOperationEvent,
+} from "@ledgerhq/types-live";
+
+import { encodeOperationId } from "@ledgerhq/coin-framework/operation";
+
+import { buildTransaction } from "./buildTransaction";
+import { calculateAmount, getNonce } from "./logic";
+import { FeeNotLoaded } from "@ledgerhq/errors";
+import IconService, { IcxTransaction } from "icon-sdk-js";
+import { SignerContext } from "@ledgerhq/coin-framework/signer";
+import { IconSignature, IconSigner } from "./signer";
+const { IconUtil, IconConverter } = IconService;
+
+const buildOptimisticOperation = (
+ account: Account,
+ transaction: Transaction,
+ fee: BigNumber,
+): Operation => {
+ const type = "OUT";
+ const value = new BigNumber(transaction.amount).plus(fee);
+ const operation: Operation = {
+ id: encodeOperationId(account.id, "", type),
+ hash: "",
+ type,
+ value,
+ fee,
+ blockHash: null,
+ blockHeight: null,
+ senders: [account.freshAddress],
+ recipients: [transaction.recipient].filter(Boolean),
+ accountId: account.id,
+ transactionSequenceNumber: getNonce(account as IconAccount),
+ date: new Date(),
+ extra: {},
+ };
+
+ return operation;
+};
+
+/**
+ * Adds signature to unsigned transaction. Will likely be a call to Icon SDK
+ */
+const addSignature = (rawTransaction: IcxTransaction, signature: string) => {
+ return {
+ rawTransaction: {
+ ...rawTransaction,
+ signature: signature,
+ },
+ signature,
+ };
+};
+
+/**
+ * Sign Transaction with Ledger hardware
+ */
+export const buildSignOperation =
+ (signerContext: SignerContext): AccountBridge["signOperation"] =>
+ ({
+ account,
+ transaction,
+ deviceId,
+ }: {
+ account: Account;
+ transaction: Transaction;
+ deviceId: DeviceId;
+ }): Observable =>
+ new Observable(o => {
+ let cancelled = false;
+ async function main() {
+ if (!transaction.fees) {
+ throw new FeeNotLoaded();
+ }
+
+ const transactionToSign = {
+ ...transaction,
+ amount: calculateAmount({
+ account: account as IconAccount,
+ transaction: transaction,
+ }),
+ };
+ const { unsigned } = await buildTransaction(
+ account as IconAccount,
+ transactionToSign,
+ transactionToSign.stepLimit,
+ );
+
+ o.next({ type: "device-signature-requested" });
+
+ const res = (await signerContext(deviceId, signer =>
+ signer.signTransaction(
+ account.freshAddressPath,
+ IconUtil.generateHashKey(IconConverter.toRawTransaction(unsigned)),
+ ),
+ )) as IconSignature;
+
+ const signed = addSignature(unsigned, res.signedRawTxBase64);
+
+ if (cancelled) return;
+ o.next({ type: "device-signature-granted" });
+
+ if (!signed.signature) {
+ throw new Error("No signature");
+ }
+
+ const operation = buildOptimisticOperation(
+ account,
+ transactionToSign,
+ transactionToSign.fees ?? new BigNumber(0),
+ );
+
+ o.next({
+ type: "signed",
+ signedOperation: {
+ operation,
+ signature: signed.signature,
+ rawData: signed.rawTransaction,
+ },
+ });
+ }
+
+ main().then(
+ () => o.complete(),
+ e => o.error(e),
+ );
+
+ return () => {
+ cancelled = true;
+ };
+ });
+
+export default buildSignOperation;
diff --git a/libs/coin-modules/coin-icon/src/signer.ts b/libs/coin-modules/coin-icon/src/signer.ts
new file mode 100644
index 000000000000..2eba65d8b952
--- /dev/null
+++ b/libs/coin-modules/coin-icon/src/signer.ts
@@ -0,0 +1,14 @@
+export type IconAddress = {
+ publicKey: string;
+ address: string;
+ chainCode?: string;
+};
+export type IconSignature = {
+ signedRawTxBase64: string;
+ hashHex: string;
+};
+
+export interface IconSigner {
+ getAddress(path: string, shouldDisplay?: boolean): Promise;
+ signTransaction(path: string, rawTxAscii: string): Promise;
+}
diff --git a/libs/coin-modules/coin-icon/src/specs.ts b/libs/coin-modules/coin-icon/src/specs.ts
new file mode 100644
index 000000000000..39fb83213ec9
--- /dev/null
+++ b/libs/coin-modules/coin-icon/src/specs.ts
@@ -0,0 +1,124 @@
+import invariant from "invariant";
+import { botTest, pickSiblings } from "@ledgerhq/coin-framework/bot/specs";
+import type { AppSpec } from "@ledgerhq/coin-framework/bot/types";
+import { toOperationRaw } from "@ledgerhq/coin-framework/serialization/index";
+import { DeviceModelId } from "@ledgerhq/devices";
+import BigNumber from "bignumber.js";
+import expect from "expect";
+import { acceptTransaction } from "./speculos-deviceActions";
+import {
+ EXISTENTIAL_DEPOSIT,
+ EXISTENTIAL_DEPOSIT_RECOMMENDED_MARGIN,
+ convertICXtoLoop,
+} from "./logic";
+import { Transaction } from "./types";
+import { getCryptoCurrencyById } from "@ledgerhq/cryptoassets/currencies";
+
+const maxAccounts = 6;
+const currency = getCryptoCurrencyById("icon");
+
+// FIX ME ICON have a bug where the amounts from the API have imprecisions
+const expectedApproximate = (
+ value: BigNumber,
+ expected: BigNumber,
+ delta = convertICXtoLoop(0.000005),
+) => {
+ if (value.minus(expected).abs().gt(delta)) {
+ expect(value.toString()).toEqual(value.toString());
+ }
+};
+
+const icon: AppSpec = {
+ name: "Icon",
+ currency,
+ appQuery: {
+ model: DeviceModelId.nanoS,
+ appName: "Icon",
+ },
+ genericDeviceAction: acceptTransaction,
+ testTimeout: 2 * 60 * 1000,
+ transactionCheck: ({ maxSpendable }) => {
+ invariant(maxSpendable.gt(EXISTENTIAL_DEPOSIT_RECOMMENDED_MARGIN), "balance is too low");
+ },
+ test: ({ operation, optimisticOperation }) => {
+ const opExpected: Record = toOperationRaw({
+ ...optimisticOperation,
+ });
+ delete opExpected.value;
+ delete opExpected.fee;
+ delete opExpected.date;
+ delete opExpected.blockHash;
+ delete opExpected.blockHeight;
+ delete opExpected.transactionSequenceNumber;
+ botTest("optimistic operation matches", () =>
+ expect(toOperationRaw(operation)).toMatchObject(opExpected),
+ );
+ },
+ mutations: [
+ {
+ name: "send 50%~",
+ maxRun: 1,
+ transaction: ({ account, siblings, bridge }) => {
+ invariant(account.spendableBalance.gt(0), "balance is 0");
+ const sibling = pickSiblings(siblings, maxAccounts);
+ let amount = account.spendableBalance.div(2).integerValue();
+
+ if (!sibling.used && amount.lt(EXISTENTIAL_DEPOSIT)) {
+ invariant(
+ account.spendableBalance.gt(EXISTENTIAL_DEPOSIT),
+ "send is too low to activate account",
+ );
+ amount = EXISTENTIAL_DEPOSIT;
+ }
+
+ return {
+ transaction: bridge.createTransaction(account),
+ updates: [
+ {
+ recipient: sibling.freshAddress,
+ },
+ {
+ amount,
+ },
+ ],
+ };
+ },
+ test: ({ accountBeforeTransaction, operation, account }) => {
+ botTest("account spendable balance decreased with operation", () =>
+ expectedApproximate(
+ account.spendableBalance,
+ accountBeforeTransaction.spendableBalance.minus(operation.value),
+ ),
+ );
+ },
+ },
+ {
+ name: "send max",
+ maxRun: 1,
+ transaction: ({ account, siblings, bridge }) => {
+ invariant(account.spendableBalance.gt(0), "balance is 0");
+ const sibling = pickSiblings(siblings, maxAccounts);
+ return {
+ transaction: bridge.createTransaction(account),
+ updates: [
+ {
+ recipient: sibling.freshAddress,
+ },
+ {
+ useAllAmount: true,
+ },
+ ],
+ };
+ },
+ test: ({ account }) => {
+ botTest("account spendable balance is zero", () =>
+ expectedApproximate(account.spendableBalance, new BigNumber(0)),
+ );
+ },
+ },
+ ],
+};
+
+export default {
+ icon,
+};
diff --git a/libs/coin-modules/coin-icon/src/speculos-deviceActions.ts b/libs/coin-modules/coin-icon/src/speculos-deviceActions.ts
new file mode 100644
index 000000000000..dba580911606
--- /dev/null
+++ b/libs/coin-modules/coin-icon/src/speculos-deviceActions.ts
@@ -0,0 +1,46 @@
+import type { DeviceAction } from "@ledgerhq/coin-framework/bot/types";
+import type { Transaction } from "./types";
+import { formatCurrencyUnit } from "@ledgerhq/coin-framework/currencies/index";
+import { deviceActionFlow, SpeculosButton } from "@ledgerhq/coin-framework/bot/specs";
+import { getAccountCurrency } from "@ledgerhq/coin-framework/account/index";
+
+const confirmWording: Record = {
+ send: "transfer",
+};
+
+export const acceptTransaction: DeviceAction = deviceActionFlow({
+ steps: [
+ {
+ title: "Confirm",
+ button: SpeculosButton.RIGHT,
+ expectedValue: ({ transaction }) => confirmWording[transaction.mode],
+ },
+ {
+ title: "Amount",
+ button: SpeculosButton.RIGHT,
+ expectedValue: ({ transaction, account }) =>
+ formatCurrencyUnit(getAccountCurrency(account).units[0], transaction.amount, {
+ disableRounding: true,
+ }),
+ },
+ {
+ title: "Address",
+ button: SpeculosButton.RIGHT,
+ expectedValue: ({ transaction }) => {
+ return transaction.recipient;
+ },
+ },
+ {
+ title: "Fees",
+ button: SpeculosButton.RIGHT,
+ expectedValue: ({ account, status }) =>
+ formatCurrencyUnit(getAccountCurrency(account).units[0], status.estimatedFees, {
+ disableRounding: true,
+ }),
+ },
+ {
+ title: "Accept",
+ button: SpeculosButton.BOTH,
+ },
+ ],
+});
diff --git a/libs/coin-modules/coin-icon/src/synchronization.ts b/libs/coin-modules/coin-icon/src/synchronization.ts
new file mode 100644
index 000000000000..18f15a34123f
--- /dev/null
+++ b/libs/coin-modules/coin-icon/src/synchronization.ts
@@ -0,0 +1,70 @@
+import { encodeAccountId } from "@ledgerhq/coin-framework/account/accountId";
+import { mergeOps, AccountShapeInfo } from "@ledgerhq/coin-framework/bridge/jsHelpers";
+import type { GetAccountShape } from "@ledgerhq/coin-framework/bridge/jsHelpers";
+
+import { getAccount, getCurrentBlockHeight, getOperations } from "./api";
+import BigNumber from "bignumber.js";
+import { getDelegation } from "./api/node";
+import { IconResources } from "./types";
+import { convertICXtoLoop } from "./logic";
+
+export const getAccountShape: GetAccountShape = async (info: AccountShapeInfo) => {
+ const { address, initialAccount, currency, derivationMode } = info;
+ const accountId = encodeAccountId({
+ type: "js",
+ version: "2",
+ currencyId: currency.id,
+ xpubOrAddress: address,
+ derivationMode,
+ });
+ try {
+ const oldOperations = initialAccount?.operations || [];
+ const blockHeight = await getCurrentBlockHeight(currency);
+ const iconAccount = await getAccount(info.address, currency);
+
+ // Merge new operations with the previously synced ones
+ const newOperations = await getOperations(
+ accountId,
+ address,
+ 0, // skip === 0 is the lastest transaction
+ currency,
+ iconAccount?.contract_updated_block - oldOperations.length,
+ );
+ const operations = mergeOps(oldOperations, newOperations);
+ const delegationData = await getDelegation(address, currency);
+ const iconResources: IconResources = {
+ nonce: 0,
+ totalDelegated: delegationData.totalDelegated,
+ votingPower: delegationData.votingPower,
+ };
+
+ const balance = convertICXtoLoop(iconAccount?.balance || 0);
+ const spendableBalance = balance
+ .minus(iconResources.totalDelegated)
+ .minus(iconResources.votingPower);
+
+ return {
+ id: accountId,
+ balance,
+ spendableBalance,
+ operationsCount: operations.length,
+ blockHeight,
+ iconResources,
+ operations,
+ };
+ } catch (error) {
+ return {
+ id: accountId,
+ balance: new BigNumber(0),
+ spendableBalance: new BigNumber(0),
+ operationsCount: 0,
+ iconResources: {
+ nonce: 0,
+ totalDelegated: new BigNumber(0),
+ votingPower: new BigNumber(0),
+ },
+ blockHeight: 0,
+ operations: [],
+ };
+ }
+};
diff --git a/libs/coin-modules/coin-icon/src/transaction.ts b/libs/coin-modules/coin-icon/src/transaction.ts
new file mode 100644
index 000000000000..eb6590eafd5c
--- /dev/null
+++ b/libs/coin-modules/coin-icon/src/transaction.ts
@@ -0,0 +1,60 @@
+import type { Transaction, TransactionRaw } from "./types";
+import { BigNumber } from "bignumber.js";
+import { formatTransactionStatus } from "@ledgerhq/coin-framework/formatters";
+import {
+ fromTransactionCommonRaw,
+ fromTransactionStatusRawCommon as fromTransactionStatusRaw,
+ toTransactionCommonRaw,
+ toTransactionStatusRawCommon as toTransactionStatusRaw,
+} from "@ledgerhq/coin-framework/serialization";
+import type { Account } from "@ledgerhq/types-live";
+import { getAccountCurrency } from "@ledgerhq/coin-framework/account/index";
+import { formatCurrencyUnit } from "@ledgerhq/coin-framework/currencies/index";
+
+export const formatTransaction = (
+ { mode, amount, recipient, useAllAmount }: Transaction,
+ account: Account,
+): string =>
+ `
+${mode.toUpperCase()} ${
+ useAllAmount
+ ? "MAX"
+ : amount.isZero()
+ ? ""
+ : " " +
+ formatCurrencyUnit(getAccountCurrency(account).units[0], amount, {
+ showCode: true,
+ disableRounding: true,
+ })
+ }${recipient ? `\nTO ${recipient}` : ""}`;
+
+export const fromTransactionRaw = (tr: TransactionRaw): Transaction => {
+ const common = fromTransactionCommonRaw(tr);
+ return {
+ ...common,
+ family: tr.family,
+ mode: tr.mode,
+ fees: tr.fees ? new BigNumber(tr.fees) : null,
+ stepLimit: tr.stepLimit ? new BigNumber(tr.stepLimit) : undefined,
+ };
+};
+
+export const toTransactionRaw = (t: Transaction): TransactionRaw => {
+ const common = toTransactionCommonRaw(t);
+ return {
+ ...common,
+ family: t.family,
+ mode: t.mode,
+ fees: t.fees?.toString() || null,
+ stepLimit: t.stepLimit?.toString() || undefined,
+ };
+};
+
+export default {
+ formatTransaction,
+ fromTransactionRaw,
+ toTransactionRaw,
+ fromTransactionStatusRaw,
+ toTransactionStatusRaw,
+ formatTransactionStatus,
+};
diff --git a/libs/coin-modules/coin-icon/src/types.ts b/libs/coin-modules/coin-icon/src/types.ts
new file mode 100644
index 000000000000..a409c5c706bf
--- /dev/null
+++ b/libs/coin-modules/coin-icon/src/types.ts
@@ -0,0 +1,61 @@
+import type { BigNumber } from "bignumber.js";
+import {
+ Account,
+ AccountRaw,
+ TransactionCommon,
+ TransactionCommonRaw,
+ TransactionStatusCommon,
+ TransactionStatusCommonRaw,
+ Operation,
+} from "@ledgerhq/types-live";
+
+/**
+ * Icon account resources
+ */
+export type IconResources = {
+ nonce: number;
+ votingPower: string | BigNumber;
+ totalDelegated: string | BigNumber;
+};
+
+/**
+ * Icon account resources from raw JSON
+ */
+export type IconResourcesRaw = {
+ nonce: number;
+ votingPower: string | BigNumber;
+ totalDelegated: string | BigNumber;
+};
+
+/**
+ * Icon transaction
+ */
+export type Transaction = TransactionCommon & {
+ mode: string;
+ family: "icon";
+ fees?: BigNumber | null | undefined;
+ stepLimit?: BigNumber;
+};
+
+/**
+ * Icon transaction from a raw JSON
+ */
+export type TransactionRaw = TransactionCommonRaw & {
+ family: "icon";
+ mode: string;
+ fees?: string | null | undefined;
+ stepLimit?: string;
+ // also the transaction fields as raw JSON data
+};
+
+export type IconOperation = Operation;
+
+export type IconAccount = Account & { iconResources: IconResources };
+
+export type IconAccountRaw = AccountRaw & {
+ iconResources: IconResourcesRaw;
+};
+
+export type TransactionStatus = TransactionStatusCommon;
+
+export type TransactionStatusRaw = TransactionStatusCommonRaw;
diff --git a/libs/coin-modules/coin-icon/tsconfig.json b/libs/coin-modules/coin-icon/tsconfig.json
new file mode 100644
index 000000000000..1011d3e82edc
--- /dev/null
+++ b/libs/coin-modules/coin-icon/tsconfig.json
@@ -0,0 +1,12 @@
+{
+ "extends": "../../../tsconfig.base",
+ "compilerOptions": {
+ "declaration": true,
+ "declarationMap": true,
+ "module": "commonjs",
+ "downlevelIteration": true,
+ "lib": ["es2020", "dom"],
+ "outDir": "lib"
+ },
+ "include": ["src/**/*"],
+}
diff --git a/libs/env/src/env.ts b/libs/env/src/env.ts
index e9364484a43e..350ecc381356 100644
--- a/libs/env/src/env.ts
+++ b/libs/env/src/env.ts
@@ -227,6 +227,36 @@ const envDefinitions = {
parser: stringParser,
desc: "Cardano API url",
},
+ ICON_NODE_ENDPOINT: {
+ parser: stringParser,
+ def: "https://icon.coin.ledger.com/api/v3",
+ desc: "ICON RPC url",
+ },
+ ICON_DEBUG_ENDPOINT: {
+ parser: stringParser,
+ def: "https://icon.coin.ledger.com/api/v3d",
+ desc: "ICON debug RPC url",
+ },
+ ICON_INDEXER_ENDPOINT: {
+ parser: stringParser,
+ def: "https://icon.coin.ledger.com/api/v1",
+ desc: "ICON API url",
+ },
+ ICON_TESTNET_NODE_ENDPOINT: {
+ parser: stringParser,
+ def: "https://berlin.net.solidwallet.io/api/v3",
+ desc: "ICON Berlin Testnet API url",
+ },
+ ICON_TESTNET_DEBUG_ENDPOINT: {
+ parser: stringParser,
+ def: "https://berlin.net.solidwallet.io/api/v3d",
+ desc: "ICON Berlin Testnet debug",
+ },
+ ICON_TESTNET_INDEXER_ENDPOINT: {
+ parser: stringParser,
+ def: "https://tracker.berlin.icon.community/api/v1",
+ desc: "ICON Berlin Testnet API url",
+ },
COINAPPS: {
def: "",
parser: stringParser,
diff --git a/libs/ledger-live-common/package.json b/libs/ledger-live-common/package.json
index 8e91dd43636e..01846705a3fc 100644
--- a/libs/ledger-live-common/package.json
+++ b/libs/ledger-live-common/package.json
@@ -140,6 +140,7 @@
"@ledgerhq/coin-framework": "workspace:^",
"@ledgerhq/coin-near": "workspace:^",
"@ledgerhq/coin-polkadot": "workspace:^",
+ "@ledgerhq/coin-icon": "workspace:^",
"@ledgerhq/coin-solana": "workspace:^",
"@ledgerhq/coin-tezos": "workspace:^",
"@ledgerhq/coin-xrp": "workspace:^",
@@ -161,6 +162,7 @@
"@ledgerhq/hw-app-trx": "workspace:^",
"@ledgerhq/hw-app-vet": "workspace:^",
"@ledgerhq/hw-app-xrp": "workspace:^",
+ "@ledgerhq/hw-app-icon": "workspace:^",
"@ledgerhq/hw-transport": "workspace:^",
"@ledgerhq/hw-transport-mocker": "workspace:^",
"@ledgerhq/live-app-sdk": "^0.8.1",
diff --git a/libs/ledger-live-common/scripts/sync-families-dispatch.mjs b/libs/ledger-live-common/scripts/sync-families-dispatch.mjs
index 41bd8af49611..4123e23d9ee2 100644
--- a/libs/ledger-live-common/scripts/sync-families-dispatch.mjs
+++ b/libs/ledger-live-common/scripts/sync-families-dispatch.mjs
@@ -31,6 +31,7 @@ const familiesWPackage = [
"solana",
"tezos",
"xrp",
+ "icon",
];
cd(path.join(__dirname, "..", "src"));
diff --git a/libs/ledger-live-common/src/__tests__/test-helpers/environment.ts b/libs/ledger-live-common/src/__tests__/test-helpers/environment.ts
index 4c583371f6fe..3ce507968e61 100644
--- a/libs/ledger-live-common/src/__tests__/test-helpers/environment.ts
+++ b/libs/ledger-live-common/src/__tests__/test-helpers/environment.ts
@@ -69,6 +69,8 @@ setSupportedCurrencies([
"songbird",
"flare",
"near",
+ "icon",
+ "icon_berlin_testnet",
"optimism",
"optimism_sepolia",
"arbitrum",
diff --git a/libs/ledger-live-common/src/config/sharedConfig.ts b/libs/ledger-live-common/src/config/sharedConfig.ts
index 04b747a980cf..bcaa6ba8eea4 100644
--- a/libs/ledger-live-common/src/config/sharedConfig.ts
+++ b/libs/ledger-live-common/src/config/sharedConfig.ts
@@ -20,6 +20,7 @@ import { stellarConfig } from "../families/stellar/config";
import { tezosConfig } from "../families/tezos/config";
import { tronConfig } from "../families/tron/config";
import { vechainConfig } from "../families/vechain/config";
+import { iconConfig } from "../families/icon/config";
import { appConfig } from "../apps/config";
const countervaluesConfig: ConfigSchema = {
@@ -61,4 +62,5 @@ export const liveConfig: ConfigSchema = {
...tezosConfig,
...tronConfig,
...vechainConfig,
+ ...iconConfig,
};
diff --git a/libs/ledger-live-common/src/families/icon/__snapshots__/bridge.integration.test.ts.snap b/libs/ledger-live-common/src/families/icon/__snapshots__/bridge.integration.test.ts.snap
new file mode 100644
index 000000000000..6796f090a074
--- /dev/null
+++ b/libs/ledger-live-common/src/families/icon/__snapshots__/bridge.integration.test.ts.snap
@@ -0,0 +1,434 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`icon currency bridge scanAccounts icon seed 1 1`] = `
+[
+ {
+ "balance": "0",
+ "currencyId": "icon",
+ "derivationMode": "icon",
+ "freshAddress": "hxcf4e2c94b52619f72aa4da13370aa6c1c3dad00d",
+ "freshAddressPath": "44'/4801368'/0'/0'/0'",
+ "iconResources": {
+ "nonce": 0,
+ "totalDelegated": "0",
+ "votingPower": "0",
+ },
+ "id": "js:2:icon:hxcf4e2c94b52619f72aa4da13370aa6c1c3dad00d:icon",
+ "index": 0,
+ "operationsCount": 21,
+ "pendingOperations": [],
+ "seedIdentifier": "0426dd3cdd69f3604e9c1d8f3021b20e133524f12894b9ef8e35d3f1f5244516d18b7bb0d1fee7b7c2972d5cde08190d66cf932d0addd5ac87f53198d050940ccb",
+ "spendableBalance": "0",
+ "swapHistory": [],
+ "syncHash": undefined,
+ "used": true,
+ },
+ {
+ "balance": "0",
+ "currencyId": "icon",
+ "derivationMode": "icon",
+ "freshAddress": "hxfc049b4ce60cc5968487d180d9c4263e63181739",
+ "freshAddressPath": "44'/4801368'/0'/0'/1'",
+ "iconResources": {
+ "nonce": 0,
+ "totalDelegated": "0",
+ "votingPower": "0",
+ },
+ "id": "js:2:icon:hxfc049b4ce60cc5968487d180d9c4263e63181739:icon",
+ "index": 1,
+ "operationsCount": 0,
+ "pendingOperations": [],
+ "seedIdentifier": "0426dd3cdd69f3604e9c1d8f3021b20e133524f12894b9ef8e35d3f1f5244516d18b7bb0d1fee7b7c2972d5cde08190d66cf932d0addd5ac87f53198d050940ccb",
+ "spendableBalance": "0",
+ "swapHistory": [],
+ "syncHash": undefined,
+ "used": false,
+ },
+]
+`;
+
+exports[`icon currency bridge scanAccounts icon seed 1 2`] = `
+[
+ [
+ {
+ "accountId": "js:2:icon:hxcf4e2c94b52619f72aa4da13370aa6c1c3dad00d:icon",
+ "blockHash": null,
+ "blockHeight": 81833326,
+ "extra": {},
+ "fee": "1250000000000000",
+ "hasFailed": false,
+ "hash": "0x09e597274b3dbf7e7225b248724f65e30a32a2c312a587e9999b7ff942585c2e",
+ "id": "js:2:icon:hxcf4e2c94b52619f72aa4da13370aa6c1c3dad00d:icon-0x09e597274b3dbf7e7225b248724f65e30a32a2c312a587e9999b7ff942585c2e-OUT",
+ "recipients": [
+ "hx2dbaef576c73cca274db0b602831c2c6a9fc4bba",
+ ],
+ "senders": [
+ "hxcf4e2c94b52619f72aa4da13370aa6c1c3dad00d",
+ ],
+ "type": "OUT",
+ "value": "750000000000000000",
+ },
+ {
+ "accountId": "js:2:icon:hxcf4e2c94b52619f72aa4da13370aa6c1c3dad00d:icon",
+ "blockHash": null,
+ "blockHeight": 80919307,
+ "extra": {},
+ "fee": "1250000000000000",
+ "hasFailed": false,
+ "hash": "0x0c3170fb250e6735e1a5f51dde9104fca3368ec745f6aa1e19679a9cc6d756da",
+ "id": "js:2:icon:hxcf4e2c94b52619f72aa4da13370aa6c1c3dad00d:icon-0x0c3170fb250e6735e1a5f51dde9104fca3368ec745f6aa1e19679a9cc6d756da-OUT",
+ "recipients": [
+ "hx2dbaef576c73cca274db0b602831c2c6a9fc4bba",
+ ],
+ "senders": [
+ "hxcf4e2c94b52619f72aa4da13370aa6c1c3dad00d",
+ ],
+ "type": "OUT",
+ "value": "1234000000000000000",
+ },
+ {
+ "accountId": "js:2:icon:hxcf4e2c94b52619f72aa4da13370aa6c1c3dad00d:icon",
+ "blockHash": null,
+ "blockHeight": 82738449,
+ "extra": {},
+ "fee": "1250000000000000",
+ "hasFailed": false,
+ "hash": "0x0f6a15de41871086480ebe1f42365df8e20537dc9eceae250bc91cbe29c017ca",
+ "id": "js:2:icon:hxcf4e2c94b52619f72aa4da13370aa6c1c3dad00d:icon-0x0f6a15de41871086480ebe1f42365df8e20537dc9eceae250bc91cbe29c017ca-OUT",
+ "recipients": [
+ "hx2dbaef576c73cca274db0b602831c2c6a9fc4bba",
+ ],
+ "senders": [
+ "hxcf4e2c94b52619f72aa4da13370aa6c1c3dad00d",
+ ],
+ "type": "OUT",
+ "value": "1200000000000000000",
+ },
+ {
+ "accountId": "js:2:icon:hxcf4e2c94b52619f72aa4da13370aa6c1c3dad00d:icon",
+ "blockHash": null,
+ "blockHeight": 80823008,
+ "extra": {},
+ "fee": "1250000000000000",
+ "hasFailed": false,
+ "hash": "0x13190d4a84cb2e088b858da3aa4f66e99e5c8444831e1701220142fa33640f41",
+ "id": "js:2:icon:hxcf4e2c94b52619f72aa4da13370aa6c1c3dad00d:icon-0x13190d4a84cb2e088b858da3aa4f66e99e5c8444831e1701220142fa33640f41-IN",
+ "recipients": [
+ "hxcf4e2c94b52619f72aa4da13370aa6c1c3dad00d",
+ ],
+ "senders": [
+ "hx2dbaef576c73cca274db0b602831c2c6a9fc4bba",
+ ],
+ "type": "IN",
+ "value": "500000000000000000",
+ },
+ {
+ "accountId": "js:2:icon:hxcf4e2c94b52619f72aa4da13370aa6c1c3dad00d:icon",
+ "blockHash": null,
+ "blockHeight": 81962827,
+ "extra": {},
+ "fee": "1250000000000000",
+ "hasFailed": false,
+ "hash": "0x15c1ee113cb1d5fe1093d4a45d7f276da473349023e1de51ace2aa2d27faf796",
+ "id": "js:2:icon:hxcf4e2c94b52619f72aa4da13370aa6c1c3dad00d:icon-0x15c1ee113cb1d5fe1093d4a45d7f276da473349023e1de51ace2aa2d27faf796-IN",
+ "recipients": [
+ "hxcf4e2c94b52619f72aa4da13370aa6c1c3dad00d",
+ ],
+ "senders": [
+ "hx2dbaef576c73cca274db0b602831c2c6a9fc4bba",
+ ],
+ "type": "IN",
+ "value": "880000000000000000",
+ },
+ {
+ "accountId": "js:2:icon:hxcf4e2c94b52619f72aa4da13370aa6c1c3dad00d:icon",
+ "blockHash": null,
+ "blockHeight": 81833253,
+ "extra": {},
+ "fee": "1250000000000000",
+ "hasFailed": false,
+ "hash": "0x23683155bebac309bac6cf932a55621adb393df306c58685a66410409428ba2f",
+ "id": "js:2:icon:hxcf4e2c94b52619f72aa4da13370aa6c1c3dad00d:icon-0x23683155bebac309bac6cf932a55621adb393df306c58685a66410409428ba2f-IN",
+ "recipients": [
+ "hxcf4e2c94b52619f72aa4da13370aa6c1c3dad00d",
+ ],
+ "senders": [
+ "hx2dbaef576c73cca274db0b602831c2c6a9fc4bba",
+ ],
+ "type": "IN",
+ "value": "750000000000000000",
+ },
+ {
+ "accountId": "js:2:icon:hxcf4e2c94b52619f72aa4da13370aa6c1c3dad00d:icon",
+ "blockHash": null,
+ "blockHeight": 82173689,
+ "extra": {},
+ "fee": "1250000000000000",
+ "hasFailed": false,
+ "hash": "0x662110befe9b8925cec86f6c13d396f35cbabfe451f3302156eede5b4bb62dc4",
+ "id": "js:2:icon:hxcf4e2c94b52619f72aa4da13370aa6c1c3dad00d:icon-0x662110befe9b8925cec86f6c13d396f35cbabfe451f3302156eede5b4bb62dc4-OUT",
+ "recipients": [
+ "hx2dbaef576c73cca274db0b602831c2c6a9fc4bba",
+ ],
+ "senders": [
+ "hxcf4e2c94b52619f72aa4da13370aa6c1c3dad00d",
+ ],
+ "type": "OUT",
+ "value": "980000000000000000",
+ },
+ {
+ "accountId": "js:2:icon:hxcf4e2c94b52619f72aa4da13370aa6c1c3dad00d:icon",
+ "blockHash": null,
+ "blockHeight": 80919225,
+ "extra": {},
+ "fee": "1250000000000000",
+ "hasFailed": false,
+ "hash": "0x666196ba5b51d3d6beb18c25ba1e6ee5b8f72520678867f3e77ef446adcde235",
+ "id": "js:2:icon:hxcf4e2c94b52619f72aa4da13370aa6c1c3dad00d:icon-0x666196ba5b51d3d6beb18c25ba1e6ee5b8f72520678867f3e77ef446adcde235-IN",
+ "recipients": [
+ "hxcf4e2c94b52619f72aa4da13370aa6c1c3dad00d",
+ ],
+ "senders": [
+ "hx2dbaef576c73cca274db0b602831c2c6a9fc4bba",
+ ],
+ "type": "IN",
+ "value": "1234000000000000000",
+ },
+ {
+ "accountId": "js:2:icon:hxcf4e2c94b52619f72aa4da13370aa6c1c3dad00d:icon",
+ "blockHash": null,
+ "blockHeight": 82738092,
+ "extra": {},
+ "fee": "1250000000000000",
+ "hasFailed": false,
+ "hash": "0x794e6038769a07d9b016ca8f3869722dcff25be6dcf609c45772a154a4e6657b",
+ "id": "js:2:icon:hxcf4e2c94b52619f72aa4da13370aa6c1c3dad00d:icon-0x794e6038769a07d9b016ca8f3869722dcff25be6dcf609c45772a154a4e6657b-OUT",
+ "recipients": [
+ "hx2dbaef576c73cca274db0b602831c2c6a9fc4bba",
+ ],
+ "senders": [
+ "hxcf4e2c94b52619f72aa4da13370aa6c1c3dad00d",
+ ],
+ "type": "OUT",
+ "value": "1234000000000000000",
+ },
+ {
+ "accountId": "js:2:icon:hxcf4e2c94b52619f72aa4da13370aa6c1c3dad00d:icon",
+ "blockHash": null,
+ "blockHeight": 82737965,
+ "extra": {},
+ "fee": "1250000000000000",
+ "hasFailed": false,
+ "hash": "0x7fef9e6b4efe6f32fea8be4f3e605189572019dd6cbbbcca211212f5331a967f",
+ "id": "js:2:icon:hxcf4e2c94b52619f72aa4da13370aa6c1c3dad00d:icon-0x7fef9e6b4efe6f32fea8be4f3e605189572019dd6cbbbcca211212f5331a967f-IN",
+ "recipients": [
+ "hxcf4e2c94b52619f72aa4da13370aa6c1c3dad00d",
+ ],
+ "senders": [
+ "hx2dbaef576c73cca274db0b602831c2c6a9fc4bba",
+ ],
+ "type": "IN",
+ "value": "1234000000000000000",
+ },
+ {
+ "accountId": "js:2:icon:hxcf4e2c94b52619f72aa4da13370aa6c1c3dad00d:icon",
+ "blockHash": null,
+ "blockHeight": 81834658,
+ "extra": {},
+ "fee": "1250000000000000",
+ "hasFailed": false,
+ "hash": "0x804c4367a21da8ee5db04c3411b716efe130a69bf55ff105c67c16bc022145bf",
+ "id": "js:2:icon:hxcf4e2c94b52619f72aa4da13370aa6c1c3dad00d:icon-0x804c4367a21da8ee5db04c3411b716efe130a69bf55ff105c67c16bc022145bf-IN",
+ "recipients": [
+ "hxcf4e2c94b52619f72aa4da13370aa6c1c3dad00d",
+ ],
+ "senders": [
+ "hx2dbaef576c73cca274db0b602831c2c6a9fc4bba",
+ ],
+ "type": "IN",
+ "value": "870000000000000000",
+ },
+ {
+ "accountId": "js:2:icon:hxcf4e2c94b52619f72aa4da13370aa6c1c3dad00d:icon",
+ "blockHash": null,
+ "blockHeight": 81962885,
+ "extra": {},
+ "fee": "1250000000000000",
+ "hasFailed": false,
+ "hash": "0x90d3cf477c0b0e397a0b2b950fcb0baf9209907427b45409d4779a800dda71c4",
+ "id": "js:2:icon:hxcf4e2c94b52619f72aa4da13370aa6c1c3dad00d:icon-0x90d3cf477c0b0e397a0b2b950fcb0baf9209907427b45409d4779a800dda71c4-OUT",
+ "recipients": [
+ "hx2dbaef576c73cca274db0b602831c2c6a9fc4bba",
+ ],
+ "senders": [
+ "hxcf4e2c94b52619f72aa4da13370aa6c1c3dad00d",
+ ],
+ "type": "OUT",
+ "value": "880000000000000000",
+ },
+ {
+ "accountId": "js:2:icon:hxcf4e2c94b52619f72aa4da13370aa6c1c3dad00d:icon",
+ "blockHash": null,
+ "blockHeight": 82738311,
+ "extra": {},
+ "fee": "1250000000000000",
+ "hasFailed": false,
+ "hash": "0x93d741e63b803c6d6e5e0310e949260b85a5aa62827c93983946d35a2a88a9b6",
+ "id": "js:2:icon:hxcf4e2c94b52619f72aa4da13370aa6c1c3dad00d:icon-0x93d741e63b803c6d6e5e0310e949260b85a5aa62827c93983946d35a2a88a9b6-IN",
+ "recipients": [
+ "hxcf4e2c94b52619f72aa4da13370aa6c1c3dad00d",
+ ],
+ "senders": [
+ "hx2dbaef576c73cca274db0b602831c2c6a9fc4bba",
+ ],
+ "type": "IN",
+ "value": "1200000000000000000",
+ },
+ {
+ "accountId": "js:2:icon:hxcf4e2c94b52619f72aa4da13370aa6c1c3dad00d:icon",
+ "blockHash": null,
+ "blockHeight": 82570455,
+ "extra": {},
+ "fee": "1250000000000000",
+ "hasFailed": false,
+ "hash": "0xaa0ed3f7d60e5c4635c5afc8e3bee597dfab8632328a503b270041744b3fce78",
+ "id": "js:2:icon:hxcf4e2c94b52619f72aa4da13370aa6c1c3dad00d:icon-0xaa0ed3f7d60e5c4635c5afc8e3bee597dfab8632328a503b270041744b3fce78-OUT",
+ "recipients": [
+ "hx2dbaef576c73cca274db0b602831c2c6a9fc4bba",
+ ],
+ "senders": [
+ "hxcf4e2c94b52619f72aa4da13370aa6c1c3dad00d",
+ ],
+ "type": "OUT",
+ "value": "1800000000000000000",
+ },
+ {
+ "accountId": "js:2:icon:hxcf4e2c94b52619f72aa4da13370aa6c1c3dad00d:icon",
+ "blockHash": null,
+ "blockHeight": 81316314,
+ "extra": {},
+ "fee": "1250000000000000",
+ "hasFailed": false,
+ "hash": "0xaccf357bc8e9a8eb05ba16004590fdf36ffc79dd2518d168634afedf4978ebed",
+ "id": "js:2:icon:hxcf4e2c94b52619f72aa4da13370aa6c1c3dad00d:icon-0xaccf357bc8e9a8eb05ba16004590fdf36ffc79dd2518d168634afedf4978ebed-OUT",
+ "recipients": [
+ "hx2dbaef576c73cca274db0b602831c2c6a9fc4bba",
+ ],
+ "senders": [
+ "hxcf4e2c94b52619f72aa4da13370aa6c1c3dad00d",
+ ],
+ "type": "OUT",
+ "value": "1464500000000000000",
+ },
+ {
+ "accountId": "js:2:icon:hxcf4e2c94b52619f72aa4da13370aa6c1c3dad00d:icon",
+ "blockHash": null,
+ "blockHeight": 82173636,
+ "extra": {},
+ "fee": "1250000000000000",
+ "hasFailed": false,
+ "hash": "0xbe34f687792b28441981a4cfaf40f85b0bd20a82129c217cf9659822a6fd1221",
+ "id": "js:2:icon:hxcf4e2c94b52619f72aa4da13370aa6c1c3dad00d:icon-0xbe34f687792b28441981a4cfaf40f85b0bd20a82129c217cf9659822a6fd1221-IN",
+ "recipients": [
+ "hxcf4e2c94b52619f72aa4da13370aa6c1c3dad00d",
+ ],
+ "senders": [
+ "hx2dbaef576c73cca274db0b602831c2c6a9fc4bba",
+ ],
+ "type": "IN",
+ "value": "980000000000000000",
+ },
+ {
+ "accountId": "js:2:icon:hxcf4e2c94b52619f72aa4da13370aa6c1c3dad00d:icon",
+ "blockHash": null,
+ "blockHeight": 80823061,
+ "extra": {},
+ "fee": "1250000000000000",
+ "hasFailed": false,
+ "hash": "0xce4c5afb7b422c5f754cd437271815e4830b2037b05a0bc9c80f7d86117859b5",
+ "id": "js:2:icon:hxcf4e2c94b52619f72aa4da13370aa6c1c3dad00d:icon-0xce4c5afb7b422c5f754cd437271815e4830b2037b05a0bc9c80f7d86117859b5-OUT",
+ "recipients": [
+ "hx2dbaef576c73cca274db0b602831c2c6a9fc4bba",
+ ],
+ "senders": [
+ "hxcf4e2c94b52619f72aa4da13370aa6c1c3dad00d",
+ ],
+ "type": "OUT",
+ "value": "500000000000000000",
+ },
+ {
+ "accountId": "js:2:icon:hxcf4e2c94b52619f72aa4da13370aa6c1c3dad00d:icon",
+ "blockHash": null,
+ "blockHeight": 81316237,
+ "extra": {},
+ "fee": "1250000000000000",
+ "hasFailed": false,
+ "hash": "0xda90a65b27bf6e0c58e00b9274d5fecdc2d637946d88334f6cc7429298492b23",
+ "id": "js:2:icon:hxcf4e2c94b52619f72aa4da13370aa6c1c3dad00d:icon-0xda90a65b27bf6e0c58e00b9274d5fecdc2d637946d88334f6cc7429298492b23-IN",
+ "recipients": [
+ "hxcf4e2c94b52619f72aa4da13370aa6c1c3dad00d",
+ ],
+ "senders": [
+ "hx2dbaef576c73cca274db0b602831c2c6a9fc4bba",
+ ],
+ "type": "IN",
+ "value": "764500000000000000",
+ },
+ {
+ "accountId": "js:2:icon:hxcf4e2c94b52619f72aa4da13370aa6c1c3dad00d:icon",
+ "blockHash": null,
+ "blockHeight": 80919366,
+ "extra": {},
+ "fee": "1250000000000000",
+ "hasFailed": false,
+ "hash": "0xdc026e198438d6d7edcf25e92ac34027a04099bf741cee077a4df0fdb3aaf00a",
+ "id": "js:2:icon:hxcf4e2c94b52619f72aa4da13370aa6c1c3dad00d:icon-0xdc026e198438d6d7edcf25e92ac34027a04099bf741cee077a4df0fdb3aaf00a-IN",
+ "recipients": [
+ "hxcf4e2c94b52619f72aa4da13370aa6c1c3dad00d",
+ ],
+ "senders": [
+ "hx2dbaef576c73cca274db0b602831c2c6a9fc4bba",
+ ],
+ "type": "IN",
+ "value": "700000000000000000",
+ },
+ {
+ "accountId": "js:2:icon:hxcf4e2c94b52619f72aa4da13370aa6c1c3dad00d:icon",
+ "blockHash": null,
+ "blockHeight": 82570388,
+ "extra": {},
+ "fee": "1250000000000000",
+ "hasFailed": false,
+ "hash": "0xf6edc460f109a188bbbb7c0d539cf9153b348603e2281ff146a0f190e2140634",
+ "id": "js:2:icon:hxcf4e2c94b52619f72aa4da13370aa6c1c3dad00d:icon-0xf6edc460f109a188bbbb7c0d539cf9153b348603e2281ff146a0f190e2140634-IN",
+ "recipients": [
+ "hxcf4e2c94b52619f72aa4da13370aa6c1c3dad00d",
+ ],
+ "senders": [
+ "hx2dbaef576c73cca274db0b602831c2c6a9fc4bba",
+ ],
+ "type": "IN",
+ "value": "1800000000000000000",
+ },
+ {
+ "accountId": "js:2:icon:hxcf4e2c94b52619f72aa4da13370aa6c1c3dad00d:icon",
+ "blockHash": null,
+ "blockHeight": 81834705,
+ "extra": {},
+ "fee": "1250000000000000",
+ "hasFailed": false,
+ "hash": "0xf8d0bfbead922dd45527e8521145d9ed86a70d1396269ee5b80c47b5851807e6",
+ "id": "js:2:icon:hxcf4e2c94b52619f72aa4da13370aa6c1c3dad00d:icon-0xf8d0bfbead922dd45527e8521145d9ed86a70d1396269ee5b80c47b5851807e6-OUT",
+ "recipients": [
+ "hx2dbaef576c73cca274db0b602831c2c6a9fc4bba",
+ ],
+ "senders": [
+ "hxcf4e2c94b52619f72aa4da13370aa6c1c3dad00d",
+ ],
+ "type": "OUT",
+ "value": "870000000000000000",
+ },
+ ],
+ [],
+]
+`;
diff --git a/libs/ledger-live-common/src/families/icon/bridge.integration.test.ts b/libs/ledger-live-common/src/families/icon/bridge.integration.test.ts
new file mode 100644
index 000000000000..8398996262d3
--- /dev/null
+++ b/libs/ledger-live-common/src/families/icon/bridge.integration.test.ts
@@ -0,0 +1,5 @@
+import "../../__tests__/test-helpers/setup";
+import { testBridge } from "../../__tests__/test-helpers/bridge";
+import { dataset } from "@ledgerhq/coin-icon/bridge.integration.test";
+
+testBridge(dataset);
diff --git a/libs/ledger-live-common/src/families/icon/bridge/mock.ts b/libs/ledger-live-common/src/families/icon/bridge/mock.ts
new file mode 100644
index 000000000000..2ac0579b6a38
--- /dev/null
+++ b/libs/ledger-live-common/src/families/icon/bridge/mock.ts
@@ -0,0 +1,90 @@
+import { BigNumber } from "bignumber.js";
+import { NotEnoughBalance, RecipientRequired, InvalidAddress, FeeTooHigh } from "@ledgerhq/errors";
+import type { Transaction } from "../types";
+import type { AccountBridge, CurrencyBridge } from "@ledgerhq/types-live";
+import { defaultUpdateTransaction } from "@ledgerhq/coin-framework/bridge/jsHelpers";
+import {
+ scanAccounts,
+ signOperation,
+ broadcast,
+ sync,
+ isInvalidRecipient,
+} from "../../../bridge/mockHelpers";
+import { getMainAccount } from "../../../account";
+
+import { makeAccountBridgeReceive } from "../../../bridge/mockHelpers";
+
+const receive = makeAccountBridgeReceive();
+
+const createTransaction = (): Transaction => ({
+ family: "icon",
+ mode: "send",
+ amount: new BigNumber(0),
+ recipient: "",
+ useAllAmount: false,
+ fees: null,
+});
+
+const updateTransaction = defaultUpdateTransaction;
+
+const prepareTransaction = async (a, t) => t;
+
+const estimateMaxSpendable = ({ account, parentAccount, transaction }) => {
+ const mainAccount = getMainAccount(account, parentAccount);
+ const estimatedFees = transaction?.fees || new BigNumber(5000);
+ return Promise.resolve(BigNumber.max(0, mainAccount.spendableBalance.minus(estimatedFees)));
+};
+
+const getTransactionStatus = (account, t) => {
+ const errors: any = {};
+ const warnings: any = {};
+ const useAllAmount = !!t.useAllAmount;
+
+ const estimatedFees = new BigNumber(5000);
+
+ const totalSpent = useAllAmount ? account.balance : new BigNumber(t.amount).plus(estimatedFees);
+
+ const amount = useAllAmount ? account.balance.minus(estimatedFees) : new BigNumber(t.amount);
+
+ if (amount.gt(0) && estimatedFees.times(10).gt(amount)) {
+ warnings.amount = new FeeTooHigh();
+ }
+
+ if (totalSpent.gt(account.balance)) {
+ errors.amount = new NotEnoughBalance();
+ }
+
+ if (!t.recipient) {
+ errors.recipient = new RecipientRequired();
+ } else if (isInvalidRecipient(t.recipient)) {
+ errors.recipient = new InvalidAddress();
+ }
+
+ return Promise.resolve({
+ errors,
+ warnings,
+ estimatedFees,
+ amount,
+ totalSpent,
+ });
+};
+
+const accountBridge: AccountBridge = {
+ estimateMaxSpendable,
+ createTransaction,
+ updateTransaction,
+ getTransactionStatus,
+ prepareTransaction,
+ sync,
+ receive,
+ signOperation,
+ broadcast,
+};
+
+const currencyBridge: CurrencyBridge = {
+ scanAccounts,
+ preload: (async () => {}) as any,
+ hydrate: () => {},
+};
+
+export default { currencyBridge, accountBridge };
diff --git a/libs/ledger-live-common/src/families/icon/config.ts b/libs/ledger-live-common/src/families/icon/config.ts
new file mode 100644
index 000000000000..74e53607232b
--- /dev/null
+++ b/libs/ledger-live-common/src/families/icon/config.ts
@@ -0,0 +1,12 @@
+import { ConfigInfo } from "@ledgerhq/live-config/LiveConfig";
+
+export const iconConfig: Record = {
+ config_currency_icon: {
+ type: "object",
+ default: {
+ status: {
+ type: "active",
+ },
+ },
+ },
+};
diff --git a/libs/ledger-live-common/src/families/icon/setup.ts b/libs/ledger-live-common/src/families/icon/setup.ts
new file mode 100644
index 000000000000..3de87b8fd9d7
--- /dev/null
+++ b/libs/ledger-live-common/src/families/icon/setup.ts
@@ -0,0 +1,45 @@
+// Goal of this file is to inject all necessary device/signer dependency to coin-modules
+
+import { createBridges } from "@ledgerhq/coin-icon/bridge/index";
+import makeCliTools from "@ledgerhq/coin-icon/cli-transaction";
+import iconResolver from "@ledgerhq/coin-icon/hw-getAddress";
+import { Transaction } from "@ledgerhq/coin-icon/types";
+import Icon from "@ledgerhq/hw-app-icon";
+import Transport from "@ledgerhq/hw-transport";
+import type { Bridge } from "@ledgerhq/types-live";
+import { CreateSigner, createResolver, executeWithSigner } from "../../bridge/setup";
+import type { Resolver } from "../../hw/getAddress/types";
+import { IconCoinConfig } from "@ledgerhq/coin-icon/config";
+import { getEnv } from "@ledgerhq/live-env";
+
+const createSigner: CreateSigner = (transport: Transport) => {
+ return new Icon(transport);
+};
+
+const getCurrencyConfig = (): IconCoinConfig => {
+ return {
+ status: {
+ type: "active",
+ },
+ infra: {
+ indexer: getEnv("ICON_INDEXER_ENDPOINT"),
+ indexer_testnet: getEnv("ICON_TESTNET_INDEXER_ENDPOINT"),
+ node_endpoint: getEnv("ICON_NODE_ENDPOINT"),
+ node_testnet_endpoint: getEnv("ICON_TESTNET_NODE_ENDPOINT"),
+ debug_endpoint: getEnv("ICON_DEBUG_ENDPOINT"),
+ debug_testnet_endpoint: getEnv("ICON_TESTNET_DEBUG_ENDPOINT"),
+ }
+ }
+
+};
+
+const bridge: Bridge = createBridges(
+ executeWithSigner(createSigner),
+ getCurrencyConfig,
+);
+
+const resolver: Resolver = createResolver(createSigner, iconResolver);
+
+const cliTools = makeCliTools();
+
+export { bridge, cliTools, resolver };
diff --git a/libs/ledger-live-common/src/families/icon/types.ts b/libs/ledger-live-common/src/families/icon/types.ts
new file mode 100644
index 000000000000..2155c8644626
--- /dev/null
+++ b/libs/ledger-live-common/src/families/icon/types.ts
@@ -0,0 +1,2 @@
+// Encapsulate for LLD et LLM
+export * from "@ledgerhq/coin-icon/types";
diff --git a/libs/ledger-live-common/src/generated/account.ts b/libs/ledger-live-common/src/generated/account.ts
index 2844a5841e76..cc6f83005667 100644
--- a/libs/ledger-live-common/src/generated/account.ts
+++ b/libs/ledger-live-common/src/generated/account.ts
@@ -3,6 +3,7 @@ import vechain from "../families/vechain/account";
import bitcoin from "@ledgerhq/coin-bitcoin/account";
import cardano from "@ledgerhq/coin-cardano/account";
import near from "@ledgerhq/coin-near/account";
+import icon from "@ledgerhq/coin-icon/account";
export default {
crypto_org,
@@ -10,4 +11,5 @@ export default {
bitcoin,
cardano,
near,
+ icon,
};
diff --git a/libs/ledger-live-common/src/generated/bridge/js.ts b/libs/ledger-live-common/src/generated/bridge/js.ts
index 2246d16405d3..7f7e0e17499f 100644
--- a/libs/ledger-live-common/src/generated/bridge/js.ts
+++ b/libs/ledger-live-common/src/generated/bridge/js.ts
@@ -19,6 +19,7 @@ import { bridge as polkadot } from "../../families/polkadot/setup";
import { bridge as solana } from "../../families/solana/setup";
import { bridge as tezos } from "../../families/tezos/setup";
import { bridge as xrp } from "../../families/xrp/setup";
+import { bridge as icon } from "../../families/icon/setup";
export default {
casper,
@@ -42,4 +43,5 @@ export default {
solana,
tezos,
xrp,
+ icon,
};
diff --git a/libs/ledger-live-common/src/generated/bridge/mock.ts b/libs/ledger-live-common/src/generated/bridge/mock.ts
index 0cfe54434411..9627ee7cf122 100644
--- a/libs/ledger-live-common/src/generated/bridge/mock.ts
+++ b/libs/ledger-live-common/src/generated/bridge/mock.ts
@@ -4,6 +4,7 @@ import cardano from "../../families/cardano/bridge/mock";
import casper from "../../families/casper/bridge/mock";
import cosmos from "../../families/cosmos/bridge/mock";
import evm from "../../families/evm/bridge/mock";
+import icon from "../../families/icon/bridge/mock";
import polkadot from "../../families/polkadot/bridge/mock";
import solana from "../../families/solana/bridge/mock";
import stellar from "../../families/stellar/bridge/mock";
@@ -19,6 +20,7 @@ export default {
casper,
cosmos,
evm,
+ icon,
polkadot,
solana,
stellar,
diff --git a/libs/ledger-live-common/src/generated/cli-transaction.ts b/libs/ledger-live-common/src/generated/cli-transaction.ts
index f0fbea4cafb8..a0c1feba46f3 100644
--- a/libs/ledger-live-common/src/generated/cli-transaction.ts
+++ b/libs/ledger-live-common/src/generated/cli-transaction.ts
@@ -17,6 +17,7 @@ import { cliTools as polkadot } from "../families/polkadot/setup";
import { cliTools as solana } from "../families/solana/setup";
import { cliTools as tezos } from "../families/tezos/setup";
import { cliTools as xrp } from "../families/xrp/setup";
+import { cliTools as icon } from "../families/icon/setup";
export default {
celo,
@@ -38,4 +39,5 @@ export default {
solana,
tezos,
xrp,
+ icon,
};
diff --git a/libs/ledger-live-common/src/generated/deviceTransactionConfig.ts b/libs/ledger-live-common/src/generated/deviceTransactionConfig.ts
index 55493601bcf0..0416476787f4 100644
--- a/libs/ledger-live-common/src/generated/deviceTransactionConfig.ts
+++ b/libs/ledger-live-common/src/generated/deviceTransactionConfig.ts
@@ -18,6 +18,7 @@ import polkadot from "@ledgerhq/coin-polkadot/deviceTransactionConfig";
import solana from "@ledgerhq/coin-solana/deviceTransactionConfig";
import tezos from "@ledgerhq/coin-tezos/deviceTransactionConfig";
import xrp from "@ledgerhq/coin-xrp/deviceTransactionConfig";
+import icon from "@ledgerhq/coin-icon/deviceTransactionConfig";
export default {
casper,
@@ -40,6 +41,7 @@ export default {
solana,
tezos,
xrp,
+ icon,
};
import { ExtraDeviceTransactionField as ExtraDeviceTransactionField_casper } from "../families/casper/deviceTransactionConfig";
import { ExtraDeviceTransactionField as ExtraDeviceTransactionField_cosmos } from "../families/cosmos/deviceTransactionConfig";
diff --git a/libs/ledger-live-common/src/generated/hw-getAddress.ts b/libs/ledger-live-common/src/generated/hw-getAddress.ts
index 86577185a7b0..4932c481fadb 100644
--- a/libs/ledger-live-common/src/generated/hw-getAddress.ts
+++ b/libs/ledger-live-common/src/generated/hw-getAddress.ts
@@ -19,6 +19,7 @@ import { resolver as polkadot } from "../families/polkadot/setup";
import { resolver as solana } from "../families/solana/setup";
import { resolver as tezos } from "../families/tezos/setup";
import { resolver as xrp } from "../families/xrp/setup";
+import { resolver as icon } from "../families/icon/setup";
export default {
casper,
@@ -42,4 +43,5 @@ export default {
solana,
tezos,
xrp,
+ icon,
};
diff --git a/libs/ledger-live-common/src/generated/specs.ts b/libs/ledger-live-common/src/generated/specs.ts
index 01978d40c849..13c51d58ef91 100644
--- a/libs/ledger-live-common/src/generated/specs.ts
+++ b/libs/ledger-live-common/src/generated/specs.ts
@@ -19,6 +19,7 @@ import polkadot from "@ledgerhq/coin-polkadot/specs";
import solana from "@ledgerhq/coin-solana/specs";
import tezos from "@ledgerhq/coin-tezos/specs";
import xrp from "@ledgerhq/coin-xrp/specs";
+import icon from "@ledgerhq/coin-icon/specs";
export default {
casper,
@@ -42,4 +43,5 @@ export default {
solana,
tezos,
xrp,
+ icon,
};
diff --git a/libs/ledger-live-common/src/generated/transaction.ts b/libs/ledger-live-common/src/generated/transaction.ts
index 9292e52e71e0..788d759c8c6f 100644
--- a/libs/ledger-live-common/src/generated/transaction.ts
+++ b/libs/ledger-live-common/src/generated/transaction.ts
@@ -19,6 +19,7 @@ import polkadot from "@ledgerhq/coin-polkadot/transaction";
import solana from "@ledgerhq/coin-solana/transaction";
import tezos from "@ledgerhq/coin-tezos/transaction";
import xrp from "@ledgerhq/coin-xrp/transaction";
+import icon from "@ledgerhq/coin-icon/transaction";
export default {
casper,
@@ -42,4 +43,5 @@ export default {
solana,
tezos,
xrp,
+ icon,
};
diff --git a/libs/ledger-live-common/src/generated/types.ts b/libs/ledger-live-common/src/generated/types.ts
index 001c1577f89b..3b5170897eb6 100644
--- a/libs/ledger-live-common/src/generated/types.ts
+++ b/libs/ledger-live-common/src/generated/types.ts
@@ -64,6 +64,12 @@ import type {
TransactionStatus as hederaTransactionStatus,
TransactionStatusRaw as hederaTransactionStatusRaw,
} from "../families/hedera/types";
+import type {
+ Transaction as iconTransaction,
+ TransactionRaw as iconTransactionRaw,
+ TransactionStatus as iconTransactionStatus,
+ TransactionStatusRaw as iconTransactionStatusRaw,
+} from "@ledgerhq/coin-icon/types";
import type {
Transaction as internet_computerTransaction,
TransactionRaw as internet_computerTransactionRaw,
@@ -137,6 +143,7 @@ export type Transaction =
| evmTransaction
| filecoinTransaction
| hederaTransaction
+ | iconTransaction
| internet_computerTransaction
| nearTransaction
| polkadotTransaction
@@ -160,6 +167,7 @@ export type TransactionRaw =
| evmTransactionRaw
| filecoinTransactionRaw
| hederaTransactionRaw
+ | iconTransactionRaw
| internet_computerTransactionRaw
| nearTransactionRaw
| polkadotTransactionRaw
@@ -183,6 +191,7 @@ export type TransactionStatus =
| evmTransactionStatus
| filecoinTransactionStatus
| hederaTransactionStatus
+ | iconTransactionStatus
| internet_computerTransactionStatus
| nearTransactionStatus
| polkadotTransactionStatus
@@ -206,6 +215,7 @@ export type TransactionStatusRaw =
| evmTransactionStatusRaw
| filecoinTransactionStatusRaw
| hederaTransactionStatusRaw
+ | iconTransactionStatusRaw
| internet_computerTransactionStatusRaw
| nearTransactionStatusRaw
| polkadotTransactionStatusRaw
diff --git a/libs/ledgerjs/packages/cryptoassets/src/abandonseed.ts b/libs/ledgerjs/packages/cryptoassets/src/abandonseed.ts
index ba7e0a3d7658..08c7ae610206 100644
--- a/libs/ledgerjs/packages/cryptoassets/src/abandonseed.ts
+++ b/libs/ledgerjs/packages/cryptoassets/src/abandonseed.ts
@@ -104,6 +104,8 @@ const abandonSeedAddresses: Partial> = {
dydx: "dydx19rl4cm2hmr8afy4kldpxz3fka4jguq0a4erelz",
vechain: EVM_DEAD_ADDRESS,
lukso: EVM_DEAD_ADDRESS,
+ icon: "hxd3f4224ffb2cfd354f8db2eef39e12aadb7a4ebb",
+ icon_berlin_testnet: "hxd3f4224ffb2cfd354f8db2eef39e12aadb7a4ebb",
linea: EVM_DEAD_ADDRESS,
linea_sepolia: EVM_DEAD_ADDRESS,
blast: EVM_DEAD_ADDRESS,
diff --git a/libs/ledgerjs/packages/cryptoassets/src/currencies.ts b/libs/ledgerjs/packages/cryptoassets/src/currencies.ts
index 9699c69fcabb..1b3dcf334f97 100644
--- a/libs/ledgerjs/packages/cryptoassets/src/currencies.ts
+++ b/libs/ledgerjs/packages/cryptoassets/src/currencies.ts
@@ -1465,12 +1465,42 @@ export const cryptocurrenciesById: Record = {
family: "icon",
units: [
{
- name: "ICON",
- code: "ICON",
- magnitude: 8,
+ name: "ICX",
+ code: "ICX",
+ magnitude: 18,
+ },
+ ],
+ explorerViews: [
+ {
+ tx: "https://tracker.icon.community/transaction/$hash",
+ address: "https://tracker.icon.community/address/$address",
+ },
+ ],
+ },
+ icon_berlin_testnet: {
+ type: "CryptoCurrency",
+ id: "icon_berlin_testnet",
+ coinType: CoinType.ICON,
+ name: "ICON Berlin Testnet",
+ managerAppName: "ICON",
+ ticker: "ICX",
+ scheme: "icon_berlin_testnet",
+ color: "#00A3B4",
+ family: "icon",
+ isTestnetFor: "icon",
+ units: [
+ {
+ name: "ICX",
+ code: "ICX",
+ magnitude: 18,
+ },
+ ],
+ explorerViews: [
+ {
+ tx: "https://tracker.berlin.icon.community/transaction/$hash",
+ address: "https://tracker.berlin.icon.community/address/$address",
},
],
- explorerViews: [],
},
iota: {
type: "CryptoCurrency",
diff --git a/libs/ledgerjs/packages/hw-app-icon/jest.config.ts b/libs/ledgerjs/packages/hw-app-icon/jest.config.ts
new file mode 100644
index 000000000000..c4f012862710
--- /dev/null
+++ b/libs/ledgerjs/packages/hw-app-icon/jest.config.ts
@@ -0,0 +1,6 @@
+import baseConfig from "../../jest.config";
+
+export default {
+ ...baseConfig,
+ rootDir: __dirname,
+};
diff --git a/libs/ledgerjs/packages/hw-app-icon/package.json b/libs/ledgerjs/packages/hw-app-icon/package.json
new file mode 100644
index 000000000000..42565645351f
--- /dev/null
+++ b/libs/ledgerjs/packages/hw-app-icon/package.json
@@ -0,0 +1,55 @@
+{
+ "name": "@ledgerhq/hw-app-icon",
+ "version": "1.0.0",
+ "description": "Ledger Hardware Wallet ICON Application API",
+ "keywords": [
+ "Ledger",
+ "LedgerWallet",
+ "icx",
+ "Icon",
+ "NanoS",
+ "Blue",
+ "Hardware Wallet"
+ ],
+ "repository": {
+ "type": "git",
+ "url": "https://github.com/LedgerHQ/ledger-live"
+ },
+ "bugs": {
+ "url": "https://github.com/LedgerHQ/ledger-live/issues"
+ },
+ "homepage": "https://github.com/LedgerHQ/ledger-live",
+ "publishConfig": {
+ "access": "public"
+ },
+ "main": "lib/Icon.js",
+ "module": "lib-es/Icon.js",
+ "types": "lib/Icon.d.ts",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@ledgerhq/errors": "workspace:^",
+ "@ledgerhq/hw-transport": "workspace:^",
+ "bip32-path": "^0.4.2"
+ },
+ "devDependencies": {
+ "@ledgerhq/hw-transport-mocker": "workspace:^",
+ "@ledgerhq/hw-transport-node-speculos": "workspace:^",
+ "@types/jest": "^29.5.10",
+ "@types/node": "^20.8.10",
+ "jest": "^29.7.0",
+ "rimraf": "^4.4.1",
+ "source-map-support": "^0.5.21",
+ "ts-jest": "^29.1.1",
+ "ts-node": "^10.4.0"
+ },
+ "scripts": {
+ "clean": "rimraf lib lib-es",
+ "build": "tsc && tsc -m ES6 --outDir lib-es",
+ "prewatch": "pnpm build",
+ "watch": "tsc --watch",
+ "doc": "documentation readme src/** --section=API --pe ts --re ts --re d.ts",
+ "lint": "eslint ./src --no-error-on-unmatched-pattern --ext .ts,.tsx",
+ "lint:fix": "pnpm lint --fix",
+ "test": "jest"
+ }
+}
diff --git a/libs/ledgerjs/packages/hw-app-icon/src/Icon.ts b/libs/ledgerjs/packages/hw-app-icon/src/Icon.ts
new file mode 100644
index 000000000000..59f066c31478
--- /dev/null
+++ b/libs/ledgerjs/packages/hw-app-icon/src/Icon.ts
@@ -0,0 +1,185 @@
+/********************************************************************************
+ * Ledger JS API for ICON
+ * (c) 2018 ICON Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ ********************************************************************************/
+
+import { splitPath, foreach, hexToBase64 } from "./utils";
+import type Transport from "@ledgerhq/hw-transport";
+import { UserRefusedOnDevice, UserRefusedAddress } from "@ledgerhq/errors";
+
+const CLA = 0xe0;
+const INS = {
+ GET_VERSION: 0x06,
+ GET_ADDR: 0x02,
+ SIGN: 0x04,
+};
+
+const SW_OK = 0x9000;
+const SW_CANCEL = 0x6986;
+
+/**
+ * ICON API
+ *
+ * @example
+ * import Icx from "@ledgerhq/hw-app-icx";
+ * const icx = new Icx(transport)
+ */
+export default class Icx {
+ transport: Transport;
+
+ constructor(transport: Transport) {
+ this.transport = transport;
+ transport.decorateAppAPIMethods(
+ this,
+ ["getAddress", "signTransaction", "getAppConfiguration"],
+ "ICON",
+ );
+ }
+
+ /**
+ * Returns public key and ICON address for a given BIP 32 path.
+ * @param path a path in BIP 32 format
+ * @option boolDisplay optionally enable or not the display
+ * @option boolChaincode optionally enable or not the chaincode request
+ * @return an object with a publickey(hexa string), address(string) and
+ * (optionally) chaincode(hexa string)
+ * @example
+ * icx.getAddress("44'/4801368'/0'", true, true).then(o => o.address)
+ */
+ getAddress(
+ path: string,
+ boolDisplay = false,
+ boolChaincode = true,
+ ): Promise<{
+ publicKey: string;
+ address: string;
+ chainCode?: string;
+ }> {
+ const paths = splitPath(path);
+ const buffer = Buffer.alloc(1 + paths.length * 4);
+ buffer[0] = paths.length;
+ paths.forEach((element, index) => {
+ buffer.writeUInt32BE(element, 1 + 4 * index);
+ });
+ const statusList = [SW_OK, SW_CANCEL];
+ return this.transport
+ .send(
+ CLA,
+ INS.GET_ADDR,
+ boolDisplay ? 0x01 : 0x00,
+ boolChaincode ? 0x01 : 0x00,
+ buffer,
+ statusList,
+ )
+ .then(response => {
+ const publicKeyLength = response[0];
+ const addressLength = response[1 + publicKeyLength];
+ const errorCodeData = response.slice(-2);
+ const returnCode = errorCodeData[0] * 0x100 + errorCodeData[1];
+
+ if (returnCode === SW_CANCEL) {
+ throw new UserRefusedAddress();
+ }
+ return {
+ publicKey: response.slice(1, 1 + publicKeyLength).toString("hex"),
+ address: response
+ .slice(1 + publicKeyLength + 1, 1 + publicKeyLength + 1 + addressLength)
+ .toString(),
+ chainCode: boolChaincode ? response.slice(-32).toString("hex") : undefined,
+ };
+ });
+ }
+
+ /**
+ * Signs a transaction and returns signed message given the raw transaction
+ * and the BIP 32 path of the account to sign
+ * @param path a path in BIP 32 format
+ * @param rawTxAscii raw transaction data to sign in ASCII string format
+ * @return an object with a base64 encoded signature and hash in hexa string
+ * @example
+ * icx.signTransaction("44'/4801368'/0'",
+ * "icx_sendTransaction.fee.0x2386f26fc10000." +
+ * "from.hxc9ecad30b05a0650a337452fce031e0c60eacc3a.nonce.0x3." +
+ * "to.hx4c5101add2caa6a920420cf951f7dd7c7df6ca24.value.0xde0b6b3a7640000")
+ * .then(result => ...)
+ */
+ signTransaction(
+ path: string,
+ rawTxAscii: string,
+ ): Promise<{
+ signedRawTxBase64: string;
+ hashHex: string;
+ }> {
+ const paths = splitPath(path);
+ let offset = 0;
+ const rawTx = Buffer.from(rawTxAscii);
+ let toSend: Buffer[] = [];
+ let response: Buffer;
+ while (offset !== rawTx.length) {
+ const maxChunkSize = offset === 0 ? 150 - 1 - paths.length * 4 - 4 : 150;
+ const chunkSize = offset + maxChunkSize > rawTx.length ? rawTx.length - offset : maxChunkSize;
+ const buffer = Buffer.alloc(offset === 0 ? 1 + paths.length * 4 + 4 + chunkSize : chunkSize);
+ if (offset === 0) {
+ buffer[0] = paths.length;
+ paths.forEach((element, index) => {
+ buffer.writeUInt32BE(element, 1 + 4 * index);
+ });
+ buffer.writeUInt32BE(rawTx.length, 1 + 4 * paths.length);
+ rawTx.copy(buffer, 1 + 4 * paths.length + 4, offset, offset + chunkSize);
+ } else {
+ rawTx.copy(buffer, 0, offset, offset + chunkSize);
+ }
+ toSend.push(buffer);
+ offset += chunkSize;
+ }
+ return foreach(toSend, (data, i) =>
+ this.transport
+ .send(CLA, INS.SIGN, i === 0 ? 0x00 : 0x80, 0x00, data, [SW_OK, SW_CANCEL])
+ .then(apduResponse => {
+ response = apduResponse;
+ }),
+ ).then(() => {
+ const errorCodeData = response.slice(-2);
+ const returnCode = errorCodeData[0] * 0x100 + errorCodeData[1];
+
+ if (returnCode === SW_CANCEL) {
+ throw new UserRefusedOnDevice();
+ }
+ // r, s, v are aligned sequencially
+ return {
+ signedRawTxBase64: hexToBase64(response.slice(0, 32 + 32 + 1).toString("hex")),
+ hashHex: response.slice(32 + 32 + 1, 32 + 32 + 1 + 32).toString("hex"),
+ };
+ });
+ }
+
+ /**
+ * Returns the application configurations such as versions.
+ * @return major/minor/patch versions of Icon application
+ */
+ getAppConfiguration(): Promise<{
+ majorVersion: number;
+ minorVersion: number;
+ patchVersion: number;
+ }> {
+ return this.transport.send(CLA, INS.GET_VERSION, 0x00, 0x00).then(response => {
+ return {
+ majorVersion: response[0],
+ minorVersion: response[1],
+ patchVersion: response[2],
+ };
+ });
+ }
+}
diff --git a/libs/ledgerjs/packages/hw-app-icon/src/utils.ts b/libs/ledgerjs/packages/hw-app-icon/src/utils.ts
new file mode 100644
index 000000000000..e3805f8c0bbb
--- /dev/null
+++ b/libs/ledgerjs/packages/hw-app-icon/src/utils.ts
@@ -0,0 +1,58 @@
+/********************************************************************************
+ * Ledger Node JS API for ICON
+ * (c) 2016-2017 Ledger
+ *
+ * Modifications (c) 2018 ICON Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ ********************************************************************************/
+
+// TODO use bip32-path library
+export function splitPath(path: string): number[] {
+ const result: number[] = [];
+ const components = path.split("/");
+ components.forEach(element => {
+ let number = parseInt(element, 10);
+ if (isNaN(number)) {
+ return; // FIXME shouldn't it throws instead?
+ }
+ if (element.length > 1 && element[element.length - 1] === "'") {
+ number += 0x80000000;
+ }
+ result.push(number);
+ });
+ return result;
+}
+
+export function foreach(arr: T[], callback: (T, number) => Promise): Promise {
+ function iterate(index, array, result) {
+ if (index >= array.length) {
+ return result;
+ } else
+ return callback(array[index], index).then(function (res) {
+ result.push(res);
+ return iterate(index + 1, array, result);
+ });
+ }
+ return Promise.resolve().then(() => iterate(0, arr, []));
+}
+
+export function hexToBase64(hexString: string) {
+ return btoa(
+ (hexString.match(/\w{2}/g) || [])
+ .map(function (a) {
+ return String.fromCharCode(parseInt(a, 16));
+ })
+ .join(""),
+ );
+}
diff --git a/libs/ledgerjs/packages/hw-app-icon/tests/Icon.test.ts b/libs/ledgerjs/packages/hw-app-icon/tests/Icon.test.ts
new file mode 100644
index 000000000000..9523fd8cd7bb
--- /dev/null
+++ b/libs/ledgerjs/packages/hw-app-icon/tests/Icon.test.ts
@@ -0,0 +1,35 @@
+import {
+ openTransportReplayer,
+ RecordStore,
+} from "@ledgerhq/hw-transport-mocker";
+import Icon from "../src/Icon";
+
+test("Icon init", async () => {
+ const transport = await openTransportReplayer(RecordStore.fromString(""));
+ const icx = new Icon(transport);
+ expect(icx).not.toBe(undefined);
+});
+
+test("getAppConfiguration", async () => {
+ const transport = await openTransportReplayer(
+ RecordStore.fromString(`
+ => e006000000
+ <= 0102039000
+ `)
+ );
+ const icon = new Icon(transport);
+ const result = await icon.getAppConfiguration();
+ expect(result).toEqual({
+ majorVersion: 1,
+ minorVersion: 2,
+ patchVersion: 3
+ });
+});
+
+test("should throw on invalid derivation path", async () => {
+ const transport = await openTransportReplayer(new RecordStore());
+ const icon = new Icon(transport);
+ return expect(
+ icon.getAddress("some invalid derivation path", false)
+ ).rejects.toThrow("EOF: no more APDU to replay");
+});
diff --git a/libs/ledgerjs/packages/hw-app-icon/tsconfig.json b/libs/ledgerjs/packages/hw-app-icon/tsconfig.json
new file mode 100644
index 000000000000..0cf4676deafc
--- /dev/null
+++ b/libs/ledgerjs/packages/hw-app-icon/tsconfig.json
@@ -0,0 +1,7 @@
+{
+ "extends": "../../tsconfig.json",
+ "compilerOptions": {
+ "outDir": "lib"
+ },
+ "include": ["src/**/*"]
+}
diff --git a/libs/ledgerjs/packages/types-cryptoassets/src/index.ts b/libs/ledgerjs/packages/types-cryptoassets/src/index.ts
index 54cfaf466b22..bb8c780074cf 100644
--- a/libs/ledgerjs/packages/types-cryptoassets/src/index.ts
+++ b/libs/ledgerjs/packages/types-cryptoassets/src/index.ts
@@ -55,6 +55,7 @@ export type CryptoCurrencyId =
| "hpb"
| "hycon"
| "icon"
+ | "icon_berlin_testnet"
| "iota"
| "iov"
| "kin"
diff --git a/libs/ledgerjs/packages/types-live/src/derivation.ts b/libs/ledgerjs/packages/types-live/src/derivation.ts
index bfdef23da0d0..4f7e46e649c4 100644
--- a/libs/ledgerjs/packages/types-live/src/derivation.ts
+++ b/libs/ledgerjs/packages/types-live/src/derivation.ts
@@ -36,4 +36,5 @@ export type DerivationMode =
| "nearbip44h"
| "vechain"
| "internet_computer"
- | "stacks_wallet";
+ | "stacks_wallet"
+ | "icon";
diff --git a/package.json b/package.json
index 7352d86197f5..31ac08c4269a 100644
--- a/package.json
+++ b/package.json
@@ -70,6 +70,7 @@
"coin:near": "pnpm --filter coin-near",
"coin:polkadot": "pnpm --filter coin-polkadot",
"coin:solana": "pnpm --filter coin-solana",
+ "coin:icon": "pnpm --filter coin-icon",
"coin:tezos": "pnpm --filter coin-tezos",
"coin:xrp": "pnpm --filter coin-xrp",
"evm-tools": "pnpm --filter evm-tools",
@@ -111,6 +112,7 @@
"ljs:hw-app-tezos": "pnpm --filter hw-app-tezos",
"ljs:hw-app-trx": "pnpm --filter hw-app-trx",
"ljs:hw-app-xrp": "pnpm --filter hw-app-xrp",
+ "ljs:hw-app-icon": "pnpm --filter hw-app-icon",
"ljs:hw-transport": "pnpm --filter hw-transport",
"ljs:hw-transport-http": "pnpm --filter hw-transport-http",
"ljs:hw-transport-mocker": "pnpm --filter hw-transport-mocker",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 380a8774ddeb..4f36d011be36 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -2088,6 +2088,76 @@ importers:
specifier: ^29.1.1
version: 29.1.2(jest@29.7.0)(typescript@5.4.3)
+ libs/coin-modules/coin-icon:
+ dependencies:
+ '@ledgerhq/coin-framework':
+ specifier: workspace:^
+ version: link:../../coin-framework
+ '@ledgerhq/cryptoassets':
+ specifier: workspace:^
+ version: link:../../ledgerjs/packages/cryptoassets
+ '@ledgerhq/devices':
+ specifier: workspace:*
+ version: link:../../ledgerjs/packages/devices
+ '@ledgerhq/errors':
+ specifier: workspace:^
+ version: link:../../ledgerjs/packages/errors
+ '@ledgerhq/live-env':
+ specifier: workspace:^
+ version: link:../../env
+ '@ledgerhq/live-network':
+ specifier: workspace:^
+ version: link:../../live-network
+ '@ledgerhq/live-promise':
+ specifier: workspace:^
+ version: link:../../promise
+ '@ledgerhq/logs':
+ specifier: workspace:^
+ version: link:../../ledgerjs/packages/logs
+ '@ledgerhq/types-cryptoassets':
+ specifier: workspace:^
+ version: link:../../ledgerjs/packages/types-cryptoassets
+ '@ledgerhq/types-live':
+ specifier: workspace:^
+ version: link:../../ledgerjs/packages/types-live
+ bignumber.js:
+ specifier: ^9.1.2
+ version: 9.1.2
+ expect:
+ specifier: ^27.4.6
+ version: 27.5.1
+ icon-sdk-js:
+ specifier: 1.5.2
+ version: 1.5.2
+ invariant:
+ specifier: ^2.2.2
+ version: 2.2.4
+ lodash:
+ specifier: ^4.17.21
+ version: 4.17.21
+ prando:
+ specifier: ^6.0.1
+ version: 6.0.1
+ rxjs:
+ specifier: ^7.8.1
+ version: 7.8.1
+ devDependencies:
+ '@types/invariant':
+ specifier: ^2.2.2
+ version: 2.2.37
+ '@types/jest':
+ specifier: ^29.5.10
+ version: 29.5.12
+ '@types/lodash':
+ specifier: ^4.14.191
+ version: 4.17.0
+ jest:
+ specifier: ^29.7.0
+ version: 29.7.0
+ ts-jest:
+ specifier: ^29.1.1
+ version: 29.1.2(jest@29.7.0)(typescript@5.4.3)
+
libs/coin-modules/coin-near:
dependencies:
'@ledgerhq/coin-framework':
@@ -2863,6 +2933,9 @@ importers:
'@ledgerhq/coin-framework':
specifier: workspace:^
version: link:../coin-framework
+ '@ledgerhq/coin-icon':
+ specifier: workspace:^
+ version: link:../coin-modules/coin-icon
'@ledgerhq/coin-near':
specifier: workspace:^
version: link:../coin-modules/coin-near
@@ -2908,6 +2981,9 @@ importers:
'@ledgerhq/hw-app-exchange':
specifier: workspace:^
version: link:../ledgerjs/packages/hw-app-exchange
+ '@ledgerhq/hw-app-icon':
+ specifier: workspace:^
+ version: link:../ledgerjs/packages/hw-app-icon
'@ledgerhq/hw-app-near':
specifier: workspace:^
version: link:../ledgerjs/packages/hw-app-near
@@ -3835,6 +3911,46 @@ importers:
specifier: ^10.4.0
version: 10.9.2(@types/node@20.12.12)(source-map-support@0.5.21)(typescript@5.4.3)
+ libs/ledgerjs/packages/hw-app-icon:
+ dependencies:
+ '@ledgerhq/errors':
+ specifier: workspace:^
+ version: link:../errors
+ '@ledgerhq/hw-transport':
+ specifier: workspace:^
+ version: link:../hw-transport
+ bip32-path:
+ specifier: ^0.4.2
+ version: 0.4.2
+ devDependencies:
+ '@ledgerhq/hw-transport-mocker':
+ specifier: workspace:^
+ version: link:../hw-transport-mocker
+ '@ledgerhq/hw-transport-node-speculos':
+ specifier: workspace:^
+ version: link:../hw-transport-node-speculos
+ '@types/jest':
+ specifier: ^29.5.10
+ version: 29.5.12
+ '@types/node':
+ specifier: ^20.8.10
+ version: 20.12.13
+ jest:
+ specifier: ^29.7.0
+ version: 29.7.0(@types/node@20.12.13)(ts-node@10.9.2)
+ rimraf:
+ specifier: ^4.4.1
+ version: 4.4.1
+ source-map-support:
+ specifier: ^0.5.21
+ version: 0.5.21
+ ts-jest:
+ specifier: ^29.1.1
+ version: 29.1.2(jest@29.7.0)(typescript@5.4.3)
+ ts-node:
+ specifier: ^10.4.0
+ version: 10.9.2(@types/node@20.12.13)(source-map-support@0.5.21)(typescript@5.4.3)
+
libs/ledgerjs/packages/hw-app-near:
dependencies:
'@ledgerhq/hw-transport':
@@ -9309,6 +9425,14 @@ packages:
/@babel/regjsgen@0.8.0:
resolution: {integrity: sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA==}
+ /@babel/runtime-corejs3@7.24.7:
+ resolution: {integrity: sha512-eytSX6JLBY6PVAeQa2bFlDx/7Mmln/gaEpsit5a3WEvjGfiIytEsgAwuIXCPM0xvw0v0cJn3ilq0/TvXrW0kgA==}
+ engines: {node: '>=6.9.0'}
+ dependencies:
+ core-js-pure: 3.36.1
+ regenerator-runtime: 0.14.1
+ dev: false
+
/@babel/runtime@7.24.1:
resolution: {integrity: sha512-+BIznRzyqBf+2wCTxcKE3wDjfGeCoVE61KSHGpkzqrLi8qxqFwBeUFyId2cxkTmm55fzDGnm0+yCxaxygrLUnQ==}
engines: {node: '>=6.9.0'}
@@ -14025,14 +14149,14 @@ packages:
'@jest/test-result': 29.7.0
'@jest/transform': 29.7.0
'@jest/types': 29.6.3
- '@types/node': 20.12.12
+ '@types/node': 20.12.13
ansi-escapes: 4.3.2
chalk: 4.1.2
ci-info: 3.9.0
exit: 0.1.2
graceful-fs: 4.2.11
jest-changed-files: 29.7.0
- jest-config: 29.7.0(@types/node@20.12.12)
+ jest-config: 29.7.0(@types/node@20.12.13)
jest-haste-map: 29.7.0
jest-message-util: 29.7.0
jest-regex-util: 29.6.3
@@ -14069,14 +14193,14 @@ packages:
'@jest/test-result': 29.7.0
'@jest/transform': 29.7.0
'@jest/types': 29.6.3
- '@types/node': 20.12.12
+ '@types/node': 20.12.13
ansi-escapes: 4.3.2
chalk: 4.1.2
ci-info: 3.9.0
exit: 0.1.2
graceful-fs: 4.2.11
jest-changed-files: 29.7.0
- jest-config: 29.7.0(@types/node@20.12.12)(ts-node@10.9.2)
+ jest-config: 29.7.0(@types/node@20.12.13)(ts-node@10.9.2)
jest-haste-map: 29.7.0
jest-message-util: 29.7.0
jest-regex-util: 29.6.3
@@ -14131,7 +14255,7 @@ packages:
dependencies:
'@jest/fake-timers': 29.7.0
'@jest/types': 29.6.3
- '@types/node': 20.12.12
+ '@types/node': 20.12.13
jest-mock: 29.7.0
/@jest/expect-utils@28.1.3:
@@ -14200,7 +14324,7 @@ packages:
dependencies:
'@jest/types': 29.6.3
'@sinonjs/fake-timers': 10.3.0
- '@types/node': 20.12.12
+ '@types/node': 20.12.13
jest-message-util: 29.7.0
jest-mock: 29.7.0
jest-util: 29.7.0
@@ -14594,7 +14718,7 @@ packages:
'@jest/schemas': 29.6.3
'@types/istanbul-lib-coverage': 2.0.6
'@types/istanbul-reports': 3.0.4
- '@types/node': 20.12.12
+ '@types/node': 20.12.13
'@types/yargs': 17.0.32
chalk: 4.1.2
@@ -22295,7 +22419,7 @@ packages:
'@storybook/builder-webpack5': 7.6.17(esbuild@0.19.12)(typescript@5.4.3)
'@storybook/preset-react-webpack': 7.6.17(@babel/core@7.24.3)(esbuild@0.19.12)(react-dom@18.2.0)(react@18.2.0)(typescript@5.4.3)
'@storybook/react': 7.6.17(react-dom@18.2.0)(react@18.2.0)(typescript@5.4.3)
- '@types/node': 18.19.26
+ '@types/node': 18.19.33
react: 18.2.0
react-dom: 18.2.0(react@18.2.0)
typescript: 5.4.3
@@ -22335,7 +22459,7 @@ packages:
'@storybook/builder-webpack5': 7.6.17(metro@0.80.8)(typescript@5.4.3)
'@storybook/preset-react-webpack': 7.6.17(@babel/core@7.24.3)(metro@0.80.8)(react-dom@18.2.0)(react@18.2.0)(typescript@5.4.3)(webpack-dev-server@4.15.2)
'@storybook/react': 7.6.17(react-dom@18.2.0)(react@18.2.0)(typescript@5.4.3)
- '@types/node': 18.19.26
+ '@types/node': 18.19.33
react: 18.2.0
react-dom: 18.2.0(react@18.2.0)
typescript: 5.4.3
@@ -22377,7 +22501,7 @@ packages:
'@storybook/types': 7.6.17
'@types/escodegen': 0.0.6
'@types/estree': 0.0.51
- '@types/node': 18.19.26
+ '@types/node': 18.19.33
acorn: 7.4.1
acorn-jsx: 5.3.2(acorn@7.4.1)
acorn-walk: 7.2.0
@@ -23660,7 +23784,7 @@ packages:
/@types/fs-extra@9.0.13:
resolution: {integrity: sha512-nEnwB++1u5lVDM2UI4c1+5R+FYaKfaAzS4OococimjVm3nQw3TuzH5UNsocrcTBbhnerblyHj4A49qXbIiZdpA==}
dependencies:
- '@types/node': 20.12.12
+ '@types/node': 20.12.13
dev: true
/@types/glob@7.2.0:
@@ -23780,7 +23904,7 @@ packages:
/@types/jsdom@20.0.1:
resolution: {integrity: sha512-d0r18sZPmMQr1eG35u12FZfhIXNrnsPU/g5wvRKCUf/tOGilKKwYMYGqh33BNR6ba+2gkHw1EUiHoN3mn7E5IQ==}
dependencies:
- '@types/node': 20.12.12
+ '@types/node': 20.12.13
'@types/tough-cookie': 4.0.5
parse5: 7.1.2
dev: true
@@ -23905,7 +24029,7 @@ packages:
/@types/node-hid@1.3.4:
resolution: {integrity: sha512-0ootpsYetN9vjqkDSwm/cA4fk/9yGM/PO0X8SLPE/BzXlUaBelImMWMymtF9QEoEzxY0pnhcROIJM0CNSUqO8w==}
dependencies:
- '@types/node': 20.12.12
+ '@types/node': 20.12.13
dev: true
/@types/node@10.12.18:
@@ -24378,7 +24502,7 @@ packages:
/@types/stream-json@1.7.7:
resolution: {integrity: sha512-hHG7cLQ09H/m9i0jzL6UJAeLLxIWej90ECn0svO4T8J0nGcl89xZDQ2ujT4WKlvg0GWkcxJbjIDzW/v7BYUM6Q==}
dependencies:
- '@types/node': 20.12.12
+ '@types/node': 20.12.13
'@types/stream-chain': 2.0.4
dev: true
@@ -24462,7 +24586,7 @@ packages:
/@types/unzipper@0.10.9:
resolution: {integrity: sha512-vHbmFZAw8emNAOVkHVbS3qBnbr0x/qHQZ+ei1HE7Oy6Tyrptl+jpqnOX+BF5owcu/HZLOV0nJK+K9sjs1Ox2JA==}
dependencies:
- '@types/node': 20.12.12
+ '@types/node': 20.12.13
dev: true
/@types/utf8@2.1.6:
@@ -28943,6 +29067,26 @@ packages:
- ts-node
dev: true
+ /create-jest@29.7.0(@types/node@20.12.13)(ts-node@10.9.2):
+ resolution: {integrity: sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q==}
+ engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
+ hasBin: true
+ dependencies:
+ '@jest/types': 29.6.3
+ chalk: 4.1.2
+ exit: 0.1.2
+ graceful-fs: 4.2.11
+ jest-config: 29.7.0(@types/node@20.12.13)(ts-node@10.9.2)
+ jest-util: 29.7.0
+ prompts: 2.4.2
+ transitivePeerDependencies:
+ - '@types/node'
+ - babel-plugin-macros
+ - metro
+ - supports-color
+ - ts-node
+ dev: true
+
/create-jest@29.7.0(ts-node@10.9.2):
resolution: {integrity: sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q==}
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
@@ -35618,6 +35762,26 @@ packages:
'@babel/runtime': 7.24.1
dev: false
+ /icon-sdk-js@1.5.2:
+ resolution: {integrity: sha512-fUYsFGFWuftxy3Ogsjy4kGUsxgH4DSdvN446BvwzaogXwYkOpzpLgZFWnQyrfuZxBSRVRzGtErlfDoMj4TOGGQ==}
+ engines: {node: '>=18.0.0', yarn: ^1.22.10}
+ dependencies:
+ '@babel/runtime-corejs3': 7.24.7
+ bignumber.js: 9.1.2
+ bip66: 1.1.5
+ buffer: 6.0.3
+ bufferutil: 4.0.8
+ core-js: 3.36.1
+ crypto-browserify: 3.12.0
+ js-sha3: 0.8.0
+ readable-stream: 4.5.2
+ secp256k1: 5.0.0
+ utf-8-validate: 6.0.3
+ utf8: 3.0.0
+ uuid: 9.0.1
+ ws: 8.16.0(bufferutil@4.0.8)(utf-8-validate@6.0.3)
+ dev: false
+
/iconv-corefoundation@1.1.7:
resolution: {integrity: sha512-T10qvkw0zz4wnm560lOEg0PovVqUXuOFhhHAkixw8/sycy7TJt7v/RrkEKEQnAw2viPSJu6iAkErxnzR0g8PpQ==}
engines: {node: ^8.11.2 || >=10}
@@ -37059,6 +37223,35 @@ packages:
- ts-node
dev: true
+ /jest-cli@29.7.0(@types/node@20.12.13)(ts-node@10.9.2):
+ resolution: {integrity: sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg==}
+ engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
+ hasBin: true
+ peerDependencies:
+ node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0
+ peerDependenciesMeta:
+ node-notifier:
+ optional: true
+ dependencies:
+ '@jest/core': 29.7.0(ts-node@10.9.2)
+ '@jest/test-result': 29.7.0
+ '@jest/types': 29.6.3
+ chalk: 4.1.2
+ create-jest: 29.7.0(@types/node@20.12.13)(ts-node@10.9.2)
+ exit: 0.1.2
+ import-local: 3.1.0
+ jest-config: 29.7.0(@types/node@20.12.13)(ts-node@10.9.2)
+ jest-util: 29.7.0
+ jest-validate: 29.7.0
+ yargs: 17.7.2
+ transitivePeerDependencies:
+ - '@types/node'
+ - babel-plugin-macros
+ - metro
+ - supports-color
+ - ts-node
+ dev: true
+
/jest-cli@29.7.0(ts-node@10.9.2):
resolution: {integrity: sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg==}
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
@@ -37407,6 +37600,89 @@ packages:
pretty-format: 29.7.0
slash: 3.0.0
strip-json-comments: 3.1.1
+ ts-node: 10.9.2(@types/node@20.12.12)(typescript@5.4.5)
+ transitivePeerDependencies:
+ - babel-plugin-macros
+ - metro
+ - supports-color
+ dev: true
+
+ /jest-config@29.7.0(@types/node@20.12.13):
+ resolution: {integrity: sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ==}
+ engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
+ peerDependencies:
+ '@types/node': '*'
+ ts-node: '>=9.0.0'
+ peerDependenciesMeta:
+ '@types/node':
+ optional: true
+ ts-node:
+ optional: true
+ dependencies:
+ '@babel/core': 7.24.3
+ '@jest/test-sequencer': 29.7.0
+ '@jest/types': 29.6.3
+ '@types/node': 20.12.13
+ babel-jest: 29.7.0(@babel/core@7.24.3)
+ chalk: 4.1.2
+ ci-info: 3.9.0
+ deepmerge: 4.3.1
+ glob: 7.2.3
+ graceful-fs: 4.2.11
+ jest-circus: 29.7.0
+ jest-environment-node: 29.7.0
+ jest-get-type: 29.6.3
+ jest-regex-util: 29.6.3
+ jest-resolve: 29.7.0
+ jest-runner: 29.7.0
+ jest-util: 29.7.0
+ jest-validate: 29.7.0
+ micromatch: 4.0.5
+ parse-json: 5.2.0
+ pretty-format: 29.7.0
+ slash: 3.0.0
+ strip-json-comments: 3.1.1
+ transitivePeerDependencies:
+ - babel-plugin-macros
+ - metro
+ - supports-color
+ dev: true
+
+ /jest-config@29.7.0(@types/node@20.12.13)(ts-node@10.9.2):
+ resolution: {integrity: sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ==}
+ engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
+ peerDependencies:
+ '@types/node': '*'
+ ts-node: '>=9.0.0'
+ peerDependenciesMeta:
+ '@types/node':
+ optional: true
+ ts-node:
+ optional: true
+ dependencies:
+ '@babel/core': 7.24.3
+ '@jest/test-sequencer': 29.7.0
+ '@jest/types': 29.6.3
+ '@types/node': 20.12.13
+ babel-jest: 29.7.0(@babel/core@7.24.3)
+ chalk: 4.1.2
+ ci-info: 3.9.0
+ deepmerge: 4.3.1
+ glob: 7.2.3
+ graceful-fs: 4.2.11
+ jest-circus: 29.7.0
+ jest-environment-node: 29.7.0
+ jest-get-type: 29.6.3
+ jest-regex-util: 29.6.3
+ jest-resolve: 29.7.0
+ jest-runner: 29.7.0
+ jest-util: 29.7.0
+ jest-validate: 29.7.0
+ micromatch: 4.0.5
+ parse-json: 5.2.0
+ pretty-format: 29.7.0
+ slash: 3.0.0
+ strip-json-comments: 3.1.1
ts-node: 10.9.2(typescript@5.4.3)
transitivePeerDependencies:
- babel-plugin-macros
@@ -37644,7 +37920,7 @@ packages:
'@jest/fake-timers': 28.1.3
'@jest/types': 28.1.3
'@types/jsdom': 16.2.15
- '@types/node': 20.12.12
+ '@types/node': 20.12.13
jest-mock: 28.1.3
jest-util: 28.1.3
jsdom: 19.0.0
@@ -37912,12 +38188,12 @@ packages:
resolution: {integrity: sha512-rMyFe1+jnyAAf+NHwTclDz0eAaLkVDdKVHHBFWsBWHnnh5YeJMNWWsv7AbFYXfK3oTqvL7VTWkhNLu1jX24D+g==}
engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0}
dependencies:
- '@babel/code-frame': 7.24.2
+ '@babel/code-frame': 7.24.6
'@jest/types': 27.5.1
'@types/stack-utils': 2.0.3
chalk: 4.1.2
graceful-fs: 4.2.11
- micromatch: 4.0.5
+ micromatch: 4.0.7
pretty-format: 27.5.1
slash: 3.0.0
stack-utils: 2.0.6
@@ -38011,7 +38287,7 @@ packages:
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
dependencies:
'@jest/types': 29.6.3
- '@types/node': 20.12.12
+ '@types/node': 20.12.13
jest-util: 29.7.0
/jest-pnp-resolver@1.2.3(jest-resolve@27.5.1):
@@ -38464,7 +38740,7 @@ packages:
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
dependencies:
'@jest/types': 29.6.3
- '@types/node': 20.12.12
+ '@types/node': 20.12.13
chalk: 4.1.2
ci-info: 3.9.0
graceful-fs: 4.2.11
@@ -38798,6 +39074,28 @@ packages:
- ts-node
dev: true
+ /jest@29.7.0(@types/node@20.12.13)(ts-node@10.9.2):
+ resolution: {integrity: sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==}
+ engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
+ hasBin: true
+ peerDependencies:
+ node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0
+ peerDependenciesMeta:
+ node-notifier:
+ optional: true
+ dependencies:
+ '@jest/core': 29.7.0(ts-node@10.9.2)
+ '@jest/types': 29.6.3
+ import-local: 3.1.0
+ jest-cli: 29.7.0(@types/node@20.12.13)(ts-node@10.9.2)
+ transitivePeerDependencies:
+ - '@types/node'
+ - babel-plugin-macros
+ - metro
+ - supports-color
+ - ts-node
+ dev: true
+
/jest@29.7.0(ts-node@10.9.2):
resolution: {integrity: sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==}
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
@@ -42825,7 +43123,6 @@ packages:
/node-addon-api@5.1.0:
resolution: {integrity: sha512-eh0GgfEkpnoWDq+VY8OyvYhFEzBk6jIYbRKdIlyTiAXIVJ8PyBaKb0rp7oDtoddbdoHWhq8wwr+XZ81F1rpNdA==}
- dev: true
/node-addon-api@6.1.0:
resolution: {integrity: sha512-+eawOlIgy680F0kBzPUNFhMZGtJ1YmqM6l4+Crf4IkImjYrO/mqPwRMh352g23uIaQKFItcQ64I7KMaJxHgAVA==}
@@ -48243,7 +48540,6 @@ packages:
elliptic: 6.5.5
node-addon-api: 5.1.0
node-gyp-build: 4.8.0
- dev: true
/secretjs@0.17.8:
resolution: {integrity: sha512-PD/GUF52GjysBo8dDVK8KZXRXON1iPXkkyBNWIBVsaap3A1nZPbqynx/VUOjSpFx103KdjvzeA4+O0+EdWWWmw==}
@@ -51382,6 +51678,39 @@ packages:
- source-map-support
dev: true
+ /ts-node@10.9.2(@types/node@20.12.13)(source-map-support@0.5.21)(typescript@5.4.3):
+ resolution: {integrity: sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==}
+ hasBin: true
+ peerDependencies:
+ '@swc/core': '>=1.2.50'
+ '@swc/wasm': '>=1.2.50'
+ '@types/node': '*'
+ typescript: '>=2.7'
+ peerDependenciesMeta:
+ '@swc/core':
+ optional: true
+ '@swc/wasm':
+ optional: true
+ dependencies:
+ '@cspotcode/source-map-support': 0.8.1(source-map-support@0.5.21)
+ '@tsconfig/node10': 1.0.11
+ '@tsconfig/node12': 1.0.11
+ '@tsconfig/node14': 1.0.3
+ '@tsconfig/node16': 1.0.4
+ '@types/node': 20.12.13
+ acorn: 8.11.3
+ acorn-walk: 8.3.2
+ arg: 4.1.3
+ create-require: 1.1.1
+ diff: 4.0.2
+ make-error: 1.3.6
+ typescript: 5.4.3
+ v8-compile-cache-lib: 3.0.1
+ yn: 3.1.1
+ transitivePeerDependencies:
+ - source-map-support
+ dev: true
+
/ts-node@10.9.2(@types/node@20.5.1)(typescript@5.4.5):
resolution: {integrity: sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==}
hasBin: true
@@ -54685,6 +55014,22 @@ packages:
bufferutil: 4.0.8
utf-8-validate: 5.0.10
+ /ws@8.16.0(bufferutil@4.0.8)(utf-8-validate@6.0.3):
+ resolution: {integrity: sha512-HS0c//TP7Ina87TfiPUz1rQzMhHrl/SG2guqRcTOIUYD2q8uhUdNHZYJUaQ8aTGPzCh+c6oawMKW35nFl1dxyQ==}
+ engines: {node: '>=10.0.0'}
+ peerDependencies:
+ bufferutil: ^4.0.1
+ utf-8-validate: '>=5.0.2'
+ peerDependenciesMeta:
+ bufferutil:
+ optional: true
+ utf-8-validate:
+ optional: true
+ dependencies:
+ bufferutil: 4.0.8
+ utf-8-validate: 6.0.3
+ dev: false
+
/x-is-string@0.1.0:
resolution: {integrity: sha512-GojqklwG8gpzOVEVki5KudKNoq7MbbjYZCbyWzEz7tyPA7eleiE0+ePwOWQQRb5fm86rD3S8Tc0tSFf3AOv50w==}
dev: false
@@ -54980,7 +55325,7 @@ packages:
dependencies:
'@types/fs-extra': 11.0.4
'@types/minimist': 1.2.5
- '@types/node': 18.19.26
+ '@types/node': 18.19.33
'@types/ps-tree': 1.1.6
'@types/which': 3.0.3
chalk: 5.3.0