@faremeter/wallet-ledger 0.10.1 → 0.10.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.
- package/dist/src/evm.d.ts +4 -0
- package/dist/src/evm.d.ts.map +1 -0
- package/dist/src/evm.js +126 -0
- package/dist/src/index.d.ts +6 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +4 -0
- package/dist/src/interface.d.ts +10 -0
- package/dist/src/interface.d.ts.map +1 -0
- package/dist/src/interface.js +14 -0
- package/dist/src/logger.d.ts +2 -0
- package/dist/src/logger.d.ts.map +1 -0
- package/dist/src/logger.js +2 -0
- package/dist/src/solana.d.ts +3 -0
- package/dist/src/solana.d.ts.map +1 -0
- package/dist/src/solana.js +22 -0
- package/dist/src/transport.d.ts +4 -0
- package/dist/src/transport.d.ts.map +1 -0
- package/dist/src/transport.js +56 -0
- package/dist/src/types.d.ts +27 -0
- package/dist/src/types.d.ts.map +1 -0
- package/dist/src/types.js +1 -0
- package/dist/src/utils.d.ts +6 -0
- package/dist/src/utils.d.ts.map +1 -0
- package/dist/src/utils.js +64 -0
- package/dist/tsconfig.tsbuildinfo +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"evm.d.ts","sourceRoot":"","sources":["../../src/evm.ts"],"names":[],"mappings":"AAAA,OAAO,EAIL,KAAK,KAAK,EAMX,MAAM,MAAM,CAAC;AAId,OAAO,KAAK,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAE9D,wBAAsB,qBAAqB,CACzC,EAAE,EAAE,aAAa,EACjB,KAAK,EAAE,KAAK,EACZ,cAAc,EAAE,MAAM,GACrB,OAAO,CAAC,eAAe,CAAC,CA4J1B"}
|
package/dist/src/evm.js
ADDED
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { createWalletClient, http, hashDomain, hashStruct, } from "viem";
|
|
2
|
+
import Eth from "@ledgerhq/hw-app-eth/lib-es/Eth.js";
|
|
3
|
+
import { type } from "arktype";
|
|
4
|
+
import { createTransport } from "./transport.js";
|
|
5
|
+
export async function createLedgerEvmWallet(ui, chain, derivationPath) {
|
|
6
|
+
const transport = await createTransport();
|
|
7
|
+
const eth = new Eth(transport);
|
|
8
|
+
const { address } = await eth.getAddress(derivationPath);
|
|
9
|
+
const formattedAddress = (address.startsWith("0x") ? address : `0x${address}`).toLowerCase();
|
|
10
|
+
const ledgerAccount = {
|
|
11
|
+
address: formattedAddress,
|
|
12
|
+
publicKey: formattedAddress,
|
|
13
|
+
type: "local",
|
|
14
|
+
source: "custom",
|
|
15
|
+
signMessage: async ({ message }) => {
|
|
16
|
+
let messageToSign;
|
|
17
|
+
if (typeof message === "string") {
|
|
18
|
+
messageToSign = Buffer.from(message).toString("hex");
|
|
19
|
+
}
|
|
20
|
+
else if (message && typeof message === "object" && "raw" in message) {
|
|
21
|
+
const raw = message.raw;
|
|
22
|
+
messageToSign =
|
|
23
|
+
typeof raw === "string"
|
|
24
|
+
? raw.slice(2)
|
|
25
|
+
: Buffer.from(raw).toString("hex");
|
|
26
|
+
}
|
|
27
|
+
else {
|
|
28
|
+
messageToSign = Buffer.from(String(message)).toString("hex");
|
|
29
|
+
}
|
|
30
|
+
const result = await eth.signPersonalMessage(derivationPath, messageToSign);
|
|
31
|
+
const signature = `0x${result.r}${result.s}${result.v.toString(16).padStart(2, "0")}`;
|
|
32
|
+
return signature;
|
|
33
|
+
},
|
|
34
|
+
signTransaction: async (transaction) => {
|
|
35
|
+
const toHex = (v, defaultValue) => v ? `0x${v.toString(16)}` : defaultValue;
|
|
36
|
+
const tx = {
|
|
37
|
+
to: transaction.to,
|
|
38
|
+
value: toHex(transaction.value, "0x0"),
|
|
39
|
+
data: transaction.data ?? "0x", // 0x is valid encoding for empty data in EVM
|
|
40
|
+
nonce: toHex(transaction.nonce, "0x0"),
|
|
41
|
+
gasLimit: toHex(transaction.gas, "0x5208"),
|
|
42
|
+
gasPrice: toHex(transaction.gasPrice),
|
|
43
|
+
maxFeePerGas: toHex(transaction.maxFeePerGas),
|
|
44
|
+
maxPriorityFeePerGas: toHex(transaction.maxPriorityFeePerGas),
|
|
45
|
+
chainId: chain.id,
|
|
46
|
+
};
|
|
47
|
+
const result = await eth.signTransaction(derivationPath, JSON.stringify(tx));
|
|
48
|
+
const signature = `0x${result.r}${result.s}${result.v}`;
|
|
49
|
+
return signature;
|
|
50
|
+
},
|
|
51
|
+
signTypedData: async (parameters) => {
|
|
52
|
+
const typedDataParams = type({
|
|
53
|
+
"domain?": {
|
|
54
|
+
"name?": "string",
|
|
55
|
+
"version?": "string",
|
|
56
|
+
"chainId?": "number",
|
|
57
|
+
"verifyingContract?": "string",
|
|
58
|
+
"salt?": "string",
|
|
59
|
+
},
|
|
60
|
+
types: "object",
|
|
61
|
+
primaryType: "string",
|
|
62
|
+
message: "object",
|
|
63
|
+
});
|
|
64
|
+
const validated = typedDataParams.assert(parameters);
|
|
65
|
+
const { domain, types, primaryType, message } = validated;
|
|
66
|
+
try {
|
|
67
|
+
// Use EIP-712 hashed message approach. This calculates the domain
|
|
68
|
+
// separator and message hash separately and sends them to the Ledger
|
|
69
|
+
// for signing.
|
|
70
|
+
// Build types with EIP712Domain
|
|
71
|
+
const typesWithDomain = {
|
|
72
|
+
EIP712Domain: [
|
|
73
|
+
...(domain?.name ? [{ name: "name", type: "string" }] : []),
|
|
74
|
+
...(domain?.version ? [{ name: "version", type: "string" }] : []),
|
|
75
|
+
...(domain?.chainId ? [{ name: "chainId", type: "uint256" }] : []),
|
|
76
|
+
...(domain?.verifyingContract
|
|
77
|
+
? [{ name: "verifyingContract", type: "address" }]
|
|
78
|
+
: []),
|
|
79
|
+
],
|
|
80
|
+
...types,
|
|
81
|
+
};
|
|
82
|
+
// Calculate hashes using viem
|
|
83
|
+
const domainSeparator = hashDomain({
|
|
84
|
+
domain: domain ?? {},
|
|
85
|
+
types: typesWithDomain,
|
|
86
|
+
});
|
|
87
|
+
// types without domain for message hash
|
|
88
|
+
const messageHash = hashStruct({
|
|
89
|
+
data: message,
|
|
90
|
+
primaryType,
|
|
91
|
+
types,
|
|
92
|
+
});
|
|
93
|
+
ui.message("\nEIP-712 hashes calculated:");
|
|
94
|
+
ui.message(` Domain separator: ${domainSeparator}`);
|
|
95
|
+
ui.message(` Message hash: ${messageHash}`);
|
|
96
|
+
ui.message(`\nPlease approve the transaction on your Ledger device...`);
|
|
97
|
+
const result = await eth.signEIP712HashedMessage(derivationPath, domainSeparator.slice(2), messageHash.slice(2));
|
|
98
|
+
const signature = `0x${result.r}${result.s}${result.v.toString(16).padStart(2, "0")}`;
|
|
99
|
+
return signature;
|
|
100
|
+
}
|
|
101
|
+
catch (error) {
|
|
102
|
+
ui.message(`EIP-712 signing failed: ${error}`);
|
|
103
|
+
throw error;
|
|
104
|
+
}
|
|
105
|
+
},
|
|
106
|
+
};
|
|
107
|
+
const client = createWalletClient({
|
|
108
|
+
account: ledgerAccount,
|
|
109
|
+
chain: chain,
|
|
110
|
+
transport: http(chain.rpcUrls.default.http[0]),
|
|
111
|
+
});
|
|
112
|
+
return {
|
|
113
|
+
chain,
|
|
114
|
+
address: formattedAddress,
|
|
115
|
+
client,
|
|
116
|
+
signTransaction: async (tx) => {
|
|
117
|
+
return await ledgerAccount.signTransaction(tx);
|
|
118
|
+
},
|
|
119
|
+
signTypedData: async (params) => {
|
|
120
|
+
return await ledgerAccount.signTypedData(params);
|
|
121
|
+
},
|
|
122
|
+
disconnect: async () => {
|
|
123
|
+
await transport.close();
|
|
124
|
+
},
|
|
125
|
+
};
|
|
126
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export { createLedgerEvmWallet } from "./evm.js";
|
|
2
|
+
export { createLedgerSolanaWallet } from "./solana.js";
|
|
3
|
+
export { selectLedgerAccount } from "./utils.js";
|
|
4
|
+
export type { LedgerEvmWallet, LedgerSolanaWallet } from "./types.js";
|
|
5
|
+
export * from "./interface.js";
|
|
6
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,qBAAqB,EAAE,MAAM,OAAO,CAAC;AAC9C,OAAO,EAAE,wBAAwB,EAAE,MAAM,UAAU,CAAC;AACpD,OAAO,EAAE,mBAAmB,EAAE,MAAM,SAAS,CAAC;AAC9C,YAAY,EAAE,eAAe,EAAE,kBAAkB,EAAE,MAAM,SAAS,CAAC;AACnE,cAAc,aAAa,CAAC"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export type createReadlineInterfaceArgs = {
|
|
2
|
+
stdin: NodeJS.ReadableStream;
|
|
3
|
+
stdout: NodeJS.WritableStream;
|
|
4
|
+
};
|
|
5
|
+
export declare function createReadlineInterface(args: createReadlineInterfaceArgs): Promise<{
|
|
6
|
+
message: (msg: string) => undefined;
|
|
7
|
+
question: (q: string) => Promise<string>;
|
|
8
|
+
close: () => Promise<void>;
|
|
9
|
+
}>;
|
|
10
|
+
//# sourceMappingURL=interface.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"interface.d.ts","sourceRoot":"","sources":["../../src/interface.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,2BAA2B,GAAG;IACxC,KAAK,EAAE,MAAM,CAAC,cAAc,CAAC;IAC7B,MAAM,EAAE,MAAM,CAAC,cAAc,CAAC;CAC/B,CAAC;AAEF,wBAAsB,uBAAuB,CAC3C,IAAI,EAAE,2BAA2B;mBAShB,MAAM;kBACD,MAAM;;GAM7B"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export async function createReadlineInterface(args) {
|
|
2
|
+
const readline = await import("readline");
|
|
3
|
+
const rl = readline.createInterface({
|
|
4
|
+
input: args.stdin,
|
|
5
|
+
output: args.stdout,
|
|
6
|
+
});
|
|
7
|
+
return {
|
|
8
|
+
message: (msg) => void args.stdout.write(msg + "\n"),
|
|
9
|
+
question: async (q) => new Promise((resolve) => {
|
|
10
|
+
rl.question(q, resolve);
|
|
11
|
+
}),
|
|
12
|
+
close: async () => rl.close(),
|
|
13
|
+
};
|
|
14
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"logger.d.ts","sourceRoot":"","sources":["../../src/logger.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,MAAM,mCAA4C,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"solana.d.ts","sourceRoot":"","sources":["../../src/solana.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,SAAS,CAAC;AAElD,wBAAsB,wBAAwB,CAC5C,OAAO,EAAE,MAAM,EACf,cAAc,EAAE,MAAM,GACrB,OAAO,CAAC,kBAAkB,CAAC,CA0B7B"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { PublicKey, VersionedTransaction } from "@solana/web3.js";
|
|
2
|
+
import Solana from "@ledgerhq/hw-app-solana/lib-es/Solana.js";
|
|
3
|
+
import { createTransport } from "./transport.js";
|
|
4
|
+
export async function createLedgerSolanaWallet(network, derivationPath) {
|
|
5
|
+
const transport = await createTransport();
|
|
6
|
+
const solana = new Solana(transport);
|
|
7
|
+
const { address } = await solana.getAddress(derivationPath);
|
|
8
|
+
const publicKey = new PublicKey(address);
|
|
9
|
+
return {
|
|
10
|
+
network,
|
|
11
|
+
publicKey,
|
|
12
|
+
updateTransaction: async (tx) => {
|
|
13
|
+
const message = tx.message.serialize();
|
|
14
|
+
const signature = await solana.signTransaction(derivationPath, Buffer.from(message));
|
|
15
|
+
tx.addSignature(publicKey, signature.signature);
|
|
16
|
+
return tx;
|
|
17
|
+
},
|
|
18
|
+
disconnect: async () => {
|
|
19
|
+
await transport.close();
|
|
20
|
+
},
|
|
21
|
+
};
|
|
22
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"transport.d.ts","sourceRoot":"","sources":["../../src/transport.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,SAAS,MAAM,wBAAwB,CAAC;AAcpD,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,CAoB1D;AAED,wBAAsB,eAAe,CAAC,UAAU,SAAI,GAAG,OAAO,CAAC,SAAS,CAAC,CAyCxE"}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { logger } from "./logger.js";
|
|
2
|
+
const LEDGER_ERRORS = {
|
|
3
|
+
"0x5515": "Ledger is locked. Please unlock your device.",
|
|
4
|
+
"0x6511": "Please unlock your Ledger and open the correct app.",
|
|
5
|
+
"0x6d00": "Wrong app open. Please open the correct app.",
|
|
6
|
+
"0x6d02": "No app open. Please open the correct app on your Ledger.",
|
|
7
|
+
"0x6e00": "Wrong app open. Please open the correct app on your Ledger.",
|
|
8
|
+
"0x6985": "Transaction rejected on Ledger device.",
|
|
9
|
+
"0x6a80": "Incorrect data. Please make sure the correct app is open on your Ledger.",
|
|
10
|
+
"0x6a83": "Wrong app open. Please open the correct app on your Ledger.",
|
|
11
|
+
};
|
|
12
|
+
export function translateLedgerError(error) {
|
|
13
|
+
const message = String(error instanceof Error ? error.message : error);
|
|
14
|
+
const hexMatch = /0x[0-9a-fA-F]{4}/.exec(message);
|
|
15
|
+
if (hexMatch && LEDGER_ERRORS[hexMatch[0]]) {
|
|
16
|
+
return new Error(LEDGER_ERRORS[hexMatch[0]]);
|
|
17
|
+
}
|
|
18
|
+
// Check for common connection errors
|
|
19
|
+
if (message.includes("NoDevice")) {
|
|
20
|
+
return new Error("No Ledger device found. Please connect your Ledger and unlock it.");
|
|
21
|
+
}
|
|
22
|
+
if (message.includes("Device busy")) {
|
|
23
|
+
return new Error("Ledger is in use by another app. Close Ledger Live.");
|
|
24
|
+
}
|
|
25
|
+
return error instanceof Error ? error : new Error(message);
|
|
26
|
+
}
|
|
27
|
+
export async function createTransport(maxRetries = 3) {
|
|
28
|
+
let lastError;
|
|
29
|
+
for (let i = 0; i < maxRetries; i++) {
|
|
30
|
+
try {
|
|
31
|
+
const isBrowser = typeof globalThis !== "undefined" && "window" in globalThis;
|
|
32
|
+
if (isBrowser) {
|
|
33
|
+
const { default: TransportWebUSB } = await import("@ledgerhq/hw-transport-webusb");
|
|
34
|
+
return await TransportWebUSB.create();
|
|
35
|
+
}
|
|
36
|
+
else {
|
|
37
|
+
const mod = await import("@ledgerhq/hw-transport-node-hid");
|
|
38
|
+
const TransportNodeHid = mod.default || mod;
|
|
39
|
+
return await TransportNodeHid.open("");
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
catch (error) {
|
|
43
|
+
const translatedError = translateLedgerError(error);
|
|
44
|
+
lastError = translatedError;
|
|
45
|
+
// Retry on generic USB errors
|
|
46
|
+
const errorMessage = translatedError.message;
|
|
47
|
+
if (i < maxRetries - 1 &&
|
|
48
|
+
(errorMessage.includes("USB") || errorMessage.includes("device"))) {
|
|
49
|
+
logger.warning(`USB connection attempt ${i + 1} failed, retrying...`);
|
|
50
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
throw lastError ?? new Error("Failed to connect to Ledger device");
|
|
56
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { Hex, WalletClient, TransactionSerializable, TypedDataDefinition, Chain } from "viem";
|
|
2
|
+
import type { PublicKey, VersionedTransaction } from "@solana/web3.js";
|
|
3
|
+
import type Transport from "@ledgerhq/hw-transport";
|
|
4
|
+
export interface LedgerEvmWallet {
|
|
5
|
+
chain: Chain;
|
|
6
|
+
address: Hex;
|
|
7
|
+
client: WalletClient;
|
|
8
|
+
signTransaction: (tx: TransactionSerializable) => Promise<Hex>;
|
|
9
|
+
signTypedData: (params: TypedDataDefinition) => Promise<Hex>;
|
|
10
|
+
disconnect: () => Promise<void>;
|
|
11
|
+
}
|
|
12
|
+
export interface LedgerSolanaWallet {
|
|
13
|
+
network: string;
|
|
14
|
+
publicKey: PublicKey;
|
|
15
|
+
updateTransaction: (tx: VersionedTransaction) => Promise<VersionedTransaction>;
|
|
16
|
+
disconnect: () => Promise<void>;
|
|
17
|
+
}
|
|
18
|
+
export interface LedgerTransportWrapper {
|
|
19
|
+
transport: Transport;
|
|
20
|
+
close: () => Promise<void>;
|
|
21
|
+
}
|
|
22
|
+
export interface UserInterface {
|
|
23
|
+
message: (msg: string) => void;
|
|
24
|
+
question: (prompt: string) => Promise<string>;
|
|
25
|
+
close: () => Promise<void>;
|
|
26
|
+
}
|
|
27
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,GAAG,EACH,YAAY,EACZ,uBAAuB,EACvB,mBAAmB,EACnB,KAAK,EACN,MAAM,MAAM,CAAC;AACd,OAAO,KAAK,EAAE,SAAS,EAAE,oBAAoB,EAAE,MAAM,iBAAiB,CAAC;AACvE,OAAO,KAAK,SAAS,MAAM,wBAAwB,CAAC;AAEpD,MAAM,WAAW,eAAe;IAC9B,KAAK,EAAE,KAAK,CAAC;IACb,OAAO,EAAE,GAAG,CAAC;IACb,MAAM,EAAE,YAAY,CAAC;IACrB,eAAe,EAAE,CAAC,EAAE,EAAE,uBAAuB,KAAK,OAAO,CAAC,GAAG,CAAC,CAAC;IAC/D,aAAa,EAAE,CAAC,MAAM,EAAE,mBAAmB,KAAK,OAAO,CAAC,GAAG,CAAC,CAAC;IAC7D,UAAU,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CACjC;AAED,MAAM,WAAW,kBAAkB;IACjC,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,SAAS,CAAC;IACrB,iBAAiB,EAAE,CACjB,EAAE,EAAE,oBAAoB,KACrB,OAAO,CAAC,oBAAoB,CAAC,CAAC;IACnC,UAAU,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CACjC;AAED,MAAM,WAAW,sBAAsB;IACrC,SAAS,EAAE,SAAS,CAAC;IACrB,KAAK,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAC5B;AAED,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;IAC/B,QAAQ,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IAC9C,KAAK,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAC5B"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../src/utils.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAU7C,wBAAsB,mBAAmB,CACvC,EAAE,EAAE,aAAa,EACjB,IAAI,EAAE,KAAK,GAAG,QAAQ,EACtB,WAAW,SAAI,GACd,OAAO,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAAC,CAyDnD"}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import Eth from "@ledgerhq/hw-app-eth/lib-es/Eth.js";
|
|
2
|
+
import Solana from "@ledgerhq/hw-app-solana/lib-es/Solana.js";
|
|
3
|
+
import { PublicKey } from "@solana/web3.js";
|
|
4
|
+
import { createTransport, translateLedgerError } from "./transport.js";
|
|
5
|
+
function evmDerivationPath(index) {
|
|
6
|
+
return `m/44'/60'/${index}'/0/0`;
|
|
7
|
+
}
|
|
8
|
+
function solanaDerivationPath(index) {
|
|
9
|
+
return `44'/501'/${index}'`;
|
|
10
|
+
}
|
|
11
|
+
export async function selectLedgerAccount(ui, type, numAccounts = 5) {
|
|
12
|
+
const isEvm = type === "evm";
|
|
13
|
+
ui.message(`\nScanning first ${numAccounts} ${isEvm ? "Ethereum" : "Solana"} accounts...`);
|
|
14
|
+
const accounts = [];
|
|
15
|
+
const transport = await createTransport();
|
|
16
|
+
try {
|
|
17
|
+
if (isEvm) {
|
|
18
|
+
const eth = new Eth(transport);
|
|
19
|
+
for (let i = 0; i < numAccounts; i++) {
|
|
20
|
+
const path = evmDerivationPath(i);
|
|
21
|
+
let result;
|
|
22
|
+
try {
|
|
23
|
+
result = await eth.getAddress(path, false);
|
|
24
|
+
}
|
|
25
|
+
catch (error) {
|
|
26
|
+
throw translateLedgerError(error);
|
|
27
|
+
}
|
|
28
|
+
const address = result.address;
|
|
29
|
+
const normalizedAddress = address.startsWith("0x")
|
|
30
|
+
? address
|
|
31
|
+
: `0x${address}`;
|
|
32
|
+
accounts.push({ path, address: normalizedAddress });
|
|
33
|
+
ui.message(`${i + 1}. ${normalizedAddress}`);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
else {
|
|
37
|
+
const solana = new Solana(transport);
|
|
38
|
+
for (let i = 0; i < numAccounts; i++) {
|
|
39
|
+
const path = solanaDerivationPath(i);
|
|
40
|
+
let result;
|
|
41
|
+
try {
|
|
42
|
+
result = await solana.getAddress(path, false);
|
|
43
|
+
}
|
|
44
|
+
catch (error) {
|
|
45
|
+
throw translateLedgerError(error);
|
|
46
|
+
}
|
|
47
|
+
const publicKey = new PublicKey(result.address);
|
|
48
|
+
const address = publicKey.toBase58();
|
|
49
|
+
accounts.push({ path, address });
|
|
50
|
+
ui.message(`${i + 1}. ${address}`);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
finally {
|
|
55
|
+
await transport.close();
|
|
56
|
+
}
|
|
57
|
+
const selection = await ui.question(`\nSelect account (1-${numAccounts}): `);
|
|
58
|
+
const index = parseInt(selection) - 1;
|
|
59
|
+
if (index < 0 || index >= accounts.length) {
|
|
60
|
+
ui.message("Invalid selection");
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
return accounts[index] ?? null;
|
|
64
|
+
}
|