diff --git a/src/bridge/jsHelpers.ts b/src/bridge/jsHelpers.ts index 53020d3d37..614c47fbb6 100644 --- a/src/bridge/jsHelpers.ts +++ b/src/bridge/jsHelpers.ts @@ -39,7 +39,6 @@ import type { import type { CurrencyBridge, AccountBridge } from "../types/bridge"; import getAddress from "../hw/getAddress"; import type { Result, GetAddressOptions } from "../hw/getAddress/types"; -import { open, close } from "../hw"; import { withDevice } from "../hw/deviceAccess"; // Customize the way to iterate on the keychain derivation @@ -276,257 +275,258 @@ export const makeScanAccounts = ) => (opts: GetAddressOptions) => Promise; }): CurrencyBridge["scanAccounts"] => ({ currency, deviceId, syncConfig }): Observable => - Observable.create((o) => { - let finished = false; + withDevice(deviceId)((transport) => + Observable.create((o) => { + let finished = false; - const unsubscribe = () => { - finished = true; - }; + const unsubscribe = () => { + finished = true; + }; - const derivationsCache = {}; + const derivationsCache = {}; - async function stepAccount( - index, - res: Result, - derivationMode, - seedIdentifier, - transport - ): Promise { - if (finished) return; + async function stepAccount( + index, + res: Result, + derivationMode, + seedIdentifier, + transport + ): Promise { + if (finished) return; - const { address, path: freshAddressPath, ...rest } = res; + const { address, path: freshAddressPath, ...rest } = res; - const accountShape: Partial = await getAccountShape( - { - transport, - currency, - index, - address, - derivationPath: freshAddressPath, - derivationMode, - rest, - }, - syncConfig - ); - if (finished) return; - - const freshAddress = address; - const operations = accountShape.operations || []; - const operationsCount = - accountShape.operationsCount || operations.length; - const creationDate = - operations.length > 0 - ? operations[operations.length - 1].date - : new Date(); - const balance = accountShape.balance || new BigNumber(0); - const spendableBalance = - accountShape.spendableBalance || new BigNumber(0); - if (!accountShape.id) throw new Error("account ID must be provided"); - if (balance.isNaN()) throw new Error("invalid balance NaN"); - const initialAccount: Account = { - type: "Account", - id: accountShape.id, - seedIdentifier, - freshAddress, - freshAddressPath, - freshAddresses: [ + const accountShape: Partial = await getAccountShape( { - address: freshAddress, + transport, + currency, + index, + address, derivationPath: freshAddressPath, + derivationMode, + rest, }, - ], - derivationMode, - name: "", - starred: false, - used: false, - index, - currency, - operationsCount, - operations: [], - swapHistory: [], - pendingOperations: [], - unit: currency.units[0], - lastSyncDate: new Date(), - creationDate, - // overrides - balance, - spendableBalance, - blockHeight: 0, - balanceHistoryCache: emptyHistoryCache, - }; - const account = { ...initialAccount, ...accountShape }; - - if (account.balanceHistoryCache === emptyHistoryCache) { - account.balanceHistoryCache = generateHistoryFromOperations(account); - } - - if (!account.used) { - account.used = !isAccountEmpty(account); - } + syncConfig + ); + if (finished) return; + + const freshAddress = address; + const operations = accountShape.operations || []; + const operationsCount = + accountShape.operationsCount || operations.length; + const creationDate = + operations.length > 0 + ? operations[operations.length - 1].date + : new Date(); + const balance = accountShape.balance || new BigNumber(0); + const spendableBalance = + accountShape.spendableBalance || new BigNumber(0); + if (!accountShape.id) throw new Error("account ID must be provided"); + if (balance.isNaN()) throw new Error("invalid balance NaN"); + const initialAccount: Account = { + type: "Account", + id: accountShape.id, + seedIdentifier, + freshAddress, + freshAddressPath, + freshAddresses: [ + { + address: freshAddress, + derivationPath: freshAddressPath, + }, + ], + derivationMode, + name: "", + starred: false, + used: false, + index, + currency, + operationsCount, + operations: [], + swapHistory: [], + pendingOperations: [], + unit: currency.units[0], + lastSyncDate: new Date(), + creationDate, + // overrides + balance, + spendableBalance, + blockHeight: 0, + balanceHistoryCache: emptyHistoryCache, + }; + const account = { ...initialAccount, ...accountShape }; + + if (account.balanceHistoryCache === emptyHistoryCache) { + account.balanceHistoryCache = + generateHistoryFromOperations(account); + } - // Bitcoin needs to compute the freshAddressPath itself, - // so we update it afterwards - if (account?.freshAddressPath) { - res.address = account.freshAddress; - derivationsCache[account.freshAddressPath] = res; - } + if (!account.used) { + account.used = !isAccountEmpty(account); + } - log("scanAccounts", "derivationsCache", res); - - log( - "scanAccounts", - `scanning ${currency.id} at ${freshAddressPath}: ${ - res.address - } resulted of ${ - account - ? `Account with ${account.operations.length} txs` - : "no account" - }` - ); - if (!account) return; - account.name = !account.used - ? getNewAccountPlaceholderName({ - currency, - index, - derivationMode, - }) - : getAccountPlaceholderName({ - currency, - index, - derivationMode, - }); + // Bitcoin needs to compute the freshAddressPath itself, + // so we update it afterwards + if (account?.freshAddressPath) { + res.address = account.freshAddress; + derivationsCache[account.freshAddressPath] = res; + } - const showNewAccount = shouldShowNewAccount(currency, derivationMode); + log("scanAccounts", "derivationsCache", res); - if (account.used || showNewAccount) { log( - "debug", - `Emit 'discovered' event for a new account found. AccountUsed: ${account.used} - showNewAccount: ${showNewAccount}` + "scanAccounts", + `scanning ${currency.id} at ${freshAddressPath}: ${ + res.address + } resulted of ${ + account + ? `Account with ${account.operations.length} txs` + : "no account" + }` ); - o.next({ - type: "discovered", - account, - }); - } - - return account; - } + if (!account) return; + account.name = !account.used + ? getNewAccountPlaceholderName({ + currency, + index, + derivationMode, + }) + : getAccountPlaceholderName({ + currency, + index, + derivationMode, + }); - async function main() { - // TODO switch to withDevice - let transport; + const showNewAccount = shouldShowNewAccount(currency, derivationMode); - try { - transport = await open(deviceId); - const getAddr = getAddressFn - ? getAddressFn(transport) - : (opts) => getAddress(transport, opts); - const derivationModes = getDerivationModesForCurrency(currency); - - for (const derivationMode of derivationModes) { - if (finished) break; - const path = getSeedIdentifierDerivation(currency, derivationMode); + if (account.used || showNewAccount) { log( - "scanAccounts", - `scanning ${currency.id} on derivationMode=${derivationMode}` + "debug", + `Emit 'discovered' event for a new account found. AccountUsed: ${account.used} - showNewAccount: ${showNewAccount}` ); - let result: Result = derivationsCache[path]; + o.next({ + type: "discovered", + account, + }); + } - if (!result) { - try { - result = await getAddr({ - currency, - path, - derivationMode, - }); + return account; + } - derivationsCache[path] = result; - } catch (e) { - if (e instanceof UnsupportedDerivation) { - log( - "scanAccounts", - "ignore derivationMode=" + derivationMode - ); - continue; + async function main() { + try { + const getAddr = getAddressFn + ? getAddressFn(transport) + : (opts) => getAddress(transport, opts); + const derivationModes = getDerivationModesForCurrency(currency); + + for (const derivationMode of derivationModes) { + if (finished) break; + const path = getSeedIdentifierDerivation( + currency, + derivationMode + ); + log( + "scanAccounts", + `scanning ${currency.id} on derivationMode=${derivationMode}` + ); + let result: Result = derivationsCache[path]; + + if (!result) { + try { + result = await getAddr({ + currency, + path, + derivationMode, + }); + + derivationsCache[path] = result; + } catch (e) { + if (e instanceof UnsupportedDerivation) { + log( + "scanAccounts", + "ignore derivationMode=" + derivationMode + ); + continue; + } + throw e; } - throw e; } - } - if (!result) continue; - const seedIdentifier = result.publicKey; - let emptyCount = 0; - const mandatoryEmptyAccountSkip = - getMandatoryEmptyAccountSkip(derivationMode); - const derivationScheme = getDerivationScheme({ - derivationMode, - currency, - }); + if (!result) continue; + const seedIdentifier = result.publicKey; + let emptyCount = 0; + const mandatoryEmptyAccountSkip = + getMandatoryEmptyAccountSkip(derivationMode); + const derivationScheme = getDerivationScheme({ + derivationMode, + currency, + }); - const stopAt = isIterableDerivationMode(derivationMode) ? 255 : 1; - const startsAt = getDerivationModeStartsAt(derivationMode); + const stopAt = isIterableDerivationMode(derivationMode) ? 255 : 1; + const startsAt = getDerivationModeStartsAt(derivationMode); - log( - "debug", - `start scanning account process. MandatoryEmptyAccountSkip ${mandatoryEmptyAccountSkip} / StartsAt: ${startsAt} - StopAt: ${stopAt}` - ); + log( + "debug", + `start scanning account process. MandatoryEmptyAccountSkip ${mandatoryEmptyAccountSkip} / StartsAt: ${startsAt} - StopAt: ${stopAt}` + ); - const iterateResult = await buildIterateResult({ - result, - derivationMode, - derivationScheme, - }); + const iterateResult = await buildIterateResult({ + result, + derivationMode, + derivationScheme, + }); - for (let index = startsAt; index < stopAt; index++) { - log("debug", `start to scan a new account. Index: ${index}`); + for (let index = startsAt; index < stopAt; index++) { + log("debug", `start to scan a new account. Index: ${index}`); - if (finished) { - log("debug", `new account scanning process has been finished`); - break; - } + if (finished) { + log( + "debug", + `new account scanning process has been finished` + ); + break; + } - if (!derivationModeSupportsIndex(derivationMode, index)) continue; + if (!derivationModeSupportsIndex(derivationMode, index)) + continue; - const res = await iterateResult({ - transport, - index, - derivationsCache, - derivationMode, - derivationScheme, - currency, - }); + const res = await iterateResult({ + transport, + index, + derivationsCache, + derivationMode, + derivationScheme, + currency, + }); - if (!res) break; + if (!res) break; - const account = await stepAccount( - index, - res, - derivationMode, - seedIdentifier, - transport - ); + const account = await stepAccount( + index, + res, + derivationMode, + seedIdentifier, + transport + ); - if (account && !account.used) { - if (emptyCount >= mandatoryEmptyAccountSkip) break; - emptyCount++; + if (account && !account.used) { + if (emptyCount >= mandatoryEmptyAccountSkip) break; + emptyCount++; + } } } - } - // } - o.complete(); - } catch (e) { - o.error(e); - } finally { - if (transport) { - close(transport, deviceId); + o.complete(); + } catch (e) { + o.error(e); } } - } - main(); - return unsubscribe; - }); + main(); + return unsubscribe; + }) + ); export function makeAccountBridgeReceive({ injectGetAddressParams, }: { diff --git a/src/families/bitcoin/js-signOperation.ts b/src/families/bitcoin/js-signOperation.ts index f4f3939f74..7957b2c15a 100644 --- a/src/families/bitcoin/js-signOperation.ts +++ b/src/families/bitcoin/js-signOperation.ts @@ -5,7 +5,7 @@ import { log } from "@ledgerhq/logs"; import type { Account, Operation, SignOperationEvent } from "./../../types"; import { isSegwitDerivationMode } from "./../../derivation"; import { encodeOperationId } from "./../../operation"; -import { open, close } from "./../../hw"; +import { withDevice } from "../../hw/deviceAccess"; import type { Transaction } from "./types"; import { getNetworkParameters } from "./networks"; import { buildTransaction } from "./js-buildTransaction"; @@ -22,14 +22,13 @@ const signOperation = ({ deviceId: any; transaction: Transaction; }): Observable => - Observable.create((o) => { - async function main() { - const { currency } = account; - const transport = await open(deviceId); - const hwApp = new Btc(transport); - const walletAccount = getWalletAccount(account); - - try { + withDevice(deviceId)((transport) => + Observable.create((o) => { + async function main() { + const { currency } = account; + const hwApp = new Btc(transport); + const walletAccount = getWalletAccount(account); + log("hw", `signTransaction ${currency.id} for account ${account.id}`); const txInfo = await buildTransaction(account, transaction); let senders = new Set(); @@ -152,15 +151,13 @@ const signOperation = ({ expirationDate: null, }, }); - } finally { - close(transport, deviceId); } - } - main().then( - () => o.complete(), - (e) => o.error(e) - ); - }); + main().then( + () => o.complete(), + (e) => o.error(e) + ); + }) + ); export default signOperation; diff --git a/src/families/crypto_org/js-signOperation.ts b/src/families/crypto_org/js-signOperation.ts index af6e24744d..d0287b3d0f 100644 --- a/src/families/crypto_org/js-signOperation.ts +++ b/src/families/crypto_org/js-signOperation.ts @@ -1,16 +1,16 @@ import { BigNumber } from "bignumber.js"; import { Observable } from "rxjs"; +import { utils } from "@crypto-com/chain-jslib"; import { FeeNotLoaded } from "@ledgerhq/errors"; +import CryptoOrgApp from "@ledgerhq/hw-app-cosmos"; import { CryptoOrgWrongSignatureHeader, CryptoOrgSignatureSize, } from "./errors"; import type { Transaction } from "./types"; import type { Account, Operation, SignOperationEvent } from "../../types"; -import { open, close } from "../../hw"; import { encodeOperationId } from "../../operation"; -import CryptoOrgApp from "@ledgerhq/hw-app-cosmos"; -import { utils } from "@crypto-com/chain-jslib"; +import { withDevice } from "../../hw/deviceAccess"; import { buildTransaction } from "./js-buildTransaction"; import { isTestNet } from "./logic"; @@ -104,11 +104,9 @@ const signOperation = ({ deviceId: any; transaction: Transaction; }): Observable => - Observable.create((o) => { - async function main() { - const transport = await open(deviceId); - - try { + withDevice(deviceId)((transport) => + Observable.create((o) => { + async function main() { o.next({ type: "device-signature-requested", }); @@ -165,15 +163,13 @@ const signOperation = ({ }, }); } - } finally { - close(transport, deviceId); } - } - main().then( - () => o.complete(), - (e) => o.error(e) - ); - }); + main().then( + () => o.complete(), + (e) => o.error(e) + ); + }) + ); export default signOperation; diff --git a/src/families/elrond/js-signOperation.ts b/src/families/elrond/js-signOperation.ts index 099d6b6033..a58047d49b 100644 --- a/src/families/elrond/js-signOperation.ts +++ b/src/families/elrond/js-signOperation.ts @@ -3,7 +3,7 @@ import { Observable } from "rxjs"; import { FeeNotLoaded } from "@ledgerhq/errors"; import type { Transaction } from "./types"; import type { Account, Operation, SignOperationEvent } from "../../types"; -import { open, close } from "../../hw"; +import { withDevice } from "../../hw/deviceAccess"; import { encodeOperationId } from "../../operation"; import Elrond from "./hw-app-elrond"; import { buildTransaction } from "./js-buildTransaction"; @@ -48,11 +48,9 @@ const signOperation = ({ deviceId: any; transaction: Transaction; }): Observable => - Observable.create((o) => { - async function main() { - const transport = await open(deviceId); - - try { + withDevice(deviceId)((transport) => + Observable.create((o) => { + async function main() { if (!transaction.fees) { throw new FeeNotLoaded(); } @@ -89,15 +87,13 @@ const signOperation = ({ expirationDate: null, }, }); - } finally { - close(transport, deviceId); } - } - main().then( - () => o.complete(), - (e) => o.error(e) - ); - }); + main().then( + () => o.complete(), + (e) => o.error(e) + ); + }) + ); export default signOperation; diff --git a/src/families/polkadot/js-signOperation.ts b/src/families/polkadot/js-signOperation.ts index 1800bf75ed..565d321614 100644 --- a/src/families/polkadot/js-signOperation.ts +++ b/src/families/polkadot/js-signOperation.ts @@ -11,7 +11,7 @@ import type { OperationType, SignOperationEvent, } from "../../types"; -import { open, close } from "../../hw"; +import { withDevice } from "../../hw/deviceAccess"; import { encodeOperationId } from "../../operation"; import { buildTransaction } from "./js-buildTransaction"; import { calculateAmount, getNonce, isFirstBond } from "./logic"; @@ -157,69 +157,69 @@ const signOperation = ({ deviceId: any; transaction: Transaction; }): Observable => - new Observable((o) => { - async function main() { - const transport = await open(deviceId); - - try { - o.next({ - type: "device-signature-requested", - }); + withDevice(deviceId)( + (transport) => + new Observable((o) => { + async function main() { + o.next({ + type: "device-signature-requested", + }); - if (!transaction.fees) { - throw new FeeNotLoaded(); + if (!transaction.fees) { + throw new FeeNotLoaded(); + } + + // Ensure amount is filled when useAllAmount + const transactionToSign = { + ...transaction, + amount: calculateAmount({ + a: account, + t: transaction, + }), + }; + const { unsigned, registry } = await buildTransaction( + account, + transactionToSign, + true + ); + const payload = registry + .createType("ExtrinsicPayload", unsigned, { + version: unsigned.version, + }) + .toU8a({ + method: true, + }); + // Sign by device + const polkadot = new Polkadot(transport); + // FIXME: the type of payload Uint8Array is not compatible with the signature of sign which accept a string + const r = await polkadot.sign( + account.freshAddressPath, + payload as any + ); + const signed = await signExtrinsic(unsigned, r.signature, registry); + o.next({ + type: "device-signature-granted", + }); + const operation = buildOptimisticOperation( + account, + transactionToSign, + transactionToSign.fees ?? new BigNumber(0) + ); + o.next({ + type: "signed", + signedOperation: { + operation, + signature: signed, + expirationDate: null, + }, + }); } - // Ensure amount is filled when useAllAmount - const transactionToSign = { - ...transaction, - amount: calculateAmount({ - a: account, - t: transaction, - }), - }; - const { unsigned, registry } = await buildTransaction( - account, - transactionToSign, - true - ); - const payload = registry - .createType("ExtrinsicPayload", unsigned, { - version: unsigned.version, - }) - .toU8a({ - method: true, - }); - // Sign by device - const polkadot = new Polkadot(transport); - // FIXME: the type of payload Uint8Array is not compatible with the signature of sign which accept a string - const r = await polkadot.sign(account.freshAddressPath, payload as any); - const signed = await signExtrinsic(unsigned, r.signature, registry); - o.next({ - type: "device-signature-granted", - }); - const operation = buildOptimisticOperation( - account, - transactionToSign, - transactionToSign.fees ?? new BigNumber(0) + main().then( + () => o.complete(), + (e) => o.error(e) ); - o.next({ - type: "signed", - signedOperation: { - operation, - signature: signed, - expirationDate: null, - }, - }); - } finally { - close(transport, deviceId); - } - } - - main().then( - () => o.complete(), - (e) => o.error(e) - ); - }); + }) + ); export default signOperation; diff --git a/src/families/ripple/bridge/js.ts b/src/families/ripple/bridge/js.ts index 9f9e7d2263..65a5faf389 100644 --- a/src/families/ripple/bridge/js.ts +++ b/src/families/ripple/bridge/js.ts @@ -32,7 +32,7 @@ import { emptyHistoryCache, } from "../../../account"; import getAddress from "../../../hw/getAddress"; -import { open, close } from "../../../hw"; +import { withDevice } from "../../../hw/deviceAccess"; import { parseAPIValue, parseAPICurrencyObject, @@ -89,106 +89,104 @@ const signOperation = ({ transaction, deviceId, }): Observable => - new Observable((o) => { - delete cacheRecipientsNew[transaction.recipient]; - const { fee } = transaction; - if (!fee) throw new FeeNotLoaded(); - - async function main() { - try { - const amount = formatAPICurrencyXRP(transaction.amount); - const tag = transaction.tag ? transaction.tag : undefined; - const payment = { - source: { - address: account.freshAddress, - amount, - }, - destination: { - address: transaction.recipient, - minAmount: amount, - tag, - }, - }; - const instruction = { - fee: formatAPICurrencyXRP(fee).value, - maxLedgerVersionOffset: 12, - }; - if (tag) - invariant( - validateTag(new BigNumber(tag)), - `tag is set but is not in a valid format, should be between [0 - ${uint32maxPlus1 - .minus(1) - .toString()}]` + withDevice(deviceId)( + (transport) => + new Observable((o) => { + delete cacheRecipientsNew[transaction.recipient]; + const { fee } = transaction; + if (!fee) throw new FeeNotLoaded(); + + async function main() { + const amount = formatAPICurrencyXRP(transaction.amount); + const tag = transaction.tag ? transaction.tag : undefined; + const payment = { + source: { + address: account.freshAddress, + amount, + }, + destination: { + address: transaction.recipient, + minAmount: amount, + tag, + }, + }; + const instruction = { + fee: formatAPICurrencyXRP(fee).value, + maxLedgerVersionOffset: 12, + }; + if (tag) + invariant( + validateTag(new BigNumber(tag)), + `tag is set but is not in a valid format, should be between [0 - ${uint32maxPlus1 + .minus(1) + .toString()}]` + ); + const prepared = await preparePayment( + account.freshAddress, + payment, + instruction ); - const prepared = await preparePayment( - account.freshAddress, - payment, - instruction - ); - let signature; - const transport = await open(deviceId); + let signature; - try { - o.next({ - type: "device-signature-requested", - }); - signature = await signTransaction( - account.currency, - transport, - account.freshAddressPath, - JSON.parse(prepared.txJSON) - ); - o.next({ - type: "device-signature-granted", - }); - } finally { - close(transport, deviceId); - } + try { + o.next({ + type: "device-signature-requested", + }); + signature = await signTransaction( + account.currency, + transport, + account.freshAddressPath, + JSON.parse(prepared.txJSON) + ); + o.next({ + type: "device-signature-granted", + }); - const hash = ""; - const operation: Operation = { - id: `${account.id}-${hash}-OUT`, - hash, - accountId: account.id, - type: "OUT", - value: transaction.amount, - fee, - blockHash: null, - blockHeight: null, - senders: [account.freshAddress], - recipients: [transaction.recipient], - date: new Date(), - // we probably can't get it so it's a predictive value - transactionSequenceNumber: await getSequenceNumber(account), - extra: {} as any, - }; + const hash = ""; + const operation: Operation = { + id: `${account.id}-${hash}-OUT`, + hash, + accountId: account.id, + type: "OUT", + value: transaction.amount, + fee, + blockHash: null, + blockHeight: null, + senders: [account.freshAddress], + recipients: [transaction.recipient], + date: new Date(), + // we probably can't get it so it's a predictive value + transactionSequenceNumber: await getSequenceNumber(account), + extra: {} as any, + }; + + if (transaction.tag) { + operation.extra.tag = transaction.tag; + } - if (transaction.tag) { - operation.extra.tag = transaction.tag; - } + o.next({ + type: "signed", + signedOperation: { + operation, + signature, + expirationDate: null, + }, + }); + } catch (e: any) { + if (e && e.name === "RippledError" && e.data.resultMessage) { + throw new Error(e.data.resultMessage); + } - o.next({ - type: "signed", - signedOperation: { - operation, - signature, - expirationDate: null, - }, - }); - } catch (e: any) { - if (e && e.name === "RippledError" && e.data.resultMessage) { - throw new Error(e.data.resultMessage); + throw e; + } } - throw e; - } - } - - main().then( - () => o.complete(), - (e) => o.error(e) - ); - }); + main().then( + () => o.complete(), + (e) => o.error(e) + ); + }) + ); const broadcast = async ({ signedOperation: { signature, operation } }) => { const submittedPayment = await submit(signature); @@ -360,185 +358,184 @@ const currencyBridge: CurrencyBridge = { preload: () => Promise.resolve({}), hydrate: () => {}, scanAccounts: ({ currency, deviceId }) => - new Observable((o) => { - let finished = false; - - const unsubscribe = () => { - finished = true; - }; + withDevice(deviceId)( + (transport) => + new Observable((o) => { + let finished = false; - async function main() { - let transport; + const unsubscribe = () => { + finished = true; + }; - try { - transport = await open(deviceId); - const serverInfo = await getServerInfo(); - const ledgers = serverInfo.completeLedgers.split("-"); - const minLedgerVersion = Number(ledgers[0]); - const maxLedgerVersion = Number(ledgers[1]); - const derivationModes = getDerivationModesForCurrency(currency); - - for (const derivationMode of derivationModes) { - const derivationScheme = getDerivationScheme({ - derivationMode, - currency, - }); - const stopAt = isIterableDerivationMode(derivationMode) ? 255 : 1; - - for (let index = 0; index < stopAt; index++) { - if (!derivationModeSupportsIndex(derivationMode, index)) continue; - const freshAddressPath = runDerivationScheme( - derivationScheme, - currency, - { - account: index, - } - ); - const { address } = await getAddress(transport, { - currency, - path: freshAddressPath, - derivationMode, - }); - if (finished) return; - const accountId = `ripplejs:2:${currency.id}:${address}:${derivationMode}`; - let info; - - try { - info = await getAccountInfo(address); - } catch (e) { - if (checkAccountNotFound(e)) { - throw e; - } - } + async function main() { + try { + const serverInfo = await getServerInfo(); + const ledgers = serverInfo.completeLedgers.split("-"); + const minLedgerVersion = Number(ledgers[0]); + const maxLedgerVersion = Number(ledgers[1]); + const derivationModes = getDerivationModesForCurrency(currency); - // fresh address is address. ripple never changes. - const freshAddress = address; + for (const derivationMode of derivationModes) { + const derivationScheme = getDerivationScheme({ + derivationMode, + currency, + }); + const stopAt = isIterableDerivationMode(derivationMode) + ? 255 + : 1; + + for (let index = 0; index < stopAt; index++) { + if (!derivationModeSupportsIndex(derivationMode, index)) + continue; + const freshAddressPath = runDerivationScheme( + derivationScheme, + currency, + { + account: index, + } + ); + const { address } = await getAddress(transport, { + currency, + path: freshAddressPath, + derivationMode, + }); + if (finished) return; + const accountId = `ripplejs:2:${currency.id}:${address}:${derivationMode}`; + let info; + + try { + info = await getAccountInfo(address); + } catch (e) { + if (checkAccountNotFound(e)) { + throw e; + } + } + + // fresh address is address. ripple never changes. + const freshAddress = address; + + if (!info) { + // account does not exist in Ripple server + // we are generating a new account locally + if (derivationMode === "") { + o.next({ + type: "discovered", + account: { + type: "Account", + id: accountId, + seedIdentifier: freshAddress, + derivationMode, + name: getNewAccountPlaceholderName({ + currency, + index, + derivationMode, + }), + starred: false, + used: false, + freshAddress, + freshAddressPath, + freshAddresses: [ + { + address: freshAddress, + derivationPath: freshAddressPath, + }, + ], + balance: new BigNumber(0), + spendableBalance: new BigNumber(0), + blockHeight: maxLedgerVersion, + index, + currency, + operationsCount: 0, + operations: [], + pendingOperations: [], + unit: currency.units[0], + // @ts-expect-error archived does not exists on type Account + archived: false, + lastSyncDate: new Date(), + creationDate: new Date(), + swapHistory: [], + balanceHistoryCache: emptyHistoryCache, // calculated in the jsHelpers + }, + }); + } + + break; + } + + if (finished) return; + const balance = parseAPIValue(info.xrpBalance); + invariant( + !balance.isNaN() && balance.isFinite(), + `Ripple: invalid balance=${balance.toString()} for address ${address}` + ); + const transactions = await getTransactions(address, { + minLedgerVersion, + maxLedgerVersion, + types: ["payment"], + }); + if (finished) return; + const account: Account = { + type: "Account", + id: accountId, + seedIdentifier: freshAddress, + derivationMode, + name: getAccountPlaceholderName({ + currency, + index, + derivationMode, + }), + starred: false, + used: true, + freshAddress, + freshAddressPath, + freshAddresses: [ + { + address: freshAddress, + derivationPath: freshAddressPath, + }, + ], + balance, + spendableBalance: balance, + // TODO calc with base reserve + blockHeight: maxLedgerVersion, + index, + currency, + operationsCount: 0, + operations: [], + pendingOperations: [], + unit: currency.units[0], + lastSyncDate: new Date(), + creationDate: new Date(), + swapHistory: [], + balanceHistoryCache: emptyHistoryCache, // calculated in the jsHelpers + }; + account.operations = transactions + .map(txToOperation(account)) + .filter(Boolean); + account.operationsCount = account.operations.length; + + if (account.operations.length > 0) { + account.creationDate = + account.operations[account.operations.length - 1].date; + } - if (!info) { - // account does not exist in Ripple server - // we are generating a new account locally - if (derivationMode === "") { o.next({ type: "discovered", - account: { - type: "Account", - id: accountId, - seedIdentifier: freshAddress, - derivationMode, - name: getNewAccountPlaceholderName({ - currency, - index, - derivationMode, - }), - starred: false, - used: false, - freshAddress, - freshAddressPath, - freshAddresses: [ - { - address: freshAddress, - derivationPath: freshAddressPath, - }, - ], - balance: new BigNumber(0), - spendableBalance: new BigNumber(0), - blockHeight: maxLedgerVersion, - index, - currency, - operationsCount: 0, - operations: [], - pendingOperations: [], - unit: currency.units[0], - // @ts-expect-error archived does not exists on type Account - archived: false, - lastSyncDate: new Date(), - creationDate: new Date(), - swapHistory: [], - balanceHistoryCache: emptyHistoryCache, // calculated in the jsHelpers - }, + account, }); } - - break; } - if (finished) return; - const balance = parseAPIValue(info.xrpBalance); - invariant( - !balance.isNaN() && balance.isFinite(), - `Ripple: invalid balance=${balance.toString()} for address ${address}` - ); - const transactions = await getTransactions(address, { - minLedgerVersion, - maxLedgerVersion, - types: ["payment"], - }); - if (finished) return; - const account: Account = { - type: "Account", - id: accountId, - seedIdentifier: freshAddress, - derivationMode, - name: getAccountPlaceholderName({ - currency, - index, - derivationMode, - }), - starred: false, - used: true, - freshAddress, - freshAddressPath, - freshAddresses: [ - { - address: freshAddress, - derivationPath: freshAddressPath, - }, - ], - balance, - spendableBalance: balance, - // TODO calc with base reserve - blockHeight: maxLedgerVersion, - index, - currency, - operationsCount: 0, - operations: [], - pendingOperations: [], - unit: currency.units[0], - lastSyncDate: new Date(), - creationDate: new Date(), - swapHistory: [], - balanceHistoryCache: emptyHistoryCache, // calculated in the jsHelpers - }; - account.operations = transactions - .map(txToOperation(account)) - .filter(Boolean); - account.operationsCount = account.operations.length; - - if (account.operations.length > 0) { - account.creationDate = - account.operations[account.operations.length - 1].date; - } - - o.next({ - type: "discovered", - account, - }); + o.complete(); + } catch (e) { + o.error(e); } } - o.complete(); - } catch (e) { - o.error(e); - } finally { - if (transport) { - await close(transport, deviceId); - } - } - } - - main(); - return unsubscribe; - }), + main(); + return unsubscribe; + }) + ), }; const sync = ({ diff --git a/src/families/stellar/js-signOperation.ts b/src/families/stellar/js-signOperation.ts index fa81f5047e..1b48e82735 100644 --- a/src/families/stellar/js-signOperation.ts +++ b/src/families/stellar/js-signOperation.ts @@ -3,7 +3,7 @@ import { Observable } from "rxjs"; import Stellar from "@ledgerhq/hw-app-str"; import { FeeNotLoaded } from "@ledgerhq/errors"; import type { Account, Operation, SignOperationEvent } from "../../types"; -import { open, close } from "../../hw"; +import { withDevice } from "../../hw/deviceAccess"; import type { Transaction } from "./types"; import { buildTransaction } from "./js-buildTransaction"; import { fetchSequence } from "./api"; @@ -48,11 +48,9 @@ const signOperation = ({ deviceId: any; transaction: Transaction; }): Observable => - Observable.create((o) => { - async function main() { - const transport = await open(deviceId); - - try { + withDevice(deviceId)((transport) => + Observable.create((o) => { + async function main() { o.next({ type: "device-signature-requested", }); @@ -86,15 +84,13 @@ const signOperation = ({ expirationDate: null, }, }); - } finally { - close(transport, deviceId); } - } - main().then( - () => o.complete(), - (e) => o.error(e) - ); - }); + main().then( + () => o.complete(), + (e) => o.error(e) + ); + }) + ); export default signOperation; diff --git a/src/families/tron/bridge/js.ts b/src/families/tron/bridge/js.ts index 1baf993173..387c63c6d0 100644 --- a/src/families/tron/bridge/js.ts +++ b/src/families/tron/bridge/js.ts @@ -32,7 +32,7 @@ import type { AccountBridge, DeviceId, } from "../../../types/bridge"; -import { open, close } from "../../../hw"; +import { withDevice } from "../../../hw/deviceAccess"; import signTransaction from "../../../hw/signTransaction"; import { makeSync, makeScanAccounts } from "../../../bridge/jsHelpers"; import { formatCurrencyUnit } from "../../../currencies"; @@ -99,62 +99,63 @@ const signOperation = ({ transaction: Transaction; deviceId: DeviceId; }): Observable => - Observable.create((o) => { - async function main() { - const subAccount = - transaction.subAccountId && account.subAccounts - ? account.subAccounts.find((sa) => sa.id === transaction.subAccountId) - : null; - const isContractAddressRecipient = - (await fetchTronContract(transaction.recipient)) !== undefined; - const fee = await getEstimatedFees( - account, - transaction, - isContractAddressRecipient - ); - const balance = subAccount - ? subAccount.balance - : BigNumber.max(0, account.spendableBalance.minus(fee)); - transaction.amount = transaction.useAllAmount - ? balance - : transaction.amount; - - // send trc20 to a new account is forbidden by us (because it will not activate the account) - if ( - transaction.recipient && - transaction.mode === "send" && - subAccount && - subAccount.type === "TokenAccount" && - subAccount.token.tokenType === "trc20" && - !isContractAddressRecipient && // send trc20 to a smart contract is allowed - (await fetchTronAccount(transaction.recipient)).length === 0 - ) { - throw new TronSendTrc20ToNewAccountForbidden(); - } + withDevice(deviceId)((transport) => + Observable.create((o) => { + async function main() { + const subAccount = + transaction.subAccountId && account.subAccounts + ? account.subAccounts.find( + (sa) => sa.id === transaction.subAccountId + ) + : null; + const isContractAddressRecipient = + (await fetchTronContract(transaction.recipient)) !== undefined; + const fee = await getEstimatedFees( + account, + transaction, + isContractAddressRecipient + ); + const balance = subAccount + ? subAccount.balance + : BigNumber.max(0, account.spendableBalance.minus(fee)); + transaction.amount = transaction.useAllAmount + ? balance + : transaction.amount; + + // send trc20 to a new account is forbidden by us (because it will not activate the account) + if ( + transaction.recipient && + transaction.mode === "send" && + subAccount && + subAccount.type === "TokenAccount" && + subAccount.token.tokenType === "trc20" && + !isContractAddressRecipient && // send trc20 to a smart contract is allowed + (await fetchTronAccount(transaction.recipient)).length === 0 + ) { + throw new TronSendTrc20ToNewAccountForbidden(); + } - const getPreparedTransaction = () => { - switch (transaction.mode) { - case "freeze": - return freezeTronTransaction(account, transaction); + const getPreparedTransaction = () => { + switch (transaction.mode) { + case "freeze": + return freezeTronTransaction(account, transaction); - case "unfreeze": - return unfreezeTronTransaction(account, transaction); + case "unfreeze": + return unfreezeTronTransaction(account, transaction); - case "vote": - return voteTronSuperRepresentatives(account, transaction); + case "vote": + return voteTronSuperRepresentatives(account, transaction); - case "claimReward": - return claimRewardTronTransaction(account); + case "claimReward": + return claimRewardTronTransaction(account); - default: - return createTronTransaction(account, transaction, subAccount); - } - }; + default: + return createTronTransaction(account, transaction, subAccount); + } + }; - const preparedTransaction = await getPreparedTransaction(); - const transport = await open(deviceId); + const preparedTransaction = await getPreparedTransaction(); - try { o.next({ type: "device-signature-requested", }); @@ -274,16 +275,14 @@ const signOperation = ({ expirationDate: null, }, }); - } finally { - close(transport, deviceId); } - } - main().then( - () => o.complete(), - (e) => o.error(e) - ); - }); + main().then( + () => o.complete(), + (e) => o.error(e) + ); + }) + ); const broadcast = async ({ signedOperation: { signature, operation, signatureRaw },