Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ionic v5 #1

Draft
wants to merge 18 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions capacitor.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { CapacitorConfig } from '@capacitor/cli'

const config: CapacitorConfig = {
appId: 'com.oasisprotocol.wallet',
appName: 'Oasis Wallet',
webDir: 'build',
server: {
androidScheme: 'https',
},
}

export default config
6 changes: 5 additions & 1 deletion extension/src/popup/routes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import React from 'react'
import { RouteObject } from 'react-router-dom'
import { App } from 'app'
import { ConnectDevicePage } from 'app/pages/ConnectDevicePage'
import { OpenWalletPageWebExtension } from 'app/pages/OpenWalletPage/webextension'
import { FromLedgerWebExtension, OpenWalletPageWebExtension } from 'app/pages/OpenWalletPage/webextension'
import { commonRoutes } from '../../../src/commonRoutes'

export const routes: RouteObject[] = [
Expand All @@ -21,4 +21,8 @@ export const routes: RouteObject[] = [
path: 'open-wallet/connect-device',
element: <ConnectDevicePage />,
},
{
path: 'open-wallet/ledger',
element: <FromLedgerWebExtension />,
},
]
10 changes: 9 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,9 @@
"lint-docs": "markdownlint --ignore '**/node_modules/**' '**/*.md'",
"extract-messages": "rm src/locales/en/translation.json && i18next-scanner --config=internals/extractMessages/i18next-scanner.config.js",
"fix-grommet-icons-types": "node ./internals/scripts/fix-grommet-icons-types.js",
"print-extension-dev-csp": "node ./internals/scripts/print-extension-dev-csp.js"
"print-extension-dev-csp": "node ./internals/scripts/print-extension-dev-csp.js",
"android": "yarn build && yarn cap run android -l --external",
"ios": "yarn build && yarn cap run ios -l --external"
},
"browserslist": {
"production": [
Expand All @@ -53,11 +55,16 @@
"report-dir": "cypress-coverage"
},
"dependencies": {
"@capacitor-community/bluetooth-le": "3.0.0",
"@capacitor/android": "5.0.4",
"@capacitor/core": "5.0.4",
"@capacitor/ios": "5.0.4",
"@ethereumjs/util": "9.0.0",
"@ledgerhq/hw-transport-webusb": "6.27.19",
"@metamask/jazzicon": "2.0.0",
"@oasisprotocol/client": "0.1.1-alpha.2",
"@oasisprotocol/client-rt": "0.2.1-alpha.2",
"@oasisprotocol/ionic-ledger-hw-transport-ble": "1.0.0-beta",
"@oasisprotocol/ledger": "1.0.0",
"@reduxjs/toolkit": "1.9.7",
"base64-arraybuffer": "1.0.2",
Expand Down Expand Up @@ -90,6 +97,7 @@
"webextension-polyfill": "0.10.0"
},
"devDependencies": {
"@capacitor/cli": "5.0.4",
"@cypress/code-coverage": "3.12.4",
"@parcel/config-webextension": "2.9.3",
"@parcel/packager-raw-url": "2.9.3",
Expand Down
1 change: 1 addition & 0 deletions playwright/tests/extension.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ test.describe('The extension popup should load', () => {

test('ask for USB permissions in ledger popup', async ({ page, context, extensionId }) => {
await page.goto(`chrome-extension://${extensionId}/${popupFile}#/open-wallet`)
await page.getByRole('button', { name: /Ledger/i }).click()
const popupPromise = context.waitForEvent('page')
await page.getByRole('button', { name: /Grant access to your Ledger/i }).click()
const popup = await popupPromise
Expand Down
2 changes: 1 addition & 1 deletion playwright/tests/ledger.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ test.describe('Ledger', () => {
expect((await page.request.head('/')).headers()).toHaveProperty('permissions-policy')
await expectNoErrorsInConsole(page)

await page.goto('/open-wallet/ledger')
await page.goto('/open-wallet/ledger/usb')
await page.getByRole('button', { name: 'Select accounts to open' }).click()
await expect(page.getByText('error').or(page.getByText('fail'))).toBeHidden()
})
Expand Down
Binary file added resources/android/icon/drawable-hdpi-icon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added resources/android/icon/drawable-ldpi-icon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added resources/android/icon/drawable-mdpi-icon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added resources/android/icon/drawable-xhdpi-icon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added resources/android/icon/drawable-xxhdpi-icon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added resources/android/icon/drawable-xxxhdpi-icon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added resources/android/icon/hdpi-foreground.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added resources/android/icon/mdpi-foreground.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added resources/android/icon/xhdpi-foreground.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added resources/android/icon/xxhdpi-foreground.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added resources/android/icon/xxxhdpi-foreground.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added resources/icon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added resources/splash.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 4 additions & 0 deletions src/app/components/ErrorFormatter/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,10 @@ export function ErrorFormatter(props: Props) {
message,
},
),
[WalletErrors.BluetoothTransportNotSupported]: t(
'errors.bluetoothTransportNotSupported',
'Your device does not support Bluetooth.',
),
}

const error = errorMap[props.code]
Expand Down
3 changes: 2 additions & 1 deletion src/app/components/ImportAccountsStepFormatter/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,10 @@ export const ImportAccountsStepFormatter = memo((props: Props) => {

const stepMap: { [code in Step]: string } = {
[Step.Idle]: t('ledger.steps.idle', 'Idle'),
[Step.OpeningUSB]: t('ledger.steps.openingUsb', 'Opening Ledger through USB'),
[Step.AccessingLedger]: t('ledger.steps.openingUsb', 'Opening Ledger through USB'),
[Step.LoadingAccounts]: t('ledger.steps.loadingAccounts', 'Loading account details'),
[Step.LoadingBalances]: t('ledger.steps.loadingBalances', 'Loading balance details'),
[Step.LoadingBleDevices]: 'Loading devices',
}

const message = stepMap[step]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ export interface DerivationFormatterProps {
export const DerivationFormatter = (props: DerivationFormatterProps) => {
const { t } = useTranslation()
const walletTypes: { [type in WalletType]: string } = {
[WalletType.Ledger]: t('toolbar.wallets.type.ledger', 'Ledger'),
[WalletType.UsbLedger]: t('toolbar.wallets.type.usbLedger', 'USB Ledger'),
[WalletType.BleLedger]: t('toolbar.wallets.type.bluetoothLedger', 'BLE Ledger'),
[WalletType.Mnemonic]: t('toolbar.wallets.type.mnemonic', 'Mnemonic'),
[WalletType.PrivateKey]: t('toolbar.wallets.type.privateKey', 'Private key'),
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ describe('<AccountSelector />', () => {
address: 'oasis1qq3xrq0urs8qcffhvmhfhz4p0mu7ewc8rscnlwxe',
balance: { available: '100', debonding: '0', delegations: '0', total: '100' },
publicKey: '00',
type: WalletType.Ledger,
type: WalletType.UsbLedger,
},
},
},
Expand Down
42 changes: 36 additions & 6 deletions src/app/lib/__tests__/ledger.test.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,16 @@
import { Ledger, LedgerSigner, requestDevice } from '../ledger'
import { canAccessBle, Ledger, LedgerSigner, requestDevice } from '../ledger'
import OasisApp from '@oasisprotocol/ledger'
import { WalletError, WalletErrors } from 'types/errors'
import { Wallet, WalletType } from 'app/state/wallet/types'
import { isSupported, requestLedgerDevice } from '@ledgerhq/hw-transport-webusb/lib-es/webusb'
import BleTransport from '@oasisprotocol/ionic-ledger-hw-transport-ble/lib'

jest.mock('@ledgerhq/hw-transport-webusb/lib-es/webusb')
jest.mock('@oasisprotocol/ionic-ledger-hw-transport-ble/lib', () => {
return {
isEnabled: jest.fn(),
}
})

jest.mock('@oasisprotocol/ledger', () => ({
...(jest.createMockFromModule('@oasisprotocol/ledger') as any),
Expand All @@ -31,6 +37,30 @@ describe('Ledger Library', () => {
jest.resetAllMocks()
})

describe('BLE Ledger', () => {
it('should support Bluetooth', async () => {
;(BleTransport.isEnabled as jest.Mock).mockResolvedValue(true)
Object.defineProperty(window.navigator, 'bluetooth', {
writable: true,
value: {
requestLEScan: jest.fn(),
},
})

const canAccessBluetooth = await canAccessBle()
expect(canAccessBluetooth).toBe(true)
})

it('should not throw if platform does not support Bluetooth', async () => {
;(BleTransport.isEnabled as jest.Mock).mockRejectedValue(
new Error('Platform does not support Bluetooth'),
)

const canAccessBluetooth = await canAccessBle()
expect(canAccessBluetooth).toBe(false)
})
})

describe('Ledger', () => {
it('enumerateAccounts should pass when Oasis App is open', async () => {
mockAppIsOpen('Oasis')
Expand Down Expand Up @@ -120,14 +150,14 @@ describe('Ledger Library', () => {

it('Should fail if the wallet does not have a path', () => {
const openWallet = () => {
new LedgerSigner({ type: WalletType.Ledger } as Wallet)
new LedgerSigner({ type: WalletType.UsbLedger } as Wallet)
}
expect(openWallet).toThrow(/ not a ledger wallet/)
})

it('Should fail without USB transport', async () => {
const signer = new LedgerSigner({
type: WalletType.Ledger,
type: WalletType.UsbLedger,
path: [44, 474, 0, 0, 0],
pathDisplay: `m/44'/474'/0'/0'/0'`,
publicKey: '00',
Expand All @@ -140,7 +170,7 @@ describe('Ledger Library', () => {

it('Should return the public key', () => {
const signer = new LedgerSigner({
type: WalletType.Ledger,
type: WalletType.UsbLedger,
path: [44, 474, 0, 0, 0],
pathDisplay: `m/44'/474'/0'/0'/0'`,
publicKey: 'aabbcc',
Expand All @@ -160,7 +190,7 @@ describe('Ledger Library', () => {
sign.mockResolvedValueOnce({ return_code: 0x6986, error_message: '' })

const signer = new LedgerSigner({
type: WalletType.Ledger,
type: WalletType.UsbLedger,
path: [44, 474, 0, 0, 0],
pathDisplay: `m/44'/474'/0'/0'/0'`,
publicKey: '00',
Expand All @@ -187,7 +217,7 @@ describe('Ledger Library', () => {
})

const signer = new LedgerSigner({
type: WalletType.Ledger,
type: WalletType.UsbLedger,
path: [44, 474, 0, 0, 0],
pathDisplay: `m/44'/474'/0'/0'/0'`,
publicKey: '00',
Expand Down
15 changes: 13 additions & 2 deletions src/app/lib/ledger.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import { ContextSigner } from '@oasisprotocol/client/dist/signature'
import OasisApp, { successOrThrow } from '@oasisprotocol/ledger'
import { Response } from '@oasisprotocol/ledger/dist/types'
import { Wallet, WalletType } from 'app/state/wallet/types'
import { LedgerWalletType, Wallet, WalletType } from 'app/state/wallet/types'
import { WalletError, WalletErrors } from 'types/errors'
import { hex2uint, publicKeyToAddress } from './helpers'
import type Transport from '@ledgerhq/hw-transport'
import { isSupported, requestLedgerDevice } from '@ledgerhq/hw-transport-webusb/lib-es/webusb'
import BleTransport from '@oasisprotocol/ionic-ledger-hw-transport-ble/lib'
import { Capacitor } from '@capacitor/core'

interface LedgerAccount {
publicKey: Uint8Array
Expand All @@ -17,6 +19,13 @@ export async function canAccessNavigatorUsb(): Promise<boolean> {
return await isSupported()
}

export async function canAccessBle(): Promise<boolean> {
const hasBLE = await BleTransport.isEnabled().catch(() => false)
// Scan depends on requestLEScan method, which is not available on the web(feature flag)
const hasLEScan = Capacitor.isNativePlatform() || !!navigator?.bluetooth?.requestLEScan
return hasBLE && hasLEScan
}

export async function requestDevice(): Promise<USBDevice | undefined> {
if (await isSupported()) {
return await requestLedgerDevice()
Expand Down Expand Up @@ -99,13 +108,15 @@ export class LedgerSigner implements ContextSigner {
protected transport?: Transport
protected path: number[]
protected publicKey: Uint8Array
transportType: LedgerWalletType

constructor(wallet: Wallet) {
if (!wallet.path || wallet.type !== WalletType.Ledger) {
if (!wallet.path || (wallet.type !== WalletType.UsbLedger && wallet.type !== WalletType.BleLedger)) {
throw new Error('Given wallet is not a ledger wallet')
}
this.path = wallet.path
this.publicKey = hex2uint(wallet.publicKey)
this.transportType = wallet.type
}

public setTransport(transport: Transport) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -252,7 +252,7 @@ exports[`<ConnectDevicePage /> should render component 1`] = `
>
<ol>
<li>
ledger.instructionSteps.connectLedger
ledger.instructionSteps.connectUsbLedger
</li>
<li>
ledger.instructionSteps.closeLedgerLive
Expand Down
3 changes: 2 additions & 1 deletion src/app/pages/ConnectDevicePage/__tests__/index.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import userEvent from '@testing-library/user-event'
import { requestDevice } from 'app/lib/ledger'
import { importAccountsActions } from 'app/state/importaccounts'
import { ConnectDevicePage } from '..'
import { WalletType } from '../../../state/wallet/types'

jest.mock('app/lib/ledger')

Expand Down Expand Up @@ -31,7 +32,7 @@ describe('<ConnectDevicePage />', () => {
expect(screen.getByLabelText('Status is okay')).toBeInTheDocument()
expect(screen.queryByRole('button')).not.toBeInTheDocument()
expect(mockDispatch).toHaveBeenCalledWith({
payload: undefined,
payload: WalletType.UsbLedger,
type: importAccountsActions.enumerateAccountsFromLedger.type,
})
})
Expand Down
8 changes: 6 additions & 2 deletions src/app/pages/ConnectDevicePage/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { WalletErrors } from 'types/errors'
import { importAccountsActions } from 'app/state/importaccounts'
import { requestDevice } from 'app/lib/ledger'
import logotype from '../../../../public/logo192.png'
import { WalletType } from '../../state/wallet/types'

type ConnectionStatus = 'connected' | 'disconnected' | 'connecting' | 'error'
type ConnectionStatusIconPros = {
Expand Down Expand Up @@ -53,7 +54,7 @@ export function ConnectDevicePage() {
const device = await requestDevice()
if (device) {
setConnection('connected')
dispatch(importAccountsActions.enumerateAccountsFromLedger())
dispatch(importAccountsActions.enumerateAccountsFromLedger(WalletType.UsbLedger))
}
} catch {
setConnection('error')
Expand Down Expand Up @@ -82,7 +83,10 @@ export function ConnectDevicePage() {
<Box gap="medium">
<ol>
<li>
{t('ledger.instructionSteps.connectLedger', 'Connect your Ledger device to the computer')}
{t(
'ledger.instructionSteps.connectUsbLedger',
'Connect your USB Ledger device to the computer',
)}
</li>
<li>{t('ledger.instructionSteps.closeLedgerLive', 'Close Ledger Live app on the computer')}</li>
<li>{t('ledger.instructionSteps.openOasisApp', 'Open the Oasis App on your Ledger device')}</li>
Expand Down
Loading
Loading