From 41afb5363675b7840ce58ea3babdf47276430c0b Mon Sep 17 00:00:00 2001 From: Davide Carpini Date: Thu, 2 Mar 2023 12:36:32 +0100 Subject: [PATCH 001/126] feat: vet and vethor integration --- apps/cli/src/live-common-setup-base.ts | 1 + .../live-common-set-supported-currencies.js | 1 + .../ledger-live-desktop/src/sentry/install.js | 1 + .../static/i18n/el/app.json | 5 +- apps/ledger-live-mobile/index.js | 1 + apps/ledger-live-mobile/src/config/urls.tsx | 2 + .../src/live-common-setup.ts | 1 + .../src/locales/en/common.json | 5 + libs/coin-framework/src/account/helpers.ts | 5 + libs/coin-framework/src/derivation.ts | 6 + libs/coin-framework/src/env.ts | 5 + libs/ledger-live-common/package.json | 4 +- .../ledger-live-common/src/account/helpers.ts | 1 - .../sortByMarketcap.test.ts.snap | 1 + .../src/data/icons/svg/VET.svg | 10 +- .../src/data/icons/svg/VTHO.svg | 10 +- .../bridge.integration.testWIP.ts.snapWIP | 1890 +++++++++++++++++ .../src/families/vechain/account.ts | 49 + .../src/families/vechain/api/index.ts | 3 + .../src/families/vechain/api/sdk.ts | 141 ++ .../src/families/vechain/api/types.ts | 81 + .../vechain/bridge.integration.testWIP | 213 ++ .../src/families/vechain/bridge/js.ts | 66 + .../src/families/vechain/cli-transaction.ts | 62 + .../src/families/vechain/constants.ts | 5 + .../families/vechain/contracts/abis/VIP180.ts | 265 +++ .../families/vechain/contracts/abis/params.ts | 70 + .../families/vechain/contracts/constants.ts | 11 + .../datasets/vechain.scanAccounts.1.ts | 19 + .../src/families/vechain/datasets/vechain.ts | 72 + .../src/families/vechain/hw-getAddress.ts | 20 + .../src/families/vechain/js-broadcast.ts | 22 + .../vechain/js-getTransactionStatus.ts | 74 + .../src/families/vechain/js-signOperation.ts | 97 + .../families/vechain/js-synchronisation.ts | 91 + .../src/families/vechain/js-transaction.ts | 139 ++ .../src/families/vechain/mock.ts | 14 + .../src/families/vechain/specs.ts | 62 + .../vechain/speculos-deviceActions.ts | 43 + .../src/families/vechain/transaction.ts | 67 + .../src/families/vechain/types.ts | 41 + .../families/vechain/utils/address-utils.ts | 11 + .../vechain/utils/calculateTransactionInfo.ts | 52 + .../src/families/vechain/utils/hex-utils.ts | 55 + .../families/vechain/utils/mapping-utils.ts | 80 + .../src/families/vechain/utils/pad-address.ts | 8 + .../vechain/utils/transaction-utils.ts | 104 + .../src/generated/account.ts | 2 + .../src/generated/bridge/js.ts | 2 + .../src/generated/cli-transaction.ts | 2 + .../src/generated/hw-getAddress.ts | 2 + libs/ledger-live-common/src/generated/mock.ts | 2 + .../ledger-live-common/src/generated/specs.ts | 2 + .../src/generated/transaction.ts | 2 + .../ledger-live-common/src/generated/types.ts | 16 +- .../packages/cryptoassets/src/currencies.ts | 12 +- .../packages/cryptoassets/src/data/vip180.ts | 7 + .../packages/cryptoassets/src/tokens.ts | 29 + libs/ledgerjs/packages/hw-app-vet/README.md | 24 + .../ledgerjs/packages/hw-app-vet/package.json | 45 + libs/ledgerjs/packages/hw-app-vet/src/Vet.ts | 139 ++ .../packages/hw-app-vet/src/constants.ts | 32 + .../packages/hw-app-vet/src/errors.ts | 4 + .../ledgerjs/packages/hw-app-vet/src/model.ts | 5 + .../ledgerjs/packages/hw-app-vet/src/utils.ts | 54 + .../packages/hw-app-vet/tsconfig.json | 8 + pnpm-lock.yaml | 362 ++-- 67 files changed, 4522 insertions(+), 215 deletions(-) create mode 100644 libs/ledger-live-common/src/families/vechain/__snapshots__/bridge.integration.testWIP.ts.snapWIP create mode 100644 libs/ledger-live-common/src/families/vechain/account.ts create mode 100644 libs/ledger-live-common/src/families/vechain/api/index.ts create mode 100644 libs/ledger-live-common/src/families/vechain/api/sdk.ts create mode 100644 libs/ledger-live-common/src/families/vechain/api/types.ts create mode 100644 libs/ledger-live-common/src/families/vechain/bridge.integration.testWIP create mode 100644 libs/ledger-live-common/src/families/vechain/bridge/js.ts create mode 100644 libs/ledger-live-common/src/families/vechain/cli-transaction.ts create mode 100644 libs/ledger-live-common/src/families/vechain/constants.ts create mode 100644 libs/ledger-live-common/src/families/vechain/contracts/abis/VIP180.ts create mode 100644 libs/ledger-live-common/src/families/vechain/contracts/abis/params.ts create mode 100644 libs/ledger-live-common/src/families/vechain/contracts/constants.ts create mode 100644 libs/ledger-live-common/src/families/vechain/datasets/vechain.scanAccounts.1.ts create mode 100644 libs/ledger-live-common/src/families/vechain/datasets/vechain.ts create mode 100644 libs/ledger-live-common/src/families/vechain/hw-getAddress.ts create mode 100644 libs/ledger-live-common/src/families/vechain/js-broadcast.ts create mode 100644 libs/ledger-live-common/src/families/vechain/js-getTransactionStatus.ts create mode 100644 libs/ledger-live-common/src/families/vechain/js-signOperation.ts create mode 100644 libs/ledger-live-common/src/families/vechain/js-synchronisation.ts create mode 100644 libs/ledger-live-common/src/families/vechain/js-transaction.ts create mode 100644 libs/ledger-live-common/src/families/vechain/mock.ts create mode 100644 libs/ledger-live-common/src/families/vechain/specs.ts create mode 100644 libs/ledger-live-common/src/families/vechain/speculos-deviceActions.ts create mode 100644 libs/ledger-live-common/src/families/vechain/transaction.ts create mode 100644 libs/ledger-live-common/src/families/vechain/types.ts create mode 100644 libs/ledger-live-common/src/families/vechain/utils/address-utils.ts create mode 100644 libs/ledger-live-common/src/families/vechain/utils/calculateTransactionInfo.ts create mode 100644 libs/ledger-live-common/src/families/vechain/utils/hex-utils.ts create mode 100644 libs/ledger-live-common/src/families/vechain/utils/mapping-utils.ts create mode 100644 libs/ledger-live-common/src/families/vechain/utils/pad-address.ts create mode 100644 libs/ledger-live-common/src/families/vechain/utils/transaction-utils.ts create mode 100644 libs/ledgerjs/packages/cryptoassets/src/data/vip180.ts create mode 100644 libs/ledgerjs/packages/hw-app-vet/README.md create mode 100644 libs/ledgerjs/packages/hw-app-vet/package.json create mode 100644 libs/ledgerjs/packages/hw-app-vet/src/Vet.ts create mode 100644 libs/ledgerjs/packages/hw-app-vet/src/constants.ts create mode 100644 libs/ledgerjs/packages/hw-app-vet/src/errors.ts create mode 100644 libs/ledgerjs/packages/hw-app-vet/src/model.ts create mode 100644 libs/ledgerjs/packages/hw-app-vet/src/utils.ts create mode 100644 libs/ledgerjs/packages/hw-app-vet/tsconfig.json diff --git a/apps/cli/src/live-common-setup-base.ts b/apps/cli/src/live-common-setup-base.ts index f180e9e81bea..7c1c77f950f5 100644 --- a/apps/cli/src/live-common-setup-base.ts +++ b/apps/cli/src/live-common-setup-base.ts @@ -58,6 +58,7 @@ setSupportedCurrencies([ "songbird", "flare", "near", + "vechain", ]); for (const k in process.env) setEnvUnsafe(k as EnvName, process.env[k]); diff --git a/apps/ledger-live-desktop/src/live-common-set-supported-currencies.js b/apps/ledger-live-desktop/src/live-common-set-supported-currencies.js index 9b66e919e38c..24f76b08aa24 100644 --- a/apps/ledger-live-desktop/src/live-common-set-supported-currencies.js +++ b/apps/ledger-live-desktop/src/live-common-set-supported-currencies.js @@ -54,4 +54,5 @@ setSupportedCurrencies([ "songbird", "flare", "near", + "vechain", ]); diff --git a/apps/ledger-live-desktop/src/sentry/install.js b/apps/ledger-live-desktop/src/sentry/install.js index 3058590ab8ef..a1ea49becb25 100644 --- a/apps/ledger-live-desktop/src/sentry/install.js +++ b/apps/ledger-live-desktop/src/sentry/install.js @@ -77,6 +77,7 @@ const ignoreErrors = [ "DisconnectedDevice", "DisconnectedDeviceDuringOperation", "EthAppPleaseEnableContractData", + "VechainAppPleaseEnableContractDataAndMultiClause", "failed with status code", "GetAppAndVersionUnsupportedFormat", "Invalid channel", diff --git a/apps/ledger-live-desktop/static/i18n/el/app.json b/apps/ledger-live-desktop/static/i18n/el/app.json index df2f81a1de16..e6293c0b6bc3 100644 --- a/apps/ledger-live-desktop/static/i18n/el/app.json +++ b/apps/ledger-live-desktop/static/i18n/el/app.json @@ -4229,6 +4229,9 @@ "EthAppPleaseEnableContractData": { "title": "Please enable contract data in the Ethereum app settings" }, + "VechainAppPleaseEnableContractDataAndMultiClause": { + "title": "Please enable contract data in the Vechain app settings" + }, "InvalidXRPTag": { "title": "Invalid XRP Destination tag" }, @@ -4337,4 +4340,4 @@ "memoPlaceholder": "Optional", "memoWarningText": "When using a memo, carefully check the information with the recipient" } -} +} \ No newline at end of file diff --git a/apps/ledger-live-mobile/index.js b/apps/ledger-live-mobile/index.js index 66c720b7a280..f0e0166fe85c 100644 --- a/apps/ledger-live-mobile/index.js +++ b/apps/ledger-live-mobile/index.js @@ -51,6 +51,7 @@ const excludedErrorName = [ // bad usage of device "BleError", "EthAppPleaseEnableContractData", + "VechainAppPleaseEnableContractDataAndMultiClause", "CantOpenDevice", "DisconnectedDevice", "DisconnectedDeviceDuringOperation", diff --git a/apps/ledger-live-mobile/src/config/urls.tsx b/apps/ledger-live-mobile/src/config/urls.tsx index ce7058ce12a3..e3d48a865a12 100644 --- a/apps/ledger-live-mobile/src/config/urls.tsx +++ b/apps/ledger-live-mobile/src/config/urls.tsx @@ -80,6 +80,8 @@ export const urls = { asa: "https://support.ledger.com/hc/en-us/articles/360015896040?utm_source=ledger_live_mobile&utm_medium=self_referral&utm_content=receive_account_flow", bep20: "https://support.ledger.com/hc/en-us/articles/4412962166289-Manage-BEP20-tokens?utm_source=ledger_live_mobile&utm_medium=self_referral&utm_content=receive_account_flow_bep20", + vip180: + "https://support.ledger.com/hc/en-us/articles/360007655934-VeChain-VET-?support=true", }, celoStakingRewards: "https://support.ledger.com/hc/en-us/articles/360020499920-Celo-CELO-?utm_source=ledger_live_mobile&utm_medium=self_referral&utm_content=celo&docs=true", diff --git a/apps/ledger-live-mobile/src/live-common-setup.ts b/apps/ledger-live-mobile/src/live-common-setup.ts index c93f8d8bd008..9fe0af303101 100644 --- a/apps/ledger-live-mobile/src/live-common-setup.ts +++ b/apps/ledger-live-mobile/src/live-common-setup.ts @@ -79,6 +79,7 @@ setSupportedCurrencies([ "songbird", "flare", "near", + "vechain", ]); if (Config.VERBOSE) { diff --git a/apps/ledger-live-mobile/src/locales/en/common.json b/apps/ledger-live-mobile/src/locales/en/common.json index 627ea8116071..cab3e6d3ef79 100644 --- a/apps/ledger-live-mobile/src/locales/en/common.json +++ b/apps/ledger-live-mobile/src/locales/en/common.json @@ -3624,6 +3624,11 @@ "title": "Add Token", "disclaimer": "{{tokenName}} is a BEP-20 Token.\nYou can receive tokens directly in a BNB account.", "learnMore": "Learn more about BEP-20" + }, + "vip180": { + "title": "Add token", + "disclaimer": "{{tokenName}} is a VIP180 Token.\nYou can receive tokens directly in a VET account.", + "learnMore": "Learn more about VIP180" } }, "showMoreChainType": "More address types", diff --git a/libs/coin-framework/src/account/helpers.ts b/libs/coin-framework/src/account/helpers.ts index e87d458103ff..101db8cba09c 100644 --- a/libs/coin-framework/src/account/helpers.ts +++ b/libs/coin-framework/src/account/helpers.ts @@ -92,6 +92,11 @@ export const getAccountSpendableBalance = (account: AccountLike): BigNumber => { }; export const isAccountEmpty = (a: AccountLike): boolean => { + if (a.type == "Account" && a.currency.family == "vechain") { + const checkSubAccounts = + a.subAccounts && a.subAccounts[0].balance.toString() == "0" ? 0 : 1; + return a.operationsCount === 0 && a.balance.isZero() && !checkSubAccounts; + } const hasSubAccounts = a.type === "Account" && a.subAccounts && a.subAccounts.length; return a.operationsCount === 0 && a.balance.isZero() && !hasSubAccounts; diff --git a/libs/coin-framework/src/derivation.ts b/libs/coin-framework/src/derivation.ts index 318b3037aba2..291bfeb28be3 100644 --- a/libs/coin-framework/src/derivation.ts +++ b/libs/coin-framework/src/derivation.ts @@ -201,6 +201,9 @@ const modes = Object.freeze({ overridesDerivation: "44'/397'/0'/0'/'", mandatoryEmptyAccountSkip: 1, }, + vechain: { + overridesDerivation: "44'/818'/0'/0/", + }, }); modes as Record; // eslint-disable-line @@ -220,6 +223,7 @@ const legacyDerivations: Record = { cardano: ["cardano"], cardano_testnet: ["cardano"], near: ["nearbip44h"], + vechain: ["vechain"], }; const legacyDerivationsPerFamily: Record = { @@ -383,6 +387,7 @@ const disableBIP44: Record = { cardano: true, cardano_testnet: true, near: true, + vechain: true, }; type SeedInfo = { purpose: number; @@ -397,6 +402,7 @@ const seedIdentifierPath: Record = { cardano: ({ purpose, coinType }) => `${purpose}'/${coinType}'/0'/0/0`, cardano_testnet: ({ purpose, coinType }) => `${purpose}'/${coinType}'/0'/0/0`, near: ({ purpose, coinType }) => `${purpose}'/${coinType}'/0'/0'/0'`, + vechain: ({ purpose, coinType }) => `${purpose}'/${coinType}'/0'/0/0`, _: ({ purpose, coinType }) => `${purpose}'/${coinType}'/0'`, }; export const getSeedIdentifierDerivation = ( diff --git a/libs/coin-framework/src/env.ts b/libs/coin-framework/src/env.ts index a0421b904d93..051f56f3b9fa 100644 --- a/libs/coin-framework/src/env.ts +++ b/libs/coin-framework/src/env.ts @@ -201,6 +201,11 @@ const envDefinitions: Record< parser: stringParser, desc: "mirror node API for Hedera", }, + API_VECHAIN_THOREST: { + def: "https://testnet.node.dev-vechain.org", + parser: stringParser, + desc: "Thorest API for VeChain", + }, BASE_SOCKET_URL: { def: "wss://scriptrunner.api.live.ledger.com/update", parser: stringParser, diff --git a/libs/ledger-live-common/package.json b/libs/ledger-live-common/package.json index b00138147671..6f65cf571fa2 100644 --- a/libs/ledger-live-common/package.json +++ b/libs/ledger-live-common/package.json @@ -146,6 +146,7 @@ "@ledgerhq/hw-app-str": "workspace:^", "@ledgerhq/hw-app-tezos": "workspace:^", "@ledgerhq/hw-app-trx": "workspace:^", + "@ledgerhq/hw-app-vet": "workspace:^", "@ledgerhq/hw-app-xrp": "workspace:^", "@ledgerhq/hw-transport": "workspace:^", "@ledgerhq/hw-transport-mocker": "workspace:^", @@ -232,6 +233,7 @@ "source-map-support": "^0.5.21", "stellar-sdk": "^10.1.1", "superstruct": "0.14.2", + "thor-devkit": "^2.0.6", "tiny-secp256k1": "^1.1.6", "triple-beam": "^1.3.0", "utility-types": "^3.10.0", @@ -294,4 +296,4 @@ "uuid": "^8.3.2", "ws": "7" } -} +} \ No newline at end of file diff --git a/libs/ledger-live-common/src/account/helpers.ts b/libs/ledger-live-common/src/account/helpers.ts index 8ebb16bd9b68..987a55dfa624 100644 --- a/libs/ledger-live-common/src/account/helpers.ts +++ b/libs/ledger-live-common/src/account/helpers.ts @@ -68,7 +68,6 @@ export const isAccountEmpty = (a: AccountLike): boolean => { tronAcc.tronResources && tronAcc.tronResources.bandwidth.freeLimit.eq(0) ); } - return commonIsAccountEmpty(a); }; diff --git a/libs/ledger-live-common/src/currencies/__snapshots__/sortByMarketcap.test.ts.snap b/libs/ledger-live-common/src/currencies/__snapshots__/sortByMarketcap.test.ts.snap index cb086059eb27..1938be23286d 100644 --- a/libs/ledger-live-common/src/currencies/__snapshots__/sortByMarketcap.test.ts.snap +++ b/libs/ledger-live-common/src/currencies/__snapshots__/sortByMarketcap.test.ts.snap @@ -1163,6 +1163,7 @@ Array [ "ethereum/erc20/btse_token", "ethereum/erc20/quras_token", "ethereum/erc20/kok_coin", + "vechain/vtho", "ethereum/erc20/chronic_token", "ethereum/erc20/litecoin_token", "ethereum/erc20/mt_token", diff --git a/libs/ledger-live-common/src/data/icons/svg/VET.svg b/libs/ledger-live-common/src/data/icons/svg/VET.svg index f687f62d6401..ed2e0e026f8f 100644 --- a/libs/ledger-live-common/src/data/icons/svg/VET.svg +++ b/libs/ledger-live-common/src/data/icons/svg/VET.svg @@ -1,3 +1,9 @@ - - + + diff --git a/libs/ledger-live-common/src/data/icons/svg/VTHO.svg b/libs/ledger-live-common/src/data/icons/svg/VTHO.svg index fb1b4bcfc767..6212d0e3a1d7 100644 --- a/libs/ledger-live-common/src/data/icons/svg/VTHO.svg +++ b/libs/ledger-live-common/src/data/icons/svg/VTHO.svg @@ -1,4 +1,8 @@ - - - + + + \ No newline at end of file diff --git a/libs/ledger-live-common/src/families/vechain/__snapshots__/bridge.integration.testWIP.ts.snapWIP b/libs/ledger-live-common/src/families/vechain/__snapshots__/bridge.integration.testWIP.ts.snapWIP new file mode 100644 index 000000000000..4d123af433d1 --- /dev/null +++ b/libs/ledger-live-common/src/families/vechain/__snapshots__/bridge.integration.testWIP.ts.snapWIP @@ -0,0 +1,1890 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`vechain currency bridge scanAccounts vechain seed 1 1`] = ` +Array [ + Object { + "currencyId": "vechain", + "derivationMode": "vechain", + "freshAddress": "0x32476193d4a32488322ffBb9835a7cF2c7e2202C", + "freshAddressPath": "44'/818'/0'/0/0", + "freshAddresses": Array [ + Object { + "address": "0x32476193d4a32488322ffBb9835a7cF2c7e2202C", + "derivationPath": "44'/818'/0'/0/0", + }, + ], + "id": "js:2:vechain:0x32476193d4a32488322ffBb9835a7cF2c7e2202C:vechain", + "index": 0, + "name": "VeChain 1", + "nfts": undefined, + "operationsCount": 28, + "pendingOperations": Array [], + "seedIdentifier": "0482c44bc99cdf08d4360c97fbf62d387288ab75c576926943ad90059002720e93f58799391393c98ad41136aa4ac871b103d25cb9a88f1aadd7dbbe3c77948889", + "starred": false, + "subAccounts": Array [], + "swapHistory": Array [], + "syncHash": undefined, + "unitMagnitude": 18, + "used": true, + }, + Object { + "approvals": undefined, + "compoundBalance": undefined, + "id": "js:2:vechain:0x32476193d4a32488322ffBb9835a7cF2c7e2202C:vechain+vechain%2Fvtho", + "operationsCount": 19, + "parentId": "js:2:vechain:0x32476193d4a32488322ffBb9835a7cF2c7e2202C:vechain", + "pendingOperations": Array [], + "starred": false, + "swapHistory": Array [], + "tokenId": "vechain/vtho", + "type": "TokenAccountRaw", + }, + Object { + "currencyId": "vechain", + "derivationMode": "vechain", + "freshAddress": "0xC09f17E9e35BCa0De7539A4372FC37D941736717", + "freshAddressPath": "44'/818'/0'/0/1", + "freshAddresses": Array [ + Object { + "address": "0xC09f17E9e35BCa0De7539A4372FC37D941736717", + "derivationPath": "44'/818'/0'/0/1", + }, + ], + "id": "js:2:vechain:0xC09f17E9e35BCa0De7539A4372FC37D941736717:vechain", + "index": 1, + "name": "VeChain 2", + "nfts": undefined, + "operationsCount": 7, + "pendingOperations": Array [], + "seedIdentifier": "0482c44bc99cdf08d4360c97fbf62d387288ab75c576926943ad90059002720e93f58799391393c98ad41136aa4ac871b103d25cb9a88f1aadd7dbbe3c77948889", + "starred": false, + "subAccounts": Array [], + "swapHistory": Array [], + "syncHash": undefined, + "unitMagnitude": 18, + "used": true, + }, + Object { + "approvals": undefined, + "compoundBalance": undefined, + "id": "js:2:vechain:0xC09f17E9e35BCa0De7539A4372FC37D941736717:vechain+vechain%2Fvtho", + "operationsCount": 3, + "parentId": "js:2:vechain:0xC09f17E9e35BCa0De7539A4372FC37D941736717:vechain", + "pendingOperations": Array [], + "starred": false, + "swapHistory": Array [], + "tokenId": "vechain/vtho", + "type": "TokenAccountRaw", + }, + Object { + "currencyId": "vechain", + "derivationMode": "vechain", + "freshAddress": "0x448251cD681cCcc791F24eA0DD26B38B9663aE74", + "freshAddressPath": "44'/818'/0'/0/2", + "freshAddresses": Array [ + Object { + "address": "0x448251cD681cCcc791F24eA0DD26B38B9663aE74", + "derivationPath": "44'/818'/0'/0/2", + }, + ], + "id": "js:2:vechain:0x448251cD681cCcc791F24eA0DD26B38B9663aE74:vechain", + "index": 2, + "name": "VeChain 3", + "nfts": undefined, + "operationsCount": 6, + "pendingOperations": Array [], + "seedIdentifier": "0482c44bc99cdf08d4360c97fbf62d387288ab75c576926943ad90059002720e93f58799391393c98ad41136aa4ac871b103d25cb9a88f1aadd7dbbe3c77948889", + "starred": false, + "subAccounts": Array [], + "swapHistory": Array [], + "syncHash": undefined, + "unitMagnitude": 18, + "used": true, + }, + Object { + "approvals": undefined, + "compoundBalance": undefined, + "id": "js:2:vechain:0x448251cD681cCcc791F24eA0DD26B38B9663aE74:vechain+vechain%2Fvtho", + "operationsCount": 6, + "parentId": "js:2:vechain:0x448251cD681cCcc791F24eA0DD26B38B9663aE74:vechain", + "pendingOperations": Array [], + "starred": false, + "swapHistory": Array [], + "tokenId": "vechain/vtho", + "type": "TokenAccountRaw", + }, + Object { + "currencyId": "vechain", + "derivationMode": "vechain", + "freshAddress": "0x087Fb90d37A2E2462CCcFEDD74eCB24c68329741", + "freshAddressPath": "44'/818'/0'/0/3", + "freshAddresses": Array [ + Object { + "address": "0x087Fb90d37A2E2462CCcFEDD74eCB24c68329741", + "derivationPath": "44'/818'/0'/0/3", + }, + ], + "id": "js:2:vechain:0x087Fb90d37A2E2462CCcFEDD74eCB24c68329741:vechain", + "index": 3, + "name": "VeChain 4", + "nfts": undefined, + "operationsCount": 3, + "pendingOperations": Array [], + "seedIdentifier": "0482c44bc99cdf08d4360c97fbf62d387288ab75c576926943ad90059002720e93f58799391393c98ad41136aa4ac871b103d25cb9a88f1aadd7dbbe3c77948889", + "starred": false, + "subAccounts": Array [], + "swapHistory": Array [], + "syncHash": undefined, + "unitMagnitude": 18, + "used": true, + }, + Object { + "approvals": undefined, + "compoundBalance": undefined, + "id": "js:2:vechain:0x087Fb90d37A2E2462CCcFEDD74eCB24c68329741:vechain+vechain%2Fvtho", + "operationsCount": 1, + "parentId": "js:2:vechain:0x087Fb90d37A2E2462CCcFEDD74eCB24c68329741:vechain", + "pendingOperations": Array [], + "starred": false, + "swapHistory": Array [], + "tokenId": "vechain/vtho", + "type": "TokenAccountRaw", + }, + Object { + "currencyId": "vechain", + "derivationMode": "vechain", + "freshAddress": "0x60cb198AD3eF380B74504D5661456554A5091a54", + "freshAddressPath": "44'/818'/0'/0/4", + "freshAddresses": Array [ + Object { + "address": "0x60cb198AD3eF380B74504D5661456554A5091a54", + "derivationPath": "44'/818'/0'/0/4", + }, + ], + "id": "js:2:vechain:0x60cb198AD3eF380B74504D5661456554A5091a54:vechain", + "index": 4, + "name": "VeChain 5", + "nfts": undefined, + "operationsCount": 0, + "pendingOperations": Array [], + "seedIdentifier": "0482c44bc99cdf08d4360c97fbf62d387288ab75c576926943ad90059002720e93f58799391393c98ad41136aa4ac871b103d25cb9a88f1aadd7dbbe3c77948889", + "starred": false, + "subAccounts": Array [], + "swapHistory": Array [], + "syncHash": undefined, + "unitMagnitude": 18, + "used": true, + }, + Object { + "approvals": undefined, + "compoundBalance": undefined, + "id": "js:2:vechain:0x60cb198AD3eF380B74504D5661456554A5091a54:vechain+vechain%2Fvtho", + "operationsCount": 2, + "parentId": "js:2:vechain:0x60cb198AD3eF380B74504D5661456554A5091a54:vechain", + "pendingOperations": Array [], + "starred": false, + "swapHistory": Array [], + "tokenId": "vechain/vtho", + "type": "TokenAccountRaw", + }, + Object { + "currencyId": "vechain", + "derivationMode": "vechain", + "freshAddress": "0x087a578ca2879E8D2345baB3B0093CfE2Aa3d74A", + "freshAddressPath": "44'/818'/0'/0/5", + "freshAddresses": Array [ + Object { + "address": "0x087a578ca2879E8D2345baB3B0093CfE2Aa3d74A", + "derivationPath": "44'/818'/0'/0/5", + }, + ], + "id": "js:2:vechain:0x087a578ca2879E8D2345baB3B0093CfE2Aa3d74A:vechain", + "index": 5, + "name": "VeChain 6", + "nfts": undefined, + "operationsCount": 0, + "pendingOperations": Array [], + "seedIdentifier": "0482c44bc99cdf08d4360c97fbf62d387288ab75c576926943ad90059002720e93f58799391393c98ad41136aa4ac871b103d25cb9a88f1aadd7dbbe3c77948889", + "starred": false, + "subAccounts": Array [], + "swapHistory": Array [], + "syncHash": undefined, + "unitMagnitude": 18, + "used": true, + }, + Object { + "approvals": undefined, + "compoundBalance": undefined, + "id": "js:2:vechain:0x087a578ca2879E8D2345baB3B0093CfE2Aa3d74A:vechain+vechain%2Fvtho", + "operationsCount": 1, + "parentId": "js:2:vechain:0x087a578ca2879E8D2345baB3B0093CfE2Aa3d74A:vechain", + "pendingOperations": Array [], + "starred": false, + "swapHistory": Array [], + "tokenId": "vechain/vtho", + "type": "TokenAccountRaw", + }, + Object { + "currencyId": "vechain", + "derivationMode": "vechain", + "freshAddress": "0x4B971838299D4e009426580920551D7C2dbBb9cc", + "freshAddressPath": "44'/818'/0'/0/6", + "freshAddresses": Array [ + Object { + "address": "0x4B971838299D4e009426580920551D7C2dbBb9cc", + "derivationPath": "44'/818'/0'/0/6", + }, + ], + "id": "js:2:vechain:0x4B971838299D4e009426580920551D7C2dbBb9cc:vechain", + "index": 6, + "name": "VeChain 7", + "nfts": undefined, + "operationsCount": 0, + "pendingOperations": Array [], + "seedIdentifier": "0482c44bc99cdf08d4360c97fbf62d387288ab75c576926943ad90059002720e93f58799391393c98ad41136aa4ac871b103d25cb9a88f1aadd7dbbe3c77948889", + "starred": false, + "subAccounts": Array [], + "swapHistory": Array [], + "syncHash": undefined, + "unitMagnitude": 18, + "used": false, + }, + Object { + "approvals": undefined, + "compoundBalance": undefined, + "id": "js:2:vechain:0x4B971838299D4e009426580920551D7C2dbBb9cc:vechain+vechain%2Fvtho", + "operationsCount": 0, + "parentId": "js:2:vechain:0x4B971838299D4e009426580920551D7C2dbBb9cc:vechain", + "pendingOperations": Array [], + "starred": false, + "swapHistory": Array [], + "tokenId": "vechain/vtho", + "type": "TokenAccountRaw", + }, +] +`; + +exports[`vechain currency bridge scanAccounts vechain seed 1 2`] = ` +Array [ + Array [ + Object { + "accountId": "js:2:vechain:0x32476193d4a32488322ffBb9835a7cF2c7e2202C:vechain", + "blockHash": "0x00de8429791220aab56f3232be8bb9a2ae18af7d35574bcf5fd7c39060843063", + "blockHeight": 14582825, + "contract": undefined, + "extra": Object {}, + "fee": "0", + "hash": "0x02ec8852f145ed805401c8f7497983773f97214a93b7a3c27f5047e22a3012ec", + "id": "0x02ec8852f145ed805401c8f7497983773f97214a93b7a3c27f5047e22a3012ec", + "operator": undefined, + "recipients": Array [ + "0x32476193d4a32488322ffbb9835a7cf2c7e2202c", + ], + "senders": Array [ + "0x4f6fc409e152d33843cf4982d414c1dd0879277e", + ], + "standard": undefined, + "tokenId": undefined, + "type": "IN", + "value": "500000000000000000000", + }, + Object { + "accountId": "js:2:vechain:0x32476193d4a32488322ffBb9835a7cF2c7e2202C:vechain", + "blockHash": "0x00ddd8047ce47f277e3a021fee267bc025a68e9669e9e25fe0839a037515603a", + "blockHeight": 14538756, + "contract": undefined, + "extra": Object {}, + "fee": "0", + "hash": "0x05ee616beb80ddd8e41cec0bd12829f21395e99c13f7e2e2ea313f20f7414c2a", + "id": "0x05ee616beb80ddd8e41cec0bd12829f21395e99c13f7e2e2ea313f20f7414c2a", + "operator": undefined, + "recipients": Array [ + "0x32476193d4a32488322ffbb9835a7cf2c7e2202c", + ], + "senders": Array [ + "0x4f6fc409e152d33843cf4982d414c1dd0879277e", + ], + "standard": undefined, + "tokenId": undefined, + "type": "IN", + "value": "500000000000000000000", + }, + Object { + "accountId": "js:2:vechain:0x32476193d4a32488322ffBb9835a7cF2c7e2202C:vechain", + "blockHash": "0x00dea9c5be3de57c718fe814e49f2b12eb4038516ee9dc83d666de580785c5c7", + "blockHeight": 14592453, + "contract": undefined, + "extra": Object {}, + "fee": "0", + "hash": "0x1f2c87cfe04cc72018e2a01e2d5a1d5a14c212bf7674b6c8158c2aad60d51f7a", + "id": "0x1f2c87cfe04cc72018e2a01e2d5a1d5a14c212bf7674b6c8158c2aad60d51f7a", + "operator": undefined, + "recipients": Array [ + "0x991a447df3a8f70060a04db2019d075cfa7de9a1", + ], + "senders": Array [ + "0x32476193d4a32488322ffbb9835a7cf2c7e2202c", + ], + "standard": undefined, + "tokenId": undefined, + "type": "OUT", + "value": "1000000000000000000000", + }, + Object { + "accountId": "js:2:vechain:0x32476193d4a32488322ffBb9835a7cF2c7e2202C:vechain", + "blockHash": "0x00decbd906961be213f830cec7e0b63ce1bad832daf74e4ce7b5cefa0ec19250", + "blockHeight": 14601177, + "contract": undefined, + "extra": Object {}, + "fee": "0", + "hash": "0x2da7e3d745bbe267b0324a85c7145d0a6e266957167f71212e553540b0036c4c", + "id": "0x2da7e3d745bbe267b0324a85c7145d0a6e266957167f71212e553540b0036c4c", + "operator": undefined, + "recipients": Array [ + "0x32476193d4a32488322ffbb9835a7cf2c7e2202c", + ], + "senders": Array [ + "0xb437deeac72ec1df37d38304089674962bec3532", + ], + "standard": undefined, + "tokenId": undefined, + "type": "IN", + "value": "500000000000000000000", + }, + Object { + "accountId": "js:2:vechain:0x32476193d4a32488322ffBb9835a7cF2c7e2202C:vechain", + "blockHash": "0x00e165a75ec2e2338f7b83186e0043f747268e377f26d36eeb61cce54a18647f", + "blockHeight": 14771623, + "contract": undefined, + "extra": Object {}, + "fee": "0", + "hash": "0x3598c9ee523740a4ac85a400893e7268eb47a5aec54f7ca40642e6cee0e945b8", + "id": "0x3598c9ee523740a4ac85a400893e7268eb47a5aec54f7ca40642e6cee0e945b8", + "operator": undefined, + "recipients": Array [ + "0xc09f17e9e35bca0de7539a4372fc37d941736717", + ], + "senders": Array [ + "0x32476193d4a32488322ffbb9835a7cf2c7e2202c", + ], + "standard": undefined, + "tokenId": undefined, + "type": "OUT", + "value": "10000000000000000000", + }, + Object { + "accountId": "js:2:vechain:0x32476193d4a32488322ffBb9835a7cF2c7e2202C:vechain", + "blockHash": "0x00df8b3b0ad77e34b85c16086dddc00c217219d26c12186d93ad3e8d5733d296", + "blockHeight": 14650171, + "contract": undefined, + "extra": Object {}, + "fee": "0", + "hash": "0x412261af940da517a5a224dade4e28f7ae33fdd2432d78264e6e2ea5c14357c5", + "id": "0x412261af940da517a5a224dade4e28f7ae33fdd2432d78264e6e2ea5c14357c5", + "operator": undefined, + "recipients": Array [ + "0x32476193d4a32488322ffbb9835a7cf2c7e2202c", + ], + "senders": Array [ + "0x32476193d4a32488322ffbb9835a7cf2c7e2202c", + ], + "standard": undefined, + "tokenId": undefined, + "type": "IN", + "value": "100000000000000000000", + }, + Object { + "accountId": "js:2:vechain:0x32476193d4a32488322ffBb9835a7cF2c7e2202C:vechain", + "blockHash": "0x00df51ccad9388a44cdaacbeaf36c3c1398558d009aca4868973b558a110376b", + "blockHeight": 14635468, + "contract": undefined, + "extra": Object {}, + "fee": "0", + "hash": "0x42e9377017cff753841ae5a9522f84ddc11c7d324096af5f9d5622223deed130", + "id": "0x42e9377017cff753841ae5a9522f84ddc11c7d324096af5f9d5622223deed130", + "operator": undefined, + "recipients": Array [ + "0x39af3f8666710501bb9f3800b8e5b64db5bc7ee2", + ], + "senders": Array [ + "0x32476193d4a32488322ffbb9835a7cf2c7e2202c", + ], + "standard": undefined, + "tokenId": undefined, + "type": "OUT", + "value": "10000000000000000000", + }, + Object { + "accountId": "js:2:vechain:0x32476193d4a32488322ffBb9835a7cF2c7e2202C:vechain", + "blockHash": "0x00dfb0aa7811e3246c20ca10e8f603a6c39f622a413d5871120434f58b5a3097", + "blockHeight": 14659754, + "contract": undefined, + "extra": Object {}, + "fee": "0", + "hash": "0x45373d9864c37fcca586a2eac6102afc7f4be18d1b948aa822b565ea6a6ea6e4", + "id": "0x45373d9864c37fcca586a2eac6102afc7f4be18d1b948aa822b565ea6a6ea6e4", + "operator": undefined, + "recipients": Array [ + "0x991a447df3a8f70060a04db2019d075cfa7de9a1", + ], + "senders": Array [ + "0x32476193d4a32488322ffbb9835a7cf2c7e2202c", + ], + "standard": undefined, + "tokenId": undefined, + "type": "OUT", + "value": "20000000000000000000", + }, + Object { + "accountId": "js:2:vechain:0x32476193d4a32488322ffBb9835a7cF2c7e2202C:vechain", + "blockHash": "0x00e10a29642c905914dc75996682a0b0681ebfc2dbe5f7cae83d2b427ff37c6c", + "blockHeight": 14748201, + "contract": undefined, + "extra": Object {}, + "fee": "0", + "hash": "0x561bbf76a2a4721a4bdb6435356bce59fd71984fda866553a8cbd295cac05494", + "id": "0x561bbf76a2a4721a4bdb6435356bce59fd71984fda866553a8cbd295cac05494", + "operator": undefined, + "recipients": Array [ + "0x448251cd681cccc791f24ea0dd26b38b9663ae74", + ], + "senders": Array [ + "0x32476193d4a32488322ffbb9835a7cf2c7e2202c", + ], + "standard": undefined, + "tokenId": undefined, + "type": "OUT", + "value": "10000000000000000000", + }, + Object { + "accountId": "js:2:vechain:0x32476193d4a32488322ffBb9835a7cF2c7e2202C:vechain", + "blockHash": "0x00df4d220b733090fac6228e6226eb4f6f4de3e4f0dbff7382378f0a4d550ebc", + "blockHeight": 14634274, + "contract": undefined, + "extra": Object {}, + "fee": "0", + "hash": "0x654378557033c66f61a21f0bc75282172e3b9c5700bdc7311058ad894142ec27", + "id": "0x654378557033c66f61a21f0bc75282172e3b9c5700bdc7311058ad894142ec27", + "operator": undefined, + "recipients": Array [ + "0x39af3f8666710501bb9f3800b8e5b64db5bc7ee2", + ], + "senders": Array [ + "0x32476193d4a32488322ffbb9835a7cf2c7e2202c", + ], + "standard": undefined, + "tokenId": undefined, + "type": "OUT", + "value": "10000000000000000000", + }, + Object { + "accountId": "js:2:vechain:0x32476193d4a32488322ffBb9835a7cF2c7e2202C:vechain", + "blockHash": "0x00e100488ffab60b466584ee57fb2b525b2ac60090b1a67fe529ec35e351b4ed", + "blockHeight": 14745672, + "contract": undefined, + "extra": Object {}, + "fee": "0", + "hash": "0x6a321fc0f23819df7f2bee821402cf6e85821fec079ac64d1fc2d22425e63566", + "id": "0x6a321fc0f23819df7f2bee821402cf6e85821fec079ac64d1fc2d22425e63566", + "operator": undefined, + "recipients": Array [ + "0x448251cd681cccc791f24ea0dd26b38b9663ae74", + ], + "senders": Array [ + "0x32476193d4a32488322ffbb9835a7cf2c7e2202c", + ], + "standard": undefined, + "tokenId": undefined, + "type": "OUT", + "value": "10000000000000000000", + }, + Object { + "accountId": "js:2:vechain:0x32476193d4a32488322ffBb9835a7cF2c7e2202C:vechain", + "blockHash": "0x00e12ad4756dfabaf0fac311833ea869f156802c0c55789c57d12f36e9be091e", + "blockHeight": 14756564, + "contract": undefined, + "extra": Object {}, + "fee": "0", + "hash": "0x80f33bdc6996b5226be4142b46796b4edf38813e54d018b3b4dc91c042da31f4", + "id": "0x80f33bdc6996b5226be4142b46796b4edf38813e54d018b3b4dc91c042da31f4", + "operator": undefined, + "recipients": Array [ + "0x32476193d4a32488322ffbb9835a7cf2c7e2202c", + ], + "senders": Array [ + "0x4f6fc409e152d33843cf4982d414c1dd0879277e", + ], + "standard": undefined, + "tokenId": undefined, + "type": "IN", + "value": "500000000000000000000", + }, + Object { + "accountId": "js:2:vechain:0x32476193d4a32488322ffBb9835a7cF2c7e2202C:vechain", + "blockHash": "0x00e16a355b657779dc97f974b69e9b9a1ed7dd5096c11e2c8c788dec2ac36ba2", + "blockHeight": 14772789, + "contract": undefined, + "extra": Object {}, + "fee": "0", + "hash": "0x85a951fdda4fe9ee3a7c6bec319977803448581d18fd7af0a2f8089187e1aeaf", + "id": "0x85a951fdda4fe9ee3a7c6bec319977803448581d18fd7af0a2f8089187e1aeaf", + "operator": undefined, + "recipients": Array [ + "0x087fb90d37a2e2462cccfedd74ecb24c68329741", + ], + "senders": Array [ + "0x32476193d4a32488322ffbb9835a7cf2c7e2202c", + ], + "standard": undefined, + "tokenId": undefined, + "type": "OUT", + "value": "90000000000000000000", + }, + Object { + "accountId": "js:2:vechain:0x32476193d4a32488322ffBb9835a7cF2c7e2202C:vechain", + "blockHash": "0x00e0ffcaf4c50c1cea51e2df2d13654bfb1ae304ac52a396a20977d1f6b1eadb", + "blockHeight": 14745546, + "contract": undefined, + "extra": Object {}, + "fee": "0", + "hash": "0x86bc2705c9af907f3e6d549340eec706eb8c31fa628a9d43907f3d239fd9036e", + "id": "0x86bc2705c9af907f3e6d549340eec706eb8c31fa628a9d43907f3d239fd9036e", + "operator": undefined, + "recipients": Array [ + "0x448251cd681cccc791f24ea0dd26b38b9663ae74", + ], + "senders": Array [ + "0x32476193d4a32488322ffbb9835a7cf2c7e2202c", + ], + "standard": undefined, + "tokenId": undefined, + "type": "OUT", + "value": "10000000000000000000", + }, + Object { + "accountId": "js:2:vechain:0x32476193d4a32488322ffBb9835a7cF2c7e2202C:vechain", + "blockHash": "0x00e0127dd329edd0b9e277a9761eb80b0344e0a15970e15fa1e767b8202522fd", + "blockHeight": 14684797, + "contract": undefined, + "extra": Object {}, + "fee": "0", + "hash": "0x8ff0c75d160e2624da342c37c916eb82da22e14061e2ca69ac5bdb7dcfd8aa18", + "id": "0x8ff0c75d160e2624da342c37c916eb82da22e14061e2ca69ac5bdb7dcfd8aa18", + "operator": undefined, + "recipients": Array [ + "0x32476193d4a32488322ffbb9835a7cf2c7e2202c", + ], + "senders": Array [ + "0x32476193d4a32488322ffbb9835a7cf2c7e2202c", + ], + "standard": undefined, + "tokenId": undefined, + "type": "IN", + "value": "10000000000000000000", + }, + Object { + "accountId": "js:2:vechain:0x32476193d4a32488322ffBb9835a7cF2c7e2202C:vechain", + "blockHash": "0x00e012aa827616bb1f5a63fc678881d6ef366072425c79246f2f98ad0332474f", + "blockHeight": 14684842, + "contract": undefined, + "extra": Object {}, + "fee": "0", + "hash": "0x967507fec325dfccae09a8a0b27ee47575fdb0890446cf41bdd41d74442d5243", + "id": "0x967507fec325dfccae09a8a0b27ee47575fdb0890446cf41bdd41d74442d5243", + "operator": undefined, + "recipients": Array [ + "0x32476193d4a32488322ffbb9835a7cf2c7e2202c", + ], + "senders": Array [ + "0x32476193d4a32488322ffbb9835a7cf2c7e2202c", + ], + "standard": undefined, + "tokenId": undefined, + "type": "IN", + "value": "10000000000000000000", + }, + Object { + "accountId": "js:2:vechain:0x32476193d4a32488322ffBb9835a7cF2c7e2202C:vechain", + "blockHash": "0x00dfb70ef235497ee7e161eb7ed5e56efa3e3de80d2a86e4ed68cd1e26c3a915", + "blockHeight": 14661390, + "contract": undefined, + "extra": Object {}, + "fee": "0", + "hash": "0x998c9c5409847b0427498d10b4e5802d04cf400c18b17f62d2f1023c4414a29c", + "id": "0x998c9c5409847b0427498d10b4e5802d04cf400c18b17f62d2f1023c4414a29c", + "operator": undefined, + "recipients": Array [ + "0xc09f17e9e35bca0de7539a4372fc37d941736717", + ], + "senders": Array [ + "0x32476193d4a32488322ffbb9835a7cf2c7e2202c", + ], + "standard": undefined, + "tokenId": undefined, + "type": "OUT", + "value": "10000000000000000000", + }, + Object { + "accountId": "js:2:vechain:0x32476193d4a32488322ffBb9835a7cF2c7e2202C:vechain", + "blockHash": "0x00e0789664f403462cb8418ea100a070a002d23ba1a2198392e279f999b3cd7a", + "blockHeight": 14710934, + "contract": undefined, + "extra": Object {}, + "fee": "0", + "hash": "0xa10016ab14fec6b80cf17cf959fae732b720b8d07f5463a69d23fd9fec9b52fd", + "id": "0xa10016ab14fec6b80cf17cf959fae732b720b8d07f5463a69d23fd9fec9b52fd", + "operator": undefined, + "recipients": Array [ + "0xc09f17e9e35bca0de7539a4372fc37d941736717", + ], + "senders": Array [ + "0x32476193d4a32488322ffbb9835a7cf2c7e2202c", + ], + "standard": undefined, + "tokenId": undefined, + "type": "OUT", + "value": "10000000000000000000", + }, + Object { + "accountId": "js:2:vechain:0x32476193d4a32488322ffBb9835a7cF2c7e2202C:vechain", + "blockHash": "0x00de83fe83af760ac74ed0ec5f1f841f97431f9580dde655a756435c391f4d6a", + "blockHeight": 14582782, + "contract": undefined, + "extra": Object {}, + "fee": "0", + "hash": "0xa7415e46d76e56668f7ca1cf980e360541092e42f2506a0e755621d09cadb617", + "id": "0xa7415e46d76e56668f7ca1cf980e360541092e42f2506a0e755621d09cadb617", + "operator": undefined, + "recipients": Array [ + "0x32476193d4a32488322ffbb9835a7cf2c7e2202c", + ], + "senders": Array [ + "0x4f6fc409e152d33843cf4982d414c1dd0879277e", + ], + "standard": undefined, + "tokenId": undefined, + "type": "IN", + "value": "500000000000000000000", + }, + Object { + "accountId": "js:2:vechain:0x32476193d4a32488322ffBb9835a7cF2c7e2202C:vechain", + "blockHash": "0x00e12a0a3b0431863e121da091199a91afb600aa71c4bd6948d72282a6ac3af5", + "blockHeight": 14756362, + "contract": undefined, + "extra": Object {}, + "fee": "0", + "hash": "0xaa8eb048f67afe9dab4981e7ecbf89c9c3ed677fa9ab67b9ebbc6932d7f8a41c", + "id": "0xaa8eb048f67afe9dab4981e7ecbf89c9c3ed677fa9ab67b9ebbc6932d7f8a41c", + "operator": undefined, + "recipients": Array [ + "0x087fb90d37a2e2462cccfedd74ecb24c68329741", + ], + "senders": Array [ + "0x32476193d4a32488322ffbb9835a7cf2c7e2202c", + ], + "standard": undefined, + "tokenId": undefined, + "type": "OUT", + "value": "10000000000000000000", + }, + Object { + "accountId": "js:2:vechain:0x32476193d4a32488322ffBb9835a7cF2c7e2202C:vechain", + "blockHash": "0x00de834410c6a6b566872943805ab8e7398e102dbf313dda24d8b91c1cf95f43", + "blockHeight": 14582596, + "contract": undefined, + "extra": Object {}, + "fee": "0", + "hash": "0xb2afcd89995e087dbba47a5ed7d2a147cf1a8529ffb1c7e32053ba0dcb074a52", + "id": "0xb2afcd89995e087dbba47a5ed7d2a147cf1a8529ffb1c7e32053ba0dcb074a52", + "operator": undefined, + "recipients": Array [ + "0x32476193d4a32488322ffbb9835a7cf2c7e2202c", + ], + "senders": Array [ + "0x4f6fc409e152d33843cf4982d414c1dd0879277e", + ], + "standard": undefined, + "tokenId": undefined, + "type": "IN", + "value": "500000000000000000000", + }, + Object { + "accountId": "js:2:vechain:0x32476193d4a32488322ffBb9835a7cF2c7e2202C:vechain", + "blockHash": "0x00e165e4c20dff0bd676d2b743e52d94e0333641a1e6fecbc2869157ed93ead0", + "blockHeight": 14771684, + "contract": undefined, + "extra": Object {}, + "fee": "0", + "hash": "0xc0ba9cfdf54317686b2407c9421b549e9e8543d78b6d2577b5ec0260bbff410c", + "id": "0xc0ba9cfdf54317686b2407c9421b549e9e8543d78b6d2577b5ec0260bbff410c", + "operator": undefined, + "recipients": Array [ + "0xc09f17e9e35bca0de7539a4372fc37d941736717", + ], + "senders": Array [ + "0x32476193d4a32488322ffbb9835a7cf2c7e2202c", + ], + "standard": undefined, + "tokenId": undefined, + "type": "OUT", + "value": "10000000000000000000", + }, + Object { + "accountId": "js:2:vechain:0x32476193d4a32488322ffBb9835a7cF2c7e2202C:vechain", + "blockHash": "0x00e0fecc22a788f3d4ca53a2a3e9ce9586203be8549b69597a9bf220a01ff69f", + "blockHeight": 14745292, + "contract": undefined, + "extra": Object {}, + "fee": "0", + "hash": "0xc483fff886b27f38fc726c04135db34ca1af1970666f7117c5c698f78d71df52", + "id": "0xc483fff886b27f38fc726c04135db34ca1af1970666f7117c5c698f78d71df52", + "operator": undefined, + "recipients": Array [ + "0x448251cd681cccc791f24ea0dd26b38b9663ae74", + ], + "senders": Array [ + "0x32476193d4a32488322ffbb9835a7cf2c7e2202c", + ], + "standard": undefined, + "tokenId": undefined, + "type": "OUT", + "value": "10000000000000000000", + }, + Object { + "accountId": "js:2:vechain:0x32476193d4a32488322ffBb9835a7cF2c7e2202C:vechain", + "blockHash": "0x00deca069518a6304feb89078edb16a59a55fed3364b14af78dc5e21c1adde16", + "blockHeight": 14600710, + "contract": undefined, + "extra": Object {}, + "fee": "0", + "hash": "0xc757939ddee9f46437991880cf2b44d8dc62e8812126ff1574c0efe911978b08", + "id": "0xc757939ddee9f46437991880cf2b44d8dc62e8812126ff1574c0efe911978b08", + "operator": undefined, + "recipients": Array [ + "0x39af3f8666710501bb9f3800b8e5b64db5bc7ee2", + ], + "senders": Array [ + "0x32476193d4a32488322ffbb9835a7cf2c7e2202c", + ], + "standard": undefined, + "tokenId": undefined, + "type": "OUT", + "value": "500000000000000000000", + }, + Object { + "accountId": "js:2:vechain:0x32476193d4a32488322ffBb9835a7cF2c7e2202C:vechain", + "blockHash": "0x00df8b464c5450cb08e502f3b69ade5989f7348c847e3634b526696f62a4cdc2", + "blockHeight": 14650182, + "contract": undefined, + "extra": Object {}, + "fee": "0", + "hash": "0xe44e5a50efd12a25a4ee860907c267649208b6ce75dd7396ea05071c001a0570", + "id": "0xe44e5a50efd12a25a4ee860907c267649208b6ce75dd7396ea05071c001a0570", + "operator": undefined, + "recipients": Array [ + "0x991a447df3a8f70060a04db2019d075cfa7de9a1", + ], + "senders": Array [ + "0x32476193d4a32488322ffbb9835a7cf2c7e2202c", + ], + "standard": undefined, + "tokenId": undefined, + "type": "OUT", + "value": "10000000000000000000", + }, + Object { + "accountId": "js:2:vechain:0x32476193d4a32488322ffBb9835a7cF2c7e2202C:vechain", + "blockHash": "0x00e012b91a052de6e05659898c81d6849e6ee25c734e0f108d75bf30028beca0", + "blockHeight": 14684857, + "contract": undefined, + "extra": Object {}, + "fee": "0", + "hash": "0xf00038075fa0a7f7640c85c1b42a24ec3d23db1df74faa45ff2b4652f441c3f2", + "id": "0xf00038075fa0a7f7640c85c1b42a24ec3d23db1df74faa45ff2b4652f441c3f2", + "operator": undefined, + "recipients": Array [ + "0xc09f17e9e35bca0de7539a4372fc37d941736717", + ], + "senders": Array [ + "0x32476193d4a32488322ffbb9835a7cf2c7e2202c", + ], + "standard": undefined, + "tokenId": undefined, + "type": "OUT", + "value": "10000000000000000000", + }, + Object { + "accountId": "js:2:vechain:0x32476193d4a32488322ffBb9835a7cF2c7e2202C:vechain", + "blockHash": "0x00e14f1f68077bb40820f4610dfcd1484bb2f414caf450144302fa9fe0843f16", + "blockHeight": 14765855, + "contract": undefined, + "extra": Object {}, + "fee": "0", + "hash": "0xf043decd93cf358b3df16103d27283d286d1cbfb2a6f6df989aaed53146a5864", + "id": "0xf043decd93cf358b3df16103d27283d286d1cbfb2a6f6df989aaed53146a5864", + "operator": undefined, + "recipients": Array [ + "0x448251cd681cccc791f24ea0dd26b38b9663ae74", + ], + "senders": Array [ + "0x32476193d4a32488322ffbb9835a7cf2c7e2202c", + ], + "standard": undefined, + "tokenId": undefined, + "type": "OUT", + "value": "10000000000000000000", + }, + Object { + "accountId": "js:2:vechain:0x32476193d4a32488322ffBb9835a7cF2c7e2202C:vechain", + "blockHash": "0x00df4cd92ef3e27a67411647561d3e2f625b9633dc1463996e72a9b80f41d84a", + "blockHeight": 14634201, + "contract": undefined, + "extra": Object {}, + "fee": "0", + "hash": "0xfb53db4e7139a349c1a6942f9c96e389ba526898980ef9da52199f08ae447fa9", + "id": "0xfb53db4e7139a349c1a6942f9c96e389ba526898980ef9da52199f08ae447fa9", + "operator": undefined, + "recipients": Array [ + "0x39af3f8666710501bb9f3800b8e5b64db5bc7ee2", + ], + "senders": Array [ + "0x32476193d4a32488322ffbb9835a7cf2c7e2202c", + ], + "standard": undefined, + "tokenId": undefined, + "type": "OUT", + "value": "100000000000000000000", + }, + ], + Array [ + Object { + "accountId": "js:2:vechain:0x32476193d4a32488322ffBb9835a7cF2c7e2202C:vechain+vechain%2Fvtho", + "blockHash": "0x00de8429791220aab56f3232be8bb9a2ae18af7d35574bcf5fd7c39060843063", + "blockHeight": 14582825, + "contract": undefined, + "extra": Object {}, + "fee": "0", + "hash": "0x02ec8852f145ed805401c8f7497983773f97214a93b7a3c27f5047e22a3012ec", + "id": "0x02ec8852f145ed805401c8f7497983773f97214a93b7a3c27f5047e22a3012ec", + "operator": undefined, + "recipients": Array [ + "0x32476193d4a32488322ffbb9835a7cf2c7e2202c", + ], + "senders": Array [ + "0x4f6fc409e152d33843cf4982d414c1dd0879277e", + ], + "standard": undefined, + "tokenId": undefined, + "type": "IN", + "value": "50000000000000000000", + }, + Object { + "accountId": "js:2:vechain:0x32476193d4a32488322ffBb9835a7cF2c7e2202C:vechain+vechain%2Fvtho", + "blockHash": "0x00e10a2cf72a615d0b3a7247085832a293aba8d1fae56ca27e2de29cf3dbe3d8", + "blockHeight": 14748204, + "contract": undefined, + "extra": Object {}, + "fee": "0", + "hash": "0x0425abc630df15bc96a1e60317653f13aeff53708cc57fce9709485fb463b880", + "id": "0x0425abc630df15bc96a1e60317653f13aeff53708cc57fce9709485fb463b880", + "operator": undefined, + "recipients": Array [ + "0x448251cd681cccc791f24ea0dd26b38b9663ae74", + ], + "senders": Array [ + "0x32476193d4a32488322ffbb9835a7cf2c7e2202c", + ], + "standard": undefined, + "tokenId": undefined, + "type": "OUT", + "value": "10000000000000000000", + }, + Object { + "accountId": "js:2:vechain:0x32476193d4a32488322ffBb9835a7cF2c7e2202C:vechain+vechain%2Fvtho", + "blockHash": "0x00ddd8047ce47f277e3a021fee267bc025a68e9669e9e25fe0839a037515603a", + "blockHeight": 14538756, + "contract": undefined, + "extra": Object {}, + "fee": "0", + "hash": "0x05ee616beb80ddd8e41cec0bd12829f21395e99c13f7e2e2ea313f20f7414c2a", + "id": "0x05ee616beb80ddd8e41cec0bd12829f21395e99c13f7e2e2ea313f20f7414c2a", + "operator": undefined, + "recipients": Array [ + "0x32476193d4a32488322ffbb9835a7cf2c7e2202c", + ], + "senders": Array [ + "0x4f6fc409e152d33843cf4982d414c1dd0879277e", + ], + "standard": undefined, + "tokenId": undefined, + "type": "IN", + "value": "50000000000000000000", + }, + Object { + "accountId": "js:2:vechain:0x32476193d4a32488322ffBb9835a7cF2c7e2202C:vechain+vechain%2Fvtho", + "blockHash": "0x00e10a9a598dba300f26779d1882222e871c15cef5e1ef079e1a1dcfdf5f7e66", + "blockHeight": 14748314, + "contract": undefined, + "extra": Object {}, + "fee": "0", + "hash": "0x05ff1133456f3252409b8ae0eeb0d95f0893058b9af3c10c730e040ad14ae58c", + "id": "0x05ff1133456f3252409b8ae0eeb0d95f0893058b9af3c10c730e040ad14ae58c", + "operator": undefined, + "recipients": Array [ + "0x448251cd681cccc791f24ea0dd26b38b9663ae74", + ], + "senders": Array [ + "0x32476193d4a32488322ffbb9835a7cf2c7e2202c", + ], + "standard": undefined, + "tokenId": undefined, + "type": "OUT", + "value": "10000000000000000000", + }, + Object { + "accountId": "js:2:vechain:0x32476193d4a32488322ffBb9835a7cF2c7e2202C:vechain+vechain%2Fvtho", + "blockHash": "0x00e12af6ddbf112e7325a5b2b3d62ef65746c730a45c43e477c872d295e7bfe6", + "blockHeight": 14756598, + "contract": undefined, + "extra": Object {}, + "fee": "0", + "hash": "0x0abd7211b4d7220eaa5a33315126b45f4f90292de0b4fbe45e5d40c0d5f6c78e", + "id": "0x0abd7211b4d7220eaa5a33315126b45f4f90292de0b4fbe45e5d40c0d5f6c78e", + "operator": undefined, + "recipients": Array [ + "0x60cb198ad3ef380b74504d5661456554a5091a54", + ], + "senders": Array [ + "0x32476193d4a32488322ffbb9835a7cf2c7e2202c", + ], + "standard": undefined, + "tokenId": undefined, + "type": "OUT", + "value": "10000000000000000000", + }, + Object { + "accountId": "js:2:vechain:0x32476193d4a32488322ffBb9835a7cF2c7e2202C:vechain+vechain%2Fvtho", + "blockHash": "0x00e14f23353a7566256360d30afb280a9b25fd721858a3ee61bf8b705ab215ce", + "blockHeight": 14765859, + "contract": undefined, + "extra": Object {}, + "fee": "0", + "hash": "0x165bd4ead23814e9f4ea78e272379ba0b4b7ac8b0625aecc9a7ee0a4e83260d0", + "id": "0x165bd4ead23814e9f4ea78e272379ba0b4b7ac8b0625aecc9a7ee0a4e83260d0", + "operator": undefined, + "recipients": Array [ + "0x448251cd681cccc791f24ea0dd26b38b9663ae74", + ], + "senders": Array [ + "0x32476193d4a32488322ffbb9835a7cf2c7e2202c", + ], + "standard": undefined, + "tokenId": undefined, + "type": "OUT", + "value": "10000000000000000000", + }, + Object { + "accountId": "js:2:vechain:0x32476193d4a32488322ffBb9835a7cF2c7e2202C:vechain+vechain%2Fvtho", + "blockHash": "0x00e12a0e4e2cfd9adb402fe2725c682e6d982013de2a218fedff5270590addc0", + "blockHeight": 14756366, + "contract": undefined, + "extra": Object {}, + "fee": "0", + "hash": "0x18bd5cf6d5168033ed07da8088ccc43230f024f64c66483ba25f3426c366c670", + "id": "0x18bd5cf6d5168033ed07da8088ccc43230f024f64c66483ba25f3426c366c670", + "operator": undefined, + "recipients": Array [ + "0x087fb90d37a2e2462cccfedd74ecb24c68329741", + ], + "senders": Array [ + "0x32476193d4a32488322ffbb9835a7cf2c7e2202c", + ], + "standard": undefined, + "tokenId": undefined, + "type": "OUT", + "value": "10000000000000000000", + }, + Object { + "accountId": "js:2:vechain:0x32476193d4a32488322ffBb9835a7cF2c7e2202C:vechain+vechain%2Fvtho", + "blockHash": "0x00df2976b5f364ce71f9b9cb7ab7557a55706d1d1bfe860505335372e5ab3a8b", + "blockHeight": 14625142, + "contract": undefined, + "extra": Object {}, + "fee": "0", + "hash": "0x1bac71725bcfbe283ccd468d2a55b7ec9515902a10a171e59a8abb0f8445edbd", + "id": "0x1bac71725bcfbe283ccd468d2a55b7ec9515902a10a171e59a8abb0f8445edbd", + "operator": undefined, + "recipients": Array [ + "0x32476193d4a32488322ffbb9835a7cf2c7e2202c", + ], + "senders": Array [ + "0xb437deeac72ec1df37d38304089674962bec3532", + ], + "standard": undefined, + "tokenId": undefined, + "type": "IN", + "value": "100000000000000000000", + }, + Object { + "accountId": "js:2:vechain:0x32476193d4a32488322ffBb9835a7cF2c7e2202C:vechain+vechain%2Fvtho", + "blockHash": "0x00e165abb9410d15bddd757cda8caececa25c2cb064dbb18fd609e34610b1977", + "blockHeight": 14771627, + "contract": undefined, + "extra": Object {}, + "fee": "0", + "hash": "0x1d1287e237368f56c6e9171505c59737ed2f7d16db1e8b0b8a19581cc1c38504", + "id": "0x1d1287e237368f56c6e9171505c59737ed2f7d16db1e8b0b8a19581cc1c38504", + "operator": undefined, + "recipients": Array [ + "0xc09f17e9e35bca0de7539a4372fc37d941736717", + ], + "senders": Array [ + "0x32476193d4a32488322ffbb9835a7cf2c7e2202c", + ], + "standard": undefined, + "tokenId": undefined, + "type": "OUT", + "value": "10000000000000000000", + }, + Object { + "accountId": "js:2:vechain:0x32476193d4a32488322ffBb9835a7cF2c7e2202C:vechain+vechain%2Fvtho", + "blockHash": "0x00e1042a110b596488489a66edff8d28c253a4bea68409c310f2673ed6706b2f", + "blockHeight": 14746666, + "contract": undefined, + "extra": Object {}, + "fee": "0", + "hash": "0x28acdf8cffbb638699fcf4de4a7114c9d252e40fc461096f584d5adfa40ede63", + "id": "0x28acdf8cffbb638699fcf4de4a7114c9d252e40fc461096f584d5adfa40ede63", + "operator": undefined, + "recipients": Array [ + "0x448251cd681cccc791f24ea0dd26b38b9663ae74", + ], + "senders": Array [ + "0x32476193d4a32488322ffbb9835a7cf2c7e2202c", + ], + "standard": undefined, + "tokenId": undefined, + "type": "OUT", + "value": "10000000000000000000", + }, + Object { + "accountId": "js:2:vechain:0x32476193d4a32488322ffBb9835a7cF2c7e2202C:vechain+vechain%2Fvtho", + "blockHash": "0x00e129e8782cb7791042401d09c685977e914a2b16b8d730bfa42c728d192419", + "blockHeight": 14756328, + "contract": undefined, + "extra": Object {}, + "fee": "0", + "hash": "0x55e79f13e5772076d19a059bade544a879ddfde0089c8ee6b745fac3ef7e709c", + "id": "0x55e79f13e5772076d19a059bade544a879ddfde0089c8ee6b745fac3ef7e709c", + "operator": undefined, + "recipients": Array [ + "0x448251cd681cccc791f24ea0dd26b38b9663ae74", + ], + "senders": Array [ + "0x32476193d4a32488322ffbb9835a7cf2c7e2202c", + ], + "standard": undefined, + "tokenId": undefined, + "type": "OUT", + "value": "10000000000000000000", + }, + Object { + "accountId": "js:2:vechain:0x32476193d4a32488322ffBb9835a7cF2c7e2202C:vechain+vechain%2Fvtho", + "blockHash": "0x00e129fa79e527b3ab5f26eebf8c230895d59e3cf6e880a0b1a30b0be7ecbe5b", + "blockHeight": 14756346, + "contract": undefined, + "extra": Object {}, + "fee": "0", + "hash": "0x7249b4ace7dc34192bf99caf29ebb0ae65f26d00008a84e5290806ac1f9be283", + "id": "0x7249b4ace7dc34192bf99caf29ebb0ae65f26d00008a84e5290806ac1f9be283", + "operator": undefined, + "recipients": Array [ + "0x448251cd681cccc791f24ea0dd26b38b9663ae74", + ], + "senders": Array [ + "0x32476193d4a32488322ffbb9835a7cf2c7e2202c", + ], + "standard": undefined, + "tokenId": undefined, + "type": "OUT", + "value": "10000000000000000000", + }, + Object { + "accountId": "js:2:vechain:0x32476193d4a32488322ffBb9835a7cF2c7e2202C:vechain+vechain%2Fvtho", + "blockHash": "0x00e12ad4756dfabaf0fac311833ea869f156802c0c55789c57d12f36e9be091e", + "blockHeight": 14756564, + "contract": undefined, + "extra": Object {}, + "fee": "0", + "hash": "0x80f33bdc6996b5226be4142b46796b4edf38813e54d018b3b4dc91c042da31f4", + "id": "0x80f33bdc6996b5226be4142b46796b4edf38813e54d018b3b4dc91c042da31f4", + "operator": undefined, + "recipients": Array [ + "0x32476193d4a32488322ffbb9835a7cf2c7e2202c", + ], + "senders": Array [ + "0x4f6fc409e152d33843cf4982d414c1dd0879277e", + ], + "standard": undefined, + "tokenId": undefined, + "type": "IN", + "value": "50000000000000000000", + }, + Object { + "accountId": "js:2:vechain:0x32476193d4a32488322ffBb9835a7cF2c7e2202C:vechain+vechain%2Fvtho", + "blockHash": "0x00e165e7e0ccf37f847d52403be182c766ad968f7baaf32a2a35470432efb597", + "blockHeight": 14771687, + "contract": undefined, + "extra": Object {}, + "fee": "0", + "hash": "0x84fa3d9f0782b044772ac42a52e6fb5c24abc2485e361e98e695637af1552ab1", + "id": "0x84fa3d9f0782b044772ac42a52e6fb5c24abc2485e361e98e695637af1552ab1", + "operator": undefined, + "recipients": Array [ + "0xc09f17e9e35bca0de7539a4372fc37d941736717", + ], + "senders": Array [ + "0x32476193d4a32488322ffbb9835a7cf2c7e2202c", + ], + "standard": undefined, + "tokenId": undefined, + "type": "OUT", + "value": "10000000000000000000", + }, + Object { + "accountId": "js:2:vechain:0x32476193d4a32488322ffBb9835a7cF2c7e2202C:vechain+vechain%2Fvtho", + "blockHash": "0x00e1ee3d93724c89703c7f92515d891b85053afcc7c5e3905b2faf73f57920cd", + "blockHeight": 14806589, + "contract": undefined, + "extra": Object {}, + "fee": "0", + "hash": "0x8c9008614928f89b1b46f7c01e1ca4d6f75ca6c71c91fb6f4b8fa9c148eec216", + "id": "0x8c9008614928f89b1b46f7c01e1ca4d6f75ca6c71c91fb6f4b8fa9c148eec216", + "operator": undefined, + "recipients": Array [ + "0x087a578ca2879e8d2345bab3b0093cfe2aa3d74a", + ], + "senders": Array [ + "0x32476193d4a32488322ffbb9835a7cf2c7e2202c", + ], + "standard": undefined, + "tokenId": undefined, + "type": "OUT", + "value": "10000000000000000000", + }, + Object { + "accountId": "js:2:vechain:0x32476193d4a32488322ffBb9835a7cF2c7e2202C:vechain+vechain%2Fvtho", + "blockHash": "0x00de83fe83af760ac74ed0ec5f1f841f97431f9580dde655a756435c391f4d6a", + "blockHeight": 14582782, + "contract": undefined, + "extra": Object {}, + "fee": "0", + "hash": "0xa7415e46d76e56668f7ca1cf980e360541092e42f2506a0e755621d09cadb617", + "id": "0xa7415e46d76e56668f7ca1cf980e360541092e42f2506a0e755621d09cadb617", + "operator": undefined, + "recipients": Array [ + "0x32476193d4a32488322ffbb9835a7cf2c7e2202c", + ], + "senders": Array [ + "0x4f6fc409e152d33843cf4982d414c1dd0879277e", + ], + "standard": undefined, + "tokenId": undefined, + "type": "IN", + "value": "50000000000000000000", + }, + Object { + "accountId": "js:2:vechain:0x32476193d4a32488322ffBb9835a7cF2c7e2202C:vechain+vechain%2Fvtho", + "blockHash": "0x00df316f944e16315aa65c71c74a98405a2f1c86fee02d9048e61bf433d3acfb", + "blockHeight": 14627183, + "contract": undefined, + "extra": Object {}, + "fee": "0", + "hash": "0xb0cfc6d7451d8bcb51100be9072de8a720bf466ee3b25e36fc76523eb7836478", + "id": "0xb0cfc6d7451d8bcb51100be9072de8a720bf466ee3b25e36fc76523eb7836478", + "operator": undefined, + "recipients": Array [ + "0x32476193d4a32488322ffbb9835a7cf2c7e2202c", + ], + "senders": Array [ + "0xb437deeac72ec1df37d38304089674962bec3532", + ], + "standard": undefined, + "tokenId": undefined, + "type": "IN", + "value": "50000000000000000000", + }, + Object { + "accountId": "js:2:vechain:0x32476193d4a32488322ffBb9835a7cF2c7e2202C:vechain+vechain%2Fvtho", + "blockHash": "0x00de834410c6a6b566872943805ab8e7398e102dbf313dda24d8b91c1cf95f43", + "blockHeight": 14582596, + "contract": undefined, + "extra": Object {}, + "fee": "0", + "hash": "0xb2afcd89995e087dbba47a5ed7d2a147cf1a8529ffb1c7e32053ba0dcb074a52", + "id": "0xb2afcd89995e087dbba47a5ed7d2a147cf1a8529ffb1c7e32053ba0dcb074a52", + "operator": undefined, + "recipients": Array [ + "0x32476193d4a32488322ffbb9835a7cf2c7e2202c", + ], + "senders": Array [ + "0x4f6fc409e152d33843cf4982d414c1dd0879277e", + ], + "standard": undefined, + "tokenId": undefined, + "type": "IN", + "value": "50000000000000000000", + }, + Object { + "accountId": "js:2:vechain:0x32476193d4a32488322ffBb9835a7cF2c7e2202C:vechain+vechain%2Fvtho", + "blockHash": "0x00e12ad741bbedb8ff7bf58041a226425eaebeedb2c562777c031e5af7a22ac6", + "blockHeight": 14756567, + "contract": undefined, + "extra": Object {}, + "fee": "0", + "hash": "0xdc62fee5c201eb7048af1d0c0cd3bf9e5c0135a545fd04360310b796ebd10866", + "id": "0xdc62fee5c201eb7048af1d0c0cd3bf9e5c0135a545fd04360310b796ebd10866", + "operator": undefined, + "recipients": Array [ + "0x60cb198ad3ef380b74504d5661456554a5091a54", + ], + "senders": Array [ + "0x32476193d4a32488322ffbb9835a7cf2c7e2202c", + ], + "standard": undefined, + "tokenId": undefined, + "type": "OUT", + "value": "10000000000000000000", + }, + ], + Array [ + Object { + "accountId": "js:2:vechain:0xC09f17E9e35BCa0De7539A4372FC37D941736717:vechain", + "blockHash": "0x00de836c09501033679fc559683a6ebfe6d8eac77dcc60e618170e09510329be", + "blockHeight": 14582636, + "contract": undefined, + "extra": Object {}, + "fee": "0", + "hash": "0x113c6b7b190e8f672874af4bf43636e4b8cc28280d91357c450ee2d384a349c0", + "id": "0x113c6b7b190e8f672874af4bf43636e4b8cc28280d91357c450ee2d384a349c0", + "operator": undefined, + "recipients": Array [ + "0xc09f17e9e35bca0de7539a4372fc37d941736717", + ], + "senders": Array [ + "0x4f6fc409e152d33843cf4982d414c1dd0879277e", + ], + "standard": undefined, + "tokenId": undefined, + "type": "IN", + "value": "500000000000000000000", + }, + Object { + "accountId": "js:2:vechain:0xC09f17E9e35BCa0De7539A4372FC37D941736717:vechain", + "blockHash": "0x00e165a75ec2e2338f7b83186e0043f747268e377f26d36eeb61cce54a18647f", + "blockHeight": 14771623, + "contract": undefined, + "extra": Object {}, + "fee": "0", + "hash": "0x3598c9ee523740a4ac85a400893e7268eb47a5aec54f7ca40642e6cee0e945b8", + "id": "0x3598c9ee523740a4ac85a400893e7268eb47a5aec54f7ca40642e6cee0e945b8", + "operator": undefined, + "recipients": Array [ + "0xc09f17e9e35bca0de7539a4372fc37d941736717", + ], + "senders": Array [ + "0x32476193d4a32488322ffbb9835a7cf2c7e2202c", + ], + "standard": undefined, + "tokenId": undefined, + "type": "IN", + "value": "10000000000000000000", + }, + Object { + "accountId": "js:2:vechain:0xC09f17E9e35BCa0De7539A4372FC37D941736717:vechain", + "blockHash": "0x00e013f489e59f7a190ecbdfc7265c077962fd816cba85600ac773d7e18516df", + "blockHeight": 14685172, + "contract": undefined, + "extra": Object {}, + "fee": "0", + "hash": "0x54ab0838986d93bbf774f026819ea9d1633b118666466500fc7eb5ab8fd4399e", + "id": "0x54ab0838986d93bbf774f026819ea9d1633b118666466500fc7eb5ab8fd4399e", + "operator": undefined, + "recipients": Array [ + "0xc09f17e9e35bca0de7539a4372fc37d941736717", + ], + "senders": Array [ + "0xc09f17e9e35bca0de7539a4372fc37d941736717", + ], + "standard": undefined, + "tokenId": undefined, + "type": "IN", + "value": "10000000000000000000", + }, + Object { + "accountId": "js:2:vechain:0xC09f17E9e35BCa0De7539A4372FC37D941736717:vechain", + "blockHash": "0x00dfb70ef235497ee7e161eb7ed5e56efa3e3de80d2a86e4ed68cd1e26c3a915", + "blockHeight": 14661390, + "contract": undefined, + "extra": Object {}, + "fee": "0", + "hash": "0x998c9c5409847b0427498d10b4e5802d04cf400c18b17f62d2f1023c4414a29c", + "id": "0x998c9c5409847b0427498d10b4e5802d04cf400c18b17f62d2f1023c4414a29c", + "operator": undefined, + "recipients": Array [ + "0xc09f17e9e35bca0de7539a4372fc37d941736717", + ], + "senders": Array [ + "0x32476193d4a32488322ffbb9835a7cf2c7e2202c", + ], + "standard": undefined, + "tokenId": undefined, + "type": "IN", + "value": "10000000000000000000", + }, + Object { + "accountId": "js:2:vechain:0xC09f17E9e35BCa0De7539A4372FC37D941736717:vechain", + "blockHash": "0x00e0789664f403462cb8418ea100a070a002d23ba1a2198392e279f999b3cd7a", + "blockHeight": 14710934, + "contract": undefined, + "extra": Object {}, + "fee": "0", + "hash": "0xa10016ab14fec6b80cf17cf959fae732b720b8d07f5463a69d23fd9fec9b52fd", + "id": "0xa10016ab14fec6b80cf17cf959fae732b720b8d07f5463a69d23fd9fec9b52fd", + "operator": undefined, + "recipients": Array [ + "0xc09f17e9e35bca0de7539a4372fc37d941736717", + ], + "senders": Array [ + "0x32476193d4a32488322ffbb9835a7cf2c7e2202c", + ], + "standard": undefined, + "tokenId": undefined, + "type": "IN", + "value": "10000000000000000000", + }, + Object { + "accountId": "js:2:vechain:0xC09f17E9e35BCa0De7539A4372FC37D941736717:vechain", + "blockHash": "0x00e165e4c20dff0bd676d2b743e52d94e0333641a1e6fecbc2869157ed93ead0", + "blockHeight": 14771684, + "contract": undefined, + "extra": Object {}, + "fee": "0", + "hash": "0xc0ba9cfdf54317686b2407c9421b549e9e8543d78b6d2577b5ec0260bbff410c", + "id": "0xc0ba9cfdf54317686b2407c9421b549e9e8543d78b6d2577b5ec0260bbff410c", + "operator": undefined, + "recipients": Array [ + "0xc09f17e9e35bca0de7539a4372fc37d941736717", + ], + "senders": Array [ + "0x32476193d4a32488322ffbb9835a7cf2c7e2202c", + ], + "standard": undefined, + "tokenId": undefined, + "type": "IN", + "value": "10000000000000000000", + }, + Object { + "accountId": "js:2:vechain:0xC09f17E9e35BCa0De7539A4372FC37D941736717:vechain", + "blockHash": "0x00e012b91a052de6e05659898c81d6849e6ee25c734e0f108d75bf30028beca0", + "blockHeight": 14684857, + "contract": undefined, + "extra": Object {}, + "fee": "0", + "hash": "0xf00038075fa0a7f7640c85c1b42a24ec3d23db1df74faa45ff2b4652f441c3f2", + "id": "0xf00038075fa0a7f7640c85c1b42a24ec3d23db1df74faa45ff2b4652f441c3f2", + "operator": undefined, + "recipients": Array [ + "0xc09f17e9e35bca0de7539a4372fc37d941736717", + ], + "senders": Array [ + "0x32476193d4a32488322ffbb9835a7cf2c7e2202c", + ], + "standard": undefined, + "tokenId": undefined, + "type": "IN", + "value": "10000000000000000000", + }, + ], + Array [ + Object { + "accountId": "js:2:vechain:0xC09f17E9e35BCa0De7539A4372FC37D941736717:vechain+vechain%2Fvtho", + "blockHash": "0x00de836c09501033679fc559683a6ebfe6d8eac77dcc60e618170e09510329be", + "blockHeight": 14582636, + "contract": undefined, + "extra": Object {}, + "fee": "0", + "hash": "0x113c6b7b190e8f672874af4bf43636e4b8cc28280d91357c450ee2d384a349c0", + "id": "0x113c6b7b190e8f672874af4bf43636e4b8cc28280d91357c450ee2d384a349c0", + "operator": undefined, + "recipients": Array [ + "0xc09f17e9e35bca0de7539a4372fc37d941736717", + ], + "senders": Array [ + "0x4f6fc409e152d33843cf4982d414c1dd0879277e", + ], + "standard": undefined, + "tokenId": undefined, + "type": "IN", + "value": "50000000000000000000", + }, + Object { + "accountId": "js:2:vechain:0xC09f17E9e35BCa0De7539A4372FC37D941736717:vechain+vechain%2Fvtho", + "blockHash": "0x00e165abb9410d15bddd757cda8caececa25c2cb064dbb18fd609e34610b1977", + "blockHeight": 14771627, + "contract": undefined, + "extra": Object {}, + "fee": "0", + "hash": "0x1d1287e237368f56c6e9171505c59737ed2f7d16db1e8b0b8a19581cc1c38504", + "id": "0x1d1287e237368f56c6e9171505c59737ed2f7d16db1e8b0b8a19581cc1c38504", + "operator": undefined, + "recipients": Array [ + "0xc09f17e9e35bca0de7539a4372fc37d941736717", + ], + "senders": Array [ + "0x32476193d4a32488322ffbb9835a7cf2c7e2202c", + ], + "standard": undefined, + "tokenId": undefined, + "type": "IN", + "value": "10000000000000000000", + }, + Object { + "accountId": "js:2:vechain:0xC09f17E9e35BCa0De7539A4372FC37D941736717:vechain+vechain%2Fvtho", + "blockHash": "0x00e165e7e0ccf37f847d52403be182c766ad968f7baaf32a2a35470432efb597", + "blockHeight": 14771687, + "contract": undefined, + "extra": Object {}, + "fee": "0", + "hash": "0x84fa3d9f0782b044772ac42a52e6fb5c24abc2485e361e98e695637af1552ab1", + "id": "0x84fa3d9f0782b044772ac42a52e6fb5c24abc2485e361e98e695637af1552ab1", + "operator": undefined, + "recipients": Array [ + "0xc09f17e9e35bca0de7539a4372fc37d941736717", + ], + "senders": Array [ + "0x32476193d4a32488322ffbb9835a7cf2c7e2202c", + ], + "standard": undefined, + "tokenId": undefined, + "type": "IN", + "value": "10000000000000000000", + }, + ], + Array [ + Object { + "accountId": "js:2:vechain:0x448251cD681cCcc791F24eA0DD26B38B9663aE74:vechain", + "blockHash": "0x00e1f3ecbe335f6d6c138c60625568c0e1d84d21f097fc92a5b8093a7a17fad2", + "blockHeight": 14808044, + "contract": undefined, + "extra": Object {}, + "fee": "0", + "hash": "0x258569e601c51a05cf6d25266e8a8ba9df28470387543e0c1e65cdc53d58331a", + "id": "0x258569e601c51a05cf6d25266e8a8ba9df28470387543e0c1e65cdc53d58331a", + "operator": undefined, + "recipients": Array [ + "0x448251cd681cccc791f24ea0dd26b38b9663ae74", + ], + "senders": Array [ + "0x087fb90d37a2e2462cccfedd74ecb24c68329741", + ], + "standard": undefined, + "tokenId": undefined, + "type": "IN", + "value": "50000000000000000000", + }, + Object { + "accountId": "js:2:vechain:0x448251cD681cCcc791F24eA0DD26B38B9663aE74:vechain", + "blockHash": "0x00e10a29642c905914dc75996682a0b0681ebfc2dbe5f7cae83d2b427ff37c6c", + "blockHeight": 14748201, + "contract": undefined, + "extra": Object {}, + "fee": "0", + "hash": "0x561bbf76a2a4721a4bdb6435356bce59fd71984fda866553a8cbd295cac05494", + "id": "0x561bbf76a2a4721a4bdb6435356bce59fd71984fda866553a8cbd295cac05494", + "operator": undefined, + "recipients": Array [ + "0x448251cd681cccc791f24ea0dd26b38b9663ae74", + ], + "senders": Array [ + "0x32476193d4a32488322ffbb9835a7cf2c7e2202c", + ], + "standard": undefined, + "tokenId": undefined, + "type": "IN", + "value": "10000000000000000000", + }, + Object { + "accountId": "js:2:vechain:0x448251cD681cCcc791F24eA0DD26B38B9663aE74:vechain", + "blockHash": "0x00e100488ffab60b466584ee57fb2b525b2ac60090b1a67fe529ec35e351b4ed", + "blockHeight": 14745672, + "contract": undefined, + "extra": Object {}, + "fee": "0", + "hash": "0x6a321fc0f23819df7f2bee821402cf6e85821fec079ac64d1fc2d22425e63566", + "id": "0x6a321fc0f23819df7f2bee821402cf6e85821fec079ac64d1fc2d22425e63566", + "operator": undefined, + "recipients": Array [ + "0x448251cd681cccc791f24ea0dd26b38b9663ae74", + ], + "senders": Array [ + "0x32476193d4a32488322ffbb9835a7cf2c7e2202c", + ], + "standard": undefined, + "tokenId": undefined, + "type": "IN", + "value": "10000000000000000000", + }, + Object { + "accountId": "js:2:vechain:0x448251cD681cCcc791F24eA0DD26B38B9663aE74:vechain", + "blockHash": "0x00e0ffcaf4c50c1cea51e2df2d13654bfb1ae304ac52a396a20977d1f6b1eadb", + "blockHeight": 14745546, + "contract": undefined, + "extra": Object {}, + "fee": "0", + "hash": "0x86bc2705c9af907f3e6d549340eec706eb8c31fa628a9d43907f3d239fd9036e", + "id": "0x86bc2705c9af907f3e6d549340eec706eb8c31fa628a9d43907f3d239fd9036e", + "operator": undefined, + "recipients": Array [ + "0x448251cd681cccc791f24ea0dd26b38b9663ae74", + ], + "senders": Array [ + "0x32476193d4a32488322ffbb9835a7cf2c7e2202c", + ], + "standard": undefined, + "tokenId": undefined, + "type": "IN", + "value": "10000000000000000000", + }, + Object { + "accountId": "js:2:vechain:0x448251cD681cCcc791F24eA0DD26B38B9663aE74:vechain", + "blockHash": "0x00e0fecc22a788f3d4ca53a2a3e9ce9586203be8549b69597a9bf220a01ff69f", + "blockHeight": 14745292, + "contract": undefined, + "extra": Object {}, + "fee": "0", + "hash": "0xc483fff886b27f38fc726c04135db34ca1af1970666f7117c5c698f78d71df52", + "id": "0xc483fff886b27f38fc726c04135db34ca1af1970666f7117c5c698f78d71df52", + "operator": undefined, + "recipients": Array [ + "0x448251cd681cccc791f24ea0dd26b38b9663ae74", + ], + "senders": Array [ + "0x32476193d4a32488322ffbb9835a7cf2c7e2202c", + ], + "standard": undefined, + "tokenId": undefined, + "type": "IN", + "value": "10000000000000000000", + }, + Object { + "accountId": "js:2:vechain:0x448251cD681cCcc791F24eA0DD26B38B9663aE74:vechain", + "blockHash": "0x00e14f1f68077bb40820f4610dfcd1484bb2f414caf450144302fa9fe0843f16", + "blockHeight": 14765855, + "contract": undefined, + "extra": Object {}, + "fee": "0", + "hash": "0xf043decd93cf358b3df16103d27283d286d1cbfb2a6f6df989aaed53146a5864", + "id": "0xf043decd93cf358b3df16103d27283d286d1cbfb2a6f6df989aaed53146a5864", + "operator": undefined, + "recipients": Array [ + "0x448251cd681cccc791f24ea0dd26b38b9663ae74", + ], + "senders": Array [ + "0x32476193d4a32488322ffbb9835a7cf2c7e2202c", + ], + "standard": undefined, + "tokenId": undefined, + "type": "IN", + "value": "10000000000000000000", + }, + ], + Array [ + Object { + "accountId": "js:2:vechain:0x448251cD681cCcc791F24eA0DD26B38B9663aE74:vechain+vechain%2Fvtho", + "blockHash": "0x00e10a2cf72a615d0b3a7247085832a293aba8d1fae56ca27e2de29cf3dbe3d8", + "blockHeight": 14748204, + "contract": undefined, + "extra": Object {}, + "fee": "0", + "hash": "0x0425abc630df15bc96a1e60317653f13aeff53708cc57fce9709485fb463b880", + "id": "0x0425abc630df15bc96a1e60317653f13aeff53708cc57fce9709485fb463b880", + "operator": undefined, + "recipients": Array [ + "0x448251cd681cccc791f24ea0dd26b38b9663ae74", + ], + "senders": Array [ + "0x32476193d4a32488322ffbb9835a7cf2c7e2202c", + ], + "standard": undefined, + "tokenId": undefined, + "type": "IN", + "value": "10000000000000000000", + }, + Object { + "accountId": "js:2:vechain:0x448251cD681cCcc791F24eA0DD26B38B9663aE74:vechain+vechain%2Fvtho", + "blockHash": "0x00e10a9a598dba300f26779d1882222e871c15cef5e1ef079e1a1dcfdf5f7e66", + "blockHeight": 14748314, + "contract": undefined, + "extra": Object {}, + "fee": "0", + "hash": "0x05ff1133456f3252409b8ae0eeb0d95f0893058b9af3c10c730e040ad14ae58c", + "id": "0x05ff1133456f3252409b8ae0eeb0d95f0893058b9af3c10c730e040ad14ae58c", + "operator": undefined, + "recipients": Array [ + "0x448251cd681cccc791f24ea0dd26b38b9663ae74", + ], + "senders": Array [ + "0x32476193d4a32488322ffbb9835a7cf2c7e2202c", + ], + "standard": undefined, + "tokenId": undefined, + "type": "IN", + "value": "10000000000000000000", + }, + Object { + "accountId": "js:2:vechain:0x448251cD681cCcc791F24eA0DD26B38B9663aE74:vechain+vechain%2Fvtho", + "blockHash": "0x00e14f23353a7566256360d30afb280a9b25fd721858a3ee61bf8b705ab215ce", + "blockHeight": 14765859, + "contract": undefined, + "extra": Object {}, + "fee": "0", + "hash": "0x165bd4ead23814e9f4ea78e272379ba0b4b7ac8b0625aecc9a7ee0a4e83260d0", + "id": "0x165bd4ead23814e9f4ea78e272379ba0b4b7ac8b0625aecc9a7ee0a4e83260d0", + "operator": undefined, + "recipients": Array [ + "0x448251cd681cccc791f24ea0dd26b38b9663ae74", + ], + "senders": Array [ + "0x32476193d4a32488322ffbb9835a7cf2c7e2202c", + ], + "standard": undefined, + "tokenId": undefined, + "type": "IN", + "value": "10000000000000000000", + }, + Object { + "accountId": "js:2:vechain:0x448251cD681cCcc791F24eA0DD26B38B9663aE74:vechain+vechain%2Fvtho", + "blockHash": "0x00e1042a110b596488489a66edff8d28c253a4bea68409c310f2673ed6706b2f", + "blockHeight": 14746666, + "contract": undefined, + "extra": Object {}, + "fee": "0", + "hash": "0x28acdf8cffbb638699fcf4de4a7114c9d252e40fc461096f584d5adfa40ede63", + "id": "0x28acdf8cffbb638699fcf4de4a7114c9d252e40fc461096f584d5adfa40ede63", + "operator": undefined, + "recipients": Array [ + "0x448251cd681cccc791f24ea0dd26b38b9663ae74", + ], + "senders": Array [ + "0x32476193d4a32488322ffbb9835a7cf2c7e2202c", + ], + "standard": undefined, + "tokenId": undefined, + "type": "IN", + "value": "10000000000000000000", + }, + Object { + "accountId": "js:2:vechain:0x448251cD681cCcc791F24eA0DD26B38B9663aE74:vechain+vechain%2Fvtho", + "blockHash": "0x00e129e8782cb7791042401d09c685977e914a2b16b8d730bfa42c728d192419", + "blockHeight": 14756328, + "contract": undefined, + "extra": Object {}, + "fee": "0", + "hash": "0x55e79f13e5772076d19a059bade544a879ddfde0089c8ee6b745fac3ef7e709c", + "id": "0x55e79f13e5772076d19a059bade544a879ddfde0089c8ee6b745fac3ef7e709c", + "operator": undefined, + "recipients": Array [ + "0x448251cd681cccc791f24ea0dd26b38b9663ae74", + ], + "senders": Array [ + "0x32476193d4a32488322ffbb9835a7cf2c7e2202c", + ], + "standard": undefined, + "tokenId": undefined, + "type": "IN", + "value": "10000000000000000000", + }, + Object { + "accountId": "js:2:vechain:0x448251cD681cCcc791F24eA0DD26B38B9663aE74:vechain+vechain%2Fvtho", + "blockHash": "0x00e129fa79e527b3ab5f26eebf8c230895d59e3cf6e880a0b1a30b0be7ecbe5b", + "blockHeight": 14756346, + "contract": undefined, + "extra": Object {}, + "fee": "0", + "hash": "0x7249b4ace7dc34192bf99caf29ebb0ae65f26d00008a84e5290806ac1f9be283", + "id": "0x7249b4ace7dc34192bf99caf29ebb0ae65f26d00008a84e5290806ac1f9be283", + "operator": undefined, + "recipients": Array [ + "0x448251cd681cccc791f24ea0dd26b38b9663ae74", + ], + "senders": Array [ + "0x32476193d4a32488322ffbb9835a7cf2c7e2202c", + ], + "standard": undefined, + "tokenId": undefined, + "type": "IN", + "value": "10000000000000000000", + }, + ], + Array [ + Object { + "accountId": "js:2:vechain:0x087Fb90d37A2E2462CCcFEDD74eCB24c68329741:vechain", + "blockHash": "0x00e1f3ecbe335f6d6c138c60625568c0e1d84d21f097fc92a5b8093a7a17fad2", + "blockHeight": 14808044, + "contract": undefined, + "extra": Object {}, + "fee": "0", + "hash": "0x258569e601c51a05cf6d25266e8a8ba9df28470387543e0c1e65cdc53d58331a", + "id": "0x258569e601c51a05cf6d25266e8a8ba9df28470387543e0c1e65cdc53d58331a", + "operator": undefined, + "recipients": Array [ + "0x448251cd681cccc791f24ea0dd26b38b9663ae74", + ], + "senders": Array [ + "0x087fb90d37a2e2462cccfedd74ecb24c68329741", + ], + "standard": undefined, + "tokenId": undefined, + "type": "OUT", + "value": "50000000000000000000", + }, + Object { + "accountId": "js:2:vechain:0x087Fb90d37A2E2462CCcFEDD74eCB24c68329741:vechain", + "blockHash": "0x00e16a355b657779dc97f974b69e9b9a1ed7dd5096c11e2c8c788dec2ac36ba2", + "blockHeight": 14772789, + "contract": undefined, + "extra": Object {}, + "fee": "0", + "hash": "0x85a951fdda4fe9ee3a7c6bec319977803448581d18fd7af0a2f8089187e1aeaf", + "id": "0x85a951fdda4fe9ee3a7c6bec319977803448581d18fd7af0a2f8089187e1aeaf", + "operator": undefined, + "recipients": Array [ + "0x087fb90d37a2e2462cccfedd74ecb24c68329741", + ], + "senders": Array [ + "0x32476193d4a32488322ffbb9835a7cf2c7e2202c", + ], + "standard": undefined, + "tokenId": undefined, + "type": "IN", + "value": "90000000000000000000", + }, + Object { + "accountId": "js:2:vechain:0x087Fb90d37A2E2462CCcFEDD74eCB24c68329741:vechain", + "blockHash": "0x00e12a0a3b0431863e121da091199a91afb600aa71c4bd6948d72282a6ac3af5", + "blockHeight": 14756362, + "contract": undefined, + "extra": Object {}, + "fee": "0", + "hash": "0xaa8eb048f67afe9dab4981e7ecbf89c9c3ed677fa9ab67b9ebbc6932d7f8a41c", + "id": "0xaa8eb048f67afe9dab4981e7ecbf89c9c3ed677fa9ab67b9ebbc6932d7f8a41c", + "operator": undefined, + "recipients": Array [ + "0x087fb90d37a2e2462cccfedd74ecb24c68329741", + ], + "senders": Array [ + "0x32476193d4a32488322ffbb9835a7cf2c7e2202c", + ], + "standard": undefined, + "tokenId": undefined, + "type": "IN", + "value": "10000000000000000000", + }, + ], + Array [ + Object { + "accountId": "js:2:vechain:0x087Fb90d37A2E2462CCcFEDD74eCB24c68329741:vechain+vechain%2Fvtho", + "blockHash": "0x00e12a0e4e2cfd9adb402fe2725c682e6d982013de2a218fedff5270590addc0", + "blockHeight": 14756366, + "contract": undefined, + "extra": Object {}, + "fee": "0", + "hash": "0x18bd5cf6d5168033ed07da8088ccc43230f024f64c66483ba25f3426c366c670", + "id": "0x18bd5cf6d5168033ed07da8088ccc43230f024f64c66483ba25f3426c366c670", + "operator": undefined, + "recipients": Array [ + "0x087fb90d37a2e2462cccfedd74ecb24c68329741", + ], + "senders": Array [ + "0x32476193d4a32488322ffbb9835a7cf2c7e2202c", + ], + "standard": undefined, + "tokenId": undefined, + "type": "IN", + "value": "10000000000000000000", + }, + ], + Array [], + Array [ + Object { + "accountId": "js:2:vechain:0x60cb198AD3eF380B74504D5661456554A5091a54:vechain+vechain%2Fvtho", + "blockHash": "0x00e12af6ddbf112e7325a5b2b3d62ef65746c730a45c43e477c872d295e7bfe6", + "blockHeight": 14756598, + "contract": undefined, + "extra": Object {}, + "fee": "0", + "hash": "0x0abd7211b4d7220eaa5a33315126b45f4f90292de0b4fbe45e5d40c0d5f6c78e", + "id": "0x0abd7211b4d7220eaa5a33315126b45f4f90292de0b4fbe45e5d40c0d5f6c78e", + "operator": undefined, + "recipients": Array [ + "0x60cb198ad3ef380b74504d5661456554a5091a54", + ], + "senders": Array [ + "0x32476193d4a32488322ffbb9835a7cf2c7e2202c", + ], + "standard": undefined, + "tokenId": undefined, + "type": "IN", + "value": "10000000000000000000", + }, + Object { + "accountId": "js:2:vechain:0x60cb198AD3eF380B74504D5661456554A5091a54:vechain+vechain%2Fvtho", + "blockHash": "0x00e12ad741bbedb8ff7bf58041a226425eaebeedb2c562777c031e5af7a22ac6", + "blockHeight": 14756567, + "contract": undefined, + "extra": Object {}, + "fee": "0", + "hash": "0xdc62fee5c201eb7048af1d0c0cd3bf9e5c0135a545fd04360310b796ebd10866", + "id": "0xdc62fee5c201eb7048af1d0c0cd3bf9e5c0135a545fd04360310b796ebd10866", + "operator": undefined, + "recipients": Array [ + "0x60cb198ad3ef380b74504d5661456554a5091a54", + ], + "senders": Array [ + "0x32476193d4a32488322ffbb9835a7cf2c7e2202c", + ], + "standard": undefined, + "tokenId": undefined, + "type": "IN", + "value": "10000000000000000000", + }, + ], + Array [], + Array [ + Object { + "accountId": "js:2:vechain:0x087a578ca2879E8D2345baB3B0093CfE2Aa3d74A:vechain+vechain%2Fvtho", + "blockHash": "0x00e1ee3d93724c89703c7f92515d891b85053afcc7c5e3905b2faf73f57920cd", + "blockHeight": 14806589, + "contract": undefined, + "extra": Object {}, + "fee": "0", + "hash": "0x8c9008614928f89b1b46f7c01e1ca4d6f75ca6c71c91fb6f4b8fa9c148eec216", + "id": "0x8c9008614928f89b1b46f7c01e1ca4d6f75ca6c71c91fb6f4b8fa9c148eec216", + "operator": undefined, + "recipients": Array [ + "0x087a578ca2879e8d2345bab3b0093cfe2aa3d74a", + ], + "senders": Array [ + "0x32476193d4a32488322ffbb9835a7cf2c7e2202c", + ], + "standard": undefined, + "tokenId": undefined, + "type": "IN", + "value": "10000000000000000000", + }, + ], + Array [], + Array [], +] +`; diff --git a/libs/ledger-live-common/src/families/vechain/account.ts b/libs/ledger-live-common/src/families/vechain/account.ts new file mode 100644 index 000000000000..abd7db27179e --- /dev/null +++ b/libs/ledger-live-common/src/families/vechain/account.ts @@ -0,0 +1,49 @@ +import type { Account, Operation } from "@ledgerhq/types-live"; +import { getAccountUnit } from "../../account"; +import { formatCurrencyUnit } from "../../currencies"; +import { Unit } from "@ledgerhq/types-cryptoassets"; + +function formatAccountSpecifics(account: Account): string { + const unit = getAccountUnit(account); + const formatConfig = { + disableRounding: true, + alwaysShowSign: false, + showCode: true, + }; + + let str = " "; + + str += + formatCurrencyUnit(unit, account.spendableBalance, formatConfig) + + " spendable. "; + + return str; +} + +function formatOperationSpecifics(op: Operation, unit: Unit): string { + const { additionalField } = op.extra; + + let str = " "; + + const formatConfig = { + disableRounding: true, + alwaysShowSign: false, + showCode: true, + }; + + str += + additionalField && !additionalField.isNaN() + ? `\n additionalField: ${ + unit + ? formatCurrencyUnit(unit, additionalField, formatConfig) + : additionalField + }` + : ""; + + return str; +} + +export default { + formatAccountSpecifics, + formatOperationSpecifics, +}; diff --git a/libs/ledger-live-common/src/families/vechain/api/index.ts b/libs/ledger-live-common/src/families/vechain/api/index.ts new file mode 100644 index 000000000000..730830b92102 --- /dev/null +++ b/libs/ledger-live-common/src/families/vechain/api/index.ts @@ -0,0 +1,3 @@ +import { getAccount, getOperations } from "./sdk"; +export * from "./sdk"; +export default { getAccount, getOperations }; diff --git a/libs/ledger-live-common/src/families/vechain/api/sdk.ts b/libs/ledger-live-common/src/families/vechain/api/sdk.ts new file mode 100644 index 000000000000..bcb7b2c34b87 --- /dev/null +++ b/libs/ledger-live-common/src/families/vechain/api/sdk.ts @@ -0,0 +1,141 @@ +import { getEnv } from "../../../env"; +import network from "../../../network"; +import { + AccountResponse, + VetTxsQuery, + TokenTxsQuery, + Query, + QueryResponse, +} from "./types"; +import type { Operation } from "@ledgerhq/types-live"; +import { + mapVetTransfersToOperations, + mapTokenTransfersToOperations, +} from "../utils/mapping-utils"; +import { padAddress } from "../utils/pad-address"; +import { TransferEventSignature } from "../contracts/constants"; +import { Transaction } from "thor-devkit"; +import { HEX_PREFIX } from "../constants"; + +const BASE_URL = getEnv("API_VECHAIN_THOREST"); + +export const getAccount = async (address: string): Promise => { + const { data } = await network({ + method: "GET", + url: `${BASE_URL}/accounts/${address}`, + }); + + return data; +}; + +/** + * Get VET operations + * @param accountId + * @param addr + * @param startAt + * @returns an array of operations + */ +export const getOperations = async ( + accountId: string, + addr: string, + startAt: number +): Promise => { + const query: VetTxsQuery = { + range: { + unit: "block", + from: startAt, + }, + criteriaSet: [{ sender: addr }, { recipient: addr }], + order: "desc", + }; + + const { data } = await network({ + method: "POST", + url: `${BASE_URL}/logs/transfer`, + data: JSON.stringify(query), + }); + + return mapVetTransfersToOperations(data, accountId, addr); +}; + +/** + * Get operations for a fungible token + * @param accountId + * @param addr + * @param tokenAddr - The token address (The VTHO token address is available from constants.ts) + * @param startAt + * @returns an array of operations + */ +export const getTokenOperations = async ( + accountId: string, + addr: string, + tokenAddr: string, + startAt: number +): Promise => { + const paddedAddress = padAddress(addr); + + const query: TokenTxsQuery = { + range: { + unit: "block", + from: startAt, + }, + criteriaSet: [ + { + address: tokenAddr, + topic0: TransferEventSignature, + topic1: paddedAddress, + }, + { + address: tokenAddr, + topic0: TransferEventSignature, + topic2: paddedAddress, + }, + ], + order: "desc", + }; + + const { data } = await network({ + method: "POST", + url: `${BASE_URL}/logs/event`, + data: JSON.stringify(query), + }); + + return mapTokenTransfersToOperations(data, accountId, addr); +}; + +/** + * Submit a transaction and return the ID + * @param tx - The transaction to submit + * @returns transaction ID + */ +export const submit = async (tx: Transaction): Promise => { + const encodedRawTx = { + raw: `${HEX_PREFIX}${tx.encode().toString("hex")}`, + }; + + const { data } = await network({ + method: "POST", + url: `${BASE_URL}/transactions`, + data: encodedRawTx, + }); + + // Expect a transaction ID + if (!data.id) throw Error("Expected an ID to be returned"); + + return data.id; +}; + +/** + * Query the blockchain + * @param queryData - The query data + * @returns a result of the query + */ +export const query = async (queryData: Query[]): Promise => { + const { data } = await network({ + method: "POST", + url: `${BASE_URL}/accounts/*`, + data: { clauses: queryData }, + }); + + return data; +}; diff --git a/libs/ledger-live-common/src/families/vechain/api/types.ts b/libs/ledger-live-common/src/families/vechain/api/types.ts new file mode 100644 index 000000000000..8c26d6f3d033 --- /dev/null +++ b/libs/ledger-live-common/src/families/vechain/api/types.ts @@ -0,0 +1,81 @@ +export interface AccountResponse { + balance: string; + energy: string; + hasCode: boolean; +} + +export interface LogMeta { + blockID: string; + blockNumber: number; + blockTimestamp: number; + txID: string; + txOrigin: string; + clauseIndex: number; +} + +export interface TransferLog { + sender: string; + recipient: string; + amount: string; + meta: LogMeta; +} + +export interface EventLog { + address: string; + topics: string[]; + data: string; + meta: LogMeta; +} + +export interface Range { + unit: "block"; + from: number; + to?: number; +} + +export interface Options { + offset: number; + limit: number; +} + +export interface VetCriteria { + recipient?: string; + sender?: string; +} + +export interface VetTxsQuery { + range?: Range; + options?: Options; + criteriaSet: VetCriteria[]; + order: "desc" | "asc"; +} + +export interface TokenCriteria { + address: string; + topic0?: string; + topic1?: string; + topic2?: string; + topic3?: string; + topic4?: string; +} + +export interface TokenTxsQuery { + range?: Range; + options?: Options; + criteriaSet: TokenCriteria[]; + order: "desc" | "asc"; +} + +export interface Query { + to: string; + data: string; +} + +export interface QueryResponse { + data: string; + events: any[]; + transfers: any[]; + gasUsed: number; + reverted: boolean; + vmError: string; +} diff --git a/libs/ledger-live-common/src/families/vechain/bridge.integration.testWIP b/libs/ledger-live-common/src/families/vechain/bridge.integration.testWIP new file mode 100644 index 000000000000..4f97eadf8097 --- /dev/null +++ b/libs/ledger-live-common/src/families/vechain/bridge.integration.testWIP @@ -0,0 +1,213 @@ +import "../../__tests__/test-helpers/setup"; +import { testBridge } from "../../__tests__/test-helpers/bridge"; +import type { AccountRaw, DatasetTest } from "@ledgerhq/types-live"; +import type { Transaction } from "./types"; +import { fromTransactionRaw } from "./transaction"; +import { DEFAULT_GAS_COEFFICIENT, TESTNET_CHAIN_TAG } from "./constants"; +import { vechain1 } from "./datasets/vechain"; +import { generateNonce } from "./utils/transaction-utils"; +import BigNumber from "bignumber.js"; +import { + setSupportedCurrencies, + listSupportedCurrencies, + getCryptoCurrencyById, +} from "../../currencies"; +import vechainScanAccounts1 from "./datasets/vechain.scanAccounts.1"; +import { AmountRequired, NotEnoughBalance } from "@ledgerhq/errors"; +import VIP180 from "./contracts/abis/VIP180"; +import { CryptoCurrencyId } from "@ledgerhq/types-cryptoassets"; + +const listSupported = listSupportedCurrencies(); +listSupported.push(getCryptoCurrencyById("vechain")); +setSupportedCurrencies(listSupported.map((c) => c.id) as CryptoCurrencyId[]); + +const dataset: DatasetTest = { + implementations: ["js"], + currencies: { + vechain: { + FIXME_ignoreAccountFields: [ + "balance", + "spendableBalance", + "estimateMaxSpendable", + "creationDate", + "blockRef", + ], + FIXME_ignoreOperationFields: ["nonce"], + scanAccounts: [vechainScanAccounts1], + accounts: [ + { + implementations: ["mock", "js"], + transactions: [ + { + name: "Send VET", + transaction: fromTransactionRaw({ + family: "vechain", + mode: "send_vet", + recipient: "0x17733CAb76d9A2112576443F21735789733B1ca3", + amount: "10000000000000000000", + body: { + chainTag: TESTNET_CHAIN_TAG, + blockRef: "0x00634a0c856ec1db", + expiration: 18, + clauses: [ + { + to: "0x17733CAb76d9A2112576443F21735789733B1ca3", + value: "10000000000000000000", + data: "0x", + }, + ], + gasPriceCoef: DEFAULT_GAS_COEFFICIENT, + gas: "0", + dependsOn: null, + nonce: generateNonce(), + }, + }), + expectedStatus: { + amount: new BigNumber("10000000000000000000"), + estimatedFees: new BigNumber("210000000000000000"), + totalSpent: new BigNumber("10000000000000000000"), + errors: {}, + warnings: {}, + }, + }, + { + name: "Send VTHO", + transaction: fromTransactionRaw({ + family: "vechain", + mode: "send_vtho", + recipient: "0x17733CAb76d9A2112576443F21735789733B1ca3", + amount: "9000000000000000000", + body: { + chainTag: TESTNET_CHAIN_TAG, + blockRef: "0x00634a0c856ec1db", + expiration: 18, + clauses: [ + { + to: "0x0000000000000000000000000000456e65726779", + value: 0, + data: VIP180.transfer.encode( + "0x17733CAb76d9A2112576443F21735789733B1ca3", + "9000000000000000000" + ), + }, + ], + gasPriceCoef: DEFAULT_GAS_COEFFICIENT, + gas: "0", + dependsOn: null, + nonce: generateNonce(), + }, + }), + expectedStatus: { + amount: new BigNumber("9000000000000000000"), + estimatedFees: new BigNumber("385030000000000000"), + totalSpent: new BigNumber("9000000000000000000"), + errors: {}, + warnings: {}, + }, + }, + { + name: "Amount required", + transaction: fromTransactionRaw({ + family: "vechain", + mode: "send_vet", + recipient: "0x17733CAb76d9A2112576443F21735789733B1ca3", + amount: "", + body: { + chainTag: TESTNET_CHAIN_TAG, + blockRef: "0x00634a0c856ec1db", + expiration: 18, + clauses: [{ to: "", value: 0, data: "0x" }], + gasPriceCoef: DEFAULT_GAS_COEFFICIENT, + gas: "0", + dependsOn: null, + nonce: generateNonce(), + }, + }), + expectedStatus: { + errors: { + amount: new AmountRequired(), + }, + warnings: {}, + }, + }, + { + name: "VET balance not enough", + transaction: fromTransactionRaw({ + family: "vechain", + mode: "send_vet", + recipient: "0x17733CAb76d9A2112576443F21735789733B1ca3", + amount: "2000000000000000000000", + body: { + chainTag: TESTNET_CHAIN_TAG, + blockRef: "0x00634a0c856ec1db", + expiration: 18, + clauses: [ + { + to: "0x17733CAb76d9A2112576443F21735789733B1ca3", + value: "2000000000000000000000", + data: "0x", + }, + ], + gasPriceCoef: DEFAULT_GAS_COEFFICIENT, + gas: "0", + dependsOn: null, + nonce: generateNonce(), + }, + }), + expectedStatus: { + amount: new BigNumber("2000000000000000000000"), + errors: { + amount: new NotEnoughBalance(), + }, + warnings: {}, + totalSpent: new BigNumber("0"), + estimatedFees: new BigNumber("0"), + }, + }, + { + name: "VTHO balance not enough", + transaction: fromTransactionRaw({ + family: "vechain", + mode: "send_vtho", + recipient: "0x17733CAb76d9A2112576443F21735789733B1ca3", + amount: "2000000000000000000000", + body: { + chainTag: TESTNET_CHAIN_TAG, + blockRef: "0x00634a0c856ec1db", + expiration: 18, + clauses: [ + { + to: "0x17733CAb76d9A2112576443F21735789733B1ca3", + value: "2000000000000000000000", + data: "0x", + }, + ], + gasPriceCoef: DEFAULT_GAS_COEFFICIENT, + gas: "0", + dependsOn: null, + nonce: generateNonce(), + }, + }), + expectedStatus: { + amount: new BigNumber("2000000000000000000000"), + errors: { + amount: new NotEnoughBalance(), + }, + warnings: {}, + totalSpent: new BigNumber("0"), + estimatedFees: new BigNumber("0"), + }, + }, + ], + raw: vechain1 as AccountRaw, + FIXME_tests: [ + "balance is sum of ops", + "empty transaction is equals to itself", + ], + }, + ], + }, + }, +}; + +testBridge(dataset); diff --git a/libs/ledger-live-common/src/families/vechain/bridge/js.ts b/libs/ledger-live-common/src/families/vechain/bridge/js.ts new file mode 100644 index 000000000000..f5f06b2f48ff --- /dev/null +++ b/libs/ledger-live-common/src/families/vechain/bridge/js.ts @@ -0,0 +1,66 @@ +import type { Transaction } from "../types"; +import { makeAccountBridgeReceive } from "../../../bridge/jsHelpers"; + +import { sync, scanAccounts } from "../js-synchronisation"; +import { + AccountBridge, + AccountLike, + CurrencyBridge, +} from "@ledgerhq/types-live"; +import { + createTransaction, + updateTransaction, + prepareTransaction, +} from "../js-transaction"; +import getTransactionStatus from "../js-getTransactionStatus"; +import signOperation from "../js-signOperation"; +import broadcast from "../js-broadcast"; +import BigNumber from "bignumber.js"; +import { calculateFee } from "../utils/transaction-utils"; + +const receive: AccountBridge["receive"] = + makeAccountBridgeReceive(); + +const currencyBridge: CurrencyBridge = { + scanAccounts, + preload: async (): Promise> => { + return {}; + }, + hydrate: (): void => {}, +}; + +const estimateMaxSpendable = async (inputs: { + account: AccountLike; + transaction: Transaction; +}): Promise => { + const { account, transaction } = inputs; + + if (account.type === "Account") { + return account.balance; + } + if (transaction) { + const estimatedFees = await calculateFee( + BigNumber(transaction.body.gas), + transaction.body.gasPriceCoef + ); + return Promise.resolve( + BigNumber.max(0, account.balance.minus(estimatedFees)) + ); + } else { + return account.balance; + } +}; + +const accountBridge: AccountBridge = { + estimateMaxSpendable, + createTransaction, + updateTransaction, + getTransactionStatus, + prepareTransaction, + sync, + receive, + signOperation, + broadcast, +}; + +export default { currencyBridge, accountBridge }; diff --git a/libs/ledger-live-common/src/families/vechain/cli-transaction.ts b/libs/ledger-live-common/src/families/vechain/cli-transaction.ts new file mode 100644 index 000000000000..f35218af1105 --- /dev/null +++ b/libs/ledger-live-common/src/families/vechain/cli-transaction.ts @@ -0,0 +1,62 @@ +import flatMap from "lodash/flatMap"; +import type { Transaction } from "../../generated/types"; +import { Transaction as VechainTransaction } from "./types"; +import type { Account, AccountLike } from "@ledgerhq/types-live"; +import { VTHO_ADDRESS } from "./contracts/constants"; +import VIP180 from "./contracts/abis/VIP180"; + +type Clauses = { + to: string; + data: string; + value: string | number; +}; +function inferTransactions( + transactions: Array<{ + account: AccountLike; + transaction: Transaction; + mainAccount: Account; + }>, + opts: Record +): Transaction[] { + return flatMap(transactions, ({ transaction, account }) => { + let subAccountId = + account.type == "Account" && account.subAccounts + ? account.subAccounts[0].id + : ""; + + if (account.type === "TokenAccount") { + subAccountId = account.id; + } + + const clauses: Array = []; + + if (opts.mode == "send_vet") { + clauses.push({ + to: transaction.recipient, + value: "0x" + transaction.amount.toString(16), + data: "0x", + }); + } else if (opts.mode == "send_vtho") { + clauses.push({ + value: 0, + to: VTHO_ADDRESS, + data: VIP180.transfer.encode( + transaction.recipient, + transaction.amount.toFixed() + ), + }); + } + + return { + ...transaction, + family: "vechain", + mode: opts.mode || "send", + subAccountId: opts.mode == "send_vtho" ? subAccountId : "", + body: { ...(transaction as VechainTransaction).body, clauses }, + } as VechainTransaction; + }); +} + +export default { + inferTransactions, +}; diff --git a/libs/ledger-live-common/src/families/vechain/constants.ts b/libs/ledger-live-common/src/families/vechain/constants.ts new file mode 100644 index 000000000000..cbb1a8a5886c --- /dev/null +++ b/libs/ledger-live-common/src/families/vechain/constants.ts @@ -0,0 +1,5 @@ +export const MAINNET_CHAIN_TAG = 74; +export const TESTNET_CHAIN_TAG = 39; +export const DEFAULT_GAS_COEFFICIENT = 0; +export const HEX_PREFIX = "0x"; +export const ZERO_ADDRESS = "0x0000000000000000000000000000000000000000"; diff --git a/libs/ledger-live-common/src/families/vechain/contracts/abis/VIP180.ts b/libs/ledger-live-common/src/families/vechain/contracts/abis/VIP180.ts new file mode 100644 index 000000000000..dd93faa21e6d --- /dev/null +++ b/libs/ledger-live-common/src/families/vechain/contracts/abis/VIP180.ts @@ -0,0 +1,265 @@ +import { abi } from "thor-devkit"; + +/** + * EVENTS + */ +const ApprovalEvent: abi.Event.Definition = { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "address", + name: "owner", + type: "address", + }, + { + indexed: true, + internalType: "address", + name: "spender", + type: "address", + }, + { + indexed: false, + internalType: "uint256", + name: "value", + type: "uint256", + }, + ], + name: "Approval", + type: "event", +}; +const TransferEvent: abi.Event.Definition = { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "address", + name: "from", + type: "address", + }, + { + indexed: true, + internalType: "address", + name: "to", + type: "address", + }, + { + indexed: false, + internalType: "uint256", + name: "value", + type: "uint256", + }, + ], + name: "Transfer", + type: "event", +}; + +/** + * FUNCTIONS + */ +const allowance: abi.Function.Definition = { + inputs: [ + { + internalType: "address", + name: "owner", + type: "address", + }, + { + internalType: "address", + name: "spender", + type: "address", + }, + ], + name: "allowance", + outputs: [ + { + internalType: "uint256", + name: "", + type: "uint256", + }, + ], + stateMutability: "view", + type: "function", +}; +const approve: abi.Function.Definition = { + inputs: [ + { + internalType: "address", + name: "spender", + type: "address", + }, + { + internalType: "uint256", + name: "amount", + type: "uint256", + }, + ], + name: "approve", + outputs: [ + { + internalType: "bool", + name: "", + type: "bool", + }, + ], + stateMutability: "nonpayable", + type: "function", +}; +const balanceOf: abi.Function.Definition = { + inputs: [ + { + internalType: "address", + name: "account", + type: "address", + }, + ], + name: "balanceOf", + outputs: [ + { + internalType: "uint256", + name: "", + type: "uint256", + }, + ], + stateMutability: "view", + type: "function", +}; +const decimals: abi.Function.Definition = { + inputs: [], + name: "decimals", + outputs: [ + { + internalType: "uint8", + name: "", + type: "uint8", + }, + ], + stateMutability: "view", + type: "function", +}; +const name: abi.Function.Definition = { + inputs: [], + name: "name", + outputs: [ + { + internalType: "string", + name: "", + type: "string", + }, + ], + stateMutability: "view", + type: "function", +}; +const supportsInterface: abi.Function.Definition = { + inputs: [ + { + internalType: "bytes4", + name: "interfaceId", + type: "bytes4", + }, + ], + name: "supportsInterface", + outputs: [ + { + internalType: "bool", + name: "", + type: "bool", + }, + ], + stateMutability: "view", + type: "function", +}; +const symbol: abi.Function.Definition = { + inputs: [], + name: "symbol", + outputs: [ + { + internalType: "string", + name: "", + type: "string", + }, + ], + stateMutability: "view", + type: "function", +}; +const totalSupply: abi.Function.Definition = { + inputs: [], + name: "totalSupply", + outputs: [ + { + internalType: "uint256", + name: "", + type: "uint256", + }, + ], + stateMutability: "view", + type: "function", +}; +const transfer: abi.Function.Definition = { + inputs: [ + { + internalType: "address", + name: "to", + type: "address", + }, + { + internalType: "uint256", + name: "amount", + type: "uint256", + }, + ], + name: "transfer", + outputs: [ + { + internalType: "bool", + name: "", + type: "bool", + }, + ], + stateMutability: "nonpayable", + type: "function", +}; +const transferFrom: abi.Function.Definition = { + inputs: [ + { + internalType: "address", + name: "from", + type: "address", + }, + { + internalType: "address", + name: "to", + type: "address", + }, + { + internalType: "uint256", + name: "amount", + type: "uint256", + }, + ], + name: "transferFrom", + outputs: [ + { + internalType: "bool", + name: "", + type: "bool", + }, + ], + stateMutability: "nonpayable", + type: "function", +}; + +export default { + ApprovalEvent: new abi.Event(ApprovalEvent), + TransferEvent: new abi.Event(TransferEvent), + allowance: new abi.Function(allowance), + approve: new abi.Function(approve), + balanceOf: new abi.Function(balanceOf), + decimals: new abi.Function(decimals), + name: new abi.Function(name), + supportsInterface: new abi.Function(supportsInterface), + symbol: new abi.Function(symbol), + totalSupply: new abi.Function(totalSupply), + transfer: new abi.Function(transfer), + transferFrom: new abi.Function(transferFrom), +}; diff --git a/libs/ledger-live-common/src/families/vechain/contracts/abis/params.ts b/libs/ledger-live-common/src/families/vechain/contracts/abis/params.ts new file mode 100644 index 000000000000..0a83fa31b950 --- /dev/null +++ b/libs/ledger-live-common/src/families/vechain/contracts/abis/params.ts @@ -0,0 +1,70 @@ +import { abi } from "thor-devkit"; + +/** + * FUNCTIONS + */ +const set: abi.Function.Definition = { + constant: false, + inputs: [ + { + name: "_key", + type: "bytes32", + }, + { + name: "_value", + type: "uint256", + }, + ], + name: "set", + outputs: [], + payable: false, + stateMutability: "nonpayable", + type: "function", +}; + +const get: abi.Function.Definition = { + constant: true, + inputs: [ + { + name: "_key", + type: "bytes32", + }, + ], + name: "get", + outputs: [ + { + name: "", + type: "uint256", + }, + ], + payable: false, + stateMutability: "view", + type: "function", +}; + +/** + * EVENTS + */ +const Set: abi.Event.Definition = { + anonymous: false, + inputs: [ + { + indexed: true, + name: "key", + type: "bytes32", + }, + { + indexed: false, + name: "value", + type: "uint256", + }, + ], + name: "Set", + type: "event", +}; + +export default { + set: new abi.Function(set), + get: new abi.Function(get), + Set: new abi.Event(Set), +}; diff --git a/libs/ledger-live-common/src/families/vechain/contracts/constants.ts b/libs/ledger-live-common/src/families/vechain/contracts/constants.ts new file mode 100644 index 000000000000..27452c292635 --- /dev/null +++ b/libs/ledger-live-common/src/families/vechain/contracts/constants.ts @@ -0,0 +1,11 @@ +export const TransferEventSignature = + "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"; + +export const VTHO_ADDRESS = "0x0000000000000000000000000000456e65726779"; + +// params.sol - builtin contract (https://www.vechain.org/whitepaper/#bit_v48i3) +export const PARAMS_ADDRESS = "0x0000000000000000000000000000506172616d73"; +export const REWARD_RATIO_KEY = + "0x00000000000000000000000000000000000000007265776172642d726174696f"; +export const BASE_GAS_PRICE_KEY = + "0x000000000000000000000000000000000000626173652d6761732d7072696365"; diff --git a/libs/ledger-live-common/src/families/vechain/datasets/vechain.scanAccounts.1.ts b/libs/ledger-live-common/src/families/vechain/datasets/vechain.scanAccounts.1.ts new file mode 100644 index 000000000000..36eb824f481e --- /dev/null +++ b/libs/ledger-live-common/src/families/vechain/datasets/vechain.scanAccounts.1.ts @@ -0,0 +1,19 @@ +export default { + name: "vechain seed 1", + apdus: ` + => e002000015058000002c80000332800000000000000000000000 + <= 410482c44bc99cdf08d4360c97fbf62d387288ab75c576926943ad90059002720e93f58799391393c98ad41136aa4ac871b103d25cb9a88f1aadd7dbbe3c7794888928333234373631393364346133323438383332326666426239383335613763463263376532323032439000 + => e002000015058000002c80000332800000000000000000000001 + <= 410447267cc778e6f13242ae7f91b0be417746e59073d952a8c39900343f9de3e478f20221c3fff122ffefb30fe6b344e929561acd5d1060936fad036b1c57a1ccd128433039663137453965333542436130446537353339413433373246433337443934313733363731379000 + => e002000015058000002c80000332800000000000000000000002 + <= 4104e83afcbbec62e097093bcc24b74a259876fb86a0c5c39aa1ed9d850870c5f8ac05ad4e11d2bbfefbca2768518c5d57118ffcb67ad0c4a7d582ac94a618fb882828343438323531634436383163436363373931463234654130444432364233384239363633614537349000 + => e002000015058000002c80000332800000000000000000000003 + <= 41042351c71fa22ea9efdfbafbdd3366a7603fdd4ba52832fa4affebeb506dd40ba221ccd03c8b88f20a4d4c46c82edb9a98b85f3d8792b59baef0be1bb5a55335fa28303837466239306433374132453234363243436346454444373465434232346336383332393734319000 + => e002000015058000002c80000332800000000000000000000004 + <= 41049b56394e897da56b1999af6b63fba884becbc8750bad5a4cf7a0133e318ff0d08a08d78148fb7962e1328f18547830f012c2a07aef6fae281f0cf4f94ed0278228363063623139384144336546333830423734353034443536363134353635353441353039316135349000 + => e002000015058000002c80000332800000000000000000000005 + <= 4104001b56209f81d70da8ad502380512eab2273487a213928239604450472196e6a79b45e98743f83b8e7e29ca14cb21f2d6582db62d48ed013c87b423b745c4c5428303837613537386361323837394538443233343562614233423030393343664532416133643734419000 + => e002000015058000002c80000332800000000000000000000006 + <= 4104a50c733851da68ed7903acc1202dbfa147bd9ea2dfc02b23e0337885f90a81fb550b8c78b1305ebf49552dd6ae472356e107be49bbed3df8c48ffb6bb1a1519b28344239373138333832393944346530303934323635383039323035353144374332646242623963639000 + `, +}; diff --git a/libs/ledger-live-common/src/families/vechain/datasets/vechain.ts b/libs/ledger-live-common/src/families/vechain/datasets/vechain.ts new file mode 100644 index 000000000000..6f21386f9f65 --- /dev/null +++ b/libs/ledger-live-common/src/families/vechain/datasets/vechain.ts @@ -0,0 +1,72 @@ +import { AccountRaw } from "@ledgerhq/types-live"; +export const vechain1: AccountRaw = { + id: "js:2:vechain:0x087Fb90d37A2E2462CCcFEDD74eCB24c68329741:vechain", + seedIdentifier: + "0482c44bc99cdf08d4360c97fbf62d387288ab75c576926943ad90059002720e93f58799391393c98ad41136aa4ac871b103d25cb9a88f1aadd7dbbe3c77948889", + name: "VeChain 4", + starred: false, + used: false, + derivationMode: "vechain", + index: 3, + freshAddress: "0x087Fb90d37A2E2462CCcFEDD74eCB24c68329741", + freshAddressPath: "44'/818'/0'/0/3", + freshAddresses: [], + blockHeight: 0, + creationDate: new Date(1675317600).toISOString(), + operationsCount: 1, + operations: [ + { + accountId: + "js:2:vechain:0x32476193d4a32488322ffBb9835a7cF2c7e2202C:vechain+vechain%2Fvtho", + blockHash: + "0x00ddd8047ce47f277e3a021fee267bc025a68e9669e9e25fe0839a037515603a", + blockHeight: 14538756, + date: new Date().toISOString(), + extra: {}, + hash: "0x05ee616beb80ddd8e41cec0bd12829f21395e99c13f7e2e2ea313f20f7414c2a", + id: "0x05ee616beb80ddd8e41cec0bd12829f21395e99c13f7e2e2ea313f20f7414c2a", + operator: undefined, + recipients: ["0x32476193d4a32488322ffbb9835a7cf2c7e2202c"], + senders: ["0x4f6fc409e152d33843cf4982d414c1dd0879277e"], + standard: undefined, + tokenId: undefined, + type: "IN", + value: "10000000000000000000", + fee: "2100000000000000", + }, + ], + pendingOperations: [], + currencyId: "vechain", + unitMagnitude: 18, + lastSyncDate: "2023-02-28T09:07:53.785Z", + balance: "10000000000000000000", + spendableBalance: "10000000000000000000", + balanceHistoryCache: { + HOUR: { balances: [], latestDate: 1677574800000 }, + DAY: { balances: [], latestDate: 1677538800000 }, + WEEK: { balances: [], latestDate: 1677366000000 }, + }, + subAccounts: [ + { + type: "TokenAccountRaw", + id: "js:2:vechain:0x087Fb90d37A2E2462CCcFEDD74eCB24c68329741:vechain+vechain%2Fvtho", + parentId: + "js:2:vechain:0x087Fb90d37A2E2462CCcFEDD74eCB24c68329741:vechain", + starred: false, + tokenId: "vechain/vtho", + balance: "10000000000000000000", + spendableBalance: "9000000000000000000", + balanceHistoryCache: { + HOUR: { latestDate: null, balances: [] }, + DAY: { latestDate: null, balances: [] }, + WEEK: { latestDate: null, balances: [] }, + }, + creationDate: "2023-02-28T09:07:53.785Z", + operationsCount: 0, + operations: [], + pendingOperations: [], + swapHistory: [], + }, + ], + swapHistory: [], +}; diff --git a/libs/ledger-live-common/src/families/vechain/hw-getAddress.ts b/libs/ledger-live-common/src/families/vechain/hw-getAddress.ts new file mode 100644 index 000000000000..1c168849f7c9 --- /dev/null +++ b/libs/ledger-live-common/src/families/vechain/hw-getAddress.ts @@ -0,0 +1,20 @@ +import Vet from "@ledgerhq/hw-app-vet"; +import eip55 from "eip55"; +import type { Resolver } from "../../hw/getAddress/types"; + +const resolver: Resolver = async ( + transport, + { path, verify, askChainCode } +) => { + const vet = new Vet(transport); + const r = await vet.getAddress(path, verify, askChainCode || false); + const address = eip55.encode(r.address); + return { + path, + address, + publicKey: r.publicKey, + chainCode: r.chainCode, + }; +}; + +export default resolver; diff --git a/libs/ledger-live-common/src/families/vechain/js-broadcast.ts b/libs/ledger-live-common/src/families/vechain/js-broadcast.ts new file mode 100644 index 000000000000..a4f7fcf802fd --- /dev/null +++ b/libs/ledger-live-common/src/families/vechain/js-broadcast.ts @@ -0,0 +1,22 @@ +import type { Operation, SignedOperation } from "@ledgerhq/types-live"; +import { patchOperationWithHash } from "../../operation"; +import { Transaction } from "thor-devkit"; +import { submit } from "./api"; + +/** + * Broadcast the signed transaction + * @param {signature: string, operation: string} signedOperation + */ +const broadcast = async ({ + signedOperation: { signature, operation }, +}: { + signedOperation: SignedOperation; +}): Promise => { + const transaction = new Transaction(operation.extra.transaction.body); + transaction.signature = Buffer.from(signature, "hex"); + const hash = await submit(transaction); + + return patchOperationWithHash(operation, hash); +}; + +export default broadcast; diff --git a/libs/ledger-live-common/src/families/vechain/js-getTransactionStatus.ts b/libs/ledger-live-common/src/families/vechain/js-getTransactionStatus.ts new file mode 100644 index 000000000000..c6c2c41bd276 --- /dev/null +++ b/libs/ledger-live-common/src/families/vechain/js-getTransactionStatus.ts @@ -0,0 +1,74 @@ +import { + AmountRequired, + FeeNotLoaded, + InvalidAddress, + InvalidAddressBecauseDestinationIsAlsoSource, + NotEnoughBalance, + RecipientRequired, +} from "@ledgerhq/errors"; +import type { TransactionStatus } from "./types"; +import type { Transaction } from "./types"; +import { Account } from "@ledgerhq/types-live"; +import { calculateTransactionInfo } from "./utils/calculateTransactionInfo"; +import { isValid } from "./utils/address-utils"; +import BigNumber from "bignumber.js"; + +// NOTE: seems like the spendableBalance is not updated correctly: +// use balance.minus(estimatedFees) instead +const getTransactionStatus = async ( + account: Account, + transaction: Transaction +): Promise => { + const { freshAddress, currency, subAccounts } = account; + const { body, recipient } = transaction; + const errors: Record = {}; + const warnings: Record = {}; + const { + amount, + isTokenAccount, + estimatedFees, + totalSpent, + spendableBalance, + } = await calculateTransactionInfo(account, transaction); + + if (!body || !body.gas) { + errors["body"] = new FeeNotLoaded(); + } + + if (!recipient) { + errors.recipient = new RecipientRequired(); + } else if (freshAddress === recipient) { + warnings.recipient = new InvalidAddressBecauseDestinationIsAlsoSource(); + } else if (!isValid(recipient)) { + errors.recipient = new InvalidAddress("", { + currencyName: currency.name, + }); + } + + if (!amount.gt(0)) { + errors.amount = new AmountRequired(); + } else { + if (amount.gt(spendableBalance)) { + errors.amount = new NotEnoughBalance(); + } + if (!isTokenAccount) { + // vet + const vthoBalance = subAccounts?.[0].balance; + if (estimatedFees.gt(vthoBalance || 0)) { + errors.amount = new NotEnoughBalance(); + } + } + } + + return Promise.resolve({ + errors, + warnings, + estimatedFees: Object.keys(errors).length + ? new BigNumber(0) + : estimatedFees, + amount: amount, + totalSpent: Object.keys(errors).length ? new BigNumber(0) : totalSpent, + }); +}; + +export default getTransactionStatus; diff --git a/libs/ledger-live-common/src/families/vechain/js-signOperation.ts b/libs/ledger-live-common/src/families/vechain/js-signOperation.ts new file mode 100644 index 000000000000..5b0350c2258e --- /dev/null +++ b/libs/ledger-live-common/src/families/vechain/js-signOperation.ts @@ -0,0 +1,97 @@ +import { BigNumber } from "bignumber.js"; +import { Observable } from "rxjs"; + +import type { Transaction } from "./types"; +import type { + Account, + Operation, + SignOperationEvent, +} from "@ledgerhq/types-live"; + +import { withDevice } from "../../hw/deviceAccess"; +import { encodeOperationId } from "../../operation"; +import { Transaction as ThorTransaction } from "thor-devkit"; +import Vet from "@ledgerhq/hw-app-vet"; +import { calculateFee } from "./utils/transaction-utils"; + +const buildOptimisticOperation = async ( + account: Account, + transaction: Transaction +): Promise => { + const type = "OUT"; + + const operation: Operation = { + id: encodeOperationId(account.id, "", type), + hash: "", + type, + value: BigNumber(transaction.amount), + fee: await calculateFee( + BigNumber(transaction.body.gas), + transaction.body.gasPriceCoef + ), + blockHash: null, + blockHeight: null, + senders: [account.freshAddress], + recipients: [transaction.recipient].filter(Boolean), + accountId: account.id, + date: new Date(), + extra: { transaction }, + }; + + return operation; +}; + +/** + * Sign Transaction with Ledger hardware + */ +const signOperation = ({ + account, + deviceId, + transaction, +}: { + account: Account; + deviceId: string; + transaction: Transaction; +}): Observable => + withDevice(deviceId)( + (transport) => + new Observable((o) => { + async function main() { + o.next({ + type: "device-signature-requested", + }); + + const unsigned = new ThorTransaction(transaction.body); + + // Sign on device + const vechainApp = new Vet(transport); + const signature = await vechainApp.signTransaction( + account.freshAddressPath, + unsigned.encode().toString("hex") + ); + + o.next({ type: "device-signature-granted" }); + + const operation = await buildOptimisticOperation( + account, + transaction + ); + + o.next({ + type: "signed", + signedOperation: { + operation, + signature: signature.toString("hex"), + expirationDate: null, + }, + }); + } + + main().then( + () => o.complete(), + (e) => o.error(e) + ); + }) + ); + +export default signOperation; diff --git a/libs/ledger-live-common/src/families/vechain/js-synchronisation.ts b/libs/ledger-live-common/src/families/vechain/js-synchronisation.ts new file mode 100644 index 000000000000..96990b2c63e6 --- /dev/null +++ b/libs/ledger-live-common/src/families/vechain/js-synchronisation.ts @@ -0,0 +1,91 @@ +import type { GetAccountShape } from "../../bridge/jsHelpers"; +import { BigNumber } from "bignumber.js"; +import { makeSync, makeScanAccounts, mergeOps } from "../../bridge/jsHelpers"; +import { encodeAccountId } from "../../account"; +import eip55 from "eip55"; +import { emptyHistoryCache } from "../../account"; + +import { getAccount, getOperations, getTokenOperations } from "./api"; +import { getTokenById } from "@ledgerhq/cryptoassets/tokens"; + +const getAccountShape: GetAccountShape = async (info) => { + type TokenType = "TokenAccount"; + const { initialAccount, currency, derivationMode } = info; + let { address } = info; + address = eip55.encode(address); + + const oldOperations = initialAccount?.operations || []; + const startAt = oldOperations.length + ? (oldOperations[0].blockHeight || 0) + 1 + : 1; + + const accountId = encodeAccountId({ + type: "js", + version: "2", + currencyId: currency.id, + xpubOrAddress: address, + derivationMode, + }); + + // get the current account balance state depending your api implementation + const { balance, energy } = await getAccount(address); + + // Merge new operations with the previously synced ones + const newOperations = await getOperations(accountId, address, startAt); + const vthoAccountId = accountId + "+" + encodeURIComponent("vechain/vtho"); + const VTHOoperations = await getTokenOperations( + vthoAccountId, + address, + "0x0000000000000000000000000000456e65726779", + 1 + ); + const operations = mergeOps(oldOperations, newOperations); + + let min_date = -1; + if (operations.length != 0) { + const operationsDates = operations.map((c) => c.date.getTime()); + operationsDates.concat(VTHOoperations.map((c) => c.date.getTime())); + min_date = Math.min(...operationsDates); + } + + const shape = { + ...info, + id: accountId, + balance: BigNumber(balance), + creationDate: min_date != -1 ? new Date(min_date) : new Date(), + spendableBalance: BigNumber(balance), + operationsCount: operations.length, + operations: operations, + subAccounts: [ + { + type: "TokenAccount" as TokenType, + id: vthoAccountId, + parentId: accountId, + token: getTokenById("vechain/vtho"), + balance: BigNumber(energy), + spendableBalance: BigNumber(energy), + creationDate: min_date != -1 ? new Date(min_date) : new Date(), + operationsCount: VTHOoperations.length, + operations: VTHOoperations, + pendingOperations: + (initialAccount?.subAccounts && + initialAccount.subAccounts[0]?.pendingOperations) || + [], + starred: + (initialAccount?.subAccounts && + initialAccount.subAccounts[0]?.starred) || + false, + balanceHistoryCache: + (initialAccount?.subAccounts && + initialAccount.subAccounts[0]?.balanceHistoryCache) || + emptyHistoryCache, + swapHistory: [], + }, + ], + }; + + return { ...shape, operations }; +}; + +export const scanAccounts = makeScanAccounts({ getAccountShape }); +export const sync = makeSync({ getAccountShape }); diff --git a/libs/ledger-live-common/src/families/vechain/js-transaction.ts b/libs/ledger-live-common/src/families/vechain/js-transaction.ts new file mode 100644 index 000000000000..6422e204a29a --- /dev/null +++ b/libs/ledger-live-common/src/families/vechain/js-transaction.ts @@ -0,0 +1,139 @@ +import { Account, TransactionCommon } from "@ledgerhq/types-live"; +import BigNumber from "bignumber.js"; +import { $Shape } from "utility-types"; +import { + DEFAULT_GAS_COEFFICIENT, + HEX_PREFIX, + TESTNET_CHAIN_TAG, +} from "./constants"; +import { Transaction } from "./types"; +import { Transaction as ThorTransaction } from "thor-devkit"; +import { + estimateGas, + generateNonce, + getBlockRef, +} from "./utils/transaction-utils"; +import { VTHO_ADDRESS } from "./contracts/constants"; +import VIP180 from "./contracts/abis/VIP180"; +import { calculateTransactionInfo } from "./utils/calculateTransactionInfo"; +import { isValid } from "./utils/address-utils"; +/** + * Create an empty VET or VTHO transaction + * + * @returns {Transaction} + */ +export const createTransaction = (): Transaction => ({ + family: "vechain", + mode: "send_vet", + body: { + chainTag: TESTNET_CHAIN_TAG, + // placeholder, if "empty" returns error on send modal open + blockRef: "0x00634a0c856ec1db", + expiration: 18, + clauses: [], + gasPriceCoef: DEFAULT_GAS_COEFFICIENT, + gas: "0", + dependsOn: null, + nonce: generateNonce(), + }, + amount: BigNumber(0), + recipient: "", + useAllAmount: false, +}); + +/** + * Apply patch to a transaction + * + * @param {Transaction} t + * @param {TransactionCommon} patch + * @returns patched transaction + */ +export const updateTransaction = ( + t: Transaction, + patch: $Shape +): Transaction => { + return { ...t, ...patch }; +}; + +/** + * Prepare transaction before checking status + * + * @param {Account} a + * @param {Transaction} t + */ +export const prepareTransaction = async ( + account: Account, + transaction: Transaction +): Promise => { + const { amount, isTokenAccount } = await calculateTransactionInfo( + account, + transaction + ); + + const blockRef = await getBlockRef(); + + const gas = await estimateGas(transaction); + + let clauses: Array = []; + if (transaction.recipient && isValid(transaction.recipient)) { + if (isTokenAccount) { + clauses = await calculateClausesVtho(transaction, amount); + } else { + clauses = await calculateClausesVet(transaction, amount); + } + } + const body = { ...transaction.body, gas, blockRef, clauses }; + + return { ...transaction, body, amount }; +}; + +const calculateClausesVtho = async ( + transaction: Transaction, + amount: BigNumber +): Promise => { + const clauses: ThorTransaction.Clause[] = []; + + // Get the existing clause or create a blank one + const updatedClause: ThorTransaction.Clause = { + to: VTHO_ADDRESS, + value: 0, + data: "0x", + }; + + transaction.mode = "send_vtho"; + const updatedValues = { + to: transaction.recipient, + amount: amount.toFixed(), + }; + + updatedClause.data = VIP180.transfer.encode( + updatedValues.to, + updatedValues.amount + ); + + clauses.push(updatedClause); + return clauses; +}; + +const calculateClausesVet = async ( + transaction: Transaction, + amount: BigNumber +): Promise => { + const clauses: ThorTransaction.Clause[] = []; + + // Get the existing clause or create a blank one + const updatedClause: ThorTransaction.Clause = { + to: null, + value: 0, + data: "0x", + }; + + transaction.mode = "send_vet"; + + updatedClause.value = `${HEX_PREFIX}${amount.toString(16)}`; + updatedClause.to = transaction.recipient; + + clauses.push(updatedClause); + + return clauses; +}; diff --git a/libs/ledger-live-common/src/families/vechain/mock.ts b/libs/ledger-live-common/src/families/vechain/mock.ts new file mode 100644 index 000000000000..d9d2d33b1d78 --- /dev/null +++ b/libs/ledger-live-common/src/families/vechain/mock.ts @@ -0,0 +1,14 @@ +import type { Account } from "@ledgerhq/types-live"; + +function postSyncAccount(account: Account): Account { + return account; +} + +function postScanAccount(account: Account): Account { + return account; +} + +export default { + postSyncAccount, + postScanAccount, +}; diff --git a/libs/ledger-live-common/src/families/vechain/specs.ts b/libs/ledger-live-common/src/families/vechain/specs.ts new file mode 100644 index 000000000000..ced903d10d25 --- /dev/null +++ b/libs/ledger-live-common/src/families/vechain/specs.ts @@ -0,0 +1,62 @@ +import expect from "expect"; +import type { + AppSpec, + TransactionArg, + TransactionRes, + TransactionTestInput, +} from "../../bot/types"; +import type { Transaction } from "./types"; +import { pickSiblings, botTest } from "../../bot/specs"; +import { DeviceModelId } from "@ledgerhq/devices"; +import { getCryptoCurrencyById } from "@ledgerhq/cryptoassets/currencies"; +import deviceAction from "../vechain/speculos-deviceActions"; + +const VeChain: AppSpec = { + name: "Vechain", + currency: getCryptoCurrencyById("vechain"), + appQuery: { + model: DeviceModelId.nanoSP, + appName: "VeChain", + }, + genericDeviceAction: deviceAction.acceptTransaction, + mutations: [ + { + name: "move ~50%", + maxRun: 2, + transaction: ({ + account, + siblings, + bridge, + maxSpendable, + }: TransactionArg): TransactionRes => { + const sibling = pickSiblings(siblings, 4); + const recipient = sibling.freshAddress; + + const transaction = bridge.createTransaction(account); + + const amount = maxSpendable.div(2).integerValue(); + + //checkSendableToEmptyAccount(amount, sibling); + + const updates = [{ amount }, { recipient }]; + return { + transaction, + updates, + }; + }, + test: ({ + account, + accountBeforeTransaction, + operation, + }: TransactionTestInput): void | undefined => { + botTest("account balance decreased with operation value", () => + expect(account.balance.toString()).toBe( + accountBeforeTransaction.balance.minus(operation.value).toString() + ) + ); + }, + }, + ], +}; + +export default { VeChain }; diff --git a/libs/ledger-live-common/src/families/vechain/speculos-deviceActions.ts b/libs/ledger-live-common/src/families/vechain/speculos-deviceActions.ts new file mode 100644 index 000000000000..699fcfa6a7b5 --- /dev/null +++ b/libs/ledger-live-common/src/families/vechain/speculos-deviceActions.ts @@ -0,0 +1,43 @@ +import type { DeviceAction } from "../../bot/types"; +import type { Transaction } from "./types"; +import { formatCurrencyUnit } from "../../currencies"; +import { deviceActionFlow, SpeculosButton } from "../../bot/specs"; + +const expectedAmount = ({ account, status }) => + formatCurrencyUnit(account.unit, status.amount, { + disableRounding: true, + }) + " MCN"; + +const acceptTransaction: DeviceAction = deviceActionFlow({ + steps: [ + { + title: "Starting Balance", + button: SpeculosButton.RIGHT, + expectedValue: expectedAmount, + }, + { + title: "Send", + button: SpeculosButton.RIGHT, + expectedValue: expectedAmount, + }, + { + title: "Fee", + button: SpeculosButton.RIGHT, + expectedValue: ({ account, status }) => + formatCurrencyUnit(account.unit, status.estimatedFees, { + disableRounding: true, + }) + " XLM", + }, + { + title: "Destination", + button: SpeculosButton.RIGHT, + expectedValue: ({ transaction }) => transaction.recipient, + }, + { + title: "Accept", + button: SpeculosButton.BOTH, + }, + ], +}); + +export default { acceptTransaction }; diff --git a/libs/ledger-live-common/src/families/vechain/transaction.ts b/libs/ledger-live-common/src/families/vechain/transaction.ts new file mode 100644 index 000000000000..5578bda83327 --- /dev/null +++ b/libs/ledger-live-common/src/families/vechain/transaction.ts @@ -0,0 +1,67 @@ +import type { + Transaction, + TransactionRaw, + TransactionStatus, + TransactionStatusRaw, +} from "./types"; +import { + formatTransactionStatusCommon as formatTransactionStatus, + fromTransactionCommonRaw, + fromTransactionStatusRawCommon, + toTransactionCommonRaw, + toTransactionStatusRawCommon, +} from "../../transaction/common"; + +// TODO: Implement formatTransaction() properly +export const formatTransaction = (t: Transaction): string => { + return `${t.mode.toUpperCase()} 1 TO ${t.recipient}`; +}; + +export const fromTransactionRaw = (tr: TransactionRaw): Transaction => { + const common = fromTransactionCommonRaw(tr); + + return { + ...tr, + ...common, + }; +}; + +export const toTransactionRaw = (t: Transaction): TransactionRaw => { + const common = toTransactionCommonRaw(t); + + return { + ...t, + ...common, + }; +}; + +export const fromTransactionStatusRaw = ( + ts: TransactionStatusRaw +): TransactionStatus => { + const common = fromTransactionStatusRawCommon(ts); + + return { + ...ts, + ...common, + }; +}; + +export const toTransactionStatusRaw = ( + ts: TransactionStatus +): TransactionStatusRaw => { + const common = toTransactionStatusRawCommon(ts); + + return { + ...ts, + ...common, + }; +}; + +export default { + formatTransaction, + formatTransactionStatus, + fromTransactionRaw, + toTransactionRaw, + fromTransactionStatusRaw, + toTransactionStatusRaw, +}; diff --git a/libs/ledger-live-common/src/families/vechain/types.ts b/libs/ledger-live-common/src/families/vechain/types.ts new file mode 100644 index 000000000000..2abb1f453ac2 --- /dev/null +++ b/libs/ledger-live-common/src/families/vechain/types.ts @@ -0,0 +1,41 @@ +import type { + TokenAccount, + TransactionCommon, + TransactionCommonRaw, + TransactionStatusCommon, + TransactionStatusCommonRaw, +} from "@ledgerhq/types-live"; +import BigNumber from "bignumber.js"; +import { Transaction as ThorTransaction } from "thor-devkit"; + +export type NetworkInfo = { + family: "ethereum"; +}; + +export type TransactionMode = "send_vet" | "send_vtho"; + +export type Transaction = TransactionCommon & { + family: "vechain"; + mode: TransactionMode; + body: ThorTransaction.Body; +}; + +export type TransactionRaw = TransactionCommonRaw & { + family: "vechain"; + mode: TransactionMode; + body: ThorTransaction.Body; +}; + +export type TransactionStatus = TransactionStatusCommon; + +export type TransactionStatusRaw = TransactionStatusCommonRaw; + +export type TransactionInfo = { + estimatedFees: BigNumber; + isTokenAccount: boolean; + amount: BigNumber; + totalSpent: BigNumber; + balance: BigNumber; + spendableBalance: BigNumber; + tokenAccount?: TokenAccount; +}; diff --git a/libs/ledger-live-common/src/families/vechain/utils/address-utils.ts b/libs/ledger-live-common/src/families/vechain/utils/address-utils.ts new file mode 100644 index 000000000000..0d91698aef7e --- /dev/null +++ b/libs/ledger-live-common/src/families/vechain/utils/address-utils.ts @@ -0,0 +1,11 @@ +import { address } from "thor-devkit"; +import hexUtils from "./hex-utils"; + +export const isValid = (addr: string): boolean => { + try { + address.toChecksumed(hexUtils.addPrefix(addr)); + return true; + } catch (e) { + return false; + } +}; diff --git a/libs/ledger-live-common/src/families/vechain/utils/calculateTransactionInfo.ts b/libs/ledger-live-common/src/families/vechain/utils/calculateTransactionInfo.ts new file mode 100644 index 000000000000..36d4e05013fb --- /dev/null +++ b/libs/ledger-live-common/src/families/vechain/utils/calculateTransactionInfo.ts @@ -0,0 +1,52 @@ +import { Account, TokenAccount } from "@ledgerhq/types-live"; +import BigNumber from "bignumber.js"; +import { Transaction, TransactionInfo } from "../types"; +import { calculateFee } from "./transaction-utils"; + +export const calculateTransactionInfo = async ( + account: Account, + transaction: Transaction +): Promise => { + const { subAccounts } = account; + const { amount: oldAmount, useAllAmount, body, subAccountId } = transaction; + const estimatedFees = await calculateFee( + BigNumber(body.gas), + body.gasPriceCoef + ); + + const tokenAccount = + subAccountId && subAccounts + ? (subAccounts.find((subAccount) => { + return subAccount.id === subAccountId; + }) as TokenAccount) + : undefined; + const isTokenAccount = !!tokenAccount; + + let balance; + let spendableBalance; + let amount; + let totalSpent; + if (isTokenAccount) { + balance = tokenAccount.balance; + spendableBalance = tokenAccount.balance.minus(estimatedFees); + amount = useAllAmount ? spendableBalance : oldAmount; + totalSpent = useAllAmount + ? tokenAccount.balance + : BigNumber(amount).plus(estimatedFees); + } else { + balance = account.balance; + spendableBalance = account.balance; + amount = useAllAmount ? spendableBalance : oldAmount; + totalSpent = useAllAmount ? account.balance : BigNumber(amount); + } + + return { + estimatedFees, + isTokenAccount, + amount, + totalSpent, + spendableBalance, + balance, + tokenAccount, + }; +}; diff --git a/libs/ledger-live-common/src/families/vechain/utils/hex-utils.ts b/libs/ledger-live-common/src/families/vechain/utils/hex-utils.ts new file mode 100644 index 000000000000..38d2025f6983 --- /dev/null +++ b/libs/ledger-live-common/src/families/vechain/utils/hex-utils.ts @@ -0,0 +1,55 @@ +const PREFIX = "0x"; +const PREFIX_REGEX = /^0[xX]/; +const HEX_REGEX = /^(0[xX])?[a-fA-F0-9]+$/; + +/** + * Returns the provied hex string with the hex prefix removed. + * If the prefix doesn't exist the hex is returned unmodified + * @param hex - the input hex string + * @returns the input hex string with the hex prefix removed + * @throws an error if the input is not a valid hex string + */ +const removePrefix = (hex: string): string => { + validate(hex); + return hex.replace(PREFIX_REGEX, ""); +}; + +/** + * Returns the provided hex string with the hex prefix added. + * If the prefix already exists the string is returned unmodified. + * If the string contains an UPPER case `X` in the prefix it will be replaced with a lower case `x` + * @param hex - the input hex string + * @returns the input hex string with the hex prefix added + * @throws an error if the input is not a valid hex string + */ +const addPrefix = (hex: string): string => { + validate(hex); + return PREFIX_REGEX.test(hex) + ? hex.replace(PREFIX_REGEX, PREFIX) + : `${PREFIX}${hex}`; +}; + +/** + * Validate the hex string. Throws an Error if not valid + * @param hex - the input hex string + * @throws an error if the input is not a valid hex string + */ +const validate = (hex: string) => { + if (!isValid(hex)) throw Error(`Provided hex value is not valid ${hex}`); +}; + +/** + * Check if input string is valid + * @param hex - the input hex string + * @returns boolean representing whether the input hex is valid + */ +const isValid = (hex: string): boolean => { + return HEX_REGEX.test(hex); +}; + +export default { + removePrefix, + addPrefix, + validate, + isValid, +}; diff --git a/libs/ledger-live-common/src/families/vechain/utils/mapping-utils.ts b/libs/ledger-live-common/src/families/vechain/utils/mapping-utils.ts new file mode 100644 index 000000000000..ef56c801f59f --- /dev/null +++ b/libs/ledger-live-common/src/families/vechain/utils/mapping-utils.ts @@ -0,0 +1,80 @@ +import vip180 from "../contracts/abis/VIP180"; +import { Operation } from "@ledgerhq/types-live"; +import BigNumber from "bignumber.js"; +import { EventLog, TransferLog } from "../api/types"; + +// TODO: Currently hardcoding the fee to 0 +export const mapVetTransfersToOperations = ( + txs: TransferLog[], + accountId: string, + addr: string +): Operation[] => { + return txs.map((tx) => { + return { + id: tx.meta.txID, + hash: tx.meta.txID, + type: tx.recipient === addr.toLowerCase() ? "IN" : "OUT", + value: new BigNumber(tx.amount), + fee: new BigNumber(0), + senders: [tx.sender], + recipients: [tx.recipient], + blockHeight: tx.meta.blockNumber, + blockHash: tx.meta.blockID, + accountId, + date: new Date(tx.meta.blockTimestamp * 1000), + extra: {}, + }; + }); +}; + +// TODO: Currently hardcoding the fee to 0 +export const mapTokenTransfersToOperations = ( + evnts: EventLog[], + accountId: string, + addr: string +): Operation[] => { + return evnts.map((evnt) => { + const decoded = vip180.TransferEvent.decode(evnt.data, evnt.topics); + return { + id: evnt.meta.txID, + hash: evnt.meta.txID, + type: decoded.to === addr.toLowerCase() ? "IN" : "OUT", + value: new BigNumber(decoded.value), + fee: new BigNumber(0), + senders: [decoded.from], + recipients: [decoded.to], + blockHeight: evnt.meta.blockNumber, + blockHash: evnt.meta.blockID, + accountId, + date: new Date(evnt.meta.blockTimestamp * 1000), + extra: {}, + }; + }); +}; + +/** + * Scale the number up by the specified number of decimal places + * @param val - the value to scale up (a number or string representation of a number) + * @param scaleDecimal - the number of decimals to scale up by + * @param roundDecimal - the number of decimals to round the result to + * @param roundingStrategy - what strategy to use when rounding. Based on the strategies defined in `bignumber.js`. Default strategy is ROUND_HALF_UP + * @returns the scaled up result as a string + */ +export const scaleNumberUp = ( + val: BigNumber.Value, + scaleDecimal: number, + roundDecimal = 0, + roundingStrategy: BigNumber.RoundingMode = BigNumber.ROUND_HALF_UP +): string => { + if (scaleDecimal === 0) return new BigNumber(val).toFixed(); + if (scaleDecimal < 0) + throw Error("Decimal value must be greater than or equal to 0"); + const valBn = new BigNumber(val); + if (valBn.isNaN()) throw Error("The value provided is NaN."); + + const amount = valBn.times(`1${"0".repeat(scaleDecimal)}`); + + if (scaleDecimal === roundDecimal) return amount.toFixed(); + + return amount.toFixed(roundDecimal, roundingStrategy); +}; diff --git a/libs/ledger-live-common/src/families/vechain/utils/pad-address.ts b/libs/ledger-live-common/src/families/vechain/utils/pad-address.ts new file mode 100644 index 000000000000..060466de2075 --- /dev/null +++ b/libs/ledger-live-common/src/families/vechain/utils/pad-address.ts @@ -0,0 +1,8 @@ +export const padAddress = (address: string): string => { + const splittedAddr = address.split("x"); + const hexMiss = 64 - splittedAddr[1].length; + for (let i = 0; i < hexMiss; i++) { + splittedAddr[1] = `0${splittedAddr[1]}`; + } + return splittedAddr.join("x"); +}; diff --git a/libs/ledger-live-common/src/families/vechain/utils/transaction-utils.ts b/libs/ledger-live-common/src/families/vechain/utils/transaction-utils.ts new file mode 100644 index 000000000000..84e9d29655fe --- /dev/null +++ b/libs/ledger-live-common/src/families/vechain/utils/transaction-utils.ts @@ -0,0 +1,104 @@ +import { getEnv } from "../../../env"; +import network from "../../../network"; +import { HEX_PREFIX, ZERO_ADDRESS } from "../constants"; +import crypto from "crypto"; +import BigNumber from "bignumber.js"; +import { Transaction as ThorTransaction } from "thor-devkit"; +import params from "../contracts/abis/params"; +import { + BASE_GAS_PRICE_KEY, + PARAMS_ADDRESS, + VTHO_ADDRESS, +} from "../contracts/constants"; +import { Query } from "../api/types"; +import { query } from "../api/sdk"; +import { isValid } from "./address-utils"; +import { Transaction } from "../types"; + +const BASE_URL = getEnv("API_VECHAIN_THOREST"); +const GAS_COEFFICIENT = 15000; + +/** + * Get the block ref to use in a transaction + * @returns the block ref of head + */ +export const getBlockRef = async (): Promise => { + const { data } = await network({ + method: "GET", + url: `${BASE_URL}/blocks/best`, + }); + + return data.id.slice(0, 18); +}; + +/** + * Generate a Unique ID to be used as a nonce + * @returns a unique string + */ +export const generateNonce = (): string => { + const randBuffer = crypto.randomBytes(Math.ceil(4)); + if (!randBuffer) throw Error("Failed to generate random hex"); + return `${HEX_PREFIX}${randBuffer.toString("hex").substring(0, 8)}`; +}; + +/** + * Estimate the gas that will be used by the transaction. + * @param transaction - The transaction to estimate the gas for + * @returns an estimate of the gas usage + */ +export const estimateGas = async (t: Transaction): Promise => { + const intrinsicGas = ThorTransaction.intrinsicGas(t.body.clauses); + + const tx = new ThorTransaction(t.body); + + const queryData: Query[] = []; + + t.body.clauses.forEach((c) => { + const recipient = + t.mode === "send_vtho" ? VTHO_ADDRESS : c.to || ZERO_ADDRESS; + + queryData.push({ + to: isValid(recipient) ? recipient : ZERO_ADDRESS, + data: `${HEX_PREFIX}${tx.encode().toString("hex")}`, + }); + }); + + const response = await query(queryData); + + const execGas = response.reduce((sum, out) => sum + out.gasUsed, 0); + + return intrinsicGas + (execGas ? execGas + GAS_COEFFICIENT : 0); +}; + +const getBaseGasPrice = async (): Promise => { + const queryData: Query = { + to: PARAMS_ADDRESS, + data: params.get.encode(BASE_GAS_PRICE_KEY), + }; + + const response = await query([queryData]); + + // Expect 1 value + if (response && response.length != 1) + throw Error("Unexpected response received for query"); + + return response[0].data; +}; + +/** + * Calculate the fee in VTHO + * @param gas - the gas used + * @param gasPriceCoef - the gas price coefficient + * @returns the fee in VTHO + */ +export const calculateFee = async ( + gas: BigNumber, + gasPriceCoef: number +): Promise => { + const baseGasPrice = await getBaseGasPrice(); + return new BigNumber(baseGasPrice) + .times(gasPriceCoef) + .idiv(255) + .plus(baseGasPrice) + .times(gas); +}; diff --git a/libs/ledger-live-common/src/generated/account.ts b/libs/ledger-live-common/src/generated/account.ts index 1e7462b0c9ac..79642ea6152e 100644 --- a/libs/ledger-live-common/src/generated/account.ts +++ b/libs/ledger-live-common/src/generated/account.ts @@ -5,6 +5,7 @@ import crypto_org from "../families/crypto_org/account"; import elrond from "../families/elrond/account"; import filecoin from "../families/filecoin/account"; import near from "../families/near/account"; +import vechain from "../families/vechain/account"; import polkadot from "@ledgerhq/coin-polkadot/account"; export default { @@ -15,5 +16,6 @@ export default { elrond, filecoin, near, + vechain, polkadot, }; diff --git a/libs/ledger-live-common/src/generated/bridge/js.ts b/libs/ledger-live-common/src/generated/bridge/js.ts index 1e66e1b9f3bf..f91303e8e978 100644 --- a/libs/ledger-live-common/src/generated/bridge/js.ts +++ b/libs/ledger-live-common/src/generated/bridge/js.ts @@ -17,6 +17,7 @@ import solana from "../../families/solana/bridge/js"; import stellar from "../../families/stellar/bridge/js"; import tezos from "../../families/tezos/bridge/js"; import tron from "../../families/tron/bridge/js"; +import vechain from "../../families/vechain/bridge/js"; export default { algorand, @@ -38,4 +39,5 @@ export default { stellar, tezos, tron, + vechain, }; diff --git a/libs/ledger-live-common/src/generated/cli-transaction.ts b/libs/ledger-live-common/src/generated/cli-transaction.ts index 0f178b359297..f3ec4ce47695 100644 --- a/libs/ledger-live-common/src/generated/cli-transaction.ts +++ b/libs/ledger-live-common/src/generated/cli-transaction.ts @@ -14,6 +14,7 @@ import solana from "../families/solana/cli-transaction"; import stellar from "../families/stellar/cli-transaction"; import tezos from "../families/tezos/cli-transaction"; import tron from "../families/tron/cli-transaction"; +import vechain from "../families/vechain/cli-transaction"; import polkadot from "@ledgerhq/coin-polkadot/cli-transaction"; export default { @@ -33,5 +34,6 @@ export default { stellar, tezos, tron, + vechain, polkadot, }; diff --git a/libs/ledger-live-common/src/generated/hw-getAddress.ts b/libs/ledger-live-common/src/generated/hw-getAddress.ts index 58863d6d5d1e..b9b5e4124467 100644 --- a/libs/ledger-live-common/src/generated/hw-getAddress.ts +++ b/libs/ledger-live-common/src/generated/hw-getAddress.ts @@ -16,6 +16,7 @@ import solana from "../families/solana/hw-getAddress"; import stellar from "../families/stellar/hw-getAddress"; import tezos from "../families/tezos/hw-getAddress"; import tron from "../families/tron/hw-getAddress"; +import vechain from "../families/vechain/hw-getAddress"; import polkadot from "@ledgerhq/coin-polkadot/hw-getAddress"; export default { @@ -37,5 +38,6 @@ export default { stellar, tezos, tron, + vechain, polkadot, }; diff --git a/libs/ledger-live-common/src/generated/mock.ts b/libs/ledger-live-common/src/generated/mock.ts index 9e9f03bc5fda..74f61a10c8a7 100644 --- a/libs/ledger-live-common/src/generated/mock.ts +++ b/libs/ledger-live-common/src/generated/mock.ts @@ -1,9 +1,11 @@ import algorand from "../families/algorand/mock"; import cosmos from "../families/cosmos/mock"; import ethereum from "../families/ethereum/mock"; +import vechain from "../families/vechain/mock"; export default { algorand, cosmos, ethereum, + vechain, }; diff --git a/libs/ledger-live-common/src/generated/specs.ts b/libs/ledger-live-common/src/generated/specs.ts index 542e986e6c74..57f36d344885 100644 --- a/libs/ledger-live-common/src/generated/specs.ts +++ b/libs/ledger-live-common/src/generated/specs.ts @@ -15,6 +15,7 @@ import solana from "../families/solana/specs"; import stellar from "../families/stellar/specs"; import tezos from "../families/tezos/specs"; import tron from "../families/tron/specs"; +import vechain from "../families/vechain/specs"; import polkadot from "@ledgerhq/coin-polkadot/specs"; export default { @@ -35,5 +36,6 @@ export default { stellar, tezos, tron, + vechain, polkadot, }; diff --git a/libs/ledger-live-common/src/generated/transaction.ts b/libs/ledger-live-common/src/generated/transaction.ts index 6b70d4f1a89d..fdb6477141d9 100644 --- a/libs/ledger-live-common/src/generated/transaction.ts +++ b/libs/ledger-live-common/src/generated/transaction.ts @@ -16,6 +16,7 @@ import solana from "../families/solana/transaction"; import stellar from "../families/stellar/transaction"; import tezos from "../families/tezos/transaction"; import tron from "../families/tron/transaction"; +import vechain from "../families/vechain/transaction"; import polkadot from "@ledgerhq/coin-polkadot/transaction"; export default { @@ -37,5 +38,6 @@ export default { stellar, tezos, tron, + vechain, polkadot, }; diff --git a/libs/ledger-live-common/src/generated/types.ts b/libs/ledger-live-common/src/generated/types.ts index 05010e818fa6..836855e07475 100644 --- a/libs/ledger-live-common/src/generated/types.ts +++ b/libs/ledger-live-common/src/generated/types.ts @@ -74,6 +74,10 @@ import { Transaction as tronTransaction } from "../families/tron/types"; import { TransactionRaw as tronTransactionRaw } from "../families/tron/types"; import { TransactionStatus as tronTransactionStatus } from "../families/tron/types"; import { TransactionStatusRaw as tronTransactionStatusRaw } from "../families/tron/types"; +import { Transaction as vechainTransaction } from "../families/vechain/types"; +import { TransactionRaw as vechainTransactionRaw } from "../families/vechain/types"; +import { TransactionStatus as vechainTransactionStatus } from "../families/vechain/types"; +import { TransactionStatusRaw as vechainTransactionStatusRaw } from "../families/vechain/types"; export type Transaction = | algorandTransaction @@ -94,7 +98,8 @@ export type Transaction = | solanaTransaction | stellarTransaction | tezosTransaction - | tronTransaction; + | tronTransaction + | vechainTransaction; export type TransactionRaw = | algorandTransactionRaw @@ -115,7 +120,8 @@ export type TransactionRaw = | solanaTransactionRaw | stellarTransactionRaw | tezosTransactionRaw - | tronTransactionRaw; + | tronTransactionRaw + | vechainTransactionRaw; export type TransactionStatus = | algorandTransactionStatus @@ -136,7 +142,8 @@ export type TransactionStatus = | solanaTransactionStatus | stellarTransactionStatus | tezosTransactionStatus - | tronTransactionStatus; + | tronTransactionStatus + | vechainTransactionStatus; export type TransactionStatusRaw = | algorandTransactionStatusRaw @@ -157,4 +164,5 @@ export type TransactionStatusRaw = | solanaTransactionStatusRaw | stellarTransactionStatusRaw | tezosTransactionStatusRaw - | tronTransactionStatusRaw; + | tronTransactionStatusRaw + | vechainTransactionStatusRaw; diff --git a/libs/ledgerjs/packages/cryptoassets/src/currencies.ts b/libs/ledgerjs/packages/cryptoassets/src/currencies.ts index 2888f99939de..0208877b1bc8 100644 --- a/libs/ledgerjs/packages/cryptoassets/src/currencies.ts +++ b/libs/ledgerjs/packages/cryptoassets/src/currencies.ts @@ -2781,18 +2781,24 @@ export const cryptocurrenciesById: Record = { managerAppName: "VeChain", ticker: "VET", scheme: "vechain", - color: "#00C2FF", + color: "#28008C", family: "vechain", units: [ { name: "VET", code: "VET", - magnitude: 8, + magnitude: 18, + }, + { + name: "WEI", + code: "WEI", + magnitude: 0, }, ], explorerViews: [ { - tx: "https://explore.veforge.com/transactions/$hash", + tx: "https://explore-testnet.vechain.org/transactions/$hash", + address: "https://explore-testnet.vechain.org/accounts/$address", }, ], }, diff --git a/libs/ledgerjs/packages/cryptoassets/src/data/vip180.ts b/libs/ledgerjs/packages/cryptoassets/src/data/vip180.ts new file mode 100644 index 000000000000..451732027368 --- /dev/null +++ b/libs/ledgerjs/packages/cryptoassets/src/data/vip180.ts @@ -0,0 +1,7 @@ +export type vip180Token = [string, string, string, number, boolean?]; + +const vechainTokens: vip180Token[] = [ + ["VTHO", "VeThor", "0x0000000000000000000000000000456E65726779", 18, true], +]; + +export default vechainTokens; diff --git a/libs/ledgerjs/packages/cryptoassets/src/tokens.ts b/libs/ledgerjs/packages/cryptoassets/src/tokens.ts index 6403a6ba9b4d..93da8849386b 100644 --- a/libs/ledgerjs/packages/cryptoassets/src/tokens.ts +++ b/libs/ledgerjs/packages/cryptoassets/src/tokens.ts @@ -12,6 +12,8 @@ import polygonTokens, { PolygonERC20Token } from "./data/polygon-erc20"; import stellarTokens, { StellarToken } from "./data/stellar"; import trc10tokens, { TRC10Token } from "./data/trc10"; import trc20tokens, { TRC20Token } from "./data/trc20"; +import vechainTokens, { vip180Token } from "./data/vip180"; + //import spltokens from "../data/spl"; const emptyArray = []; const tokensArray: TokenCurrency[] = []; @@ -31,6 +33,7 @@ addTokens(asatokens.map(convertAlgorandASATokens)); addTokens(esdttokens.map(convertElrondESDTTokens)); addTokens(cardanoNativeTokens.map(convertCardanoNativeTokens)); addTokens(stellarTokens.map(convertStellarTokens)); +addTokens(vechainTokens.map(convertVechainToken)); //addTokens(spltokens.map(convertSplTokens)); type TokensListOptions = { withDelisted: boolean; @@ -317,6 +320,32 @@ function convertTRONTokens(type: "trc10" | "trc20") { }); } +function convertVechainToken([ + abbr, + name, + contractAddress, + precision, + enableCountervalues, +]: vip180Token): TokenCurrency { + return { + type: "TokenCurrency", + id: "vechain/vtho", + contractAddress: contractAddress, + parentCurrency: getCryptoCurrencyById("vechain"), + tokenType: "vip180", + name, + ticker: abbr, + disableCountervalue: !enableCountervalues, + units: [ + { + name, + code: abbr, + magnitude: precision, + }, + ], + }; +} + function convertElrondESDTTokens([ ticker, identifier, diff --git a/libs/ledgerjs/packages/hw-app-vet/README.md b/libs/ledgerjs/packages/hw-app-vet/README.md new file mode 100644 index 000000000000..f5e87357f42a --- /dev/null +++ b/libs/ledgerjs/packages/hw-app-vet/README.md @@ -0,0 +1,24 @@ + + +[GitHub](https://github.com/LedgerHQ/ledger-live/), +[Ledger Devs Discord](https://developers.ledger.com/discord-pro), +[Developer Portal](https://developers.ledger.com/) + +## @ledgerhq/hw-app-vet + +Ledger Hardware Wallet ETH JavaScript bindings. + +*** + +## Are you adding Ledger support to your software wallet? + +You may be using this package to communicate with the VeChain Nano App. + +For a smooth and quick integration: + +* See the developers’ documentation on the [Developer Portal](https://developers.ledger.com/docs/transport/overview/) and +* Go on [Discord](https://developers.ledger.com/discord-pro/) to chat with developer support and the developer community. + +*** + +## API diff --git a/libs/ledgerjs/packages/hw-app-vet/package.json b/libs/ledgerjs/packages/hw-app-vet/package.json new file mode 100644 index 000000000000..7ce482f87649 --- /dev/null +++ b/libs/ledgerjs/packages/hw-app-vet/package.json @@ -0,0 +1,45 @@ +{ + "name": "@ledgerhq/hw-app-vet", + "version": "0.0.1", + "description": "Ledger Hardware Wallet VeChain Application API", + "keywords": [ + "Ledger", + "LedgerWallet", + "VeChain", + "vet", + "vtho", + "VeThor", + "NanoS", + "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/ledgerjs/packages/hw-app-vet", + "publishConfig": { + "access": "public" + }, + "main": "lib/Vet.js", + "module": "lib-es/Vet.js", + "dependencies": { + "@ledgerhq/cryptoassets": "workspace:^", + "@ledgerhq/errors": "workspace:^", + "@ledgerhq/hw-transport": "workspace:^", + "@ledgerhq/hw-transport-mocker": "workspace:^", + "@ledgerhq/logs": "workspace:^" + }, + "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-vet/src/Vet.ts b/libs/ledgerjs/packages/hw-app-vet/src/Vet.ts new file mode 100644 index 000000000000..fd1514f4d25d --- /dev/null +++ b/libs/ledgerjs/packages/hw-app-vet/src/Vet.ts @@ -0,0 +1,139 @@ +import type Transport from "@ledgerhq/hw-transport"; +import { StatusCodes } from "./constants"; +import { VETLedgerAccount } from "./model"; +import { splitPath, splitRaw } from "./utils"; +import { Buffer } from "buffer"; + +import { VechainAppPleaseEnableContractDataAndMultiClause } from "./errors"; + +const remapTransactionRelatedErrors = (e) => { + if (e && e.statusCode === 0x6a80) { + return new VechainAppPleaseEnableContractDataAndMultiClause( + "Please enable contract data in Vechain app settings" + ); + } + + return e; +}; + +/** + * VeChain API + * + * @example + * import Vet from "@ledgerhq/hw-app-vet"; + * const vet = new Vet(transport) + */ + +export default class Vet { + transport: Transport; + + constructor(transport: Transport, scrambleKey = "V3T") { + this.transport = transport; + transport.decorateAppAPIMethods( + this, + ["getAppConfiguration", "getAddress", "signTransaction"], + scrambleKey + ); + } + + async getAppConfiguration(): Promise { + const response = await this.transport.send( + 0xe0, + 0x06, + 0x00, + 0x00, + Buffer.alloc(0), + [StatusCodes.OK] + ); + + return response.slice(0, 4); + } + + /** + * get VeChain address for a given BIP 32 path. + * @param path a path in BIP 32 format + * @option display + * @option chainCode + * @return an object with a publicKey and address + * @example + * vet.getAddress("m/44'/818'/0'/0").then(o => o.address) + */ + async getAddress( + path: string, + display?: boolean, + chainCode?: boolean, + statusCodes: StatusCodes[] = [StatusCodes.OK] + ): 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 response = await this.transport.send( + 0xe0, + 0x02, + display ? 0x01 : 0x00, + chainCode ? 0x01 : 0x00, + buffer, + statusCodes + ); + + const publicKeyLength = response[0]; + const addressLength = response[1 + publicKeyLength]; + const acc: VETLedgerAccount = { + publicKey: response.slice(1, 1 + publicKeyLength).toString("hex"), + address: + "0x" + + response + .slice( + 1 + publicKeyLength + 1, + 1 + publicKeyLength + 1 + addressLength + ) + .toString("ascii") + .toLowerCase(), + }; + if (chainCode) { + acc.chainCode = response + .slice( + 1 + publicKeyLength + 1 + addressLength, + 1 + publicKeyLength + 1 + addressLength + 32 + ) + .toString("hex"); + } + return acc; + } + + /** + * You can sign a transaction and retrieve v, r, s given the raw transaction and the BIP 32 path of the account to sign. + * + * @param path: the BIP32 path to sign the transaction on + * @param rawTxHex: the raw vechain transaction in hexadecimal to sign + */ + async signTransaction(path: string, rawTxHex: string): Promise { + const buffers = splitRaw(path, rawTxHex, true); + const responses = [] as Buffer[]; + + for (let i = 0; i < buffers.length; i++) { + const data = buffers[i]; + responses.push( + await this.transport + .send(0xe0, 0x04, i === 0 ? 0x00 : 0x80, 0x00, data, [StatusCodes.OK]) + .catch((e) => { + throw remapTransactionRelatedErrors(e); + }) + ); + } + + const lastResponse = responses[responses.length - 1]; + if (lastResponse.length < 65) { + throw new Error("invalid signature"); + } + + return lastResponse.slice(0, 65); + } +} diff --git a/libs/ledgerjs/packages/hw-app-vet/src/constants.ts b/libs/ledgerjs/packages/hw-app-vet/src/constants.ts new file mode 100644 index 000000000000..6c9ed9a0eefd --- /dev/null +++ b/libs/ledgerjs/packages/hw-app-vet/src/constants.ts @@ -0,0 +1,32 @@ +export enum StatusCodes { + PIN_REMAINING_ATTEMPTS = 0x63c0, + INCORRECT_LENGTH = 0x6700, + COMMAND_INCOMPATIBLE_FILE_STRUCTURE = 0x6981, + SECURITY_STATUS_NOT_SATISFIED = 0x6982, + CONDITIONS_OF_USE_NOT_SATISFIED = 0x6985, + INCORRECT_DATA = 0x6a80, + NOT_ENOUGH_MEMORY_SPACE = 0x6a84, + REFERENCED_DATA_NOT_FOUND = 0x6a88, + FILE_ALREADY_EXISTS = 0x6a89, + INCORRECT_P1_P2 = 0x6b00, + INS_NOT_SUPPORTED = 0x6d00, + CLA_NOT_SUPPORTED = 0x6e00, + TECHNICAL_PROBLEM = 0x6f00, + OK = 0x9000, + MEMORY_PROBLEM = 0x9240, + NO_EF_SELECTED = 0x9400, + INVALID_OFFSET = 0x9402, + FILE_NOT_FOUND = 0x9404, + INCONSISTENT_FILE = 0x9408, + ALGORITHM_NOT_SUPPORTED = 0x9484, + INVALID_KCV = 0x9485, + CODE_NOT_INITIALIZED = 0x9802, + ACCESS_CONDITION_NOT_FULFILLED = 0x9804, + CONTRADICTION_SECRET_CODE_STATUS = 0x9808, + CONTRADICTION_INVALIDATION = 0x9810, + CODE_BLOCKED = 0x9840, + MAX_VALUE_REACHED = 0x9850, + GP_AUTH_FAILED = 0x6300, + LICENSING = 0x6f42, + HALTED = 0x6faa, +} diff --git a/libs/ledgerjs/packages/hw-app-vet/src/errors.ts b/libs/ledgerjs/packages/hw-app-vet/src/errors.ts new file mode 100644 index 000000000000..3bdcfda5fa0d --- /dev/null +++ b/libs/ledgerjs/packages/hw-app-vet/src/errors.ts @@ -0,0 +1,4 @@ +import { createCustomErrorClass } from "@ledgerhq/errors"; + +export const VechainAppPleaseEnableContractDataAndMultiClause = + createCustomErrorClass("VechainAppPleaseEnableContractDataAndMultiClause"); diff --git a/libs/ledgerjs/packages/hw-app-vet/src/model.ts b/libs/ledgerjs/packages/hw-app-vet/src/model.ts new file mode 100644 index 000000000000..8e8a5139e2e5 --- /dev/null +++ b/libs/ledgerjs/packages/hw-app-vet/src/model.ts @@ -0,0 +1,5 @@ +export interface VETLedgerAccount { + publicKey: string; + address: string; + chainCode?: string; +} diff --git a/libs/ledgerjs/packages/hw-app-vet/src/utils.ts b/libs/ledgerjs/packages/hw-app-vet/src/utils.ts new file mode 100644 index 000000000000..6807f22f02b2 --- /dev/null +++ b/libs/ledgerjs/packages/hw-app-vet/src/utils.ts @@ -0,0 +1,54 @@ +import { Buffer } from "buffer"; + +export const splitPath = (path: string): number[] => { + return path + .split("/") + .map((elem) => { + let num = parseInt(elem, 10); + if (elem.length > 1 && elem[elem.length - 1] === "'") { + num += 0x80000000; + } + return num; + }) + .filter((num) => !isNaN(num)); +}; + +export const splitRaw = ( + path: string, + rawHex: string, + isTransaction: boolean +): Buffer[] => { + const contentByteLength = isTransaction ? 0 : 4; + const paths = splitPath(path); + let offset = 0; + const raw = Buffer.from(rawHex, "hex"); + const buffers: Buffer[] = []; + while (offset !== raw.length) { + const maxChunkSize = + offset === 0 ? 255 - 1 - paths.length * 4 - contentByteLength : 255; + const chunkSize = + offset + maxChunkSize > raw.length ? raw.length - offset : maxChunkSize; + const buffer = Buffer.alloc( + offset === 0 + ? 1 + paths.length * 4 + contentByteLength + chunkSize + : chunkSize + ); + if (offset === 0) { + buffer[0] = paths.length; + paths.forEach((element, index) => { + buffer.writeUInt32BE(element, 1 + 4 * index); + }); + if (isTransaction) { + raw.copy(buffer, 1 + 4 * paths.length, offset, offset + chunkSize); + } else { + buffer.writeUInt32BE(raw.length, 1 + 4 * paths.length); + raw.copy(buffer, 1 + 4 * paths.length + 4, offset, offset + chunkSize); + } + } else { + raw.copy(buffer, 0, offset, offset + chunkSize); + } + buffers.push(buffer); + offset += chunkSize; + } + return buffers; +}; diff --git a/libs/ledgerjs/packages/hw-app-vet/tsconfig.json b/libs/ledgerjs/packages/hw-app-vet/tsconfig.json new file mode 100644 index 000000000000..6cb1875bbe90 --- /dev/null +++ b/libs/ledgerjs/packages/hw-app-vet/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": "lib", + "moduleResolution": "node" + }, + "include": ["src/**/*"] +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 5fe9377eec42..fd3b3c9a85ad 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1205,6 +1205,7 @@ importers: '@ledgerhq/hw-app-str': workspace:^ '@ledgerhq/hw-app-tezos': workspace:^ '@ledgerhq/hw-app-trx': workspace:^ + '@ledgerhq/hw-app-vet': workspace:^ '@ledgerhq/hw-app-xrp': workspace:^ '@ledgerhq/hw-transport': workspace:^ '@ledgerhq/hw-transport-mocker': workspace:^ @@ -1337,6 +1338,7 @@ importers: source-map-support: ^0.5.21 stellar-sdk: ^10.1.1 superstruct: 0.14.2 + thor-devkit: ^2.0.6 timemachine: ^0.3.2 tiny-secp256k1: ^1.1.6 triple-beam: ^1.3.0 @@ -1388,6 +1390,7 @@ importers: '@ledgerhq/hw-app-str': link:../ledgerjs/packages/hw-app-str '@ledgerhq/hw-app-tezos': link:../ledgerjs/packages/hw-app-tezos '@ledgerhq/hw-app-trx': link:../ledgerjs/packages/hw-app-trx + '@ledgerhq/hw-app-vet': link:../ledgerjs/packages/hw-app-vet '@ledgerhq/hw-app-xrp': link:../ledgerjs/packages/hw-app-xrp '@ledgerhq/hw-transport': link:../ledgerjs/packages/hw-transport '@ledgerhq/hw-transport-mocker': link:../ledgerjs/packages/hw-transport-mocker @@ -1474,6 +1477,7 @@ importers: source-map-support: 0.5.21 stellar-sdk: 10.1.2 superstruct: 0.14.2 + thor-devkit: 2.0.7 tiny-secp256k1: 1.1.6 triple-beam: 1.3.0 utility-types: 3.10.0 @@ -2073,6 +2077,39 @@ importers: ts-node: 10.7.0_33lso24kgdvwdhve7eirdtgycu typescript: 4.6.4 + libs/ledgerjs/packages/hw-app-vet: + specifiers: + '@ledgerhq/cryptoassets': workspace:^ + '@ledgerhq/errors': workspace:^ + '@ledgerhq/hw-transport': workspace:^ + '@ledgerhq/hw-transport-mocker': workspace:^ + '@ledgerhq/logs': workspace:^ + '@types/jest': '*' + '@types/node': '*' + documentation: 13.2.4 + jest: ^28.1.1 + rimraf: '*' + source-map-support: '*' + ts-jest: ^28.0.5 + ts-node: ^10.4.0 + typescript: ^4 + dependencies: + '@ledgerhq/cryptoassets': link:../cryptoassets + '@ledgerhq/errors': link:../errors + '@ledgerhq/hw-transport': link:../hw-transport + '@ledgerhq/hw-transport-mocker': link:../hw-transport-mocker + '@ledgerhq/logs': link:../logs + devDependencies: + '@types/jest': 27.5.1 + '@types/node': 17.0.32 + documentation: 13.2.4 + jest: 28.1.3_5gljhujmafmfys272awzdklxia + rimraf: 4.1.2 + source-map-support: 0.5.21 + ts-jest: 28.0.8_mgg23zyyvjoe75wbzvxzqqpmne + ts-node: 10.9.1_c7x7xisxsab3cereq3ok2kgue4 + typescript: 4.9.5 + libs/ledgerjs/packages/hw-app-xrp: specifiers: '@ledgerhq/hw-transport': workspace:^ @@ -12079,7 +12116,7 @@ packages: engines: {node: ^8.13.0 || >=10.10.0} dependencies: '@grpc/proto-loader': 0.6.12 - '@types/node': 18.14.0 + '@types/node': 17.0.32 dev: false /@grpc/proto-loader/0.6.12: @@ -12312,7 +12349,7 @@ packages: engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} dependencies: '@jest/types': 28.1.3 - '@types/node': 18.14.0 + '@types/node': 17.0.32 chalk: 4.1.2 jest-message-util: 28.1.3 jest-util: 28.1.3 @@ -12471,14 +12508,14 @@ packages: '@jest/test-result': 28.1.3 '@jest/transform': 28.1.3 '@jest/types': 28.1.3 - '@types/node': 18.14.0 + '@types/node': 17.0.32 ansi-escapes: 4.3.2 chalk: 4.1.2 ci-info: 3.3.2 exit: 0.1.2 graceful-fs: 4.2.10 jest-changed-files: 28.1.3 - jest-config: 28.1.3_@types+node@18.14.0 + jest-config: 28.1.3_@types+node@17.0.32 jest-haste-map: 28.1.3 jest-message-util: 28.1.3 jest-regex-util: 28.0.2 @@ -12515,14 +12552,14 @@ packages: '@jest/test-result': 28.1.3 '@jest/transform': 28.1.3_metro@0.67.0 '@jest/types': 28.1.3 - '@types/node': 18.14.0 + '@types/node': 17.0.32 ansi-escapes: 4.3.2 chalk: 4.1.2 ci-info: 3.3.2 exit: 0.1.2 graceful-fs: 4.2.10 jest-changed-files: 28.1.3 - jest-config: 28.1.3_7pfhl4ojhd742rt2464dctxvle + jest-config: 28.1.3_yf3cg2odbh43cd2z24nrd3jmti jest-haste-map: 28.1.3_metro@0.67.0 jest-message-util: 28.1.3 jest-regex-util: 28.0.2 @@ -12558,14 +12595,14 @@ packages: '@jest/test-result': 28.1.3 '@jest/transform': 28.1.3 '@jest/types': 28.1.3 - '@types/node': 18.14.0 + '@types/node': 17.0.32 ansi-escapes: 4.3.2 chalk: 4.1.2 ci-info: 3.3.2 exit: 0.1.2 graceful-fs: 4.2.10 jest-changed-files: 28.1.3 - jest-config: 28.1.3_pskp4jkjhhn6qjwkeo2bph2enm + jest-config: 28.1.3_r5rwvyw2qgx3fnijwswldzdnfy jest-haste-map: 28.1.3 jest-message-util: 28.1.3 jest-regex-util: 28.0.2 @@ -12602,14 +12639,14 @@ packages: '@jest/test-result': 28.1.3 '@jest/transform': 28.1.3 '@jest/types': 28.1.3 - '@types/node': 18.14.0 + '@types/node': 17.0.32 ansi-escapes: 4.3.2 chalk: 4.1.2 ci-info: 3.3.2 exit: 0.1.2 graceful-fs: 4.2.10 jest-changed-files: 28.1.3 - jest-config: 28.1.3_lszm27wzplody5hzcr5ax274dy + jest-config: 28.1.3_5gljhujmafmfys272awzdklxia jest-haste-map: 28.1.3 jest-message-util: 28.1.3 jest-regex-util: 28.0.2 @@ -12666,7 +12703,7 @@ packages: dependencies: '@jest/fake-timers': 28.1.3 '@jest/types': 28.1.3 - '@types/node': 18.14.0 + '@types/node': 17.0.32 jest-mock: 28.1.3 /@jest/environment/29.3.1: @@ -12740,7 +12777,7 @@ packages: dependencies: '@jest/types': 28.1.3 '@sinonjs/fake-timers': 9.1.2 - '@types/node': 18.14.0 + '@types/node': 17.0.32 jest-message-util: 28.1.3 jest-mock: 28.1.3 jest-util: 28.1.3 @@ -12873,7 +12910,7 @@ packages: '@jest/transform': 28.1.3 '@jest/types': 28.1.3 '@jridgewell/trace-mapping': 0.3.17 - '@types/node': 18.14.0 + '@types/node': 17.0.32 chalk: 4.1.2 collect-v8-coverage: 1.0.1 exit: 0.1.2 @@ -12912,7 +12949,7 @@ packages: '@jest/transform': 28.1.3_metro@0.67.0 '@jest/types': 28.1.3 '@jridgewell/trace-mapping': 0.3.17 - '@types/node': 18.14.0 + '@types/node': 17.0.32 chalk: 4.1.2 collect-v8-coverage: 1.0.1 exit: 0.1.2 @@ -12978,12 +13015,6 @@ packages: dependencies: '@sinclair/typebox': 0.24.20 - /@jest/schemas/29.0.0: - resolution: {integrity: sha512-3Ab5HgYIIAnS0HjqJHQYZS+zXc4tUmTmBH3z83ajI6afXp8X3ZtdLX+nXx+I7LNkJD7uN9LAVhgnjDgZa2z0kA==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - dependencies: - '@sinclair/typebox': 0.24.20 - /@jest/schemas/29.4.0: resolution: {integrity: sha512-0E01f/gOZeNTG76i5eWWSupvSHaIINrTie7vCyjiYFKgzNdyEGd12BUv4oNBFHOqlHDbtoJi3HrQ38KCC90NsQ==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} @@ -13280,7 +13311,7 @@ packages: dependencies: '@types/istanbul-lib-coverage': 2.0.4 '@types/istanbul-reports': 3.0.1 - '@types/node': 18.14.0 + '@types/node': 17.0.32 '@types/yargs': 15.0.14 chalk: 4.1.2 @@ -13290,7 +13321,7 @@ packages: dependencies: '@types/istanbul-lib-coverage': 2.0.4 '@types/istanbul-reports': 3.0.1 - '@types/node': 18.14.0 + '@types/node': 17.0.32 '@types/yargs': 16.0.4 chalk: 4.1.2 @@ -13301,7 +13332,7 @@ packages: '@jest/schemas': 28.1.3 '@types/istanbul-lib-coverage': 2.0.4 '@types/istanbul-reports': 3.0.1 - '@types/node': 18.14.0 + '@types/node': 17.0.32 '@types/yargs': 17.0.10 chalk: 4.1.2 @@ -13309,7 +13340,7 @@ packages: resolution: {integrity: sha512-d0S0jmmTpjnhCmNpApgX3jrUZgZ22ivKJRvL2lli5hpCRoNnp1f85r2/wpKfXuYu8E7Jjh1hGfhPyup1NM5AmA==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: - '@jest/schemas': 29.0.0 + '@jest/schemas': 29.4.0 '@types/istanbul-lib-coverage': 2.0.4 '@types/istanbul-reports': 3.0.1 '@types/node': 17.0.32 @@ -14120,7 +14151,7 @@ packages: /@octokit/types/2.16.2: resolution: {integrity: sha512-O75k56TYvJ8WpAakWwYRN8Bgu60KrmX0z1KqFp1kNiFNkgW+JW+9EBKZ+S33PU6SLvbihqd+3drvPxKK68Ee8Q==} dependencies: - '@types/node': 18.14.0 + '@types/node': 17.0.32 dev: true /@octokit/types/6.34.0: @@ -15404,7 +15435,7 @@ packages: '@rollup/pluginutils': 3.1.0_rollup@2.79.1 '@types/resolve': 1.17.1 builtin-modules: 3.3.0 - deepmerge: 4.2.2 + deepmerge: 4.3.0 is-module: 1.0.0 resolve: 1.22.1 rollup: 2.79.1 @@ -17110,7 +17141,7 @@ packages: '@storybook/store': 6.4.22_sfoxds7t5ydpegc3knd667wn6m '@storybook/theming': 6.4.22_sfoxds7t5ydpegc3knd667wn6m '@storybook/ui': 6.4.22_k2mvpji5i2ojml6m4ftklg47pa - '@types/node': 14.18.17 + '@types/node': 14.18.36 '@types/webpack': 4.41.32 autoprefixer: 9.8.8 babel-loader: 8.2.5_nwtvwtk5tmh22l2urnqucz7kqu @@ -17204,7 +17235,7 @@ packages: '@storybook/store': 6.4.22_sfoxds7t5ydpegc3knd667wn6m '@storybook/theming': 6.4.22_sfoxds7t5ydpegc3knd667wn6m '@storybook/ui': 6.4.22_k2mvpji5i2ojml6m4ftklg47pa - '@types/node': 14.18.17 + '@types/node': 14.18.36 '@types/webpack': 4.41.32 autoprefixer: 9.8.8 babel-loader: 8.2.5_nwtvwtk5tmh22l2urnqucz7kqu @@ -17298,7 +17329,7 @@ packages: '@storybook/store': 6.4.22_sfoxds7t5ydpegc3knd667wn6m '@storybook/theming': 6.4.22_sfoxds7t5ydpegc3knd667wn6m '@storybook/ui': 6.4.22_k2mvpji5i2ojml6m4ftklg47pa - '@types/node': 14.18.17 + '@types/node': 14.18.36 '@types/webpack': 4.41.32 autoprefixer: 9.8.8 babel-loader: 8.2.5_nwtvwtk5tmh22l2urnqucz7kqu @@ -19524,7 +19555,7 @@ packages: /@types/bn.js/4.11.6: resolution: {integrity: sha512-pqr857jrp2kPuO9uRjZ3PwnJTjoQy+fcdxvBTvHm6dkmEL9q+hDD/2j/0ELOBPtPnS8LjCX0gI9nbl8lVkadpg==} dependencies: - '@types/node': 18.14.0 + '@types/node': 17.0.32 dev: false /@types/bn.js/5.1.0: @@ -19536,7 +19567,7 @@ packages: /@types/bn.js/5.1.1: resolution: {integrity: sha512-qNrYbZqMx0uJAfKnKclPh+dTwK33KfLHYqtyODwd5HnXOjnkhc4qgn3BrK6RWyGZm5+sIFE7Q7Vz6QQtJB7w7g==} dependencies: - '@types/node': 18.14.0 + '@types/node': 17.0.32 dev: false /@types/body-parser/1.19.2: @@ -19582,7 +19613,7 @@ packages: dependencies: '@types/http-cache-semantics': 4.0.1 '@types/keyv': 3.1.4 - '@types/node': 16.18.12 + '@types/node': 17.0.32 '@types/responselike': 1.0.0 /@types/cbor/6.0.0: @@ -19714,19 +19745,19 @@ packages: /@types/fs-extra/9.0.13: resolution: {integrity: sha512-nEnwB++1u5lVDM2UI4c1+5R+FYaKfaAzS4OococimjVm3nQw3TuzH5UNsocrcTBbhnerblyHj4A49qXbIiZdpA==} dependencies: - '@types/node': 18.14.0 + '@types/node': 17.0.32 dev: true /@types/glob/7.2.0: resolution: {integrity: sha512-ZUxbzKl0IfJILTS6t7ip5fQQM/J3TJYubDm3nMbgubNNYS62eXeUpoLUC8/7fJNiFYHTrGPQn7hspDUzIHX3UA==} dependencies: '@types/minimatch': 3.0.5 - '@types/node': 18.14.0 + '@types/node': 17.0.32 /@types/graceful-fs/4.1.5: resolution: {integrity: sha512-anKkLmZZ+xm4p8JWBf4hElkM4XR+EZeA2M9BAkkTldmcyDY4mbdIJnRghDJH3Ov5ooY7/UAoENtmdMSkaAd7Cw==} dependencies: - '@types/node': 18.14.0 + '@types/node': 17.0.32 /@types/hammerjs/2.0.41: resolution: {integrity: sha512-ewXv/ceBaJprikMcxCmWU1FKyMAQ2X7a9Gtmzw8fcg2kIePI1crERDM818W+XYrxqdBBOdlf2rm137bU+BltCA==} @@ -19979,12 +20010,9 @@ packages: /@types/node/17.0.32: resolution: {integrity: sha512-eAIcfAvhf/BkHcf4pkLJ7ECpBAhh9kcxRBpip9cTiO+hf+aJrsxYxBeS6OXvOd9WqNAJmavXVpZvY1rBjNsXmw==} - /@types/node/18.11.18: - resolution: {integrity: sha512-DHQpWGjyQKSHj3ebjFI/wRKcqQcdR+MoFBygntYOZytCqNfkd2ZC4ARDJ2DQqhjH5p85Nnd3jhUJIXrszFX/JA==} - dev: true - /@types/node/18.14.0: resolution: {integrity: sha512-5EWrvLmglK+imbCJY0+INViFWUHg1AHel1sq4ZVSfdcNqGy9Edv3UB9IIzzg+xPaUcAgZYcfVs2fBcwDeZzU0A==} + dev: true /@types/normalize-package-data/2.4.1: resolution: {integrity: sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw==} @@ -20023,7 +20051,7 @@ packages: /@types/pbkdf2/3.1.0: resolution: {integrity: sha512-Cf63Rv7jCQ0LaL8tNXmEyqTHuIJxRdlS5vMh1mj5voN4+QFhVZnlZruezqpWYDiJ8UTzhP0VmeLXCmBk66YrMQ==} dependencies: - '@types/node': 18.14.0 + '@types/node': 17.0.32 dev: false /@types/pino-http/5.8.1: @@ -20058,7 +20086,7 @@ packages: resolution: {integrity: sha512-ULqvZNGMv0zRFvqn8/4LSPtnmN4MfhlPNtJCTpKuIIxGVGZ2rYWzFXrvEBoh9CVyqSE7D6YFRJ1hydLHI6kbWw==} requiresBuild: true dependencies: - '@types/node': 18.14.0 + '@types/node': 17.0.32 xmlbuilder: 15.1.1 dev: true optional: true @@ -20356,7 +20384,7 @@ packages: /@types/secp256k1/4.0.3: resolution: {integrity: sha512-Da66lEIFeIz9ltsdMZcpQvmrmmoqrfju8pm1BH8WbYjZSwUgCwXLb9C+9XYogwBITnbsSaMdVPb2ekf7TV+03w==} dependencies: - '@types/node': 18.14.0 + '@types/node': 17.0.32 dev: false /@types/semver/6.2.3: @@ -21708,6 +21736,21 @@ packages: wonka: 4.0.15 dev: true + /@vechain/ethers/4.0.27-5: + resolution: {integrity: sha512-dR+rTUauPJpqHNBdEgV6Xh+o009uCRPCvN2HWYIAzZP2SvgsPHLxNUzeRbRKhNzz/HC8HjWNvECRxODF88B03Q==} + dependencies: + '@types/node': 10.17.60 + aes-js: 3.0.0 + bn.js: 4.12.0 + elliptic: 6.5.4 + hash.js: 1.1.3 + js-sha3: 0.5.7 + scrypt-js: 2.0.4 + setimmediate: 1.0.4 + uuid: 2.0.1 + xmlhttprequest: 1.8.0 + dev: false + /@vitejs/plugin-react/3.1.0_vite@4.1.1: resolution: {integrity: sha512-AfgcRL8ZBhAlc3BFdigClmTUMISmmzHn7sB2h9U1odvc5U/MjWXsAaz18b/WoppUTDBzxOJwo2VdClfUcItu9g==} engines: {node: ^14.18.0 || >=16.0.0} @@ -24515,6 +24558,10 @@ packages: resolution: {integrity: sha512-eJzYkFYy9L4JzXsbymsFn3p54D+llV27oTQ+ziJG7WFRheJcNZilgVXMG0LoZtlQSKBsJdWtLFqOD0u+U0jZKA==} dev: false + /bignumber.js/7.2.1: + resolution: {integrity: sha512-S4XzBk5sMB+Rcb/LNcpzXr57VRTxgAvaAEDAl1AwRx27j00hT84O6OkteE7u8UB3NuaaygCRrEpqox4uDOrbdQ==} + dev: false + /bignumber.js/9.0.1: resolution: {integrity: sha512-IdZR9mh6ahOBv/hYGiXyVuyCetmGJhtYkqLBpTStdhEGjegpPlUawydyaF3pbIOFynJTpllEs+NP+CS9jKFLjA==} dev: false @@ -25610,7 +25657,7 @@ packages: /canvas-renderer/2.2.0: resolution: {integrity: sha512-Itdq9pwXcs4IbbkRCXc7reeGBk6i6tlDtZTjE1yc+KvYkx1Mt3WLf6tidZ/Ixbm7Vmi+jpWKG0dRBor67x9yGw==} dependencies: - '@types/node': 18.14.0 + '@types/node': 17.0.32 dev: false /capability/0.2.5: @@ -33751,7 +33798,7 @@ packages: chalk: 4.1.2 chokidar: 3.5.3 cosmiconfig: 6.0.0 - deepmerge: 4.2.2 + deepmerge: 4.3.0 eslint: 7.32.0 fs-extra: 9.1.0 glob: 7.2.3 @@ -33783,7 +33830,7 @@ packages: chalk: 4.1.2 chokidar: 3.5.3 cosmiconfig: 6.0.0 - deepmerge: 4.2.2 + deepmerge: 4.3.0 eslint: 8.15.0 fs-extra: 9.1.0 glob: 7.2.3 @@ -33815,7 +33862,7 @@ packages: chalk: 4.1.2 chokidar: 3.5.3 cosmiconfig: 6.0.0 - deepmerge: 4.2.2 + deepmerge: 4.3.0 eslint: 8.2.0 fs-extra: 9.1.0 glob: 7.2.3 @@ -33847,7 +33894,7 @@ packages: chalk: 4.1.2 chokidar: 3.5.3 cosmiconfig: 6.0.0 - deepmerge: 4.2.2 + deepmerge: 4.3.0 eslint: 8.15.0 fs-extra: 9.1.0 glob: 7.2.3 @@ -34987,6 +35034,13 @@ packages: readable-stream: 3.6.0 safe-buffer: 5.2.1 + /hash.js/1.1.3: + resolution: {integrity: sha512-/UETyP0W22QILqS+6HowevwhEFJ3MBJnwTf75Qob9Wz9t0DPuisL8kW8YZMK62dHAKE1c1p+gY1TtOLY+USEHA==} + dependencies: + inherits: 2.0.4 + minimalistic-assert: 1.0.1 + dev: false + /hash.js/1.1.7: resolution: {integrity: sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==} dependencies: @@ -36277,7 +36331,7 @@ packages: engines: {node: '>= 0.4'} dependencies: call-bind: 1.0.2 - define-properties: 1.1.4 + define-properties: 1.2.0 dev: false /is-negated-glob/1.0.0: @@ -37254,7 +37308,7 @@ packages: babel-jest: 27.5.1_@babel+core@7.20.12 chalk: 4.1.2 ci-info: 3.3.2 - deepmerge: 4.2.2 + deepmerge: 4.3.0 glob: 7.2.3 graceful-fs: 4.2.10 jest-circus: 27.5.1 @@ -37360,45 +37414,6 @@ packages: - supports-color dev: true - /jest-config/28.1.3_7pfhl4ojhd742rt2464dctxvle: - resolution: {integrity: sha512-MG3INjByJ0J4AsNBm7T3hsuxKQqFIiRo/AUqb1q9LRKI5UU6Aar9JHbr9Ivn1TVwfUD9KirRoM/T6u8XlcQPHQ==} - engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} - peerDependencies: - '@types/node': '*' - ts-node: '>=9.0.0' - peerDependenciesMeta: - '@types/node': - optional: true - ts-node: - optional: true - dependencies: - '@babel/core': 7.20.12 - '@jest/test-sequencer': 28.1.3_metro@0.67.0 - '@jest/types': 28.1.3 - '@types/node': 18.14.0 - babel-jest: 28.1.3_okjo2qlwbydbhwsvujz5mo2jxm - chalk: 4.1.2 - ci-info: 3.3.2 - deepmerge: 4.3.0 - glob: 7.2.3 - graceful-fs: 4.2.10 - jest-circus: 28.1.3_metro@0.67.0 - jest-environment-node: 28.1.3 - jest-get-type: 28.0.2 - jest-regex-util: 28.0.2 - jest-resolve: 28.1.3_metro@0.67.0 - jest-runner: 28.1.3_metro@0.67.0 - jest-util: 28.1.3 - jest-validate: 28.1.3 - micromatch: 4.0.5 - parse-json: 5.2.0 - pretty-format: 28.1.3 - slash: 3.0.0 - strip-json-comments: 3.1.1 - transitivePeerDependencies: - - metro - - supports-color - /jest-config/28.1.3_@types+node@14.18.17: resolution: {integrity: sha512-MG3INjByJ0J4AsNBm7T3hsuxKQqFIiRo/AUqb1q9LRKI5UU6Aar9JHbr9Ivn1TVwfUD9KirRoM/T6u8XlcQPHQ==} engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} @@ -37479,46 +37494,6 @@ packages: - supports-color dev: true - /jest-config/28.1.3_@types+node@18.14.0: - resolution: {integrity: sha512-MG3INjByJ0J4AsNBm7T3hsuxKQqFIiRo/AUqb1q9LRKI5UU6Aar9JHbr9Ivn1TVwfUD9KirRoM/T6u8XlcQPHQ==} - engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} - peerDependencies: - '@types/node': '*' - ts-node: '>=9.0.0' - peerDependenciesMeta: - '@types/node': - optional: true - ts-node: - optional: true - dependencies: - '@babel/core': 7.20.12 - '@jest/test-sequencer': 28.1.3 - '@jest/types': 28.1.3 - '@types/node': 18.14.0 - babel-jest: 28.1.3_@babel+core@7.20.12 - chalk: 4.1.2 - ci-info: 3.3.2 - deepmerge: 4.3.0 - glob: 7.2.3 - graceful-fs: 4.2.10 - jest-circus: 28.1.3 - jest-environment-node: 28.1.3 - jest-get-type: 28.0.2 - jest-regex-util: 28.0.2 - jest-resolve: 28.1.3 - jest-runner: 28.1.3 - jest-util: 28.1.3 - jest-validate: 28.1.3 - micromatch: 4.0.5 - parse-json: 5.2.0 - pretty-format: 28.1.3 - slash: 3.0.0 - strip-json-comments: 3.1.1 - transitivePeerDependencies: - - metro - - supports-color - dev: true - /jest-config/28.1.3_csdo2gfejusb3n4cacufvlevtu: resolution: {integrity: sha512-MG3INjByJ0J4AsNBm7T3hsuxKQqFIiRo/AUqb1q9LRKI5UU6Aar9JHbr9Ivn1TVwfUD9KirRoM/T6u8XlcQPHQ==} engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} @@ -37560,47 +37535,6 @@ packages: - supports-color dev: true - /jest-config/28.1.3_lszm27wzplody5hzcr5ax274dy: - resolution: {integrity: sha512-MG3INjByJ0J4AsNBm7T3hsuxKQqFIiRo/AUqb1q9LRKI5UU6Aar9JHbr9Ivn1TVwfUD9KirRoM/T6u8XlcQPHQ==} - engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} - peerDependencies: - '@types/node': '*' - ts-node: '>=9.0.0' - peerDependenciesMeta: - '@types/node': - optional: true - ts-node: - optional: true - dependencies: - '@babel/core': 7.20.12 - '@jest/test-sequencer': 28.1.3 - '@jest/types': 28.1.3 - '@types/node': 18.14.0 - babel-jest: 28.1.3_@babel+core@7.20.12 - chalk: 4.1.2 - ci-info: 3.3.2 - deepmerge: 4.3.0 - glob: 7.2.3 - graceful-fs: 4.2.10 - jest-circus: 28.1.3 - jest-environment-node: 28.1.3 - jest-get-type: 28.0.2 - jest-regex-util: 28.0.2 - jest-resolve: 28.1.3 - jest-runner: 28.1.3 - jest-util: 28.1.3 - jest-validate: 28.1.3 - micromatch: 4.0.5 - parse-json: 5.2.0 - pretty-format: 28.1.3 - slash: 3.0.0 - strip-json-comments: 3.1.1 - ts-node: 10.9.1_c7x7xisxsab3cereq3ok2kgue4 - transitivePeerDependencies: - - metro - - supports-color - dev: true - /jest-config/28.1.3_metro@0.67.0: resolution: {integrity: sha512-MG3INjByJ0J4AsNBm7T3hsuxKQqFIiRo/AUqb1q9LRKI5UU6Aar9JHbr9Ivn1TVwfUD9KirRoM/T6u8XlcQPHQ==} engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} @@ -37639,7 +37573,7 @@ packages: - metro - supports-color - /jest-config/28.1.3_pskp4jkjhhn6qjwkeo2bph2enm: + /jest-config/28.1.3_r5rwvyw2qgx3fnijwswldzdnfy: resolution: {integrity: sha512-MG3INjByJ0J4AsNBm7T3hsuxKQqFIiRo/AUqb1q9LRKI5UU6Aar9JHbr9Ivn1TVwfUD9KirRoM/T6u8XlcQPHQ==} engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} peerDependencies: @@ -37654,7 +37588,7 @@ packages: '@babel/core': 7.20.12 '@jest/test-sequencer': 28.1.3 '@jest/types': 28.1.3 - '@types/node': 18.14.0 + '@types/node': 17.0.32 babel-jest: 28.1.3_@babel+core@7.20.12 chalk: 4.1.2 ci-info: 3.3.2 @@ -37680,7 +37614,7 @@ packages: - supports-color dev: true - /jest-config/28.1.3_r5rwvyw2qgx3fnijwswldzdnfy: + /jest-config/28.1.3_yf3cg2odbh43cd2z24nrd3jmti: resolution: {integrity: sha512-MG3INjByJ0J4AsNBm7T3hsuxKQqFIiRo/AUqb1q9LRKI5UU6Aar9JHbr9Ivn1TVwfUD9KirRoM/T6u8XlcQPHQ==} engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} peerDependencies: @@ -37693,21 +37627,21 @@ packages: optional: true dependencies: '@babel/core': 7.20.12 - '@jest/test-sequencer': 28.1.3 + '@jest/test-sequencer': 28.1.3_metro@0.67.0 '@jest/types': 28.1.3 '@types/node': 17.0.32 - babel-jest: 28.1.3_@babel+core@7.20.12 + babel-jest: 28.1.3_okjo2qlwbydbhwsvujz5mo2jxm chalk: 4.1.2 ci-info: 3.3.2 deepmerge: 4.3.0 glob: 7.2.3 graceful-fs: 4.2.10 - jest-circus: 28.1.3 + jest-circus: 28.1.3_metro@0.67.0 jest-environment-node: 28.1.3 jest-get-type: 28.0.2 jest-regex-util: 28.0.2 - jest-resolve: 28.1.3 - jest-runner: 28.1.3 + jest-resolve: 28.1.3_metro@0.67.0 + jest-runner: 28.1.3_metro@0.67.0 jest-util: 28.1.3 jest-validate: 28.1.3 micromatch: 4.0.5 @@ -37715,11 +37649,9 @@ packages: pretty-format: 28.1.3 slash: 3.0.0 strip-json-comments: 3.1.1 - ts-node: 10.7.0_33lso24kgdvwdhve7eirdtgycu transitivePeerDependencies: - metro - supports-color - dev: true /jest-diff/24.9.0: resolution: {integrity: sha512-qMfrTs8AdJE2iqrTp0hzh7kTd2PQWrsFyj9tORoKmu32xjPjeE4NyjVRDz8ybYwqS2ik8N4hsIpiVTyFeo2lBQ==} @@ -37920,7 +37852,7 @@ packages: '@jest/environment': 28.1.3 '@jest/fake-timers': 28.1.3 '@jest/types': 28.1.3 - '@types/node': 18.14.0 + '@types/node': 17.0.32 jest-mock: 28.1.3 jest-util: 28.1.3 @@ -38082,7 +38014,7 @@ packages: dependencies: '@jest/types': 28.1.3 '@types/graceful-fs': 4.1.5 - '@types/node': 18.14.0 + '@types/node': 17.0.32 anymatch: 3.1.2 fb-watchman: 2.0.1 graceful-fs: 4.2.10 @@ -38102,7 +38034,7 @@ packages: dependencies: '@jest/types': 28.1.3 '@types/graceful-fs': 4.1.5 - '@types/node': 18.14.0 + '@types/node': 17.0.32 anymatch: 3.1.2 fb-watchman: 2.0.1 graceful-fs: 4.2.10 @@ -38345,7 +38277,7 @@ packages: engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} dependencies: '@jest/types': 28.1.3 - '@types/node': 18.14.0 + '@types/node': 17.0.32 /jest-mock/29.3.1: resolution: {integrity: sha512-H8/qFDtDVMFvFP4X8NuOT3XRDzOUTz+FeACjufHzsOIBAxivLqkB1PoLCaJx9iPPQ8dZThHPp/G3WRWyMgA3JA==} @@ -38584,7 +38516,7 @@ packages: '@jest/test-result': 28.1.3 '@jest/transform': 28.1.3 '@jest/types': 28.1.3 - '@types/node': 18.14.0 + '@types/node': 17.0.32 chalk: 4.1.2 emittery: 0.10.2 graceful-fs: 4.2.10 @@ -38614,7 +38546,7 @@ packages: '@jest/test-result': 28.1.3 '@jest/transform': 28.1.3_metro@0.67.0 '@jest/types': 28.1.3 - '@types/node': 18.14.0 + '@types/node': 17.0.32 chalk: 4.1.2 emittery: 0.10.2 graceful-fs: 4.2.10 @@ -38768,7 +38700,7 @@ packages: resolution: {integrity: sha512-S5wqyz0DXnNJPd/xfIzZ5Xnp1HrJWBczg8mMfMpN78OJ5eDxXyf+Ygld9wX1DnUWbIbhM1YDY95NjR4CBXkb2g==} engines: {node: '>= 10.14.2'} dependencies: - '@types/node': 18.14.0 + '@types/node': 17.0.32 graceful-fs: 4.2.10 dev: true @@ -38776,7 +38708,7 @@ packages: resolution: {integrity: sha512-jZCyo6iIxO1aqUxpuBlwTDMkzOAJS4a3eYz3YzgxxVQFwLeSA7Jfq5cbqCY+JLvTDrWirgusI/0KwxKMgrdf7w==} engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} dependencies: - '@types/node': 18.14.0 + '@types/node': 17.0.32 graceful-fs: 4.2.10 /jest-snapshot/24.9.0: @@ -38917,7 +38849,7 @@ packages: engines: {node: '>= 10.14.2'} dependencies: '@jest/types': 26.6.2 - '@types/node': 18.14.0 + '@types/node': 17.0.32 chalk: 4.1.2 graceful-fs: 4.2.10 is-ci: 2.0.0 @@ -38929,7 +38861,7 @@ packages: engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} dependencies: '@jest/types': 27.5.1 - '@types/node': 18.14.0 + '@types/node': 17.0.32 chalk: 4.1.2 ci-info: 3.3.2 graceful-fs: 4.2.10 @@ -38940,7 +38872,7 @@ packages: engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} dependencies: '@jest/types': 28.1.3 - '@types/node': 18.14.0 + '@types/node': 17.0.32 chalk: 4.1.2 ci-info: 3.3.2 graceful-fs: 4.2.10 @@ -39075,7 +39007,7 @@ packages: dependencies: '@jest/test-result': 28.1.3 '@jest/types': 28.1.3 - '@types/node': 18.14.0 + '@types/node': 17.0.32 ansi-escapes: 4.3.2 chalk: 4.1.2 emittery: 0.10.2 @@ -39142,7 +39074,7 @@ packages: metro: optional: true dependencies: - '@types/node': 18.14.0 + '@types/node': 17.0.32 merge-stream: 2.0.0 supports-color: 8.1.1 @@ -39155,7 +39087,7 @@ packages: metro: optional: true dependencies: - '@types/node': 18.14.0 + '@types/node': 17.0.32 merge-stream: 2.0.0 metro: 0.67.0 supports-color: 8.1.1 @@ -39169,7 +39101,7 @@ packages: metro: optional: true dependencies: - '@types/node': 18.14.0 + '@types/node': 17.0.32 merge-stream: 2.0.0 metro: 0.67.0 supports-color: 8.1.1 @@ -39183,7 +39115,7 @@ packages: metro: optional: true dependencies: - '@types/node': 18.14.0 + '@types/node': 17.0.32 merge-stream: 2.0.0 metro: 0.67.0 supports-color: 8.1.1 @@ -46492,7 +46424,7 @@ packages: /probe-image-size/6.0.0: resolution: {integrity: sha512-99PZ5+RU4gqiTfK5ZDMDkZtn6eL4WlKfFyVJV7lFQvH3iGmQ85DqMTOdxorERO26LHkevR2qsxnHp0x/2UDJPA==} dependencies: - deepmerge: 4.2.2 + deepmerge: 4.3.0 needle: 2.9.1 stream-parser: 0.3.1 transitivePeerDependencies: @@ -46773,7 +46705,7 @@ packages: '@protobufjs/pool': 1.1.0 '@protobufjs/utf8': 1.1.0 '@types/long': 4.0.2 - '@types/node': 18.14.0 + '@types/node': 17.0.32 long: 4.0.0 dev: false @@ -50256,6 +50188,10 @@ packages: ajv-keywords: 5.1.0_ajv@8.11.0 dev: false + /scrypt-js/2.0.4: + resolution: {integrity: sha512-4KsaGcPnuhtCZQCxFxN3GVYIhKFPTdLd8PLC552XwbMndtD0cjRFAhDuuydXQ0h08ZfPgzqe6EKHozpuH74iDw==} + dev: false + /scrypt-js/3.0.1: resolution: {integrity: sha512-cdwTTnqPu0Hyvf5in5asVdZocVDTNRmR7XEcJuIzMjJeSHybHl7vpB66AzwTaIg6CLSbtjcxc8fqcySfnTkccA==} @@ -50609,6 +50545,10 @@ packages: is-plain-object: 2.0.4 split-string: 3.1.0 + /setimmediate/1.0.4: + resolution: {integrity: sha512-/TjEmXQVEzdod/FFskf3o7oOAsGhHf2j1dZqRFbDzq4F3mvvxflIIi4Hd3bLQE9y/CpwqfSQam5JakI/mi3Pog==} + dev: false + /setimmediate/1.0.5: resolution: {integrity: sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==} @@ -52835,6 +52775,18 @@ packages: dependencies: any-promise: 1.3.0 + /thor-devkit/2.0.7: + resolution: {integrity: sha512-km+r+4fD9KP9Ea8bCfobVg6dsRgiTfi2GK9vVGHmnFHP7WJc98C+7B/VmfunEigsavkwcHcASLX50CwzZfgU+g==} + dependencies: + '@vechain/ethers': 4.0.27-5 + bignumber.js: 7.2.1 + blakejs: 1.2.1 + elliptic: 6.5.4 + fast-json-stable-stringify: 2.1.0 + js-sha3: 0.5.7 + rlp: 2.2.7 + dev: false + /throat/4.1.0: resolution: {integrity: sha1-iQN8vJLFarGJJua6TLsgDhVnKmo=} @@ -54686,7 +54638,7 @@ packages: /util.promisify/1.0.1: resolution: {integrity: sha512-g9JpC/3He3bm38zsLupWryXHoEcS22YHthuPQSJdMy6KNrzIRzWqcsHzD/WUnqe45whVou4VIsPew37DoXWNrA==} dependencies: - define-properties: 1.1.4 + define-properties: 1.2.0 es-abstract: 1.20.1 has-symbols: 1.0.3 object.getownpropertydescriptors: 2.1.4 @@ -54695,7 +54647,7 @@ packages: resolution: {integrity: sha512-/s3UsZUrIfa6xDhr7zZhnE9SLQ5RIXyYfiVnMMyMDzOc8WhWN4Nbh36H842OyurKbCDAesZOJaVyvmSl6fhGQw==} dependencies: call-bind: 1.0.2 - define-properties: 1.1.4 + define-properties: 1.2.0 for-each: 0.3.3 has-symbols: 1.0.3 object.getownpropertydescriptors: 2.1.4 @@ -54743,6 +54695,11 @@ packages: resolution: {integrity: sha512-dsNgbLaTrd6l3MMxTtouOCFw4CBFc/3a+GgYA2YyrJvyQ1u6q4pcu3ktLoUZ/VN/Aw9WsauazbgsgdfVWgAKQg==} dev: true + /uuid/2.0.1: + resolution: {integrity: sha512-nWg9+Oa3qD2CQzHIP4qKUqwNfzKn8P0LtFhotaCTFchsV7ZfDhAybeip/HZVeMIpZi9JgY1E3nUlwaCmZT1sEg==} + deprecated: Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details. + dev: false + /uuid/3.3.2: resolution: {integrity: sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==} deprecated: Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details. @@ -57284,6 +57241,11 @@ packages: dependencies: sax: 1.2.4 + /xmlhttprequest/1.8.0: + resolution: {integrity: sha512-58Im/U0mlVBLM38NdZjHyhuMtCqa61469k2YP/AaPbvCoV9aQGUpbJBj1QRm2ytRiVQBD/fsw7L2bJGDVQswBA==} + engines: {node: '>=0.4.0'} + dev: false + /xregexp/4.4.1: resolution: {integrity: sha512-2u9HwfadaJaY9zHtRRnH6BY6CQVNQKkYm3oLtC9gJXXzfsbACg5X5e4EZZGVAH+YIfa+QA9lsFQTTe3HURF3ag==} dependencies: @@ -57571,7 +57533,7 @@ packages: dependencies: '@types/fs-extra': 9.0.13 '@types/minimist': 1.2.2 - '@types/node': 18.11.18 + '@types/node': 18.14.0 '@types/ps-tree': 1.1.2 '@types/which': 2.0.1 chalk: 5.0.1 From 043b3385c3c0c222df99a7414c4f799fee7fe26f Mon Sep 17 00:00:00 2001 From: Davide Carpini Date: Tue, 7 Mar 2023 14:38:04 +0100 Subject: [PATCH 002/126] fix(libs): fix block height --- .../src/families/vechain/api/sdk.ts | 9 +++++++++ .../src/families/vechain/js-synchronisation.ts | 12 +++++++++++- .../ledgerjs/packages/cryptoassets/src/currencies.ts | 1 + 3 files changed, 21 insertions(+), 1 deletion(-) diff --git a/libs/ledger-live-common/src/families/vechain/api/sdk.ts b/libs/ledger-live-common/src/families/vechain/api/sdk.ts index bcb7b2c34b87..6642c39fe2c0 100644 --- a/libs/ledger-live-common/src/families/vechain/api/sdk.ts +++ b/libs/ledger-live-common/src/families/vechain/api/sdk.ts @@ -28,6 +28,15 @@ export const getAccount = async (address: string): Promise => { return data; }; +export const getLastBlock = async (): Promise<{ number: number }> => { + const { data } = await network({ + method: "GET", + url: `${BASE_URL}/blocks/best`, + }); + + return data; +}; + /** * Get VET operations * @param accountId diff --git a/libs/ledger-live-common/src/families/vechain/js-synchronisation.ts b/libs/ledger-live-common/src/families/vechain/js-synchronisation.ts index 96990b2c63e6..a07a87ec5cee 100644 --- a/libs/ledger-live-common/src/families/vechain/js-synchronisation.ts +++ b/libs/ledger-live-common/src/families/vechain/js-synchronisation.ts @@ -5,7 +5,12 @@ import { encodeAccountId } from "../../account"; import eip55 from "eip55"; import { emptyHistoryCache } from "../../account"; -import { getAccount, getOperations, getTokenOperations } from "./api"; +import { + getAccount, + getLastBlock, + getOperations, + getTokenOperations, +} from "./api"; import { getTokenById } from "@ledgerhq/cryptoassets/tokens"; const getAccountShape: GetAccountShape = async (info) => { @@ -30,6 +35,9 @@ const getAccountShape: GetAccountShape = async (info) => { // get the current account balance state depending your api implementation const { balance, energy } = await getAccount(address); + // get the current block height + const { number: blockHeight } = await getLastBlock(); + // Merge new operations with the previously synced ones const newOperations = await getOperations(accountId, address, startAt); const vthoAccountId = accountId + "+" + encodeURIComponent("vechain/vtho"); @@ -56,6 +64,7 @@ const getAccountShape: GetAccountShape = async (info) => { spendableBalance: BigNumber(balance), operationsCount: operations.length, operations: operations, + blockHeight, subAccounts: [ { type: "TokenAccount" as TokenType, @@ -67,6 +76,7 @@ const getAccountShape: GetAccountShape = async (info) => { creationDate: min_date != -1 ? new Date(min_date) : new Date(), operationsCount: VTHOoperations.length, operations: VTHOoperations, + blockHeight, pendingOperations: (initialAccount?.subAccounts && initialAccount.subAccounts[0]?.pendingOperations) || diff --git a/libs/ledgerjs/packages/cryptoassets/src/currencies.ts b/libs/ledgerjs/packages/cryptoassets/src/currencies.ts index 0208877b1bc8..af0b720b73b4 100644 --- a/libs/ledgerjs/packages/cryptoassets/src/currencies.ts +++ b/libs/ledgerjs/packages/cryptoassets/src/currencies.ts @@ -2783,6 +2783,7 @@ export const cryptocurrenciesById: Record = { scheme: "vechain", color: "#28008C", family: "vechain", + blockAvgTime: 10, units: [ { name: "VET", From 4dde433723862dae3f73e805476cf31d60244600 Mon Sep 17 00:00:00 2001 From: Davide Carpini Date: Tue, 7 Mar 2023 15:49:08 +0100 Subject: [PATCH 003/126] fix(libs): fix error message when vtho not enough --- apps/ledger-live-desktop/static/i18n/en/app.json | 6 +++++- apps/ledger-live-mobile/src/locales/en/common.json | 6 +++++- libs/coin-framework/src/derivation.ts | 2 +- .../src/families/vechain/js-getTransactionStatus.ts | 5 ++++- 4 files changed, 15 insertions(+), 4 deletions(-) diff --git a/apps/ledger-live-desktop/static/i18n/en/app.json b/apps/ledger-live-desktop/static/i18n/en/app.json index 66d1cdb9ddd1..4e608afff255 100644 --- a/apps/ledger-live-desktop/static/i18n/en/app.json +++ b/apps/ledger-live-desktop/static/i18n/en/app.json @@ -5153,6 +5153,10 @@ "title": "Not enough EGLD to pay the transaction fees", "description": "Please send some EGLD to your account to pay for transactions." }, + "NotEnoughVTHO": { + "title": "Sorry, insufficient VTHO funds", + "description": "Please make sure the account has enough VTHO." + }, "NotEnoughBalance": { "title": "Sorry, insufficient funds", "description": "Please make sure the account has enough funds." @@ -5775,4 +5779,4 @@ "installCTA": "Restart installation" } } -} +} \ No newline at end of file diff --git a/apps/ledger-live-mobile/src/locales/en/common.json b/apps/ledger-live-mobile/src/locales/en/common.json index cab3e6d3ef79..a9d83769bb84 100644 --- a/apps/ledger-live-mobile/src/locales/en/common.json +++ b/apps/ledger-live-mobile/src/locales/en/common.json @@ -439,6 +439,10 @@ "ElrondDelegationBelowMinimumError": { "title": "Remaining delegation can't be less than {{formattedAmount}}" }, + "NotEnoughVTHO": { + "title": "Sorry, insufficient VTHO funds", + "description": "Please make sure the account has enough VTHO." + }, "NotEnoughEGLDForFees": { "title": "Not enough EGLD to pay the transaction fees", "description": "Please send some EGLD to your account to pay for transactions." @@ -6116,4 +6120,4 @@ "deviceConnect": { "title": "Connect device" } -} +} \ No newline at end of file diff --git a/libs/coin-framework/src/derivation.ts b/libs/coin-framework/src/derivation.ts index 291bfeb28be3..10d626576bf2 100644 --- a/libs/coin-framework/src/derivation.ts +++ b/libs/coin-framework/src/derivation.ts @@ -201,7 +201,7 @@ const modes = Object.freeze({ overridesDerivation: "44'/397'/0'/0'/'", mandatoryEmptyAccountSkip: 1, }, - vechain: { + vechain: { overridesDerivation: "44'/818'/0'/0/", }, }); diff --git a/libs/ledger-live-common/src/families/vechain/js-getTransactionStatus.ts b/libs/ledger-live-common/src/families/vechain/js-getTransactionStatus.ts index c6c2c41bd276..e7907f899c3c 100644 --- a/libs/ledger-live-common/src/families/vechain/js-getTransactionStatus.ts +++ b/libs/ledger-live-common/src/families/vechain/js-getTransactionStatus.ts @@ -1,5 +1,6 @@ import { AmountRequired, + createCustomErrorClass, FeeNotLoaded, InvalidAddress, InvalidAddressBecauseDestinationIsAlsoSource, @@ -13,6 +14,8 @@ import { calculateTransactionInfo } from "./utils/calculateTransactionInfo"; import { isValid } from "./utils/address-utils"; import BigNumber from "bignumber.js"; +const NotEnoughVTHO = createCustomErrorClass("NotEnoughVTHO"); + // NOTE: seems like the spendableBalance is not updated correctly: // use balance.minus(estimatedFees) instead const getTransactionStatus = async ( @@ -55,7 +58,7 @@ const getTransactionStatus = async ( // vet const vthoBalance = subAccounts?.[0].balance; if (estimatedFees.gt(vthoBalance || 0)) { - errors.amount = new NotEnoughBalance(); + errors.amount = new NotEnoughVTHO(); } } } From 1e350390100b0c9e59f62c413d466841366b6eab Mon Sep 17 00:00:00 2001 From: Davide Carpini Date: Fri, 3 Mar 2023 14:52:21 +0100 Subject: [PATCH 004/126] fix(mobile): hide vtho chart --- .../assets/images/graph/graphPlaceholder.png | Bin 0 -> 63627 bytes .../src/components/AccountGraphCard.tsx | 71 ++++++++++-------- .../src/components/AssetCentricGraphCard.tsx | 47 +++++++----- .../src/components/Graph/Placeholder.tsx | 36 +++++++++ .../Graph/tokensWithUnsupportedGraph.ts | 1 + .../src/locales/en/common.json | 3 +- 6 files changed, 107 insertions(+), 51 deletions(-) create mode 100644 apps/ledger-live-mobile/assets/images/graph/graphPlaceholder.png create mode 100644 apps/ledger-live-mobile/src/components/Graph/Placeholder.tsx create mode 100644 apps/ledger-live-mobile/src/components/Graph/tokensWithUnsupportedGraph.ts diff --git a/apps/ledger-live-mobile/assets/images/graph/graphPlaceholder.png b/apps/ledger-live-mobile/assets/images/graph/graphPlaceholder.png new file mode 100644 index 0000000000000000000000000000000000000000..e3b6b3f6c08d85a13f1feddf21dbe771b601d515 GIT binary patch literal 63627 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 literal 0 HcmV?d00001 diff --git a/apps/ledger-live-mobile/src/components/AccountGraphCard.tsx b/apps/ledger-live-mobile/src/components/AccountGraphCard.tsx index 7e5295bec7b4..fb31d62f6c83 100644 --- a/apps/ledger-live-mobile/src/components/AccountGraphCard.tsx +++ b/apps/ledger-live-mobile/src/components/AccountGraphCard.tsx @@ -47,6 +47,8 @@ import { NavigatorName, ScreenName } from "../const"; import { track } from "../analytics"; import { StackNavigatorNavigation } from "./RootNavigator/types/helpers"; import { BaseNavigatorStackParamList } from "./RootNavigator/types/BaseNavigator"; +import { GraphPlaceholder } from "./Graph/Placeholder"; +import { tokensWithUnsupportedGraph } from "./Graph/tokensWithUnsupportedGraph"; const { width } = getWindowDimensions(); @@ -173,38 +175,45 @@ function AccountGraphCard({ parentAccount={parentAccount} currency={currency} /> - - - {!loading ? ( - - + ) : ( + <> + + {!loading ? ( + + + + ) : ( + + )} + + + - - ) : ( - - )} - - - - + + + )} +