Skip to content
This repository has been archived by the owner on Jun 16, 2022. It is now read-only.

Commit

Permalink
Merge pull request #605 from LedgerHQ/develop
Browse files Browse the repository at this point in the history
prepare for alpha.12
  • Loading branch information
gre authored Jun 19, 2018
2 parents 142d9d9 + 1de93c6 commit 0b97467
Show file tree
Hide file tree
Showing 53 changed files with 729 additions and 380 deletions.
156 changes: 118 additions & 38 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,81 +5,161 @@

:warning: Disclaimer: this project is under active development. Use at your own risks.

## Installation
<img src="/static/docs/ledgerLogo.png" width="200"/>

#### Requirements
> Ledger Live Desktop is a new generation Ledger Wallet application build with React, Redux and Electron to run natively on the web. The main goal of the app is to provide our users with a single wallet for all crypto currencies supported by our devices. To learn more check out [Ledger](https://www.ledgerwallet.com/?utm_source=redirection&utm_medium=variable)
Project has been tested with
## Architecture

- [NodeJS](https://nodejs.org) v9.3.0
- [Yarn](https://yarnpkg.com) v1.3.0
From one side Ledger Desktop app connected to the Blockchain via the in-house written C++ library - LibCore and from the other it communicates to the Ledger Hardware Device to securely sign all transactions.

<p align="center">
<img src="/static/docs/architecture.jpg" width="550"/>
</p>

## Setup

### Requirements

- [NodeJS](https://nodejs.org) LTS
- [Yarn](https://yarnpkg.com) LTS
- [Python](https://www.python.org/) v2.7.10 (used by [node-gyp](https://github.com/nodejs/node-gyp) to build native addons)
- You will also need a C++ compiler

#### Optional
### Optional

- `Museo Sans` font - for Ledger guys, [follow that link](https://drive.google.com/drive/folders/14R6kGFtx53DuqTyIOjnT7BGogzeyMSzN), download `museosans.zip` and extract it inside the `static/fonts/museosans` directory
- In the application we use `Museo Sans` font. To include it in the app, you need to have a zip file `museosans.zip` which you should extract and place inside the `static/fonts/museosans` directory

#### Setup
## Install

1. Install dependencies
1. Clone or fork the repo

```bash
git clone [email protected]:LedgerHQ/ledger-live-desktop.git
```

2. Install dependencies

```bash
yarn
```

2. Create `.env` file
## Run

Launch the app

```bash
# ENV VARIABLES
# -------------
yarn start
```

## Build

# Where errors will be tracked (you may not want to edit this line)
# SENTRY_URL=
```bash
# Build & package the whole app
# Creates a .dmg for Mac, .exe installer for Windows, or .AppImage for Linux
# Output files will be created in dist/ folder
yarn dist
```

# OPTIONAL ENV VARIABLES
# ----------------------
**Note:** Use `yarn dist:dir` to speed up the process: it will skip the packaging step. Handy for debugging builds. You can also use `BUNDLE_ANALYZER=1 yarn dist:dir` to generate [webpack-bundle-analyzer](https://github.com/webpack-contrib/webpack-bundle-analyzer) report.

# API base url, fallback to our API if not set
API_BASE_URL=http://...
---

# Setup device debug mode
DEBUG_DEVICE=0
## Config (optional helpers)

# Developer tools position (used only in dev)
# can be one of: right, bottom, undocked, detach
DEV_TOOLS_MODE=bottom
### Environment variables

# Filter debug output
DEBUG=lwd*,-lwd:syncb
(you can use a .env or export environment variables)

# hide the dev window
```bash
DEV_TOOLS_MODE=bottom # devtools position Options: right, bottom, undocked, detach
HIDE_DEV_WINDOW=0

## flags for development purpose
DEBUG_DEVICE=1
DEBUG_NETWORK=1
DEBUG_COMMANDS=1
DEBUG_DB=1
DEBUG_ACTION=1
DEBUG_TAB_KEY=1
DEBUG_LIBCORE=1
DEBUG_WS=1
LEDGER_RESET_ALL=1
LEDGER_DEBUG_ALL_LANGS=1
SKIP_GENUINE=1
SKIP_ONBOARDING=1
SHOW_LEGACY_NEW_ACCOUNT=1
HIGHLIGHT_I18N=1

## constants
GET_CALLS_TIMEOUT=30000
GET_CALLS_RETRY=2
SYNC_MAX_CONCURRENT=6
SYNC_BOOT_DELAY=2000
SYNC_ALL_INTERVAL=60000
CHECK_APP_INTERVAL_WHEN_INVALID=600
CHECK_APP_INTERVAL_WHEN_VALID=1200
CHECK_UPDATE_DELAY=5000
DEVICE_DISCONNECT_DEBOUNCE=500
```

#### Development commands
### Launch storybook

```bash
# Launch the app
yarn start
We use [storybook](https://storybook.js.org/) for UI development.

# Launch the storybook
```bash
yarn storybook
```

# Code quality checks
### Run code quality checks

```bash
yarn lint # launch eslint
yarn prettier # launch prettier
yarn flow # launch flow
yarn test # launch unit tests
```

#### Building from source
### Programmaically reset hard the app

Stop the app and to clean accounts, settings, etc, run

```bash
# Build & package the whole app
# Creates a .dmg for Mac, .exe installer for Windows, or .AppImage for Linux
# Output files will be created in dist/ folder
yarn dist
rm -rf ~/Library/Application\ Support/Electron/
```

**Note:** Use `yarn dist:dir` to speed up the process: it will skip the packaging step. Handy for debugging builds. You can also use `BUNDLE_ANALYZER=1 yarn dist:dir` to generate [webpack-bundle-analyzer](https://github.com/webpack-contrib/webpack-bundle-analyzer) report.
## File structure

```
.
├── dist : output folder generate by the build
├── scripts : commands (for building, releasing,...)
├── src
│   ├── internals : code that run on the 'internal' thread.
│   ├── main : code that run on the 'main' thread.
│   ├── renderer : code that run on the 'renderer' thread
│   ├── components : all the React components
| └── modals : sub levels for the modals
│   ├── api : related to HTTP APIs
│   ├── bridge : an abstraction on top of blockchains apis (libcore / js impls)
│   ├── commands : an abstraction to run code over the internal thread
│   ├── icons : all the icons of our app, as React components.
│   ├── config : contains the constants,...
│   ├── helpers : generic folder for our business logic (might be reorganized in the future)
│   ├── middlewares : redux middlewares
│   ├── actions : redux actions
│   ├── reducers : redux reducers
│   ├── sentry : for our bug tracker
│   ├── stories : for storybook
│   ├── styles : theme
│   ├── logger.js : abstraction for all our console.log s
│   └── types : global flow types
├── static
│   ├── docs
│   ├── fonts
│   ├── i18n
│   ├── images
│   └── videos
├── webpack : build configuration
└── yarn.lock
```
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@
"@ledgerhq/hw-transport": "^4.13.0",
"@ledgerhq/hw-transport-node-hid": "^4.13.0",
"@ledgerhq/ledger-core": "2.0.0-rc.1",
"@ledgerhq/live-common": "2.30.0",
"@ledgerhq/live-common": "2.31.0",
"async": "^2.6.1",
"axios": "^0.18.0",
"babel-runtime": "^6.26.0",
Expand Down Expand Up @@ -96,6 +96,7 @@
"secp256k1": "3.3.1",
"semaphore": "^1.1.0",
"semver": "^5.5.0",
"smoothscroll-polyfill": "^0.4.3",
"source-map": "0.7.3",
"source-map-support": "^0.5.4",
"styled-components": "^3.3.2",
Expand Down
5 changes: 2 additions & 3 deletions src/api/Ledger.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
// @flow
import type { Currency } from '@ledgerhq/live-common/lib/types'

const BASE_URL = process.env.LEDGER_REST_API_BASE || 'https://api.ledgerwallet.com/'
import { LEDGER_REST_API_BASE } from 'config/constants'

export const blockchainBaseURL = ({ ledgerExplorerId }: Currency): ?string =>
ledgerExplorerId ? `${BASE_URL}blockchain/v2/${ledgerExplorerId}` : null
ledgerExplorerId ? `${LEDGER_REST_API_BASE}blockchain/v2/${ledgerExplorerId}` : null
12 changes: 4 additions & 8 deletions src/api/Ripple.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
// @flow
import logger from 'logger'
import { RippleAPI } from 'ripple-lib'
import type { CryptoCurrency } from '@ledgerhq/live-common/lib/types'
import {
parseCurrencyUnit,
getCryptoCurrencyById,
Expand All @@ -10,14 +9,11 @@ import {

const rippleUnit = getCryptoCurrencyById('ripple').units[0]

const apiEndpoint = {
ripple: 'wss://s1.ripple.com',
}
export const defaultEndpoint = 'wss://s2.ripple.com'

export const apiForCurrency = (currency: CryptoCurrency) => {
const api = new RippleAPI({
server: apiEndpoint[currency.id],
})
export const apiForEndpointConfig = (endpointConfig: ?string = null) => {
const server = endpointConfig || defaultEndpoint
const api = new RippleAPI({ server })
api.on('error', (errorCode, errorMessage) => {
logger.warn(`Ripple API error: ${errorCode}: ${errorMessage}`)
})
Expand Down
33 changes: 21 additions & 12 deletions src/bridge/RippleJSBridge.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ import { getDerivations } from 'helpers/derivations'
import getAddress from 'commands/getAddress'
import signTransaction from 'commands/signTransaction'
import {
apiForCurrency,
apiForEndpointConfig,
defaultEndpoint,
parseAPIValue,
parseAPICurrencyObject,
formatAPICurrencyXRP,
Expand Down Expand Up @@ -47,7 +48,7 @@ const EditAdvancedOptions = ({ onChange, value }: EditProps<Transaction>) => (
)

async function signAndBroadcast({ a, t, deviceId, isCancelled, onSigned, onOperationBroadcasted }) {
const api = apiForCurrency(a.currency)
const api = apiForEndpointConfig(a.endpointConfig)
try {
await api.connect()
const amount = formatAPICurrencyXRP(t.amount)
Expand Down Expand Up @@ -217,10 +218,11 @@ const txToOperation = (account: Account) => ({
return op
}

const getServerInfo = (perCurrencyId => currency => {
if (perCurrencyId[currency.id]) return perCurrencyId[currency.id]()
const getServerInfo = (map => endpointConfig => {
if (!endpointConfig) endpointConfig = ''
if (map[endpointConfig]) return map[endpointConfig]()
const f = throttle(async () => {
const api = apiForCurrency(currency)
const api = apiForEndpointConfig(endpointConfig)
try {
await api.connect()
const res = await api.getServerInfo()
Expand All @@ -232,7 +234,7 @@ const getServerInfo = (perCurrencyId => currency => {
api.disconnect()
}
}, 60000)
perCurrencyId[currency.id] = f
map[endpointConfig] = f
return f()
})({})

Expand All @@ -244,10 +246,10 @@ const RippleJSBridge: WalletBridge<Transaction> = {
}

async function main() {
const api = apiForCurrency(currency)
const api = apiForEndpointConfig()
try {
await api.connect()
const serverInfo = await getServerInfo(currency)
const serverInfo = await getServerInfo()
const ledgers = serverInfo.completeLedgers.split('-')
const minLedgerVersion = Number(ledgers[0])
const maxLedgerVersion = Number(ledgers[1])
Expand Down Expand Up @@ -342,19 +344,19 @@ const RippleJSBridge: WalletBridge<Transaction> = {
return { unsubscribe }
},

synchronize: ({ currency, freshAddress, blockHeight }) =>
synchronize: ({ endpointConfig, freshAddress, blockHeight }) =>
Observable.create(o => {
let finished = false
const unsubscribe = () => {
finished = true
}

async function main() {
const api = apiForCurrency(currency)
const api = apiForEndpointConfig(endpointConfig)
try {
await api.connect()
if (finished) return
const serverInfo = await getServerInfo(currency)
const serverInfo = await getServerInfo(endpointConfig)
if (finished) return
const ledgers = serverInfo.completeLedgers.split('-')
const minLedgerVersion = Number(ledgers[0])
Expand Down Expand Up @@ -456,7 +458,7 @@ const RippleJSBridge: WalletBridge<Transaction> = {
isValidTransaction: (a, t) => (t.amount > 0 && t.recipient && true) || false,

canBeSpent: async (a, t) => {
const r = await getServerInfo(a.currency)
const r = await getServerInfo(a.endpointConfig)
return t.amount + t.fee + parseAPIValue(r.validatedLedger.reserveBaseXRP) <= a.balance
},

Expand Down Expand Up @@ -495,6 +497,13 @@ const RippleJSBridge: WalletBridge<Transaction> = {
),
),
}),

getDefaultEndpointConfig: () => defaultEndpoint,

validateEndpointConfig: async endpointConfig => {
const api = apiForEndpointConfig(endpointConfig)
await api.connect()
},
}

export default RippleJSBridge
3 changes: 3 additions & 0 deletions src/bridge/types.js
Original file line number Diff line number Diff line change
Expand Up @@ -111,4 +111,7 @@ export interface WalletBridge<Transaction> {
// Implement an optimistic response for signAndBroadcast.
// you likely should add the operation in account.pendingOperations but maybe you want to clean it (because maybe some are replaced / cancelled by this one?)
addPendingOperation?: (account: Account, optimisticOperation: Operation) => Account;

getDefaultEndpointConfig?: () => string;
validateEndpointConfig?: (endpointConfig: string) => Promise<void>;
}
2 changes: 1 addition & 1 deletion src/commands/getIsGenuine.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { fromPromise } from 'rxjs/observable/fromPromise'
import getIsGenuine from 'helpers/devices/getIsGenuine'
import { withDevice } from 'helpers/deviceAccess'

type Input = *
type Input = * // FIXME !
type Result = string

const cmd: Command<Input, Result> = createCommand('getIsGenuine', ({ devicePath, targetId }) =>
Expand Down
Loading

0 comments on commit 0b97467

Please sign in to comment.