From 97c84780c0c16cd710e77dadc01a6d46041ea9ff Mon Sep 17 00:00:00 2001 From: sh-wallet <137136919+sh-wallet@users.noreply.github.com> Date: Wed, 15 Jan 2025 13:48:06 +0530 Subject: [PATCH 1/3] chore: ledger mobile handling --- packages/background/src/ledger/ledger.ts | 1 - packages/background/src/ledger/options.ts | 1 + packages/background/src/ledger/service.ts | 260 +++++++++++++++--- packages/mobile/src/background/background.ts | 1 + packages/mobile/src/modals/ledger/index.tsx | 14 +- .../src/modals/ledger/ledger-selector.tsx | 28 +- packages/mobile/src/modals/sign/index.tsx | 2 +- .../src/screens/register/ledger/index.tsx | 14 +- 8 files changed, 255 insertions(+), 66 deletions(-) diff --git a/packages/background/src/ledger/ledger.ts b/packages/background/src/ledger/ledger.ts index b8c40ca618..7a1e19bc6b 100644 --- a/packages/background/src/ledger/ledger.ts +++ b/packages/background/src/ledger/ledger.ts @@ -122,7 +122,6 @@ export class Ledger { if (transport) { await transport.close(); } - console.log("Testing1::", e.message); if ( e.message === "Device is on screen saver" || diff --git a/packages/background/src/ledger/options.ts b/packages/background/src/ledger/options.ts index 8253674efa..64a21b6745 100644 --- a/packages/background/src/ledger/options.ts +++ b/packages/background/src/ledger/options.ts @@ -4,6 +4,7 @@ export type TransportIniter = (...args: any[]) => Promise; export interface LedgerOptions { defaultMode: string; + platform: string; transportIniters: { [mode: string]: TransportIniter; }; diff --git a/packages/background/src/ledger/service.ts b/packages/background/src/ledger/service.ts index b1d2d7ad97..4fc6db5529 100644 --- a/packages/background/src/ledger/service.ts +++ b/packages/background/src/ledger/service.ts @@ -7,6 +7,8 @@ import { LedgerWebUSBIniter, } from "./ledger"; +import delay from "delay"; + import { APP_PORT, Env } from "@keplr-wallet/router"; import { BIP44HDPath } from "../keyring"; import { KVStore } from "@keplr-wallet/common"; @@ -17,6 +19,8 @@ import { EthSignType } from "@keplr-wallet/types"; import { ErrFailedInit, ErrPublicKeyUnmatched } from "./types"; export class LedgerService { + private previousInitAborter: ((e: Error) => void) | undefined; + protected options: LedgerOptions; protected interactionService!: InteractionService; @@ -28,6 +32,7 @@ export class LedgerService { this.options = { defaultMode: options.defaultMode || "webusb", transportIniters: options.transportIniters ?? {}, + platform: options.platform || "extension", }; if (!this.options.transportIniters["webusb"]) { @@ -68,13 +73,21 @@ export class LedgerService { return await this.useLedger( env, ledgerApp, - async (ledger) => { + async (ledger, retryCount) => { try { // Specific apps only support each coin type for app. return await ledger.getPublicKey(ledgerApp, bip44HDPath); } catch (e) { console.log(e); throw e; + } finally { + // Notify UI Ledger pubkey derivation succeeded only when Ledger initialization is tried again. + if (retryCount > 0) { + this.interactionService.dispatchEvent(APP_PORT, "ledger-init", { + event: "get-pubkey", + success: true, + }); + } } }, cosmosLikeApp @@ -93,7 +106,7 @@ export class LedgerService { return await this.useLedger( env, ledgerApp, - async (ledger) => { + async (ledger, retryCount: number) => { try { this.interactionService.dispatchEvent(APP_PORT, "ledger-sign", { event: "sign-txn-guide", @@ -117,13 +130,25 @@ export class LedgerService { event: "sign-txn-guide", isShow: false, }); + if (retryCount > 0) { + this.interactionService.dispatchEvent(APP_PORT, "ledger-init", { + event: "sign", + success: true, + }); + } return signature; } catch (e) { this.interactionService.dispatchEvent(APP_PORT, "ledger-sign", { event: "sign-txn-guide", isShow: false, }); - + // Notify UI Ledger signing failed only when Ledger initialization is tried again. + if (retryCount > 0) { + this.interactionService.dispatchEvent(APP_PORT, "ledger-init", { + event: "sign", + success: false, + }); + } throw e; } }, @@ -140,50 +165,74 @@ export class LedgerService { ): Promise { const ledgerApp = LedgerApp.Ethereum; - return await this.useLedger(env, ledgerApp, async (ledger) => { - try { - this.interactionService.dispatchEvent(APP_PORT, "ledger-sign", { - event: "sign-txn-guide", - isShow: true, - }); - const pubKey = await ledger.getPublicKey(ledgerApp, bip44HDPath); - if ( - Buffer.from(expectedPubKey).toString("hex") !== - Buffer.from(pubKey).toString("hex") - ) { - throw new LedgerInitError( - LedgerInitErrorOn.App, - ErrPublicKeyUnmatched, - "Unmatched public key" + return await this.useLedger( + env, + ledgerApp, + async (ledger, retryCount: number) => { + try { + this.interactionService.dispatchEvent(APP_PORT, "ledger-sign", { + event: "sign-txn-guide", + isShow: true, + }); + const pubKey = await ledger.getPublicKey(ledgerApp, bip44HDPath); + if ( + Buffer.from(expectedPubKey).toString("hex") !== + Buffer.from(pubKey).toString("hex") + ) { + throw new LedgerInitError( + LedgerInitErrorOn.App, + ErrPublicKeyUnmatched, + "Unmatched public key" + ); + } + const signature = await ledger.signEthereum( + bip44HDPath, + type, + message ); + // Notify UI Ledger signing succeeded only when Ledger initialization is tried again. + this.interactionService.dispatchEvent(APP_PORT, "ledger-sign", { + event: "sign-txn-guide", + isShow: false, + }); + if (retryCount > 0) { + this.interactionService.dispatchEvent(APP_PORT, "ledger-init", { + event: "sign", + success: true, + }); + } + return signature; + } catch (e) { + this.interactionService.dispatchEvent(APP_PORT, "ledger-sign", { + event: "sign-txn-guide", + isShow: false, + }); + // Notify UI Ledger signing failed only when Ledger initialization is tried again. + if (retryCount > 0) { + this.interactionService.dispatchEvent(APP_PORT, "ledger-init", { + event: "sign", + success: false, + }); + } + throw e; } - const signature = await ledger.signEthereum(bip44HDPath, type, message); - // Notify UI Ledger signing succeeded only when Ledger initialization is tried again. - this.interactionService.dispatchEvent(APP_PORT, "ledger-sign", { - event: "sign-txn-guide", - isShow: false, - }); - return signature; - } catch (e) { - this.interactionService.dispatchEvent(APP_PORT, "ledger-sign", { - event: "sign-txn-guide", - isShow: false, - }); - throw e; } - }); + ); } async useLedger( env: Env, ledgerApp: LedgerApp, - fn: (ledger: Ledger) => Promise, + fn: (ledger: Ledger, retryCount: number) => Promise, cosmosLikeApp: string = "Cosmos" ): Promise { - let ledger: { ledger: Ledger } | undefined; + let ledger: { ledger: Ledger; retryCount: number } | undefined; try { - ledger = await this.initLedger(env, ledgerApp, cosmosLikeApp); - return await fn(ledger.ledger); + ledger = + this.options.platform === "extension" + ? await this.initLedger(env, ledgerApp, cosmosLikeApp) + : await this.initLedgerMobile(env, ledgerApp, cosmosLikeApp); + return await fn(ledger.ledger, ledger.retryCount); } finally { if (ledger) { await ledger.ledger.close(); @@ -195,9 +244,8 @@ export class LedgerService { env: Env, ledgerApp: LedgerApp, cosmosLikeApp: string = "Cosmos" - ): Promise<{ ledger: Ledger }> { - const initArgs: any[] = []; - + ): Promise<{ ledger: Ledger; retryCount: number }> { + const retryCount = 0; while (true) { const mode = await this.getMode(); try { @@ -212,12 +260,13 @@ export class LedgerService { const ledger = await Ledger.init( transportIniter, - initArgs, + undefined, ledgerApp, cosmosLikeApp ); return { ledger, + retryCount, }; } catch (e) { this.interactionService.dispatchEventAndData( @@ -236,6 +285,137 @@ export class LedgerService { } } + async initLedgerMobile( + env: Env, + ledgerApp: LedgerApp, + cosmosLikeApp: string = "Cosmos" + ): Promise<{ ledger: Ledger; retryCount: number }> { + if (this.previousInitAborter) { + this.previousInitAborter( + new Error( + "New ledger request occurred before the ledger was initialized" + ) + ); + } + + const aborter = (() => { + let _reject: (reason?: any) => void | undefined; + + return { + wait: () => { + return new Promise((_, reject) => { + _reject = reject; + }); + }, + abort: (e: Error) => { + if (_reject) { + _reject(e); + } + }, + }; + })(); + + // This ensures that the ledger connection is not executed concurrently. + // Without this, the prior signing request can be delivered to the ledger and possibly make a user take a mistake. + this.previousInitAborter = aborter.abort; + + let retryCount = 0; + let initArgs: any[] = []; + while (true) { + const mode = await this.getMode(); + try { + const transportIniter = this.options.transportIniters[mode]; + if (!transportIniter) { + throw new Error(`Unknown mode: ${mode}`); + } + + const ledger = await Ledger.init( + transportIniter, + initArgs, + ledgerApp, + cosmosLikeApp + ); + this.previousInitAborter = undefined; + return { + ledger, + retryCount, + }; + } catch (e) { + console.log(e); + + const timeoutAbortController = new AbortController(); + + try { + const promises: Promise[] = [ + (async () => { + const response = (await this.interactionService.waitApprove( + env, + "/ledger-grant", + "ledger-init", + { + event: "init-failed", + ledgerApp, + mode, + cosmosLikeApp, + }, + { + forceOpenWindow: false, + channel: "ledger", + } + )) as + | { + abort?: boolean; + initArgs?: any[]; + } + | undefined; + + if (response?.abort) { + throw new Error("Ledger init aborted"); + } + + if (response?.initArgs) { + initArgs = response.initArgs; + } + })(), + ]; + + promises.push( + (async () => { + let timeoutAborted = false; + // If ledger is not inited in 5 minutes, abort it. + try { + await delay(5 * 60 * 1000, { + signal: timeoutAbortController.signal, + }); + } catch (e) { + if (e.name === "AbortError") { + timeoutAborted = true; + } else { + throw e; + } + } + if (!timeoutAborted) { + this.interactionService.dispatchEvent(APP_PORT, "ledger-init", { + event: "init-aborted", + mode, + }); + throw new Error("Ledger init timeout"); + } + })() + ); + + promises.push(aborter.wait()); + + await Promise.race(promises); + } finally { + timeoutAbortController.abort(); + } + } + + retryCount++; + } + } + /** * Mode means that which transport should be used. * "webusb" and "webhid" are used in the extension environment (web). diff --git a/packages/mobile/src/background/background.ts b/packages/mobile/src/background/background.ts index a9856ed207..3549ab5958 100644 --- a/packages/mobile/src/background/background.ts +++ b/packages/mobile/src/background/background.ts @@ -67,6 +67,7 @@ const { initFn } = init( }, { defaultMode: "ble", + platform: "mobile", transportIniters: { ble: async (deviceId?: string) => { const lastDeviceId = await getLastUsedLedgerDeviceId(); diff --git a/packages/mobile/src/modals/ledger/index.tsx b/packages/mobile/src/modals/ledger/index.tsx index 4787659fed..bb17c09fe1 100644 --- a/packages/mobile/src/modals/ledger/index.tsx +++ b/packages/mobile/src/modals/ledger/index.tsx @@ -104,6 +104,13 @@ export const LedgerGranterModal: FunctionComponent<{ if (!resumed.current) { ledgerInitStore.abortAll(); } + + setBluetoothMode(BluetoothMode.Ledger); + setIsPairingText("Waiting for bluetooth signal..."); + setMainContent( + "press and hold two buttons at the same time and enter your pin" + ); + setIsPaired(false); }); useEffect(() => { @@ -161,10 +168,9 @@ export const LedgerGranterModal: FunctionComponent<{ next: (e: { type: string; descriptor: any }) => { if (e.type === "add") { const device = e.descriptor; - if (!_devices.find((d) => d.id === device.id)) { - console.log( - `Ledger device found (id: ${device.id}, name: ${device.name})` - ); + const isDevice = !_devices.find((d) => d.id === device.id); + + if (isDevice) { _devices = [ ..._devices, { diff --git a/packages/mobile/src/modals/ledger/ledger-selector.tsx b/packages/mobile/src/modals/ledger/ledger-selector.tsx index e098af846d..3f8a5322fb 100644 --- a/packages/mobile/src/modals/ledger/ledger-selector.tsx +++ b/packages/mobile/src/modals/ledger/ledger-selector.tsx @@ -1,4 +1,9 @@ -import { Ledger, LedgerApp, LedgerInitErrorOn } from "@keplr-wallet/background"; +import { + Ledger, + LedgerApp, + LedgerInitError, + LedgerInitErrorOn, +} from "@keplr-wallet/background"; import React, { FunctionComponent, useState } from "react"; import { useStyle } from "styles/index"; import { BluetoothMode } from "."; @@ -55,17 +60,14 @@ export const LedgerNanoBLESelector: FunctionComponent<{ setIsPairingText(`Paired with ${name}`); }, 2000); await ledger.close(); - setTimeout(function () { - onCanResume(); - setBluetoothMode(BluetoothMode.Ledger); - setIsPairingText("Waiting for bluetooth signal..."); - setMainContent( - "press and hold two buttons at the same time and enter your pin" - ); - setIsPaired(false); - }, 6000); + onCanResume(); } catch (e) { - console.log(e); + console.log( + "Ledger-Selector:", + e, + e.errorOn, + e instanceof LedgerInitError + ); if (e.errorOn != null) { initErrorOn = e.errorOn; if (initErrorOn === LedgerInitErrorOn.App) { @@ -79,9 +81,9 @@ export const LedgerNanoBLESelector: FunctionComponent<{ setIsConnecting(false); } } else { - initErrorOn = LedgerInitErrorOn.Unknown; + setMainContent("Please unlock ledger nano X"); + setIsConnecting(false); } - await TransportBLE.disconnect(deviceId); } }; diff --git a/packages/mobile/src/modals/sign/index.tsx b/packages/mobile/src/modals/sign/index.tsx index 6c2d10c781..8c5e70f882 100644 --- a/packages/mobile/src/modals/sign/index.tsx +++ b/packages/mobile/src/modals/sign/index.tsx @@ -352,7 +352,7 @@ export const SignModal: FunctionComponent<{ ); } } catch (error) { - console.log(error); + console.log("Sign:Error", error); } }} /> diff --git a/packages/mobile/src/screens/register/ledger/index.tsx b/packages/mobile/src/screens/register/ledger/index.tsx index 7175fb15a1..4fc8b66a9b 100644 --- a/packages/mobile/src/screens/register/ledger/index.tsx +++ b/packages/mobile/src/screens/register/ledger/index.tsx @@ -258,13 +258,18 @@ export const LedgerScreen: FunctionComponent = () => { }; const submit = handleSubmit(async () => { + analyticsStore.logEvent("register_next_click", { + registerType: "ledger", + accountType: "ledger", + pageName: "Register", + }); + setShowPassword(false); if (Platform.OS === "android" && location == undefined) { setLocationError("Location services are disabled"); return; } setIsCreating(true); - try { await registerConfig.createLedger( getValues("name"), @@ -293,7 +298,7 @@ export const LedgerScreen: FunctionComponent = () => { } catch (e) { ledgerInitStore.abortAll(); // Definitely, the error can be thrown when the ledger connection failed - console.log(e); + console.log("Ledger:Creation", e); } finally { setIsCreating(false); } @@ -576,11 +581,6 @@ export const LedgerScreen: FunctionComponent = () => { loading={isCreating} onPress={() => { submit(); - analyticsStore.logEvent("register_next_click", { - registerType: "ledger", - accountType: "ledger", - pageName: "Register", - }); }} disabled={!isBLEAvailable} /> From e2f275763216ad26b759007f0bb26d2864f9927b Mon Sep 17 00:00:00 2001 From: sh-wallet <137136919+sh-wallet@users.noreply.github.com> Date: Wed, 22 Jan 2025 14:12:21 +0530 Subject: [PATCH 2/3] chore: ext ledger permission handling --- packages/background/src/ledger/ledger.ts | 77 +-- packages/background/src/ledger/service.ts | 22 +- packages/background/src/ledger/types.ts | 1 + packages/background/src/side-panel/handler.ts | 4 +- .../components-v2/dropdown/style.module.scss | 2 +- .../tabs/tabsPanel-2/style.module.scss | 1 + .../fetch-extension/src/languages/en.json | 2 + .../pages-new/main/wallet-details/index.tsx | 4 +- .../src/pages-new/register/ledger/index.tsx | 21 +- .../pages-new/sign/details-tab.module.scss | 1 - .../src/pages-new/sign/index.tsx | 103 ++-- .../src/pages-new/sign/ledger-guide-box.tsx | 69 ++- .../src/pages/ledger-grant/index.tsx | 562 ++++++++++-------- .../src/pages/ledger-grant/style.module.scss | 226 +++++-- .../src/pages/ledger/grant.tsx | 373 ------------ .../src/pages/ledger/index.tsx | 373 +++++++++++- .../src/pages/ledger/style.module.scss | 5 +- .../src/pages/main/account.tsx | 4 +- .../src/modals/ledger/ledger-selector.tsx | 28 +- packages/mobile/src/router/rn-router.ts | 4 +- .../src/interaction-addon/handler.ts | 4 +- .../src/requester/extension.ts | 25 +- .../router-extension/src/router/extension.ts | 4 +- packages/router-mock/src/router/index.ts | 4 +- packages/router/src/error.ts | 4 +- packages/router/src/interfaces.ts | 2 +- 26 files changed, 1107 insertions(+), 818 deletions(-) delete mode 100644 packages/fetch-extension/src/pages/ledger/grant.tsx diff --git a/packages/background/src/ledger/ledger.ts b/packages/background/src/ledger/ledger.ts index 7a1e19bc6b..974a1dee2e 100644 --- a/packages/background/src/ledger/ledger.ts +++ b/packages/background/src/ledger/ledger.ts @@ -17,19 +17,15 @@ import { ErrFailedGetVersion, ErrFailedInit, ErrFailedSign, + ErrModuleLedgerSign, } from "./types"; +import { WalletError } from "@keplr-wallet/router"; export enum LedgerApp { Cosmos = "cosmos", Ethereum = "ethereum", } -export enum LedgerInitErrorOn { - Transport, - App, - Unknown, -} - export const LedgerWebUSBIniter: TransportIniter = async () => { return await TransportWebUSB.create(); }; @@ -38,19 +34,6 @@ export const LedgerWebHIDIniter: TransportIniter = async () => { return await TransportWebHID.create(); }; -export class LedgerInitError extends Error { - public readonly errorOn: LedgerInitErrorOn; - public readonly code: number; - - constructor(errorOn: LedgerInitErrorOn, code: number, message?: string) { - super(message); - this.errorOn = errorOn; - this.code = code; - - Object.setPrototypeOf(this, LedgerInitError.prototype); - } -} - export class Ledger { constructor( public readonly cosmosApp: CosmosApp | undefined = undefined, @@ -67,8 +50,8 @@ export class Ledger { try { transport = await transportIniter(...initArgs); } catch (e) { - throw new LedgerInitError( - LedgerInitErrorOn.Transport, + throw new WalletError( + ErrModuleLedgerSign, ErrFailedInit, "Connect and unlock your Ledger device." ); @@ -95,8 +78,8 @@ export class Ledger { // However, it is almost same as that the device is not unlocked to user-side. // So, handle this case as initializing failed in `Transport`. if (versionResponse.deviceLocked) { - throw new LedgerInitError( - LedgerInitErrorOn.Transport, + throw new WalletError( + ErrModuleLedgerSign, ErrCodeDeviceLocked, "Unlock your Ledger device." ); @@ -110,8 +93,8 @@ export class Ledger { console.log(e); } if (appName && appName !== cosmosLikeApp) { - throw new LedgerInitError( - LedgerInitErrorOn.App, + throw new WalletError( + ErrModuleLedgerSign, ErrCodeUnsupportedApp, "Invalid cosmos app selected" ); @@ -127,16 +110,16 @@ export class Ledger { e.message === "Device is on screen saver" || e.message?.includes("Unknown Status Code: 21781") ) { - throw new LedgerInitError( - LedgerInitErrorOn.Transport, + throw new WalletError( + ErrModuleLedgerSign, ErrCodeDeviceLocked, "Unlock your Ledger device." ); } - throw new LedgerInitError( - LedgerInitErrorOn.App, - ErrFailedInit, + throw new WalletError( + ErrModuleLedgerSign, + ErrCodeAppNotInitialised, `Open the ${cosmosLikeApp} app on Ledger and try again.` ); } @@ -151,8 +134,8 @@ export class Ledger { testMode: boolean; }> { if (!this.cosmosApp) { - throw new LedgerInitError( - LedgerInitErrorOn.App, + throw new WalletError( + ErrModuleLedgerSign, ErrCodeAppNotInitialised, "Please initialize Cosmos app first" ); @@ -161,8 +144,8 @@ export class Ledger { const result = await this.cosmosApp.getVersion(); if (result.error_message !== "No errors") { - throw new LedgerInitError( - LedgerInitErrorOn.App, + throw new WalletError( + ErrModuleLedgerSign, ErrFailedGetVersion, result.error_message ); @@ -181,8 +164,8 @@ export class Ledger { async getPublicKey(app: LedgerApp, fields: BIP44HDPath): Promise { if (app === LedgerApp.Ethereum) { if (!this.ethereumApp) { - throw new LedgerInitError( - LedgerInitErrorOn.App, + throw new WalletError( + ErrModuleLedgerSign, ErrCodeAppNotInitialised, "Please initialize Ethereum app first" ); @@ -197,12 +180,12 @@ export class Ledger { // Compress the public key return publicKeyConvert(pubKey, true); } catch (e: any) { - throw new LedgerInitError(LedgerInitErrorOn.App, e); + throw new WalletError(ErrModuleLedgerSign, e); } } else { if (!this.cosmosApp) { - throw new LedgerInitError( - LedgerInitErrorOn.App, + throw new WalletError( + ErrModuleLedgerSign, ErrCodeAppNotInitialised, "Please initialize Cosmos app first" ); @@ -214,8 +197,8 @@ export class Ledger { fields.addressIndex ); if (result.error_message !== "No errors") { - throw new LedgerInitError( - LedgerInitErrorOn.App, + throw new WalletError( + ErrModuleLedgerSign, ErrFailedGetPublicKey, result.error_message ); @@ -227,8 +210,8 @@ export class Ledger { async sign(fields: BIP44HDPath, message: Uint8Array): Promise { if (!this.cosmosApp) { - throw new LedgerInitError( - LedgerInitErrorOn.App, + throw new WalletError( + ErrModuleLedgerSign, ErrCodeAppNotInitialised, "Please initialize Cosmos app first" ); @@ -241,8 +224,8 @@ export class Ledger { message ); if (result.error_message !== "No errors") { - throw new LedgerInitError( - LedgerInitErrorOn.App, + throw new WalletError( + ErrModuleLedgerSign, ErrFailedSign, result.error_message ); @@ -258,8 +241,8 @@ export class Ledger { message: Uint8Array ) { if (!this.ethereumApp) { - throw new LedgerInitError( - LedgerInitErrorOn.App, + throw new WalletError( + ErrModuleLedgerSign, ErrCodeAppNotInitialised, "Please initialize Ethereum app first" ); diff --git a/packages/background/src/ledger/service.ts b/packages/background/src/ledger/service.ts index 4fc6db5529..7e6abcfaf6 100644 --- a/packages/background/src/ledger/service.ts +++ b/packages/background/src/ledger/service.ts @@ -1,22 +1,24 @@ import { Ledger, LedgerApp, - LedgerInitError, - LedgerInitErrorOn, LedgerWebHIDIniter, LedgerWebUSBIniter, } from "./ledger"; import delay from "delay"; -import { APP_PORT, Env } from "@keplr-wallet/router"; +import { APP_PORT, Env, WalletError } from "@keplr-wallet/router"; import { BIP44HDPath } from "../keyring"; import { KVStore } from "@keplr-wallet/common"; import { InteractionService } from "../interaction"; import { LedgerOptions } from "./options"; import { Buffer } from "buffer/"; import { EthSignType } from "@keplr-wallet/types"; -import { ErrFailedInit, ErrPublicKeyUnmatched } from "./types"; +import { + ErrFailedInit, + ErrModuleLedgerSign, + ErrPublicKeyUnmatched, +} from "./types"; export class LedgerService { private previousInitAborter: ((e: Error) => void) | undefined; @@ -117,8 +119,8 @@ export class LedgerService { Buffer.from(expectedPubKey).toString("hex") !== Buffer.from(pubKey).toString("hex") ) { - throw new LedgerInitError( - LedgerInitErrorOn.App, + throw new WalletError( + ErrModuleLedgerSign, ErrPublicKeyUnmatched, "Unmatched public key" ); @@ -179,8 +181,8 @@ export class LedgerService { Buffer.from(expectedPubKey).toString("hex") !== Buffer.from(pubKey).toString("hex") ) { - throw new LedgerInitError( - LedgerInitErrorOn.App, + throw new WalletError( + ErrModuleLedgerSign, ErrPublicKeyUnmatched, "Unmatched public key" ); @@ -251,8 +253,8 @@ export class LedgerService { try { const transportIniter = this.options.transportIniters[mode]; if (!transportIniter) { - throw new LedgerInitError( - LedgerInitErrorOn.App, + throw new WalletError( + ErrModuleLedgerSign, ErrFailedInit, `Unknown mode: ${mode}` ); diff --git a/packages/background/src/ledger/types.ts b/packages/background/src/ledger/types.ts index 7e9d00bcc4..997e37a8ed 100644 --- a/packages/background/src/ledger/types.ts +++ b/packages/background/src/ledger/types.ts @@ -8,3 +8,4 @@ export const ErrFailedSign = 6; export const ErrSignRejected = 7; export const ErrCodeAppNotInitialised = 8; export const ErrFailedGetVersion = 9; +export const ErrFailedUnknown = 10; diff --git a/packages/background/src/side-panel/handler.ts b/packages/background/src/side-panel/handler.ts index 3ee2691c6b..ae77e519ef 100644 --- a/packages/background/src/side-panel/handler.ts +++ b/packages/background/src/side-panel/handler.ts @@ -2,7 +2,7 @@ import { Env, Handler, InternalHandler, - KeplrError, + WalletError, Message, } from "@keplr-wallet/router"; import { SidePanelService } from "./service"; @@ -33,7 +33,7 @@ export const getHandler: (service: SidePanelService) => Handler = ( msg as SetSidePanelEnabledMsg ); default: - throw new KeplrError("sidePanll", 221, "Unknown msg type"); + throw new WalletError("sidePanll", 221, "Unknown msg type"); } }; }; diff --git a/packages/fetch-extension/src/components-v2/dropdown/style.module.scss b/packages/fetch-extension/src/components-v2/dropdown/style.module.scss index 26cd983688..715af4318c 100644 --- a/packages/fetch-extension/src/components-v2/dropdown/style.module.scss +++ b/packages/fetch-extension/src/components-v2/dropdown/style.module.scss @@ -24,7 +24,7 @@ padding: 24px 12px 12px 12px; border-radius: 24px 24px 0px 0px; background: rgb(0, 13, 61, 0.8); - max-height: 578px; + max-height: 98%; overflow-x: scroll; } diff --git a/packages/fetch-extension/src/components-v2/tabs/tabsPanel-2/style.module.scss b/packages/fetch-extension/src/components-v2/tabs/tabsPanel-2/style.module.scss index 25cb087087..7f2237d909 100644 --- a/packages/fetch-extension/src/components-v2/tabs/tabsPanel-2/style.module.scss +++ b/packages/fetch-extension/src/components-v2/tabs/tabsPanel-2/style.module.scss @@ -1,4 +1,5 @@ .tab-container { + height: 100%; cursor: pointer; font-size: 13px; align-items: center; diff --git a/packages/fetch-extension/src/languages/en.json b/packages/fetch-extension/src/languages/en.json index 78a90fc141..0de77bea4e 100644 --- a/packages/fetch-extension/src/languages/en.json +++ b/packages/fetch-extension/src/languages/en.json @@ -395,6 +395,8 @@ "ledger.confirm.success.paragraph": "This page will automatically close in 3 seconds", "ledger.confirm.rejected": "Rejected by Ledger", "ledger.confirm.rejected.paragraph": "This page will automatically close in 3 seconds", + "ledger.cosmos": "Cosmos", + "ledger.ethereum": "Ethereum", "fee-buttons.select.low": "Low", "fee-buttons.select.average": "Average", diff --git a/packages/fetch-extension/src/pages-new/main/wallet-details/index.tsx b/packages/fetch-extension/src/pages-new/main/wallet-details/index.tsx index 292dfaaa81..8c565c2c4f 100644 --- a/packages/fetch-extension/src/pages-new/main/wallet-details/index.tsx +++ b/packages/fetch-extension/src/pages-new/main/wallet-details/index.tsx @@ -1,7 +1,7 @@ import { Address } from "@components/address"; import { useNotification } from "@components/notification"; import { ToolTip } from "@components/tooltip"; -import { KeplrError } from "@keplr-wallet/router"; +import { WalletError } from "@keplr-wallet/router"; import { WalletStatus } from "@keplr-wallet/stores"; import { formatAddress, separateNumericAndDenom } from "@utils/format"; import React, { useCallback, useEffect, useState } from "react"; @@ -268,7 +268,7 @@ export const WalletDetailsView = observer( tooltip={(() => { if ( accountInfo.rejectionReason && - accountInfo.rejectionReason instanceof KeplrError && + accountInfo.rejectionReason instanceof WalletError && accountInfo.rejectionReason.module === "keyring" && accountInfo.rejectionReason.code === 152 ) { diff --git a/packages/fetch-extension/src/pages-new/register/ledger/index.tsx b/packages/fetch-extension/src/pages-new/register/ledger/index.tsx index a5122a207d..7ddd4ad63e 100644 --- a/packages/fetch-extension/src/pages-new/register/ledger/index.tsx +++ b/packages/fetch-extension/src/pages-new/register/ledger/index.tsx @@ -11,7 +11,8 @@ import { observer } from "mobx-react-lite"; import { useStore } from "../../../stores"; import { ledgerUSBVendorId } from "@ledgerhq/devices"; import { ButtonV2 } from "@components-v2/buttons/button"; -import { LedgerGrantView } from "../../../pages/ledger"; +import { LedgerSetupView } from "../../../pages/ledger"; +import { useNotification } from "@components/notification"; export const TypeImportLedger = "import-ledger"; @@ -45,6 +46,7 @@ export const ImportLedgerPage: FunctionComponent<{ setSelectedCard: any; }> = observer(({ registerConfig, setSelectedCard }) => { const intl = useIntl(); + const notification = useNotification(); const bip44Option = useBIP44Option(118); const [isShowLedgerSetup, setShowLedgerSetup] = useState(false); const { analyticsStore, ledgerInitStore } = useStore(); @@ -93,6 +95,21 @@ export const ImportLedgerPage: FunctionComponent<{ if (isShowUSBPermission) { await ensureUSBPermission(); } + } catch (e) { + notification.push({ + type: "warning", + placement: "top-center", + duration: 5, + content: "Please select a device to continue.", + canDelete: true, + transition: { + duration: 0.25, + }, + }); + return; + } + + try { await registerConfig.createLedger( name, password, @@ -110,7 +127,7 @@ export const ImportLedgerPage: FunctionComponent<{ } return isShowLedgerSetup ? ( - setShowLedgerSetup(false)} onInitSucceed={async () => createLedger(getValues()["name"], getValues()["password"]) diff --git a/packages/fetch-extension/src/pages-new/sign/details-tab.module.scss b/packages/fetch-extension/src/pages-new/sign/details-tab.module.scss index d8a4330eae..6680f6e3fc 100644 --- a/packages/fetch-extension/src/pages-new/sign/details-tab.module.scss +++ b/packages/fetch-extension/src/pages-new/sign/details-tab.module.scss @@ -22,7 +22,6 @@ .msg { display: flex; flex-direction: row; - display: flex; padding: 12px 18px; align-items: center; gap: 144px; diff --git a/packages/fetch-extension/src/pages-new/sign/index.tsx b/packages/fetch-extension/src/pages-new/sign/index.tsx index 1a3ef35838..80c5dd4add 100644 --- a/packages/fetch-extension/src/pages-new/sign/index.tsx +++ b/packages/fetch-extension/src/pages-new/sign/index.tsx @@ -25,15 +25,20 @@ import { EthSignType } from "@keplr-wallet/types"; import { Dropdown } from "@components-v2/dropdown"; import { TabsPanel } from "@components-v2/tabs/tabsPanel-2"; import { ButtonV2 } from "@components-v2/buttons/button"; -import { LedgerApp } from "@keplr-wallet/background"; -import { LedgerBox } from "./ledger-guide-box"; +import { + GetSidePanelEnabledMsg, + GetSidePanelIsSupportedMsg, + LedgerApp, +} from "@keplr-wallet/background"; +import { LedgerBox, LedgerGuideBoxProps } from "./ledger-guide-box"; import { useUSBDevices } from "@utils/ledger"; - -interface LedgerGuideBoxInfo { - title: string; - subtitle: string; - isWarning: boolean; -} +import { InExtensionMessageRequester } from "@keplr-wallet/router-extension"; +import { BACKGROUND_PORT, WalletError } from "@keplr-wallet/router"; +import { + ErrFailedInit, + ErrFailedUnknown, +} from "@keplr-wallet/background/src/ledger/types"; +import { ErrModuleLedgerSign } from "@keplr-wallet/background/build/ledger/types"; export const SignPageV2: FunctionComponent = observer(() => { const navigate = useNavigate(); @@ -57,8 +62,9 @@ export const SignPageV2: FunctionComponent = observer(() => { const [approveButtonClicked, setApproveButtonClicked] = useState(false); const { testUSBDevices } = useUSBDevices(); const [ledgerInfo, setLedgerInfo] = useState< - LedgerGuideBoxInfo | undefined + LedgerGuideBoxProps | undefined >(); + const [sidePanelEnabled, setSidePanelEnabled] = useState(false); const current = chainStore.current; // There are services that sometimes use invalid tx to sign arbitrary data on the sign page. @@ -83,21 +89,6 @@ export const SignPageV2: FunctionComponent = observer(() => { const signDocHelper = useSignDocHelper(feeConfig, memoConfig); amountConfig.setSignDocHelper(signDocHelper); - // Events are failing in manifest v3 - // useEffect(() => { - // const data = ledgerInitStore.isShowSignTxnGuide; - // if (data) { - // setLedgerInfo({ - // title: "Sign on Ledger", - // subtitle: - // "To proceed, please review and approve the transaction on your Ledger device.", - // isWarning: false, - // }); - // } else { - // setLedgerInfo(undefined); - // } - // }, [ledgerInitStore.isShowSignTxnGuide]); - useEffect(() => { if (signInteractionStore.waitingData) { const data = signInteractionStore.waitingData; @@ -272,6 +263,33 @@ export const SignPageV2: FunctionComponent = observer(() => { }, ]; + useEffect(() => { + const msg = new GetSidePanelIsSupportedMsg(); + new InExtensionMessageRequester() + .sendMessage(BACKGROUND_PORT, msg) + .then((_) => { + const msg = new GetSidePanelEnabledMsg(); + new InExtensionMessageRequester() + .sendMessage(BACKGROUND_PORT, msg) + .then((res) => { + setSidePanelEnabled(res.enabled); + }); + }); + }, []); + + function calculateHeight() { + /// Adjusting tab height according to ledger guide box + if (sidePanelEnabled) { + return ledgerInfo ? "70%" : "80%"; + } + + return ledgerInfo + ? ledgerInfo.ledgerError.code === ErrFailedInit + ? "245px" + : "265px" + : "320px"; + } + return (
{ @@ -282,7 +300,7 @@ export const SignPageV2: FunctionComponent = observer(() => { isLoaded ? (
{ if (window.history.length > 1) { @@ -294,10 +312,7 @@ export const SignPageV2: FunctionComponent = observer(() => { setIsOpen={setIsOpen} isOpen={isOpen} > - + {ledgerInfo ? (
{
) : null} @@ -373,7 +388,9 @@ export const SignPageV2: FunctionComponent = observer(() => { if ( !(await testUSBDevices(ledgerInitStore.isWebHID)) ) { - throw new Error( + throw new WalletError( + ErrModuleLedgerSign, + ErrFailedInit, "Connect and unlock your Ledger device." ); } else { @@ -388,10 +405,13 @@ export const SignPageV2: FunctionComponent = observer(() => { if (keyRingStore.keyRingType === "ledger") { setLedgerInfo({ - title: "Sign on Ledger", - subtitle: - "To proceed, please review and approve the transaction on your Ledger device.", isWarning: false, + title: "Sign on Ledger", + ledgerError: new WalletError( + ErrModuleLedgerSign, + ErrFailedUnknown, + "To proceed, please review and approve the transaction on your Ledger device." + ), }); } @@ -414,11 +434,16 @@ export const SignPageV2: FunctionComponent = observer(() => { } catch (e) { setApproveButtonClicked(false); - setLedgerInfo({ - title: "Error", - subtitle: e.message, - isWarning: true, - }); + if ( + e instanceof WalletError && + e.module === ErrModuleLedgerSign + ) { + setLedgerInfo({ + isWarning: true, + title: "Error", + ledgerError: e, + }); + } } }} /> diff --git a/packages/fetch-extension/src/pages-new/sign/ledger-guide-box.tsx b/packages/fetch-extension/src/pages-new/sign/ledger-guide-box.tsx index ed0b5592de..aade3ed275 100644 --- a/packages/fetch-extension/src/pages-new/sign/ledger-guide-box.tsx +++ b/packages/fetch-extension/src/pages-new/sign/ledger-guide-box.tsx @@ -1,19 +1,31 @@ -import React, { FunctionComponent } from "react"; +import React, { FunctionComponent, useEffect, useState } from "react"; import "./ledger-guide-box.module.scss"; import style from "./ledger-guide-box.module.scss"; import classnames from "classnames"; +import { ErrFailedInit } from "@keplr-wallet/background/src/ledger/types"; +import { WalletError } from "@keplr-wallet/router"; -interface LedgerBoxProps { - title: string; - message: string; +export interface LedgerGuideBoxProps { + ledgerError: WalletError; isWarning: boolean; + title: string; } -export const LedgerBox: FunctionComponent = ({ - title, - message, +export const LedgerBox: FunctionComponent = ({ + ledgerError, isWarning, + title, }) => { + const [transportErrorCount, setTransportErrorCount] = useState(0); + + useEffect(() => { + if (ledgerError.code === ErrFailedInit) { + setTransportErrorCount((c) => c + 1); + } else { + setTransportErrorCount(0); + } + }, [ledgerError]); + return (
@@ -27,7 +39,48 @@ export const LedgerBox: FunctionComponent = ({ {title}
-
{message}
+ {transportErrorCount < 2 ? ( +
+ {ledgerError.message} +
+ ) : ( + +
+ ASI Alliance Wallet may have lost its USB permission by an unknown + reason. +
+ +
+ )}
); }; diff --git a/packages/fetch-extension/src/pages/ledger-grant/index.tsx b/packages/fetch-extension/src/pages/ledger-grant/index.tsx index 09193c0fea..c107a58dff 100644 --- a/packages/fetch-extension/src/pages/ledger-grant/index.tsx +++ b/packages/fetch-extension/src/pages/ledger-grant/index.tsx @@ -1,188 +1,69 @@ -/* eslint-disable react/no-deprecated */ -import { BACKGROUND_PORT } from "@keplr-wallet/router"; -import { InExtensionMessageRequester } from "@keplr-wallet/router-extension"; -import React, { FunctionComponent, useState } from "react"; +import React, { ChangeEvent, FunctionComponent, useState } from "react"; +import "../../styles/global.scss"; + +import style from "./style.module.scss"; +import { FormattedMessage, useIntl } from "react-intl"; +import classnames from "classnames"; +import { + NotificationProvider, + NotificationStoreProvider, + useNotification, +} from "@components/notification"; +import { CosmosApp } from "@keplr-wallet/ledger-cosmos"; import { Ledger, LedgerApp, - LedgerGetWebHIDFlagMsg, LedgerWebHIDIniter, LedgerWebUSBIniter, } from "@keplr-wallet/background"; -import ReactDOM from "react-dom"; -import style from "./style.module.scss"; -import { Buffer } from "buffer/"; -import { CosmosApp } from "@keplr-wallet/ledger-cosmos"; import delay from "delay"; +import { StoreProvider, useStore } from "../../stores"; +import ReactDOM from "react-dom"; +import { observer } from "mobx-react-lite"; import { useAutoLockMonitoring } from "../../use-auto-lock-monitoring"; -const PrimaryLoading: FunctionComponent = () => { - return ( - - - - - - - - - - - ); -}; +import { AppIntlProvider } from "../../languages"; +import { + AdditionalIntlMessages, + LanguageToFiatCurrency, +} from "../../config.ui"; +import { LoadingIndicatorProvider } from "@components/loading-indicator"; +import { ConfirmProvider } from "@components/confirm"; +import { ErrorBoundary } from "../../error-boundary"; +import { HashRouter, Route, Routes } from "react-router-dom"; +import { DropdownContextProvider } from "@components-v2/dropdown/dropdown-context"; +import { ChatStoreProvider } from "@components/chat/store"; -const FailedLoading: FunctionComponent = () => { - return ( - - - - - - - - - - +export const LedgerGrantPage: FunctionComponent = observer(() => { + const intl = useIntl(); + const notification = useNotification(); + const { ledgerInitStore } = useStore(); + const [status, setStatus] = useState<"select" | "failed" | "success">( + "select" ); -}; - -export const LedgerGrantFullScreenPage: FunctionComponent = () => { - useAutoLockMonitoring(); - - const request: { - app: LedgerApp; + const [showWebHIDWarning, setShowWebHIDWarning] = useState(false); + const [isLoading, setIsLoading] = useState<{ cosmosLikeApp: string; - } = (() => { - const r = new URLSearchParams(window.location.search).get("request"); - if (!r) { - return { - app: LedgerApp.Cosmos, - cosmosLikeApp: "Cosmos", - }; - } - - return JSON.parse(Buffer.from(r, "base64").toString()); - })(); + loading: boolean; + }>({ + cosmosLikeApp: "", + loading: false, + }); - const [isLoading, setIsLoading] = useState(false); - const [hasFailed, setHasFailed] = useState(false); - const [succeded, setSucceded] = useState(false); - - const getPermission = async () => { - setIsLoading(true); + const handlePermission = async (app: LedgerApp, cosmosLikeApp: string) => { + setIsLoading({ cosmosLikeApp, loading: true }); try { - const msgRequester = new InExtensionMessageRequester(); - const isWebHID = await msgRequester.sendMessage( - BACKGROUND_PORT, - new LedgerGetWebHIDFlagMsg() - ); - const transportIniter = isWebHID + const transportIniter = ledgerInitStore.isWebHID ? LedgerWebHIDIniter : LedgerWebUSBIniter; const transport = await transportIniter(); try { - await CosmosApp.openApp( - transport, - (() => { - if (request.app === LedgerApp.Ethereum) { - return "Ethereum"; - } - return request.cosmosLikeApp || "Cosmos"; - })() - ); + await CosmosApp.openApp(transport, cosmosLikeApp); } catch (e) { console.log(e); } finally { - await transport.close(); - await delay(500); let ledger: Ledger | undefined; @@ -190,120 +71,297 @@ export const LedgerGrantFullScreenPage: FunctionComponent = () => { ledger = await Ledger.init( transportIniter, undefined, - request.app, - request.cosmosLikeApp + app, + cosmosLikeApp ); } finally { await ledger?.close(); } - setSucceded(true); + setStatus("success"); } } catch (e) { console.log(e); - setHasFailed(true); + setStatus("failed"); } finally { - setIsLoading(false); + setIsLoading({ cosmosLikeApp: "", loading: false }); } }; + const toggleWebHIDFlag = async (e: ChangeEvent) => { + e.preventDefault(); + + if (!ledgerInitStore.isWebHID && !(await Ledger.isWebHIDSupported())) { + setShowWebHIDWarning(true); + return; + } + setShowWebHIDWarning(false); + + await ledgerInitStore.setWebHID(!ledgerInitStore.isWebHID); + }; return ( -
-
-
- logo +
+
+
logo
-
-

Allow Browser to Connect to Ledger

-

- We’ve identified a Chrome related bug where attempting to connect a - hardware wallet in a popup may cause browser to crash. As a - temporary measure, you can give Ledger permission in this page. - Click the button below then try again. -

- {!succeded ? ( -
+
+
+
+ Allow Browser to Connect to Ledger +
+
+ You need to reapprove connection to your Ledger. Select the + appropriate app, and after successfully connecting with your Ledger + device, close this page and retry your previous transaction + (signing). +
+ {(() => { + switch (status) { + case "failed": + return ( + + ); + case "success": + return ( +
- + ); + case "select": + return ( + +
+ + +
+ {showWebHIDWarning ? ( +
+ { + navigator.clipboard + .writeText( + "chrome://flags/#enable-experimental-web-platform-features" + ) + .then(() => { + notification.push({ + placement: "top-center", + type: "success", + duration: 2, + content: intl.formatMessage({ + id: "ledger.option.webhid.link.copied", + }), + canDelete: true, + transition: { + duration: 0.25, + }, + }); + }); + }} + > + chrome://flags/#enable-experimental-web-platform-features + + ), + }} + /> +
+ ) : null} +
+ + } + title={intl.formatMessage({ id: "ledger.cosmos" })} + paragraph={ + "Click here to grant permission for Cosmos access." + } + isLoading={isLoading.cosmosLikeApp === "Cosmos"} + onClick={async () => { + if (!isLoading.loading) { + await handlePermission(LedgerApp.Cosmos, "Cosmos"); + } + }} + /> + + } + title={intl.formatMessage({ id: "ledger.ethereum" })} + paragraph={ + "Click here to grant permission for Ethereum access." + } + isLoading={isLoading.cosmosLikeApp === "Ethereum"} + onClick={async () => { + if (!isLoading.loading) { + await handlePermission( + LedgerApp.Ethereum, + "Ethereum" + ); + } + }} /> - - )} -
- - ) : ( -
- Success! You can close this web page. -
- )} +
+ ); + } + })()}
); +}); + +const Instruction: FunctionComponent<{ + icon: React.ReactElement; + title: string; + paragraph: string; + isLoading: boolean; + onClick: () => void; +}> = ({ icon, title, paragraph, isLoading = false, children, onClick }) => { + return ( +
+
{icon}
+
+

{title}

+ {isLoading ? ( + + ) : ( +

{paragraph}

+ )} + {children} +
+
+ ); }; -ReactDOM.render(, document.getElementById("app")); +const AutoLockMonitor: FunctionComponent = observer(() => { + useAutoLockMonitoring(); + + return null; +}); + +ReactDOM.render( + + + + + + + + + + + + + } /> + + + + + + + + + + + , + document.getElementById("app") +); diff --git a/packages/fetch-extension/src/pages/ledger-grant/style.module.scss b/packages/fetch-extension/src/pages/ledger-grant/style.module.scss index c1574a1143..d7d40569f7 100644 --- a/packages/fetch-extension/src/pages/ledger-grant/style.module.scss +++ b/packages/fetch-extension/src/pages/ledger-grant/style.module.scss @@ -1,47 +1,65 @@ -@charset "utf-8"; - -@import url("https://fonts.googleapis.com/css?family=Nunito+Sans:300,400,600,700,800&display=swap"); - -body { - font-family: "Nunito Sans", sans-serif; -} - .container { + width: 100%; + height: 100%; display: flex; - align-items: center; - justify-content: center; - width: 100vw; - height: 100vh; -} - -.inner { - display: flex; + background-repeat: no-repeat; + background-size: cover; + background-position: center; + background-image: url("../../public/assets/svg/wireframe/bg-register.svg"); flex-direction: column; - max-width: 764px; - - margin-bottom: 40px; -} - -.title { - font-weight: 700; - font-size: 36px; - line-height: 49px; - letter-spacing: 0.006em; - - color: #1e1e24; - - margin: 36px 0 12px; + padding: 0; } -.description { - font-weight: 400; - font-size: 20px; - line-height: 124.4%; - letter-spacing: 0.002em; +.logoContainer { + // margin-top: 40px; - color: #64646d; + display: flex; + flex-direction: column; - margin: 0 0 40px; + .logoInnerContainer { + display: flex; + flex-direction: column; + align-items: center; + margin: 32px 0; + + &.justify-center { + justify-content: center; + } + + .icon { + // height: 100px; + width: 215px; + // position: absolute; + // top: 5px; + // right: 45%; + } + + .logo { + width: 200px; + margin-left: 218px; + filter: invert(100%); + opacity: 0.3; + } + + .logoInnerContainer { + margin-top: 8px; + margin-left: 20px; + + display: flex; + flex-direction: column; + justify-content: space-evenly; + align-items: start; + + .logo { + height: 100px; + } + + .paragraph { + font-size: 16px; + line-height: 1; + } + } + } } .buttonText { @@ -62,17 +80,135 @@ body { color: #314fdf; } -@keyframes spinAround { - from { - transform: rotate(0deg); +.ledgerContainer { + width: 390px; +} + +.buttons { + display: flex; + padding-top: 15px; + gap: 5px; +} + +.button { + flex: 1; +} + +.instructions { + width: 390px; + margin: 0 auto; + height: 720px; + border-radius: 25px; + background-color: rgba(255, 255, 255, 0.1); + display: flex; + flex-direction: column; + + .backButton { + display: flex; + text-align: left; + padding: 0; + margin-bottom: 26px; + } +} + +.pageTitle { + font-size: 32px; + color: #fff; + margin-top: 10px; + margin-bottom: 10px; + font-weight: 400; + align-items: center; +} + +.pageSubTitle { + margin-top: 12px; + margin-bottom: 12px; + font-size: 16px; + color: white; +} + +.instruction { + margin-bottom: 30px; + padding: 20px; + border-radius: 20px; + + color: white; + + display: flex; + align-items: center; + background-color: rgba(255, 255, 255, 0.1); + .icon { + img { + filter: invert(1); + } + min-width: 50px; + display: flex; + justify-content: center; + margin-right: 20px; + } + + .inner { + h1 { + font-size: 20px; + font-weight: bold; + color: white; + margin-bottom: 8px; + } + + p { + font-size: 16px; + line-height: 1.5; + letter-spacing: -0.3px; + margin-bottom: 10px; + } + } +} + +.ledgerCheckbox { + margin-top: 12px; + margin-bottom: 24px; + + &:checked { + accent-color: #010d3e !important; + background: transparent !important; + opacity: 1; + border-color: white !important; + } + + &:not(:disabled):active { + ~ .ledgerCheckboxLabel { + &::before { + background-color: transparent !important; + border-color: white !important; + } + } + } + + &:checked ~ .ledgerCheckboxLabel::before { + border-color: white !important; + background-color: transparent !important; } - to { - transform: rotate(359deg); + &:focus:not(:checked) ~ .ledgerCheckboxLabel { + &::before { + border-color: white !important; + } } } -.spin { - -webkit-animation: spinAround 1.5s infinite linear; - animation: spinAround 1.5s infinite linear; +.ledgerCheckboxLabel { + color: white; + margin-top: 12px; + margin-bottom: 24px; + + &::before { + border-radius: 0.25rem !important; + border: 1px solid white; + background: transparent !important; + } + + &::after { + border: 1px solid white; + border-radius: 0.25rem !important; + } } diff --git a/packages/fetch-extension/src/pages/ledger/grant.tsx b/packages/fetch-extension/src/pages/ledger/grant.tsx deleted file mode 100644 index a1a1f53700..0000000000 --- a/packages/fetch-extension/src/pages/ledger/grant.tsx +++ /dev/null @@ -1,373 +0,0 @@ -import React, { - ChangeEvent, - FunctionComponent, - useEffect, - useState, -} from "react"; - -import { - Ledger, - LedgerApp, - LedgerInitErrorOn, - LedgerWebHIDIniter, - LedgerWebUSBIniter, -} from "@keplr-wallet/background"; - -import style from "./style.module.scss"; - -import { FormattedMessage, useIntl } from "react-intl"; -import { useNotification } from "@components/notification"; -import delay from "delay"; -import { observer } from "mobx-react-lite"; -import { useStore } from "../../stores"; -import { CosmosApp } from "@keplr-wallet/ledger-cosmos"; -import { ButtonV2 } from "@components-v2/buttons/button"; -import { useNavigate } from "react-router"; -import { BackButton } from "../../pages-new/register"; -import classnames from "classnames"; -import { useUSBDevices } from "@utils/ledger"; - -export const LedgerGrantView: FunctionComponent<{ - onBackPress?: () => void; - onInitSucceed: () => void; -}> = observer(({ onBackPress, onInitSucceed }) => { - const { ledgerInitStore } = useStore(); - - const intl = useIntl(); - const navigate = useNavigate(); - - const notification = useNotification(); - - const [showWebHIDWarning, setShowWebHIDWarning] = useState(false); - - const toggleWebHIDFlag = async (e: ChangeEvent) => { - e.preventDefault(); - - if (!ledgerInitStore.isWebHID && !(await Ledger.isWebHIDSupported())) { - setShowWebHIDWarning(true); - return; - } - setShowWebHIDWarning(false); - - await ledgerInitStore.setWebHID(!ledgerInitStore.isWebHID); - }; - - useEffect(() => { - if (ledgerInitStore.isSignCompleted) { - setTimeout(window.close, 3000); - } - - if (ledgerInitStore.isGetPubKeySucceeded) { - // Don't need to delay to close because this event probably occurs only in the register page in tab. - // So, don't need to consider the window refocusing. - window.close(); - } - - if (ledgerInitStore.isInitAborted) { - // If ledger init is aborted due to the timeout on the background, just close the window. - window.close(); - } - }, [ - ledgerInitStore.isGetPubKeySucceeded, - ledgerInitStore.isSignCompleted, - ledgerInitStore.isInitAborted, - ]); - - const [initTryCount, setInitTryCount] = useState(0); - const [initErrorOn, setInitErrorOn] = useState( - undefined - ); - const [tryInitializing, setTryInitializing] = useState(false); - const { testUSBDevices } = useUSBDevices(); - - const tryInit = async () => { - setTryInitializing(true); - - let initErrorOn: LedgerInitErrorOn | undefined; - - try { - if (!(await testUSBDevices(ledgerInitStore.isWebHID))) { - throw new Error("There is no device selected"); - } - - const transportIniter = ledgerInitStore.isWebHID - ? LedgerWebHIDIniter - : LedgerWebUSBIniter; - const transport = await transportIniter(); - try { - if (ledgerInitStore.requestedLedgerApp === LedgerApp.Cosmos) { - await CosmosApp.openApp( - transport, - ledgerInitStore.cosmosLikeApp || "Cosmos" - ); - } else if (ledgerInitStore.requestedLedgerApp === LedgerApp.Ethereum) { - await CosmosApp.openApp(transport, "Ethereum"); - } - } catch (e) { - // Ignore error - console.log(e); - } finally { - await transport.close(); - - await delay(500); - } - - // I don't know the reason exactly. - // However, we sometimes should wait for some until actually app opened. - // It is hard to set exact delay. So, just wait for 500ms 5 times. - let tempSuccess = false; - for (let i = 0; i < 5; i++) { - // Test again to ensure usb permission after interaction. - if (await testUSBDevices(ledgerInitStore.isWebHID)) { - tempSuccess = true; - break; - } - - await delay(500); - } - if (!tempSuccess) { - throw new Error("There is no device selected"); - } - - const ledger = await Ledger.init( - ledgerInitStore.isWebHID ? LedgerWebHIDIniter : LedgerWebUSBIniter, - undefined, - // requestedLedgerApp should be set if ledger init needed. - ledgerInitStore.requestedLedgerApp!, - ledgerInitStore.cosmosLikeApp || "Cosmos" - ); - await ledger.close(); - // Unfortunately, closing ledger blocks the writing to Ledger on background process. - // I'm not sure why this happens. But, not closing reduce this problem if transport is webhid. - if (!ledgerInitStore.isWebHID) { - delay(1000); - } else { - delay(500); - } - } catch (e) { - console.log(e); - - if (e.errorOn != null) { - initErrorOn = e.errorOn; - } else { - initErrorOn = LedgerInitErrorOn.Unknown; - } - } - - setInitTryCount(initTryCount + 1); - setInitErrorOn(initErrorOn); - setTryInitializing(false); - - if (initErrorOn === undefined) { - onInitSucceed(); - await ledgerInitStore.resume(); - } - }; - - return ( -
-
- { - ledgerInitStore.abortAll(); - if (onBackPress) { - onBackPress(); - } else { - navigate(-1); - } - }} - /> -
- Please connect your hardware wallet -
- - } - title={intl.formatMessage({ id: "ledger.step1" })} - paragraph={intl.formatMessage({ id: "ledger.step1.paragraph" })} - selected={ - !( - initErrorOn === LedgerInitErrorOn.App || - (initTryCount > 0 && initErrorOn == null) - ) - } - pass={initTryCount > 0 && initErrorOn === LedgerInitErrorOn.App} - /> - { - switch (ledgerInitStore.requestedLedgerApp) { - case "ethereum": - return require("../../public/assets/img/ethereum.svg"); - case "cosmos": - if (ledgerInitStore.cosmosLikeApp === "Terra") { - return require("../../public/assets/img/ledger-terra.svg"); - } - return require("../../public/assets/img/atom-o.svg"); - default: - return require("@assets/img/atom-o.svg"); - } - })()} - style={{ height: "34px" }} - alt="atom" - /> - } - title={intl.formatMessage({ id: "ledger.step2" })} - paragraph={intl.formatMessage( - { - id: "ledger.step2.paragraph", - }, - { - ledgerApp: (() => { - switch (ledgerInitStore.requestedLedgerApp) { - case "ethereum": - return "Ethereum"; - case "cosmos": - return ledgerInitStore.cosmosLikeApp || "Cosmos"; - default: - return "Cosmos"; - } - })(), - } - )} - selected={ - initErrorOn === LedgerInitErrorOn.App || - (initTryCount > 0 && initErrorOn == null) - } - pass={initTryCount > 0 && initErrorOn == null} - /> -
-
- - -
- {showWebHIDWarning ? ( -
- { - navigator.clipboard - .writeText( - "chrome://flags/#enable-experimental-web-platform-features" - ) - .then(() => { - notification.push({ - placement: "top-center", - type: "success", - duration: 2, - content: intl.formatMessage({ - id: "ledger.option.webhid.link.copied", - }), - canDelete: true, - transition: { - duration: 0.25, - }, - }); - }); - }} - > - chrome://flags/#enable-experimental-web-platform-features - - ), - }} - /> -
- ) : null} -
- - ) : ( - - ) - } - onClick={async (e: any) => { - e.preventDefault(); - await tryInit(); - }} - dataLoading={tryInitializing} - disabled={tryInitializing} - /> -
-
-
- ); -}); - -const Instruction: FunctionComponent<{ - icon: React.ReactElement; - title: string; - paragraph: string; - pass: boolean; - selected: boolean; -}> = ({ icon, title, paragraph, children, pass, selected }) => { - return ( -
-
{icon}
-
-

- {title} - {pass ? ( - - ) : null} -

-

{paragraph}

- {children} -
-
- ); -}; diff --git a/packages/fetch-extension/src/pages/ledger/index.tsx b/packages/fetch-extension/src/pages/ledger/index.tsx index 88aff70821..10020364f5 100644 --- a/packages/fetch-extension/src/pages/ledger/index.tsx +++ b/packages/fetch-extension/src/pages/ledger/index.tsx @@ -1 +1,372 @@ -export * from "./grant"; +import React, { + ChangeEvent, + FunctionComponent, + useEffect, + useState, +} from "react"; + +import { + Ledger, + LedgerApp, + LedgerWebHIDIniter, + LedgerWebUSBIniter, +} from "@keplr-wallet/background"; + +import style from "./style.module.scss"; + +import { FormattedMessage, useIntl } from "react-intl"; +import { useNotification } from "@components/notification"; +import delay from "delay"; +import { observer } from "mobx-react-lite"; +import { useStore } from "../../stores"; +import { CosmosApp } from "@keplr-wallet/ledger-cosmos"; +import { ButtonV2 } from "@components-v2/buttons/button"; +import { useNavigate } from "react-router"; +import { BackButton } from "../../pages-new/register"; +import classnames from "classnames"; +import { useUSBDevices } from "@utils/ledger"; +import { WalletError } from "@keplr-wallet/router"; +import { ErrCodeAppNotInitialised } from "@keplr-wallet/background/build/ledger/types"; + +export const LedgerSetupView: FunctionComponent<{ + onBackPress?: () => void; + onInitSucceed: () => void; +}> = observer(({ onBackPress, onInitSucceed }) => { + const { ledgerInitStore } = useStore(); + + const intl = useIntl(); + const navigate = useNavigate(); + + const notification = useNotification(); + + const [showWebHIDWarning, setShowWebHIDWarning] = useState(false); + + const toggleWebHIDFlag = async (e: ChangeEvent) => { + e.preventDefault(); + + if (!ledgerInitStore.isWebHID && !(await Ledger.isWebHIDSupported())) { + setShowWebHIDWarning(true); + return; + } + setShowWebHIDWarning(false); + + await ledgerInitStore.setWebHID(!ledgerInitStore.isWebHID); + }; + + useEffect(() => { + if (ledgerInitStore.isSignCompleted) { + setTimeout(window.close, 3000); + } + + if (ledgerInitStore.isGetPubKeySucceeded) { + // Don't need to delay to close because this event probably occurs only in the register page in tab. + // So, don't need to consider the window refocusing. + window.close(); + } + + if (ledgerInitStore.isInitAborted) { + // If ledger init is aborted due to the timeout on the background, just close the window. + window.close(); + } + }, [ + ledgerInitStore.isGetPubKeySucceeded, + ledgerInitStore.isSignCompleted, + ledgerInitStore.isInitAborted, + ]); + + const [initTryCount, setInitTryCount] = useState(0); + const [initErrorOn, setInitErrorOn] = useState( + undefined + ); + const [tryInitializing, setTryInitializing] = useState(false); + const { testUSBDevices } = useUSBDevices(); + + const tryInit = async () => { + setTryInitializing(true); + + let initErrorOn: WalletError | undefined; + + try { + if (!(await testUSBDevices(ledgerInitStore.isWebHID))) { + throw new Error("There is no device selected"); + } + + const transportIniter = ledgerInitStore.isWebHID + ? LedgerWebHIDIniter + : LedgerWebUSBIniter; + const transport = await transportIniter(); + try { + if (ledgerInitStore.requestedLedgerApp === LedgerApp.Cosmos) { + await CosmosApp.openApp( + transport, + ledgerInitStore.cosmosLikeApp || "Cosmos" + ); + } else if (ledgerInitStore.requestedLedgerApp === LedgerApp.Ethereum) { + await CosmosApp.openApp(transport, "Ethereum"); + } + } catch (e) { + // Ignore error + console.log(e); + } finally { + await transport.close(); + + await delay(500); + } + + // I don't know the reason exactly. + // However, we sometimes should wait for some until actually app opened. + // It is hard to set exact delay. So, just wait for 500ms 5 times. + let tempSuccess = false; + for (let i = 0; i < 5; i++) { + // Test again to ensure usb permission after interaction. + if (await testUSBDevices(ledgerInitStore.isWebHID)) { + tempSuccess = true; + break; + } + + await delay(500); + } + if (!tempSuccess) { + throw new Error("There is no device selected"); + } + + const ledger = await Ledger.init( + ledgerInitStore.isWebHID ? LedgerWebHIDIniter : LedgerWebUSBIniter, + undefined, + // requestedLedgerApp should be set if ledger init needed. + ledgerInitStore.requestedLedgerApp!, + ledgerInitStore.cosmosLikeApp || "Cosmos" + ); + await ledger.close(); + // Unfortunately, closing ledger blocks the writing to Ledger on background process. + // I'm not sure why this happens. But, not closing reduce this problem if transport is webhid. + if (!ledgerInitStore.isWebHID) { + delay(1000); + } else { + delay(500); + } + } catch (e) { + console.log(e); + + initErrorOn = e; + } + + setInitTryCount(initTryCount + 1); + setInitErrorOn(initErrorOn); + setTryInitializing(false); + + if (initErrorOn === undefined) { + onInitSucceed(); + await ledgerInitStore.resume(); + } + }; + + return ( +
+
+ { + ledgerInitStore.abortAll(); + if (onBackPress) { + onBackPress(); + } else { + navigate(-1); + } + }} + /> +
+ Please connect your hardware wallet +
+ + } + title={intl.formatMessage({ id: "ledger.step1" })} + paragraph={intl.formatMessage({ id: "ledger.step1.paragraph" })} + selected={ + !( + initErrorOn?.code === ErrCodeAppNotInitialised || + (initTryCount > 0 && initErrorOn == null) + ) + } + pass={ + initTryCount > 0 && initErrorOn?.code === ErrCodeAppNotInitialised + } + /> + { + switch (ledgerInitStore.requestedLedgerApp) { + case "ethereum": + return require("../../public/assets/img/ethereum.svg"); + case "cosmos": + if (ledgerInitStore.cosmosLikeApp === "Terra") { + return require("../../public/assets/img/ledger-terra.svg"); + } + return require("../../public/assets/img/atom-o.svg"); + default: + return require("@assets/img/atom-o.svg"); + } + })()} + style={{ height: "34px" }} + alt="atom" + /> + } + title={intl.formatMessage({ id: "ledger.step2" })} + paragraph={intl.formatMessage( + { + id: "ledger.step2.paragraph", + }, + { + ledgerApp: (() => { + switch (ledgerInitStore.requestedLedgerApp) { + case "ethereum": + return "Ethereum"; + case "cosmos": + return ledgerInitStore.cosmosLikeApp || "Cosmos"; + default: + return "Cosmos"; + } + })(), + } + )} + selected={ + initErrorOn?.code === ErrCodeAppNotInitialised || + (initTryCount > 0 && initErrorOn == null) + } + pass={initTryCount > 0 && initErrorOn == null} + /> +
+
+ + +
+ {showWebHIDWarning ? ( +
+ { + navigator.clipboard + .writeText( + "chrome://flags/#enable-experimental-web-platform-features" + ) + .then(() => { + notification.push({ + placement: "top-center", + type: "success", + duration: 2, + content: intl.formatMessage({ + id: "ledger.option.webhid.link.copied", + }), + canDelete: true, + transition: { + duration: 0.25, + }, + }); + }); + }} + > + chrome://flags/#enable-experimental-web-platform-features + + ), + }} + /> +
+ ) : null} +
+ + ) : ( + + ) + } + onClick={async (e: any) => { + e.preventDefault(); + await tryInit(); + }} + dataLoading={tryInitializing} + disabled={tryInitializing} + /> +
+
+
+ ); +}); + +const Instruction: FunctionComponent<{ + icon: React.ReactElement; + title: string; + paragraph: string; + pass: boolean; + selected: boolean; +}> = ({ icon, title, paragraph, children, pass, selected }) => { + return ( +
+
{icon}
+
+

+ {title} + {pass ? ( + + ) : null} +

+

{paragraph}

+ {children} +
+
+ ); +}; diff --git a/packages/fetch-extension/src/pages/ledger/style.module.scss b/packages/fetch-extension/src/pages/ledger/style.module.scss index f6fb9c8fee..645194fce4 100644 --- a/packages/fetch-extension/src/pages/ledger/style.module.scss +++ b/packages/fetch-extension/src/pages/ledger/style.module.scss @@ -99,8 +99,8 @@ width: 390px; margin: 0px auto; height: 720px; - // border-radius: 25px; - // background-color: rgba(255, 255, 255, 0.1); + border-radius: 25px; + background-color: rgba(255, 255, 255, 0.1); display: flex; flex-direction: column; @@ -118,6 +118,7 @@ margin-top: 10px; margin-bottom: 10px; font-weight: 400; + align-items: center; } .instruction { diff --git a/packages/fetch-extension/src/pages/main/account.tsx b/packages/fetch-extension/src/pages/main/account.tsx index 61a3a3edc8..ff279d850c 100644 --- a/packages/fetch-extension/src/pages/main/account.tsx +++ b/packages/fetch-extension/src/pages/main/account.tsx @@ -1,7 +1,7 @@ import { Address } from "@components/address"; import { useNotification } from "@components/notification"; import { ToolTip } from "@components/tooltip"; -import { KeplrError } from "@keplr-wallet/router"; +import { WalletError } from "@keplr-wallet/router"; import { WalletStatus } from "@keplr-wallet/stores"; import { observer } from "mobx-react-lite"; import React, { @@ -138,7 +138,7 @@ export const AccountView: FunctionComponent = observer(() => { tooltip={(() => { if ( accountInfo.rejectionReason && - accountInfo.rejectionReason instanceof KeplrError && + accountInfo.rejectionReason instanceof WalletError && accountInfo.rejectionReason.module === "keyring" && accountInfo.rejectionReason.code === 152 ) { diff --git a/packages/mobile/src/modals/ledger/ledger-selector.tsx b/packages/mobile/src/modals/ledger/ledger-selector.tsx index 3f8a5322fb..25d58f9943 100644 --- a/packages/mobile/src/modals/ledger/ledger-selector.tsx +++ b/packages/mobile/src/modals/ledger/ledger-selector.tsx @@ -1,15 +1,16 @@ -import { - Ledger, - LedgerApp, - LedgerInitError, - LedgerInitErrorOn, -} from "@keplr-wallet/background"; +import { Ledger, LedgerApp } from "@keplr-wallet/background"; import React, { FunctionComponent, useState } from "react"; import { useStyle } from "styles/index"; import { BluetoothMode } from "."; import { ViewStyle } from "react-native"; import { BlurButton } from "components/new/button/blur-button"; import TransportBLE from "@ledgerhq/react-native-hw-transport-ble"; +import { WalletError } from "@keplr-wallet/router"; +import { + ErrCodeAppNotInitialised, + ErrCodeDeviceLocked, + ErrModuleLedgerSign, +} from "@keplr-wallet/background/build/ledger/types"; export const LedgerNanoBLESelector: FunctionComponent<{ deviceId: string; @@ -35,8 +36,6 @@ export const LedgerNanoBLESelector: FunctionComponent<{ const [isConnecting, setIsConnecting] = useState(false); const testLedgerConnection = async () => { - let initErrorOn: LedgerInitErrorOn | undefined; - try { setIsPaired(false); setIsConnecting(true); @@ -62,21 +61,14 @@ export const LedgerNanoBLESelector: FunctionComponent<{ await ledger.close(); onCanResume(); } catch (e) { - console.log( - "Ledger-Selector:", - e, - e.errorOn, - e instanceof LedgerInitError - ); - if (e.errorOn != null) { - initErrorOn = e.errorOn; - if (initErrorOn === LedgerInitErrorOn.App) { + if (e instanceof WalletError && e.module === ErrModuleLedgerSign) { + if (e.code === ErrCodeAppNotInitialised) { setBluetoothMode(BluetoothMode.Device); setMainContent( "Open Cosmos app on your ledger and pair with ASI Alliance Wallet" ); setIsConnecting(false); - } else if (initErrorOn === LedgerInitErrorOn.Transport) { + } else if (e.code === ErrCodeDeviceLocked) { setMainContent("Please unlock ledger nano X"); setIsConnecting(false); } diff --git a/packages/mobile/src/router/rn-router.ts b/packages/mobile/src/router/rn-router.ts index d0602c14d2..1ab7caf0de 100644 --- a/packages/mobile/src/router/rn-router.ts +++ b/packages/mobile/src/router/rn-router.ts @@ -1,6 +1,6 @@ import { EnvProducer, - KeplrError, + WalletError, MessageSender, Result, Router, @@ -45,7 +45,7 @@ export class RNRouterBase extends Router { console.log( `Failed to process msg ${message.type}: ${e?.message || e?.toString()}` ); - if (e instanceof KeplrError) { + if (e instanceof WalletError) { sender.resolver({ error: { code: e.code, diff --git a/packages/router-extension/src/interaction-addon/handler.ts b/packages/router-extension/src/interaction-addon/handler.ts index a340932ca7..e9e01a185a 100644 --- a/packages/router-extension/src/interaction-addon/handler.ts +++ b/packages/router-extension/src/interaction-addon/handler.ts @@ -2,7 +2,7 @@ import { Env, Handler, InternalHandler, - KeplrError, + WalletError, Message, } from "@keplr-wallet/router"; import { InteractionAddonService } from "./service"; @@ -16,7 +16,7 @@ export const getHandler: (service: InteractionAddonService) => Handler = ( case ReplacePageMsg: return handleReplacePageMsg(service)(env, msg as ReplacePageMsg); default: - throw new KeplrError( + throw new WalletError( "extension-interaction-addon", 100, "Unknown msg type" diff --git a/packages/router-extension/src/requester/extension.ts b/packages/router-extension/src/requester/extension.ts index f2b82556b1..5c14bcb743 100644 --- a/packages/router-extension/src/requester/extension.ts +++ b/packages/router-extension/src/requester/extension.ts @@ -2,6 +2,7 @@ import { MessageRequester, Message, JSONUint8Array, + WalletError, } from "@keplr-wallet/router"; import { getKeplrExtensionRouterId } from "../utils"; @@ -37,7 +38,17 @@ export class InExtensionMessageRequester implements MessageRequester { if (typeof result.error === "string") { throw new Error(result.error); } else { - throw new Error(result.error.message); + if ("module" in result.error) { + if (typeof result.error.module === "string") { + throw new WalletError( + result.error.module, + result.error.code, + result.error.message + ); + } + } else { + throw new Error(result.error.message); + } } } @@ -76,7 +87,17 @@ export class InExtensionMessageRequester implements MessageRequester { if (typeof result.error === "string") { throw new Error(result.error); } else { - throw new Error(result.error.message); + if ("module" in result.error) { + if (typeof result.error.module === "string") { + throw new WalletError( + result.error.module, + result.error.code, + result.error.message + ); + } + } else { + throw new Error(result.error.message); + } } } diff --git a/packages/router-extension/src/router/extension.ts b/packages/router-extension/src/router/extension.ts index 01fae81252..41975939df 100644 --- a/packages/router-extension/src/router/extension.ts +++ b/packages/router-extension/src/router/extension.ts @@ -3,7 +3,7 @@ import { MessageSender, Result, EnvProducer, - KeplrError, + WalletError, } from "@keplr-wallet/router"; import { getKeplrExtensionRouterId } from "../utils"; @@ -70,7 +70,7 @@ export class ExtensionRouter extends Router { console.log( `Failed to process msg ${message.type}: ${e?.message || e?.toString()}` ); - if (e instanceof KeplrError) { + if (e instanceof WalletError) { return Promise.resolve({ error: { code: e.code, diff --git a/packages/router-mock/src/router/index.ts b/packages/router-mock/src/router/index.ts index c95b8b88d0..bcc7bf3bf0 100644 --- a/packages/router-mock/src/router/index.ts +++ b/packages/router-mock/src/router/index.ts @@ -2,7 +2,7 @@ import { Router, MessageSender, Result, - KeplrError, + WalletError, } from "@keplr-wallet/router"; import { EventEmitter } from "events"; @@ -38,7 +38,7 @@ export class MockRouter extends Router { console.log( `Failed to process msg ${message.type}: ${e?.message || e?.toString()}` ); - if (e instanceof KeplrError) { + if (e instanceof WalletError) { sender.resolver({ error: { code: e.code, diff --git a/packages/router/src/error.ts b/packages/router/src/error.ts index 7336819248..a42e5dc792 100644 --- a/packages/router/src/error.ts +++ b/packages/router/src/error.ts @@ -1,4 +1,4 @@ -export class KeplrError extends Error { +export class WalletError extends Error { public readonly module: string; public readonly code: number; @@ -7,6 +7,6 @@ export class KeplrError extends Error { this.module = module; this.code = code; - Object.setPrototypeOf(this, KeplrError.prototype); + Object.setPrototypeOf(this, WalletError.prototype); } } diff --git a/packages/router/src/interfaces.ts b/packages/router/src/interfaces.ts index 79b8488e9e..e6f2906df6 100644 --- a/packages/router/src/interfaces.ts +++ b/packages/router/src/interfaces.ts @@ -1,7 +1,7 @@ export interface Result { /** * NOTE: If `error` is of type `{ module:string; code: number; message: string }`, - * it should be considered and processed as `KeplrError`. + * it should be considered and processed as `WalletError`. */ error?: string | { module: string; code: number; message: string }; return?: any; From 3ac89538170f6d5622adc4bfd64e819253ff4075 Mon Sep 17 00:00:00 2001 From: sh-wallet <137136919+sh-wallet@users.noreply.github.com> Date: Fri, 24 Jan 2025 15:38:23 +0530 Subject: [PATCH 3/3] chore: mobile ledger updates --- packages/mobile/ios/Podfile.lock | 116 +++++++++--------- .../src/modals/ledger/ledger-selector.tsx | 11 +- .../src/screens/register/ledger/index.tsx | 13 ++ 3 files changed, 79 insertions(+), 61 deletions(-) diff --git a/packages/mobile/ios/Podfile.lock b/packages/mobile/ios/Podfile.lock index 6aed1a34d2..caab5925a2 100644 --- a/packages/mobile/ios/Podfile.lock +++ b/packages/mobile/ios/Podfile.lock @@ -830,36 +830,36 @@ EXTERNAL SOURCES: SPEC CHECKSUMS: Amplitude: 10f20d4436fdcd38ddd5be1da7033fcc21f4143e - amplitude-react-native: 6e5b9e38c53e11a86f62f874a42cf176e2b82c86 + amplitude-react-native: 31547517af995bb7e18052580ce3bad6907bd39e AnalyticsConnector: a53214d38ae22734c6266106c0492b37832633a9 AppCenter: 3fd04aa1b166e16fdb03ec81dabe488aece83fbd - appcenter-analytics: 1aea9ada8922d2e2df3e069a28eda21157c9d30d - appcenter-core: c593b2e4ac28d6a21b6a217c4a5e6b3de7b0b00f + appcenter-analytics: 3c06d292afa81fdbf4f48790007e189d01e77d3b + appcenter-core: 69deb1f6818d1e489fa36ad1be543b50bb81aebf AppCenterReactNativeShared: f395caeabde0dc3a11609dbcb737d0f14cd40e79 Base64: cecfb41a004124895a7bcee567a89bae5a89d49b BEMCheckBox: 5ba6e37ade3d3657b36caecc35c8b75c6c2b1a4e boost: 7dcd2de282d72e344012f7d6564d024930a6a440 - BVLinearGradient: 880f91a7854faff2df62518f0281afb1c60d49a3 - CodePush: 9eddecce05cd10491e2673b259c85885a462be33 + BVLinearGradient: cb006ba232a1f3e4f341bb62c42d1098c284da70 + CodePush: 3e3003b55a7b1956021250cf43163efc63926132 DoubleConversion: 5189b271737e1565bdce30deb4a08d647e3f5f54 - EXApplication: 02655a251434d564bb0e73291f5a490c74b5b76f - EXBarCodeScanner: 31350bf37e191366075b8748e7c04fc8da6dbba3 - EXCamera: a78e115ec222af91598c1acef238db0695bb4897 - EXConstants: 6cb14c7520a3538d37894fe8f0de9b8196d38455 - EXFileSystem: d7f59869885cfeab3ac771e2a8d0f5ed98cd3fdb - EXFont: 738c44c390953ebcbab075a4848bfbef025fd9ee - EXImageLoader: 34b214f9387e98f3c73989f15d8d5b399c9ab3f7 - EXLocation: e5dd52bdf61ccb6153c9e46ad4cb2686c96a35f9 - Expo: f2485e8fab9988e5dcee34159df5e475c9c3730c - ExpoAppleAuthentication: 265219fa0ba1110872079f55f56686b9737b0065 - ExpoBlur: 2e733ec3aa76653040b29a833984f128a293295a - ExpoClipboard: ddd3ecf4af93c003667dab75a0f2ed3ae4f73d85 - ExpoCrypto: a382ab9a2fa91f0b511ce1fe4d6baecee40a1615 - ExpoKeepAwake: be4cbd52d9b177cde0fd66daa1913afa3161fc1d - ExpoModulesCore: 924a69d9bffa2925c1f7eafab37764565f861d5d - ExpoSecureStore: eea16d67ae42a61a3a25c581198525bfa2cce61c - ExpoWebBrowser: 2c788f9c07718a780fe6d8bf2f6195c47609faaa - EXSplashScreen: c06a8a301f48b2fed6a05cb56789e02b0196bade + EXApplication: a38b05670c49d81d0fe0c8b9dc119d568000f3b9 + EXBarCodeScanner: f516211cac0f8530d4e9246cb2ebfbc7adbfe58e + EXCamera: 1d5d9112081055577ddd0b587cc3322528f8b53c + EXConstants: d1cf429bf70fa335a73417b5160c92d727ea467c + EXFileSystem: bff246197140a683de8874e30c61b857c7750e0d + EXFont: aa39b3f790e2b3188986ac5e8684cf6003c00a18 + EXImageLoader: 2c79db9355756be7494a2257648235ee47dc0f9c + EXLocation: ba79521b873dd91b2fa935d027723a0d3c1f95d0 + Expo: 2dd3330a16ecb6a3632f298d563dd0c30bc33855 + ExpoAppleAuthentication: 8a661b6f4936affafd830f983ac22463c936dad5 + ExpoBlur: 08a62bac745873bb72e7403896551726df115ccd + ExpoClipboard: ba10c66f1cef01e1ac13100f0808a54068aea760 + ExpoCrypto: e0714ca676dc875ee772802cf20bcea6ec75c3e7 + ExpoKeepAwake: 8ab1087501f5ccb91146447756b787575b13f13e + ExpoModulesCore: 2cf9ff822208b714143eecdc7cc3d8df606e4d10 + ExpoSecureStore: 0edc5688f16a10a1b0efa29ab191a7d89b97a2fd + ExpoWebBrowser: 025c51f93c6a04beb169388877918de64ccae171 + EXSplashScreen: ca1bb631dff1b35ff8527a9ee800a41d0999fe7a FBLazyVector: 25cbffbaec517695d376ab4bc428948cd0f08088 FBReactNativeSpec: e03b22fbf7017a6f76641ea4472e73c915dcdda7 fmt: ff9d55029c625d3757ed641535fd4a75fedc7ce9 @@ -867,34 +867,34 @@ SPEC CHECKSUMS: JWT: ef71dfb03e1f842081e64dc42eef0e164f35d251 libwebp: 1786c9f4ff8a279e4dac1e8f385004d5fc253009 lottie-ios: 69495122151a378fdc7d1bb4c5930347e37baf1f - lottie-react-native: 8f9d4be452e23f6e5ca0fdc11669dc99ab52be81 + lottie-react-native: 5d89c05930d4180a1e39b1757d46e6c0eec90255 MultiplatformBleAdapter: 5a6a897b006764392f9cef785e4360f54fb9477d - RCT-Folly: 424b8c9a7a0b9ab2886ffe9c3b041ef628fd4fb1 + RCT-Folly: 8dc08ca5a393b48b1c523ab6220dfdcc0fe000ad RCTRequired: fb207f74935626041e7308c9e88dcdda680f1073 RCTTypeSafety: 146fd11361680250b7580dd1f7f601995cfad1b1 React: f3712351445cc96ba507425675a0cd8d31321d0c React-callinvoker: dcc51a66e02d20a70aeca2abbb1388d4d3011bf8 - React-Codegen: 04b7e88a7f5d3933d058ffb9cea7b0268666de79 - React-Core: ed3aeebf41aeb621de2ab4b58216a2fd5a5fd141 - React-CoreModules: 9d1e6f44bf658431a3b99561c8058b54b5959190 - React-cxxreact: d2d14fc0c0782bd9ed7a556892769b4034ae027c + React-Codegen: ef431087b06572288cd0f789c9cf1d22b37c3019 + React-Core: 88bf9e0d862195fda28723fd95aef3111025f300 + React-CoreModules: 96a557c45f6be644a82d63066c4ac79173bba0ff + React-cxxreact: 3db957f2a0db039b95c1103ea2274e36815b8009 React-debug: 4e90d08c78aa207c064a3860e1540ff252695585 React-jsc: 9ffa4c837c5286366d27c892b6c7c34da3cd5f3d - React-jsi: 020729f637b93456de0018061d44ce36f33c2d8a - React-jsiexecutor: ce8ecfcd3b7dbc9cb65a661110be17f5afd18aa3 + React-jsi: 08cb162e1d192bf197bc0693270ab65d8e9d4d5c + React-jsiexecutor: b71b576b4447d9fed6f2f1b146550de70d49a75a React-jsinspector: b86a8abae760c28d69366bbc1d991561e51341ed - React-logger: ed7c9e01e58529065e7da6bf8318baf15024283e - react-native-ble-plx: f10240444452dfb2d2a13a0e4f58d7783e92d76e - react-native-blur: cfdad7b3c01d725ab62a8a729f42ea463998afa2 - react-native-linear-gradient-text: 730c566376c8aa55688030899b044f7853b2fc60 - react-native-netinfo: fefd4e98d75cbdd6e85fc530f7111a8afdf2b0c5 - react-native-safe-area-context: 9697629f7b2cda43cf52169bb7e0767d330648c2 - react-native-webview: 8a4f68a75fc7efaedfdeba6d5aa2db4a4f74f281 - React-NativeModulesApple: 7bab439cb5de9a76299210ed1127698170777a7f + React-logger: 8c0f8173197ad28ac3212c18f8141690209dfe52 + react-native-ble-plx: 2dd8780ee8d34c60399aaa897249ed1860ebef10 + react-native-blur: c91c10c67be1f096415e63925640baa59e2132d2 + react-native-linear-gradient-text: f31d139238b43c1c7db6c3b6a54b1fdfb2fb0137 + react-native-netinfo: 58a62fc2fc7245875718b99ac9914c307e1523ca + react-native-safe-area-context: 8745463257fac6150abd2281b02de640b3595ec7 + react-native-webview: acea6ec36cdb651fa193cdf690798bf0a587b5fb + React-NativeModulesApple: ee6c836571c874dc879cf87603edff00d8dded46 React-perflogger: 6acc671f527e69c0cd93b8e62821d33d3ddf25ca React-RCTActionSheet: 569bb9db46d85565d14697e15ecf2166e035eb07 React-RCTAnimation: 0eea98143c2938a8751a33722623d3e8a38fe1e4 - React-RCTAppDelegate: 74d38dbb3d8691f72e6dda670006e85d9ea21c91 + React-RCTAppDelegate: 11e6d38c00a34e1025b9ef26bb13968f6d9ed902 React-RCTBlob: 9b3b60e806ce5c9fe5a8ee449f3e41087617441c React-RCTImage: 0220975422a367e784dfd794adfc6454fab23c1f React-RCTLinking: 1abf9834017e080ecbd5b6a28b4fb15eb843a3dd @@ -904,23 +904,23 @@ SPEC CHECKSUMS: React-RCTVibration: 372a12b697a170aaee792f8a9999c40e1f2692d0 React-rncore: d1ccbd5adaf4a67703790838b7c62f140e72d32a React-runtimeexecutor: d4f7ff5073fcf87e14dbf89541d434218630246e - React-runtimescheduler: b360635f6f804ec42fa875500620882a6b97d2f5 - React-utils: 8eb3c12fd4a4da6df3824f7d9a961d73a6ed6e5d - ReactCommon: 317bddf4a70fca9e542343e942a504285282971c - RNCAsyncStorage: f47fe18526970a69c34b548883e1aeceb115e3e1 - RNCCheckbox: a3ca9978cb0846b981d28da4e9914bd437403d77 - RNCMaskedView: 090213d32d8b3bb83a4dcb7d12c18f0152591906 - RNDeviceInfo: 5795b418ed3451ebcaf39384e6cf51f60cb931c9 - RNFastImage: 5c9c9fed9c076e521b3f509fe79e790418a544e8 - RNFS: 4ac0f0ea233904cb798630b3c077808c06931688 - RNGestureHandler: dec4645026e7401a0899f2846d864403478ff6a5 - RNKeychain: ff836453cba46938e0e9e4c22e43d43fa2c90333 - RNLocalize: dbea38dcb344bf80ff18a1757b1becf11f70cae4 - RNReanimated: c2027c397233801d4aceb7375e000ac87ec2e67d - RNScreens: 6a8a3c6b808aa48dca1780df7b73ea524f602c63 - RNScrypt: 95fdef077b482e9fdf8a761120cb69d129ef4016 - RNSentry: bec881f5b16c6b4df4d6f554326ca979e08d6ba2 - RNSVG: 80584470ff1ffc7994923ea135a3e5ad825546b9 + React-runtimescheduler: 06b060b5b022f4cdb6bd9fd3405396372179cd9b + React-utils: e50991349b1b749744f35ff93d943343886deb24 + ReactCommon: 394d4d2b27d88bb8ae15fa7f864a4a7525f467f0 + RNCAsyncStorage: bcf77a018826d6affe62ff0b5526e60de29f78da + RNCCheckbox: 450ce156f3e29e25efa0315c96cfbabe5a39ded1 + RNCMaskedView: de80352547bd4f0d607bf6bab363d826822bd126 + RNDeviceInfo: 55dd986d234bf6d56469fb70a5a3ae972fc59330 + RNFastImage: 462a183c4b0b6b26fdfd639e1ed6ba37536c3b87 + RNFS: 89de7d7f4c0f6bafa05343c578f61118c8282ed8 + RNGestureHandler: 025e54543330650898436f429d9220ebf64d22f5 + RNKeychain: 3a8b12f0fdd0b967811a48d9e83d582fc2e483eb + RNLocalize: 01b15a24a8f1856eeaeeb77f4179ccd88c117a73 + RNReanimated: 7a6c3992ad88c186fc5790d7dc3124a3e3d7bd43 + RNScreens: f1a2bc1890623ba12ff757d72e54d04af1cb7842 + RNScrypt: 3e8612600c93aaa3bfa7849b6d1a4ab61613e2af + RNSentry: f2c6948497745dbe362800f91d328c59c3fbc5e7 + RNSVG: 51fc2eb4e3fc8056fa19c34101461fc26a4326f9 SDWebImage: a7f831e1a65eb5e285e3fb046a23fcfbf08e696d SDWebImageWebPCoder: 908b83b6adda48effe7667cd2b7f78c897e5111d Sentry: f8374b5415bc38dfb5645941b3ae31230fbeae57 @@ -931,4 +931,4 @@ SPEC CHECKSUMS: PODFILE CHECKSUM: 1b80cbddaba737f92135e95d5ff6a43d162ebc77 -COCOAPODS: 1.15.2 +COCOAPODS: 1.16.2 diff --git a/packages/mobile/src/modals/ledger/ledger-selector.tsx b/packages/mobile/src/modals/ledger/ledger-selector.tsx index 25d58f9943..4a7cc36b10 100644 --- a/packages/mobile/src/modals/ledger/ledger-selector.tsx +++ b/packages/mobile/src/modals/ledger/ledger-selector.tsx @@ -9,6 +9,7 @@ import { WalletError } from "@keplr-wallet/router"; import { ErrCodeAppNotInitialised, ErrCodeDeviceLocked, + ErrFailedInit, ErrModuleLedgerSign, } from "@keplr-wallet/background/build/ledger/types"; @@ -32,7 +33,6 @@ export const LedgerNanoBLESelector: FunctionComponent<{ }) => { const style = useStyle(); - // const [pairingText, setIsPairingText] = useState(""); const [isConnecting, setIsConnecting] = useState(false); const testLedgerConnection = async () => { @@ -62,8 +62,13 @@ export const LedgerNanoBLESelector: FunctionComponent<{ onCanResume(); } catch (e) { if (e instanceof WalletError && e.module === ErrModuleLedgerSign) { - if (e.code === ErrCodeAppNotInitialised) { - setBluetoothMode(BluetoothMode.Device); + setBluetoothMode(BluetoothMode.Device); + if (e.code === ErrFailedInit) { + setMainContent( + "press and hold two buttons at the same time and enter your pin" + ); + setIsConnecting(false); + } else if (e.code === ErrCodeAppNotInitialised) { setMainContent( "Open Cosmos app on your ledger and pair with ASI Alliance Wallet" ); diff --git a/packages/mobile/src/screens/register/ledger/index.tsx b/packages/mobile/src/screens/register/ledger/index.tsx index 4fc8b66a9b..1337569222 100644 --- a/packages/mobile/src/screens/register/ledger/index.tsx +++ b/packages/mobile/src/screens/register/ledger/index.tsx @@ -184,6 +184,18 @@ export const LedgerScreen: FunctionComponent = () => { ], { cancelable: false } ); + } else if (newState === State.Unsupported) { + Alert.alert( + "Bluetooth Unavailable", + "This device does not support Bluetooth connectivity.", + [ + { + text: "Close", + style: "cancel", + }, + ], + { cancelable: false } + ); } }, true); return () => { @@ -269,6 +281,7 @@ export const LedgerScreen: FunctionComponent = () => { setLocationError("Location services are disabled"); return; } + setIsCreating(true); try { await registerConfig.createLedger(