@reef-knot/ledger-connector 5.1.0 → 5.2.0-alpha.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,5 @@
1
+ import type Eth from '@ledgerhq/hw-app-eth';
2
+ import { type Address, type LocalAccount } from 'viem';
3
+ export type WithEthApp = <T>(callback: (eth: Eth) => T | Promise<T>) => Promise<T>;
4
+ export declare const createLedgerAccount: (address: Address, path: string, withEthApp: WithEthApp) => LocalAccount;
5
+ //# sourceMappingURL=account.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"account.d.ts","sourceRoot":"","sources":["../../src/hid/account.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,GAAG,MAAM,sBAAsB,CAAC;AAE5C,OAAO,EAOL,KAAK,OAAO,EACZ,KAAK,YAAY,EAGlB,MAAM,MAAM,CAAC;AAId,MAAM,MAAM,UAAU,GAAG,CAAC,CAAC,EACzB,QAAQ,EAAE,CAAC,GAAG,EAAE,GAAG,KAAK,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,KACnC,OAAO,CAAC,CAAC,CAAC,CAAC;AAiBhB,eAAO,MAAM,mBAAmB,GAC9B,SAAS,OAAO,EAChB,MAAM,MAAM,EACZ,YAAY,UAAU,KACrB,YAiFC,CAAC"}
@@ -0,0 +1,56 @@
1
+ import { bytesToHex, getTypesForEIP712Domain, hashStruct, serializeSignature, serializeTransaction, stringToHex } from "viem";
2
+ import { toAccount } from "viem/accounts";
3
+ //#region src/hid/account.ts
4
+ const RESOLUTION_CONFIG = {
5
+ erc20: true,
6
+ externalPlugins: true,
7
+ nft: true
8
+ };
9
+ const shouldFallbackToHashedSigning = (error) => error?.statusText === "INS_NOT_SUPPORTED";
10
+ const toSignatureHex = ({ r, s, v }) => serializeSignature({
11
+ r: `0x${r}`,
12
+ s: `0x${s}`,
13
+ v: BigInt(v)
14
+ });
15
+ const createLedgerAccount = (address, path, withEthApp) => toAccount({
16
+ address,
17
+ async signTransaction(transaction, { serializer = serializeTransaction } = {}) {
18
+ const unsignedRawTx = (await serializer(transaction)).slice(2);
19
+ const { r, s, v } = await withEthApp((eth) => eth.clearSignTransaction(path, unsignedRawTx, RESOLUTION_CONFIG, true));
20
+ return serializer(transaction, {
21
+ r: `0x${r}`,
22
+ s: `0x${s}`,
23
+ v: BigInt(`0x${v}`)
24
+ });
25
+ },
26
+ async signMessage({ message }) {
27
+ const messageHex = typeof message === "string" ? stringToHex(message) : typeof message.raw === "string" ? message.raw : bytesToHex(message.raw);
28
+ const signature = await withEthApp((eth) => eth.signPersonalMessage(path, messageHex.slice(2)));
29
+ return toSignatureHex(signature);
30
+ },
31
+ async signTypedData(parameters) {
32
+ const { domain = {}, types, primaryType, message } = parameters;
33
+ const signature = await withEthApp(async (eth) => {
34
+ try {
35
+ return await eth.signEIP712Message(path, parameters);
36
+ } catch (error) {
37
+ if (!shouldFallbackToHashedSigning(error)) throw error;
38
+ const typedDataDomain = domain;
39
+ const domainSeparator = hashStruct({
40
+ data: typedDataDomain,
41
+ primaryType: "EIP712Domain",
42
+ types: { EIP712Domain: getTypesForEIP712Domain({ domain: typedDataDomain }) }
43
+ });
44
+ const messageHash = hashStruct({
45
+ data: message,
46
+ primaryType,
47
+ types
48
+ });
49
+ return eth.signEIP712HashedMessage(path, domainSeparator.slice(2), messageHash.slice(2));
50
+ }
51
+ });
52
+ return toSignatureHex(signature);
53
+ }
54
+ });
55
+ //#endregion
56
+ export { createLedgerAccount };
@@ -1 +1 @@
1
- {"version":3,"file":"connector.d.ts","sourceRoot":"","sources":["../../src/hid/connector.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,KAAK,EAAE,MAAM,cAAc,CAAC;AAGrC,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AACnD,eAAO,MAAM,WAAW,cAAc,CAAC;AACvC,eAAO,MAAM,IAAI,WAAW,CAAC;AAG7B,wBAAgB,kBAAkB,CAAC,EACjC,GAAG,EACH,YAAY,GACb,EAAE;IACD,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC5B,YAAY,EAAE,KAAK,CAAC;CACrB,yGAiHA;yBAvHe,kBAAkB"}
1
+ {"version":3,"file":"connector.d.ts","sourceRoot":"","sources":["../../src/hid/connector.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,KAAK,EAAE,MAAM,cAAc,CAAC;AAUrC,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AACnD,eAAO,MAAM,WAAW,cAAc,CAAC;AACvC,eAAO,MAAM,IAAI,WAAW,CAAC;AAG7B,wBAAgB,kBAAkB,CAAC,EACjC,GAAG,EACH,YAAY,GACb,EAAE;IACD,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC5B,YAAY,EAAE,KAAK,CAAC;CACrB,yGAgIA;yBAtIe,kBAAkB"}
@@ -1,52 +1,64 @@
1
- import { checkError, clearLedgerDerivationPath } from "./helpers.js";
1
+ import { checkError, clearLedgerAccount, clearLedgerChainId, clearLedgerDerivationPath, restoreLedgerChainId, saveLedgerChainId } from "./helpers.js";
2
2
  import { ChainNotConfiguredError, ConnectorNotFoundError, createConnector } from "wagmi";
3
-
3
+ import { SwitchChainError } from "viem";
4
4
  //#region src/hid/connector.ts
5
5
  const idLedgerHid = "ledgerHID";
6
6
  const name = "Ledger";
7
7
  ledgerHIDConnector.type = "ledgerHID";
8
8
  function ledgerHIDConnector({ rpc, defaultChain }) {
9
9
  const providers = {};
10
+ let currentChainId = restoreLedgerChainId();
10
11
  return createConnector(({ chains, emitter }) => ({
11
12
  id: idLedgerHid,
12
13
  name,
13
14
  type: ledgerHIDConnector.type,
14
15
  async getProvider({ chainId } = {}) {
15
- const chain = chains.find((x) => x.id === chainId) ?? defaultChain;
16
+ const chain = chains.find((x) => x.id === (chainId ?? currentChainId)) ?? defaultChain;
16
17
  if (!providers[chain.id]) {
17
18
  const { LedgerHQProvider } = await import("./provider.js");
18
- providers[chain.id] = new LedgerHQProvider({ url: rpc?.[chain.id] }, chain.id);
19
+ providers[chain.id] = new LedgerHQProvider({
20
+ chain,
21
+ rpcUrl: rpc?.[chain.id]
22
+ });
19
23
  }
20
24
  return providers[chain.id];
21
25
  },
22
- async connect({ withCapabilities = false } = {}) {
26
+ async connect({ chainId, withCapabilities = false } = {}) {
23
27
  try {
24
- const provider = await this.getProvider();
28
+ currentChainId = chainId ?? currentChainId;
29
+ const provider = await this.getProvider({ chainId: currentChainId });
25
30
  provider.on("disconnect", this.onDisconnect);
26
31
  const account = await provider.enable();
27
- const chainId = await this.getChainId();
32
+ const connectedChainId = await this.getChainId();
33
+ currentChainId = connectedChainId;
34
+ saveLedgerChainId(connectedChainId);
28
35
  return {
29
36
  accounts: withCapabilities ? [{
30
37
  address: account,
31
38
  capabilities: {}
32
39
  }] : [account],
33
- chainId
40
+ chainId: connectedChainId
34
41
  };
35
42
  } catch (error) {
43
+ currentChainId = restoreLedgerChainId();
36
44
  return checkError(error);
37
45
  }
38
46
  },
39
47
  async disconnect() {
40
- (await this.getProvider()).removeListener("disconnect", this.onDisconnect);
48
+ Object.values(providers).forEach((provider) => {
49
+ provider.removeListener("disconnect", this.onDisconnect);
50
+ provider.resetAccount();
51
+ });
52
+ currentChainId = void 0;
41
53
  clearLedgerDerivationPath();
54
+ clearLedgerChainId();
55
+ clearLedgerAccount();
42
56
  },
43
57
  async getAccounts() {
44
58
  return [await (await this.getProvider()).getAddress()];
45
59
  },
46
60
  async getChainId() {
47
- const { chainId } = await (await this.getProvider()).getNetwork();
48
- if (chainId) return chainId;
49
- throw new ChainNotConfiguredError();
61
+ return (await this.getProvider()).chain.id;
50
62
  },
51
63
  async isAuthorized() {
52
64
  try {
@@ -59,22 +71,12 @@ function ledgerHIDConnector({ rpc, defaultChain }) {
59
71
  }
60
72
  },
61
73
  async switchChain({ chainId }) {
62
- const id = chainId.toString(16);
63
- emitter.emit("change", { chainId: Number(chainId) });
64
- return Promise.resolve(chains.find((x) => x.id === chainId) ?? {
65
- id: chainId,
66
- name: `Chain ${id}`,
67
- network: `${id}`,
68
- nativeCurrency: {
69
- decimals: 18,
70
- name: "Ether",
71
- symbol: "ETH"
72
- },
73
- rpcUrls: {
74
- default: { http: [""] },
75
- public: { http: [""] }
76
- }
77
- });
74
+ const chain = chains.find((x) => x.id === chainId);
75
+ if (!chain) throw new SwitchChainError(new ChainNotConfiguredError());
76
+ currentChainId = chainId;
77
+ saveLedgerChainId(chainId);
78
+ emitter.emit("change", { chainId });
79
+ return chain;
78
80
  },
79
81
  onDisconnect() {
80
82
  emitter.emit("disconnect");
@@ -83,6 +85,5 @@ function ledgerHIDConnector({ rpc, defaultChain }) {
83
85
  onChainChanged() {}
84
86
  }));
85
87
  }
86
-
87
88
  //#endregion
88
- export { idLedgerHid, ledgerHIDConnector };
89
+ export { idLedgerHid, ledgerHIDConnector, name };
@@ -1,2 +1,4 @@
1
1
  export declare const LS_KEY_DERIVATION_PATH = "reef-knot_ledger-derivation-path";
2
+ export declare const LS_KEY_CHAIN_ID = "reef-knot_ledger-chain-id";
3
+ export declare const LS_KEY_ACCOUNT = "reef-knot_ledger-account";
2
4
  //# sourceMappingURL=constants.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../../src/hid/constants.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,sBAAsB,qCAAqC,CAAC"}
1
+ {"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../../src/hid/constants.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,sBAAsB,qCAAqC,CAAC;AACzE,eAAO,MAAM,eAAe,8BAA8B,CAAC;AAC3D,eAAO,MAAM,cAAc,6BAA6B,CAAC"}
@@ -1,5 +1,6 @@
1
1
  //#region src/hid/constants.ts
2
2
  const LS_KEY_DERIVATION_PATH = "reef-knot_ledger-derivation-path";
3
-
3
+ const LS_KEY_CHAIN_ID = "reef-knot_ledger-chain-id";
4
+ const LS_KEY_ACCOUNT = "reef-knot_ledger-account";
4
5
  //#endregion
5
- export { LS_KEY_DERIVATION_PATH };
6
+ export { LS_KEY_ACCOUNT, LS_KEY_CHAIN_ID, LS_KEY_DERIVATION_PATH };
@@ -1,13 +1,15 @@
1
- import { BigNumberish } from '@ethersproject/bignumber';
2
- import { TransactionRequestExtended, UnsignedTransactionStrict } from './types';
3
- export declare const isHIDSupported: () => boolean;
4
- export declare const hasEIP1559: (tx: {
5
- type?: number;
6
- maxFeePerGas?: BigNumberish;
7
- maxPriorityFeePerGas?: BigNumberish;
8
- }) => boolean;
9
- export declare const toNumber: (value: BigNumberish | undefined | null) => number | undefined;
10
- export declare const convertToUnsigned: (tx: TransactionRequestExtended) => Promise<UnsignedTransactionStrict>;
1
+ import { type Address } from 'viem';
2
+ export type PersistedLedgerAccount = {
3
+ address: Address;
4
+ path: string;
5
+ };
6
+ export declare const isLockedDeviceError: (error: unknown) => boolean;
11
7
  export declare const checkError: (error: any) => never;
12
8
  export declare const clearLedgerDerivationPath: () => void;
9
+ export declare const saveLedgerChainId: (chainId: number) => void;
10
+ export declare const restoreLedgerChainId: () => number | undefined;
11
+ export declare const clearLedgerChainId: () => void;
12
+ export declare const saveLedgerAccount: (account: PersistedLedgerAccount) => void;
13
+ export declare const restoreLedgerAccount: () => PersistedLedgerAccount | undefined;
14
+ export declare const clearLedgerAccount: () => void;
13
15
  //# sourceMappingURL=helpers.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"helpers.d.ts","sourceRoot":"","sources":["../../src/hid/helpers.ts"],"names":[],"mappings":"AAAA,OAAO,EAAa,YAAY,EAAE,MAAM,0BAA0B,CAAC;AAEnE,OAAO,EAAE,0BAA0B,EAAE,yBAAyB,EAAE,MAAM,SAAS,CAAC;AAGhF,eAAO,MAAM,cAAc,eAM1B,CAAC;AAEF,eAAO,MAAM,UAAU,OAAQ;IAC7B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,YAAY,CAAC,EAAE,YAAY,CAAC;IAC5B,oBAAoB,CAAC,EAAE,YAAY,CAAC;CACrC,YAIA,CAAC;AAEF,eAAO,MAAM,QAAQ,UACZ,YAAY,GAAG,SAAS,GAAG,IAAI,KACrC,MAAM,GAAG,SAEX,CAAC;AAEF,eAAO,MAAM,iBAAiB,OACxB,0BAA0B,KAC7B,OAAO,CAAC,yBAAyB,CAgCnC,CAAC;AAEF,eAAO,MAAM,UAAU,UAAW,GAAG,KAAG,KAmBvC,CAAC;AAEF,eAAO,MAAM,yBAAyB,YAErC,CAAC"}
1
+ {"version":3,"file":"helpers.d.ts","sourceRoot":"","sources":["../../src/hid/helpers.ts"],"names":[],"mappings":"AAAA,OAAO,EAAa,KAAK,OAAO,EAAE,MAAM,MAAM,CAAC;AAO/C,MAAM,MAAM,sBAAsB,GAAG;IACnC,OAAO,EAAE,OAAO,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;CACd,CAAC;AAEF,eAAO,MAAM,mBAAmB,GAAI,OAAO,OAAO,KAAG,OACc,CAAC;AAEpE,eAAO,MAAM,UAAU,GAAI,OAAO,GAAG,KAAG,KAmBvC,CAAC;AAEF,eAAO,MAAM,yBAAyB,YAErC,CAAC;AAEF,eAAO,MAAM,iBAAiB,GAAI,SAAS,MAAM,SAGhD,CAAC;AAEF,eAAO,MAAM,oBAAoB,QAAO,MAAM,GAAG,SAMhD,CAAC;AAEF,eAAO,MAAM,kBAAkB,YAG9B,CAAC;AAEF,eAAO,MAAM,iBAAiB,GAAI,SAAS,sBAAsB,SAGhE,CAAC;AAEF,eAAO,MAAM,oBAAoB,QAAO,sBAAsB,GAAG,SAchE,CAAC;AAEF,eAAO,MAAM,kBAAkB,YAG9B,CAAC"}
@@ -1,37 +1,7 @@
1
- import { LS_KEY_DERIVATION_PATH } from "./constants.js";
2
- import { BigNumber } from "@ethersproject/bignumber";
3
- import { resolveProperties } from "@ethersproject/properties";
4
-
1
+ import { LS_KEY_ACCOUNT, LS_KEY_CHAIN_ID, LS_KEY_DERIVATION_PATH } from "./constants.js";
2
+ import { isAddress } from "viem";
5
3
  //#region src/hid/helpers.ts
6
- const hasEIP1559 = (tx) => {
7
- return tx.type === 2 || tx.maxFeePerGas != null || tx.maxPriorityFeePerGas != null;
8
- };
9
- const toNumber = (value) => {
10
- return value == null ? void 0 : BigNumber.from(value).toNumber();
11
- };
12
- const convertToUnsigned = async (tx) => {
13
- const resolvedTx = await resolveProperties(tx);
14
- const { chainId, data, gasLimit, gas, gasPrice, value, to } = resolvedTx;
15
- const nonce = toNumber(resolvedTx.nonce);
16
- const type = toNumber(resolvedTx.type);
17
- const baseTx = {
18
- gasLimit: gasLimit || gas,
19
- type: type ?? 2,
20
- chainId,
21
- gasPrice,
22
- nonce,
23
- value,
24
- data,
25
- to
26
- };
27
- if (tx.accessList != null) baseTx.accessList = tx.accessList;
28
- if (hasEIP1559(tx)) {
29
- baseTx.maxFeePerGas = tx.maxFeePerGas;
30
- baseTx.maxPriorityFeePerGas = tx.maxPriorityFeePerGas;
31
- baseTx.type = 2;
32
- }
33
- return baseTx;
34
- };
4
+ const isLockedDeviceError = (error) => error?.name === "LockedDeviceError";
35
5
  const checkError = (error) => {
36
6
  if (error.statusText === "INS_NOT_SUPPORTED") error.message = "Device is not supported. Make sure the Ethereum app is open on the device.";
37
7
  if (error.statusText === "UNKNOWN_ERROR") error.message = "Unknown error. Make sure the device is connected and the Ethereum app is open on the device.";
@@ -41,6 +11,40 @@ const checkError = (error) => {
41
11
  const clearLedgerDerivationPath = () => {
42
12
  window?.localStorage.removeItem(LS_KEY_DERIVATION_PATH);
43
13
  };
44
-
14
+ const saveLedgerChainId = (chainId) => {
15
+ if (typeof window === "undefined") return;
16
+ window.localStorage.setItem(LS_KEY_CHAIN_ID, String(chainId));
17
+ };
18
+ const restoreLedgerChainId = () => {
19
+ if (typeof window === "undefined") return void 0;
20
+ const stored = window.localStorage.getItem(LS_KEY_CHAIN_ID);
21
+ if (!stored) return void 0;
22
+ const chainId = Number(stored);
23
+ return Number.isInteger(chainId) && chainId > 0 ? chainId : void 0;
24
+ };
25
+ const clearLedgerChainId = () => {
26
+ if (typeof window === "undefined") return;
27
+ window.localStorage.removeItem(LS_KEY_CHAIN_ID);
28
+ };
29
+ const saveLedgerAccount = (account) => {
30
+ if (typeof window === "undefined") return;
31
+ window.localStorage.setItem(LS_KEY_ACCOUNT, JSON.stringify(account));
32
+ };
33
+ const restoreLedgerAccount = () => {
34
+ if (typeof window === "undefined") return void 0;
35
+ const stored = window.localStorage.getItem(LS_KEY_ACCOUNT);
36
+ if (!stored) return void 0;
37
+ try {
38
+ const { address, path } = JSON.parse(stored);
39
+ if (address && isAddress(address) && typeof path === "string") return {
40
+ address,
41
+ path
42
+ };
43
+ } catch {}
44
+ };
45
+ const clearLedgerAccount = () => {
46
+ if (typeof window === "undefined") return;
47
+ window.localStorage.removeItem(LS_KEY_ACCOUNT);
48
+ };
45
49
  //#endregion
46
- export { checkError, clearLedgerDerivationPath, convertToUnsigned, toNumber };
50
+ export { checkError, clearLedgerAccount, clearLedgerChainId, clearLedgerDerivationPath, isLockedDeviceError, restoreLedgerAccount, restoreLedgerChainId, saveLedgerAccount, saveLedgerChainId };
package/dist/hid/index.js CHANGED
@@ -1,2 +1,3 @@
1
1
  import { LS_KEY_DERIVATION_PATH } from "./constants.js";
2
2
  import { idLedgerHid, ledgerHIDConnector } from "./connector.js";
3
+ export { LS_KEY_DERIVATION_PATH, idLedgerHid, ledgerHIDConnector };
@@ -1,21 +1,42 @@
1
- import { JsonRpcBatchProvider, Network } from '@ethersproject/providers';
1
+ import type Eth from '@ledgerhq/hw-app-eth';
2
2
  import type TransportWebHID from '@ledgerhq/hw-transport-webhid';
3
- import { LedgerHQSigner } from './signer';
4
- export declare class LedgerHQProvider extends JsonRpcBatchProvider {
5
- signer?: LedgerHQSigner;
3
+ import { type Address, type Chain, type LocalAccount } from 'viem';
4
+ type RequestArguments = {
5
+ method: string;
6
+ params?: unknown[];
7
+ };
8
+ type ProviderEvent = 'disconnect';
9
+ type Listener = () => void;
10
+ export declare class LedgerHQProvider {
11
+ readonly chain: Chain;
6
12
  device?: HIDDevice;
7
13
  transport?: typeof TransportWebHID;
8
- constructor(...args: any[]);
14
+ private readonly rpcUrl?;
15
+ private account?;
16
+ private accountPath?;
17
+ private publicClient?;
18
+ private walletClient?;
19
+ private listeners;
20
+ constructor({ chain, rpcUrl }: {
21
+ chain: Chain;
22
+ rpcUrl?: string;
23
+ });
24
+ on(event: ProviderEvent, listener: Listener): void;
25
+ removeListener(event: ProviderEvent, listener: Listener): void;
26
+ emit(event: ProviderEvent): void;
27
+ get derivationPath(): string;
9
28
  loadTransport(): Promise<void>;
10
- getSigner(): LedgerHQSigner;
11
- listAccounts(): Promise<Array<string>>;
12
- detectNetwork(): Promise<Network>;
13
29
  getTransport(): Promise<TransportWebHID>;
14
- enable(): Promise<string>;
15
- getAddress(): Promise<string>;
16
- request({ method, params, }: {
17
- method: string;
18
- params: Array<unknown>;
19
- }): Promise<unknown>;
30
+ withEthApp<T>(callback: (eth: Eth) => T | Promise<T>): Promise<T>;
31
+ enable(): Promise<Address>;
32
+ resetAccount(): void;
33
+ getAccount(): Promise<LocalAccount>;
34
+ private readAddress;
35
+ getAddress(): Promise<Address>;
36
+ private getPublicClient;
37
+ private getWalletClient;
38
+ private sendTransaction;
39
+ request({ method, params }: RequestArguments): Promise<unknown>;
20
40
  }
41
+ export {};
21
42
  //# sourceMappingURL=provider.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"provider.d.ts","sourceRoot":"","sources":["../../src/hid/provider.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,oBAAoB,EAAE,OAAO,EAAE,MAAM,0BAA0B,CAAC;AACzE,OAAO,KAAK,eAAe,MAAM,+BAA+B,CAAC;AAEjE,OAAO,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AAI1C,qBAAa,gBAAiB,SAAQ,oBAAoB;IACjD,MAAM,CAAC,EAAE,cAAc,CAAC;IAExB,MAAM,CAAC,EAAE,SAAS,CAAC;IAEnB,SAAS,CAAC,EAAE,OAAO,eAAe,CAAC;gBAE9B,GAAG,IAAI,EAAE,GAAG,EAAE;IAKpB,aAAa;IASnB,SAAS,IAAI,cAAc;IAI3B,YAAY,IAAI,OAAO,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;IAItC,aAAa,IAAI,OAAO,CAAC,OAAO,CAAC;IAI3B,YAAY,IAAI,OAAO,CAAC,eAAe,CAAC;IAcxC,MAAM,IAAI,OAAO,CAAC,MAAM,CAAC;IAyBzB,UAAU,IAAI,OAAO,CAAC,MAAM,CAAC;IAK7B,OAAO,CAAC,EACZ,MAAM,EACN,MAAM,GACP,EAAE;QACD,MAAM,EAAE,MAAM,CAAC;QACf,MAAM,EAAE,KAAK,CAAC,OAAO,CAAC,CAAC;KACxB,GAAG,OAAO,CAAC,OAAO,CAAC;CAwCrB"}
1
+ {"version":3,"file":"provider.d.ts","sourceRoot":"","sources":["../../src/hid/provider.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,GAAG,MAAM,sBAAsB,CAAC;AAC5C,OAAO,KAAK,eAAe,MAAM,+BAA+B,CAAC;AACjE,OAAO,EAYL,KAAK,OAAO,EACZ,KAAK,KAAK,EAEV,KAAK,YAAY,EAIlB,MAAM,MAAM,CAAC;AA6Cd,KAAK,gBAAgB,GAAG;IACtB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,OAAO,EAAE,CAAC;CACpB,CAAC;AAEF,KAAK,aAAa,GAAG,YAAY,CAAC;AAClC,KAAK,QAAQ,GAAG,MAAM,IAAI,CAAC;AAE3B,qBAAa,gBAAgB;IAC3B,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAC;IAEf,MAAM,CAAC,EAAE,SAAS,CAAC;IAEnB,SAAS,CAAC,EAAE,OAAO,eAAe,CAAC;IAE1C,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAS;IAEjC,OAAO,CAAC,OAAO,CAAC,CAAe;IAE/B,OAAO,CAAC,WAAW,CAAC,CAAS;IAE7B,OAAO,CAAC,YAAY,CAAC,CAAe;IAEpC,OAAO,CAAC,YAAY,CAAC,CAAe;IAEpC,OAAO,CAAC,SAAS,CAAqD;gBAE1D,EAAE,KAAK,EAAE,MAAM,EAAE,EAAE;QAAE,KAAK,EAAE,KAAK,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAA;KAAE;IAehE,EAAE,CAAC,KAAK,EAAE,aAAa,EAAE,QAAQ,EAAE,QAAQ;IAI3C,cAAc,CAAC,KAAK,EAAE,aAAa,EAAE,QAAQ,EAAE,QAAQ;IAIvD,IAAI,CAAC,KAAK,EAAE,aAAa;IAMzB,IAAI,cAAc,WAQjB;IAEK,aAAa;IAQb,YAAY,IAAI,OAAO,CAAC,eAAe,CAAC;IAc9C,UAAU,CAAC,CAAC,EAAE,QAAQ,EAAE,CAAC,GAAG,EAAE,GAAG,KAAK,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC;IAoB3D,MAAM,IAAI,OAAO,CAAC,OAAO,CAAC;IAyBhC,YAAY;IAMN,UAAU,IAAI,OAAO,CAAC,YAAY,CAAC;YAqC3B,WAAW;IAoBnB,UAAU,IAAI,OAAO,CAAC,OAAO,CAAC;IAOpC,OAAO,CAAC,eAAe;YAQT,eAAe;YAUf,eAAe;IAuEvB,OAAO,CAAC,EAAE,MAAM,EAAE,MAAW,EAAE,EAAE,gBAAgB,GAAG,OAAO,CAAC,OAAO,CAAC;CA0D3E"}
@@ -1,14 +1,50 @@
1
- import { checkError, convertToUnsigned } from "./helpers.js";
2
- import { LedgerHQSigner } from "./signer.js";
3
- import { arrayify } from "@ethersproject/bytes";
4
- import { JsonRpcBatchProvider } from "@ethersproject/providers";
5
- import invariant from "tiny-invariant";
6
-
1
+ import "./constants.js";
2
+ import { checkError, clearLedgerAccount, isLockedDeviceError, restoreLedgerAccount, saveLedgerAccount } from "./helpers.js";
3
+ import { createLedgerAccount } from "./account.js";
4
+ import { MethodNotSupportedRpcError, createPublicClient, createWalletClient, getAddress, hexToBigInt, hexToNumber, http, isAddress, isAddressEqual, isHex, numberToHex } from "viem";
7
5
  //#region src/hid/provider.ts
8
- var LedgerHQProvider = class extends JsonRpcBatchProvider {
9
- constructor(...args) {
10
- super(...args);
11
- this.signer = this.getSigner();
6
+ const NOOP = () => {};
7
+ const DEFAULT_DERIVATION_PATH = "m/44'/60'/0'/0/0";
8
+ const TX_TYPES = {
9
+ "0x0": "legacy",
10
+ "0x1": "eip2930",
11
+ "0x2": "eip1559"
12
+ };
13
+ const UNSUPPORTED_WALLET_METHODS = /* @__PURE__ */ new Set([
14
+ "eth_signTransaction",
15
+ "eth_signTypedData",
16
+ "eth_signTypedData_v1",
17
+ "eth_signTypedData_v3",
18
+ "eth_decrypt",
19
+ "eth_getEncryptionPublicKey",
20
+ "personal_ecRecover"
21
+ ]);
22
+ let deviceSessionLock = Promise.resolve();
23
+ const withDeviceSession = (fn) => {
24
+ const result = deviceSessionLock.then(fn);
25
+ deviceSessionLock = result.then(NOOP, NOOP);
26
+ return result;
27
+ };
28
+ var LedgerHQProvider = class {
29
+ constructor({ chain, rpcUrl }) {
30
+ this.listeners = {};
31
+ this.chain = chain;
32
+ this.rpcUrl = rpcUrl;
33
+ if (!rpcUrl) console.warn(`[reef-knot] No RPC URL configured for chain ${chain.id} (${chain.name}), falling back to its default public RPC endpoint`);
34
+ }
35
+ on(event, listener) {
36
+ var _this$listeners;
37
+ ((_this$listeners = this.listeners)[event] ?? (_this$listeners[event] = /* @__PURE__ */ new Set())).add(listener);
38
+ }
39
+ removeListener(event, listener) {
40
+ this.listeners[event]?.delete(listener);
41
+ }
42
+ emit(event) {
43
+ this.listeners[event]?.forEach((listener) => listener());
44
+ }
45
+ get derivationPath() {
46
+ if (typeof window !== "undefined") return window.localStorage.getItem("reef-knot_ledger-derivation-path") || DEFAULT_DERIVATION_PATH;
47
+ return DEFAULT_DERIVATION_PATH;
12
48
  }
13
49
  async loadTransport() {
14
50
  if (!this.transport) {
@@ -16,15 +52,6 @@ var LedgerHQProvider = class extends JsonRpcBatchProvider {
16
52
  this.transport = TransportWebHID;
17
53
  }
18
54
  }
19
- getSigner() {
20
- return new LedgerHQSigner(this);
21
- }
22
- listAccounts() {
23
- throw new Error("method is not implemented");
24
- }
25
- detectNetwork() {
26
- return Promise.resolve(this._network);
27
- }
28
55
  async getTransport() {
29
56
  await this.loadTransport();
30
57
  try {
@@ -35,6 +62,21 @@ var LedgerHQProvider = class extends JsonRpcBatchProvider {
35
62
  return checkError(error);
36
63
  }
37
64
  }
65
+ withEthApp(callback) {
66
+ return withDeviceSession(async () => {
67
+ const transport = await this.getTransport();
68
+ try {
69
+ const { default: Eth } = await import("@ledgerhq/hw-app-eth");
70
+ const eth = new Eth(transport);
71
+ await eth.getAppConfiguration();
72
+ return await callback(eth);
73
+ } catch (error) {
74
+ return checkError(error);
75
+ } finally {
76
+ await transport.close();
77
+ }
78
+ });
79
+ }
38
80
  async enable() {
39
81
  try {
40
82
  await this.loadTransport();
@@ -46,52 +88,151 @@ var LedgerHQProvider = class extends JsonRpcBatchProvider {
46
88
  }
47
89
  };
48
90
  hid.addEventListener("disconnect", onDisconnect);
49
- if (!this.signer) this.signer = this.getSigner();
91
+ this.resetAccount();
50
92
  return await this.getAddress();
51
93
  } catch (error) {
52
94
  return checkError(error);
53
95
  }
54
96
  }
97
+ resetAccount() {
98
+ this.account = void 0;
99
+ this.accountPath = void 0;
100
+ this.walletClient = void 0;
101
+ }
102
+ async getAccount() {
103
+ const path = this.derivationPath;
104
+ if (!this.account || this.accountPath !== path) {
105
+ const { address, verifiedByDevice } = await this.readAddress(path);
106
+ let verified = verifiedByDevice;
107
+ const withVerifyingEthApp = (cb) => this.withEthApp(async (eth) => {
108
+ if (!verified) {
109
+ const actual = await eth.getAddress(path);
110
+ if (!isAddressEqual(getAddress(actual.address), address)) {
111
+ clearLedgerAccount();
112
+ this.resetAccount();
113
+ this.emit("disconnect");
114
+ throw new Error("The connected account does not match the device. Please reconnect.");
115
+ }
116
+ verified = true;
117
+ }
118
+ return cb(eth);
119
+ });
120
+ this.account = createLedgerAccount(address, path, withVerifyingEthApp);
121
+ this.accountPath = path;
122
+ this.walletClient = void 0;
123
+ }
124
+ return this.account;
125
+ }
126
+ async readAddress(path) {
127
+ try {
128
+ const { address } = await this.withEthApp((eth) => eth.getAddress(path));
129
+ const checksummed = getAddress(address);
130
+ saveLedgerAccount({
131
+ address: checksummed,
132
+ path
133
+ });
134
+ return {
135
+ address: checksummed,
136
+ verifiedByDevice: true
137
+ };
138
+ } catch (error) {
139
+ const persisted = restoreLedgerAccount();
140
+ if (isLockedDeviceError(error) && persisted?.path === path) return {
141
+ address: persisted.address,
142
+ verifiedByDevice: false
143
+ };
144
+ return checkError(error);
145
+ }
146
+ }
55
147
  async getAddress() {
56
- invariant(this.signer, "Signer is not defined");
57
- return await this.signer.getAddress();
148
+ const { address } = await this.getAccount();
149
+ return address;
150
+ }
151
+ getPublicClient() {
152
+ this.publicClient ?? (this.publicClient = createPublicClient({
153
+ chain: this.chain,
154
+ transport: http(this.rpcUrl, { batch: true })
155
+ }));
156
+ return this.publicClient;
157
+ }
158
+ async getWalletClient() {
159
+ const account = await this.getAccount();
160
+ this.walletClient ?? (this.walletClient = createWalletClient({
161
+ account,
162
+ chain: this.chain,
163
+ transport: http(this.rpcUrl, { batch: true })
164
+ }));
165
+ return this.walletClient;
58
166
  }
59
- async request({ method, params }) {
60
- invariant(this.signer, "Signer is not defined");
167
+ async sendTransaction(transaction) {
168
+ const walletClient = await this.getWalletClient();
169
+ const account = walletClient.account;
170
+ if (transaction.from && (!isAddress(transaction.from) || !isAddressEqual(transaction.from, account.address))) throw new Error("from address mismatch");
171
+ const type = transaction.type ? TX_TYPES[transaction.type] : void 0;
172
+ const request = {
173
+ account,
174
+ chain: this.chain,
175
+ to: transaction.to ?? void 0,
176
+ data: transaction.data,
177
+ value: transaction.value ? hexToBigInt(transaction.value) : void 0,
178
+ gas: transaction.gas ? hexToBigInt(transaction.gas) : void 0,
179
+ nonce: transaction.nonce != null ? hexToNumber(transaction.nonce) : void 0
180
+ };
181
+ const accessList = transaction.accessList;
182
+ const gasPrice = transaction.gasPrice ? hexToBigInt(transaction.gasPrice) : void 0;
183
+ if (type === "legacy") return walletClient.sendTransaction({
184
+ ...request,
185
+ type: "legacy",
186
+ gasPrice
187
+ });
188
+ if (type === "eip2930") return walletClient.sendTransaction({
189
+ ...request,
190
+ type: "eip2930",
191
+ gasPrice,
192
+ accessList
193
+ });
194
+ if (!type && gasPrice) return walletClient.sendTransaction({
195
+ ...request,
196
+ gasPrice,
197
+ accessList
198
+ });
199
+ return walletClient.sendTransaction({
200
+ ...request,
201
+ type,
202
+ accessList,
203
+ maxFeePerGas: transaction.maxFeePerGas ? hexToBigInt(transaction.maxFeePerGas) : void 0,
204
+ maxPriorityFeePerGas: transaction.maxPriorityFeePerGas ? hexToBigInt(transaction.maxPriorityFeePerGas) : void 0
205
+ });
206
+ }
207
+ async request({ method, params = [] }) {
61
208
  switch (method) {
62
- case "eth_sendTransaction": {
63
- const sourceTx = params[0];
64
- const unsignedTx = await convertToUnsigned(sourceTx);
65
- const signedTx = await this.signer.signTransaction(unsignedTx);
66
- return this.send("eth_sendRawTransaction", [signedTx]);
67
- }
68
- case "eth_accounts": return [await this.getAddress()];
69
- case "eth_signTypedData_v4": {
70
- if (typeof params[1] !== "string") throw new Error("eth_signTypedData_v4 arg 1 is not a string");
71
- const payload = JSON.parse(params[1]);
72
- return await this.signer.__signEIP712Message({
73
- domain: payload.domain,
74
- types: payload.types,
75
- primaryType: payload.primaryType,
76
- message: payload.message
77
- });
78
- }
209
+ case "eth_chainId": return numberToHex(this.chain.id);
210
+ case "eth_accounts":
211
+ case "eth_requestAccounts": return [await this.getAddress()];
212
+ case "eth_sendTransaction": return this.sendTransaction(params[0]);
79
213
  case "personal_sign": {
80
214
  const messageHex = params[0];
81
- if (typeof messageHex !== "string") throw new Error("personal_sign message must be a string");
82
- const messageBytes = arrayify(messageHex);
83
- return await this.signer.signMessage(messageBytes);
215
+ if (!isHex(messageHex)) throw new Error("personal_sign message must be a hex string");
216
+ return (await this.getAccount()).signMessage({ message: { raw: messageHex } });
84
217
  }
85
218
  case "eth_sign": {
86
219
  const messageHex = params[1];
87
- if (typeof messageHex !== "string") throw new Error("eth_sign message must be a string");
88
- const messageBytes = arrayify(messageHex);
89
- return await this.signer.signMessage(messageBytes);
220
+ if (!isHex(messageHex)) throw new Error("eth_sign message must be a hex string");
221
+ return (await this.getAccount()).signMessage({ message: { raw: messageHex } });
222
+ }
223
+ case "eth_signTypedData_v4":
224
+ if (typeof params[1] !== "string") throw new Error("eth_signTypedData_v4 arg 1 is not a string");
225
+ return (await this.getAccount()).signTypedData(JSON.parse(params[1]));
226
+ default: {
227
+ if (method.startsWith("wallet_") || UNSUPPORTED_WALLET_METHODS.has(method)) throw new MethodNotSupportedRpcError(/* @__PURE__ */ new Error(`The method ${method} is not supported`), { method });
228
+ const rpcRequest = this.getPublicClient().request;
229
+ return rpcRequest({
230
+ method,
231
+ params
232
+ });
90
233
  }
91
- default: return this.send(method, params);
92
234
  }
93
235
  }
94
236
  };
95
-
96
237
  //#endregion
97
- export { LedgerHQProvider };
238
+ export { LedgerHQProvider };
@@ -1 +1 @@
1
- {"version":3,"file":"connector.d.ts","sourceRoot":"","sources":["../../src/iframe/connector.ts"],"names":[],"mappings":"AAWA,OAAO,EAAE,KAAK,EAAE,MAAM,cAAc,CAAC;AAIrC,OAAO,KAAK,EAAE,6BAA6B,EAAE,MAAM,2BAA2B,CAAC;AAC/E,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAC;AAEvD,eAAO,MAAM,YAAY,eAAe,CAAC;AACzC,eAAO,MAAM,IAAI,gBAAgB,CAAC;AAElC,KAAK,uBAAuB,GAAG;IAC7B,OAAO,CAAC,EAAE,6BAA6B,CAAC;IACxC,YAAY,EAAE,KAAK,CAAC;CACrB,CAAC;AAGF,wBAAgB,mBAAmB,CAAC,EAClC,OAAO,EACP,YAAY,GACb,EAAE,uBAAuB,6GAmJzB;yBAtJe,mBAAmB"}
1
+ {"version":3,"file":"connector.d.ts","sourceRoot":"","sources":["../../src/iframe/connector.ts"],"names":[],"mappings":"AAaA,OAAO,EAAE,KAAK,EAAE,MAAM,cAAc,CAAC;AACrC,OAAO,KAAK,EAAE,6BAA6B,EAAE,MAAM,2BAA2B,CAAC;AAC/E,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAC;AAEvD,eAAO,MAAM,YAAY,eAAe,CAAC;AACzC,eAAO,MAAM,IAAI,gBAAgB,CAAC;AAElC,KAAK,uBAAuB,GAAG;IAC7B,OAAO,CAAC,EAAE,6BAA6B,CAAC;IACxC,YAAY,EAAE,KAAK,CAAC;CACrB,CAAC;AAGF,wBAAgB,mBAAmB,CAAC,EAClC,OAAO,EACP,YAAY,GACb,EAAE,uBAAuB,6GA0IzB;yBA7Ie,mBAAmB"}
@@ -1,9 +1,5 @@
1
1
  import { SwitchChainNotSupportedError, createConnector } from "wagmi";
2
- import { ResourceUnavailableRpcError, UserRejectedRequestError } from "viem";
3
- import { getAddress } from "@ethersproject/address";
4
- import { hexValue } from "@ethersproject/bytes";
5
- import { Web3Provider } from "@ethersproject/providers";
6
-
2
+ import { ResourceUnavailableRpcError, UserRejectedRequestError, getAddress, numberToHex } from "viem";
7
3
  //#region src/iframe/connector.ts
8
4
  const idLedgerLive = "ledgerLive";
9
5
  const name = "Ledger Live";
@@ -48,7 +44,7 @@ function ledgerLiveConnector({ options, defaultChain }) {
48
44
  },
49
45
  async switchChain({ chainId }) {
50
46
  const provider = await this.getProvider();
51
- const id = hexValue(chainId);
47
+ const id = numberToHex(chainId);
52
48
  try {
53
49
  await provider.send("wallet_switchEthereumChain", [{ chainId: id }]);
54
50
  return chains.find((x) => x.id === chainId) ?? {
@@ -72,16 +68,12 @@ function ledgerLiveConnector({ options, defaultChain }) {
72
68
  }
73
69
  },
74
70
  async getAccounts() {
75
- return (await (await this.getProvider()).send("eth_requestAccounts")).map(getAddress);
71
+ return (await (await this.getProvider()).send("eth_requestAccounts")).map((account) => getAddress(account));
76
72
  },
77
73
  async getChainId() {
78
74
  const chainId = await (await this.getProvider()).send("eth_chainId");
79
75
  return Number(chainId);
80
76
  },
81
- async getSigner() {
82
- const [provider, accounts] = await Promise.all([this.getProvider(), this.getAccounts()]);
83
- return new Web3Provider(provider).getSigner(accounts[0]);
84
- },
85
77
  async isAuthorized() {
86
78
  try {
87
79
  return !!(await (await this.getProvider()).send("eth_accounts"))[0];
@@ -108,6 +100,5 @@ function ledgerLiveConnector({ options, defaultChain }) {
108
100
  }
109
101
  }));
110
102
  }
111
-
112
103
  //#endregion
113
- export { idLedgerLive, ledgerLiveConnector, name };
104
+ export { idLedgerLive, ledgerLiveConnector, name };
@@ -9,6 +9,5 @@ const isLedgerLive = () => {
9
9
  return false;
10
10
  }
11
11
  };
12
-
13
12
  //#endregion
14
- export { isIframe, isLedgerLive };
13
+ export { isIframe, isLedgerLive };
@@ -1,2 +1,3 @@
1
1
  import { idLedgerLive, ledgerLiveConnector, name } from "./connector.js";
2
2
  import { isIframe, isLedgerLive } from "./helpers.js";
3
+ export { idLedgerLive, isIframe, isLedgerLive, ledgerLiveConnector, name };
@@ -1,11 +1,9 @@
1
1
  import { IFrameEthereumProvider } from "@ledgerhq/iframe-provider";
2
-
3
2
  //#region src/iframe/provider.ts
4
3
  var LedgerIFrameProvider = class extends IFrameEthereumProvider {
5
4
  request({ method, params }) {
6
5
  return this.send(method, params);
7
6
  }
8
7
  };
9
-
10
8
  //#endregion
11
- export { LedgerIFrameProvider };
9
+ export { LedgerIFrameProvider };
package/dist/index.js CHANGED
@@ -4,5 +4,4 @@ import "./iframe/index.js";
4
4
  import { LS_KEY_DERIVATION_PATH } from "./hid/constants.js";
5
5
  import { idLedgerHid, ledgerHIDConnector } from "./hid/connector.js";
6
6
  import "./hid/index.js";
7
-
8
- export { LS_KEY_DERIVATION_PATH, idLedgerHid, idLedgerLive, isIframe, isLedgerLive, ledgerHIDConnector, ledgerLiveConnector, name };
7
+ export { LS_KEY_DERIVATION_PATH, idLedgerHid, idLedgerLive, isIframe, isLedgerLive, ledgerHIDConnector, ledgerLiveConnector, name };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@reef-knot/ledger-connector",
3
- "version": "5.1.0",
3
+ "version": "5.2.0-alpha.2",
4
4
  "main": "dist/index.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "exports": {
@@ -38,29 +38,17 @@
38
38
  "typecheck": "tsc --noEmit"
39
39
  },
40
40
  "devDependencies": {
41
- "@types/w3c-web-hid": "^1.0.2",
41
+ "@types/w3c-web-hid": "^1.0.7",
42
42
  "build-config": "*",
43
43
  "eslint-config-custom": "*",
44
44
  "viem": ">=2.50",
45
45
  "wagmi": ">=3.6"
46
46
  },
47
47
  "dependencies": {
48
- "@ethersproject/abstract-signer": "^5.7.0",
49
- "@ethersproject/address": "^5.7.0",
50
- "@ethersproject/bignumber": "^5.7.0",
51
- "@ethersproject/bytes": "^5.7.0",
52
- "@ethersproject/hash": "^5.7.0",
53
- "@ethersproject/properties": "^5.7.0",
54
- "@ethersproject/providers": "^5.7.2",
55
- "@ethersproject/strings": "^5.7.0",
56
- "@ethersproject/transactions": "^5.7.0",
57
- "@ledgerhq/hw-app-eth": "^6.37.1",
58
- "@ledgerhq/hw-transport": "^6.31.0",
59
- "@ledgerhq/hw-transport-webhid": "^6.29.0",
60
- "@ledgerhq/iframe-provider": "^0.4.3",
61
- "@web3-react/abstract-connector": "^6.0.7",
62
- "@web3-react/types": "^6.0.7",
63
- "tiny-invariant": "^1.2.0"
48
+ "@ledgerhq/hw-app-eth": "7.8.12",
49
+ "@ledgerhq/hw-transport": "6.35.7",
50
+ "@ledgerhq/hw-transport-webhid": "6.36.0",
51
+ "@ledgerhq/iframe-provider": "^0.4.3"
64
52
  },
65
53
  "peerDependencies": {
66
54
  "@tanstack/react-query": "^5.29.0",
@@ -1,29 +0,0 @@
1
- import type Eth from '@ledgerhq/hw-app-eth';
2
- import { LoadConfig, ResolutionConfig } from '@ledgerhq/hw-app-eth/lib/services/types';
3
- import { EIP712Message } from '@ledgerhq/types-live';
4
- import { JsonRpcSigner, TransactionRequest } from '@ethersproject/providers';
5
- import { Signer, TypedDataDomain, TypedDataField, TypedDataSigner } from '@ethersproject/abstract-signer';
6
- import { UnsignedTransaction } from '@ethersproject/transactions';
7
- import { Bytes } from '@ethersproject/bytes';
8
- import type { LedgerHQProvider } from './provider';
9
- import type { UnsignedTransactionStrict } from './types';
10
- export declare class LedgerHQSigner extends Signer implements TypedDataSigner {
11
- readonly provider: LedgerHQProvider;
12
- get path(): string;
13
- _index: number;
14
- _address: string;
15
- constructor(provider: LedgerHQProvider);
16
- withEthApp<T>(callback: (eth: Eth) => T): Promise<T>;
17
- getAddress(): Promise<string>;
18
- signMessage(message: Bytes | string): Promise<string>;
19
- populateUnsigned(transaction: UnsignedTransactionStrict): Promise<UnsignedTransaction>;
20
- signTransaction(transaction: TransactionRequest, loadConfig?: LoadConfig, resolutionConfig?: ResolutionConfig): Promise<string>;
21
- connect(provider: LedgerHQProvider): JsonRpcSigner;
22
- connectUnchecked(): JsonRpcSigner;
23
- sendUncheckedTransaction(): Promise<string>;
24
- unlock(): Promise<boolean>;
25
- _legacySignMessage(_message: Bytes | string): Promise<string>;
26
- _signTypedData(domain: TypedDataDomain, _types: Record<string, Array<TypedDataField>>, value: Record<string, any>): Promise<string>;
27
- __signEIP712Message(data: EIP712Message): Promise<string>;
28
- }
29
- //# sourceMappingURL=signer.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"signer.d.ts","sourceRoot":"","sources":["../../src/hid/signer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,GAAG,MAAM,sBAAsB,CAAC;AAC5C,OAAO,EACL,UAAU,EACV,gBAAgB,EACjB,MAAM,yCAAyC,CAAC;AAEjD,OAAO,EAAE,aAAa,EAAsB,MAAM,sBAAsB,CAAC;AACzE,OAAO,EAAE,aAAa,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AAE7E,OAAO,EACL,MAAM,EACN,eAAe,EACf,cAAc,EACd,eAAe,EAChB,MAAM,gCAAgC,CAAC;AACxC,OAAO,EAAa,mBAAmB,EAAE,MAAM,6BAA6B,CAAC;AAE7E,OAAO,EAAE,KAAK,EAA0B,MAAM,sBAAsB,CAAC;AAMrE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AACnD,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,SAAS,CAAC;AAIzD,qBAAa,cAAe,SAAQ,MAAO,YAAW,eAAe;IACnE,QAAQ,CAAC,QAAQ,EAAE,gBAAgB,CAAC;IAEpC,IAAI,IAAI,WAKP;IAGD,MAAM,SAAK;IACX,QAAQ,SAAM;gBAEF,QAAQ,EAAE,gBAAgB;IAKhC,UAAU,CAAC,CAAC,EAAE,QAAQ,EAAE,CAAC,GAAG,EAAE,GAAG,KAAK,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC;IAiBpD,UAAU,IAAI,OAAO,CAAC,MAAM,CAAC;IAM7B,WAAW,CAAC,OAAO,EAAE,KAAK,GAAG,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAiBrD,gBAAgB,CACpB,WAAW,EAAE,yBAAyB,GACrC,OAAO,CAAC,mBAAmB,CAAC;IAczB,eAAe,CACnB,WAAW,EAAE,kBAAkB,EAC/B,UAAU,GAAE,UAAe,EAC3B,gBAAgB,GAAE,gBAAqB,GACtC,OAAO,CAAC,MAAM,CAAC;IAuBlB,OAAO,CAAC,QAAQ,EAAE,gBAAgB,GAAG,aAAa;IAKlD,gBAAgB,IAAI,aAAa;IAKjC,wBAAwB,IAAI,OAAO,CAAC,MAAM,CAAC;IAKrC,MAAM,IAAI,OAAO,CAAC,OAAO,CAAC;IAK1B,kBAAkB,CAAC,QAAQ,EAAE,KAAK,GAAG,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAK7D,cAAc,CAClB,MAAM,EAAE,eAAe,EACvB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,cAAc,CAAC,CAAC,EAC7C,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GACzB,OAAO,CAAC,MAAM,CAAC;IA8BZ,mBAAmB,CAAC,IAAI,EAAE,aAAa,GAAG,OAAO,CAAC,MAAM,CAAC;CAOhE"}
@@ -1,146 +0,0 @@
1
- import { LS_KEY_DERIVATION_PATH } from "./constants.js";
2
- import { checkError, convertToUnsigned, toNumber } from "./helpers.js";
3
- import { hexlify, joinSignature } from "@ethersproject/bytes";
4
- import { BigNumber } from "@ethersproject/bignumber";
5
- import { Signer } from "@ethersproject/abstract-signer";
6
- import { serialize } from "@ethersproject/transactions";
7
- import { toUtf8Bytes } from "@ethersproject/strings";
8
- import { _TypedDataEncoder } from "@ethersproject/hash";
9
-
10
- //#region src/hid/signer.ts
11
- const defaultPath = "m/44'/60'/0'/0/0";
12
- var LedgerHQSigner = class LedgerHQSigner extends Signer {
13
- get path() {
14
- if (typeof window !== "undefined") return window.localStorage.getItem(LS_KEY_DERIVATION_PATH) || defaultPath;
15
- return defaultPath;
16
- }
17
- constructor(provider) {
18
- super();
19
- this._index = 0;
20
- this._address = "";
21
- this.provider = provider;
22
- }
23
- async withEthApp(callback) {
24
- const transport = await this.provider.getTransport();
25
- try {
26
- const { default: Eth } = await import("@ledgerhq/hw-app-eth");
27
- const eth = new Eth(transport);
28
- await eth.getAppConfiguration();
29
- return await callback(eth);
30
- } catch (error) {
31
- return checkError(error);
32
- } finally {
33
- await transport.close();
34
- }
35
- }
36
- async getAddress() {
37
- const account = await this.withEthApp((eth) => eth.getAddress(this.path));
38
- this._address = this.provider.formatter.address(account.address);
39
- return this._address;
40
- }
41
- async signMessage(message) {
42
- if (typeof message === "string") message = toUtf8Bytes(message);
43
- const messageHex = hexlify(message).substring(2);
44
- const sig = await this.withEthApp((eth) => eth.signPersonalMessage(this.path, messageHex));
45
- sig.r = `0x${sig.r}`;
46
- sig.s = `0x${sig.s}`;
47
- return joinSignature(sig);
48
- }
49
- async populateUnsigned(transaction) {
50
- const populated = await this.populateTransaction(transaction);
51
- const nonce = toNumber(populated.nonce);
52
- if (populated.type === 0) {
53
- const { gasLimit, type, chainId, gasPrice, value, data, to } = populated;
54
- return {
55
- gasLimit,
56
- type,
57
- chainId,
58
- gasPrice,
59
- nonce,
60
- value,
61
- data,
62
- to
63
- };
64
- }
65
- return {
66
- ...populated,
67
- nonce
68
- };
69
- }
70
- async signTransaction(transaction, loadConfig = {}, resolutionConfig = {}) {
71
- const unsignedTx = await convertToUnsigned(transaction);
72
- const populatedTx = await this.populateUnsigned(unsignedTx);
73
- const serializedTx = serialize(populatedTx).substring(2);
74
- const { ledgerService } = await import("@ledgerhq/hw-app-eth");
75
- const resolution = await ledgerService.resolveTransaction(serializedTx, loadConfig, resolutionConfig);
76
- const sig = await this.withEthApp((eth) => eth.signTransaction(this.path, serializedTx, resolution));
77
- return serialize(populatedTx, {
78
- v: BigNumber.from(`0x${sig.v}`).toNumber(),
79
- r: `0x${sig.r}`,
80
- s: `0x${sig.s}`
81
- });
82
- }
83
- connect(provider) {
84
- return new LedgerHQSigner(provider);
85
- }
86
- connectUnchecked() {
87
- throw new Error("method is not implemented");
88
- }
89
- sendUncheckedTransaction() {
90
- throw new Error("method is not implemented");
91
- }
92
- async unlock() {
93
- throw new Error("method is not implemented");
94
- }
95
- async _legacySignMessage(_message) {
96
- throw new Error("method is not implemented");
97
- }
98
- async _signTypedData(domain, _types, value) {
99
- const types = { ..._types };
100
- delete types.EIP712Domain;
101
- const encoder = new _TypedDataEncoder(types);
102
- const data = {
103
- domain: {
104
- name: domain.name,
105
- verifyingContract: domain.verifyingContract,
106
- version: domain.version,
107
- chainId: domain.chainId ? parseInt(domain.chainId.toString()) : void 0
108
- },
109
- types: {
110
- ...types,
111
- EIP712Domain: [
112
- {
113
- name: "name",
114
- type: "string"
115
- },
116
- {
117
- name: "version",
118
- type: "string"
119
- },
120
- {
121
- name: "chainId",
122
- type: "uint256"
123
- },
124
- {
125
- name: "verifyingContract",
126
- type: "address"
127
- }
128
- ]
129
- },
130
- primaryType: encoder.primaryType,
131
- message: value
132
- };
133
- return this.__signEIP712Message(data);
134
- }
135
- async __signEIP712Message(data) {
136
- const { r, s, v } = await this.withEthApp((eth) => eth.signEIP712Message(this.path, data));
137
- return joinSignature({
138
- r: `0x${r}`,
139
- s: `0x${s}`,
140
- v
141
- });
142
- }
143
- };
144
-
145
- //#endregion
146
- export { LedgerHQSigner };
@@ -1,10 +0,0 @@
1
- import { BigNumberish } from '@ethersproject/bignumber';
2
- import { TransactionRequest } from '@ethersproject/providers';
3
- import { UnsignedTransaction } from '@ethersproject/transactions';
4
- export type UnsignedTransactionStrict = Omit<UnsignedTransaction, 'type'> & {
5
- type?: number;
6
- };
7
- export type TransactionRequestExtended = TransactionRequest & {
8
- gas?: BigNumberish;
9
- };
10
- //# sourceMappingURL=types.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/hid/types.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AACxD,OAAO,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AAC9D,OAAO,EAAE,mBAAmB,EAAE,MAAM,6BAA6B,CAAC;AAElE,MAAM,MAAM,yBAAyB,GAAG,IAAI,CAAC,mBAAmB,EAAE,MAAM,CAAC,GAAG;IAC1E,IAAI,CAAC,EAAE,MAAM,CAAC;CACf,CAAC;AAEF,MAAM,MAAM,0BAA0B,GAAG,kBAAkB,GAAG;IAC5D,GAAG,CAAC,EAAE,YAAY,CAAC;CACpB,CAAC"}