@onekeyfe/hardware-cli 1.1.25-alpha.0
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/.claude-plugin/plugin.json +14 -0
- package/dist/chains.d.ts +74 -0
- package/dist/chains.js +307 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +824 -0
- package/dist/index.d.ts +96 -0
- package/dist/index.js +30 -0
- package/dist/sdk.d.ts +16 -0
- package/dist/sdk.js +187 -0
- package/evals/cases.json +564 -0
- package/evals/run-evals.sh +136 -0
- package/package.json +34 -0
- package/rollup.config.js +28 -0
- package/skills/device/SKILL.md +191 -0
- package/skills/firmware/SKILL.md +163 -0
- package/skills/security/SKILL.md +328 -0
- package/skills/signing/SKILL.md +478 -0
- package/src/chains.ts +387 -0
- package/src/cli.ts +855 -0
- package/src/index.ts +38 -0
- package/src/sdk.ts +198 -0
- package/tsconfig.json +18 -0
package/src/cli.ts
ADDED
|
@@ -0,0 +1,855 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
|
|
3
|
+
import { createSDK } from './sdk';
|
|
4
|
+
import {
|
|
5
|
+
resolveBatchGetAddress,
|
|
6
|
+
resolveGetAddress,
|
|
7
|
+
resolveGetPublicKey,
|
|
8
|
+
resolveSignMessage,
|
|
9
|
+
resolveSignTransaction,
|
|
10
|
+
} from './chains';
|
|
11
|
+
|
|
12
|
+
const program = new Command();
|
|
13
|
+
|
|
14
|
+
program
|
|
15
|
+
.name('onekey-hw')
|
|
16
|
+
.description('OneKey hardware wallet CLI for AI agent integration')
|
|
17
|
+
.version('1.1.25-alpha.0');
|
|
18
|
+
|
|
19
|
+
// ============================================================
|
|
20
|
+
// Global Options
|
|
21
|
+
// ============================================================
|
|
22
|
+
|
|
23
|
+
program.option('--json', 'Output in JSON format (for agent consumption)');
|
|
24
|
+
program.option('--connect-id <id>', 'Device connection ID (USB: serial, iOS: uuid, Android: MAC)');
|
|
25
|
+
program.option(
|
|
26
|
+
'--device-id <id>',
|
|
27
|
+
'Persistent device ID from getFeatures (changes when seed changes)'
|
|
28
|
+
);
|
|
29
|
+
program.option('--passphrase-state <state>', 'Passphrase state for hidden wallet access');
|
|
30
|
+
program.option('--use-empty-passphrase', 'Use standard wallet (skip passphrase prompt)');
|
|
31
|
+
|
|
32
|
+
// ============================================================
|
|
33
|
+
// Device Commands
|
|
34
|
+
// ============================================================
|
|
35
|
+
|
|
36
|
+
program
|
|
37
|
+
.command('search')
|
|
38
|
+
.description('Search for connected OneKey hardware wallet devices')
|
|
39
|
+
.option('--timeout <ms>', 'Search timeout in milliseconds', '10000')
|
|
40
|
+
.action(async opts => {
|
|
41
|
+
const sdk = await createSDK(program.opts());
|
|
42
|
+
try {
|
|
43
|
+
const result = await sdk.searchDevices();
|
|
44
|
+
outputResult(program.opts(), result);
|
|
45
|
+
} finally {
|
|
46
|
+
sdk.dispose();
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
program
|
|
51
|
+
.command('status')
|
|
52
|
+
.description('Get device features and current status')
|
|
53
|
+
.action(async () => {
|
|
54
|
+
const globalOpts = program.opts();
|
|
55
|
+
const sdk = await createSDK(globalOpts);
|
|
56
|
+
try {
|
|
57
|
+
const result = await sdk.getFeatures(globalOpts.connectId);
|
|
58
|
+
outputResult(globalOpts, result);
|
|
59
|
+
} finally {
|
|
60
|
+
sdk.dispose();
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
// ============================================================
|
|
65
|
+
// Signing Commands
|
|
66
|
+
// ============================================================
|
|
67
|
+
|
|
68
|
+
program
|
|
69
|
+
.command('get-address')
|
|
70
|
+
.description('Get a cryptocurrency address from the hardware wallet')
|
|
71
|
+
.requiredOption('--chain <chain>', 'Target blockchain (evm, btc, sol, ...)')
|
|
72
|
+
.option('--path <path>', 'BIP44 derivation path')
|
|
73
|
+
.option('--show-on-device <bool>', 'Display address on device for verification', 'true')
|
|
74
|
+
.action(async opts => {
|
|
75
|
+
const globalOpts = program.opts();
|
|
76
|
+
const sdk = await createSDK(globalOpts);
|
|
77
|
+
try {
|
|
78
|
+
const result = await resolveGetAddress(sdk, {
|
|
79
|
+
chain: opts.chain,
|
|
80
|
+
path: opts.path,
|
|
81
|
+
showOnDevice: opts.showOnDevice === 'true',
|
|
82
|
+
...getCommonParams(globalOpts),
|
|
83
|
+
});
|
|
84
|
+
outputResult(globalOpts, result);
|
|
85
|
+
} finally {
|
|
86
|
+
sdk.dispose();
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
program
|
|
91
|
+
.command('get-public-key')
|
|
92
|
+
.description('Get public key from the hardware wallet')
|
|
93
|
+
.requiredOption('--chain <chain>', 'Target blockchain')
|
|
94
|
+
.option('--path <path>', 'BIP44 derivation path')
|
|
95
|
+
.action(async opts => {
|
|
96
|
+
const globalOpts = program.opts();
|
|
97
|
+
const sdk = await createSDK(globalOpts);
|
|
98
|
+
try {
|
|
99
|
+
const result = await resolveGetPublicKey(sdk, {
|
|
100
|
+
chain: opts.chain,
|
|
101
|
+
path: opts.path,
|
|
102
|
+
...getCommonParams(globalOpts),
|
|
103
|
+
});
|
|
104
|
+
outputResult(globalOpts, result);
|
|
105
|
+
} finally {
|
|
106
|
+
sdk.dispose();
|
|
107
|
+
}
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
program
|
|
111
|
+
.command('sign-transaction')
|
|
112
|
+
.description('Sign a blockchain transaction (requires device confirmation)')
|
|
113
|
+
.requiredOption('--chain <chain>', 'Target blockchain')
|
|
114
|
+
.requiredOption('--tx <json>', 'Transaction data (JSON)')
|
|
115
|
+
.option('--path <path>', 'BIP44 derivation path')
|
|
116
|
+
.action(async opts => {
|
|
117
|
+
const globalOpts = program.opts();
|
|
118
|
+
const sdk = await createSDK(globalOpts);
|
|
119
|
+
try {
|
|
120
|
+
const tx = safeJsonParse(opts.tx, '--tx') as Record<string, unknown>;
|
|
121
|
+
const result = await resolveSignTransaction(sdk, {
|
|
122
|
+
chain: opts.chain,
|
|
123
|
+
path: opts.path,
|
|
124
|
+
transaction: tx,
|
|
125
|
+
...getCommonParams(globalOpts),
|
|
126
|
+
});
|
|
127
|
+
outputResult(globalOpts, result);
|
|
128
|
+
} finally {
|
|
129
|
+
sdk.dispose();
|
|
130
|
+
}
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
program
|
|
134
|
+
.command('sign-message')
|
|
135
|
+
.description('Sign a message (requires device confirmation)')
|
|
136
|
+
.requiredOption('--chain <chain>', 'Target blockchain')
|
|
137
|
+
.requiredOption('--message <msg>', 'Message to sign')
|
|
138
|
+
.option('--path <path>', 'BIP44 derivation path')
|
|
139
|
+
.action(async opts => {
|
|
140
|
+
const globalOpts = program.opts();
|
|
141
|
+
const sdk = await createSDK(globalOpts);
|
|
142
|
+
try {
|
|
143
|
+
const result = await resolveSignMessage(sdk, {
|
|
144
|
+
chain: opts.chain,
|
|
145
|
+
path: opts.path,
|
|
146
|
+
message: opts.message,
|
|
147
|
+
...getCommonParams(globalOpts),
|
|
148
|
+
});
|
|
149
|
+
outputResult(globalOpts, result);
|
|
150
|
+
} finally {
|
|
151
|
+
sdk.dispose();
|
|
152
|
+
}
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
program
|
|
156
|
+
.command('sign-typed-data')
|
|
157
|
+
.description('Sign EIP-712 typed data (EVM only, requires device confirmation)')
|
|
158
|
+
.requiredOption('--data <json>', 'EIP-712 typed data JSON')
|
|
159
|
+
.option('--path <path>', 'BIP44 derivation path')
|
|
160
|
+
.option('--metamask-v4-compat', 'Use MetaMask V4 compatibility mode', true)
|
|
161
|
+
.action(async opts => {
|
|
162
|
+
const globalOpts = program.opts();
|
|
163
|
+
const sdk = await createSDK(globalOpts);
|
|
164
|
+
try {
|
|
165
|
+
const data = safeJsonParse(opts.data, '--data');
|
|
166
|
+
const params = getCommonParams(globalOpts);
|
|
167
|
+
const path = opts.path || "m/44'/60'/0'/0/0";
|
|
168
|
+
const result = await sdk.evmSignTypedData(params.connectId || '', params.deviceId || '', {
|
|
169
|
+
path,
|
|
170
|
+
metamaskV4Compat: opts.metamaskV4Compat,
|
|
171
|
+
data,
|
|
172
|
+
...params,
|
|
173
|
+
} as any);
|
|
174
|
+
outputResult(globalOpts, result);
|
|
175
|
+
} finally {
|
|
176
|
+
sdk.dispose();
|
|
177
|
+
}
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
program
|
|
181
|
+
.command('sign-psbt')
|
|
182
|
+
.description('Sign a Bitcoin PSBT (Pro/Classic1s only, requires device confirmation)')
|
|
183
|
+
.requiredOption('--psbt <hex>', 'Hex-encoded PSBT data')
|
|
184
|
+
.option('--coin <coin>', 'Bitcoin network: btc, ltc, etc.', 'btc')
|
|
185
|
+
.action(async opts => {
|
|
186
|
+
const globalOpts = program.opts();
|
|
187
|
+
const sdk = await createSDK(globalOpts);
|
|
188
|
+
try {
|
|
189
|
+
const params = getCommonParams(globalOpts);
|
|
190
|
+
const result = await sdk.btcSignPsbt(params.connectId || '', params.deviceId || '', {
|
|
191
|
+
psbt: opts.psbt,
|
|
192
|
+
coin: opts.coin,
|
|
193
|
+
...params,
|
|
194
|
+
} as any);
|
|
195
|
+
outputResult(globalOpts, result);
|
|
196
|
+
} finally {
|
|
197
|
+
sdk.dispose();
|
|
198
|
+
}
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
program
|
|
202
|
+
.command('verify-message')
|
|
203
|
+
.description('Verify a signed message on-device (BTC, EVM, Starcoin)')
|
|
204
|
+
.requiredOption('--chain <chain>', 'Target blockchain (btc, evm, starcoin)')
|
|
205
|
+
.requiredOption('--address <addr>', 'Signer address')
|
|
206
|
+
.requiredOption('--message <msg>', 'Original message')
|
|
207
|
+
.requiredOption('--signature <sig>', 'Signature to verify')
|
|
208
|
+
.action(async opts => {
|
|
209
|
+
const globalOpts = program.opts();
|
|
210
|
+
const sdk = await createSDK(globalOpts);
|
|
211
|
+
try {
|
|
212
|
+
const params = getCommonParams(globalOpts);
|
|
213
|
+
const cid = params.connectId || '';
|
|
214
|
+
const did = params.deviceId || '';
|
|
215
|
+
let result: unknown;
|
|
216
|
+
switch (opts.chain.toLowerCase()) {
|
|
217
|
+
case 'evm':
|
|
218
|
+
case 'eth':
|
|
219
|
+
case 'ethereum':
|
|
220
|
+
result = await sdk.evmVerifyMessage(cid, did, {
|
|
221
|
+
address: opts.address,
|
|
222
|
+
messageHex: opts.message,
|
|
223
|
+
signature: opts.signature,
|
|
224
|
+
});
|
|
225
|
+
break;
|
|
226
|
+
case 'btc':
|
|
227
|
+
case 'bitcoin':
|
|
228
|
+
result = await sdk.btcVerifyMessage(cid, did, {
|
|
229
|
+
address: opts.address,
|
|
230
|
+
message: opts.message,
|
|
231
|
+
signature: opts.signature,
|
|
232
|
+
coin: 'btc',
|
|
233
|
+
});
|
|
234
|
+
break;
|
|
235
|
+
case 'starcoin':
|
|
236
|
+
case 'stc':
|
|
237
|
+
result = await sdk.starcoinVerifyMessage(cid, did, {
|
|
238
|
+
publicKey: opts.address,
|
|
239
|
+
message: opts.message,
|
|
240
|
+
signature: opts.signature,
|
|
241
|
+
});
|
|
242
|
+
break;
|
|
243
|
+
default:
|
|
244
|
+
throw new Error(
|
|
245
|
+
`verifyMessage not supported for chain: ${opts.chain}. Supported: evm, btc, starcoin`
|
|
246
|
+
);
|
|
247
|
+
}
|
|
248
|
+
outputResult(globalOpts, result);
|
|
249
|
+
} finally {
|
|
250
|
+
sdk.dispose();
|
|
251
|
+
}
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
program
|
|
255
|
+
.command('batch-get-address')
|
|
256
|
+
.description('Get addresses for multiple chains/paths in a single session')
|
|
257
|
+
.requiredOption('--bundle <json>', 'JSON array of {chain, path, showOnDevice}')
|
|
258
|
+
.action(async opts => {
|
|
259
|
+
const globalOpts = program.opts();
|
|
260
|
+
const sdk = await createSDK(globalOpts);
|
|
261
|
+
try {
|
|
262
|
+
const bundle = safeJsonParse(opts.bundle, '--bundle') as Array<{
|
|
263
|
+
chain: string;
|
|
264
|
+
path?: string;
|
|
265
|
+
showOnDevice?: boolean;
|
|
266
|
+
}>;
|
|
267
|
+
const result = await resolveBatchGetAddress(sdk, {
|
|
268
|
+
bundle,
|
|
269
|
+
...getCommonParams(globalOpts),
|
|
270
|
+
});
|
|
271
|
+
outputResult(globalOpts, result);
|
|
272
|
+
} finally {
|
|
273
|
+
sdk.dispose();
|
|
274
|
+
}
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
// ============================================================
|
|
278
|
+
// Chain-Specific Commands
|
|
279
|
+
// ============================================================
|
|
280
|
+
|
|
281
|
+
program
|
|
282
|
+
.command('evm-sign-eip712')
|
|
283
|
+
.description('Sign EIP-712 message by domain/message hash (EVM, requires device confirmation)')
|
|
284
|
+
.requiredOption('--domain-hash <hex>', 'EIP-712 domain separator hash')
|
|
285
|
+
.requiredOption('--message-hash <hex>', 'EIP-712 message hash')
|
|
286
|
+
.option('--path <path>', 'BIP44 derivation path', "m/44'/60'/0'/0/0")
|
|
287
|
+
.action(async opts => {
|
|
288
|
+
const globalOpts = program.opts();
|
|
289
|
+
const sdk = await createSDK(globalOpts);
|
|
290
|
+
try {
|
|
291
|
+
const p = getCommonParams(globalOpts);
|
|
292
|
+
const result = await sdk.evmSignMessageEIP712(p.connectId || '', p.deviceId || '', {
|
|
293
|
+
path: opts.path,
|
|
294
|
+
domainHash: opts.domainHash,
|
|
295
|
+
messageHash: opts.messageHash,
|
|
296
|
+
});
|
|
297
|
+
outputResult(globalOpts, result);
|
|
298
|
+
} finally {
|
|
299
|
+
sdk.dispose();
|
|
300
|
+
}
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
program
|
|
304
|
+
.command('sol-sign-offchain')
|
|
305
|
+
.description('Sign a Solana off-chain message (requires device confirmation)')
|
|
306
|
+
.requiredOption('--message-hex <hex>', 'Off-chain message as hex')
|
|
307
|
+
.option('--path <path>', 'BIP44 derivation path', "m/44'/501'/0'/0'")
|
|
308
|
+
.action(async opts => {
|
|
309
|
+
const globalOpts = program.opts();
|
|
310
|
+
const sdk = await createSDK(globalOpts);
|
|
311
|
+
try {
|
|
312
|
+
const p = getCommonParams(globalOpts);
|
|
313
|
+
const result = await sdk.solSignOffchainMessage(p.connectId || '', p.deviceId || '', {
|
|
314
|
+
path: opts.path,
|
|
315
|
+
messageHex: opts.messageHex,
|
|
316
|
+
});
|
|
317
|
+
outputResult(globalOpts, result);
|
|
318
|
+
} finally {
|
|
319
|
+
sdk.dispose();
|
|
320
|
+
}
|
|
321
|
+
});
|
|
322
|
+
|
|
323
|
+
program
|
|
324
|
+
.command('nostr-encrypt')
|
|
325
|
+
.description('Encrypt a message for a Nostr recipient')
|
|
326
|
+
.requiredOption('--pubkey <hex>', 'Recipient Nostr public key')
|
|
327
|
+
.requiredOption('--plaintext <text>', 'Message to encrypt')
|
|
328
|
+
.option('--path <path>', 'BIP44 derivation path', "m/44'/1237'/0'/0/0")
|
|
329
|
+
.option('--show-on-device <bool>', 'Display on device', 'false')
|
|
330
|
+
.action(async opts => {
|
|
331
|
+
const globalOpts = program.opts();
|
|
332
|
+
const sdk = await createSDK(globalOpts);
|
|
333
|
+
try {
|
|
334
|
+
const p = getCommonParams(globalOpts);
|
|
335
|
+
const result = await sdk.nostrEncryptMessage(p.connectId || '', p.deviceId || '', {
|
|
336
|
+
path: opts.path,
|
|
337
|
+
pubkey: opts.pubkey,
|
|
338
|
+
plaintext: opts.plaintext,
|
|
339
|
+
showOnOneKey: opts.showOnDevice === 'true',
|
|
340
|
+
});
|
|
341
|
+
outputResult(globalOpts, result);
|
|
342
|
+
} finally {
|
|
343
|
+
sdk.dispose();
|
|
344
|
+
}
|
|
345
|
+
});
|
|
346
|
+
|
|
347
|
+
program
|
|
348
|
+
.command('nostr-decrypt')
|
|
349
|
+
.description('Decrypt a Nostr encrypted message')
|
|
350
|
+
.requiredOption('--pubkey <hex>', 'Sender Nostr public key')
|
|
351
|
+
.requiredOption('--ciphertext <text>', 'Encrypted message')
|
|
352
|
+
.option('--path <path>', 'BIP44 derivation path', "m/44'/1237'/0'/0/0")
|
|
353
|
+
.option('--show-on-device <bool>', 'Display on device', 'false')
|
|
354
|
+
.action(async opts => {
|
|
355
|
+
const globalOpts = program.opts();
|
|
356
|
+
const sdk = await createSDK(globalOpts);
|
|
357
|
+
try {
|
|
358
|
+
const p = getCommonParams(globalOpts);
|
|
359
|
+
const result = await sdk.nostrDecryptMessage(p.connectId || '', p.deviceId || '', {
|
|
360
|
+
path: opts.path,
|
|
361
|
+
pubkey: opts.pubkey,
|
|
362
|
+
ciphertext: opts.ciphertext,
|
|
363
|
+
showOnOneKey: opts.showOnDevice === 'true',
|
|
364
|
+
});
|
|
365
|
+
outputResult(globalOpts, result);
|
|
366
|
+
} finally {
|
|
367
|
+
sdk.dispose();
|
|
368
|
+
}
|
|
369
|
+
});
|
|
370
|
+
|
|
371
|
+
program
|
|
372
|
+
.command('nostr-sign-schnorr')
|
|
373
|
+
.description('Sign a Schnorr signature for Nostr')
|
|
374
|
+
.requiredOption('--hash <hex>', 'Hash to sign (hex)')
|
|
375
|
+
.option('--path <path>', 'BIP44 derivation path', "m/44'/1237'/0'/0/0")
|
|
376
|
+
.action(async opts => {
|
|
377
|
+
const globalOpts = program.opts();
|
|
378
|
+
const sdk = await createSDK(globalOpts);
|
|
379
|
+
try {
|
|
380
|
+
const p = getCommonParams(globalOpts);
|
|
381
|
+
const result = await sdk.nostrSignSchnorr(p.connectId || '', p.deviceId || '', {
|
|
382
|
+
path: opts.path,
|
|
383
|
+
hash: opts.hash,
|
|
384
|
+
});
|
|
385
|
+
outputResult(globalOpts, result);
|
|
386
|
+
} finally {
|
|
387
|
+
sdk.dispose();
|
|
388
|
+
}
|
|
389
|
+
});
|
|
390
|
+
|
|
391
|
+
program
|
|
392
|
+
.command('lnurl-auth')
|
|
393
|
+
.description('Authenticate with LNURL (Lightning Network)')
|
|
394
|
+
.requiredOption('--domain <domain>', 'Service domain')
|
|
395
|
+
.requiredOption('--k1 <hex>', 'Challenge k1 parameter')
|
|
396
|
+
.action(async opts => {
|
|
397
|
+
const globalOpts = program.opts();
|
|
398
|
+
const sdk = await createSDK(globalOpts);
|
|
399
|
+
try {
|
|
400
|
+
const p = getCommonParams(globalOpts);
|
|
401
|
+
const result = await sdk.lnurlAuth(p.connectId || '', p.deviceId || '', {
|
|
402
|
+
domain: opts.domain,
|
|
403
|
+
k1: opts.k1,
|
|
404
|
+
});
|
|
405
|
+
outputResult(globalOpts, result);
|
|
406
|
+
} finally {
|
|
407
|
+
sdk.dispose();
|
|
408
|
+
}
|
|
409
|
+
});
|
|
410
|
+
|
|
411
|
+
program
|
|
412
|
+
.command('conflux-sign-cip23')
|
|
413
|
+
.description('Sign a Conflux CIP-23 structured message')
|
|
414
|
+
.requiredOption('--domain-hash <hex>', 'CIP-23 domain hash')
|
|
415
|
+
.requiredOption('--message-hash <hex>', 'CIP-23 message hash')
|
|
416
|
+
.option('--path <path>', 'BIP44 derivation path', "m/44'/503'/0'/0/0")
|
|
417
|
+
.action(async opts => {
|
|
418
|
+
const globalOpts = program.opts();
|
|
419
|
+
const sdk = await createSDK(globalOpts);
|
|
420
|
+
try {
|
|
421
|
+
const p = getCommonParams(globalOpts);
|
|
422
|
+
const result = await sdk.confluxSignMessageCIP23(p.connectId || '', p.deviceId || '', {
|
|
423
|
+
path: opts.path,
|
|
424
|
+
domainHash: opts.domainHash,
|
|
425
|
+
messageHash: opts.messageHash,
|
|
426
|
+
});
|
|
427
|
+
outputResult(globalOpts, result);
|
|
428
|
+
} finally {
|
|
429
|
+
sdk.dispose();
|
|
430
|
+
}
|
|
431
|
+
});
|
|
432
|
+
|
|
433
|
+
program
|
|
434
|
+
.command('aptos-sign-in')
|
|
435
|
+
.description('Sign an Aptos sign-in message')
|
|
436
|
+
.requiredOption('--payload <text>', 'Sign-in payload string')
|
|
437
|
+
.option('--path <path>', 'BIP44 derivation path', "m/44'/637'/0'/0'/0'")
|
|
438
|
+
.action(async opts => {
|
|
439
|
+
const globalOpts = program.opts();
|
|
440
|
+
const sdk = await createSDK(globalOpts);
|
|
441
|
+
try {
|
|
442
|
+
const p = getCommonParams(globalOpts);
|
|
443
|
+
const result = await sdk.aptosSignInMessage(p.connectId || '', p.deviceId || '', {
|
|
444
|
+
path: opts.path,
|
|
445
|
+
payload: opts.payload,
|
|
446
|
+
});
|
|
447
|
+
outputResult(globalOpts, result);
|
|
448
|
+
} finally {
|
|
449
|
+
sdk.dispose();
|
|
450
|
+
}
|
|
451
|
+
});
|
|
452
|
+
|
|
453
|
+
program
|
|
454
|
+
.command('ton-sign-proof')
|
|
455
|
+
.description('Sign a TON proof (for wallet authentication)')
|
|
456
|
+
.requiredOption('--appdomain <domain>', 'Application domain')
|
|
457
|
+
.requiredOption('--expire-at <timestamp>', 'Proof expiration timestamp')
|
|
458
|
+
.option('--comment <text>', 'Optional comment')
|
|
459
|
+
.option('--path <path>', 'BIP44 derivation path', "m/44'/607'/0'")
|
|
460
|
+
.action(async opts => {
|
|
461
|
+
const globalOpts = program.opts();
|
|
462
|
+
const sdk = await createSDK(globalOpts);
|
|
463
|
+
try {
|
|
464
|
+
const p = getCommonParams(globalOpts);
|
|
465
|
+
const result = await sdk.tonSignProof(p.connectId || '', p.deviceId || '', {
|
|
466
|
+
path: opts.path,
|
|
467
|
+
appdomain: opts.appdomain,
|
|
468
|
+
expireAt: safeParseInt(opts.expireAt, '--expire-at'),
|
|
469
|
+
...(opts.comment ? { comment: opts.comment } : {}),
|
|
470
|
+
});
|
|
471
|
+
outputResult(globalOpts, result);
|
|
472
|
+
} finally {
|
|
473
|
+
sdk.dispose();
|
|
474
|
+
}
|
|
475
|
+
});
|
|
476
|
+
|
|
477
|
+
// ============================================================
|
|
478
|
+
// Firmware Commands
|
|
479
|
+
// ============================================================
|
|
480
|
+
|
|
481
|
+
program
|
|
482
|
+
.command('firmware-check')
|
|
483
|
+
.description('Check if firmware updates are available')
|
|
484
|
+
.action(async () => {
|
|
485
|
+
const globalOpts = program.opts();
|
|
486
|
+
const sdk = await createSDK(globalOpts);
|
|
487
|
+
try {
|
|
488
|
+
const result = await sdk.checkFirmwareRelease(globalOpts.connectId);
|
|
489
|
+
outputResult(globalOpts, result);
|
|
490
|
+
} finally {
|
|
491
|
+
sdk.dispose();
|
|
492
|
+
}
|
|
493
|
+
});
|
|
494
|
+
|
|
495
|
+
program
|
|
496
|
+
.command('firmware-check-all')
|
|
497
|
+
.description('Check all firmware components (system, BLE, bootloader)')
|
|
498
|
+
.action(async () => {
|
|
499
|
+
const globalOpts = program.opts();
|
|
500
|
+
const sdk = await createSDK(globalOpts);
|
|
501
|
+
try {
|
|
502
|
+
const result = await sdk.checkAllFirmwareRelease(globalOpts.connectId);
|
|
503
|
+
outputResult(globalOpts, result);
|
|
504
|
+
} finally {
|
|
505
|
+
sdk.dispose();
|
|
506
|
+
}
|
|
507
|
+
});
|
|
508
|
+
|
|
509
|
+
program
|
|
510
|
+
.command('firmware-update')
|
|
511
|
+
.description('Update device firmware')
|
|
512
|
+
.option('--version <ver>', 'Target firmware version (e.g., "4.8.0")')
|
|
513
|
+
.option('--platform <platform>', 'Platform: native | desktop | ext | web', 'desktop')
|
|
514
|
+
.action(async opts => {
|
|
515
|
+
const globalOpts = program.opts();
|
|
516
|
+
const sdk = await createSDK(globalOpts);
|
|
517
|
+
try {
|
|
518
|
+
// firmwareUpdateV2 requires: connectId, deviceId, { updateType, platform, version? }
|
|
519
|
+
const params: Record<string, unknown> = {
|
|
520
|
+
updateType: 'firmware',
|
|
521
|
+
platform: opts.platform,
|
|
522
|
+
};
|
|
523
|
+
if (opts.version) {
|
|
524
|
+
params.version = parseVersion(opts.version);
|
|
525
|
+
}
|
|
526
|
+
// firmwareUpdateV2 signature: (connectId, params) — 2 args only
|
|
527
|
+
const result = await sdk.firmwareUpdateV2(globalOpts.connectId, params as any);
|
|
528
|
+
outputResult(globalOpts, result);
|
|
529
|
+
} finally {
|
|
530
|
+
sdk.dispose();
|
|
531
|
+
}
|
|
532
|
+
});
|
|
533
|
+
|
|
534
|
+
program
|
|
535
|
+
.command('firmware-update-ble')
|
|
536
|
+
.description('Update BLE (Bluetooth) firmware')
|
|
537
|
+
.option('--version <ver>', 'Target BLE firmware version')
|
|
538
|
+
.option('--platform <platform>', 'Platform: native | desktop | ext | web', 'desktop')
|
|
539
|
+
.action(async opts => {
|
|
540
|
+
const globalOpts = program.opts();
|
|
541
|
+
const sdk = await createSDK(globalOpts);
|
|
542
|
+
try {
|
|
543
|
+
const params: Record<string, unknown> = {
|
|
544
|
+
updateType: 'ble',
|
|
545
|
+
platform: opts.platform,
|
|
546
|
+
};
|
|
547
|
+
if (opts.version) {
|
|
548
|
+
params.version = parseVersion(opts.version);
|
|
549
|
+
}
|
|
550
|
+
const result = await sdk.firmwareUpdateV2(globalOpts.connectId, params as any);
|
|
551
|
+
outputResult(globalOpts, result);
|
|
552
|
+
} finally {
|
|
553
|
+
sdk.dispose();
|
|
554
|
+
}
|
|
555
|
+
});
|
|
556
|
+
|
|
557
|
+
program
|
|
558
|
+
.command('bootloader-check')
|
|
559
|
+
.description('Check bootloader version and status')
|
|
560
|
+
.action(async () => {
|
|
561
|
+
const globalOpts = program.opts();
|
|
562
|
+
const sdk = await createSDK(globalOpts);
|
|
563
|
+
try {
|
|
564
|
+
const result = await sdk.checkBootloaderRelease(globalOpts.connectId);
|
|
565
|
+
outputResult(globalOpts, result);
|
|
566
|
+
} finally {
|
|
567
|
+
sdk.dispose();
|
|
568
|
+
}
|
|
569
|
+
});
|
|
570
|
+
|
|
571
|
+
// ============================================================
|
|
572
|
+
// Security / Management Commands
|
|
573
|
+
// ============================================================
|
|
574
|
+
|
|
575
|
+
program
|
|
576
|
+
.command('change-pin')
|
|
577
|
+
.description('Change or set the device PIN code')
|
|
578
|
+
.option('--remove', 'Remove PIN protection instead of changing')
|
|
579
|
+
.action(async opts => {
|
|
580
|
+
const globalOpts = program.opts();
|
|
581
|
+
const sdk = await createSDK(globalOpts);
|
|
582
|
+
try {
|
|
583
|
+
const result = await sdk.deviceChangePin(globalOpts.connectId, {
|
|
584
|
+
remove: opts.remove ?? false,
|
|
585
|
+
} as any);
|
|
586
|
+
outputResult(globalOpts, result);
|
|
587
|
+
} finally {
|
|
588
|
+
sdk.dispose();
|
|
589
|
+
}
|
|
590
|
+
});
|
|
591
|
+
|
|
592
|
+
program
|
|
593
|
+
.command('passphrase-state')
|
|
594
|
+
.description('Get current passphrase state (for hidden wallet session management)')
|
|
595
|
+
.action(async () => {
|
|
596
|
+
const globalOpts = program.opts();
|
|
597
|
+
const sdk = await createSDK(globalOpts);
|
|
598
|
+
try {
|
|
599
|
+
const result = await sdk.getPassphraseState(globalOpts.connectId, {
|
|
600
|
+
useEmptyPassphrase: globalOpts.useEmptyPassphrase,
|
|
601
|
+
} as any);
|
|
602
|
+
outputResult(globalOpts, result);
|
|
603
|
+
} finally {
|
|
604
|
+
sdk.dispose();
|
|
605
|
+
}
|
|
606
|
+
});
|
|
607
|
+
|
|
608
|
+
program
|
|
609
|
+
.command('toggle-passphrase')
|
|
610
|
+
.description('Enable or disable passphrase (hidden wallet) protection')
|
|
611
|
+
.requiredOption('--enable <bool>', 'true to enable, false to disable')
|
|
612
|
+
.action(async opts => {
|
|
613
|
+
const globalOpts = program.opts();
|
|
614
|
+
const sdk = await createSDK(globalOpts);
|
|
615
|
+
try {
|
|
616
|
+
const result = await sdk.deviceSettings(globalOpts.connectId, {
|
|
617
|
+
usePassphrase: opts.enable === 'true',
|
|
618
|
+
});
|
|
619
|
+
outputResult(globalOpts, result);
|
|
620
|
+
} finally {
|
|
621
|
+
sdk.dispose();
|
|
622
|
+
}
|
|
623
|
+
});
|
|
624
|
+
|
|
625
|
+
program
|
|
626
|
+
.command('device-backup')
|
|
627
|
+
.description('Trigger recovery phrase backup on device')
|
|
628
|
+
.action(async () => {
|
|
629
|
+
const globalOpts = program.opts();
|
|
630
|
+
const sdk = await createSDK(globalOpts);
|
|
631
|
+
try {
|
|
632
|
+
const result = await sdk.deviceBackup(globalOpts.connectId);
|
|
633
|
+
outputResult(globalOpts, result);
|
|
634
|
+
} finally {
|
|
635
|
+
sdk.dispose();
|
|
636
|
+
}
|
|
637
|
+
});
|
|
638
|
+
|
|
639
|
+
program
|
|
640
|
+
.command('device-recovery')
|
|
641
|
+
.description('Recover wallet from recovery phrase (entered on device)')
|
|
642
|
+
.option('--word-count <count>', 'Recovery phrase length: 12, 18, or 24', '24')
|
|
643
|
+
.option('--passphrase-protection <bool>', 'Enable passphrase after recovery', 'false')
|
|
644
|
+
.option('--pin-protection <bool>', 'Set PIN after recovery', 'true')
|
|
645
|
+
.option('--label <name>', 'Device label')
|
|
646
|
+
.action(async opts => {
|
|
647
|
+
const globalOpts = program.opts();
|
|
648
|
+
const sdk = await createSDK(globalOpts);
|
|
649
|
+
try {
|
|
650
|
+
const result = await sdk.deviceRecovery(globalOpts.connectId, {
|
|
651
|
+
wordCount: safeParseInt(opts.wordCount, '--word-count'),
|
|
652
|
+
passphraseProtection: opts.passphraseProtection === 'true',
|
|
653
|
+
pinProtection: opts.pinProtection === 'true',
|
|
654
|
+
label: opts.label,
|
|
655
|
+
});
|
|
656
|
+
outputResult(globalOpts, result);
|
|
657
|
+
} finally {
|
|
658
|
+
sdk.dispose();
|
|
659
|
+
}
|
|
660
|
+
});
|
|
661
|
+
|
|
662
|
+
program
|
|
663
|
+
.command('device-reset')
|
|
664
|
+
.description('Initialize device with a new wallet seed (DESTROYS current wallet)')
|
|
665
|
+
.option('--word-count <count>', 'Seed phrase length: 12, 18, or 24', '24')
|
|
666
|
+
.option('--passphrase-protection <bool>', 'Enable passphrase', 'false')
|
|
667
|
+
.option('--pin-protection <bool>', 'Set PIN', 'true')
|
|
668
|
+
.option('--label <name>', 'Device label')
|
|
669
|
+
.action(async opts => {
|
|
670
|
+
const globalOpts = program.opts();
|
|
671
|
+
const sdk = await createSDK(globalOpts);
|
|
672
|
+
try {
|
|
673
|
+
const result = await sdk.deviceReset(globalOpts.connectId, {
|
|
674
|
+
strength: wordCountToStrength(safeParseInt(opts.wordCount, '--word-count')),
|
|
675
|
+
passphraseProtection: opts.passphraseProtection === 'true',
|
|
676
|
+
pinProtection: opts.pinProtection === 'true',
|
|
677
|
+
label: opts.label,
|
|
678
|
+
});
|
|
679
|
+
outputResult(globalOpts, result);
|
|
680
|
+
} finally {
|
|
681
|
+
sdk.dispose();
|
|
682
|
+
}
|
|
683
|
+
});
|
|
684
|
+
|
|
685
|
+
program
|
|
686
|
+
.command('device-wipe')
|
|
687
|
+
.description('Factory reset — erase ALL data (IRREVERSIBLE)')
|
|
688
|
+
.action(async () => {
|
|
689
|
+
const globalOpts = program.opts();
|
|
690
|
+
const sdk = await createSDK(globalOpts);
|
|
691
|
+
try {
|
|
692
|
+
const result = await sdk.deviceWipe(globalOpts.connectId);
|
|
693
|
+
outputResult(globalOpts, result);
|
|
694
|
+
} finally {
|
|
695
|
+
sdk.dispose();
|
|
696
|
+
}
|
|
697
|
+
});
|
|
698
|
+
|
|
699
|
+
program
|
|
700
|
+
.command('device-settings')
|
|
701
|
+
.description('Update device label and settings')
|
|
702
|
+
.option('--label <name>', 'Device display name')
|
|
703
|
+
.option('--auto-lock-delay <seconds>', 'Auto-lock timeout in seconds (0 = disabled)')
|
|
704
|
+
.option('--language <lang>', 'Device language')
|
|
705
|
+
.option('--passphrase-always-on-device <bool>', 'Always enter passphrase on device')
|
|
706
|
+
.option('--haptic-feedback <bool>', 'Enable/disable haptic feedback')
|
|
707
|
+
.option('--auto-shutdown-delay <seconds>', 'Auto shutdown timeout in seconds')
|
|
708
|
+
.action(async opts => {
|
|
709
|
+
const globalOpts = program.opts();
|
|
710
|
+
const sdk = await createSDK(globalOpts);
|
|
711
|
+
try {
|
|
712
|
+
// Map CLI options to SDK param names (camelCase → snake_case handled by SDK)
|
|
713
|
+
// Reference: packages/core/src/api/device/DeviceSettings.ts
|
|
714
|
+
const settings: Record<string, unknown> = {};
|
|
715
|
+
if (opts.label !== undefined) settings.label = opts.label;
|
|
716
|
+
if (opts.autoLockDelay !== undefined)
|
|
717
|
+
settings.autoLockDelayMs = safeParseInt(opts.autoLockDelay, '--auto-lock-delay') * 1000;
|
|
718
|
+
if (opts.language !== undefined) settings.language = opts.language;
|
|
719
|
+
if (opts.passphraseAlwaysOnDevice !== undefined)
|
|
720
|
+
settings.passphraseAlwaysOnDevice = opts.passphraseAlwaysOnDevice === 'true';
|
|
721
|
+
if (opts.hapticFeedback !== undefined)
|
|
722
|
+
settings.hapticFeedback = opts.hapticFeedback === 'true';
|
|
723
|
+
if (opts.autoShutdownDelay !== undefined)
|
|
724
|
+
settings.autoShutdownDelayMs =
|
|
725
|
+
safeParseInt(opts.autoShutdownDelay, '--auto-shutdown-delay') * 1000;
|
|
726
|
+
|
|
727
|
+
if (Object.keys(settings).length === 0) {
|
|
728
|
+
outputResult(globalOpts, {
|
|
729
|
+
success: false,
|
|
730
|
+
error: 'No settings provided. Use --label, --auto-lock-delay, --language, etc.',
|
|
731
|
+
});
|
|
732
|
+
return;
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
const result = await sdk.deviceSettings(globalOpts.connectId, settings);
|
|
736
|
+
outputResult(globalOpts, result);
|
|
737
|
+
} finally {
|
|
738
|
+
sdk.dispose();
|
|
739
|
+
}
|
|
740
|
+
});
|
|
741
|
+
|
|
742
|
+
program
|
|
743
|
+
.command('device-verify')
|
|
744
|
+
.description('Verify device is genuine OneKey hardware')
|
|
745
|
+
.action(async () => {
|
|
746
|
+
const globalOpts = program.opts();
|
|
747
|
+
const sdk = await createSDK(globalOpts);
|
|
748
|
+
try {
|
|
749
|
+
const result = await sdk.deviceVerify(globalOpts.connectId);
|
|
750
|
+
outputResult(globalOpts, result);
|
|
751
|
+
} finally {
|
|
752
|
+
sdk.dispose();
|
|
753
|
+
}
|
|
754
|
+
});
|
|
755
|
+
|
|
756
|
+
program
|
|
757
|
+
.command('lock')
|
|
758
|
+
.description('Lock the device')
|
|
759
|
+
.action(async () => {
|
|
760
|
+
const globalOpts = program.opts();
|
|
761
|
+
const sdk = await createSDK(globalOpts);
|
|
762
|
+
try {
|
|
763
|
+
const result = await sdk.deviceLock(globalOpts.connectId);
|
|
764
|
+
outputResult(globalOpts, result);
|
|
765
|
+
} finally {
|
|
766
|
+
sdk.dispose();
|
|
767
|
+
}
|
|
768
|
+
});
|
|
769
|
+
|
|
770
|
+
// ============================================================
|
|
771
|
+
// Helpers
|
|
772
|
+
// ============================================================
|
|
773
|
+
|
|
774
|
+
/**
|
|
775
|
+
* Extract common device params from global CLI options.
|
|
776
|
+
* These are passed to every SDK method call.
|
|
777
|
+
*/
|
|
778
|
+
function getCommonParams(globalOpts: Record<string, any>) {
|
|
779
|
+
return {
|
|
780
|
+
connectId: globalOpts.connectId,
|
|
781
|
+
deviceId: globalOpts.deviceId,
|
|
782
|
+
passphraseState: globalOpts.passphraseState,
|
|
783
|
+
useEmptyPassphrase: globalOpts.useEmptyPassphrase,
|
|
784
|
+
};
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
function outputResult(globalOpts: { json?: boolean }, result: unknown): void {
|
|
788
|
+
// #10 FIX: Always use JSON.stringify to avoid [Object] truncation
|
|
789
|
+
console.log(JSON.stringify(result, null, 2));
|
|
790
|
+
|
|
791
|
+
// #11 FIX: Exit with code 1 on SDK failure
|
|
792
|
+
if (result && typeof result === 'object' && 'success' in result && !(result as any).success) {
|
|
793
|
+
process.exitCode = 1;
|
|
794
|
+
}
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
function wordCountToStrength(wordCount: number): number {
|
|
798
|
+
// #8 FIX: Validate word count is one of the allowed values
|
|
799
|
+
if (![12, 18, 24].includes(wordCount)) {
|
|
800
|
+
throw new Error(`Invalid word count: ${wordCount}. Must be 12, 18, or 24.`);
|
|
801
|
+
}
|
|
802
|
+
switch (wordCount) {
|
|
803
|
+
case 12:
|
|
804
|
+
return 128;
|
|
805
|
+
case 18:
|
|
806
|
+
return 192;
|
|
807
|
+
case 24:
|
|
808
|
+
default:
|
|
809
|
+
return 256;
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
/**
|
|
814
|
+
* #6 FIX: Safe JSON.parse with structured error output
|
|
815
|
+
*/
|
|
816
|
+
function safeJsonParse(input: string, label: string): unknown {
|
|
817
|
+
try {
|
|
818
|
+
return JSON.parse(input);
|
|
819
|
+
} catch {
|
|
820
|
+
console.error(
|
|
821
|
+
JSON.stringify({
|
|
822
|
+
success: false,
|
|
823
|
+
payload: {
|
|
824
|
+
error: `Invalid JSON for ${label}: ${input.slice(0, 100)}`,
|
|
825
|
+
code: 'INVALID_JSON',
|
|
826
|
+
},
|
|
827
|
+
})
|
|
828
|
+
);
|
|
829
|
+
process.exit(1);
|
|
830
|
+
}
|
|
831
|
+
}
|
|
832
|
+
|
|
833
|
+
/**
|
|
834
|
+
* #9 FIX: Safe parseInt with NaN check
|
|
835
|
+
*/
|
|
836
|
+
function safeParseInt(input: string, label: string): number {
|
|
837
|
+
const num = parseInt(input, 10);
|
|
838
|
+
if (Number.isNaN(num)) {
|
|
839
|
+
throw new Error(`Invalid number for ${label}: "${input}"`);
|
|
840
|
+
}
|
|
841
|
+
return num;
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
/**
|
|
845
|
+
* #15 FIX: Validate firmware version format
|
|
846
|
+
*/
|
|
847
|
+
function parseVersion(input: string): number[] {
|
|
848
|
+
const parts = input.split('.').map(Number);
|
|
849
|
+
if (parts.length < 2 || parts.length > 4 || parts.some(Number.isNaN)) {
|
|
850
|
+
throw new Error(`Invalid version format: "${input}". Expected format: "4.8.0"`);
|
|
851
|
+
}
|
|
852
|
+
return parts;
|
|
853
|
+
}
|
|
854
|
+
|
|
855
|
+
program.parse();
|