cburgmer/rasterizeHTML.js 1991
Renders HTML into the browser's canvas
gre/bezier-easing 1210
cubic-bezier implementation for your JavaScript animation easings – MIT License
gl-transitions/gl-transitions 689
The open collection of GL Transitions
Cubic Bezier Curve editor made with React & SVG
"Behind Asteroids, The Dark Side" is a JS13K entry for 2015 – winner in Desktop, Mobile and Community categories
gre/beez 34
100% web real-time audio experiment using smartphones as effect controller. (tech: Android Chrome + WebRTC + Web Audio API)
Note frequencies for equal-tempered scale
Blazing Race is a HTML5 against-the-clock platform game where you control a fireball with your mouse and you try to light candles.
slice/append/insert/subset/copy operations on AudioBuffer linked-list chunks
push eventLedgerHQ/ledger-live-common
commit sha 8ca2ed2a318e70d90deaa613ec63d28bd044571e
Fixes bug in qr export with missing swapHistory
commit sha fe686b787a4a61cb16e5a22de90a5544a4a30a4f
Merge pull request #805 from juan-cortes/cross-fix Fixes bug in qr export with missing swapHistory
push time in 35 minutes
PR merged LedgerHQ/ledger-live-common
There is currently a bug where scanning a live qr export of accounts would yield no accounts due to the missing swapHistory after encoding/decoding. This addresses that issue and adds a few tests to check that we don't break it in the future. Ideally, the tests would also check if the linked operations and accounts exist but I'm leaving that as a nice to have for the future.
pr closed time in 35 minutes
Pull request review commentLedgerHQ/ledger-live-common
Failsafe for swaphistory cross export/import
const asResultAccount = (unsafe: mixed): AccountData => { index, balance, // $FlowFixMe No idea how to make flow like this- swapHistory: invalidSwapHistory ? [] : swapHistory,+ swapHistory: swapHistory || invalidSwapHistory ? [] : swapHistory,
ok yeah, that's why we need to build up an array
comment created time in 7 hours
Pull request review commentLedgerHQ/ledger-live-common
Failsafe for swaphistory cross export/import
export const accountDataToAccount = ({ index, freshAddress, freshAddressPath,- swapHistory: swapHistory.map(fromSwapOperationRaw),+ swapHistory: (swapHistory || []).map(fromSwapOperationRaw),
This part makes no sense to me. Why would it be falsy? To me it means the error is somewhere else where we miss to set it as clearly we have defined our type to not be falsy
comment created time in 14 hours
Pull request review commentLedgerHQ/ledger-live-common
Failsafe for swaphistory cross export/import
const asResultAccount = (unsafe: mixed): AccountData => { index, balance, // $FlowFixMe No idea how to make flow like this- swapHistory: invalidSwapHistory ? [] : swapHistory,+ swapHistory: swapHistory || invalidSwapHistory ? [] : swapHistory,
Let's review tomorrow. I would prefer we satisfy flow, maybe by building up an array, and instead of using a flag, I feel we can simply use that same array to set it to [] or something
comment created time in 14 hours
Pull request review commentLedgerHQ/ledger-live-common
Failsafe for swaphistory cross export/import
const asResultAccount = (unsafe: mixed): AccountData => { index, balance, // $FlowFixMe No idea how to make flow like this- swapHistory: invalidSwapHistory ? [] : swapHistory,+ swapHistory: swapHistory || invalidSwapHistory ? [] : swapHistory,
I don't understand the logic. Did you mean !swapHistory ?
comment created time in 14 hours
Pull request review commentLedgerHQ/ledger-live-common
Failsafe for swaphistory cross export/import
const asResultAccount = (unsafe: mixed): AccountData => { index, balance, // $FlowFixMe No idea how to make flow like this
Can you remove this flowfixme then? I imagine it was why it was complaining
comment created time in 14 hours
Pull request review commentLedgerHQ/ledger-live-common
+// @flow++import type {+ CryptoCurrency,+ TokenAccount,+ Account,+ SyncConfig,+} from "../../types";+import type { CoreAccount } from "../../libcore/types";+import { minimalOperationsBuilder } from "../../reconciliation";+import { buildASAOperation } from "./buildASAOperation";+import { BigNumber } from "bignumber.js";+import { findTokenById, listTokensForCryptoCurrency } from "../../currencies";+import { promiseAllBatched } from "../../promise";++const OperationOrderKey = {+ date: 0,+};++async function buildAlgorandTokenAccount({+ parentAccountId,+ token,+ coreAccount,+ existingTokenAccount,+ balance,+}) {+ const id = parentAccountId + "+" + token.id;+ const getAllOperations = async () => {+ const query = await coreAccount.queryOperations();+ await query.complete();+ await query.addOrder(OperationOrderKey.date, false);+ const coreOperations = await query.execute();++ const operations = await minimalOperationsBuilder(+ (existingTokenAccount && existingTokenAccount.operations) || [],+ coreOperations,+ (coreOperation) =>+ buildASAOperation({+ coreOperation,+ accountId: id,+ })+ );++ return operations;+ };++ const operations = await getAllOperations();++ const tokenAccount: $Exact<TokenAccount> = {+ type: "TokenAccount",+ id,+ parentId: parentAccountId,+ starred: false,+ token,+ operationsCount: operations.length,+ operations,+ pendingOperations: [],+ balance,+ swapHistory: [],+ creationDate:+ operations.length > 0+ ? operations[operations.length - 1].date+ : new Date(),+ };++ return tokenAccount;+}++async function algorandBuildTokenAccounts({+ currency,+ coreAccount,+ accountId,+ existingAccount,+ syncConfig,+}: {+ currency: CryptoCurrency,+ coreAccount: CoreAccount,+ accountId: string,+ existingAccount: ?Account,+ syncConfig: SyncConfig,+}): Promise<?(TokenAccount[])> {+ const { blacklistedTokenIds = [] } = syncConfig;+ if (listTokensForCryptoCurrency(currency).length === 0) return undefined;+ const tokenAccounts = [];+ const algorandAccount = await coreAccount.asAlgorandAccount();+ const accountASA = await algorandAccount.getAssetsBalances();++ const existingAccountByTicker = {}; // used for fast lookup+ const existingAccountTickers = []; // used to keep track of ordering+ if (existingAccount && existingAccount.subAccounts) {+ for (const existingSubAccount of existingAccount.subAccounts) {+ if (existingSubAccount.type === "TokenAccount") {+ const { ticker, id } = existingSubAccount.token;+ if (!blacklistedTokenIds.includes(id)) {+ existingAccountTickers.push(ticker);+ existingAccountByTicker[ticker] = existingSubAccount;+ }+ }+ }+ }++ // filter by token existence+ promiseAllBatched(3, accountASA, async (asa) => {
misses a await to not have race condition and/or finishing earlier than expected the withLibcore scope which can have bad consequences on mobile.
i highly suspect this will not work on mobile and was tested only on cli/desktop which is not actually asynchronous.
comment created time in 21 hours
Pull request review commentLedgerHQ/ledger-live-common
+/********************************************************************************
i can deal with it after it's merged, but basically we should make a @ledgerhq/hw-app-algorand with what you have so the community can reuse / contribute.
comment created time in 21 hours
pull request commentLedgerHQ/ledger-live-desktop
LL-2885 Update link/wording for lostSeed link
@juan-cortes can you attach the new screenshot so it pass CI?
comment created time in a day
Pull request review commentLedgerHQ/ledger-live-desktop
LL-2685 Add buy cta to notEnoughGas
const Exchange = () => { </Box> <TabBar tabs={tabs.map(tab => t(tab.title))} onIndexChange={setActiveTabIndex} /> <Card grow style={{ overflow: "hidden" }}>- {tabs[activeTabIndex].component}+ <Component defaultCurrency={maybeDefaultCurrency} />
i think a more generic way would be to pass {...location?.state}
comment created time in a day
push eventLedgerHQ/ledger-live-desktop
commit sha 396fb631bea222cc7e66b2541f07d4d1cc11fb49
Fixed typo "displayed on your device"
push time in a day
Pull request review commentLedgerHQ/ledger-live-common
const stringParser = (v: mixed): ?string => const envDefinitions = { API_ALGORAND_BLOCKCHAIN_EXPLORER_API_ENDPOINT: {- def: "https://algorand.coin.staging.aws.ledger.com",+ def: "https://algorand.coin.ledger.com/",
extra slash?
comment created time in a day
push eventLedgerHQ/ledger-live-common
commit sha ac0fa48ca2b22ebf3fcd2f7be32e00e0d318f6d8
LL-2969 Change manager icon source
commit sha e1621db5d894aff03cb5ca1ad9e36c086fcb1fc4
Merge pull request #801 from juan-cortes/LL-2969 LL-2969 Change manager icon source
push time in a day
Pull request review commentLedgerHQ/ledger-live-desktop
added a new account selector component
+// @flow++import { useState, useCallback, useMemo } from "react";+import type { Account, SubAccount } from "@ledgerhq/live-common/lib/types/account";+import type { CryptoCurrency, TokenCurrency } from "@ledgerhq/live-common/lib/types/currencies";+import { BigNumber } from "bignumber.js";++type CryptoOrTokenCurrency = TokenCurrency | CryptoCurrency;++const generateTokenAccount = (account: Account, currency: TokenCurrency): SubAccount => ({+ type: "TokenAccount",+ id: account.id + "+" + currency.contractAddress,+ parentId: account.id,+ token: currency,+ balance: BigNumber(0),+ operationsCount: 0,+ creationDate: new Date(),+ operations: [],+ pendingOperations: [],+ starred: false,+});++export type AccountTuple = {+ account: ?Account,+ subAccount: ?SubAccount,+};++function getAccountTuplesForCurrency(+ currency: CryptoOrTokenCurrency,+ allAccounts: Account[],+): AccountTuple[] {+ if (currency.type === "TokenCurrency") {+ return allAccounts+ .filter(account => account.currency.id === currency.parentCurrency.id)+ .map(account => ({+ account,+ subAccount:+ (account.subAccounts &&+ account.subAccounts.find(+ (subAcc: SubAccount) =>+ subAcc.type === "TokenAccount" && subAcc.token.id === currency.id,+ )) ||+ generateTokenAccount(account, currency),+ }));+ }+ return allAccounts+ .filter(account => account.currency.id === currency.id)+ .map(account => ({+ account,+ subAccount: null,+ }));+}++export function useCurrencyAccountSelect(+ allCurrencies: CryptoOrTokenCurrency[],+ allAccounts: Account[],+) {+ const [state, setState] = useState(() => {+ const defaultCurrency = allCurrencies[0];+ const availableAccounts = getAccountTuplesForCurrency(defaultCurrency, allAccounts);+ const { account, subAccount } = availableAccounts.length+ ? availableAccounts[0]+ : { account: null, subAccount: null };++ return {+ currency: defaultCurrency,+ account,+ subAccount,+ };+ });++ const { currency, account, subAccount } = state;
when looking at the state currently involved, i think there can still be a case where allAccounts is more updated than the internal state. I mean, there is clearly two single source of trust regarding the reference to account
a solution to this problem is to do this instead:
const account = allAccounts.find(a => a.id===accountId)
const subAccount = (account && account.subAccounts || []).find(a => a.id===subAccountId)
where accountId and subAccountId would be what is actually stored as a state in this hook.
in case of Buy feature, you probably didn't see any issue because we don't care about account updates & it doesn't impact the feature. In case of swap, it's way more dangerous (e.g. balance can updates)
comment created time in 4 days
Pull request review commentLedgerHQ/ledger-live-mobile
add manager provider to experimental settings
const FeatureRow = ({ feature }: Props) => { return ( <SettingsRow event={`${feature.name}Row`}- title={feature.title}+ title={type === "integer" ? `${feature.title}` : feature.title}
mmh why is this needed?
comment created time in 4 days
Pull request review commentLedgerHQ/ledger-live-mobile
add manager provider to experimental settings
+// @flow++import React, { useCallback, useState, useEffect } from "react";+import { Switch, TextInput, StyleSheet } from "react-native";+import { getEnvDefault } from "@ledgerhq/live-common/lib/env";++import { View } from "react-native-animatable";+import Track from "../../../analytics/Track";+import colors from "../../../colors";++type Props = {+ name: *,+ isDefault: boolean,+ readOnly: boolean,+ onChange: (name: string, val: mixed) => boolean,+ value: number,+ minValue: number,+ maxValue: number,+};++const FeatureInteger = ({+ name,+ isDefault,+ readOnly,+ onChange,+ value,+ minValue,+ maxValue,+}: Props) => {+ const constraintValue = useCallback(+ v => {+ let value = v;+ if (typeof maxValue === "number" && parseInt(value, 10) > maxValue) {+ value = maxValue;+ }+ if (typeof minValue === "number" && parseInt(value, 10) < minValue) {+ value = minValue;+ }++ if (!value) {+ value = maxValue || minValue;
I don't really understand what this is doing. isn't the code above already doing this?
comment created time in 4 days
Pull request review commentLedgerHQ/ledger-live-mobile
add manager provider to experimental settings
export const NavigatorName = { Onboarding: "Onboarding", PasswordAddFlow: "PasswordAddFlow", PasswordModifyFlow: "PasswordModifyFlow",+ ManagerProdiver: "ManagerProdiver",
typo on the name
comment created time in 4 days
push eventLedgerHQ/ledger-live-common
commit sha ca3a91cfd1ddc0a616624d0df6de2ed077fc925f
Swap - Changes for mobile compatibility Also adapts cli to not break
commit sha 78aaff2e3860f476d666dadcdf59be7d0f955029
Swap - History export helper
commit sha 656f25927c844e08b82dcc8b91c0594f26458b9d
Merge pull request #798 from juan-cortes/swap-mobile-and-export Swap mobile compatibility and export operations
push time in 4 days
PR merged LedgerHQ/ledger-live-common
Introduces the changes needed to the initSwap device action and implements the export operations feature that will be triggered from desktop. This is the result of the peer programming done with @gre that identified a mismatch between the action and command signatures. It also introduces the fix to make cli swap command work again.
pr closed time in 4 days
Pull request review commentLedgerHQ/ledger-live-common
+// @flow++import type { TokenCurrency } from "../../types";+import { getCryptoCurrencyById } from "../../data/cryptocurrencies";+import { addTokens } from "../../data/tokens";++type TokenType = "asa";+
this file sounds the proper place to add a extractTokenId helper
comment created time in 4 days
Pull request review commentLedgerHQ/ledger-live-common
export default (arg: { if (process.env.VERBOSE) { log("libcore-call", id + "." + method, args); }- const constructorArgs =- typeof njsBuggyMethodIsNotStatic === "function"- ? njsBuggyMethodIsNotStatic(args)- : args;- const value = new m(...constructorArgs)[method](...args);+ let value;
mmh why is this now changing?
comment created time in 4 days
Pull request review commentLedgerHQ/ledger-live-common
+// @flow+import expect from "expect";+import invariant from "invariant";+import type { Transaction } from "./types";+import { getCryptoCurrencyById, parseCurrencyUnit } from "../../currencies";+import { isAccountEmpty } from "../../account";+import { pickSiblings } from "../../bot/specs";+import type { AppSpec } from "../../bot/types";++const currency = getCryptoCurrencyById("algorand");++// Minimum fees+const minFees = parseCurrencyUnit(currency.units[0], "0.001");++// Minimum balance required for a new non-ASA account+const minBalanceNewAccount = parseCurrencyUnit(currency.units[0], "0.1");++// Minimum balance for a non-ASA account+let getMinBalance = (account) => {+ const minBalance = parseCurrencyUnit(currency.units[0], "0.1");+ const numberOfTokens = account.subAccounts+ ? account.subAccounts.filter((a) => a.type === "TokenAccount").length+ : 0;+ return minBalance.multipliedBy(1 + numberOfTokens);+};++// Spendable balance for a non-ASA account+let getSpendableBalance = (maxSpendable) => {+ maxSpendable = maxSpendable.minus(minFees);+ invariant(maxSpendable.gt(0), "spendable balance is too low");+ return maxSpendable;+};++// Ensure that, when the recipient corresponds to an empty account,+// the amount to send is greater or equal to the required minimum+// balance for such a recipient+let checkSendableToEmptyAccount = (amount, recipient) => {+ if (isAccountEmpty(recipient) && amount.lte(minBalanceNewAccount)) {+ invariant(+ amount.gt(minBalanceNewAccount),+ "not enough funds to send to new account"+ );+ }+};++// Extract asset ID from asset-type subaccount+let extractAssetId = (subaccount) => {+ if (subaccount.type !== "TokenAccount") {+ return null;+ }++ const assetIdRegex = /algorand\/asa\/(\d+)/g; // e.g, 'algorand/asa/312769'
this is yet another logic to extract out the "id". this really must be factorized into a function
comment created time in 4 days
Pull request review commentLedgerHQ/ledger-live-common
+// @flow+import expect from "expect";+import invariant from "invariant";+import type { Transaction } from "./types";+import { getCryptoCurrencyById, parseCurrencyUnit } from "../../currencies";+import { isAccountEmpty } from "../../account";+import { pickSiblings } from "../../bot/specs";+import type { AppSpec } from "../../bot/types";++const currency = getCryptoCurrencyById("algorand");++// Minimum fees+const minFees = parseCurrencyUnit(currency.units[0], "0.001");++// Minimum balance required for a new non-ASA account+const minBalanceNewAccount = parseCurrencyUnit(currency.units[0], "0.1");++// Minimum balance for a non-ASA account+let getMinBalance = (account) => {+ const minBalance = parseCurrencyUnit(currency.units[0], "0.1");+ const numberOfTokens = account.subAccounts+ ? account.subAccounts.filter((a) => a.type === "TokenAccount").length+ : 0;+ return minBalance.multipliedBy(1 + numberOfTokens);+};++// Spendable balance for a non-ASA account+let getSpendableBalance = (maxSpendable) => {+ maxSpendable = maxSpendable.minus(minFees);+ invariant(maxSpendable.gt(0), "spendable balance is too low");+ return maxSpendable;+};++// Ensure that, when the recipient corresponds to an empty account,+// the amount to send is greater or equal to the required minimum+// balance for such a recipient+let checkSendableToEmptyAccount = (amount, recipient) => {+ if (isAccountEmpty(recipient) && amount.lte(minBalanceNewAccount)) {+ invariant(+ amount.gt(minBalanceNewAccount),+ "not enough funds to send to new account"+ );+ }+};++// Extract asset ID from asset-type subaccount+let extractAssetId = (subaccount) => {+ if (subaccount.type !== "TokenAccount") {+ return null;+ }++ const assetIdRegex = /algorand\/asa\/(\d+)/g; // e.g, 'algorand/asa/312769'+ const match = assetIdRegex.exec(subaccount.token.id);+ return match !== null ? match[1] : null;+};++// Get list of ASAs associated with the account+let getAssets = (account) => {+ return account.subAccounts+ ? account.subAccounts.filter((a) => a.type === "TokenAccount")+ : [];+};++// Get list of ASAs IDs common between two accounts (intersection)+let getCommonAssetsIds = (senderAccount, recipientAccount) => {+ const senderAssetsIds = getAssets(senderAccount)+ .filter((a) => a.balance.gt(0))+ .map((a) => extractAssetId(a));++ const recipientAssetsIds = getAssets(recipientAccount).map((a) =>+ extractAssetId(a)+ );++ return senderAssetsIds.filter((assetId) =>+ recipientAssetsIds.includes(assetId)+ );+};++// Get Subaccount ID+// eslint-disable-next-line no-unused-vars+let getSubaccountId = (account, assetId) => {+ if (account.subAccounts == null) return null;++ account.subAccounts.forEach((subaccount) => {+ if (subaccount.id.endsWith(assetId)) {+ return subaccount.id; // e.g., libcore:1:algorand:fef9 . . . 4d7af:+algorand/asa/342836+ }+ });+};++const algorand: AppSpec<Transaction> = {+ name: "Algorand",+ currency,+ appQuery: {+ model: "nanoS",+ appName: "Algorand",+ },+ mutations: [+ {+ name: "move ~50%",+ maxRun: 2,+ transaction: ({ account, siblings, bridge, maxSpendable }) => {+ const spendableBalance = getSpendableBalance(maxSpendable);++ const sibling = pickSiblings(siblings, 4);+ const recipient = sibling.freshAddress;++ let transaction = bridge.createTransaction(account);++ let amount = spendableBalance+ .div(1.9 + 0.2 * Math.random())+ .integerValue();++ checkSendableToEmptyAccount(amount, sibling);++ const updates = [{ amount }, { recipient }];+ return {+ transaction,+ updates,+ };+ },+ test: ({ account, accountBeforeTransaction, operation }) => {+ expect(account.balance.toString()).toBe(+ accountBeforeTransaction.balance.minus(operation.value).toString()+ );+ },+ },+ {+ name: "send max",+ maxRun: 1,+ transaction: ({ account, siblings, bridge, maxSpendable }) => {+ const spendableBalance = getSpendableBalance(maxSpendable);+ const sibling = pickSiblings(siblings, 4);++ // Send the full spendable balance+ const amount = spendableBalance;++ checkSendableToEmptyAccount(amount, sibling);++ return {+ transaction: bridge.createTransaction(account),+ updates: [+ {+ recipient: pickSiblings(siblings, 30).freshAddress,+ },+ { useAllAmount: true },+ ],+ };+ },+ test: ({ account }) => {+ const minBalance = getMinBalance(account);
correct me if i'm wrong but we shouldn't have to do this "min balance", that logic that is included in getMinBalance should be implemented as part of the account.spendableBalance. is it not the case?
comment created time in 4 days
Pull request review commentLedgerHQ/ledger-live-common
+// @flow+import expect from "expect";+import invariant from "invariant";+import type { Transaction } from "./types";+import { getCryptoCurrencyById, parseCurrencyUnit } from "../../currencies";+import { isAccountEmpty } from "../../account";+import { pickSiblings } from "../../bot/specs";+import type { AppSpec } from "../../bot/types";++const currency = getCryptoCurrencyById("algorand");++// Minimum fees+const minFees = parseCurrencyUnit(currency.units[0], "0.001");++// Minimum balance required for a new non-ASA account+const minBalanceNewAccount = parseCurrencyUnit(currency.units[0], "0.1");++// Minimum balance for a non-ASA account+let getMinBalance = (account) => {+ const minBalance = parseCurrencyUnit(currency.units[0], "0.1");+ const numberOfTokens = account.subAccounts+ ? account.subAccounts.filter((a) => a.type === "TokenAccount").length+ : 0;+ return minBalance.multipliedBy(1 + numberOfTokens);+};++// Spendable balance for a non-ASA account+let getSpendableBalance = (maxSpendable) => {+ maxSpendable = maxSpendable.minus(minFees);+ invariant(maxSpendable.gt(0), "spendable balance is too low");+ return maxSpendable;+};++// Ensure that, when the recipient corresponds to an empty account,+// the amount to send is greater or equal to the required minimum+// balance for such a recipient+let checkSendableToEmptyAccount = (amount, recipient) => {+ if (isAccountEmpty(recipient) && amount.lte(minBalanceNewAccount)) {+ invariant(+ amount.gt(minBalanceNewAccount),+ "not enough funds to send to new account"+ );+ }+};++// Extract asset ID from asset-type subaccount+let extractAssetId = (subaccount) => {+ if (subaccount.type !== "TokenAccount") {+ return null;+ }++ const assetIdRegex = /algorand\/asa\/(\d+)/g; // e.g, 'algorand/asa/312769'+ const match = assetIdRegex.exec(subaccount.token.id);+ return match !== null ? match[1] : null;+};++// Get list of ASAs associated with the account+let getAssets = (account) => {+ return account.subAccounts+ ? account.subAccounts.filter((a) => a.type === "TokenAccount")+ : [];+};++// Get list of ASAs IDs common between two accounts (intersection)+let getCommonAssetsIds = (senderAccount, recipientAccount) => {+ const senderAssetsIds = getAssets(senderAccount)+ .filter((a) => a.balance.gt(0))+ .map((a) => extractAssetId(a));++ const recipientAssetsIds = getAssets(recipientAccount).map((a) =>+ extractAssetId(a)+ );++ return senderAssetsIds.filter((assetId) =>+ recipientAssetsIds.includes(assetId)+ );+};++// Get Subaccount ID+// eslint-disable-next-line no-unused-vars+let getSubaccountId = (account, assetId) => {+ if (account.subAccounts == null) return null;++ account.subAccounts.forEach((subaccount) => {+ if (subaccount.id.endsWith(assetId)) {+ return subaccount.id; // e.g., libcore:1:algorand:fef9 . . . 4d7af:+algorand/asa/342836
this can't work inside a forEach 🤔
.find is what you need
comment created time in 4 days
Pull request review commentLedgerHQ/ledger-live-common
+// @flow+import expect from "expect";+import invariant from "invariant";+import type { Transaction } from "./types";+import { getCryptoCurrencyById, parseCurrencyUnit } from "../../currencies";+import { isAccountEmpty } from "../../account";+import { pickSiblings } from "../../bot/specs";+import type { AppSpec } from "../../bot/types";++const currency = getCryptoCurrencyById("algorand");++// Minimum fees+const minFees = parseCurrencyUnit(currency.units[0], "0.001");++// Minimum balance required for a new non-ASA account+const minBalanceNewAccount = parseCurrencyUnit(currency.units[0], "0.1");++// Minimum balance for a non-ASA account+let getMinBalance = (account) => {+ const minBalance = parseCurrencyUnit(currency.units[0], "0.1");+ const numberOfTokens = account.subAccounts+ ? account.subAccounts.filter((a) => a.type === "TokenAccount").length+ : 0;+ return minBalance.multipliedBy(1 + numberOfTokens);+};++// Spendable balance for a non-ASA account+let getSpendableBalance = (maxSpendable) => {+ maxSpendable = maxSpendable.minus(minFees);+ invariant(maxSpendable.gt(0), "spendable balance is too low");+ return maxSpendable;+};++// Ensure that, when the recipient corresponds to an empty account,+// the amount to send is greater or equal to the required minimum+// balance for such a recipient+let checkSendableToEmptyAccount = (amount, recipient) => {+ if (isAccountEmpty(recipient) && amount.lte(minBalanceNewAccount)) {+ invariant(+ amount.gt(minBalanceNewAccount),+ "not enough funds to send to new account"+ );+ }+};++// Extract asset ID from asset-type subaccount+let extractAssetId = (subaccount) => {+ if (subaccount.type !== "TokenAccount") {+ return null;+ }++ const assetIdRegex = /algorand\/asa\/(\d+)/g; // e.g, 'algorand/asa/312769'+ const match = assetIdRegex.exec(subaccount.token.id);+ return match !== null ? match[1] : null;+};++// Get list of ASAs associated with the account+let getAssets = (account) => {+ return account.subAccounts+ ? account.subAccounts.filter((a) => a.type === "TokenAccount")+ : [];+};++// Get list of ASAs IDs common between two accounts (intersection)+let getCommonAssetsIds = (senderAccount, recipientAccount) => {+ const senderAssetsIds = getAssets(senderAccount)+ .filter((a) => a.balance.gt(0))+ .map((a) => extractAssetId(a));++ const recipientAssetsIds = getAssets(recipientAccount).map((a) =>+ extractAssetId(a)+ );++ return senderAssetsIds.filter((assetId) =>+ recipientAssetsIds.includes(assetId)+ );+};++// Get Subaccount ID+// eslint-disable-next-line no-unused-vars+let getSubaccountId = (account, assetId) => {+ if (account.subAccounts == null) return null;++ account.subAccounts.forEach((subaccount) => {+ if (subaccount.id.endsWith(assetId)) {+ return subaccount.id; // e.g., libcore:1:algorand:fef9 . . . 4d7af:+algorand/asa/342836+ }+ });+};++const algorand: AppSpec<Transaction> = {+ name: "Algorand",+ currency,+ appQuery: {+ model: "nanoS",+ appName: "Algorand",+ },+ mutations: [+ {+ name: "move ~50%",+ maxRun: 2,+ transaction: ({ account, siblings, bridge, maxSpendable }) => {+ const spendableBalance = getSpendableBalance(maxSpendable);++ const sibling = pickSiblings(siblings, 4);+ const recipient = sibling.freshAddress;++ let transaction = bridge.createTransaction(account);++ let amount = spendableBalance+ .div(1.9 + 0.2 * Math.random())+ .integerValue();++ checkSendableToEmptyAccount(amount, sibling);++ const updates = [{ amount }, { recipient }];+ return {+ transaction,+ updates,+ };+ },+ test: ({ account, accountBeforeTransaction, operation }) => {+ expect(account.balance.toString()).toBe(+ accountBeforeTransaction.balance.minus(operation.value).toString()+ );+ },+ },+ {+ name: "send max",+ maxRun: 1,+ transaction: ({ account, siblings, bridge, maxSpendable }) => {+ const spendableBalance = getSpendableBalance(maxSpendable);+ const sibling = pickSiblings(siblings, 4);++ // Send the full spendable balance+ const amount = spendableBalance;++ checkSendableToEmptyAccount(amount, sibling);++ return {+ transaction: bridge.createTransaction(account),+ updates: [+ {+ recipient: pickSiblings(siblings, 30).freshAddress,
shouldn't it be sibling.freshAddress ?
comment created time in 4 days
Pull request review commentLedgerHQ/ledger-live-common
+// @flow+import expect from "expect";+import invariant from "invariant";+import type { Transaction } from "./types";+import { getCryptoCurrencyById, parseCurrencyUnit } from "../../currencies";+import { isAccountEmpty } from "../../account";+import { pickSiblings } from "../../bot/specs";+import type { AppSpec } from "../../bot/types";++const currency = getCryptoCurrencyById("algorand");++// Minimum fees+const minFees = parseCurrencyUnit(currency.units[0], "0.001");++// Minimum balance required for a new non-ASA account+const minBalanceNewAccount = parseCurrencyUnit(currency.units[0], "0.1");++// Minimum balance for a non-ASA account+let getMinBalance = (account) => {+ const minBalance = parseCurrencyUnit(currency.units[0], "0.1");+ const numberOfTokens = account.subAccounts+ ? account.subAccounts.filter((a) => a.type === "TokenAccount").length+ : 0;+ return minBalance.multipliedBy(1 + numberOfTokens);+};++// Spendable balance for a non-ASA account+let getSpendableBalance = (maxSpendable) => {+ maxSpendable = maxSpendable.minus(minFees);+ invariant(maxSpendable.gt(0), "spendable balance is too low");+ return maxSpendable;+};++// Ensure that, when the recipient corresponds to an empty account,+// the amount to send is greater or equal to the required minimum+// balance for such a recipient+let checkSendableToEmptyAccount = (amount, recipient) => {+ if (isAccountEmpty(recipient) && amount.lte(minBalanceNewAccount)) {+ invariant(+ amount.gt(minBalanceNewAccount),+ "not enough funds to send to new account"+ );+ }+};++// Extract asset ID from asset-type subaccount+let extractAssetId = (subaccount) => {+ if (subaccount.type !== "TokenAccount") {+ return null;+ }++ const assetIdRegex = /algorand\/asa\/(\d+)/g; // e.g, 'algorand/asa/312769'+ const match = assetIdRegex.exec(subaccount.token.id);+ return match !== null ? match[1] : null;+};++// Get list of ASAs associated with the account+let getAssets = (account) => {+ return account.subAccounts+ ? account.subAccounts.filter((a) => a.type === "TokenAccount")+ : [];+};++// Get list of ASAs IDs common between two accounts (intersection)+let getCommonAssetsIds = (senderAccount, recipientAccount) => {+ const senderAssetsIds = getAssets(senderAccount)+ .filter((a) => a.balance.gt(0))+ .map((a) => extractAssetId(a));++ const recipientAssetsIds = getAssets(recipientAccount).map((a) =>+ extractAssetId(a)+ );++ return senderAssetsIds.filter((assetId) =>+ recipientAssetsIds.includes(assetId)+ );+};++// Get Subaccount ID+// eslint-disable-next-line no-unused-vars
this should not be needed
comment created time in 4 days
Pull request review commentLedgerHQ/ledger-live-common
+// @flow+import { FeeNotLoaded } from "@ledgerhq/errors";+import type { Account } from "../../types";+import { libcoreAmountToBigNumber } from "../../libcore/buildBigNumber";+import type { Core, CoreAccount } from "../../libcore/types";+import type { CoreAlgorandTransaction, Transaction } from "./types";++const setInfo = async (+ core,+ buildedTransaction,+ transaction,+ subAccount,+ account,+ isPartial+) => {+ const { amount, recipient, assetId, mode, useAllAmount } = transaction;+ if (subAccount || (assetId && mode === "optIn")) {+ let targetAssetId =+ subAccount && subAccount.type === "TokenAccount"+ ? subAccount.token.id.split("/")[2]+ : assetId?.split("/")[2] || assetId;++ if (targetAssetId === "" || isNaN(parseInt(targetAssetId))) {
why would the NaN case happen? 🤔 we need to sanitize tokens in the first place instead because to me this check should not be needed
comment created time in 4 days
Pull request review commentLedgerHQ/ledger-live-common
+// @flow++import type {+ CryptoCurrency,+ TokenAccount,+ Account,+ SyncConfig,+} from "../../types";+import type { CoreAccount } from "../../libcore/types";+import { minimalOperationsBuilder } from "../../reconciliation";+import { buildASAOperation } from "./buildASAOperation";+import { BigNumber } from "bignumber.js";+import { findTokenById, listTokensForCryptoCurrency } from "../../currencies";++const OperationOrderKey = {+ date: 0,+};++async function buildAlgorandTokenAccount({+ parentAccountId,+ token,+ coreAccount,+ existingTokenAccount,+ balance,+}) {+ const id = parentAccountId + "+" + token.id;+ const getAllOperations = async () => {+ const query = await coreAccount.queryOperations();+ await query.complete();+ await query.addOrder(OperationOrderKey.date, false);+ const coreOperations = await query.execute();++ const operations = await minimalOperationsBuilder(+ (existingTokenAccount && existingTokenAccount.operations) || [],+ coreOperations,+ (coreOperation) =>+ buildASAOperation({+ coreOperation,+ accountId: id,+ })+ );++ return operations;+ };++ const operations = await getAllOperations();++ const tokenAccount: $Exact<TokenAccount> = {+ type: "TokenAccount",+ id,+ parentId: parentAccountId,+ starred: false,+ token,+ operationsCount: operations.length,+ operations,+ pendingOperations: [],+ balance,+ creationDate:+ operations.length > 0+ ? operations[operations.length - 1].date+ : new Date(),+ };++ return tokenAccount;+}++async function algorandBuildTokenAccounts({+ currency,+ coreAccount,+ accountId,+ existingAccount,+ syncConfig,+}: {+ currency: CryptoCurrency,+ coreAccount: CoreAccount,+ accountId: string,+ existingAccount: ?Account,+ syncConfig: SyncConfig,+}): Promise<?(TokenAccount[])> {+ const { blacklistedTokenIds = [] } = syncConfig;+ if (listTokensForCryptoCurrency(currency).length === 0) return undefined;+ const tokenAccounts = [];+ const algorandAccount = await coreAccount.asAlgorandAccount();+ const accountASA = await algorandAccount.getAssetsBalances();++ const existingAccountByTicker = {}; // used for fast lookup+ const existingAccountTickers = []; // used to keep track of ordering+ if (existingAccount && existingAccount.subAccounts) {+ for (const existingSubAccount of existingAccount.subAccounts) {+ if (existingSubAccount.type === "TokenAccount") {+ const { ticker, id } = existingSubAccount.token;+ if (!blacklistedTokenIds.includes(id)) {+ existingAccountTickers.push(ticker);+ existingAccountByTicker[ticker] = existingSubAccount;+ }+ }+ }+ }++ // filter by token existence+ accountASA.map(async (asa) => {
- ⚠️ the async functions that happen inside the map is never awaited.
- please use
promiseAllBatched
comment created time in 4 days
Pull request review commentLedgerHQ/ledger-live-common
+// @flow+import { FeeNotLoaded } from "@ledgerhq/errors";+import type { Account } from "../../types";+import { libcoreAmountToBigNumber } from "../../libcore/buildBigNumber";+import type { Core, CoreAccount } from "../../libcore/types";+import type { CoreAlgorandTransaction, Transaction } from "./types";++const setInfo = async (+ core,+ buildedTransaction,+ transaction,+ subAccount,+ account,+ isPartial+) => {+ const { amount, recipient, assetId, mode, useAllAmount } = transaction;+ if (subAccount || (assetId && mode === "optIn")) {+ let targetAssetId =+ subAccount && subAccount.type === "TokenAccount"+ ? subAccount.token.id.split("/")[2]+ : assetId?.split("/")[2] || assetId;
all these .split("/")[2] magic should be shared in a helper that decode a token id.
comment created time in 4 days
Pull request review commentLedgerHQ/ledger-live-common
+/********************************************************************************
is there plan to contribute back to ledgerjs repo?
comment created time in 4 days
Pull request review commentLedgerHQ/ledger-live-common
+// @flow+import { BigNumber } from "bignumber.js";+import {+ AmountRequired,+ InvalidAddressBecauseDestinationIsAlsoSource,+ NotEnoughBalance,+ FeeNotLoaded,+} from "@ledgerhq/errors";+import {+ AlgorandASANotOptInInRecipient,+ AlgorandNoAssetId,+} from "../../../errors";+import { validateRecipient } from "../../../bridge/shared";+import type { AccountBridge, CurrencyBridge, Account } from "../../../types";+import type { Transaction } from "../types";+import { AlgorandOperationTypeEnum } from "../types";+import { scanAccounts } from "../../../libcore/scanAccounts";+import { sync } from "../../../libcore/syncAccount";+import type { CacheRes } from "../../../cache";+import { makeLRUCache } from "../../../cache";+import { getMainAccount } from "../../../account";+import broadcast from "../libcore-broadcast";+import signOperation from "../libcore-signOperation";+import { getFeesForTransaction } from "../../../libcore/getFeesForTransaction";+import { withLibcore } from "../../../libcore/access";+import { getCoreAccount } from "../../../libcore/getCoreAccount";+import { libcoreAmountToBigNumber } from "../../../libcore/buildBigNumber";++const MAX_MEMO_SIZE = 32;++export const calculateFees: CacheRes<+ Array<{ a: Account, t: Transaction }>,+ { estimatedFees: BigNumber, estimatedGas: ?BigNumber }+> = makeLRUCache(+ async ({+ a,+ t,+ }): Promise<{ estimatedFees: BigNumber, estimatedGas: ?BigNumber }> => {+ return await getFeesForTransaction({+ account: a,+ transaction: t,+ });+ },+ ({ a, t }) =>+ `${a.id}_${t.amount.toString()}_${t.recipient}_${String(t.useAllAmount)}_${+ t.memo ? t.memo.toString() : ""+ }`+);++const getSpendableMaxForOptIn = async (account) =>+ await withLibcore(async (core) => {+ const { coreAccount } = await getCoreAccount(core, account);++ const algorandAccount = await coreAccount.asAlgorandAccount();+ const spendableBalanceBigInt = await algorandAccount.getSpendableBalance(+ AlgorandOperationTypeEnum.ASSET_OPT_IN+ );+ const spendableBalance = await libcoreAmountToBigNumber(+ spendableBalanceBigInt+ );++ return spendableBalance;+ });++const createTransaction = () => ({+ family: "algorand",+ amount: BigNumber(0),+ fees: null,+ recipient: "",+ useAllAmount: false,+ memo: null,+ mode: "send",+ assetId: null,+});++const updateTransaction = (t, patch) => {+ return { ...t, ...patch };+};++const recipientHasAsset = async (assetId, recipient, account) =>+ await withLibcore(async (core) => {+ const { coreAccount } = await getCoreAccount(core, account);++ const algorandAccount = await coreAccount.asAlgorandAccount();+ const hasAsset = await algorandAccount.hasAsset(recipient, assetId);++ return hasAsset;+ });+/*+ * Here are the list of the differents things we check+ * - Check if recipient is the same in case of send+ * - Check if recipient is valid+ * - Check if amounts are set+ * - Check if fees are loaded+ * - Check if is a send Max and set the amount+ * - Check if Token is already optin at the recipient+ * - Check if memo is too long+ */+const getTransactionStatus = async (a: Account, t) => {+ const errors = {};+ const warnings = {};+ const tokenAccount = !t.subAccountId+ ? null+ : a.subAccounts && a.subAccounts.find((ta) => ta.id === t.subAccountId);++ if (t.mode === "send" && a.freshAddress === t.recipient) {+ errors.recipient = new InvalidAddressBecauseDestinationIsAlsoSource();+ } else {+ const { recipientError, recipientWarning } = await validateRecipient(+ a.currency,+ t.recipient+ );++ if (recipientError) {+ errors.recipient = recipientError;+ }++ if (recipientWarning) {+ warnings.recipient = recipientWarning;+ }+ }++ let estimatedFees = t.fees || BigNumber(0);+ let amount = t.amount;+ let totalSpent = estimatedFees;++ switch (t.mode) {+ case "send": {+ if (amount.lte(0) && !t.useAllAmount) {+ errors.amount = new AmountRequired();+ }++ if (!t.fees || !t.fees.gt(0)) {+ errors.fees = new FeeNotLoaded();+ }++ if (+ tokenAccount &&+ !(await recipientHasAsset(+ tokenAccount.id.split("/")[2],+ t.recipient,+ a+ ))+ ) {+ errors.recipient = new AlgorandASANotOptInInRecipient();+ }++ amount = t.useAllAmount+ ? tokenAccount+ ? tokenAccount.balance+ : a.spendableBalance.minus(estimatedFees)+ : amount;++ if (amount.lt(0)) {+ amount = BigNumber(0);+ }++ totalSpent = tokenAccount ? amount : amount.plus(estimatedFees);++ if (+ (amount.lte(0) && t.useAllAmount) || // if use all Amount sets an amount at 0+ (tokenAccount && a.spendableBalance.lt(estimatedFees)) || // if spendable balance lower than fees for token+ (!errors.recipient && !errors.amount && tokenAccount+ ? totalSpent.gt(tokenAccount.balance)+ : totalSpent.gt(a.spendableBalance)) // if spendable balance lower than total+ ) {+ errors.amount = new NotEnoughBalance();+ }+ break;+ }++ case "optIn": {+ if (!t.assetId) {+ errors.assetId = new AlgorandNoAssetId();+ }+ const spendableBalance = await getSpendableMaxForOptIn(a);+ if (spendableBalance.lt(estimatedFees)) {+ errors.amount = new NotEnoughBalance();+ }+ break;+ }++ case "claimReward": {+ if (a.spendableBalance.lt(totalSpent)) {+ errors.amount = new NotEnoughBalance();+ }+ break;+ }+ }++ if (t.memo && t.memo.length > MAX_MEMO_SIZE) {+ throw new Error("Memo is too long");+ }++ return Promise.resolve({+ errors,+ warnings,+ estimatedFees,+ amount,+ totalSpent,+ });+};++const sameFees = (a, b) => (!a || !b ? a === b : a.eq(b));++const prepareTransaction = async (a, t) => {+ let fees = t.fees;+ let amount = t.amount;+ let recipient = t.recipient;++ if (t.mode === "optIn" || t.mode === "claimReward") {+ recipient = a.freshAddress;+ amount = BigNumber(0);+ }++ if (recipient || t.mode !== "send") {+ const errors = (await validateRecipient(a.currency, recipient))+ .recipientError;+ if (!errors) {+ const res = await calculateFees({+ a,+ t,+ });++ fees = res.estimatedFees;+ }+ }++ if (+ !sameFees(t.fees, fees) ||+ !sameFees(t.amount, amount) ||+ t.recipient !== recipient+ ) {+ return { ...t, fees, amount, recipient };+ }++ return t;+};++const estimateMaxSpendable = async ({+ account,+ parentAccount,+ transaction,+}) => {+ const mainAccount = getMainAccount(account, parentAccount);+ const t = await prepareTransaction(mainAccount, {+ ...createTransaction(),+ subAccountId: account.type === "Account" ? null : account.id,+ recipient: "fakeAddressToBeDetermined",
you can take address of
SEED="abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about" ledger-live sync -c algorand -d speculos:nanos:algorand
but also, import it from src/data/abandonseed.js
comment created time in 4 days
Pull request review commentLedgerHQ/ledger-live-common
+// @flow+import { BigNumber } from "bignumber.js";+import {+ AmountRequired,+ InvalidAddressBecauseDestinationIsAlsoSource,+ NotEnoughBalance,+ FeeNotLoaded,+} from "@ledgerhq/errors";+import {+ AlgorandASANotOptInInRecipient,+ AlgorandNoAssetId,+} from "../../../errors";+import { validateRecipient } from "../../../bridge/shared";+import type { AccountBridge, CurrencyBridge, Account } from "../../../types";+import type { Transaction } from "../types";+import { AlgorandOperationTypeEnum } from "../types";+import { scanAccounts } from "../../../libcore/scanAccounts";+import { sync } from "../../../libcore/syncAccount";+import type { CacheRes } from "../../../cache";+import { makeLRUCache } from "../../../cache";+import { getMainAccount } from "../../../account";+import broadcast from "../libcore-broadcast";+import signOperation from "../libcore-signOperation";+import { getFeesForTransaction } from "../../../libcore/getFeesForTransaction";+import { withLibcore } from "../../../libcore/access";+import { getCoreAccount } from "../../../libcore/getCoreAccount";+import { libcoreAmountToBigNumber } from "../../../libcore/buildBigNumber";++const MAX_MEMO_SIZE = 32;++export const calculateFees: CacheRes<+ Array<{ a: Account, t: Transaction }>,+ { estimatedFees: BigNumber, estimatedGas: ?BigNumber }+> = makeLRUCache(+ async ({+ a,+ t,+ }): Promise<{ estimatedFees: BigNumber, estimatedGas: ?BigNumber }> => {+ return await getFeesForTransaction({+ account: a,+ transaction: t,+ });+ },+ ({ a, t }) =>+ `${a.id}_${t.amount.toString()}_${t.recipient}_${String(t.useAllAmount)}_${+ t.memo ? t.memo.toString() : ""+ }`+);++const getSpendableMaxForOptIn = async (account) =>+ await withLibcore(async (core) => {+ const { coreAccount } = await getCoreAccount(core, account);++ const algorandAccount = await coreAccount.asAlgorandAccount();+ const spendableBalanceBigInt = await algorandAccount.getSpendableBalance(+ AlgorandOperationTypeEnum.ASSET_OPT_IN+ );+ const spendableBalance = await libcoreAmountToBigNumber(+ spendableBalanceBigInt+ );++ return spendableBalance;+ });++const createTransaction = () => ({+ family: "algorand",+ amount: BigNumber(0),+ fees: null,+ recipient: "",+ useAllAmount: false,+ memo: null,+ mode: "send",+ assetId: null,+});++const updateTransaction = (t, patch) => {+ return { ...t, ...patch };+};++const recipientHasAsset = async (assetId, recipient, account) =>+ await withLibcore(async (core) => {+ const { coreAccount } = await getCoreAccount(core, account);++ const algorandAccount = await coreAccount.asAlgorandAccount();+ const hasAsset = await algorandAccount.hasAsset(recipient, assetId);++ return hasAsset;+ });+/*+ * Here are the list of the differents things we check+ * - Check if recipient is the same in case of send+ * - Check if recipient is valid+ * - Check if amounts are set+ * - Check if fees are loaded+ * - Check if is a send Max and set the amount+ * - Check if Token is already optin at the recipient+ * - Check if memo is too long+ */+const getTransactionStatus = async (a: Account, t) => {+ const errors = {};+ const warnings = {};+ const tokenAccount = !t.subAccountId+ ? null+ : a.subAccounts && a.subAccounts.find((ta) => ta.id === t.subAccountId);++ if (t.mode === "send" && a.freshAddress === t.recipient) {+ errors.recipient = new InvalidAddressBecauseDestinationIsAlsoSource();+ } else {+ const { recipientError, recipientWarning } = await validateRecipient(+ a.currency,+ t.recipient+ );++ if (recipientError) {+ errors.recipient = recipientError;+ }++ if (recipientWarning) {+ warnings.recipient = recipientWarning;+ }+ }++ let estimatedFees = t.fees || BigNumber(0);+ let amount = t.amount;+ let totalSpent = estimatedFees;++ switch (t.mode) {+ case "send": {+ if (amount.lte(0) && !t.useAllAmount) {+ errors.amount = new AmountRequired();+ }++ if (!t.fees || !t.fees.gt(0)) {+ errors.fees = new FeeNotLoaded();+ }++ if (+ tokenAccount &&+ !(await recipientHasAsset(+ tokenAccount.id.split("/")[2],+ t.recipient,+ a+ ))+ ) {+ errors.recipient = new AlgorandASANotOptInInRecipient();+ }++ amount = t.useAllAmount+ ? tokenAccount+ ? tokenAccount.balance+ : a.spendableBalance.minus(estimatedFees)+ : amount;++ if (amount.lt(0)) {+ amount = BigNumber(0);+ }++ totalSpent = tokenAccount ? amount : amount.plus(estimatedFees);++ if (+ (amount.lte(0) && t.useAllAmount) || // if use all Amount sets an amount at 0+ (tokenAccount && a.spendableBalance.lt(estimatedFees)) || // if spendable balance lower than fees for token+ (!errors.recipient && !errors.amount && tokenAccount+ ? totalSpent.gt(tokenAccount.balance)+ : totalSpent.gt(a.spendableBalance)) // if spendable balance lower than total+ ) {+ errors.amount = new NotEnoughBalance();+ }+ break;+ }++ case "optIn": {+ if (!t.assetId) {+ errors.assetId = new AlgorandNoAssetId();
to be confirmed, but to me this looks like more an invariant that a possible case that the UI will ever allow. So we should turn into invariant and no need for custom error.
comment created time in 4 days
Pull request review commentLedgerHQ/ledger-live-common
+// @flow+import { BigNumber } from "bignumber.js";+import {+ AmountRequired,+ InvalidAddressBecauseDestinationIsAlsoSource,+ NotEnoughBalance,+ FeeNotLoaded,+} from "@ledgerhq/errors";+import {+ AlgorandASANotOptInInRecipient,+ AlgorandNoAssetId,+} from "../../../errors";+import { validateRecipient } from "../../../bridge/shared";+import type { AccountBridge, CurrencyBridge, Account } from "../../../types";+import type { Transaction } from "../types";+import { AlgorandOperationTypeEnum } from "../types";+import { scanAccounts } from "../../../libcore/scanAccounts";+import { sync } from "../../../libcore/syncAccount";+import type { CacheRes } from "../../../cache";+import { makeLRUCache } from "../../../cache";+import { getMainAccount } from "../../../account";+import broadcast from "../libcore-broadcast";+import signOperation from "../libcore-signOperation";+import { getFeesForTransaction } from "../../../libcore/getFeesForTransaction";+import { withLibcore } from "../../../libcore/access";+import { getCoreAccount } from "../../../libcore/getCoreAccount";+import { libcoreAmountToBigNumber } from "../../../libcore/buildBigNumber";++const MAX_MEMO_SIZE = 32;
this need to be exported to the UI.
comment created time in 4 days
Pull request review commentLedgerHQ/ledger-live-common
+// @flow+import { BigNumber } from "bignumber.js";+import {+ AmountRequired,+ InvalidAddressBecauseDestinationIsAlsoSource,+ NotEnoughBalance,+ FeeNotLoaded,+} from "@ledgerhq/errors";+import {+ AlgorandASANotOptInInRecipient,+ AlgorandNoAssetId,+} from "../../../errors";+import { validateRecipient } from "../../../bridge/shared";+import type { AccountBridge, CurrencyBridge, Account } from "../../../types";+import type { Transaction } from "../types";+import { AlgorandOperationTypeEnum } from "../types";+import { scanAccounts } from "../../../libcore/scanAccounts";+import { sync } from "../../../libcore/syncAccount";+import type { CacheRes } from "../../../cache";+import { makeLRUCache } from "../../../cache";+import { getMainAccount } from "../../../account";+import broadcast from "../libcore-broadcast";+import signOperation from "../libcore-signOperation";+import { getFeesForTransaction } from "../../../libcore/getFeesForTransaction";+import { withLibcore } from "../../../libcore/access";+import { getCoreAccount } from "../../../libcore/getCoreAccount";+import { libcoreAmountToBigNumber } from "../../../libcore/buildBigNumber";++const MAX_MEMO_SIZE = 32;++export const calculateFees: CacheRes<+ Array<{ a: Account, t: Transaction }>,+ { estimatedFees: BigNumber, estimatedGas: ?BigNumber }+> = makeLRUCache(+ async ({+ a,+ t,+ }): Promise<{ estimatedFees: BigNumber, estimatedGas: ?BigNumber }> => {+ return await getFeesForTransaction({+ account: a,+ transaction: t,+ });+ },+ ({ a, t }) =>+ `${a.id}_${t.amount.toString()}_${t.recipient}_${String(t.useAllAmount)}_${+ t.memo ? t.memo.toString() : ""+ }`+);++const getSpendableMaxForOptIn = async (account) =>+ await withLibcore(async (core) => {+ const { coreAccount } = await getCoreAccount(core, account);++ const algorandAccount = await coreAccount.asAlgorandAccount();+ const spendableBalanceBigInt = await algorandAccount.getSpendableBalance(+ AlgorandOperationTypeEnum.ASSET_OPT_IN+ );+ const spendableBalance = await libcoreAmountToBigNumber(+ spendableBalanceBigInt+ );++ return spendableBalance;+ });++const createTransaction = () => ({+ family: "algorand",+ amount: BigNumber(0),+ fees: null,+ recipient: "",+ useAllAmount: false,+ memo: null,+ mode: "send",+ assetId: null,+});++const updateTransaction = (t, patch) => {+ return { ...t, ...patch };+};++const recipientHasAsset = async (assetId, recipient, account) =>+ await withLibcore(async (core) => {+ const { coreAccount } = await getCoreAccount(core, account);++ const algorandAccount = await coreAccount.asAlgorandAccount();+ const hasAsset = await algorandAccount.hasAsset(recipient, assetId);++ return hasAsset;+ });+/*+ * Here are the list of the differents things we check+ * - Check if recipient is the same in case of send+ * - Check if recipient is valid+ * - Check if amounts are set+ * - Check if fees are loaded+ * - Check if is a send Max and set the amount+ * - Check if Token is already optin at the recipient+ * - Check if memo is too long+ */+const getTransactionStatus = async (a: Account, t) => {+ const errors = {};+ const warnings = {};+ const tokenAccount = !t.subAccountId+ ? null+ : a.subAccounts && a.subAccounts.find((ta) => ta.id === t.subAccountId);++ if (t.mode === "send" && a.freshAddress === t.recipient) {+ errors.recipient = new InvalidAddressBecauseDestinationIsAlsoSource();+ } else {+ const { recipientError, recipientWarning } = await validateRecipient(+ a.currency,+ t.recipient+ );++ if (recipientError) {+ errors.recipient = recipientError;+ }++ if (recipientWarning) {+ warnings.recipient = recipientWarning;+ }+ }++ let estimatedFees = t.fees || BigNumber(0);+ let amount = t.amount;+ let totalSpent = estimatedFees;++ switch (t.mode) {+ case "send": {+ if (amount.lte(0) && !t.useAllAmount) {+ errors.amount = new AmountRequired();+ }++ if (!t.fees || !t.fees.gt(0)) {+ errors.fees = new FeeNotLoaded();+ }++ if (+ tokenAccount &&+ !(await recipientHasAsset(+ tokenAccount.id.split("/")[2],
shouldn't this be tokenAccount.token.id ?
comment created time in 4 days
issue openedLedgerHQ/ledger-live-common
NetworkInfo type to only be in each family, not shared between families
ctx https://github.com/LedgerHQ/ledger-live-common/pull/799
created time in 4 days
issue closedLedgerHQ/ledger-live-desktop
Ledger Live Version and Operating System
<!-- Precise the app version (Settings > About or bottom-left corner on a crash screen) -->
- tested on Ledger Live 2.9.0
- Platform and version: Arch Linux with kernel 5.7.11-arch1-1, french locales
Expected behavior
Correct balance is displayed for my Tezos account.
Actual behavior
Ledger Live is not displaying the real staking balance of my Tezos account.
Steps to reproduce the behavior
Start Ledger Live.
I have 59,151.593699ꜩ on this account, but Ledger Live thinks I have 59 151 593 699ꜩ. I tried to clear the cache to force a resync, without success. Maybe Ledger can send me the difference in ꜩ to fix this bug?

closed time in 4 days
neves-0issue commentLedgerHQ/ledger-live-desktop
Hi, thanks a lot for raising this issue.
This issue can happen for various reasons. We prefer to investigate such issues via contacting our customer support team.
Are you in contact with our support team?
Thanks
comment created time in 4 days
pull request commentLedgerHQ/ledger-live-mobile
awaiting conflict resolution
comment created time in 4 days
push eventLedgerHQ/ledger-live-mobile
commit sha d88273bcb12dfd459edcf1e8f9287fdb6eb54f20
(Manager): deprecated firmware banner with support contact link added
commit sha 7a1b92605ccb2cbe5e745b8da4a2aa6a20b4b454
Merge pull request #1313 from LFBarreto/LL-2645 LL-2645 Deprecated firmware banner
push time in 4 days
PR merged LedgerHQ/ledger-live-mobile
(Manager): deprecated firmware banner with support contact link added
HODL: needs a live-common bump

Type
Feature
Context
LL-2645
Parts of the app affected / Test plan
Manager
pr closed time in 4 days
push eventLedgerHQ/ledger-live-mobile
commit sha 4e0b569043270e801e3770850fb3dac09dd832cf
(Wording): tezos update provider name and url
commit sha d4c792afaba0949292690978fcb9bc3ab38012bc
Merge pull request #1326 from LFBarreto/LL-2777 LL-2777 (Wording): tezos update provider name and url
push time in 4 days
push eventLedgerHQ/ledger-live-mobile
commit sha cb2cb3536d21c6248ad91bca5bfb8eaa9bf251db
live-common 13.5.0
commit sha da4f0f39668b5b2d9f81d9ce3edb9e730b91d73f
Merge pull request #1334 from gre/live-common-13-5-0 live-common 13.5.0
push time in 4 days
PR merged LedgerHQ/ledger-live-mobile
- live-common bump: please see full detail in https://github.com/LedgerHQ/ledger-live-common/releases/tag/v13.5.0
- some minor libs bump. check integrity of the app on android and ios should be enough (not too deep).
pr closed time in 4 days
push eventLedgerHQ/ledger-live-desktop
commit sha a5cd248a0fb7b18a52597f73c6284c5ba82b159c
live-common 13.5.0
commit sha c4d40a683df826df004465363fb37e9e5c0d7ef8
Merge pull request #3097 from gre/live-common-13-5-0 live-common 13.5.0
push time in 4 days
PR merged LedgerHQ/ledger-live-desktop
- live-common bump: please see full detail in https://github.com/LedgerHQ/ledger-live-common/releases/tag/v13.5.0
- small update of usb-detection that shouldn't be too impacting. just test a bit basic device flows.
pr closed time in 4 days
PR closed LedgerHQ/ledger-api-countervalue
pr closed time in 4 days
push eventLedgerHQ/ledger-api-countervalue
commit sha 719648896f087058bb6acb663a5c7420ca103af8
update live-common
commit sha d47d3f4d0143f700c66325c65e84f1219b4b430e
v1.11.0
push time in 4 days
push eventgre/ledger-api-countervalue
commit sha 719648896f087058bb6acb663a5c7420ca103af8
update live-common
commit sha d47d3f4d0143f700c66325c65e84f1219b4b430e
v1.11.0
push time in 4 days
push eventLedgerHQ/ledger-live-common
commit sha 45e4a1a9d882ccbb2b0ce0bf07a52241c3d20016
update deps
push time in 4 days
created tagLedgerHQ/ledger-live-common
Common ground for the Ledger Wallet apps
created time in 4 days
push eventLedgerHQ/ledger-live-common
commit sha 26ebf9c0daa49f357bd374c22b8554a8d64cee73
v13.6.0
push time in 4 days
push eventLedgerHQ/ledger-live-common
commit sha bd88691beb42f756e7ac1b15f9b68877df8f23f3
Update ERC20 list
push time in 4 days
issue commentLedgerHQ/ledger-live-common
@bitseaman What is your Nano firmware version and ZCash app version? You might need to make sure your app is up to date to support the new blockchain protocol update of ZCash.
comment created time in 4 days
push eventLedgerHQ/ledgerjs
commit sha 7415a94eb8da0eae873551c9dbd393e0eec0ec4a
v5.21.0
push time in 4 days
push eventLedgerHQ/ledgerjs
commit sha 2c3a6753e08c02cc232bbb7b9f220e151d3a1ef2
sync erc20 signatures
push time in 4 days
push eventLedgerHQ/ledgerjs
commit sha bbcc74c47f4072997d343070f99fb0914ffff2fd
libs update tooling update (flow/jest/rollup/babel/eslint)
push time in 5 days
push eventLedgerHQ/ledger-live-common
commit sha 4c2fd31f94d3ccc016b807a35b5128d1dfd17818
(CryptoCurrencies): add algorand data
commit sha 0651ff51c91485550b2ffc07aef1402047fc24d7
Merge pull request #796 from LFBarreto/add-algorand-data (CryptoCurrencies): add algorand data
push time in 5 days
PR merged LedgerHQ/ledger-live-common
Add algorand data in cryptocurrencies to prepare for countervalues
pr closed time in 5 days
issue commentLedgerHQ/ledgerjs
Yes, unfortunately with U2F protocol, there is nothing we can easily fix. The protocol is very limited.
It is recommended to use webusb and/or webhid API for web integration.
I don't really understand what you want is to fix in this issue. Is it about documenting this error code? I tend to simply deprecate the u2f library..
comment created time in 5 days
issue commentLedgerHQ/ledger-live-desktop
[Discussion] Those ads are getting a little too far.
Ledger Live aim to offer many features in one app and this is one way to promote these features. To me it can be very interesting if we make it smarter (like contextual to your accounts) for instance, we have content that mentions you can "Delegate on Tezos" but only appearing if you have Tezos. I think it can be really helpful for users that don't know such feature is possible.. This banner always existed if you had Tezos, we simply generalized it and unified it.
That said, I shared your feedback internally. We can always refine the visibility of this banner. You see ads where I see just the potential of more content (like more documentation around https://www.ledger.com/academy/ ) / other dynamic content. Maybe i'm wrong and we can always refine to make it better and less intrusive. We read your feedbacks.
comment created time in 7 days
PR opened LedgerHQ/ledger-live-desktop
- live-common bump: please see full detail in https://github.com/LedgerHQ/ledger-live-common/releases/tag/v13.5.0
- small update of usb-detection that shouldn't be too impacting. just test a bit basic device flows.
pr created time in 7 days
PR opened LedgerHQ/ledger-live-mobile
- live-common bump: please see full detail in https://github.com/LedgerHQ/ledger-live-common/releases/tag/v13.5.0
- some minor libs bump. check integrity of the app on android and ios should be enough (not too deep).
pr created time in 7 days
push eventLedgerHQ/ledger-live-common
commit sha badcfaf8fc532f0ba73c06d9eadd504bad8d6d04
cli update
push time in 7 days
push eventLedgerHQ/ledger-live-common
commit sha 126acb917badb4d857dd227e11100ae998d13016
update deps
commit sha d96189ed87da242cb0165d732ffc0997834004f3
v13.5.0
push time in 7 days
push eventLedgerHQ/ledger-live-common
commit sha e9a22de78d5714cf1a5dc25a8e7f713cfec79dd2
Rework actions app polling to fix connect/disconnect glitch (#791) * Rework actions app polling to fix connect/disconnect glitch * v13.5.0-actions.0 * Fixes opened event to reset device * v13.5.0-actions.1 * Fixes expect issue * v13.5.0-actions.2
push time in 7 days
PR merged LedgerHQ/ledger-live-common
to be tested in context of mobile rework
pr closed time in 7 days
push eventLedgerHQ/ledger-live-common
commit sha 1f3fed76b786b44325fec2881f423d9b05b93bd3
Use more specific error for user reject swap on the modal flow
commit sha 642b0a0d8e3cf7cc6a4967e9fce0890e56cc794f
Swap - Allow a setToAccount dispatch in logic.js
commit sha 822a92b8b75bef8f0ff8efcc91244137a3cdf891
Swap - Fixed typings for token/cryptocurrencies
commit sha b01eebafd4ddbb5ae5499b10fe888a687d5f1940
Merge pull request #795 from juan-cortes/swap-user-reject Swap - Second iteration following the feedback sheet
push time in 7 days
push eventgre/ledger-live-common
commit sha 732cd77f85ba7215604ae69d86a02c72fc556fa9
Fixes expect issue
commit sha d3fc319a47a0f7c4a3c6aba00dc06a534f600045
v13.5.0-actions.2
push time in 7 days
push eventgre/ledger-live-common
commit sha 7ea016b1aab12800925ce1c8c3d4588d3f7d59f1
Fixes usage of CashAddr format on device
commit sha fb7fd02a9b9dd450cc6ad3c1a95ddf10f437a7f2
Add a way to specify the osu to cli firmwareUpdate
commit sha a629637ff9fef34a8bfd0930e68841da16a11f3e
(Eth): fix operationsCount post sync patch update
commit sha e9eab76d6228b6233d3dfabb5def5222afdb7e87
Merge pull request #793 from LFBarreto/LL-2817 LL-2817 (Eth): fix operationsCount post sync patch update
commit sha 43bf21bc2d9b2370b76d1e6b6ec9af5f84630a0e
Merge pull request #788 from gre/LL-2802 Fixes usage of CashAddr format on device
commit sha fd358d5dc105b7c1d312812d4f548350cfa00a38
Merge pull request #792 from LedgerHQ/custom-firmware-flashing Add a way to specify the osu to cli firmwareUpdate
commit sha 1ce6a041cce98204ec0b1c8af6f522cd8a08b7b3
snapshot
commit sha 568309fbf5e1e8350466e368b6156afb9a0d8ccf
Rework actions app polling to fix connect/disconnect glitch
commit sha e9a14829e8f7085bcefacdeefb86e64ac15e8292
v13.5.0-actions.0
commit sha 4472d2c49d966b44420f7c66b6b0d954eb5467e9
Fixes opened event to reset device
commit sha 0b83eadb885be81e9e4c802f91220d437d96992c
v13.5.0-actions.1
push time in 7 days
issue commentLedgerHQ/ledger-live-desktop
Cashaddr format for Bitcoin Cash
https://github.com/LedgerHQ/ledger-live-common/pull/788 :)
comment created time in 7 days
push eventLedgerHQ/ledger-live-common
commit sha 1ce6a041cce98204ec0b1c8af6f522cd8a08b7b3
snapshot
push time in 7 days
push eventgre/ledger-live-common
commit sha e2f1a88f0d523fdc143ffac6d0f0539a556f4052
kickoff compound data models
push time in 7 days
PR merged LedgerHQ/ledger-live-common
allows ledger-live firmwareUpdate commands to flash any arbitrary firmware, even if not yet available in production app.
ledger-live firmwareUpdate --osuVersion 1.2.4-2-osu --to-my-own-risk
pr closed time in 7 days
push eventLedgerHQ/ledger-live-common
commit sha fb7fd02a9b9dd450cc6ad3c1a95ddf10f437a7f2
Add a way to specify the osu to cli firmwareUpdate
commit sha fd358d5dc105b7c1d312812d4f548350cfa00a38
Merge pull request #792 from LedgerHQ/custom-firmware-flashing Add a way to specify the osu to cli firmwareUpdate
push time in 7 days
push eventLedgerHQ/ledger-live-common
commit sha 7ea016b1aab12800925ce1c8c3d4588d3f7d59f1
Fixes usage of CashAddr format on device
commit sha 43bf21bc2d9b2370b76d1e6b6ec9af5f84630a0e
Merge pull request #788 from gre/LL-2802 Fixes usage of CashAddr format on device
push time in 7 days
PR merged LedgerHQ/ledger-live-common
This fixes the fact device display a cashaddr format when you input one.
it also allows user to input either bitcoincash:X or X where X is a cash address p2psh address format
Bot testing will cover the 3 supported addresses (legacy, cashaddr, cashaddr without prefix)
pr closed time in 7 days
push eventgre/ledger-live-common
commit sha a198d13b79ddcc90f0898734954f9241d303aee3
Attempt to fixes actions app polling glitch
push time in 7 days
push eventLedgerHQ/ledger-live-common
commit sha a629637ff9fef34a8bfd0930e68841da16a11f3e
(Eth): fix operationsCount post sync patch update
commit sha e9eab76d6228b6233d3dfabb5def5222afdb7e87
Merge pull request #793 from LFBarreto/LL-2817 LL-2817 (Eth): fix operationsCount post sync patch update
push time in 7 days
PR merged LedgerHQ/ledger-live-common
operationsCount is now updated after each post sync patch.
pr closed time in 7 days
Pull request review commentLedgerHQ/ledger-live-desktop
added a new account selector component
+// @flow++import { getAccountCurrency, getAccountName } from "@ledgerhq/live-common/lib/account";+import type { TFunction } from "react-i18next";+import type { AccountLike, Account } from "@ledgerhq/live-common/lib/types";+import React, { useCallback, useState, useMemo, useEffect } from "react";+import { withTranslation } from "react-i18next";+import { connect } from "react-redux";+import { createFilter } from "react-select";+import { createStructuredSelector } from "reselect";+import { shallowAccountsSelector } from "~/renderer/reducers/accounts";+import Select from "~/renderer/components/Select";+import type { CryptoCurrency, TokenCurrency } from "@ledgerhq/live-common/lib/types/currencies";+import type { SubAccount } from "@ledgerhq/live-common/lib/types/account";+import { MenuOption } from "~/renderer/components/PerCurrencySelectAccount/Option";+import { BigNumber } from "bignumber.js";++const mapStateToProps = createStructuredSelector({+ accounts: shallowAccountsSelector,+});++type Option = {+ matched: "boolean",+ account: Account,+ subAccount: SubAccount,+};+const getOptionValue = option => {+ return option.account ? option.account.id : null;+};++const defaultFilter = createFilter({+ stringify: ({ data: account }) => {+ const currency = getAccountCurrency(account);+ const name = getAccountName(account);+ return `${currency.ticker}|${currency.name}|${name}`;+ },+});+const filterOption = o => (candidate, input) => {+ const selfMatches = defaultFilter(candidate, input);+ if (selfMatches) return [selfMatches, true];+ return [false, false];+};++const AccountOption = React.memo(function AccountOption({+ account,+ subAccount,+ currency,+ isValue,+}: {+ account: Account,+ subAccount: SubAccount,+ currency: CryptoCurrency | TokenCurrency,+ isValue?: boolean,+}) {+ return <MenuOption isValue={isValue} account={account} subAccount={subAccount} />;+});++type OwnProps = {+ currency: TokenCurrency | CryptoCurrency,+ mandatoryTokens?: boolean,+ value: ?AccountLike,+ onChange: (account: ?AccountLike, tokenAccount: ?Account) => void,+};++type Props = OwnProps & {+ accounts: Account[],+ t: TFunction,+ onChange: (account: ?AccountLike, tokenAccount: ?Account) => void, // I have to add it twice else flow is unhappy, don't ask why+};++const getAccountsByCurrency = (accounts: Account[], currency: TokenCurrency | CryptoCurrency) =>+ accounts.filter((acc: Account) => acc.currency.id === currency.id);++// TODO: token account generation logic in live-common+const generateTokenAccount = (account: Account, currency: TokenCurrency) => ({+ type: "TokenAccount",+ id: account.id + "+" + currency.contractAddress,+ parentId: account.id,+ token: currency,+ balance: BigNumber(0),+ operationsCount: 0,+ creationDate: new Date(),+ operations: [],+ pendingOperations: [],+ starred: false,+});++const RawSelectAccount = ({+ accounts,+ onChange,+ t,+ currency,+ mandatoryTokens,+ ...props+}: Props) => {+ const availableAccounts = useMemo(() => {+ if (currency.type === "TokenCurrency") {+ const mainAccounts = getAccountsByCurrency(accounts, currency.parentCurrency);+ return mainAccounts.map((acc: Account) => {+ return {+ account: acc,+ subAccount:+ (acc.subAccounts &&+ acc.subAccounts.find(+ (subAcc: SubAccount) =>+ subAcc.type === "TokenAccount" && subAcc.token.id === currency.id,+ )) ||+ generateTokenAccount(acc, currency),+ };+ });+ }+ return getAccountsByCurrency(accounts, currency).map((acc: Account) => ({+ account: acc,+ subAccount: null,+ }));+ }, [accounts, currency]);++ const [selectedValue, setSelectedValue] = useState();++ useEffect(() => {
That effect is dangerous because it assumes the accounts prop never update.
This needs to use a ref to statically store the initial value because I think you want it to be just at init?
const initialAcc = useRef(availableAccounts)
comment created time in 8 days
Pull request review commentLedgerHQ/ledger-live-desktop
added a new account selector component
+// @flow++import { getAccountCurrency, getAccountName } from "@ledgerhq/live-common/lib/account";+import type { TFunction } from "react-i18next";+import type { AccountLike, Account } from "@ledgerhq/live-common/lib/types";+import React, { useCallback, useState, useMemo, useEffect } from "react";+import { withTranslation } from "react-i18next";+import { connect } from "react-redux";+import { createFilter } from "react-select";+import { createStructuredSelector } from "reselect";+import { shallowAccountsSelector } from "~/renderer/reducers/accounts";+import Select from "~/renderer/components/Select";+import type { CryptoCurrency, TokenCurrency } from "@ledgerhq/live-common/lib/types/currencies";+import type { SubAccount } from "@ledgerhq/live-common/lib/types/account";+import { MenuOption } from "~/renderer/components/PerCurrencySelectAccount/Option";+import { BigNumber } from "bignumber.js";++const mapStateToProps = createStructuredSelector({+ accounts: shallowAccountsSelector,+});++type Option = {+ matched: "boolean",+ account: Account,+ subAccount: SubAccount,+};+const getOptionValue = option => {+ return option.account ? option.account.id : null;+};++const defaultFilter = createFilter({+ stringify: ({ data: account }) => {+ const currency = getAccountCurrency(account);+ const name = getAccountName(account);+ return `${currency.ticker}|${currency.name}|${name}`;+ },+});+const filterOption = o => (candidate, input) => {+ const selfMatches = defaultFilter(candidate, input);+ if (selfMatches) return [selfMatches, true];+ return [false, false];+};++const AccountOption = React.memo(function AccountOption({+ account,+ subAccount,+ currency,+ isValue,+}: {+ account: Account,+ subAccount: SubAccount,+ currency: CryptoCurrency | TokenCurrency,+ isValue?: boolean,+}) {+ return <MenuOption isValue={isValue} account={account} subAccount={subAccount} />;+});++type OwnProps = {+ currency: TokenCurrency | CryptoCurrency,+ mandatoryTokens?: boolean,+ value: ?AccountLike,+ onChange: (account: ?AccountLike, tokenAccount: ?Account) => void,+};++type Props = OwnProps & {+ accounts: Account[],+ t: TFunction,+ onChange: (account: ?AccountLike, tokenAccount: ?Account) => void, // I have to add it twice else flow is unhappy, don't ask why+};++const getAccountsByCurrency = (accounts: Account[], currency: TokenCurrency | CryptoCurrency) =>+ accounts.filter((acc: Account) => acc.currency.id === currency.id);++// TODO: token account generation logic in live-common+const generateTokenAccount = (account: Account, currency: TokenCurrency) => ({+ type: "TokenAccount",+ id: account.id + "+" + currency.contractAddress,+ parentId: account.id,+ token: currency,+ balance: BigNumber(0),+ operationsCount: 0,+ creationDate: new Date(),+ operations: [],+ pendingOperations: [],+ starred: false,+});++const RawSelectAccount = ({+ accounts,+ onChange,+ t,+ currency,+ mandatoryTokens,+ ...props+}: Props) => {+ const availableAccounts = useMemo(() => {+ if (currency.type === "TokenCurrency") {+ const mainAccounts = getAccountsByCurrency(accounts, currency.parentCurrency);+ return mainAccounts.map((acc: Account) => {
My understanding is that accountWithMandatory would indeed simplify some code here and as you will have to do the same on mobile is better as shared code.
But at the same time this seems to be implemented in a different paradigm where values are actually tuples with sub accounts? I'm ok with that PR solution that seems better to keep the account reference and avoid bad surprise. However that generateTokenAccount must be moved to live-common. Please provide a PR to make it there.
comment created time in 8 days
PR opened LedgerHQ/ledger-live-common
allows ledger-live firmwareUpdate commands to flash any arbitrary firmware, even if not yet available in production app.
ledger-live firmwareUpdate --osuVersion 1.2.4-2-osu --to-my-own-risk
pr created time in 8 days
create barnchLedgerHQ/ledger-live-common
branch : custom-firmware-flashing
created branch time in 8 days
push eventLedgerHQ/ledger-live-mobile
commit sha a26312d996599cd914c8c7b4df0cde809a8d4770
(AssetAllocation): fix balance centered
commit sha 1110f4113d5fd66a4f0143dfb0404b5b0e688dcf
Merge pull request #1330 from LFBarreto/LL-2708 LL-2708 (AssetAllocation): fix balance centered
push time in 10 days
PR merged LedgerHQ/ledger-live-mobile
In Asset allocation screen the balance countervalues were not centered

Type
UI Polish
Context
LL-2708
Parts of the app affected / Test plan
Go to the asset allocation then long press on a currency to go to the correct page.
pr closed time in 10 days
