profile
viewpoint
Gaëtan Renaudeau gre @ledgerhq Paris, France https://twitter.com/greweb gl-react creator – gl-transitions.com – front lead @LedgerHQ – 👾👨‍👩‍👦‍👦👨🏻‍🌾🌱🀄️🍷🥖

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

gre/bezier-easing-editor 226

Cubic Bezier Curve editor made with React & SVG

gre/behind-asteroids 127

"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)

gre/audio-notes 13

Note frequencies for equal-tempered scale

gre/blazing-race 5

Blazing Race is a HTML5 against-the-clock platform game where you control a fireball with your mouse and you try to light candles.

gre/audio-chunks 4

slice/append/insert/subset/copy operations on AudioBuffer linked-list chunks

push eventLedgerHQ/ledger-live-common

Juan Cortes Ross

commit sha 8ca2ed2a318e70d90deaa613ec63d28bd044571e

Fixes bug in qr export with missing swapHistory

view details

Gaëtan Renaudeau

commit sha fe686b787a4a61cb16e5a22de90a5544a4a30a4f

Merge pull request #805 from juan-cortes/cross-fix Fixes bug in qr export with missing swapHistory

view details

push time in 35 minutes

PR merged LedgerHQ/ledger-live-common

Reviewers
Fixes bug in qr export with missing swapHistory

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.

+84 -20

1 comment

3 changed files

juan-cortes

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

juan-cortes

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

juan-cortes

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

juan-cortes

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 ?

juan-cortes

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

juan-cortes

comment created time in 14 hours

Pull request review commentLedgerHQ/ledger-live-common

Algorand

+// @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.

henri-ly

comment created time in 21 hours

Pull request review commentLedgerHQ/ledger-live-common

Algorand

+/********************************************************************************

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.

henri-ly

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?

juan-cortes

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}

juan-cortes

comment created time in a day

push eventLedgerHQ/ledger-live-desktop

Gaëtan Renaudeau

commit sha 396fb631bea222cc7e66b2541f07d4d1cc11fb49

Fixed typo "displayed on your device"

view details

push time in a day

Pull request review commentLedgerHQ/ledger-live-common

Algorand

 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?

henri-ly

comment created time in a day

PR opened LedgerHQ/ledger-live-common

WIP ethereum js rework
+23763 -342

0 comment

8 changed files

pr created time in a day

create barnchgre/ledger-live-common

branch : ethereumjs-reboot

created branch time in a day

issue openedLedgerHQ/ledger-live-common

tools: /apps is broken

created time in a day

push eventLedgerHQ/ledger-live-common

Juan Cortes Ross

commit sha ac0fa48ca2b22ebf3fcd2f7be32e00e0d318f6d8

LL-2969 Change manager icon source

view details

Gaëtan Renaudeau

commit sha e1621db5d894aff03cb5ca1ad9e36c086fcb1fc4

Merge pull request #801 from juan-cortes/LL-2969 LL-2969 Change manager icon source

view details

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)

IAmMorrow

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?

valpinkman

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?

valpinkman

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

valpinkman

comment created time in 4 days

push eventLedgerHQ/ledger-live-common

Juan Cortes Ross

commit sha ca3a91cfd1ddc0a616624d0df6de2ed077fc925f

Swap - Changes for mobile compatibility Also adapts cli to not break

view details

Juan Cortes Ross

commit sha 78aaff2e3860f476d666dadcdf59be7d0f955029

Swap - History export helper

view details

Gaëtan Renaudeau

commit sha 656f25927c844e08b82dcc8b91c0594f26458b9d

Merge pull request #798 from juan-cortes/swap-mobile-and-export Swap mobile compatibility and export operations

view details

push time in 4 days

PR merged LedgerHQ/ledger-live-common

Swap mobile compatibility and export operations

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.

+129 -41

2 comments

5 changed files

juan-cortes

pr closed time in 4 days

Pull request review commentLedgerHQ/ledger-live-common

Algorand

+// @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

henri-ly

comment created time in 4 days

Pull request review commentLedgerHQ/ledger-live-common

Algorand

 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?

henri-ly

comment created time in 4 days

Pull request review commentLedgerHQ/ledger-live-common

Algorand

+// @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

henri-ly

comment created time in 4 days

Pull request review commentLedgerHQ/ledger-live-common

Algorand

+// @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?

henri-ly

comment created time in 4 days

Pull request review commentLedgerHQ/ledger-live-common

Algorand

+// @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

henri-ly

comment created time in 4 days

Pull request review commentLedgerHQ/ledger-live-common

Algorand

+// @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 ?

henri-ly

comment created time in 4 days

Pull request review commentLedgerHQ/ledger-live-common

Algorand

+// @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

henri-ly

comment created time in 4 days

Pull request review commentLedgerHQ/ledger-live-common

Algorand

+// @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

henri-ly

comment created time in 4 days

Pull request review commentLedgerHQ/ledger-live-common

Algorand

+// @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
henri-ly

comment created time in 4 days

Pull request review commentLedgerHQ/ledger-live-common

Algorand

+// @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.

henri-ly

comment created time in 4 days

Pull request review commentLedgerHQ/ledger-live-common

Algorand

+/********************************************************************************

is there plan to contribute back to ledgerjs repo?

henri-ly

comment created time in 4 days

Pull request review commentLedgerHQ/ledger-live-common

Algorand

+// @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

henri-ly

comment created time in 4 days

Pull request review commentLedgerHQ/ledger-live-common

Algorand

+// @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.

henri-ly

comment created time in 4 days

Pull request review commentLedgerHQ/ledger-live-common

Algorand

+// @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.

henri-ly

comment created time in 4 days

Pull request review commentLedgerHQ/ledger-live-common

Algorand

+// @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 ?

henri-ly

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

Wrong Tezos balance

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?

image

closed time in 4 days

neves-0

issue commentLedgerHQ/ledger-live-desktop

Wrong Tezos balance

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

neves-0

comment created time in 4 days

pull request commentLedgerHQ/ledger-live-mobile

upgrade dependencies

awaiting conflict resolution

LFBarreto

comment created time in 4 days

push eventLedgerHQ/ledger-live-mobile

LFBarreto

commit sha d88273bcb12dfd459edcf1e8f9287fdb6eb54f20

(Manager): deprecated firmware banner with support contact link added

view details

Gaëtan Renaudeau

commit sha 7a1b92605ccb2cbe5e745b8da4a2aa6a20b4b454

Merge pull request #1313 from LFBarreto/LL-2645 LL-2645 Deprecated firmware banner

view details

push time in 4 days

PR merged LedgerHQ/ledger-live-mobile

Reviewers
LL-2645 Deprecated firmware banner

(Manager): deprecated firmware banner with support contact link added

HODL: needs a live-common bump

Screenshot_1593446878

Type

Feature

Context

LL-2645

Parts of the app affected / Test plan

Manager

+25 -4

4 comments

2 changed files

LFBarreto

pr closed time in 4 days

push eventLedgerHQ/ledger-live-mobile

LFBarreto

commit sha 4e0b569043270e801e3770850fb3dac09dd832cf

(Wording): tezos update provider name and url

view details

Gaëtan Renaudeau

commit sha d4c792afaba0949292690978fcb9bc3ab38012bc

Merge pull request #1326 from LFBarreto/LL-2777 LL-2777 (Wording): tezos update provider name and url

view details

push time in 4 days

push eventLedgerHQ/ledger-live-mobile

Gaëtan Renaudeau

commit sha cb2cb3536d21c6248ad91bca5bfb8eaa9bf251db

live-common 13.5.0

view details

Gaëtan Renaudeau

commit sha da4f0f39668b5b2d9f81d9ce3edb9e730b91d73f

Merge pull request #1334 from gre/live-common-13-5-0 live-common 13.5.0

view details

push time in 4 days

PR merged LedgerHQ/ledger-live-mobile

live-common 13.5.0 important
  • 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).
+920 -769

1 comment

2 changed files

gre

pr closed time in 4 days

push eventLedgerHQ/ledger-live-desktop

Gaëtan Renaudeau

commit sha a5cd248a0fb7b18a52597f73c6284c5ba82b159c

live-common 13.5.0

view details

Gaëtan Renaudeau

commit sha c4d40a683df826df004465363fb37e9e5c0d7ef8

Merge pull request #3097 from gre/live-common-13-5-0 live-common 13.5.0

view details

push time in 4 days

PR merged LedgerHQ/ledger-live-desktop

live-common 13.5.0 important
  • 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.
+59 -37

0 comment

2 changed files

gre

pr closed time in 4 days

created tagLedgerHQ/ledger-api-countervalue

tagv1.11.0

created time in 4 days

push eventLedgerHQ/ledger-api-countervalue

Gaëtan Renaudeau

commit sha 719648896f087058bb6acb663a5c7420ca103af8

update live-common

view details

Gaëtan Renaudeau

commit sha d47d3f4d0143f700c66325c65e84f1219b4b430e

v1.11.0

view details

push time in 4 days

created taggre/ledger-api-countervalue

tagv1.11.0

created time in 4 days

push eventgre/ledger-api-countervalue

Gaëtan Renaudeau

commit sha 719648896f087058bb6acb663a5c7420ca103af8

update live-common

view details

Gaëtan Renaudeau

commit sha d47d3f4d0143f700c66325c65e84f1219b4b430e

v1.11.0

view details

push time in 4 days

release LedgerHQ/ledger-live-common

v13.6.0

released time in 4 days

push eventLedgerHQ/ledger-live-common

Gaëtan Renaudeau

commit sha 45e4a1a9d882ccbb2b0ce0bf07a52241c3d20016

update deps

view details

push time in 4 days

created tagLedgerHQ/ledger-live-common

tagv13.6.0

Common ground for the Ledger Wallet apps

created time in 4 days

push eventLedgerHQ/ledger-live-common

Gaëtan Renaudeau

commit sha 26ebf9c0daa49f357bd374c22b8554a8d64cee73

v13.6.0

view details

push time in 4 days

push eventLedgerHQ/ledger-live-common

Gaëtan Renaudeau

commit sha bd88691beb42f756e7ac1b15f9b68877df8f23f3

Update ERC20 list

view details

push time in 4 days

issue commentLedgerHQ/ledger-live-common

error when sending ZEC

@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.

bitseaman

comment created time in 4 days

release LedgerHQ/ledgerjs

v5.21.0

released time in 4 days

created tagLedgerHQ/ledgerjs

tagv5.21.0

Ledger's JavaScript libraries

created time in 4 days

push eventLedgerHQ/ledgerjs

Gaëtan Renaudeau

commit sha 7415a94eb8da0eae873551c9dbd393e0eec0ec4a

v5.21.0

view details

push time in 4 days

push eventLedgerHQ/ledgerjs

Gaëtan Renaudeau

commit sha 2c3a6753e08c02cc232bbb7b9f220e151d3a1ef2

sync erc20 signatures

view details

push time in 4 days

push eventLedgerHQ/ledgerjs

Gaëtan Renaudeau

commit sha bbcc74c47f4072997d343070f99fb0914ffff2fd

libs update tooling update (flow/jest/rollup/babel/eslint)

view details

push time in 5 days

push eventLedgerHQ/ledger-live-common

LFBarreto

commit sha 4c2fd31f94d3ccc016b807a35b5128d1dfd17818

(CryptoCurrencies): add algorand data

view details

Gaëtan Renaudeau

commit sha 0651ff51c91485550b2ffc07aef1402047fc24d7

Merge pull request #796 from LFBarreto/add-algorand-data (CryptoCurrencies): add algorand data

view details

push time in 5 days

PR merged LedgerHQ/ledger-live-common

(CryptoCurrencies): add algorand data

Add algorand data in cryptocurrencies to prepare for countervalues

+29 -0

1 comment

1 changed file

LFBarreto

pr closed time in 5 days

issue commentLedgerHQ/ledgerjs

U2F DEVICE_INELIGIBLE

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..

justinvforvendetta

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.

Zaczero

comment created time in 7 days

PR opened LedgerHQ/ledger-live-desktop

live-common 13.5.0
  • 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.
+59 -37

0 comment

2 changed files

pr created time in 7 days

create barnchgre/ledger-live-desktop

branch : live-common-13-5-0

created branch time in 7 days

PR opened LedgerHQ/ledger-live-mobile

live-common 13.5.0
  • 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).
+920 -769

0 comment

2 changed files

pr created time in 7 days

create barnchgre/ledger-live-mobile

branch : live-common-13-5-0

created branch time in 7 days

release LedgerHQ/ledger-live-common

v13.5.0

released time in 7 days

push eventLedgerHQ/ledger-live-common

Gaëtan Renaudeau

commit sha badcfaf8fc532f0ba73c06d9eadd504bad8d6d04

cli update

view details

push time in 7 days

push eventLedgerHQ/ledger-live-common

Gaëtan Renaudeau

commit sha 126acb917badb4d857dd227e11100ae998d13016

update deps

view details

Gaëtan Renaudeau

commit sha d96189ed87da242cb0165d732ffc0997834004f3

v13.5.0

view details

push time in 7 days

push eventLedgerHQ/ledger-live-common

Gaëtan Renaudeau

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

view details

push time in 7 days

PR merged LedgerHQ/ledger-live-common

Rework actions app polling to fix connect/disconnect glitch

to be tested in context of mobile rework

+90 -28

1 comment

6 changed files

gre

pr closed time in 7 days

push eventLedgerHQ/ledger-live-common

Juan Cortes Ross

commit sha 1f3fed76b786b44325fec2881f423d9b05b93bd3

Use more specific error for user reject swap on the modal flow

view details

Juan Cortes Ross

commit sha 642b0a0d8e3cf7cc6a4967e9fce0890e56cc794f

Swap - Allow a setToAccount dispatch in logic.js

view details

Juan Cortes Ross

commit sha 822a92b8b75bef8f0ff8efcc91244137a3cdf891

Swap - Fixed typings for token/cryptocurrencies

view details

Gaëtan Renaudeau

commit sha b01eebafd4ddbb5ae5499b10fe888a687d5f1940

Merge pull request #795 from juan-cortes/swap-user-reject Swap - Second iteration following the feedback sheet

view details

push time in 7 days

push eventgre/ledger-live-common

Gaëtan Renaudeau

commit sha 732cd77f85ba7215604ae69d86a02c72fc556fa9

Fixes expect issue

view details

Gaëtan Renaudeau

commit sha d3fc319a47a0f7c4a3c6aba00dc06a534f600045

v13.5.0-actions.2

view details

push time in 7 days

push eventgre/ledger-live-common

Gaëtan Renaudeau

commit sha 7ea016b1aab12800925ce1c8c3d4588d3f7d59f1

Fixes usage of CashAddr format on device

view details

Gaëtan Renaudeau

commit sha fb7fd02a9b9dd450cc6ad3c1a95ddf10f437a7f2

Add a way to specify the osu to cli firmwareUpdate

view details

LFBarreto

commit sha a629637ff9fef34a8bfd0930e68841da16a11f3e

(Eth): fix operationsCount post sync patch update

view details

Gaëtan Renaudeau

commit sha e9eab76d6228b6233d3dfabb5def5222afdb7e87

Merge pull request #793 from LFBarreto/LL-2817 LL-2817 (Eth): fix operationsCount post sync patch update

view details

Gaëtan Renaudeau

commit sha 43bf21bc2d9b2370b76d1e6b6ec9af5f84630a0e

Merge pull request #788 from gre/LL-2802 Fixes usage of CashAddr format on device

view details

Gaëtan Renaudeau

commit sha fd358d5dc105b7c1d312812d4f548350cfa00a38

Merge pull request #792 from LedgerHQ/custom-firmware-flashing Add a way to specify the osu to cli firmwareUpdate

view details

Gaëtan Renaudeau

commit sha 1ce6a041cce98204ec0b1c8af6f522cd8a08b7b3

snapshot

view details

Gaëtan Renaudeau

commit sha 568309fbf5e1e8350466e368b6156afb9a0d8ccf

Rework actions app polling to fix connect/disconnect glitch

view details

Gaëtan Renaudeau

commit sha e9a14829e8f7085bcefacdeefb86e64ac15e8292

v13.5.0-actions.0

view details

Gaëtan Renaudeau

commit sha 4472d2c49d966b44420f7c66b6b0d954eb5467e9

Fixes opened event to reset device

view details

Gaëtan Renaudeau

commit sha 0b83eadb885be81e9e4c802f91220d437d96992c

v13.5.0-actions.1

view details

push time in 7 days

issue commentLedgerHQ/ledger-live-desktop

Cashaddr format for Bitcoin Cash

https://github.com/LedgerHQ/ledger-live-common/pull/788 :)

KMFDM29

comment created time in 7 days

push eventLedgerHQ/ledger-live-common

Gaëtan Renaudeau

commit sha 1ce6a041cce98204ec0b1c8af6f522cd8a08b7b3

snapshot

view details

push time in 7 days

push eventgre/ledger-live-common

Gaëtan Renaudeau

commit sha e2f1a88f0d523fdc143ffac6d0f0539a556f4052

kickoff compound data models

view details

push time in 7 days

create barnchgre/ledger-live-common

branch : compound-types

created branch time in 7 days

PR merged LedgerHQ/ledger-live-common

Add a way to specify the osu to cli firmwareUpdate

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
+127 -14

1 comment

1 changed file

gre

pr closed time in 7 days

push eventLedgerHQ/ledger-live-common

Gaëtan Renaudeau

commit sha fb7fd02a9b9dd450cc6ad3c1a95ddf10f437a7f2

Add a way to specify the osu to cli firmwareUpdate

view details

Gaëtan Renaudeau

commit sha fd358d5dc105b7c1d312812d4f548350cfa00a38

Merge pull request #792 from LedgerHQ/custom-firmware-flashing Add a way to specify the osu to cli firmwareUpdate

view details

push time in 7 days

push eventLedgerHQ/ledger-live-common

Gaëtan Renaudeau

commit sha 7ea016b1aab12800925ce1c8c3d4588d3f7d59f1

Fixes usage of CashAddr format on device

view details

Gaëtan Renaudeau

commit sha 43bf21bc2d9b2370b76d1e6b6ec9af5f84630a0e

Merge pull request #788 from gre/LL-2802 Fixes usage of CashAddr format on device

view details

push time in 7 days

PR merged LedgerHQ/ledger-live-common

Fixes usage of CashAddr format on device important

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)

+184 -34

2 comments

11 changed files

gre

pr closed time in 7 days

push eventgre/ledger-live-common

Gaëtan Renaudeau

commit sha a198d13b79ddcc90f0898734954f9241d303aee3

Attempt to fixes actions app polling glitch

view details

push time in 7 days

push eventLedgerHQ/ledger-live-common

LFBarreto

commit sha a629637ff9fef34a8bfd0930e68841da16a11f3e

(Eth): fix operationsCount post sync patch update

view details

Gaëtan Renaudeau

commit sha e9eab76d6228b6233d3dfabb5def5222afdb7e87

Merge pull request #793 from LFBarreto/LL-2817 LL-2817 (Eth): fix operationsCount post sync patch update

view details

push time in 7 days

PR merged LedgerHQ/ledger-live-common

Reviewers
LL-2817 (Eth): fix operationsCount post sync patch update

operationsCount is now updated after each post sync patch.

+1 -0

1 comment

1 changed file

LFBarreto

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)
IAmMorrow

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.

IAmMorrow

comment created time in 8 days

PR opened LedgerHQ/ledger-live-common

Add a way to specify the osu to cli firmwareUpdate

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
+127 -14

0 comment

1 changed file

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

LFBarreto

commit sha a26312d996599cd914c8c7b4df0cde809a8d4770

(AssetAllocation): fix balance centered

view details

Gaëtan Renaudeau

commit sha 1110f4113d5fd66a4f0143dfb0404b5b0e688dcf

Merge pull request #1330 from LFBarreto/LL-2708 LL-2708 (AssetAllocation): fix balance centered

view details

push time in 10 days

PR merged LedgerHQ/ledger-live-mobile

LL-2708 (AssetAllocation): fix balance centered

In Asset allocation screen the balance countervalues were not centered

Screenshot_1595507050

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.

+1 -1

0 comment

1 changed file

LFBarreto

pr closed time in 10 days

more