Enable "Chain Specific App" for Ledger (#10826)

* Bump ui to 3.9.1

* Enable "Chain Specific App" support for Ledger

* Add Ledger query for accounts and modals
This commit is contained in:
Tarik Gul 2024-08-14 06:38:04 -07:00 committed by GitHub
parent 1a160117dc
commit dbb85b4975
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 128 additions and 67 deletions

View File

@ -40,7 +40,7 @@
"@polkadot/api": "^12.2.1",
"@polkadot/api-derive": "^12.2.1",
"@polkadot/networks": "^13.0.2",
"@polkadot/react-identicon": "^3.8.3",
"@polkadot/react-identicon": "^3.9.1",
"@polkadot/types": "^12.2.1",
"@polkadot/types-codec": "^12.2.1",
"@polkadot/util": "^13.0.2",

View File

@ -1,10 +1,14 @@
// Copyright 2017-2024 @polkadot/app-accounts authors & contributors
// SPDX-License-Identifier: Apache-2.0
// This is for the use of `Ledger`
//
/* eslint-disable deprecation/deprecation */
import type { ApiPromise } from '@polkadot/api';
import type { SubmittableExtrinsic } from '@polkadot/api/types';
import type { DeriveDemocracyLock, DeriveStakingAccount } from '@polkadot/api-derive/types';
import type { LedgerGeneric } from '@polkadot/hw-ledger';
import type { Ledger, LedgerGeneric } from '@polkadot/hw-ledger';
import type { ActionStatus } from '@polkadot/react-components/Status/types';
import type { Option } from '@polkadot/types';
import type { ProxyDefinition, RecoveryConfig } from '@polkadot/types/interfaces';
@ -17,6 +21,7 @@ import useAccountLocks from '@polkadot/app-referenda/useAccountLocks';
import { AddressInfo, AddressSmall, Badge, Button, ChainLock, Columar, CryptoType, Forget, LinkExternal, Menu, Popup, styled, Table, Tags, TransferModal } from '@polkadot/react-components';
import { useAccountInfo, useApi, useBalancesAll, useBestNumber, useCall, useLedger, useQueue, useStakingInfo, useToggle } from '@polkadot/react-hooks';
import { keyring } from '@polkadot/ui-keyring';
import { settings } from '@polkadot/ui-settings';
import { BN, BN_ZERO, formatBalance, formatNumber, isFunction } from '@polkadot/util';
import Backup from '../modals/Backup.js';
@ -136,10 +141,16 @@ function createClearReferendaTx (api: ApiPromise, address: string, ids: [BN, BN]
return api.tx.utility.batch(inner);
}
async function showLedgerAddress (getLedger: () => LedgerGeneric, meta: KeyringJson$Meta, ss58Prefix: number): Promise<void> {
async function showLedgerAddress (getLedger: () => LedgerGeneric | Ledger, meta: KeyringJson$Meta, ss58Prefix: number): Promise<void> {
const currApp = settings.get().ledgerApp;
const ledger = getLedger();
await ledger.getAddress(ss58Prefix, true, meta.accountOffset || 0, meta.addressOffset || 0);
if (currApp === 'migration' || currApp === 'generic') {
await (ledger as LedgerGeneric).getAddress(ss58Prefix, true, meta.accountOffset || 0, meta.addressOffset || 0);
} else {
// This will always be the `chainSpecific` setting if the above condition is not met
await (ledger as Ledger).getAddress(true, meta.accountOffset || 0, meta.addressOffset || 0);
}
}
const transformRecovery = {

View File

@ -1,8 +1,12 @@
// Copyright 2017-2024 @polkadot/app-accounts authors & contributors
// SPDX-License-Identifier: Apache-2.0
// This is for the use of `Ledger`
//
/* eslint-disable deprecation/deprecation */
import type { ApiPromise } from '@polkadot/api';
import type { LedgerGeneric } from '@polkadot/hw-ledger';
import type { Ledger, LedgerGeneric } from '@polkadot/hw-ledger';
import React, { useCallback, useRef, useState } from 'react';
@ -28,8 +32,20 @@ interface Props {
export const AVAIL_INDEXES = arrayRange(20);
// query the ledger for the address, adding it to the keyring
async function queryLedger (api: ApiPromise, getLedger: () => LedgerGeneric, name: string, accountOffset: number, addressOffset: number, ss58Prefix: number): Promise<void> {
const { address } = await getLedger().getAddress(ss58Prefix, false, accountOffset, addressOffset);
async function queryLedger (api: ApiPromise, getLedger: () => LedgerGeneric | Ledger, name: string, accountOffset: number, addressOffset: number, ss58Prefix: number): Promise<void> {
let address: string;
const currApp = settings.get().ledgerApp;
if (currApp === 'migration' || currApp === 'generic') {
const addr = await (getLedger() as LedgerGeneric).getAddress(ss58Prefix, false, accountOffset, addressOffset);
address = addr.address;
} else {
// This will always be the `chainSpecific` setting if the above condition is not met
const addr = await (getLedger() as Ledger).getAddress(false, accountOffset, addressOffset);
address = addr.address;
}
keyring.addHardware(address, 'ledger', {
accountOffset,

View File

@ -21,11 +21,11 @@
"@fortawesome/react-fontawesome": "^0.2.0",
"@polkadot/keyring": "^13.0.2",
"@polkadot/react-api": "^0.142.2-20-x",
"@polkadot/react-identicon": "^3.8.3",
"@polkadot/react-qr": "^3.8.3",
"@polkadot/react-identicon": "^3.9.1",
"@polkadot/react-qr": "^3.9.1",
"@polkadot/react-query": "^0.142.2-20-x",
"@polkadot/ui-keyring": "^3.8.3",
"@polkadot/ui-settings": "^3.8.3",
"@polkadot/ui-keyring": "^3.9.1",
"@polkadot/ui-settings": "^3.9.1",
"@polkadot/util": "^13.0.2",
"@polkadot/util-crypto": "^13.0.2",
"chart.js": "^4.4.1",

View File

@ -1,12 +1,16 @@
// Copyright 2017-2024 @polkadot/react-hooks authors & contributors
// SPDX-License-Identifier: Apache-2.0
// This is for the use of `Ledger`
//
/* eslint-disable deprecation/deprecation */
import type { ApiPromise } from '@polkadot/api';
import type { TransportType } from '@polkadot/hw-ledger-transports/types';
import { useCallback, useMemo } from 'react';
import { LedgerGeneric } from '@polkadot/hw-ledger';
import { Ledger, LedgerGeneric } from '@polkadot/hw-ledger';
import { knownGenesis, knownLedger } from '@polkadot/networks/defaults';
import { settings } from '@polkadot/ui-settings';
import { assert } from '@polkadot/util';
@ -22,7 +26,7 @@ interface StateBase {
}
interface State extends StateBase {
getLedger: () => LedgerGeneric;
getLedger: () => LedgerGeneric | Ledger;
}
const EMPTY_STATE: StateBase = {
@ -37,11 +41,11 @@ const ledgerChains = Object
.keys(knownGenesis)
.filter((n) => knownLedger[n]);
const ledgerHashes = ledgerChains.reduce<string[]>((all, n) => [...all, ...knownGenesis[n]], []);
let ledger: LedgerGeneric | null = null;
let ledger: LedgerGeneric | Ledger | null = null;
let ledgerType: TransportType | null = null;
let ledgerApp: string | null;
function retrieveLedger (api: ApiPromise): LedgerGeneric {
function retrieveLedger (api: ApiPromise): LedgerGeneric | Ledger {
const currType = settings.get().ledgerConn as TransportType;
const currApp = settings.get().ledgerApp;
@ -57,6 +61,8 @@ function retrieveLedger (api: ApiPromise): LedgerGeneric {
ledger = new LedgerGeneric(currType, network, knownLedger.polkadot);
} else if (currApp === 'migration') {
ledger = new LedgerGeneric(currType, network, knownLedger[network]);
} else if (currApp === 'chainSpecific') {
ledger = new Ledger(currType, network);
} else {
// This will never get touched since it will always hit the above two. This satisfies the compiler.
ledger = new LedgerGeneric(currType, network, knownLedger.polkadot);

View File

@ -1,10 +1,14 @@
// Copyright 2017-2024 @polkadot/react-signer authors & contributors
// SPDX-License-Identifier: Apache-2.0
// This is for the use of `Ledger`
//
/* eslint-disable deprecation/deprecation */
import type { ApiPromise } from '@polkadot/api';
import type { SignerOptions } from '@polkadot/api/submittable/types';
import type { SubmittableExtrinsic } from '@polkadot/api/types';
import type { LedgerGeneric } from '@polkadot/hw-ledger';
import type { Ledger, LedgerGeneric } from '@polkadot/hw-ledger';
import type { KeyringPair } from '@polkadot/keyring/types';
import type { QueueTx, QueueTxMessageSetStatus } from '@polkadot/react-components/Status/types';
import type { Option } from '@polkadot/types';
@ -19,6 +23,7 @@ import { web3FromSource } from '@polkadot/extension-dapp';
import { Button, ErrorBoundary, Modal, Output, styled, Toggle } from '@polkadot/react-components';
import { useApi, useLedger, useQueue, useToggle } from '@polkadot/react-hooks';
import { keyring } from '@polkadot/ui-keyring';
import { settings } from '@polkadot/ui-settings';
import { assert, nextTick } from '@polkadot/util';
import { addressEq } from '@polkadot/util-crypto';
@ -190,7 +195,7 @@ async function wrapTx (api: ApiPromise, currentItem: QueueTx, { isMultiCall, mul
return tx;
}
async function extractParams (api: ApiPromise, address: string, options: Partial<SignerOptions>, getLedger: () => LedgerGeneric, setQrState: (state: QrState) => void): Promise<['qr' | 'signing', string, Partial<SignerOptions>, boolean]> {
async function extractParams (api: ApiPromise, address: string, options: Partial<SignerOptions>, getLedger: () => LedgerGeneric | Ledger, setQrState: (state: QrState) => void): Promise<['qr' | 'signing', string, Partial<SignerOptions>, boolean]> {
const pair = keyring.getPair(address);
const { meta: { accountOffset, addressOffset, isExternal, isHardware, isInjected, isLocal, isProxied, source } } = pair;
@ -286,10 +291,18 @@ function TxSigned ({ className, currentItem, isQueueSubmit, queueSize, requestAd
passwordError = unlockAccount(senderInfo);
} else if (flags.isHardware) {
try {
const currApp = settings.get().ledgerApp;
const ledger = getLedger();
const { address } = await ledger.getAddress(api.consts.system.ss58Prefix.toNumber(), false, flags.accountOffset, flags.addressOffset);
console.log(`Signing with Ledger address ${address}`);
if (currApp === 'migration' || currApp === 'generic') {
const { address } = await (ledger as LedgerGeneric).getAddress(api.consts.system.ss58Prefix.toNumber(), false, flags.accountOffset, flags.addressOffset);
console.log(`Signing with Ledger address ${address}`);
} else {
const { address } = await (ledger as Ledger).getAddress(false, flags.accountOffset, flags.addressOffset);
console.log(`Signing with Ledger address ${address}`);
}
} catch (error) {
console.error(error);

View File

@ -1,11 +1,16 @@
// Copyright 2017-2024 @polkadot/react-signer authors & contributors
// SPDX-License-Identifier: Apache-2.0
// This is for the use of `Ledger`
//
/* eslint-disable deprecation/deprecation */
import type { ApiPromise } from '@polkadot/api';
import type { Signer, SignerResult } from '@polkadot/api/types';
import type { LedgerGeneric } from '@polkadot/hw-ledger';
import type { Ledger, LedgerGeneric } from '@polkadot/hw-ledger';
import type { Registry, SignerPayloadJSON } from '@polkadot/types/types';
import { settings } from '@polkadot/ui-settings';
import { objectSpread, u8aToHex } from '@polkadot/util';
import { merkleizeMetadata } from '@polkadot-api/merkleize-metadata';
@ -14,11 +19,11 @@ let id = 0;
export class LedgerSigner implements Signer {
readonly #accountIndex: number;
readonly #addressOffset: number;
readonly #getLedger: () => LedgerGeneric;
readonly #getLedger: () => LedgerGeneric | Ledger;
readonly #registry: Registry;
readonly #api: ApiPromise;
constructor (api: ApiPromise, getLedger: () => LedgerGeneric, accountIndex: number, addressOffset: number) {
constructor (api: ApiPromise, getLedger: () => LedgerGeneric | Ledger, accountIndex: number, addressOffset: number) {
this.#accountIndex = accountIndex;
this.#addressOffset = addressOffset;
this.#getLedger = getLedger;
@ -47,25 +52,35 @@ export class LedgerSigner implements Signer {
}
public async signPayload (payload: SignerPayloadJSON): Promise<SignerResult> {
const { address } = await this.#getLedger().getAddress(
this.#api.consts.system.ss58Prefix.toNumber(),
false,
this.#accountIndex,
this.#addressOffset
);
const { raw, txMetadata } = await this.getMetadataProof(payload);
const currApp = settings.get().ledgerApp;
const buff = Buffer.from(txMetadata);
const { signature } = await this.#getLedger().signWithMetadata(raw.toU8a(true), this.#accountIndex, this.#addressOffset, { metadata: buff });
if (currApp === 'migration' || currApp === 'generic') {
const { address } = await (this.#getLedger() as LedgerGeneric).getAddress(
this.#api.consts.system.ss58Prefix.toNumber(),
false,
this.#accountIndex,
this.#addressOffset
);
const { raw, txMetadata } = await this.getMetadataProof(payload);
const extrinsic = this.#registry.createType(
'Extrinsic',
{ method: raw.method },
{ version: 4 }
);
const buff = Buffer.from(txMetadata);
const { signature } = await (this.#getLedger() as LedgerGeneric).signWithMetadata(raw.toU8a(true), this.#accountIndex, this.#addressOffset, { metadata: buff });
extrinsic.addSignature(address, signature, raw.toHex());
const extrinsic = this.#registry.createType(
'Extrinsic',
{ method: raw.method },
{ version: 4 }
);
return { id: ++id, signature, signedTransaction: extrinsic.toHex() };
extrinsic.addSignature(address, signature, raw.toHex());
return { id: ++id, signature, signedTransaction: extrinsic.toHex() };
} else {
// This is when the option is `chainSpecific`
const raw = this.#registry.createType('ExtrinsicPayload', payload, { version: payload.version });
const { signature } = await this.#getLedger().sign(raw.toU8a(true), this.#accountIndex, this.#addressOffset);
return { id: ++id, signature };
}
}
}

View File

@ -1782,7 +1782,7 @@ __metadata:
"@polkadot/api": "npm:^12.2.1"
"@polkadot/api-derive": "npm:^12.2.1"
"@polkadot/networks": "npm:^13.0.2"
"@polkadot/react-identicon": "npm:^3.8.3"
"@polkadot/react-identicon": "npm:^3.9.1"
"@polkadot/types": "npm:^12.2.1"
"@polkadot/types-codec": "npm:^12.2.1"
"@polkadot/util": "npm:^13.0.2"
@ -2116,11 +2116,11 @@ __metadata:
"@fortawesome/react-fontawesome": "npm:^0.2.0"
"@polkadot/keyring": "npm:^13.0.2"
"@polkadot/react-api": "npm:^0.142.2-20-x"
"@polkadot/react-identicon": "npm:^3.8.3"
"@polkadot/react-qr": "npm:^3.8.3"
"@polkadot/react-identicon": "npm:^3.9.1"
"@polkadot/react-qr": "npm:^3.9.1"
"@polkadot/react-query": "npm:^0.142.2-20-x"
"@polkadot/ui-keyring": "npm:^3.8.3"
"@polkadot/ui-settings": "npm:^3.8.3"
"@polkadot/ui-keyring": "npm:^3.9.1"
"@polkadot/ui-settings": "npm:^3.9.1"
"@polkadot/util": "npm:^13.0.2"
"@polkadot/util-crypto": "npm:^13.0.2"
"@types/chartjs-plugin-crosshair": "npm:^1.1.4"
@ -2171,13 +2171,13 @@ __metadata:
languageName: unknown
linkType: soft
"@polkadot/react-identicon@npm:^3.8.3":
version: 3.8.3
resolution: "@polkadot/react-identicon@npm:3.8.3"
"@polkadot/react-identicon@npm:^3.9.1":
version: 3.9.1
resolution: "@polkadot/react-identicon@npm:3.9.1"
dependencies:
"@polkadot/keyring": "npm:^13.0.2"
"@polkadot/ui-settings": "npm:3.8.3"
"@polkadot/ui-shared": "npm:3.8.3"
"@polkadot/ui-settings": "npm:3.9.1"
"@polkadot/ui-shared": "npm:3.9.1"
"@polkadot/util": "npm:^13.0.2"
"@polkadot/util-crypto": "npm:^13.0.2"
ethereum-blockies-base64: "npm:^1.0.2"
@ -2192,7 +2192,7 @@ __metadata:
react: "*"
react-dom: "*"
react-is: "*"
checksum: 10/6dddfca42732d141acd2e460d25635845287e0e8bfae6c170f72a691c49b1f34d08e1e975d1a2c2216e1814f1928a29982b806d6bc30f3f08ddddad214b91867
checksum: 10/6a3ad4e129af15e2d6833def626085e392c960211935cf901560f434247560ae6dcfb8d8ecde38b56f317ac3e0d84653ef9c5b68e258c2791467b1339b595bef
languageName: node
linkType: hard
@ -2209,11 +2209,11 @@ __metadata:
languageName: unknown
linkType: soft
"@polkadot/react-qr@npm:^3.8.3":
version: 3.8.3
resolution: "@polkadot/react-qr@npm:3.8.3"
"@polkadot/react-qr@npm:^3.9.1":
version: 3.9.1
resolution: "@polkadot/react-qr@npm:3.9.1"
dependencies:
"@polkadot/ui-settings": "npm:3.8.3"
"@polkadot/ui-settings": "npm:3.9.1"
"@polkadot/util": "npm:^13.0.2"
"@polkadot/util-crypto": "npm:^13.0.2"
"@zxing/browser": "npm:^0.1.5"
@ -2227,7 +2227,7 @@ __metadata:
react: "*"
react-dom: "*"
react-is: "*"
checksum: 10/1afb77b45a4b6bf0cb110dbf405bfd8a1edbabb9e4639e3b34f0f7f0c1c706799f462b1590e18da8d8de369172f075bdbc9800e7d54e9b48041724203294c600
checksum: 10/f196e19e6d5c11ef7f7824fa67ad41fcde790ad6505113a2e8383c4c79c5c2a8947a619c0513709d9983679ff60af7f8c0811e1a232772fb5438dcbecd442e21
languageName: node
linkType: hard
@ -2396,12 +2396,12 @@ __metadata:
languageName: node
linkType: hard
"@polkadot/ui-keyring@npm:^3.8.3":
version: 3.8.3
resolution: "@polkadot/ui-keyring@npm:3.8.3"
"@polkadot/ui-keyring@npm:^3.9.1":
version: 3.9.1
resolution: "@polkadot/ui-keyring@npm:3.9.1"
dependencies:
"@polkadot/keyring": "npm:^13.0.2"
"@polkadot/ui-settings": "npm:3.8.3"
"@polkadot/ui-settings": "npm:3.9.1"
"@polkadot/util": "npm:^13.0.2"
"@polkadot/util-crypto": "npm:^13.0.2"
mkdirp: "npm:^3.0.1"
@ -2412,13 +2412,13 @@ __metadata:
"@polkadot/keyring": "*"
"@polkadot/ui-settings": "*"
"@polkadot/util": "*"
checksum: 10/d8b3ebf3da39ebbcabe1e0c41c9ed731a14c186c1698c598b4aac6217162c7174453860d06582200b10bc687fc75ec713238ce1687efb4672de989ab728ee99f
checksum: 10/eec438ef2ad9363d77f51d60df9802f1cad3379661883a9412f9ca1af44e31444fc7c04eec1ec7ecf2ae76af1718ffce8e335dcf572d89a6ffc6403bf6c36423
languageName: node
linkType: hard
"@polkadot/ui-settings@npm:3.8.3, @polkadot/ui-settings@npm:^3.8.3":
version: 3.8.3
resolution: "@polkadot/ui-settings@npm:3.8.3"
"@polkadot/ui-settings@npm:3.9.1, @polkadot/ui-settings@npm:^3.9.1":
version: 3.9.1
resolution: "@polkadot/ui-settings@npm:3.9.1"
dependencies:
"@polkadot/networks": "npm:^13.0.2"
"@polkadot/util": "npm:^13.0.2"
@ -2428,20 +2428,20 @@ __metadata:
peerDependencies:
"@polkadot/networks": "*"
"@polkadot/util": "*"
checksum: 10/be6d9f5111ba94b9e86b3348bd0f7e68008611b54fcc159b3a163d7331b345f1552b2d1ba7b6b763387607110e06eecd799b7ad61ee4d5847056b128a85bfdcf
checksum: 10/d3a28af0233f2ee9a8453501de3dd2fabc46bc98cb4c0e46a79654c87a0a41f3148dd6949b796932a4c12fc974b2b924d9eb0e76f91f7ce9ea27e5012e1e606a
languageName: node
linkType: hard
"@polkadot/ui-shared@npm:3.8.3":
version: 3.8.3
resolution: "@polkadot/ui-shared@npm:3.8.3"
"@polkadot/ui-shared@npm:3.9.1":
version: 3.9.1
resolution: "@polkadot/ui-shared@npm:3.9.1"
dependencies:
colord: "npm:^2.9.3"
tslib: "npm:^2.6.2"
peerDependencies:
"@polkadot/util": "*"
"@polkadot/util-crypto": "*"
checksum: 10/a7cbeed7ccbe2f3212d20155787d3a7f4ec2a5662dfb950d965a1ac8ea05fd380f3b9456147cf2e15dc5adbf0df71eedfd94c0707db44a16f1bf2f84c0ebf01c
checksum: 10/80a3a59ca25ed0e572a2eb85533f54f9714c6fd21f85fcf46775fc57c5d55c048049378c94372260df9d860569197ea38dbf3a24cc8ed1e416f67c69408f3789
languageName: node
linkType: hard