diff --git a/.github/workflows/bot-staging-explorer-btc.yml b/.github/workflows/bot-staging-explorer-btc.yml index a7148e1520..5914b174e9 100644 --- a/.github/workflows/bot-staging-explorer-btc.yml +++ b/.github/workflows/bot-staging-explorer-btc.yml @@ -8,7 +8,7 @@ on: ref: description: "Which ref (branch or tag) to pull from ?" required: false - default: master + default: develop jobs: start-runner: @@ -66,7 +66,7 @@ jobs: env: SHOW_LEGACY_NEW_ACCOUNT: "1" DEBUG_HTTP_RESPONSE: "1" - SEED: ${{ secrets.SEED4 }} + SEED: ${{ secrets.SEED2 }} BOT_REPORT_FOLDER: botreport VERBOSE_FILE: botreport/logs.txt GITHUB_SHA: ${GITHUB_SHA} @@ -79,9 +79,6 @@ jobs: EXPLORER: https://explorers.api-01.live.ledger-stg.com run: mkdir botreport; COINAPPS=$PWD/coin-apps yarn ci-test-bot timeout-minutes: 120 - - name: Run coverage - if: failure() || success() - run: CODECOV_TOKEN=${{ secrets.CODECOV_TOKEN }} npx codecov - name: upload logs if: failure() || success() uses: actions/upload-artifact@v1 diff --git a/.github/workflows/bot-staging-explorer-eth.yml b/.github/workflows/bot-staging-explorer-eth.yml index 2482d4fedc..253a6a5b34 100644 --- a/.github/workflows/bot-staging-explorer-eth.yml +++ b/.github/workflows/bot-staging-explorer-eth.yml @@ -8,7 +8,7 @@ on: ref: description: "Which ref (branch or tag) to pull from ?" required: false - default: master + default: develop jobs: start-runner: @@ -79,9 +79,6 @@ jobs: EXPLORER: https://explorers.api-01.live.ledger-stg.com run: mkdir botreport; COINAPPS=$PWD/coin-apps yarn ci-test-bot timeout-minutes: 120 - - name: Run coverage - if: failure() || success() - run: CODECOV_TOKEN=${{ secrets.CODECOV_TOKEN }} npx codecov - name: upload logs if: failure() || success() uses: actions/upload-artifact@v1 diff --git a/dependencies.md b/dependencies.md index 3f199ac19e..70854f815c 100644 --- a/dependencies.md +++ b/dependencies.md @@ -105,3 +105,4 @@ yarn upgrade-interactive -i --latest |winston | logs | monthly | |xstate | generic helper for React | **TBD why it's needed.** | |zcash-bitcore-lib | Bitcoin coin integration | monthly | +|varuint-bitcoin | Bitcoin coin integration | monthly | \ No newline at end of file diff --git a/package.json b/package.json index f31a3116e1..f0deab3523 100644 --- a/package.json +++ b/package.json @@ -134,6 +134,7 @@ "stellar-sdk": "^10.0.1", "superstruct": "^0.14.2", "triple-beam": "^1.3.0", + "varuint-bitcoin": "1.1.2", "winston": "^3.4.0", "xstate": "^4.28.1" }, diff --git a/src/__tests__/families/bitcoin/wallet-btc/xpub.txs.dogecoin.test.ts b/src/__tests__/families/bitcoin/wallet-btc/xpub.txs.dogecoin.test.ts new file mode 100644 index 0000000000..7b8a6b0374 --- /dev/null +++ b/src/__tests__/families/bitcoin/wallet-btc/xpub.txs.dogecoin.test.ts @@ -0,0 +1,96 @@ +import coininfo from "coininfo"; +import BigNumber from "bignumber.js"; +import { DerivationModes } from "../../../../families/bitcoin/wallet-btc/types"; +import Xpub from "../../../../families/bitcoin/wallet-btc/xpub"; +import Doge from "../../../../families/bitcoin/wallet-btc/crypto/doge"; +import BitcoinLikeExplorer from "../../../../families/bitcoin/wallet-btc/explorer"; +import BitcoinLikeStorage from "../../../../families/bitcoin/wallet-btc/storage"; +import { Merge } from "../../../../families/bitcoin/wallet-btc/pickingstrategies/Merge"; +import BitcoinLikeWallet from "../../../../families/bitcoin/wallet-btc/wallet"; +import MockBtc from "../../../../mock/Btc"; + +describe("testing dogecoin transactions", () => { + const wallet = new BitcoinLikeWallet(); + const explorer = new BitcoinLikeExplorer({ + explorerURI: "https://explorers.api.vault.ledger.com/blockchain/v3/doge", + explorerVersion: "v3", + disableBatchSize: true, + }); + + const network = coininfo.dogecoin.main.toBitcoinJS(); + const crypto = new Doge({ network }); + + const storage = new BitcoinLikeStorage(); + const xpub = new Xpub({ + storage, + explorer, + crypto, + xpub: "dgub8rBf7BYsf5YoMezYuPaEhc2tsr7sQA2v2xNCj4mt1czF1m4hRiBdjYeAq5xDVQhN5HqYQnxv2DwyfmDvp1QEfmi44b8uynPL45KXQJrsoi8", + derivationMode: DerivationModes.LEGACY, + }); + it("testing dogecoin transactions with huge amount", async () => { + const utxoPickingStrategy = new Merge(xpub.crypto, xpub.derivationMode, []); + const changeAddress = await xpub.getNewAddress(1, 0); + xpub.storage.appendTxs([ + { + id: "c4ee70c30b9c5c5fed60c37ce86046156af3623f32aa5be94556b35dcf0af147", + inputs: [], + outputs: [ + { + output_index: 0, + value: "500000000000000000", // huge utxo + address: "mwXTtHo8Yy3aNKUUZLkBDrTcKT9qG9TqLb", + output_hash: + "c4ee70c30b9c5c5fed60c37ce86046156af3623f32aa5be94556b35dcf0af147", + block_height: 1, + rbf: false, + }, + { + output_index: 1, + value: "0", + address: "", + output_hash: + "c4ee70c30b9c5c5fed60c37ce86046156af3623f32aa5be94556b35dcf0af147", + block_height: 1, + rbf: false, + }, + ], + block: { + hash: "73c565a6f226978df23480e440b27eb02f307855f50aa3bc72ebb586938f23e0", + height: 1, + time: "2021-07-28T13:34:17Z", + }, + account: 0, + index: 0, + address: "mwXTtHo8Yy3aNKUUZLkBDrTcKT9qG9TqLb", + received_at: "2021-07-28T13:34:17Z", + }, + ]); + + const txInfo = await xpub.buildTx({ + destAddress: "D9fSjc6zAyjdRgSfbfMLv5z5FpuacvguUi", + amount: new BigNumber(200000000000000000), + feePerByte: 100, + changeAddress, + utxoPickingStrategy, + sequence: 0, + }); + const account = await wallet.generateAccount({ + xpub: xpub.xpub, + path: "44'/0'", + index: 0, + currency: "dogecoin", + network: "mainnet", + derivationMode: DerivationModes.LEGACY, + explorer: "ledgerv3", + explorerURI: "https://explorers.api.vault.ledger.com/blockchain/v3/doge", + storage: "mock", + storageParams: [], + }); + await wallet.signAccountTx({ + btc: new MockBtc(), + fromAccount: account, + txInfo, + }); + }, 100000); +}); diff --git a/src/__tests__/test-helpers/setup.ts b/src/__tests__/test-helpers/setup.ts index 494cbc8625..068ce67b1d 100644 --- a/src/__tests__/test-helpers/setup.ts +++ b/src/__tests__/test-helpers/setup.ts @@ -2,7 +2,7 @@ import BigNumber from "bignumber.js"; import { setSupportedCurrencies } from "../../currencies"; import { setPlatformVersion } from "../../platform/version"; -jest.setTimeout(240000); +jest.setTimeout(360000); expect.extend({ toBeBigNumber(value) { diff --git a/src/exchange/swap/getProviders.ts b/src/exchange/swap/getProviders.ts index 7f27d1b539..7582b2619f 100644 --- a/src/exchange/swap/getProviders.ts +++ b/src/exchange/swap/getProviders.ts @@ -13,7 +13,7 @@ const getProviders: GetProviders = async () => { }); if (!res.data.length) { - return new SwapNoAvailableProviders(); + throw new SwapNoAvailableProviders(); } return res.data; diff --git a/src/families/bitcoin/getAccountNetworkInfo.ts b/src/families/bitcoin/getAccountNetworkInfo.ts index d13cb34c91..ac8b75490e 100644 --- a/src/families/bitcoin/getAccountNetworkInfo.ts +++ b/src/families/bitcoin/getAccountNetworkInfo.ts @@ -47,10 +47,12 @@ export async function getAccountNetworkInfo( } // Fix fees if suggested fee is too low, this is only for viacoin/decred because the fees backend endpoint is broken if ( - (account.currency.id === "viacoin" || account.currency.id === "decred") && + (account.currency.id === "viacoin" || + account.currency.id === "decred" || + account.currency.id === "qtum") && feesPerByte[2].toNumber() < Math.ceil(relayFee * 100000) ) { - feesPerByte[2] = new BigNumber(Math.ceil(relayFee * 100000)).plus(1); + feesPerByte[2] = new BigNumber(Math.ceil(relayFee * 100000)).plus(2); feesPerByte[1] = feesPerByte[2].plus(1); if (feesPerByte[1].plus(1).gt(feesPerByte[0])) { feesPerByte[0] = feesPerByte[1].plus(1); diff --git a/src/families/bitcoin/specs.ts b/src/families/bitcoin/specs.ts index d4600d42b4..e2098bc913 100644 --- a/src/families/bitcoin/specs.ts +++ b/src/families/bitcoin/specs.ts @@ -10,11 +10,7 @@ import { pickSiblings } from "../../bot/specs"; import { bitcoinPickingStrategy } from "./types"; import type { MutationSpec, AppSpec } from "../../bot/types"; import { LowerThanMinimumRelayFee } from "../../errors"; -import { - getMinRelayFee, - getUTXOStatus, - bchToCashaddrAddressWithoutPrefix, -} from "./logic"; +import { getMinRelayFee, getUTXOStatus } from "./logic"; import { DeviceModelId } from "@ledgerhq/devices"; type Arg = Partial<{ minimalAmount: BigNumber; @@ -316,6 +312,9 @@ const bitcoinGold: AppSpec = { mutations: bitcoinLikeMutations(), }; +const bchToCashaddrAddressWithoutPrefix = (recipient) => + bchaddrjs.toCashAddress(recipient).split(":")[1]; + const bitcoinCash: AppSpec = { name: "Bitcoin Cash", currency: getCryptoCurrencyById("bitcoin_cash"), diff --git a/src/families/bitcoin/wallet-btc/utils.ts b/src/families/bitcoin/wallet-btc/utils.ts index 972ca92e95..0dcbc48455 100644 --- a/src/families/bitcoin/wallet-btc/utils.ts +++ b/src/families/bitcoin/wallet-btc/utils.ts @@ -7,6 +7,7 @@ import { Currency, ICrypto } from "./crypto/types"; import cryptoFactory from "./crypto/factory"; import { fallbackValidateAddress } from "./crypto/base"; import { UnsupportedDerivation } from "../../../errors"; +import varuint from "varuint-bitcoin"; export function parseHexString(str: any) { const result: Array = []; @@ -261,3 +262,10 @@ export function isTaprootAddress(address: string, currency?: Currency) { return false; } } + +export function writeVarInt(buffer: Buffer, i: number, offset: number) { + // refer to https://github.com/bitcoinjs/bitcoinjs-lib/blob/1f44f722d30cd14a1861c8546e6b455f73862c1e/src/bufferutils.js#L78 + varuint.encode(i, buffer, offset); + offset += varuint.encode.bytes; + return offset; +} diff --git a/src/families/bitcoin/wallet-btc/wallet.ts b/src/families/bitcoin/wallet-btc/wallet.ts index bbd3cbca27..4379938eb8 100644 --- a/src/families/bitcoin/wallet-btc/wallet.ts +++ b/src/families/bitcoin/wallet-btc/wallet.ts @@ -2,15 +2,10 @@ // @ts-ignore import { flatten } from "lodash"; import BigNumber from "bignumber.js"; -// eslint-disable-next-line @typescript-eslint/ban-ts-comment -// @ts-ignore -import { BufferWriter } from "bitcoinjs-lib/src/bufferutils"; - import Btc from "@ledgerhq/hw-app-btc"; import { log } from "@ledgerhq/logs"; import { Transaction } from "@ledgerhq/hw-app-btc/lib/types"; import { Currency } from "./crypto/types"; - import { TransactionInfo, DerivationModes } from "./types"; import { Account, SerializedAccount } from "./account"; import Xpub from "./xpub"; @@ -246,16 +241,33 @@ class BitcoinLikeWallet { length += 2 * txInfo.outputs.length; } const buffer = Buffer.allocUnsafe(length); - const bufferWriter = new BufferWriter(buffer, 0); - bufferWriter.writeVarInt(txInfo.outputs.length); + let bufferOffset = 0; + bufferOffset = utils.writeVarInt( + buffer, + txInfo.outputs.length, + bufferOffset + ); txInfo.outputs.forEach((txOut) => { - // xpub splits output into smaller outputs than SAFE_MAX_INT anyway - bufferWriter.writeUInt64(txOut.value.toNumber()); + // refer to https://github.com/bitcoinjs/bitcoinjs-lib/blob/59b21162a2c4645c64271ca004c7a3755a3d72fb/ts_src/bufferutils.ts#L26 + buffer.writeUInt32LE( + txOut.value.modulo(new BigNumber(0x100000000)).toNumber(), + bufferOffset + ); + buffer.writeUInt32LE( + txOut.value.dividedToIntegerBy(new BigNumber(0x100000000)).toNumber(), + bufferOffset + 4 + ); + bufferOffset += 8; if (additionals && additionals.includes("decred")) { - bufferWriter.writeVarInt(0); - bufferWriter.writeVarInt(0); + bufferOffset = utils.writeVarInt(buffer, 0, bufferOffset); + bufferOffset = utils.writeVarInt(buffer, 0, bufferOffset); } - bufferWriter.writeVarSlice(txOut.script); + bufferOffset = utils.writeVarInt( + buffer, + txOut.script.length, + bufferOffset + ); + bufferOffset += txOut.script.copy(buffer, bufferOffset); }); const outputScriptHex = buffer.toString("hex"); const associatedKeysets = txInfo.associatedDerivations.map( diff --git a/src/families/cosmos/__snapshots__/bridge.test.ts.snap b/src/families/cosmos/__snapshots__/bridge.test.ts.snap index 1ec247e79a..0d892e7221 100644 --- a/src/families/cosmos/__snapshots__/bridge.test.ts.snap +++ b/src/families/cosmos/__snapshots__/bridge.test.ts.snap @@ -3,7 +3,7 @@ exports[`cosmos currency bridge scanAccounts cosmos seed 1 1`] = ` Array [ Object { - "balance": "2467871", + "balance": "2264526", "cosmosResources": Object { "delegatedBalance": "2139044", "withdrawAddress": "cosmos1g84934jpu3v5de5yqukkkhxmcvsw3u2ajxvpdl", @@ -22,7 +22,7 @@ Array [ "index": 0, "name": "Cosmos 1", "nfts": undefined, - "operationsCount": 22, + "operationsCount": 23, "pendingOperations": Array [], "seedIdentifier": "0388459b2653519948b12492f1a0b464720110c147a8155d23d423a5cc3c21d89a", "starred": false, @@ -33,7 +33,7 @@ Array [ "xpub": "cosmos1g84934jpu3v5de5yqukkkhxmcvsw3u2ajxvpdl", }, Object { - "balance": "0", + "balance": "300000", "cosmosResources": Object { "delegatedBalance": "0", "withdrawAddress": "cosmos108uy5q9jt59gwugq5yrdhkzcd9jryslmpcstk5", @@ -52,7 +52,7 @@ Array [ "index": 1, "name": "Cosmos 2", "nfts": undefined, - "operationsCount": 12, + "operationsCount": 14, "pendingOperations": Array [], "seedIdentifier": "0388459b2653519948b12492f1a0b464720110c147a8155d23d423a5cc3c21d89a", "starred": false, @@ -63,9 +63,9 @@ Array [ "xpub": "cosmos108uy5q9jt59gwugq5yrdhkzcd9jryslmpcstk5", }, Object { - "balance": "478443", + "balance": "367382", "cosmosResources": Object { - "delegatedBalance": "1000", + "delegatedBalance": "0", "withdrawAddress": "cosmos1cgc696ay2pg6d4gcejek2y8la66j7e5y3c7kyw", }, "currencyId": "cosmos", @@ -82,7 +82,7 @@ Array [ "index": 2, "name": "Cosmos 3", "nfts": undefined, - "operationsCount": 8, + "operationsCount": 10, "pendingOperations": Array [], "seedIdentifier": "0388459b2653519948b12492f1a0b464720110c147a8155d23d423a5cc3c21d89a", "starred": false, @@ -224,6 +224,30 @@ Array [ "type": "IN", "value": "1000", }, + Object { + "accountId": "js:2:cosmos:cosmos1g84934jpu3v5de5yqukkkhxmcvsw3u2ajxvpdl:", + "blockHash": null, + "blockHeight": "10348199", + "contract": undefined, + "extra": Object { + "validators": Array [], + }, + "fee": "3345", + "hash": "381E494804FFC8EBDBA17B457D7D666DCE377D6D757F6B298FD820250450D409", + "id": "js:2:cosmos:cosmos1g84934jpu3v5de5yqukkkhxmcvsw3u2ajxvpdl:-381E494804FFC8EBDBA17B457D7D666DCE377D6D757F6B298FD820250450D409-OUT", + "operator": undefined, + "recipients": Array [ + "cosmos108uy5q9jt59gwugq5yrdhkzcd9jryslmpcstk5", + ], + "senders": Array [ + "cosmos1g84934jpu3v5de5yqukkkhxmcvsw3u2ajxvpdl", + ], + "standard": undefined, + "tokenId": undefined, + "transactionSequenceNumber": 79, + "type": "OUT", + "value": "203345", + }, Object { "accountId": "js:2:cosmos:cosmos1g84934jpu3v5de5yqukkkhxmcvsw3u2ajxvpdl:", "blockHash": null, @@ -710,6 +734,30 @@ Array [ "type": "IN", "value": "1000", }, + Object { + "accountId": "js:2:cosmos:cosmos108uy5q9jt59gwugq5yrdhkzcd9jryslmpcstk5:", + "blockHash": null, + "blockHeight": "10348199", + "contract": undefined, + "extra": Object { + "validators": Array [], + }, + "fee": "3345", + "hash": "381E494804FFC8EBDBA17B457D7D666DCE377D6D757F6B298FD820250450D409", + "id": "js:2:cosmos:cosmos108uy5q9jt59gwugq5yrdhkzcd9jryslmpcstk5:-381E494804FFC8EBDBA17B457D7D666DCE377D6D757F6B298FD820250450D409-IN", + "operator": undefined, + "recipients": Array [ + "cosmos108uy5q9jt59gwugq5yrdhkzcd9jryslmpcstk5", + ], + "senders": Array [ + "cosmos1g84934jpu3v5de5yqukkkhxmcvsw3u2ajxvpdl", + ], + "standard": undefined, + "tokenId": undefined, + "transactionSequenceNumber": 79, + "type": "IN", + "value": "200000", + }, Object { "accountId": "js:2:cosmos:cosmos108uy5q9jt59gwugq5yrdhkzcd9jryslmpcstk5:", "blockHash": null, @@ -950,6 +998,30 @@ Array [ "type": "OUT", "value": "15050", }, + Object { + "accountId": "js:2:cosmos:cosmos108uy5q9jt59gwugq5yrdhkzcd9jryslmpcstk5:", + "blockHash": null, + "blockHeight": "10348072", + "contract": undefined, + "extra": Object { + "validators": Array [], + }, + "fee": "3342", + "hash": "E7F1D275353E097E0A19217A1D0BFE2DC8947100213E912EFCC896329D950981", + "id": "js:2:cosmos:cosmos108uy5q9jt59gwugq5yrdhkzcd9jryslmpcstk5:-E7F1D275353E097E0A19217A1D0BFE2DC8947100213E912EFCC896329D950981-IN", + "operator": undefined, + "recipients": Array [ + "cosmos108uy5q9jt59gwugq5yrdhkzcd9jryslmpcstk5", + ], + "senders": Array [ + "cosmos1cgc696ay2pg6d4gcejek2y8la66j7e5y3c7kyw", + ], + "standard": undefined, + "tokenId": undefined, + "transactionSequenceNumber": 28, + "type": "IN", + "value": "100000", + }, ], Array [ Object { @@ -1121,6 +1193,31 @@ Array [ "type": "OUT", "value": "6565", }, + Object { + "accountId": "js:2:cosmos:cosmos1cgc696ay2pg6d4gcejek2y8la66j7e5y3c7kyw:", + "blockHash": null, + "blockHeight": "10337853", + "contract": undefined, + "extra": Object { + "validators": Array [ + Object { + "address": "cosmosvaloper17zcpywlhgcpk7ff505vr8mnc4wwpv5fcta6enz", + "amount": "1000", + }, + ], + }, + "fee": "7719", + "hash": "DD5085AF922A2BAD032131A2F9168EB1498E615063ECBC6C86CE31D5B9981E2A", + "id": "js:2:cosmos:cosmos1cgc696ay2pg6d4gcejek2y8la66j7e5y3c7kyw:-DD5085AF922A2BAD032131A2F9168EB1498E615063ECBC6C86CE31D5B9981E2A-UNDELEGATE", + "operator": undefined, + "recipients": Array [], + "senders": Array [], + "standard": undefined, + "tokenId": undefined, + "transactionSequenceNumber": 27, + "type": "UNDELEGATE", + "value": "7719", + }, Object { "accountId": "js:2:cosmos:cosmos1cgc696ay2pg6d4gcejek2y8la66j7e5y3c7kyw:", "blockHash": null, @@ -1150,6 +1247,30 @@ Array [ "type": "DELEGATE", "value": "8399", }, + Object { + "accountId": "js:2:cosmos:cosmos1cgc696ay2pg6d4gcejek2y8la66j7e5y3c7kyw:", + "blockHash": null, + "blockHeight": "10348072", + "contract": undefined, + "extra": Object { + "validators": Array [], + }, + "fee": "3342", + "hash": "E7F1D275353E097E0A19217A1D0BFE2DC8947100213E912EFCC896329D950981", + "id": "js:2:cosmos:cosmos1cgc696ay2pg6d4gcejek2y8la66j7e5y3c7kyw:-E7F1D275353E097E0A19217A1D0BFE2DC8947100213E912EFCC896329D950981-OUT", + "operator": undefined, + "recipients": Array [ + "cosmos108uy5q9jt59gwugq5yrdhkzcd9jryslmpcstk5", + ], + "senders": Array [ + "cosmos1cgc696ay2pg6d4gcejek2y8la66j7e5y3c7kyw", + ], + "standard": undefined, + "tokenId": undefined, + "transactionSequenceNumber": 28, + "type": "OUT", + "value": "103342", + }, ], Array [], ] diff --git a/src/families/cosmos/api/Cosmos.ts b/src/families/cosmos/api/Cosmos.ts index ad1659388e..8eb3c987ca 100644 --- a/src/families/cosmos/api/Cosmos.ts +++ b/src/families/cosmos/api/Cosmos.ts @@ -1,14 +1,17 @@ import { getEnv } from "../../../env"; import BigNumber from "bignumber.js"; import network from "../../../network"; -import { Operation } from "../../../types"; +import { CryptoCurrency, Operation } from "../../../types"; import { patchOperationWithHash } from "../../../operation"; const defaultEndpoint = getEnv( "API_COSMOS_BLOCKCHAIN_EXPLORER_API_ENDPOINT" ).replace(/\/$/, ""); -export const getAccountInfo = async (address: string): Promise => { +export const getAccountInfo = async ( + address: string, + currency: CryptoCurrency +): Promise => { try { const [ { accountNumber, sequence }, @@ -21,10 +24,10 @@ export const getAccountInfo = async (address: string): Promise => { withdrawAddress, ] = await Promise.all([ getAccount(address), - getAllBalances(address), + getAllBalances(address, currency), getHeight(), getTransactions(address), - getDelegations(address), + getDelegations(address, currency), getRedelegations(address), getUnbondings(address), getWithdrawAddress(address), @@ -95,7 +98,10 @@ const getHeight = async (): Promise => { return data.block.header.height; }; -const getAllBalances = async (address: string): Promise => { +const getAllBalances = async ( + address: string, + currency: CryptoCurrency +): Promise => { const { data } = await network({ method: "GET", url: `${defaultEndpoint}/cosmos/bank/v1beta1/balances/${address}`, @@ -104,13 +110,17 @@ const getAllBalances = async (address: string): Promise => { let amount = new BigNumber(0); for (const elem of data.balances) { - amount = amount.plus(elem.amount); + if (elem.denom === currency.units[1].code) + amount = amount.plus(elem.amount); } return amount; }; -const getDelegations = async (address: string): Promise => { +const getDelegations = async ( + address: string, + currency: CryptoCurrency +): Promise => { const delegations: Array = []; const { data: data1 } = await network({ @@ -135,7 +145,10 @@ const getDelegations = async (address: string): Promise => { delegations.push({ validatorAddress: d.delegation.validator_address, - amount: new BigNumber(d.balance.amount), + amount: + d.balance.denom === currency.units[1].code + ? new BigNumber(d.balance.amount) + : new BigNumber(0), pendingRewards: new BigNumber(0), status, }); diff --git a/src/families/cosmos/js-synchronisation.ts b/src/families/cosmos/js-synchronisation.ts index 05b033a4ba..b5bfd101dc 100644 --- a/src/families/cosmos/js-synchronisation.ts +++ b/src/families/cosmos/js-synchronisation.ts @@ -20,7 +20,7 @@ const txToOps = (info: any, id: string, txs: any): Operation[] => { let fees = new BigNumber(0); tx.tx.auth_info.fee.amount.forEach((elem) => { - fees = fees.plus(elem.amount); + if (elem.denom === currency.units[1].code) fees = fees.plus(elem.amount); }); const op: Operation = { @@ -184,7 +184,7 @@ export const getAccountShape: GetAccountShape = async (info) => { redelegations, unbondings, withdrawAddress, - } = await getAccountInfo(xpubOrAddress); + } = await getAccountInfo(xpubOrAddress, currency); const oldOperations = initialAccount?.operations || []; const newOperations = txToOps(info, accountId, txs); diff --git a/src/families/cosmos/validators.ts b/src/families/cosmos/validators.ts index 11946e1891..038fe79fbb 100644 --- a/src/families/cosmos/validators.ts +++ b/src/families/cosmos/validators.ts @@ -31,7 +31,7 @@ const cacheValidators = makeLRUCache( if (isStargate(currency)) { const url = `${getBaseApiUrl( currency - )}/${namespace}/staking/${version}/validators?status=BOND_STATUS_BONDED&pagination.limit=130`; + )}/${namespace}/staking/${version}/validators?status=BOND_STATUS_BONDED&pagination.limit=175`; const { data } = await network({ url, method: "GET", diff --git a/src/featureFlags/defaultFeatures.ts b/src/featureFlags/defaultFeatures.ts index 89241ffe63..f7c4a9640d 100644 --- a/src/featureFlags/defaultFeatures.ts +++ b/src/featureFlags/defaultFeatures.ts @@ -49,6 +49,8 @@ export const defaultFeatures: DefaultFeatures = { }, minimum_number_of_app_starts_since_last_crash: 2, }, + typeform_url: + "https://form.typeform.com/to/Jo7gqcB4?typeform-medium=embed-sdk&typeform-medium-version=next&typeform-embed=popup-blank", support_email: "support@ledger.com", }, }, diff --git a/tools/package.json b/tools/package.json index 233fc38337..13a0f21fde 100644 --- a/tools/package.json +++ b/tools/package.json @@ -3,36 +3,18 @@ "version": "0.1.0", "private": true, "dependencies": { - "@ledgerhq/hw-transport": "^6.7.0", - "@ledgerhq/hw-transport-http": "^6.7.0", - "@ledgerhq/hw-transport-u2f": "^5.36.0-deprecated", - "@ledgerhq/hw-transport-web-ble": "^6.7.0", - "@ledgerhq/hw-transport-webhid": "^6.7.0", - "@ledgerhq/hw-transport-webusb": "^6.7.0", - "@ledgerhq/live-common": "^22.0.0", - "@material-ui/core": "^4.11.3", - "babel-polyfill": "^6.26.0", "bignumber.js": "^9.0.1", "flow-bin": "^0.145.0", - "qrcode": "^1.4.2", + "invariant": "^2.2.4", "react": "^17.0.2", "react-dom": "^17.0.2", "react-inspector": "^4.0.1", - "react-redux": "^7.2.4", "react-router": "^5.2.0", "react-router-dom": "^5.2.0", "react-scripts": "3.4.3", - "react-select": "^3.2.0", + "react-select": "^5.3.2", "react-table": "^6.11.4", - "react-tooltip": "^3.11.5", - "redux": "^4.1.0", - "redux-devtools-extension": "^2.13.9", - "redux-thunk": "^2.2.0", - "reselect": "^4.0.0", - "rxjs": "^6.6.7", "styled-components": "^4.4.0", - "timeago.js": "^4.0.2", - "victory": "^34.3.12", "webpack": "4.42" }, "scripts": { diff --git a/tools/src/demos/Currencies/index.js b/tools/src/demos/Currencies/index.js deleted file mode 100644 index 9f16a7a196..0000000000 --- a/tools/src/demos/Currencies/index.js +++ /dev/null @@ -1,190 +0,0 @@ -// @flow -import React, { Component } from "react"; -import styled from "styled-components"; -import { listCryptoCurrencies } from "@ledgerhq/live-common/lib/currencies"; -import { getCryptoCurrencyIcon } from "@ledgerhq/live-common/lib/react"; -import { - blockchainBaseURL, - hasCurrencyExplorer, -} from "@ledgerhq/live-common/lib/api/Ledger"; -import api from "@ledgerhq/live-common/lib/countervalues/api"; - -const Section = styled.div` - padding: 20px 40px; -`; - -const Intro = styled.div` - margin-top: 20px; - padding: 0 40px; - font-size: 16px; -`; - -const SectionHeader = styled.h1``; - -const CryptoList = styled.div` - display: flex; - flex-direction: column; -`; - -const CryptoCell = styled.div` - color: ${(p) => p.color}; - display: flex; - flex-direction: row; - align-items: center; - margin: 10px; -`; - -const Problem = styled.div` - width: 50px; - color: red; - text-decoration: underline; -`; -const NoProblem = styled.div` - width: 50px; - color: green; -`; - -const CryptoName = styled.div` - padding: 6px; - font-size: 14px; -`; - -const CryptoInfo = styled.div` - padding: 6px; - font-size: 14px; - color: #999; - flex: 1; -`; - -const AltIcon = styled.div` - font-size: 24px; -`; - -const IconWrapper = styled.div` - color: ${(p) => p.color}; - background-color: ${(p) => p.bg}; - border-radius: 8px; - display: flex; - overflow: hidden; - flex-direction: column; - justify-content: center; - align-items: center; - width: ${(p) => p.size}px; - height: ${(p) => p.size}px; - margin-right: 10px; -`; - -class Crypto extends Component<*> { - render() { - const { crypto } = this.props; - const Icon = getCryptoCurrencyIcon(crypto); - const validationErrors = [ - Icon ? null : "icon is missing", - crypto.family === "bitcoin" && !crypto.bitcoinLikeInfo - ? "bitcoin family coins must provide bitcoinLikeInfo" - : null, - crypto.family === "ethereum" && !crypto.ethereumLikeInfo - ? "ethereum family coins must provide ethereumLikeInfo" - : null, - crypto.units.length === 0 - ? "at least one unit must be provided in units" - : null, - ].filter((o) => o); - return ( - - {validationErrors.length ? ( - KO - ) : ( - OK - )} - - {Icon ? : {crypto.ticker}} - - - {Icon ? : {crypto.ticker}} - - - {crypto.name} ({crypto.ticker}) - - - {[ - "#" + crypto.id + "(" + crypto.coinType + ")", - "belongs to " + crypto.family + " family", - crypto.isTestnetFor && - "is testnet of '" + crypto.isTestnetFor + "'", - crypto.supportsSegwit && "supports segwit", - crypto.forkedFrom && "forked " + crypto.forkedFrom, - crypto.managerAppName && - "on Manager '" + crypto.managerAppName + "'", - crypto.ledgerExplorerId && - "ledger explorer '" + crypto.ledgerExplorerId + "'", - crypto.blockAvgTime && "blockAvgTime=" + crypto.blockAvgTime, - crypto.scheme && "scheme=" + crypto.scheme, - crypto.bitcoinLikeInfo && - "bitcoinLikeInfo=" + JSON.stringify(crypto.bitcoinLikeInfo), - crypto.ethereumLikeInfo && - "ethereumLikeInfo=" + JSON.stringify(crypto.ethereumLikeInfo), - "units are " + - crypto.units - .map((u) => u.code + "(^" + u.magnitude + ")") - .join(" "), - hasCurrencyExplorer(crypto) - ? "ledger explorer is " + blockchainBaseURL(crypto) - : "doesn't have ledger explorer", - ] - .filter((o) => o) - .join(", ")} - - - ); - } -} - -class Currencies extends Component<*, *> { - static demo = { - title: "Currencies", - url: "/currencies", - }; - - state = { - tickers: [], - }; - - async componentDidMount() { - const tickers = await api.fetchMarketcapTickers(); - this.setState({ tickers }); - } - - render() { - const { tickers } = this.state; - const all = listCryptoCurrencies(true); - const available = tickers - .map((ticker) => all.find((a) => a.ticker === ticker)) - .filter(Boolean); - const unavailable = all.filter((a) => !available.includes(a)); - return ( -
- - This shows @ledgerhq/live-common database (Disclaimer: - regardless if a given crypto-asset is supported) - -
- Crypto assets - - {available.map((a) => ( - - ))} - - no countervalues yet - - {unavailable.map((a) => ( - - ))} - -
-
- ); - } -} - -export default Currencies; diff --git a/tools/src/demos/apps/index.js b/tools/src/demos/apps/index.js deleted file mode 100644 index 029c22c783..0000000000 --- a/tools/src/demos/apps/index.js +++ /dev/null @@ -1,261 +0,0 @@ -// @flow -import React, { useState, useEffect } from "react"; -import styled from "styled-components"; -import Select from "react-select"; -import type { CryptoCurrency } from "@ledgerhq/live-common/lib/types"; -import { listCryptoCurrencies } from "@ledgerhq/live-common/lib/currencies"; -import { setEnv } from "@ledgerhq/live-common/lib/env"; -import { getCryptoCurrencyIcon } from "@ledgerhq/live-common/lib/react"; -import manager from "@ledgerhq/live-common/lib/manager"; - -const coins = listCryptoCurrencies(); - -const Container = styled.div` - width: 600px; - margin: 20px auto; -`; - -const Section = styled.div` - padding: 20px 0; -`; - -const SectionHead = styled.div` - font-size: 18px; - padding: 20px 0; - color: ${(p) => (p.error ? "#ea2e49" : "#6490f1")}; -`; - -const SectionBody = styled.div` - display: flex; - flex-direction: row; - flex-wrap: wrap; -`; - -const CoinContainer = styled.div` - width: 80px; - padding: 4px; -`; - -const IconWrapper = styled.div` - color: ${(p) => p.color}; - background-color: ${(p) => p.bg}; - border-radius: 8px; - display: flex; - overflow: hidden; - flex-direction: column; - justify-content: center; - align-items: center; - width: ${(p) => p.size}px; - height: ${(p) => p.size}px; - margin-right: 10px; -`; - -const CryptoName = styled.div` - padding: 6px; - font-size: 12px; - font-weight: bold; -`; - -const AltIcon = styled.div` - font-size: 24px; -`; - -const CoinPreview = ({ coin }: { coin: CryptoCurrency }) => { - const text = coin.managerAppName; - const Icon = getCryptoCurrencyIcon(coin); - return ( - - - {Icon ? : {coin.ticker}} - - {text} - - ); -}; - -const AppPreview = ({ app }: *) => { - return ( - - - {`${app.name} ${app.version}`} - - ); -}; - -const AppWithCoinPreview = ({ app }: *) => { - return ; -}; - -const choices = [ - { - label: "Nano S 1.6.1", - deviceInfo: { - version: "1.6.1", - mcuVersion: "1.12", - majMin: "1.6", - providerName: null, - targetId: 823132164, - isOSU: false, - isBootloader: false, - managerAllowed: false, - pinValidated: true, - }, - }, - { - label: "Nano S 1.5.5", - deviceInfo: { - version: "1.5.5", - mcuVersion: "1.7", - majMin: "1.5", - providerName: null, - targetId: 823132164, - isOSU: false, - isBootloader: false, - managerAllowed: false, - pinValidated: true, - }, - }, - { - label: "Nano X 1.2.4-5", - deviceInfo: { - version: "1.2.4-5", - mcuVersion: "2.10", - majMin: "1.2", - providerName: null, - targetId: 855638020, - isOSU: false, - isBootloader: false, - managerAllowed: false, - pinValidated: true, - }, - }, -]; - -const providers = [ - { label: "production (provider 1)", value: 1 }, - { label: "beta (provider 4)", value: 4 }, -]; - -const Apps = () => { - const [apps, setApps] = useState([]); - const [choice, setChoice] = useState(choices[0]); - const [provider, setProvider] = useState(providers[0]); - - useEffect(() => { - setApps([]); - setEnv("FORCE_PROVIDER", provider.value); - manager.getAppsList(choice.deviceInfo).then(setApps); - }, [choice, provider]); - - const unknownApps = []; - const knownAppsWithCoin = []; - const deprecatedApps = []; - apps.forEach((app) => { - const coin = coins.find((c) => app.name === c.managerAppName); - if (coin) { - knownAppsWithCoin.push({ - app, - coin, - }); - } else if (!manager.canHandleInstall(app)) { - deprecatedApps.push(app); - } else { - unknownApps.push(app); - } - }); - const notFoundCryptoCurrencies = - apps.length === 0 - ? [] - : coins.filter( - (c) => - c.managerAppName && - !apps.find((app) => app.name === c.managerAppName) - ); - - return ( - -

Manager apps for device

- - c.label} - getOptionValue={(c) => c.label} - /> - - {apps.length === 0 ? ( -
- Loading... -
- ) : null} - - {notFoundCryptoCurrencies.length > 0 ? ( -
- - {"can't find a Manager app for these crypto-currencies"} - - - {notFoundCryptoCurrencies.map((coin) => ( - - ))} - -
- ) : null} - {unknownApps.length > 0 ? ( -
- - {"can't find crypto-currencies corresponding to these Manager apps"} - - - {unknownApps.map((app) => ( - - ))} - -
- ) : null} - {knownAppsWithCoin.length > 0 ? ( -
- Apps correctly recognized by Live - - {knownAppsWithCoin.map((all) => ( - - ))} - -
- ) : null} - {deprecatedApps.length > 0 ? ( -
- - {"Apps recognized as "} - deprecated - {" by Live (no install available)"} - - - {deprecatedApps.map((app) => ( - - ))} - -
- ) : null} -
- ); -}; - -Apps.demo = { - title: "Apps", - url: "/apps", - hidden: true, -}; - -export default Apps; diff --git a/tools/src/demos/assets/index.js b/tools/src/demos/assets/index.js deleted file mode 100644 index b51162f6f7..0000000000 --- a/tools/src/demos/assets/index.js +++ /dev/null @@ -1,255 +0,0 @@ -// @flow -import React, { Component, useCallback, useState, useMemo } from "react"; -import ReactTable from "react-table"; -import { BigNumber } from "bignumber.js"; -import { - listTokens, - listCryptoCurrencies, - isCurrencySupported, - getCryptoCurrencyById, - getFiatCurrencyByTicker, - useCurrenciesByMarketcap, - useMarketcapTickers, - formatCurrencyUnit, -} from "@ledgerhq/live-common/lib/currencies"; - -const usdFiat = getFiatCurrencyByTicker("USD"); -const bitcoin = getCryptoCurrencyById("bitcoin"); -const ethereum = getCryptoCurrencyById("ethereum"); - -const DownloadData = ({ data }) => { - const onClick = useCallback(() => { - const csv = [ - ["id", "name", "ticker", "type", "live", "url", "USD", "magnitude"], - ] - .concat( - data.map((d) => [ - d.id, - d.name, - d.ticker, - d.type, - d.livesupport, - d.type === "TokenCurrency" - ? `https://etherscan.io/address/${d.contractAddress}` - : "", - d.usdValue, - d.units[0].magnitude, - ]) - ) - .map((row) => - row.map((cell) => String(cell).replace(/[,\n\r]/g, "")).join(",") - ) - .join("\n"); - const dataUrl = `data:text/csv,` + encodeURIComponent(csv); - window.open(dataUrl); - }, [data]); - - return ; -}; - -const columns = [ - { - Header: "Live?", - width: 80, - accessor: "livesupport", - }, - { - Header: "Delisted?", - width: 80, - accessor: "delisted", - }, - { - Header: "type", - width: 120, - accessor: "typeText", - }, - { - Header: "id", - width: 120, - accessor: "id", - }, - { - Header: "Name", - accessor: "name", - }, - { - Header: "Ticker", - accessor: "ticker", - width: 100, - }, - { - Header: "Magnitude", - id: "magnitude", - accessor: (o) => o.units[0].magnitude, - width: 100, - }, - { - Header: "extra", - id: "extra", - accessor: (token) => - token.type === "TokenCurrency" ? ( - - {token.contractAddress} - - ) : ( - "coinType=" + token.coinType - ), - }, - { - id: "countervalue", - Header: (p) => { - const data = p.data.map((d) => d._original); - const supported = data.filter((d) => d.countervalueStatus === "yes"); - const withExchange = data.filter((d) => d.exchange); - const percentageSupport = supported.length / data.length; - const realPercentageSupport = withExchange.length / data.length; - return ( -
- countervalue -
- {supported.length} have marketcap ( - {Math.floor(percentageSupport * 1000) / 10}%) -
-
- {withExchange.length} supported ( - {Math.floor(realPercentageSupport * 1000) / 10}%) -
- -
- ); - }, - accessor: "countervalueText", - }, - { - Header: "USD", - accessor: "usdValue", - width: 100, - }, -]; - -const counterpartFor = (c) => - c === bitcoin || c === ethereum - ? usdFiat - : c.type === "CryptoCurrency" - ? bitcoin - : ethereum; - -const Assets = () => { - const tokens = listTokens({ withDelisted: true }); - const currencies = listCryptoCurrencies(); - const all = useMemo(() => currencies.concat(tokens), [tokens, currencies]); - const tickers = useMarketcapTickers() || []; - const [rates] = useState({}); - const byMarketcap = useCurrenciesByMarketcap(all); - const data = byMarketcap.map((t) => { - let countervalueStatus = "no"; - let loading = false; - let exchange; - let formatted = ""; - let usdValue = ""; - if (t.disableCountervalue) { - countervalueStatus = "disabled"; - } else if (tickers.includes(t.ticker)) { - countervalueStatus = "yes"; - const counter = counterpartFor(t); - if (rates[counter.ticker]) { - const ratePerExchange = (rates[counter.ticker] || {})[t.ticker] || {}; - exchange = Object.keys(ratePerExchange)[0]; - if (exchange) { - const latest = ratePerExchange[exchange].latest || 0; - - if (counter !== usdFiat) { - if (rates[usdFiat.ticker]) { - const intermRatePerExchange = - (rates[usdFiat.ticker] || {})[counter.ticker] || {}; - const intermExchange = Object.keys(intermRatePerExchange)[0]; - if (intermExchange) { - const intermLatest = - intermRatePerExchange[intermExchange].latest || 0; - usdValue = formatCurrencyUnit( - usdFiat.units[0], - BigNumber(latest) - .times(intermLatest) - .times(10 ** t.units[0].magnitude) - ); - } - } - } else { - usdValue = formatCurrencyUnit( - counter.units[0], - BigNumber(latest).times(10 ** t.units[0].magnitude) - ); - } - - formatted = formatCurrencyUnit( - counter.units[0], - BigNumber(latest).times(10 ** t.units[0].magnitude), - { - showCode: true, - } - ); - } - } else { - loading = true; - } - } - const countervalueText = - countervalueStatus !== "yes" - ? countervalueStatus - : loading - ? "..." - : exchange - ? exchange + " @ " + formatted - : "no exchange found"; - const livesupport = - t.type === "TokenCurrency" || isCurrencySupported(t) ? "yes" : "no"; - return { - ...t, - typeText: - t.type === "TokenCurrency" - ? "token on " + t.parentCurrency.name - : "coin", - countervalueStatus, - countervalueText, - exchange, - loading, - livesupport, - delisted: t.delisted ? "yes" : "no", - usdValue, - }; - }); - - return ( -
- -
- ); -}; - -export default class Demo extends Component<{}> { - static demo = { - title: "Assets", - url: "/assets", - }; - render() { - return ; - } -} diff --git a/tools/src/demos/bridgestream/index.js b/tools/src/demos/bridgestream/index.js deleted file mode 100644 index fd489ac79b..0000000000 --- a/tools/src/demos/bridgestream/index.js +++ /dev/null @@ -1,157 +0,0 @@ -// @flow -import React, { PureComponent, Component } from "react"; -import qrcode from "qrcode"; - -type Props = { - data: string, - errorCorrectionLevel: string, - size: number, - style?: * -}; - -class QRCode extends PureComponent { - static defaultProps = { - size: 200, - errorCorrectionLevel: "Q" - }; - - componentDidMount() { - this.drawQRCode(); - } - - componentDidUpdate() { - this.drawQRCode(); - } - - _canvas = null; - - drawQRCode() { - const { data, size, errorCorrectionLevel } = this.props; - qrcode.toCanvas(this._canvas, data, { - width: size, - margin: 0, - errorCorrectionLevel, - color: { - light: "#ffffff" - } - }); - } - - render() { - return (this._canvas = n)} />; - } -} - -const checkArrayString = (m: mixed): string[] => { - if (!Array.isArray(m)) throw new Error("JSON is not an array"); - const res = []; - for (const item of m) { - if (typeof item === "string") { - res.push(item); - } - } - return res; -}; - -const parseData = (inputValue: string): string[] => - checkArrayString( - JSON.parse(atob(inputValue.trim().replace("BRIDGESTREAM_DATA=", ""))) - ); - -class BridgeStream extends Component<*, *> { - state = { - data: null, - error: null - }; - - input = React.createRef(); - - gifIt = () => { - const qrcodes = document.getElementById("qrcodes"); - if (!qrcodes) throw new Error("no qrcodes"); - - const gif = new window.GIF({ - workers: 2, - quality: 10 - }); - - const els = [...qrcodes.children]; - - els.forEach(canvas => { - gif.addFrame(canvas, { delay: 200 }); - }); - - gif.on("finished", blob => { - window.open(URL.createObjectURL(blob)); - }); - - gif.render(); - }; - - onSubmit = (evt: *) => { - evt.preventDefault(); - const input = this.input.current; - if (input) { - try { - const data = parseData(input.value); - this.setState({ data, error: null }); - setTimeout(this.gifIt, 1000); - } catch (error) { - this.setState({ data: null, error }); - } - } - }; - - render() { - const { error, data } = this.state; - if (data) { - return ( -
- {data.map((data, i) => ( - - ))} -
- ); - } - return ( -
-
- - -
-
{error ? String(error) : null}
-
- ); - } -} - -// $FlowFixMe -BridgeStream.demo = { - title: "BridgeStream", - url: "/bridgestream", - hidden: true -}; - -export default BridgeStream; diff --git a/tools/src/demos/bridgetest/index.js b/tools/src/demos/bridgetest/index.js deleted file mode 100644 index ba61d7a58a..0000000000 --- a/tools/src/demos/bridgetest/index.js +++ /dev/null @@ -1,116 +0,0 @@ -// @flow -import React, { useState, useEffect } from "react"; -import Eth from "@ledgerhq/hw-app-eth"; -import WebSocketTransport from "@ledgerhq/hw-transport-http/lib/WebSocketTransport"; - -const bridgeURL = "ws://localhost:8435"; - -function check() { - return WebSocketTransport.check(bridgeURL); -} - -function checkLoop(isCancelled) { - return check().catch(async () => { - await delay(500); - if (isCancelled()) return; - return checkLoop(isCancelled); - }); -} - -function delay(ms) { - return new Promise((success) => setTimeout(success, ms)); -} - -async function verifyAddress(onAddress) { - const transport = await WebSocketTransport.open(bridgeURL); - try { - const eth = new Eth(transport); - const { address } = await eth.getAddress("44'/60'/0'/0/0"); - onAddress(address); - // trigger verification - await eth.getAddress("44'/60'/0'/0/0", true); - } finally { - await transport.close(); - } -} - -const initialState = { - opened: false, - available: false, - address: "", -}; - -const BridgeTest = () => { - const [{ opened, address, available }, setState] = useState(initialState); - - useEffect(() => { - if (!opened) return; - let cancelled = false; - - checkLoop(() => cancelled) - .then(() => { - if (cancelled) return; - setState({ - ...initialState, - opened: true, - available: true, - }); - return verifyAddress((address) => - setState({ - opened: true, - available: true, - address, - }) - ).then(() => { - setState(initialState); - }); - }) - .catch((error) => { - alert("" + error); - console.error(error); - setState(initialState); - }); - - return () => { - cancelled = true; - }; - }, [opened]); - - return ( -
- {opened ? ( - !available ? ( -
- Connecting...{" "} - -
- ) : !address ? ( -
Loading...
- ) : ( -
{"Verify on your device the address: " + address}
- ) - ) : ( - - setState({ - ...initialState, - opened: true, - }) - } - href={`ledgerlive://bridge?appName=Ethereum&origin=${window.location.host}`} - > - Open with Ledger Live - - )} -
- ); -}; - -// $FlowFixMe -BridgeTest.demo = { - title: "Bridge test", - url: "/bridgetest", - hidden: true, -}; - -export default BridgeTest; diff --git a/tools/src/demos/countervalues/CurrentRate.js b/tools/src/demos/countervalues/CurrentRate.js deleted file mode 100644 index ab79fb5a25..0000000000 --- a/tools/src/demos/countervalues/CurrentRate.js +++ /dev/null @@ -1,30 +0,0 @@ -// @flow -import React from "react"; -import BigNumber from "bignumber.js"; -import type { Currency } from "@ledgerhq/live-common/lib/types"; -import { formatCurrencyUnit } from "@ledgerhq/live-common/lib/currencies"; -import { useCalculate } from "@ledgerhq/live-common/lib/countervalues/react"; - -const CurrentRate = ({ from, to }: { from: Currency, to: Currency }) => { - const value = 10 ** from.units[0].magnitude; - const countervalue = useCalculate({ from, to, value, disableRounding: true }); - return ( - - - {formatCurrencyUnit(from.units[0], BigNumber(value), { - showCode: true, - })} - - {" = "} - - {countervalue - ? formatCurrencyUnit(to.units[0], BigNumber(countervalue), { - showCode: true, - }) - : "???"} - - - ); -}; - -export default CurrentRate; diff --git a/tools/src/demos/countervalues/GraphRate.js b/tools/src/demos/countervalues/GraphRate.js deleted file mode 100644 index 3ee8822a86..0000000000 --- a/tools/src/demos/countervalues/GraphRate.js +++ /dev/null @@ -1,58 +0,0 @@ -// @flow -import React from "react"; -import { VictoryLine } from "victory"; -import type { Currency } from "@ledgerhq/live-common/lib/types"; -import { useCalculateMany } from "@ledgerhq/live-common/lib/countervalues/react"; - -type Props = { - from: Currency, - to: Currency, - count: number, - increment: number, - width: number, - height: number, -}; - -const GraphRate = ({ from, to, width, height, count, increment }: Props) => { - const inputData = []; - let t = Date.now() - count * increment; - const value = 10 ** from.units[0].magnitude; - for (let i = 0; i < count; i++) { - const date = new Date(t); - inputData.push({ date, value }); - t += increment; - } - const outputData = useCalculateMany(inputData, { - from, - to, - disableRounding: true, - }); - - const data = inputData.map(({ date }, i) => ({ - date, - value: outputData[i] || 0, - })); - - return ( - - - - ); -}; - -export default GraphRate; diff --git a/tools/src/demos/countervalues/index.js b/tools/src/demos/countervalues/index.js deleted file mode 100644 index 2690913fb1..0000000000 --- a/tools/src/demos/countervalues/index.js +++ /dev/null @@ -1,137 +0,0 @@ -// @flow -import React, { useState, useEffect } from "react"; -import { - listSupportedCurrencies, - getFiatCurrencyByTicker, -} from "@ledgerhq/live-common/lib/currencies"; -import type { - CountervaluesSettings, - TrackingPair, -} from "@ledgerhq/live-common/lib/countervalues/types"; -import { importCountervalues } from "@ledgerhq/live-common/lib/countervalues/logic"; -import { - Countervalues, - useCountervaluesPolling, - useCountervaluesExport, -} from "@ledgerhq/live-common/lib/countervalues/react"; -import CurrentRate from "./CurrentRate"; -import GraphRate from "./GraphRate"; - -const Row = ({ pair }: { pair: TrackingPair }) => { - return ( -
- {" 1Y"} - - {" 1M"} - - {" 7D"} - - {" 1D"} - - -
- ); -}; - -const App = ({ userSettings }: { userSettings: CountervaluesSettings }) => { - const polling = useCountervaluesPolling(); - return ( -
-
- {polling.pending ? "loading..." : "loaded."} - - -
-
- {userSettings.trackingPairs.map((pair) => ( - - ))} -
-
- ); -}; - -const initialUserSettings = { - // TODO we could make this dynamic / saved in LocalStorage - trackingPairs: listSupportedCurrencies() - .filter((c) => !c.isTestnetFor) - .map((from) => ({ - from, - // this to be dynamical (actually let user conf the from-to) - to: getFiatCurrencyByTicker("USD"), - // this to be dynamical (we want to challenge change of this over time!) - startDate: new Date(Date.now() - 365 * 24 * 60 * 60 * 1000), - })), - autofillGaps: true, -}; - -const LS_KEY = "countervalues"; -let initialCountervalues; -try { - const json = localStorage.getItem(LS_KEY); - if (json) { - initialCountervalues = importCountervalues( - JSON.parse(json), - initialUserSettings - ); - } -} catch (e) { - console.warn(e); -} - -const HookChanges = () => { - const e = useCountervaluesExport(); - useEffect(() => { - localStorage.setItem(LS_KEY, JSON.stringify(e)); - }, [e]); - return null; -}; - -const Demo = () => { - const [userSettings, setUserSettings] = useState(initialUserSettings); - - return ( - - - - - ); -}; - -Demo.demo = { - title: "Countervalues", - url: "/countervalues", -}; - -export default Demo; diff --git a/tools/src/demos/derivations/CurrencySelect.js b/tools/src/demos/derivations/CurrencySelect.js deleted file mode 100644 index 5ed1a72a1f..0000000000 --- a/tools/src/demos/derivations/CurrencySelect.js +++ /dev/null @@ -1,57 +0,0 @@ -// @flow -import React, { Component } from "react"; -import MenuItem from "@material-ui/core/MenuItem"; -import Select from "@material-ui/core/Select"; -import { getCryptoCurrencyIcon } from "@ledgerhq/live-common/lib/react"; -import type { Currency, CryptoCurrency } from "@ledgerhq/live-common/lib/types"; - -function inferCrypto(currency: Currency): ?CryptoCurrency { - if ("coinType" in currency) { - return currency; - } -} - -class CurrencySelect extends Component<{ - value: ?Currency, - currencies: Currency[], - onChange: (?Currency) => void -}> { - handleChange = (e: *) => { - this.props.onChange(this.props.currencies[e.target.value]); - }; - render() { - const { currencies, value } = this.props; - const i = currencies.indexOf(value); - return ( - - ); - } -} - -export default CurrencySelect; diff --git a/tools/src/demos/derivations/index.js b/tools/src/demos/derivations/index.js deleted file mode 100644 index 64fdcce47d..0000000000 --- a/tools/src/demos/derivations/index.js +++ /dev/null @@ -1,167 +0,0 @@ -// @flow -import React, { Component } from "react"; -import TransportWebUSB from "@ledgerhq/hw-transport-webusb"; -import styled from "styled-components"; -import { listCryptoCurrencies } from "@ledgerhq/live-common/lib/currencies"; -import { getAccountPlaceholderName } from "@ledgerhq/live-common/lib/account"; -import getAddress from "@ledgerhq/live-common/lib/hw/getAddress"; -import { - getDerivationModesForCurrency, - getDerivationScheme, - runDerivationScheme, - isIterableDerivationMode, - derivationModeSupportsIndex -} from "@ledgerhq/live-common/lib/derivation"; -import CurrencySelect from "./CurrencySelect"; - -const Main = styled.div` - padding: 40px; -`; - -let transportGlobalP; -let queue = Promise.resolve(); -const execInQueue = (job: () => Promise) => { - const p = queue.then(job); - queue = p; - return queue; -}; - -class CurrencyDerivation extends Component<*, *> { - state = { - address: "", - path: "", - error: null - }; - - componentDidMount() { - this.getAddress(); - } - - getAddress = (verify: boolean = false) => - execInQueue(async () => { - const { derivationMode, currency, index: account } = this.props; - try { - const p = transportGlobalP || TransportWebUSB.create(); - transportGlobalP = p; - const transport = await p; - const { address, path } = await getAddress(transport, { - currency, - path: runDerivationScheme( - getDerivationScheme({ currency, derivationMode }), - currency, - { account } - ), - derivationMode, - verify - }); - this.setState({ address, path }); - } catch (error) { - this.setState({ error }); - } - }); - - onVerify = () => this.getAddress(true); - - render() { - const { currency, derivationMode, index } = this.props; - const { address, path, error } = this.state; - - return ( -
- - {getAccountPlaceholderName({ currency, derivationMode, index })} - - {error ? ( - - {String(error.message || error)} - - - ) : path ? ( - - - {`${path}: ${address}`} - - - - ) : null} -
- ); - } -} - -class CurrencyDerivations extends Component<*, *> { - state = { - total: 1 - }; - - more = () => this.setState(({ total }) => ({ total: total + 1 })); - - render() { - const { currency } = this.props; - const { total } = this.state; - - return ( -
- {Array(total) - .fill(null) - .map((_, index) => - getDerivationModesForCurrency(currency) - .filter(mode => derivationModeSupportsIndex(mode, index)) - .filter(mode => index === 0 || isIterableDerivationMode(mode)) - .map(derivationMode => ( - - )) - )} - -
- ); - } -} - -class Derivations extends Component<*, *> { - static demo = { - title: "Derivations", - url: "/derivations", - hidden: true - }; - - state = { - currency: null - }; - - onChangeCurrency = (currency: *) => { - this.setState({ currency }); - }; - - render() { - const { currency } = this.state; - const currencies = listCryptoCurrencies(true).sort((a, b) => - a.family === b.family - ? a.name.localeCompare(b.name) - : a.family.localeCompare(b.family) - ); - return ( -
-
- -
- {currency ? ( - - ) : null} -
- ); - } -} - -export default Derivations; diff --git a/tools/src/demos/erc20/TokenSelect.js b/tools/src/demos/erc20/TokenSelect.js deleted file mode 100644 index 10d9fb3284..0000000000 --- a/tools/src/demos/erc20/TokenSelect.js +++ /dev/null @@ -1,52 +0,0 @@ -// @flow -import React, { useState, useEffect } from "react"; -import Select from "react-select"; -import { listTokens } from "@ledgerhq/live-common/lib/currencies"; -import type { TokenCurrency } from "@ledgerhq/live-common/lib/types"; -import api from "@ledgerhq/live-common/lib/countervalues/api"; - -let tickers; -const tickersP = api.fetchMarketcapTickers().then((t) => { - tickers = t; - return t; -}); - -const rank = (token) => { - const i = tickers.indexOf(token.ticker); - if (i === -1) return Infinity; - return i; -}; - -const getSortedTokens = () => { - let tokens = listTokens(); - if (!tickers) return tokens; - return tokens.slice(0).sort((a, b) => rank(a) - rank(b)); -}; - -type Props = { - value: ?TokenCurrency, - onChange: (?TokenCurrency) => void, -}; - -const TokenSelect = ({ value, onChange }: Props) => { - const [tokens, setTokens] = useState(getSortedTokens); - - useEffect(() => { - if (!tickers) { - tickersP.then(() => setTokens(getSortedTokens())); - } - }, []); - - return ( -