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 ( + + + }> + + + <Trans i18nKey="account.availableBalance" /> + + + + + + {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