@dfinity/hardware-wallet-cli 0.1.1 → 0.2.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.
@@ -1,2 +1,2 @@
1
1
  # The codebase is owned by the Governance & Identity Experience team at DFINITY
2
- * @dfinity/gix
2
+ # For questions, reach out to: <gix@dfinity.org>
package/README.md CHANGED
@@ -16,3 +16,16 @@ Live desktop app running on your computer. If you are facing connection issues
16
16
  when doing so, Ledger provides platform-specific
17
17
  [troubleshooting instructions](https://support.ledger.com/hc/en-us/articles/115005165269-Fix-USB-connection-issues-with-Ledger-Live?support=true)
18
18
  on their support site.
19
+
20
+ ## Development
21
+
22
+ Clone the repository.
23
+
24
+ Install dependencies with `npm install`.
25
+
26
+ To execute a command, you can use `npm run execute -- <args>`.
27
+
28
+ For example
29
+
30
+ * The command `ic-hardware-wallet --network https://nnsdapp.dfinity.network icp balance`.
31
+ * Would be `npm run execute -- --network https://nnsdapp.dfinity.network icp balance` for development.
package/build/index.js CHANGED
@@ -10,6 +10,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
10
10
  const commander_1 = require("commander");
11
11
  const identity_1 = require("./src/ledger/identity");
12
12
  const nns_1 = require("@dfinity/nns");
13
+ const principal_1 = require("@dfinity/principal");
13
14
  const agent_1 = require("@dfinity/agent");
14
15
  const chalk_1 = __importDefault(require("chalk"));
15
16
  // Add polyfill for `window` for `TransportWebHID` checks to work.
@@ -17,7 +18,6 @@ require("node-window-polyfill/register");
17
18
  // Add polyfill for `window.fetch` for agent-js to work.
18
19
  // @ts-ignore (no types are available)
19
20
  const node_fetch_1 = __importDefault(require("node-fetch"));
20
- const principal_1 = require("@dfinity/principal");
21
21
  global.fetch = node_fetch_1.default;
22
22
  window.fetch = node_fetch_1.default;
23
23
  const program = new commander_1.Command();
@@ -39,16 +39,24 @@ async function getAgent(identity) {
39
39
  }
40
40
  return agent;
41
41
  }
42
+ async function getLedgerIdentity() {
43
+ const principalPath = tryParseInt(program.opts().principal);
44
+ if (principalPath < 0 || principalPath > 255) {
45
+ throw new commander_1.InvalidArgumentError("Principal path must be between 0 and 255 inclusive.");
46
+ }
47
+ return identity_1.LedgerIdentity.create(`m/44'/223'/0'/0/${principalPath}`);
48
+ }
42
49
  /**
43
50
  * Fetches the balance of the main account on the wallet.
44
51
  */
45
52
  async function getBalance() {
46
- const identity = await identity_1.LedgerIdentity.create();
53
+ const identity = await getLedgerIdentity();
47
54
  const accountIdentifier = nns_1.AccountIdentifier.fromPrincipal({
48
55
  principal: identity.getPrincipal(),
49
56
  });
50
57
  const ledger = nns_1.LedgerCanister.create({
51
58
  agent: await getAgent(new agent_1.AnonymousIdentity()),
59
+ hardwareWallet: true,
52
60
  });
53
61
  const balance = await ledger.accountBalance({
54
62
  accountIdentifier: accountIdentifier,
@@ -62,9 +70,10 @@ async function getBalance() {
62
70
  * @param amount Amount to send in e8s.
63
71
  */
64
72
  async function sendICP(to, amount) {
65
- const identity = await identity_1.LedgerIdentity.create();
73
+ const identity = await getLedgerIdentity();
66
74
  const ledger = nns_1.LedgerCanister.create({
67
75
  agent: await getAgent(identity),
76
+ hardwareWallet: true,
68
77
  });
69
78
  const blockHeight = await ledger.transfer({
70
79
  to: to,
@@ -77,12 +86,14 @@ async function sendICP(to, amount) {
77
86
  * Shows the principal and account idenifier on the terminal and on the wallet's screen.
78
87
  */
79
88
  async function showInfo(showOnDevice) {
80
- const identity = await identity_1.LedgerIdentity.create();
89
+ const identity = await getLedgerIdentity();
81
90
  const accountIdentifier = nns_1.AccountIdentifier.fromPrincipal({
82
91
  principal: identity.getPrincipal(),
83
92
  });
93
+ const publicKey = identity.getPublicKey();
84
94
  log(chalk_1.default.bold(`Principal: `) + identity.getPrincipal());
85
95
  log(chalk_1.default.bold(`Address (${identity.derivePath}): `) + accountIdentifier.toHex());
96
+ log(chalk_1.default.bold('Public key: ') + publicKey.toHex());
86
97
  if (showOnDevice) {
87
98
  log("Displaying the principal and the address on the device...");
88
99
  await identity.showAddressAndPubKeyOnDevice();
@@ -94,7 +105,7 @@ async function showInfo(showOnDevice) {
94
105
  * @param amount Amount to stake in e8s.
95
106
  */
96
107
  async function stakeNeuron(stake) {
97
- const identity = await identity_1.LedgerIdentity.create();
108
+ const identity = await getLedgerIdentity();
98
109
  const ledger = nns_1.LedgerCanister.create({
99
110
  agent: await getAgent(identity),
100
111
  });
@@ -126,7 +137,7 @@ async function stakeNeuron(stake) {
126
137
  }
127
138
  }
128
139
  async function increaseDissolveDelay(neuronId, years, days, minutes, seconds) {
129
- const identity = await identity_1.LedgerIdentity.create();
140
+ const identity = await getLedgerIdentity();
130
141
  const governance = nns_1.GovernanceCanister.create({
131
142
  agent: await getAgent(identity),
132
143
  hardwareWallet: true,
@@ -142,7 +153,7 @@ async function increaseDissolveDelay(neuronId, years, days, minutes, seconds) {
142
153
  ok();
143
154
  }
144
155
  async function disburseNeuron(neuronId, to, amount) {
145
- const identity = await identity_1.LedgerIdentity.create();
156
+ const identity = await getLedgerIdentity();
146
157
  const governance = nns_1.GovernanceCanister.create({
147
158
  agent: await getAgent(identity),
148
159
  hardwareWallet: true,
@@ -155,7 +166,7 @@ async function disburseNeuron(neuronId, to, amount) {
155
166
  ok();
156
167
  }
157
168
  async function spawnNeuron(neuronId, controller) {
158
- const identity = await identity_1.LedgerIdentity.create();
169
+ const identity = await getLedgerIdentity();
159
170
  const governance = nns_1.GovernanceCanister.create({
160
171
  agent: await getAgent(identity),
161
172
  hardwareWallet: true,
@@ -167,7 +178,7 @@ async function spawnNeuron(neuronId, controller) {
167
178
  ok(`Spawned neuron with ID ${spawnedNeuronId}`);
168
179
  }
169
180
  async function startDissolving(neuronId) {
170
- const identity = await identity_1.LedgerIdentity.create();
181
+ const identity = await getLedgerIdentity();
171
182
  const governance = nns_1.GovernanceCanister.create({
172
183
  agent: await getAgent(identity),
173
184
  hardwareWallet: true,
@@ -176,7 +187,7 @@ async function startDissolving(neuronId) {
176
187
  ok();
177
188
  }
178
189
  async function stopDissolving(neuronId) {
179
- const identity = await identity_1.LedgerIdentity.create();
190
+ const identity = await getLedgerIdentity();
180
191
  const governance = nns_1.GovernanceCanister.create({
181
192
  agent: await getAgent(identity),
182
193
  hardwareWallet: true,
@@ -185,7 +196,7 @@ async function stopDissolving(neuronId) {
185
196
  ok();
186
197
  }
187
198
  async function addHotkey(neuronId, principal) {
188
- const identity = await identity_1.LedgerIdentity.create();
199
+ const identity = await getLedgerIdentity();
189
200
  const governance = nns_1.GovernanceCanister.create({
190
201
  agent: await getAgent(identity),
191
202
  hardwareWallet: true,
@@ -197,7 +208,7 @@ async function addHotkey(neuronId, principal) {
197
208
  ok();
198
209
  }
199
210
  async function removeHotkey(neuronId, principal) {
200
- const identity = await identity_1.LedgerIdentity.create();
211
+ const identity = await getLedgerIdentity();
201
212
  const governance = nns_1.GovernanceCanister.create({
202
213
  agent: await getAgent(identity),
203
214
  hardwareWallet: true,
@@ -209,7 +220,7 @@ async function removeHotkey(neuronId, principal) {
209
220
  ok();
210
221
  }
211
222
  async function listNeurons() {
212
- const identity = await identity_1.LedgerIdentity.create();
223
+ const identity = await getLedgerIdentity();
213
224
  const governance = nns_1.GovernanceCanister.create({
214
225
  agent: await getAgent(identity),
215
226
  hardwareWallet: true,
@@ -230,20 +241,15 @@ async function listNeurons() {
230
241
  /**
231
242
  * Fetches the balance of the main account on the wallet.
232
243
  */
233
- async function claimNeurons(hexPubKey) {
234
- const isHex = hexPubKey.match("^[0-9a-fA-F]+$");
235
- if (!isHex) {
236
- throw new Error(`${hexPubKey} is not a hex string.`);
237
- }
238
- if (hexPubKey.length < 130 || hexPubKey.length > 150) {
239
- throw new Error(`The key must be >= 130 characters and <= 150 characters.`);
240
- }
241
- const identity = await identity_1.LedgerIdentity.create();
244
+ async function claimNeurons() {
245
+ const identity = await getLedgerIdentity();
246
+ const publicKey = identity.getPublicKey();
247
+ const hexPubKey = publicKey.toHex();
242
248
  const governance = await nns_1.GenesisTokenCanister.create({
243
249
  agent: await getAgent(identity),
244
250
  });
245
251
  const claimedNeuronIds = await governance.claimNeurons({
246
- hexPubKey: hexPubKey,
252
+ hexPubKey,
247
253
  });
248
254
  ok(`Successfully claimed the following neurons: ${claimedNeuronIds}`);
249
255
  }
@@ -360,8 +366,8 @@ async function main() {
360
366
  .requiredOption("--principal <principal>", "Principal", tryParsePrincipal)
361
367
  .action((args) => run(() => removeHotkey(args.neuronId, args.principal))))
362
368
  .addCommand(new commander_1.Command("claim")
363
- .requiredOption("--hex-public-key <public-key>", "Claim the caller's GTC neurons.")
364
- .action((args) => run(() => claimNeurons(args.hexPublicKey))));
369
+ .description("Claim the caller's GTC neurons.")
370
+ .action((args) => run(() => claimNeurons())));
365
371
  const icp = new commander_1.Command("icp")
366
372
  .description("Commands for managing ICP.")
367
373
  .showSuggestionAfterError()
@@ -381,6 +387,7 @@ async function main() {
381
387
  .addOption(new commander_1.Option("--network <network>", "The IC network to talk to.")
382
388
  .default("https://ic0.app")
383
389
  .env("IC_NETWORK"))
390
+ .addOption(new commander_1.Option("--principal <principal>", "The derivation path to use for the principal.\n(e.g. --principal 123 will result in a derivation path of m/44'/223'/0'/0/123)\nMust be >= 0 && <= 255").default(0))
384
391
  .addCommand(new commander_1.Command("info")
385
392
  .option("-n --no-show-on-device")
386
393
  .description("Show the wallet's principal, address, and balance.")
@@ -1,7 +1,11 @@
1
1
  "use strict";
2
2
  var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
3
  if (k2 === undefined) k2 = k;
4
- Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
5
9
  }) : (function(o, m, k, k2) {
6
10
  if (k2 === undefined) k2 = k;
7
11
  o[k2] = m[k];
@@ -14,6 +14,11 @@ function equals(b1, b2) {
14
14
  }
15
15
  return true;
16
16
  }
17
+ const buf2hex = (buffer) => {
18
+ return [...new Uint8Array(buffer)]
19
+ .map((x) => x.toString(16).padStart(2, "0"))
20
+ .join("");
21
+ };
17
22
  // This implementation is adjusted from the Ed25519PublicKey.
18
23
  // The RAW_KEY_LENGTH and DER_PREFIX are modified accordingly
19
24
  class Secp256k1PublicKey {
@@ -58,6 +63,17 @@ class Secp256k1PublicKey {
58
63
  toRaw() {
59
64
  return this.rawKey;
60
65
  }
66
+ toHex() {
67
+ const hexPubKey = buf2hex(this.toRaw());
68
+ const isHex = hexPubKey.match("^[0-9a-fA-F]+$");
69
+ if (!isHex) {
70
+ throw new Error(`${hexPubKey} is not a hex string.`);
71
+ }
72
+ if (hexPubKey.length < 130 || hexPubKey.length > 150) {
73
+ throw new Error(`The key must be >= 130 characters and <= 150 characters.`);
74
+ }
75
+ return hexPubKey;
76
+ }
61
77
  }
62
78
  exports.Secp256k1PublicKey = Secp256k1PublicKey;
63
79
  // The length of secp256k1 public keys is always 65 bytes.
package/index.ts CHANGED
@@ -15,6 +15,8 @@ import {
15
15
  InsufficientAmountError,
16
16
  InsufficientFundsError,
17
17
  } from "@dfinity/nns";
18
+ import { Principal } from "@dfinity/principal";
19
+ import type { Secp256k1PublicKey } from "src/ledger/secp256k1";
18
20
  import { Agent, AnonymousIdentity, HttpAgent, Identity } from "@dfinity/agent";
19
21
  import chalk from "chalk";
20
22
 
@@ -24,7 +26,7 @@ import "node-window-polyfill/register";
24
26
  // Add polyfill for `window.fetch` for agent-js to work.
25
27
  // @ts-ignore (no types are available)
26
28
  import fetch from "node-fetch";
27
- import { Principal } from "@dfinity/principal";
29
+
28
30
  global.fetch = fetch;
29
31
  window.fetch = fetch;
30
32
 
@@ -54,17 +56,28 @@ async function getAgent(identity: Identity): Promise<Agent> {
54
56
  return agent;
55
57
  }
56
58
 
59
+ async function getLedgerIdentity(): Promise<LedgerIdentity> {
60
+ const principalPath = tryParseInt(program.opts().principal);
61
+ if (principalPath < 0 || principalPath > 255) {
62
+ throw new InvalidArgumentError(
63
+ "Principal path must be between 0 and 255 inclusive."
64
+ );
65
+ }
66
+ return LedgerIdentity.create(`m/44'/223'/0'/0/${principalPath}`);
67
+ }
68
+
57
69
  /**
58
70
  * Fetches the balance of the main account on the wallet.
59
71
  */
60
72
  async function getBalance() {
61
- const identity = await LedgerIdentity.create();
73
+ const identity = await getLedgerIdentity();
62
74
  const accountIdentifier = AccountIdentifier.fromPrincipal({
63
75
  principal: identity.getPrincipal(),
64
76
  });
65
77
 
66
78
  const ledger = LedgerCanister.create({
67
79
  agent: await getAgent(new AnonymousIdentity()),
80
+ hardwareWallet: true,
68
81
  });
69
82
 
70
83
  const balance = await ledger.accountBalance({
@@ -81,9 +94,10 @@ async function getBalance() {
81
94
  * @param amount Amount to send in e8s.
82
95
  */
83
96
  async function sendICP(to: AccountIdentifier, amount: ICP) {
84
- const identity = await LedgerIdentity.create();
97
+ const identity = await getLedgerIdentity();
85
98
  const ledger = LedgerCanister.create({
86
99
  agent: await getAgent(identity),
100
+ hardwareWallet: true,
87
101
  });
88
102
 
89
103
  const blockHeight = await ledger.transfer({
@@ -99,15 +113,19 @@ async function sendICP(to: AccountIdentifier, amount: ICP) {
99
113
  * Shows the principal and account idenifier on the terminal and on the wallet's screen.
100
114
  */
101
115
  async function showInfo(showOnDevice?: boolean) {
102
- const identity = await LedgerIdentity.create();
116
+ const identity = await getLedgerIdentity();
103
117
  const accountIdentifier = AccountIdentifier.fromPrincipal({
104
118
  principal: identity.getPrincipal(),
105
119
  });
120
+ const publicKey = identity.getPublicKey() as Secp256k1PublicKey;
106
121
 
107
122
  log(chalk.bold(`Principal: `) + identity.getPrincipal());
108
123
  log(
109
124
  chalk.bold(`Address (${identity.derivePath}): `) + accountIdentifier.toHex()
110
125
  );
126
+ log(
127
+ chalk.bold('Public key: ') + publicKey.toHex()
128
+ )
111
129
 
112
130
  if (showOnDevice) {
113
131
  log("Displaying the principal and the address on the device...");
@@ -121,7 +139,7 @@ async function showInfo(showOnDevice?: boolean) {
121
139
  * @param amount Amount to stake in e8s.
122
140
  */
123
141
  async function stakeNeuron(stake: ICP) {
124
- const identity = await LedgerIdentity.create();
142
+ const identity = await getLedgerIdentity();
125
143
  const ledger = LedgerCanister.create({
126
144
  agent: await getAgent(identity),
127
145
  });
@@ -160,7 +178,7 @@ async function increaseDissolveDelay(
160
178
  minutes: number,
161
179
  seconds: number
162
180
  ) {
163
- const identity = await LedgerIdentity.create();
181
+ const identity = await getLedgerIdentity();
164
182
  const governance = GovernanceCanister.create({
165
183
  agent: await getAgent(identity),
166
184
  hardwareWallet: true,
@@ -181,7 +199,7 @@ async function increaseDissolveDelay(
181
199
  }
182
200
 
183
201
  async function disburseNeuron(neuronId: bigint, to?: string, amount?: bigint) {
184
- const identity = await LedgerIdentity.create();
202
+ const identity = await getLedgerIdentity();
185
203
  const governance = GovernanceCanister.create({
186
204
  agent: await getAgent(identity),
187
205
  hardwareWallet: true,
@@ -197,7 +215,7 @@ async function disburseNeuron(neuronId: bigint, to?: string, amount?: bigint) {
197
215
  }
198
216
 
199
217
  async function spawnNeuron(neuronId: string, controller?: Principal) {
200
- const identity = await LedgerIdentity.create();
218
+ const identity = await getLedgerIdentity();
201
219
  const governance = GovernanceCanister.create({
202
220
  agent: await getAgent(identity),
203
221
  hardwareWallet: true,
@@ -211,7 +229,7 @@ async function spawnNeuron(neuronId: string, controller?: Principal) {
211
229
  }
212
230
 
213
231
  async function startDissolving(neuronId: bigint) {
214
- const identity = await LedgerIdentity.create();
232
+ const identity = await getLedgerIdentity();
215
233
  const governance = GovernanceCanister.create({
216
234
  agent: await getAgent(identity),
217
235
  hardwareWallet: true,
@@ -223,7 +241,7 @@ async function startDissolving(neuronId: bigint) {
223
241
  }
224
242
 
225
243
  async function stopDissolving(neuronId: bigint) {
226
- const identity = await LedgerIdentity.create();
244
+ const identity = await getLedgerIdentity();
227
245
  const governance = GovernanceCanister.create({
228
246
  agent: await getAgent(identity),
229
247
  hardwareWallet: true,
@@ -235,7 +253,7 @@ async function stopDissolving(neuronId: bigint) {
235
253
  }
236
254
 
237
255
  async function addHotkey(neuronId: bigint, principal: Principal) {
238
- const identity = await LedgerIdentity.create();
256
+ const identity = await getLedgerIdentity();
239
257
  const governance = GovernanceCanister.create({
240
258
  agent: await getAgent(identity),
241
259
  hardwareWallet: true,
@@ -250,7 +268,7 @@ async function addHotkey(neuronId: bigint, principal: Principal) {
250
268
  }
251
269
 
252
270
  async function removeHotkey(neuronId: bigint, principal: Principal) {
253
- const identity = await LedgerIdentity.create();
271
+ const identity = await getLedgerIdentity();
254
272
  const governance = GovernanceCanister.create({
255
273
  agent: await getAgent(identity),
256
274
  hardwareWallet: true,
@@ -265,7 +283,7 @@ async function removeHotkey(neuronId: bigint, principal: Principal) {
265
283
  }
266
284
 
267
285
  async function listNeurons() {
268
- const identity = await LedgerIdentity.create();
286
+ const identity = await getLedgerIdentity();
269
287
  const governance = GovernanceCanister.create({
270
288
  agent: await getAgent(identity),
271
289
  hardwareWallet: true,
@@ -288,23 +306,18 @@ async function listNeurons() {
288
306
  /**
289
307
  * Fetches the balance of the main account on the wallet.
290
308
  */
291
- async function claimNeurons(hexPubKey: string) {
292
- const isHex = hexPubKey.match("^[0-9a-fA-F]+$");
293
- if (!isHex) {
294
- throw new Error(`${hexPubKey} is not a hex string.`);
295
- }
309
+ async function claimNeurons() {
310
+ const identity = await getLedgerIdentity();
296
311
 
297
- if (hexPubKey.length < 130 || hexPubKey.length > 150) {
298
- throw new Error(`The key must be >= 130 characters and <= 150 characters.`);
299
- }
312
+ const publicKey = identity.getPublicKey() as Secp256k1PublicKey;
313
+ const hexPubKey = publicKey.toHex();
300
314
 
301
- const identity = await LedgerIdentity.create();
302
315
  const governance = await GenesisTokenCanister.create({
303
316
  agent: await getAgent(identity),
304
317
  });
305
318
 
306
319
  const claimedNeuronIds = await governance.claimNeurons({
307
- hexPubKey: hexPubKey,
320
+ hexPubKey,
308
321
  });
309
322
 
310
323
  ok(`Successfully claimed the following neurons: ${claimedNeuronIds}`);
@@ -477,11 +490,10 @@ async function main() {
477
490
  )
478
491
  .addCommand(
479
492
  new Command("claim")
480
- .requiredOption(
481
- "--hex-public-key <public-key>",
493
+ .description(
482
494
  "Claim the caller's GTC neurons."
483
495
  )
484
- .action((args) => run(() => claimNeurons(args.hexPublicKey)))
496
+ .action((args) => run(() => claimNeurons()))
485
497
  );
486
498
 
487
499
  const icp = new Command("icp")
@@ -518,6 +530,11 @@ async function main() {
518
530
  .default("https://ic0.app")
519
531
  .env("IC_NETWORK")
520
532
  )
533
+ .addOption(
534
+ new Option("--principal <principal>", "The derivation path to use for the principal.\n(e.g. --principal 123 will result in a derivation path of m/44'/223'/0'/0/123)\nMust be >= 0 && <= 255").default(
535
+ 0
536
+ )
537
+ )
521
538
  .addCommand(
522
539
  new Command("info")
523
540
  .option("-n --no-show-on-device")
package/package.json CHANGED
@@ -1,14 +1,17 @@
1
1
  {
2
2
  "name": "@dfinity/hardware-wallet-cli",
3
- "version": "0.1.1",
3
+ "version": "0.2.2",
4
4
  "description": "A CLI to interact with the Internet Computer App on Ledger Nano S/X devices.",
5
5
  "main": "./build/index.js",
6
6
  "scripts": {
7
7
  "format": "prettier --write .",
8
8
  "test": "echo \"Error: no test specified\" && exit 1",
9
9
  "build": "tsc --build",
10
+ "prepack": "npm run build",
10
11
  "clean": "tsc --build --clean",
11
- "refresh": "rm -rf ./node_modules ./package-lock.json && npm install"
12
+ "refresh": "rm -rf ./node_modules ./package-lock.json && npm install",
13
+ "execute": "ts-node ./index.ts",
14
+ "update:nns": "npm update @dfinity/nns"
12
15
  },
13
16
  "repository": {
14
17
  "type": "git",
@@ -22,7 +25,7 @@
22
25
  "homepage": "https://github.com/dfinity/hardware-wallet-cli#readme",
23
26
  "dependencies": {
24
27
  "@dfinity/agent": "^0.11.2",
25
- "@dfinity/nns": "^0.4.2",
28
+ "@dfinity/nns": "nightly",
26
29
  "@ledgerhq/hw-transport-node-hid-noevents": "^6.3.0",
27
30
  "@ledgerhq/hw-transport-webhid": "^6.27.1",
28
31
  "@zondax/ledger-icp": "^0.6.0",
@@ -35,7 +38,8 @@
35
38
  "@types/google-protobuf": "^3.15.6",
36
39
  "@types/node": "^17.0.16",
37
40
  "@types/node-hid": "^1.3.1",
38
- "prettier": "^2.6.2"
41
+ "prettier": "^2.6.2",
42
+ "ts-node": "^10.8.0"
39
43
  },
40
44
  "bin": {
41
45
  "ic-hardware-wallet": "./build/index.js"
@@ -15,6 +15,12 @@ function equals(b1: ArrayBuffer, b2: ArrayBuffer): boolean {
15
15
  return true;
16
16
  }
17
17
 
18
+ const buf2hex = (buffer: ArrayBuffer): string => {
19
+ return [...new Uint8Array(buffer)]
20
+ .map((x) => x.toString(16).padStart(2, "0"))
21
+ .join("");
22
+ };
23
+
18
24
  // This implementation is adjusted from the Ed25519PublicKey.
19
25
  // The RAW_KEY_LENGTH and DER_PREFIX are modified accordingly
20
26
  export class Secp256k1PublicKey implements PublicKey {
@@ -93,4 +99,19 @@ export class Secp256k1PublicKey implements PublicKey {
93
99
  public toRaw(): ArrayBuffer {
94
100
  return this.rawKey;
95
101
  }
102
+
103
+ public toHex(): string {
104
+ const hexPubKey = buf2hex(this.toRaw());
105
+
106
+ const isHex = hexPubKey.match("^[0-9a-fA-F]+$");
107
+ if (!isHex) {
108
+ throw new Error(`${hexPubKey} is not a hex string.`);
109
+ }
110
+
111
+ if (hexPubKey.length < 130 || hexPubKey.length > 150) {
112
+ throw new Error(`The key must be >= 130 characters and <= 150 characters.`);
113
+ }
114
+
115
+ return hexPubKey;
116
+ }
96
117
  }