@ledgerhq/coin-hedera 1.13.0-nightly.2 → 1.13.0-nightly.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +1 -1
- package/.unimportedrc.json +1 -1
- package/CHANGELOG.md +14 -0
- package/jest.config.js +1 -1
- package/jest.integ.config.js +8 -0
- package/lib/api/index.d.ts +4 -0
- package/lib/api/index.d.ts.map +1 -0
- package/lib/api/index.js +119 -0
- package/lib/api/index.js.map +1 -0
- package/lib/bridge/broadcast.d.ts +1 -1
- package/lib/bridge/broadcast.d.ts.map +1 -1
- package/lib/bridge/broadcast.js +6 -9
- package/lib/bridge/broadcast.js.map +1 -1
- package/lib/bridge/buildOptimisticOperation.d.ts.map +1 -1
- package/lib/bridge/buildOptimisticOperation.js +14 -11
- package/lib/bridge/buildOptimisticOperation.js.map +1 -1
- package/lib/bridge/createTransaction.d.ts +1 -1
- package/lib/bridge/createTransaction.d.ts.map +1 -1
- package/lib/bridge/createTransaction.js +2 -0
- package/lib/bridge/createTransaction.js.map +1 -1
- package/lib/bridge/estimateMaxSpendable.d.ts.map +1 -1
- package/lib/bridge/estimateMaxSpendable.js +2 -2
- package/lib/bridge/estimateMaxSpendable.js.map +1 -1
- package/lib/bridge/getTransactionStatus.d.ts.map +1 -1
- package/lib/bridge/getTransactionStatus.js +11 -10
- package/lib/bridge/getTransactionStatus.js.map +1 -1
- package/lib/bridge/prepareTransaction.d.ts.map +1 -1
- package/lib/bridge/prepareTransaction.js +6 -5
- package/lib/bridge/prepareTransaction.js.map +1 -1
- package/lib/bridge/signOperation.d.ts +1 -1
- package/lib/bridge/signOperation.d.ts.map +1 -1
- package/lib/bridge/signOperation.js +53 -14
- package/lib/bridge/signOperation.js.map +1 -1
- package/lib/bridge/synchronisation.d.ts.map +1 -1
- package/lib/bridge/synchronisation.js +24 -11
- package/lib/bridge/synchronisation.js.map +1 -1
- package/lib/bridge/utils.d.ts +3 -13
- package/lib/bridge/utils.d.ts.map +1 -1
- package/lib/bridge/utils.js +10 -119
- package/lib/bridge/utils.js.map +1 -1
- package/lib/config.d.ts +8 -0
- package/lib/config.d.ts.map +1 -0
- package/lib/config.js +9 -0
- package/lib/config.js.map +1 -0
- package/lib/constants.d.ts +11 -8
- package/lib/constants.d.ts.map +1 -1
- package/lib/constants.js +17 -13
- package/lib/constants.js.map +1 -1
- package/lib/deviceTransactionConfig.d.ts +1 -1
- package/lib/deviceTransactionConfig.d.ts.map +1 -1
- package/lib/deviceTransactionConfig.js +3 -3
- package/lib/deviceTransactionConfig.js.map +1 -1
- package/lib/logic/broadcast.d.ts +3 -0
- package/lib/logic/broadcast.d.ts.map +1 -0
- package/lib/logic/broadcast.js +11 -0
- package/lib/logic/broadcast.js.map +1 -0
- package/lib/logic/combine.d.ts +2 -0
- package/lib/logic/combine.d.ts.map +1 -0
- package/lib/logic/combine.js +19 -0
- package/lib/logic/combine.js.map +1 -0
- package/lib/logic/craftTransaction.d.ts +8 -0
- package/lib/logic/craftTransaction.d.ts.map +1 -0
- package/lib/logic/craftTransaction.js +107 -0
- package/lib/logic/craftTransaction.js.map +1 -0
- package/lib/logic/estimateFees.d.ts +5 -0
- package/lib/logic/estimateFees.d.ts.map +1 -0
- package/lib/logic/estimateFees.js +25 -0
- package/lib/logic/estimateFees.js.map +1 -0
- package/lib/logic/getAssetFromToken.d.ts +4 -0
- package/lib/logic/getAssetFromToken.d.ts.map +1 -0
- package/lib/logic/getAssetFromToken.js +14 -0
- package/lib/logic/getAssetFromToken.js.map +1 -0
- package/lib/logic/getBalance.d.ts +4 -0
- package/lib/logic/getBalance.d.ts.map +1 -0
- package/lib/logic/getBalance.js +36 -0
- package/lib/logic/getBalance.js.map +1 -0
- package/lib/logic/getTokenFromAsset.d.ts +4 -0
- package/lib/logic/getTokenFromAsset.d.ts.map +1 -0
- package/lib/logic/getTokenFromAsset.js +13 -0
- package/lib/logic/getTokenFromAsset.js.map +1 -0
- package/lib/logic/index.d.ts +10 -0
- package/lib/logic/index.d.ts.map +1 -0
- package/lib/logic/index.js +22 -0
- package/lib/logic/index.js.map +1 -0
- package/lib/logic/lastBlock.d.ts +12 -0
- package/lib/logic/lastBlock.d.ts.map +1 -0
- package/lib/logic/lastBlock.js +25 -0
- package/lib/logic/lastBlock.js.map +1 -0
- package/lib/logic/listOperations.d.ts +19 -0
- package/lib/logic/listOperations.d.ts.map +1 -0
- package/lib/logic/listOperations.js +179 -0
- package/lib/logic/listOperations.js.map +1 -0
- package/lib/logic/utils.d.ts +55 -0
- package/lib/logic/utils.d.ts.map +1 -0
- package/lib/logic/utils.js +197 -0
- package/lib/logic/utils.js.map +1 -0
- package/lib/network/api.d.ts +24 -0
- package/lib/network/api.d.ts.map +1 -0
- package/lib/network/api.js +119 -0
- package/lib/network/api.js.map +1 -0
- package/lib/network/rpc.d.ts +12 -0
- package/lib/network/rpc.d.ts.map +1 -0
- package/lib/network/rpc.js +22 -0
- package/lib/network/rpc.js.map +1 -0
- package/lib/{api → network}/utils.d.ts +1 -5
- package/lib/network/utils.d.ts.map +1 -0
- package/lib/network/utils.js +52 -0
- package/lib/network/utils.js.map +1 -0
- package/lib/test/bridgeDatasetTest.d.ts.map +1 -1
- package/lib/test/bridgeDatasetTest.js +5 -1
- package/lib/test/bridgeDatasetTest.js.map +1 -1
- package/lib/test/fixtures/account.fixture.d.ts +17 -0
- package/lib/test/fixtures/account.fixture.d.ts.map +1 -1
- package/lib/test/fixtures/account.fixture.js +18 -1
- package/lib/test/fixtures/account.fixture.js.map +1 -1
- package/lib/test/fixtures/currency.fixture.d.ts.map +1 -1
- package/lib/test/fixtures/currency.fixture.js +1 -1
- package/lib/test/fixtures/currency.fixture.js.map +1 -1
- package/lib/test/fixtures/mirror.fixture.d.ts +1 -1
- package/lib/test/fixtures/mirror.fixture.d.ts.map +1 -1
- package/lib/test/fixtures/network.fixture.d.ts +3 -0
- package/lib/test/fixtures/network.fixture.d.ts.map +1 -0
- package/lib/test/fixtures/network.fixture.js +9 -0
- package/lib/test/fixtures/network.fixture.js.map +1 -0
- package/lib/test/fixtures/transaction.fixture.d.ts.map +1 -1
- package/lib/test/fixtures/transaction.fixture.js +3 -0
- package/lib/test/fixtures/transaction.fixture.js.map +1 -1
- package/lib/transaction.d.ts +1 -1
- package/lib/transaction.d.ts.map +1 -1
- package/lib/transaction.js +35 -6
- package/lib/transaction.js.map +1 -1
- package/lib/types/alpaca.d.ts +3 -0
- package/lib/types/alpaca.d.ts.map +1 -0
- package/lib/{api/types.js → types/alpaca.js} +1 -1
- package/lib/types/alpaca.js.map +1 -0
- package/lib/types/bridge.d.ts +28 -9
- package/lib/types/bridge.d.ts.map +1 -1
- package/lib/types/index.d.ts +2 -0
- package/lib/types/index.d.ts.map +1 -1
- package/lib/types/index.js +2 -0
- package/lib/types/index.js.map +1 -1
- package/lib/{api/types.d.ts → types/mirror.d.ts} +21 -2
- package/lib/types/mirror.d.ts.map +1 -0
- package/lib/types/mirror.js +3 -0
- package/lib/types/mirror.js.map +1 -0
- package/lib-es/api/index.d.ts +4 -0
- package/lib-es/api/index.d.ts.map +1 -0
- package/lib-es/api/index.js +112 -0
- package/lib-es/api/index.js.map +1 -0
- package/lib-es/bridge/broadcast.d.ts +1 -1
- package/lib-es/bridge/broadcast.d.ts.map +1 -1
- package/lib-es/bridge/broadcast.js +4 -7
- package/lib-es/bridge/broadcast.js.map +1 -1
- package/lib-es/bridge/buildOptimisticOperation.d.ts.map +1 -1
- package/lib-es/bridge/buildOptimisticOperation.js +12 -9
- package/lib-es/bridge/buildOptimisticOperation.js.map +1 -1
- package/lib-es/bridge/createTransaction.d.ts +1 -1
- package/lib-es/bridge/createTransaction.d.ts.map +1 -1
- package/lib-es/bridge/createTransaction.js +2 -0
- package/lib-es/bridge/createTransaction.js.map +1 -1
- package/lib-es/bridge/estimateMaxSpendable.d.ts.map +1 -1
- package/lib-es/bridge/estimateMaxSpendable.js +2 -2
- package/lib-es/bridge/estimateMaxSpendable.js.map +1 -1
- package/lib-es/bridge/getTransactionStatus.d.ts.map +1 -1
- package/lib-es/bridge/getTransactionStatus.js +7 -6
- package/lib-es/bridge/getTransactionStatus.js.map +1 -1
- package/lib-es/bridge/prepareTransaction.d.ts.map +1 -1
- package/lib-es/bridge/prepareTransaction.js +4 -3
- package/lib-es/bridge/prepareTransaction.js.map +1 -1
- package/lib-es/bridge/signOperation.d.ts +1 -1
- package/lib-es/bridge/signOperation.d.ts.map +1 -1
- package/lib-es/bridge/signOperation.js +53 -14
- package/lib-es/bridge/signOperation.js.map +1 -1
- package/lib-es/bridge/synchronisation.d.ts.map +1 -1
- package/lib-es/bridge/synchronisation.js +24 -11
- package/lib-es/bridge/synchronisation.js.map +1 -1
- package/lib-es/bridge/utils.d.ts +3 -13
- package/lib-es/bridge/utils.d.ts.map +1 -1
- package/lib-es/bridge/utils.js +7 -113
- package/lib-es/bridge/utils.js.map +1 -1
- package/lib-es/config.d.ts +8 -0
- package/lib-es/config.d.ts.map +1 -0
- package/lib-es/config.js +4 -0
- package/lib-es/config.js.map +1 -0
- package/lib-es/constants.d.ts +11 -8
- package/lib-es/constants.d.ts.map +1 -1
- package/lib-es/constants.js +13 -9
- package/lib-es/constants.js.map +1 -1
- package/lib-es/deviceTransactionConfig.d.ts +1 -1
- package/lib-es/deviceTransactionConfig.d.ts.map +1 -1
- package/lib-es/deviceTransactionConfig.js +1 -1
- package/lib-es/deviceTransactionConfig.js.map +1 -1
- package/lib-es/logic/broadcast.d.ts +3 -0
- package/lib-es/logic/broadcast.d.ts.map +1 -0
- package/lib-es/logic/broadcast.js +7 -0
- package/lib-es/logic/broadcast.js.map +1 -0
- package/lib-es/logic/combine.d.ts +2 -0
- package/lib-es/logic/combine.d.ts.map +1 -0
- package/lib-es/logic/combine.js +12 -0
- package/lib-es/logic/combine.js.map +1 -0
- package/lib-es/logic/craftTransaction.d.ts +8 -0
- package/lib-es/logic/craftTransaction.d.ts.map +1 -0
- package/lib-es/logic/craftTransaction.js +100 -0
- package/lib-es/logic/craftTransaction.js.map +1 -0
- package/lib-es/logic/estimateFees.d.ts +5 -0
- package/lib-es/logic/estimateFees.d.ts.map +1 -0
- package/lib-es/logic/estimateFees.js +18 -0
- package/lib-es/logic/estimateFees.js.map +1 -0
- package/lib-es/logic/getAssetFromToken.d.ts +4 -0
- package/lib-es/logic/getAssetFromToken.d.ts.map +1 -0
- package/lib-es/logic/getAssetFromToken.js +10 -0
- package/lib-es/logic/getAssetFromToken.js.map +1 -0
- package/lib-es/logic/getBalance.d.ts +4 -0
- package/lib-es/logic/getBalance.d.ts.map +1 -0
- package/lib-es/logic/getBalance.js +32 -0
- package/lib-es/logic/getBalance.js.map +1 -0
- package/lib-es/logic/getTokenFromAsset.d.ts +4 -0
- package/lib-es/logic/getTokenFromAsset.d.ts.map +1 -0
- package/lib-es/logic/getTokenFromAsset.js +9 -0
- package/lib-es/logic/getTokenFromAsset.js.map +1 -0
- package/lib-es/logic/index.d.ts +10 -0
- package/lib-es/logic/index.d.ts.map +1 -0
- package/lib-es/logic/index.js +10 -0
- package/lib-es/logic/index.js.map +1 -0
- package/lib-es/logic/lastBlock.d.ts +12 -0
- package/lib-es/logic/lastBlock.d.ts.map +1 -0
- package/lib-es/logic/lastBlock.js +21 -0
- package/lib-es/logic/lastBlock.js.map +1 -0
- package/lib-es/logic/listOperations.d.ts +19 -0
- package/lib-es/logic/listOperations.d.ts.map +1 -0
- package/lib-es/logic/listOperations.js +172 -0
- package/lib-es/logic/listOperations.js.map +1 -0
- package/lib-es/logic/utils.d.ts +55 -0
- package/lib-es/logic/utils.d.ts.map +1 -0
- package/lib-es/logic/utils.js +174 -0
- package/lib-es/logic/utils.js.map +1 -0
- package/lib-es/network/api.d.ts +24 -0
- package/lib-es/network/api.d.ts.map +1 -0
- package/lib-es/network/api.js +113 -0
- package/lib-es/network/api.js.map +1 -0
- package/lib-es/network/rpc.d.ts +12 -0
- package/lib-es/network/rpc.d.ts.map +1 -0
- package/lib-es/network/rpc.js +19 -0
- package/lib-es/network/rpc.js.map +1 -0
- package/lib-es/{api → network}/utils.d.ts +1 -5
- package/lib-es/network/utils.d.ts.map +1 -0
- package/lib-es/network/utils.js +45 -0
- package/lib-es/network/utils.js.map +1 -0
- package/lib-es/test/bridgeDatasetTest.d.ts.map +1 -1
- package/lib-es/test/bridgeDatasetTest.js +5 -1
- package/lib-es/test/bridgeDatasetTest.js.map +1 -1
- package/lib-es/test/fixtures/account.fixture.d.ts +17 -0
- package/lib-es/test/fixtures/account.fixture.d.ts.map +1 -1
- package/lib-es/test/fixtures/account.fixture.js +17 -0
- package/lib-es/test/fixtures/account.fixture.js.map +1 -1
- package/lib-es/test/fixtures/currency.fixture.d.ts.map +1 -1
- package/lib-es/test/fixtures/currency.fixture.js +1 -1
- package/lib-es/test/fixtures/currency.fixture.js.map +1 -1
- package/lib-es/test/fixtures/mirror.fixture.d.ts +1 -1
- package/lib-es/test/fixtures/mirror.fixture.d.ts.map +1 -1
- package/lib-es/test/fixtures/network.fixture.d.ts +3 -0
- package/lib-es/test/fixtures/network.fixture.d.ts.map +1 -0
- package/lib-es/test/fixtures/network.fixture.js +5 -0
- package/lib-es/test/fixtures/network.fixture.js.map +1 -0
- package/lib-es/test/fixtures/transaction.fixture.d.ts.map +1 -1
- package/lib-es/test/fixtures/transaction.fixture.js +3 -0
- package/lib-es/test/fixtures/transaction.fixture.js.map +1 -1
- package/lib-es/transaction.d.ts +1 -1
- package/lib-es/transaction.d.ts.map +1 -1
- package/lib-es/transaction.js +35 -6
- package/lib-es/transaction.js.map +1 -1
- package/lib-es/types/alpaca.d.ts +3 -0
- package/lib-es/types/alpaca.d.ts.map +1 -0
- package/lib-es/types/alpaca.js +2 -0
- package/lib-es/types/alpaca.js.map +1 -0
- package/lib-es/types/bridge.d.ts +28 -9
- package/lib-es/types/bridge.d.ts.map +1 -1
- package/lib-es/types/index.d.ts +2 -0
- package/lib-es/types/index.d.ts.map +1 -1
- package/lib-es/types/index.js +2 -0
- package/lib-es/types/index.js.map +1 -1
- package/lib-es/{api/types.d.ts → types/mirror.d.ts} +21 -2
- package/lib-es/types/mirror.d.ts.map +1 -0
- package/lib-es/types/mirror.js +2 -0
- package/lib-es/types/mirror.js.map +1 -0
- package/package.json +4 -3
- package/src/api/index.integ.test.ts +401 -0
- package/src/api/index.test.ts +30 -0
- package/src/api/index.ts +149 -0
- package/src/bridge/broadcast.ts +5 -10
- package/src/bridge/buildOptimisticOperation.integration.test.ts +8 -8
- package/src/bridge/buildOptimisticOperation.ts +13 -10
- package/src/bridge/createTransaction.ts +3 -1
- package/src/bridge/estimateMaxSpendable.ts +6 -3
- package/src/bridge/getTransactionStatus.test.ts +11 -10
- package/src/bridge/getTransactionStatus.ts +12 -11
- package/src/bridge/js-estimateMaxSpendable.integration.test.ts +6 -3
- package/src/bridge/prepareTransaction.test.ts +9 -17
- package/src/bridge/prepareTransaction.ts +5 -4
- package/src/bridge/serialization.test.ts +6 -6
- package/src/bridge/signOperation.ts +69 -16
- package/src/bridge/synchronisation.ts +22 -14
- package/src/bridge/utils.integration.test.ts +19 -248
- package/src/bridge/utils.ts +14 -160
- package/src/config.ts +7 -0
- package/src/constants.ts +15 -9
- package/src/deviceTransactionConfig.ts +2 -2
- package/src/logic/broadcast.test.ts +58 -0
- package/src/logic/broadcast.ts +8 -0
- package/src/logic/combine.test.ts +119 -0
- package/src/logic/combine.ts +14 -0
- package/src/logic/craftTransaction.test.ts +215 -0
- package/src/logic/craftTransaction.ts +175 -0
- package/src/logic/estimateFees.test.ts +99 -0
- package/src/logic/estimateFees.ts +28 -0
- package/src/logic/getAssetFromToken.test.ts +27 -0
- package/src/logic/getAssetFromToken.ts +12 -0
- package/src/logic/getBalance.test.ts +200 -0
- package/src/logic/getBalance.ts +39 -0
- package/src/logic/getTokenFromAsset.test.ts +22 -0
- package/src/logic/getTokenFromAsset.ts +17 -0
- package/src/logic/index.ts +9 -0
- package/src/logic/lastBlock.test.ts +23 -0
- package/src/logic/lastBlock.ts +23 -0
- package/src/logic/listOperations.test.ts +388 -0
- package/src/logic/listOperations.ts +247 -0
- package/src/logic/utils.test.ts +432 -0
- package/src/logic/utils.ts +255 -0
- package/src/{api/mirror.test.ts → network/api.test.ts} +81 -35
- package/src/network/api.ts +159 -0
- package/src/network/rpc.test.ts +68 -0
- package/src/network/rpc.ts +25 -0
- package/src/network/utils.test.ts +175 -0
- package/src/network/utils.ts +58 -0
- package/src/test/bridgeDatasetTest.ts +6 -2
- package/src/test/fixtures/account.fixture.ts +18 -0
- package/src/test/fixtures/currency.fixture.ts +1 -1
- package/src/test/fixtures/mirror.fixture.ts +1 -1
- package/src/test/fixtures/network.fixture.ts +6 -0
- package/src/test/fixtures/transaction.fixture.ts +5 -2
- package/src/transaction.ts +40 -9
- package/src/types/alpaca.ts +3 -0
- package/src/types/bridge.ts +36 -10
- package/src/types/index.ts +2 -0
- package/src/{api/types.ts → types/mirror.ts} +23 -1
- package/lib/api/mirror.d.ts +0 -6
- package/lib/api/mirror.d.ts.map +0 -1
- package/lib/api/mirror.js +0 -84
- package/lib/api/mirror.js.map +0 -1
- package/lib/api/network.d.ts +0 -11
- package/lib/api/network.d.ts.map +0 -1
- package/lib/api/network.js +0 -80
- package/lib/api/network.js.map +0 -1
- package/lib/api/types.d.ts.map +0 -1
- package/lib/api/types.js.map +0 -1
- package/lib/api/utils.d.ts.map +0 -1
- package/lib/api/utils.js +0 -132
- package/lib/api/utils.js.map +0 -1
- package/lib/logic.d.ts +0 -11
- package/lib/logic.d.ts.map +0 -1
- package/lib/logic.js +0 -37
- package/lib/logic.js.map +0 -1
- package/lib-es/api/mirror.d.ts +0 -6
- package/lib-es/api/mirror.d.ts.map +0 -1
- package/lib-es/api/mirror.js +0 -74
- package/lib-es/api/mirror.js.map +0 -1
- package/lib-es/api/network.d.ts +0 -11
- package/lib-es/api/network.d.ts.map +0 -1
- package/lib-es/api/network.js +0 -71
- package/lib-es/api/network.js.map +0 -1
- package/lib-es/api/types.d.ts.map +0 -1
- package/lib-es/api/types.js +0 -2
- package/lib-es/api/types.js.map +0 -1
- package/lib-es/api/utils.d.ts.map +0 -1
- package/lib-es/api/utils.js +0 -124
- package/lib-es/api/utils.js.map +0 -1
- package/lib-es/logic.d.ts +0 -11
- package/lib-es/logic.d.ts.map +0 -1
- package/lib-es/logic.js +0 -29
- package/lib-es/logic.js.map +0 -1
- package/src/api/mirror.ts +0 -91
- package/src/api/network.test.ts +0 -49
- package/src/api/network.ts +0 -125
- package/src/api/utils.ts +0 -150
- package/src/logic.test.ts +0 -152
- package/src/logic.ts +0 -66
|
@@ -0,0 +1,432 @@
|
|
|
1
|
+
import BigNumber from "bignumber.js";
|
|
2
|
+
import { createHash } from "crypto";
|
|
3
|
+
import { Transaction } from "@hashgraph/sdk";
|
|
4
|
+
import type { AssetInfo } from "@ledgerhq/coin-framework/api/types";
|
|
5
|
+
import { getCryptoCurrencyById } from "@ledgerhq/cryptoassets/currencies";
|
|
6
|
+
import { InvalidAddress } from "@ledgerhq/errors";
|
|
7
|
+
import { HEDERA_TRANSACTION_MODES, SYNTHETIC_BLOCK_WINDOW_SECONDS } from "../constants";
|
|
8
|
+
import { apiClient } from "../network/api";
|
|
9
|
+
import { getMockedOperation } from "../test/fixtures/operation.fixture";
|
|
10
|
+
import { getMockedTokenCurrency } from "../test/fixtures/currency.fixture";
|
|
11
|
+
import { getMockedAccount, getMockedTokenAccount } from "../test/fixtures/account.fixture";
|
|
12
|
+
import {
|
|
13
|
+
serializeSignature,
|
|
14
|
+
deserializeSignature,
|
|
15
|
+
serializeTransaction,
|
|
16
|
+
deserializeTransaction,
|
|
17
|
+
getOperationValue,
|
|
18
|
+
getMemoFromBase64,
|
|
19
|
+
sendRecipientCanNext,
|
|
20
|
+
isValidExtra,
|
|
21
|
+
isTokenAssociationRequired,
|
|
22
|
+
isAutoTokenAssociationEnabled,
|
|
23
|
+
isTokenAssociateTransaction,
|
|
24
|
+
getTransactionExplorer,
|
|
25
|
+
checkAccountTokenAssociationStatus,
|
|
26
|
+
safeParseAccountId,
|
|
27
|
+
getSyntheticBlock,
|
|
28
|
+
} from "./utils";
|
|
29
|
+
import { HederaRecipientInvalidChecksum } from "../errors";
|
|
30
|
+
|
|
31
|
+
jest.mock("../network/api");
|
|
32
|
+
|
|
33
|
+
describe("utils", () => {
|
|
34
|
+
beforeEach(() => {
|
|
35
|
+
jest.clearAllMocks();
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
describe("signature serialization", () => {
|
|
39
|
+
it("should serialize a signature to base64", () => {
|
|
40
|
+
const signature = new Uint8Array([1, 2, 3, 4, 5]);
|
|
41
|
+
const serialized = serializeSignature(signature);
|
|
42
|
+
|
|
43
|
+
expect(serialized).toBe("AQIDBAU=");
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it("should deserialize a base64 signature to Uint8Array", () => {
|
|
47
|
+
const base64Signature = "AQIDBAU=";
|
|
48
|
+
const deserialized = deserializeSignature(base64Signature);
|
|
49
|
+
|
|
50
|
+
expect(deserialized).toEqual(Buffer.from([1, 2, 3, 4, 5]));
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
describe("transaction serialization", () => {
|
|
55
|
+
beforeEach(() => {
|
|
56
|
+
jest.spyOn(Transaction, "fromBytes");
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
afterEach(() => {
|
|
60
|
+
jest.restoreAllMocks();
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it("should serialize a transaction to hex", () => {
|
|
64
|
+
const mockTransaction = {
|
|
65
|
+
toBytes: jest.fn().mockReturnValue(Buffer.from([10, 20, 30, 40, 50])),
|
|
66
|
+
} as unknown as Transaction;
|
|
67
|
+
|
|
68
|
+
const serialized = serializeTransaction(mockTransaction);
|
|
69
|
+
|
|
70
|
+
expect(serialized).toBe("0a141e2832");
|
|
71
|
+
expect(mockTransaction.toBytes).toHaveBeenCalled();
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it("should deserialize a hex string to a Transaction", () => {
|
|
75
|
+
const mockTransaction = { id: "mock-transaction-id" };
|
|
76
|
+
(Transaction.fromBytes as jest.Mock).mockReturnValue(mockTransaction);
|
|
77
|
+
|
|
78
|
+
const hexTransaction = "0a141e2832";
|
|
79
|
+
const deserialized = deserializeTransaction(hexTransaction);
|
|
80
|
+
|
|
81
|
+
const hexTransactionBuffer = Buffer.from([10, 20, 30, 40, 50]);
|
|
82
|
+
expect(Transaction.fromBytes).toHaveBeenCalledTimes(1);
|
|
83
|
+
expect(Transaction.fromBytes).toHaveBeenCalledWith(hexTransactionBuffer);
|
|
84
|
+
expect(deserialized).toBe(mockTransaction);
|
|
85
|
+
});
|
|
86
|
+
});
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
describe("getOperationValue", () => {
|
|
90
|
+
const nativeAsset: AssetInfo = { type: "native" };
|
|
91
|
+
const tokenAsset: AssetInfo = { type: "hts", assetReference: "0.0.1234" };
|
|
92
|
+
|
|
93
|
+
it("should return 0 for FEES operations", () => {
|
|
94
|
+
const operation = getMockedOperation({
|
|
95
|
+
type: "FEES",
|
|
96
|
+
value: BigNumber(0),
|
|
97
|
+
fee: BigNumber(100),
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
expect(getOperationValue({ asset: nativeAsset, operation })).toBe(BigInt(0));
|
|
101
|
+
expect(getOperationValue({ asset: tokenAsset, operation })).toBe(BigInt(0));
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it("should substract fee from value for native OUT operations", () => {
|
|
105
|
+
const operation = getMockedOperation({
|
|
106
|
+
type: "OUT",
|
|
107
|
+
value: BigNumber(1000),
|
|
108
|
+
fee: BigNumber(100),
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
expect(getOperationValue({ asset: nativeAsset, operation })).toBe(BigInt(900));
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
it("should return value for other operations", () => {
|
|
115
|
+
const operationOut = getMockedOperation({
|
|
116
|
+
type: "OUT",
|
|
117
|
+
value: BigNumber(500),
|
|
118
|
+
fee: BigNumber(20),
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
const operationIn = getMockedOperation({
|
|
122
|
+
type: "IN",
|
|
123
|
+
value: BigNumber(800),
|
|
124
|
+
fee: BigNumber(30),
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
expect(getOperationValue({ asset: tokenAsset, operation: operationOut })).toBe(BigInt(500));
|
|
128
|
+
expect(getOperationValue({ asset: tokenAsset, operation: operationIn })).toBe(BigInt(800));
|
|
129
|
+
expect(getOperationValue({ asset: nativeAsset, operation: operationIn })).toBe(BigInt(800));
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
describe("getMemoFromBase64", () => {
|
|
133
|
+
it("decodes a simple base64 string", () => {
|
|
134
|
+
expect(getMemoFromBase64("YnJkZw==")).toBe("brdg");
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
it("decodes an empty string", () => {
|
|
138
|
+
expect(getMemoFromBase64("")).toBe("");
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
it("decodes a base64 string with spaces", () => {
|
|
142
|
+
const input = Buffer.from("hello world", "utf-8").toString("base64");
|
|
143
|
+
expect(getMemoFromBase64(input)).toBe("hello world");
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
it("decodes special characters", () => {
|
|
147
|
+
const input = Buffer.from("😀✨", "utf-8").toString("base64");
|
|
148
|
+
expect(getMemoFromBase64(input)).toBe("😀✨");
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
it("returns null for bad input", () => {
|
|
152
|
+
expect(getMemoFromBase64(undefined)).toBeNull();
|
|
153
|
+
expect(getMemoFromBase64(null as unknown as string)).toBeNull();
|
|
154
|
+
expect(getMemoFromBase64({} as unknown as string)).toBeNull();
|
|
155
|
+
expect(getMemoFromBase64(10 as unknown as string)).toBeNull();
|
|
156
|
+
});
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
describe("getTransactionExplorer", () => {
|
|
160
|
+
test("Tx explorer URL is converted from hash to consensus timestamp", async () => {
|
|
161
|
+
const explorerView = getCryptoCurrencyById("hedera").explorerViews[0];
|
|
162
|
+
expect(explorerView).toEqual({
|
|
163
|
+
tx: expect.any(String),
|
|
164
|
+
address: expect.any(String),
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
const mockedOperation = getMockedOperation({
|
|
168
|
+
extra: { consensusTimestamp: "1.2.3.4" },
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
const newUrl = getTransactionExplorer(explorerView, mockedOperation);
|
|
172
|
+
expect(newUrl).toBe("https://hashscan.io/mainnet/transaction/1.2.3.4");
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
test("Tx explorer URL is based on transaction id if consensus timestamp is not available", async () => {
|
|
176
|
+
const explorerView = getCryptoCurrencyById("hedera").explorerViews[0];
|
|
177
|
+
expect(explorerView).toEqual({
|
|
178
|
+
tx: expect.any(String),
|
|
179
|
+
address: expect.any(String),
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
const mockedOperation = getMockedOperation({
|
|
183
|
+
extra: { transactionId: "0.0.1234567-123-123" },
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
const newUrl = getTransactionExplorer(explorerView, mockedOperation);
|
|
187
|
+
expect(newUrl).toBe("https://hashscan.io/mainnet/transaction/0.0.1234567-123-123");
|
|
188
|
+
});
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
describe("isTokenAssociateTransaction", () => {
|
|
192
|
+
test("returns correct value based on tx.properties", () => {
|
|
193
|
+
expect(
|
|
194
|
+
isTokenAssociateTransaction({ mode: HEDERA_TRANSACTION_MODES.TokenAssociate } as any),
|
|
195
|
+
).toBe(true);
|
|
196
|
+
expect(isTokenAssociateTransaction({ mode: HEDERA_TRANSACTION_MODES.Send } as any)).toBe(
|
|
197
|
+
false,
|
|
198
|
+
);
|
|
199
|
+
expect(isTokenAssociateTransaction({} as any)).toBe(false);
|
|
200
|
+
});
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
describe("isAutoTokenAssociationEnabled", () => {
|
|
204
|
+
test("returns value based on isAutoTokenAssociationEnabled flag", () => {
|
|
205
|
+
expect(
|
|
206
|
+
isAutoTokenAssociationEnabled({
|
|
207
|
+
hederaResources: { isAutoTokenAssociationEnabled: true },
|
|
208
|
+
} as any),
|
|
209
|
+
).toBe(true);
|
|
210
|
+
|
|
211
|
+
expect(
|
|
212
|
+
isAutoTokenAssociationEnabled({
|
|
213
|
+
hederaResources: { isAutoTokenAssociationEnabled: false },
|
|
214
|
+
} as any),
|
|
215
|
+
).toBe(false);
|
|
216
|
+
|
|
217
|
+
expect(isAutoTokenAssociationEnabled({} as any)).toBe(false);
|
|
218
|
+
});
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
describe("isTokenAssociationRequired", () => {
|
|
222
|
+
test("should return false if token is already associated (token account exists)", () => {
|
|
223
|
+
const mockedTokenCurrency = getMockedTokenCurrency();
|
|
224
|
+
const mockedTokenAccount = getMockedTokenAccount(mockedTokenCurrency);
|
|
225
|
+
const mockedAccount = getMockedAccount({ subAccounts: [mockedTokenAccount] });
|
|
226
|
+
|
|
227
|
+
expect(isTokenAssociationRequired(mockedAccount, mockedTokenCurrency)).toBe(false);
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
test("should return false if auto token associations are enabled", () => {
|
|
231
|
+
const mockedTokenCurrency = getMockedTokenCurrency();
|
|
232
|
+
const mockedAccount = getMockedAccount({
|
|
233
|
+
subAccounts: [],
|
|
234
|
+
hederaResources: {
|
|
235
|
+
maxAutomaticTokenAssociations: -1,
|
|
236
|
+
isAutoTokenAssociationEnabled: true,
|
|
237
|
+
},
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
expect(isTokenAssociationRequired(mockedAccount, mockedTokenCurrency)).toBe(false);
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
test("should return true if token is not associated and auto associations are disabled", () => {
|
|
244
|
+
const mockedTokenCurrency = getMockedTokenCurrency();
|
|
245
|
+
const mockedAccount = getMockedAccount({ subAccounts: [] });
|
|
246
|
+
|
|
247
|
+
expect(isTokenAssociationRequired(mockedAccount, mockedTokenCurrency)).toBe(true);
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
test("should return false if token is undefined", () => {
|
|
251
|
+
const mockedAccount = getMockedAccount({ subAccounts: [] });
|
|
252
|
+
|
|
253
|
+
expect(isTokenAssociationRequired(mockedAccount, undefined)).toBe(false);
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
test("should return false for legacy accounts without subAccounts or hederaResources", () => {
|
|
257
|
+
const mockedTokenCurrency = getMockedTokenCurrency();
|
|
258
|
+
const mockedAccount = getMockedAccount();
|
|
259
|
+
|
|
260
|
+
delete mockedAccount.subAccounts;
|
|
261
|
+
delete mockedAccount.hederaResources;
|
|
262
|
+
|
|
263
|
+
expect(isTokenAssociationRequired(mockedAccount, mockedTokenCurrency)).toBe(true);
|
|
264
|
+
});
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
describe("isValidExtra", () => {
|
|
268
|
+
test("returns true for object and false for invalid types", () => {
|
|
269
|
+
expect(isValidExtra({ some: "value" })).toBe(true);
|
|
270
|
+
expect(isValidExtra(null)).toBe(false);
|
|
271
|
+
expect(isValidExtra(undefined)).toBe(false);
|
|
272
|
+
expect(isValidExtra("string")).toBe(false);
|
|
273
|
+
expect(isValidExtra(123)).toBe(false);
|
|
274
|
+
expect(isValidExtra([])).toBe(false);
|
|
275
|
+
});
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
describe("sendRecipientCanNext", () => {
|
|
279
|
+
test("handles association warnings", () => {
|
|
280
|
+
expect(sendRecipientCanNext({ warnings: {} } as any)).toBe(true);
|
|
281
|
+
expect(sendRecipientCanNext({ warnings: { missingAssociation: new Error() } } as any)).toBe(
|
|
282
|
+
false,
|
|
283
|
+
);
|
|
284
|
+
expect(
|
|
285
|
+
sendRecipientCanNext({ warnings: { unverifiedAssociation: new Error() } } as any),
|
|
286
|
+
).toBe(false);
|
|
287
|
+
});
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
describe("checkAccountTokenAssociationStatus", () => {
|
|
291
|
+
const accountId = "0.0.1234";
|
|
292
|
+
const tokenId = "0.0.5678";
|
|
293
|
+
|
|
294
|
+
beforeEach(() => {
|
|
295
|
+
jest.clearAllMocks();
|
|
296
|
+
// reset LRU cache to make sure all tests receive correct mocks from mockedGetAccount
|
|
297
|
+
checkAccountTokenAssociationStatus.clear(`${accountId}-${tokenId}`);
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
test("returns true if max_automatic_token_associations === -1", async () => {
|
|
301
|
+
(apiClient.getAccount as jest.Mock).mockResolvedValueOnce({
|
|
302
|
+
account: accountId,
|
|
303
|
+
max_automatic_token_associations: -1,
|
|
304
|
+
balance: {
|
|
305
|
+
balance: 0,
|
|
306
|
+
timestamp: "",
|
|
307
|
+
tokens: [],
|
|
308
|
+
},
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
const result = await checkAccountTokenAssociationStatus(accountId, tokenId);
|
|
312
|
+
expect(result).toBe(true);
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
test("returns true if token is already associated", async () => {
|
|
316
|
+
(apiClient.getAccount as jest.Mock).mockResolvedValueOnce({
|
|
317
|
+
account: accountId,
|
|
318
|
+
max_automatic_token_associations: 0,
|
|
319
|
+
balance: {
|
|
320
|
+
balance: 1,
|
|
321
|
+
timestamp: "",
|
|
322
|
+
tokens: [{ token_id: tokenId, balance: 1 }],
|
|
323
|
+
},
|
|
324
|
+
});
|
|
325
|
+
|
|
326
|
+
const result = await checkAccountTokenAssociationStatus(accountId, tokenId);
|
|
327
|
+
expect(result).toBe(true);
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
test("returns false if token is not associated", async () => {
|
|
331
|
+
(apiClient.getAccount as jest.Mock).mockResolvedValueOnce({
|
|
332
|
+
account: accountId,
|
|
333
|
+
max_automatic_token_associations: 0,
|
|
334
|
+
balance: {
|
|
335
|
+
balance: 1,
|
|
336
|
+
timestamp: "",
|
|
337
|
+
tokens: [{ token_id: "0.0.9999", balance: 1 }],
|
|
338
|
+
},
|
|
339
|
+
});
|
|
340
|
+
|
|
341
|
+
const result = await checkAccountTokenAssociationStatus(accountId, tokenId);
|
|
342
|
+
expect(result).toBe(false);
|
|
343
|
+
});
|
|
344
|
+
|
|
345
|
+
test("supports addresses with checksum", async () => {
|
|
346
|
+
const addressWithChecksum = "0.0.9124531-xrxlv";
|
|
347
|
+
|
|
348
|
+
(apiClient.getAccount as jest.Mock).mockResolvedValueOnce({
|
|
349
|
+
account: accountId,
|
|
350
|
+
max_automatic_token_associations: 0,
|
|
351
|
+
balance: {
|
|
352
|
+
balance: 1,
|
|
353
|
+
timestamp: "",
|
|
354
|
+
tokens: [{ token_id: "0.0.9999", balance: 1 }],
|
|
355
|
+
},
|
|
356
|
+
});
|
|
357
|
+
|
|
358
|
+
await checkAccountTokenAssociationStatus(addressWithChecksum, tokenId);
|
|
359
|
+
expect(apiClient.getAccount).toHaveBeenCalledTimes(1);
|
|
360
|
+
expect(apiClient.getAccount).toHaveBeenCalledWith("0.0.9124531");
|
|
361
|
+
});
|
|
362
|
+
});
|
|
363
|
+
|
|
364
|
+
describe("safeParseAccountId", () => {
|
|
365
|
+
test("returns account id and no checksum for valid address without checksum", () => {
|
|
366
|
+
const [error, result] = safeParseAccountId("0.0.9124531");
|
|
367
|
+
|
|
368
|
+
expect(error).toBeNull();
|
|
369
|
+
expect(result?.accountId).toBe("0.0.9124531");
|
|
370
|
+
expect(result?.checksum).toBeNull();
|
|
371
|
+
});
|
|
372
|
+
|
|
373
|
+
test("returns account id and checksum for valid address with correct checksum", () => {
|
|
374
|
+
const [error, result] = safeParseAccountId("0.0.9124531-xrxlv");
|
|
375
|
+
|
|
376
|
+
expect(error).toBeNull();
|
|
377
|
+
expect(result?.accountId).toBe("0.0.9124531");
|
|
378
|
+
expect(result?.checksum).toBe("xrxlv");
|
|
379
|
+
});
|
|
380
|
+
|
|
381
|
+
test("returns error for valid address with incorrect checksum", () => {
|
|
382
|
+
const [error, accountId] = safeParseAccountId("0.0.9124531-invld");
|
|
383
|
+
|
|
384
|
+
expect(error).toBeInstanceOf(HederaRecipientInvalidChecksum);
|
|
385
|
+
expect(accountId).toBeNull();
|
|
386
|
+
});
|
|
387
|
+
|
|
388
|
+
test("returns error for invalid address format", () => {
|
|
389
|
+
const [error, accountId] = safeParseAccountId("not-a-valid-address");
|
|
390
|
+
|
|
391
|
+
expect(error).toBeInstanceOf(InvalidAddress);
|
|
392
|
+
expect(accountId).toBeNull();
|
|
393
|
+
});
|
|
394
|
+
});
|
|
395
|
+
|
|
396
|
+
describe("getSyntheticBlock", () => {
|
|
397
|
+
it("calculates correct blockHeight and blockHash for typical timestamp, with default block window", () => {
|
|
398
|
+
const consensusTimestamp = "1760523159.854347000";
|
|
399
|
+
const blockWindowSeconds = SYNTHETIC_BLOCK_WINDOW_SECONDS;
|
|
400
|
+
const expectedSeconds = Math.floor(Number(consensusTimestamp));
|
|
401
|
+
const expectedBlockHeight = Math.floor(expectedSeconds / blockWindowSeconds);
|
|
402
|
+
const expectedBlockHash = createHash("sha256")
|
|
403
|
+
.update(expectedBlockHeight.toString())
|
|
404
|
+
.digest("hex");
|
|
405
|
+
|
|
406
|
+
const result = getSyntheticBlock(consensusTimestamp);
|
|
407
|
+
|
|
408
|
+
expect(result.blockHeight).toBe(expectedBlockHeight);
|
|
409
|
+
expect(result.blockHash).toBe(expectedBlockHash);
|
|
410
|
+
});
|
|
411
|
+
|
|
412
|
+
it("supports custom blockWindowSeconds", () => {
|
|
413
|
+
const consensusTimestamp = "1760523159.854347000";
|
|
414
|
+
const blockWindowSeconds = 3600;
|
|
415
|
+
const expectedSeconds = Math.floor(Number(consensusTimestamp));
|
|
416
|
+
const expectedBlockHeight = Math.floor(expectedSeconds / blockWindowSeconds);
|
|
417
|
+
const expectedBlockHash = createHash("sha256")
|
|
418
|
+
.update(expectedBlockHeight.toString())
|
|
419
|
+
.digest("hex");
|
|
420
|
+
|
|
421
|
+
const result = getSyntheticBlock(consensusTimestamp, blockWindowSeconds);
|
|
422
|
+
|
|
423
|
+
expect(result.blockHeight).toBe(expectedBlockHeight);
|
|
424
|
+
expect(result.blockHash).toBe(expectedBlockHash);
|
|
425
|
+
});
|
|
426
|
+
|
|
427
|
+
it("throws error for invalid consensusTimestamp", () => {
|
|
428
|
+
expect(() => getSyntheticBlock("not_a_number")).toThrow();
|
|
429
|
+
expect(() => getSyntheticBlock("")).toThrow();
|
|
430
|
+
});
|
|
431
|
+
});
|
|
432
|
+
});
|
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
import BigNumber from "bignumber.js";
|
|
2
|
+
import { createHash } from "crypto";
|
|
3
|
+
import invariant from "invariant";
|
|
4
|
+
import { AccountId, Transaction as HederaSDKTransaction } from "@hashgraph/sdk";
|
|
5
|
+
import { AssetInfo, TransactionIntent } from "@ledgerhq/coin-framework/api/types";
|
|
6
|
+
import { findCryptoCurrencyById } from "@ledgerhq/cryptoassets/currencies";
|
|
7
|
+
import { getFiatCurrencyByTicker } from "@ledgerhq/cryptoassets/fiats";
|
|
8
|
+
import cvsApi from "@ledgerhq/live-countervalues/api/index";
|
|
9
|
+
import { InvalidAddress } from "@ledgerhq/errors";
|
|
10
|
+
import { makeLRUCache, seconds } from "@ledgerhq/live-network/cache";
|
|
11
|
+
import type { Currency, ExplorerView, TokenCurrency } from "@ledgerhq/types-cryptoassets";
|
|
12
|
+
import type { AccountLike, Operation as LiveOperation } from "@ledgerhq/types-live";
|
|
13
|
+
import {
|
|
14
|
+
HEDERA_OPERATION_TYPES,
|
|
15
|
+
HEDERA_TRANSACTION_MODES,
|
|
16
|
+
SYNTHETIC_BLOCK_WINDOW_SECONDS,
|
|
17
|
+
} from "../constants";
|
|
18
|
+
import { apiClient } from "../network/api";
|
|
19
|
+
import type {
|
|
20
|
+
HederaAccount,
|
|
21
|
+
HederaOperationExtra,
|
|
22
|
+
Transaction,
|
|
23
|
+
TransactionStatus,
|
|
24
|
+
TransactionTokenAssociate,
|
|
25
|
+
} from "../types";
|
|
26
|
+
import { rpcClient } from "../network/rpc";
|
|
27
|
+
import { HederaRecipientInvalidChecksum } from "../errors";
|
|
28
|
+
|
|
29
|
+
export const serializeSignature = (signature: Uint8Array) => {
|
|
30
|
+
return Buffer.from(signature).toString("base64");
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export const deserializeSignature = (signature: string) => {
|
|
34
|
+
return Buffer.from(signature, "base64");
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
export const serializeTransaction = (tx: HederaSDKTransaction) => {
|
|
38
|
+
return Buffer.from(tx.toBytes()).toString("hex");
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
export const deserializeTransaction = (tx: string) => {
|
|
42
|
+
return HederaSDKTransaction.fromBytes(Buffer.from(tx, "hex"));
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
export const getOperationValue = ({
|
|
46
|
+
asset,
|
|
47
|
+
operation,
|
|
48
|
+
}: {
|
|
49
|
+
asset: AssetInfo;
|
|
50
|
+
operation: LiveOperation<HederaOperationExtra>;
|
|
51
|
+
}) => {
|
|
52
|
+
if (operation.type === "FEES") {
|
|
53
|
+
return BigInt(0);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (asset.type === "native" && operation.type === "OUT") {
|
|
57
|
+
return BigInt(operation.value.toFixed(0)) - BigInt(operation.fee.toFixed(0));
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return BigInt(operation.value.toFixed(0));
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
// this utils extracts the bodyBytes from a Hedera Transaction that are required for signing
|
|
64
|
+
// hardcoded `.get(0)` is here because we are always using single node account id
|
|
65
|
+
// this is because we want to avoid "signing" loop for users, as described here:
|
|
66
|
+
// https://github.com/LedgerHQ/ledger-live/pull/72/commits/1e942687d4301660e43e0c4b5419fcfa2733b290
|
|
67
|
+
export const getHederaTransactionBodyBytes = (tx: HederaSDKTransaction) => {
|
|
68
|
+
const bodyBytes = tx._signedTransactions.get(0)?.bodyBytes;
|
|
69
|
+
invariant(bodyBytes, "hedera: tx body bytes are missing");
|
|
70
|
+
return bodyBytes;
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
export const mapIntentToSDKOperation = (txIntent: TransactionIntent) => {
|
|
74
|
+
if (txIntent.type === HEDERA_TRANSACTION_MODES.TokenAssociate) {
|
|
75
|
+
return HEDERA_OPERATION_TYPES.TokenAssociate;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (txIntent.type === HEDERA_TRANSACTION_MODES.Send && txIntent.asset.type !== "native") {
|
|
79
|
+
return HEDERA_OPERATION_TYPES.TokenTransfer;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return HEDERA_OPERATION_TYPES.CryptoTransfer;
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
export const getMemoFromBase64 = (memoBase64: string | undefined): string | null => {
|
|
86
|
+
try {
|
|
87
|
+
if (memoBase64 === undefined) return null;
|
|
88
|
+
|
|
89
|
+
return Buffer.from(memoBase64, "base64").toString("utf-8");
|
|
90
|
+
} catch {
|
|
91
|
+
return null;
|
|
92
|
+
}
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
// NOTE: convert from the non-url-safe version of base64 to the url-safe version (that the explorer uses)
|
|
96
|
+
export function base64ToUrlSafeBase64(data: string): string {
|
|
97
|
+
// Might be nice to use this alternative if .nvmrc changes to >= Node v14.18.0
|
|
98
|
+
// base64url encoding option isn't supported until then
|
|
99
|
+
// Buffer.from(data, "base64").toString("base64url");
|
|
100
|
+
|
|
101
|
+
return data.replace(/\//g, "_").replace(/\+/g, "-");
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export const getTransactionExplorer = (
|
|
105
|
+
explorerView: ExplorerView | null | undefined,
|
|
106
|
+
operation: LiveOperation,
|
|
107
|
+
): string | undefined => {
|
|
108
|
+
const extra = isValidExtra(operation.extra) ? operation.extra : null;
|
|
109
|
+
|
|
110
|
+
return explorerView?.tx?.replace(
|
|
111
|
+
"$hash",
|
|
112
|
+
extra?.consensusTimestamp ?? extra?.transactionId ?? "0",
|
|
113
|
+
);
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
export const isTokenAssociateTransaction = (tx: Transaction): tx is TransactionTokenAssociate => {
|
|
117
|
+
return tx.mode === HEDERA_TRANSACTION_MODES.TokenAssociate;
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
export const isAutoTokenAssociationEnabled = (account: AccountLike) => {
|
|
121
|
+
const hederaAccount = "hederaResources" in account ? (account as HederaAccount) : null;
|
|
122
|
+
|
|
123
|
+
return hederaAccount?.hederaResources?.isAutoTokenAssociationEnabled ?? false;
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
export const isTokenAssociationRequired = (
|
|
127
|
+
account: AccountLike,
|
|
128
|
+
token: TokenCurrency | null | undefined,
|
|
129
|
+
) => {
|
|
130
|
+
const subAccounts = !!account && "subAccounts" in account ? account.subAccounts ?? [] : [];
|
|
131
|
+
const isTokenAssociated = subAccounts.some(item => item.token.id === token?.id);
|
|
132
|
+
|
|
133
|
+
return !!token && !isTokenAssociated && !isAutoTokenAssociationEnabled(account);
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
export const isValidExtra = (extra: unknown): extra is HederaOperationExtra => {
|
|
137
|
+
return !!extra && typeof extra === "object" && !Array.isArray(extra);
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
// disables the "Continue" button in the Send modal's Recipient step during token transfers if:
|
|
141
|
+
// - the recipient is not associated with the token
|
|
142
|
+
// - the association status can't be verified
|
|
143
|
+
export const sendRecipientCanNext = (status: TransactionStatus) => {
|
|
144
|
+
const { missingAssociation, unverifiedAssociation } = status.warnings;
|
|
145
|
+
|
|
146
|
+
return !missingAssociation && !unverifiedAssociation;
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
// note: this is currently called frequently by getTransactionStatus; LRU cache prevents duplicated requests
|
|
150
|
+
export const getCurrencyToUSDRate = makeLRUCache(
|
|
151
|
+
async (currency: Currency) => {
|
|
152
|
+
try {
|
|
153
|
+
const [rate] = await cvsApi.fetchLatest([
|
|
154
|
+
{
|
|
155
|
+
from: currency,
|
|
156
|
+
to: getFiatCurrencyByTicker("USD"),
|
|
157
|
+
startDate: new Date(),
|
|
158
|
+
},
|
|
159
|
+
]);
|
|
160
|
+
|
|
161
|
+
invariant(rate, "no value returned from cvs api");
|
|
162
|
+
|
|
163
|
+
return new BigNumber(rate);
|
|
164
|
+
} catch {
|
|
165
|
+
return null;
|
|
166
|
+
}
|
|
167
|
+
},
|
|
168
|
+
currency => currency.ticker,
|
|
169
|
+
seconds(3),
|
|
170
|
+
);
|
|
171
|
+
|
|
172
|
+
export const checkAccountTokenAssociationStatus = makeLRUCache(
|
|
173
|
+
async (address: string, tokenId: string) => {
|
|
174
|
+
const [parsingError, parsingResult] = safeParseAccountId(address);
|
|
175
|
+
|
|
176
|
+
if (parsingError) {
|
|
177
|
+
throw parsingError;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
const addressWithoutChecksum = parsingResult.accountId;
|
|
181
|
+
const mirrorAccount = await apiClient.getAccount(addressWithoutChecksum);
|
|
182
|
+
|
|
183
|
+
// auto association is enabled
|
|
184
|
+
if (mirrorAccount.max_automatic_token_associations === -1) {
|
|
185
|
+
return true;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
const isTokenAssociated = mirrorAccount.balance.tokens.some(token => {
|
|
189
|
+
return token.token_id === tokenId;
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
return isTokenAssociated;
|
|
193
|
+
},
|
|
194
|
+
(accountId, tokenId) => `${accountId}-${tokenId}`,
|
|
195
|
+
seconds(30),
|
|
196
|
+
);
|
|
197
|
+
|
|
198
|
+
export const safeParseAccountId = (
|
|
199
|
+
address: string,
|
|
200
|
+
): [Error, null] | [null, { accountId: string; checksum: string | null }] => {
|
|
201
|
+
const currency = findCryptoCurrencyById("hedera");
|
|
202
|
+
const currencyName = currency?.name ?? "Hedera";
|
|
203
|
+
|
|
204
|
+
try {
|
|
205
|
+
const accountId = AccountId.fromString(address);
|
|
206
|
+
|
|
207
|
+
// verify checksum if present
|
|
208
|
+
// FIXME: migrate to EntityIdHelper methods once SDK is upgraded:
|
|
209
|
+
// https://github.com/hiero-ledger/hiero-sdk-js/blob/main/src/EntityIdHelper.js#L197
|
|
210
|
+
// https://github.com/hiero-ledger/hiero-sdk-js/blob/main/src/EntityIdHelper.js#L446
|
|
211
|
+
const checksum: string | null = address.split("-")[1] ?? null;
|
|
212
|
+
if (checksum) {
|
|
213
|
+
const client = rpcClient.getInstance();
|
|
214
|
+
const recipientWithChecksum = accountId.toStringWithChecksum(client);
|
|
215
|
+
const expectedChecksum = recipientWithChecksum.split("-")[1];
|
|
216
|
+
|
|
217
|
+
if (checksum !== expectedChecksum) {
|
|
218
|
+
return [new HederaRecipientInvalidChecksum(), null];
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
const result = {
|
|
223
|
+
accountId: accountId.toString(),
|
|
224
|
+
checksum,
|
|
225
|
+
};
|
|
226
|
+
|
|
227
|
+
return [null, result];
|
|
228
|
+
} catch (err) {
|
|
229
|
+
return [new InvalidAddress("", { currencyName }), null];
|
|
230
|
+
}
|
|
231
|
+
};
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* Calculates a synthetic block height based on Hedera consensus timestamp.
|
|
235
|
+
*
|
|
236
|
+
* @param consensusTimestamp - Hedera consensus timestamp
|
|
237
|
+
* @param blockWindowSeconds - Duration of one synthetic block in seconds (default: 10)
|
|
238
|
+
* @returns Deterministic synthetic block (height and hash)
|
|
239
|
+
*/
|
|
240
|
+
export function getSyntheticBlock(
|
|
241
|
+
consensusTimestamp: string,
|
|
242
|
+
blockWindowSeconds = SYNTHETIC_BLOCK_WINDOW_SECONDS,
|
|
243
|
+
) {
|
|
244
|
+
const seconds = Math.floor(Number(consensusTimestamp));
|
|
245
|
+
|
|
246
|
+
if (Number.isNaN(seconds) || seconds === 0) {
|
|
247
|
+
throw new Error(`Invalid consensus timestamp: ${consensusTimestamp}`);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
const blockHeight = Math.floor(seconds / blockWindowSeconds);
|
|
251
|
+
const blockHash = createHash("sha256").update(blockHeight.toString()).digest("hex");
|
|
252
|
+
const blockTime = new Date(seconds * 1000);
|
|
253
|
+
|
|
254
|
+
return { blockHeight, blockHash, blockTime };
|
|
255
|
+
}
|