@onekeyfe/hd-core 1.1.31 → 1.1.32-alpha.4

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,531 @@
1
+ import { ERRORS, HardwareErrorCode } from '@onekeyfe/hd-shared';
2
+
3
+ import KaspaSignTransaction from '../src/api/kaspa/KaspaSignTransaction';
4
+
5
+ // Mock the config module to avoid package.json resolution issues
6
+ jest.mock('../src/data/config', () => ({
7
+ getSDKVersion: jest.fn(() => '1.0.0'),
8
+ DEFAULT_DOMAIN: 'https://jssdk.onekey.so/1.0.0/',
9
+ }));
10
+
11
+ jest.mock('../src/data-manager/TransportManager', () => ({
12
+ getMessageVersion: jest.fn(() => 'v2'),
13
+ }));
14
+
15
+ jest.mock('../src/device/Device', () => ({
16
+ Device: jest.fn(),
17
+ }));
18
+
19
+ const PATH = "m/44'/111111'/0'/0/0";
20
+ const CHANGE_PATH = "m/44'/111111'/0'/0/1";
21
+ const SCRIPT = `20${'ab'.repeat(32)}ac`; // schnorr P2PK
22
+ const ECDSA_SCRIPT = `21${'ab'.repeat(33)}ab`; // ECDSA P2PK
23
+ const P2SH_SCRIPT = `aa20${'cd'.repeat(32)}87`; // KRC20-style commit script
24
+ const ADDRESS = 'kaspa:qr0lr4ml9fn3chekrqmjdkergxl93l4wrk3dankcgvjq776s9wn9jkdskewva';
25
+
26
+ const buildInput = (script?: string) => ({
27
+ path: PATH,
28
+ prevTxId: 'aa'.repeat(32),
29
+ outputIndex: 0,
30
+ sequenceNumber: 0,
31
+ output: script !== undefined ? { satoshis: 200000, script } : { satoshis: 200000 },
32
+ });
33
+
34
+ const createMethod = (overrides: Record<string, unknown> = {}) =>
35
+ new KaspaSignTransaction({
36
+ id: 1,
37
+ payload: {
38
+ method: 'kaspaSignTransaction',
39
+ version: 0,
40
+ lockTime: 0,
41
+ inputs: [buildInput(SCRIPT)],
42
+ outputs: [{ satoshis: 100000, script: SCRIPT }],
43
+ ...overrides,
44
+ },
45
+ });
46
+
47
+ const txRequest = (message: Record<string, unknown>) => ({ type: 'KaspaTxRequest', message });
48
+ const signedTx = (signature: string) => ({ type: 'KaspaSignedTx', message: { signature } });
49
+
50
+ describe('KaspaSignTransaction capability flags', () => {
51
+ const CASES: [string, Record<string, unknown>, boolean, boolean][] = [
52
+ ['script only (today callers)', {}, true, false],
53
+ [
54
+ 'address only',
55
+ { inputs: [buildInput()], outputs: [{ satoshis: 1, address: ADDRESS }] },
56
+ false,
57
+ true,
58
+ ],
59
+ [
60
+ 'script + address',
61
+ { outputs: [{ satoshis: 1, script: SCRIPT, address: ADDRESS }] },
62
+ true,
63
+ true,
64
+ ],
65
+ [
66
+ 'ECDSA P2PK scripts',
67
+ {
68
+ inputs: [buildInput(ECDSA_SCRIPT)],
69
+ outputs: [{ satoshis: 1, script: ECDSA_SCRIPT, address: ADDRESS }],
70
+ },
71
+ true,
72
+ true,
73
+ ],
74
+ [
75
+ 'tx payload rules out legacy',
76
+ { outputs: [{ satoshis: 1, script: SCRIPT, address: ADDRESS }], payload: 'aabb' },
77
+ false,
78
+ true,
79
+ ],
80
+ [
81
+ 'non-zero subNetworkID rules out legacy',
82
+ {
83
+ outputs: [{ satoshis: 1, script: SCRIPT, address: ADDRESS }],
84
+ subNetworkID: `01${'0'.repeat(38)}`,
85
+ },
86
+ false,
87
+ true,
88
+ ],
89
+ ['32-zero subNetworkID stays legacy-signable', { subNetworkID: '0'.repeat(32) }, true, false],
90
+ [
91
+ 'non-default sigHashType forces blind signing',
92
+ { outputs: [{ satoshis: 1, script: SCRIPT, address: ADDRESS }], sigHashType: 0x03 },
93
+ true,
94
+ false,
95
+ ],
96
+ [
97
+ 'P2SH input forces blind signing (KRC20 reveal)',
98
+ {
99
+ inputs: [buildInput(P2SH_SCRIPT)],
100
+ outputs: [{ satoshis: 1, script: SCRIPT, address: ADDRESS }],
101
+ },
102
+ true,
103
+ false,
104
+ ],
105
+ [
106
+ 'P2SH output forces blind signing (KRC20 commit)',
107
+ { outputs: [{ satoshis: 1, script: P2SH_SCRIPT, address: ADDRESS }] },
108
+ true,
109
+ false,
110
+ ],
111
+ [
112
+ 'empty-string script treated as absent',
113
+ { inputs: [buildInput('')], outputs: [{ satoshis: 1, script: '', address: ADDRESS }] },
114
+ false,
115
+ true,
116
+ ],
117
+ ];
118
+
119
+ it.each(CASES)('%s', (_name, overrides, legacy, streaming) => {
120
+ const method = createMethod(overrides);
121
+ method.init();
122
+
123
+ expect([method.supportsLegacy, method.supportsStreaming]).toEqual([legacy, streaming]);
124
+ });
125
+
126
+ it('rejects malformed refTxs entries at init', () => {
127
+ expect(() =>
128
+ createMethod({ refTxs: [{ version: 0, inputs: [], outputs: [] }] }).init()
129
+ ).toThrow('txId');
130
+ });
131
+
132
+ it('throws when neither protocol fits', () => {
133
+ expect(() => createMethod({ outputs: [{ satoshis: 1 }] }).init()).toThrow(
134
+ 'outputs require either address/addressN'
135
+ );
136
+ });
137
+ });
138
+
139
+ describe('KaspaSignTransaction protocol negotiation', () => {
140
+ let mockTypedCall: jest.Mock;
141
+ let mockDevice: any;
142
+
143
+ beforeEach(() => {
144
+ mockTypedCall = jest.fn();
145
+ mockDevice = { commands: { typedCall: mockTypedCall } };
146
+ });
147
+
148
+ const runMethod = (overrides: Record<string, unknown> = {}) => {
149
+ const method = createMethod(overrides);
150
+ method.device = mockDevice;
151
+ method.init();
152
+ return method.run();
153
+ };
154
+
155
+ it('streaming: superset first packet, device-driven flow, real message bodies', async () => {
156
+ mockTypedCall
157
+ .mockResolvedValueOnce(txRequest({ request_type: 'KASPA_TX_INPUT', request_index: 0 }))
158
+ .mockResolvedValueOnce(txRequest({ request_type: 'KASPA_TX_OUTPUT', request_index: 0 }))
159
+ .mockResolvedValueOnce(txRequest({ request_type: 'KASPA_TX_OUTPUT', request_index: 1 }))
160
+ .mockResolvedValueOnce(
161
+ txRequest({
162
+ request_type: 'KASPA_TX_FINISHED',
163
+ signature: { signature_index: 0, signature: 'deadbeef' },
164
+ })
165
+ );
166
+
167
+ // Production shape: string amounts / sequence / lockTime.
168
+ const result = await runMethod({
169
+ lockTime: '0',
170
+ inputs: [
171
+ {
172
+ path: PATH,
173
+ prevTxId: 'aa'.repeat(32),
174
+ outputIndex: 1,
175
+ sequenceNumber: '0',
176
+ output: { satoshis: '990096458', script: SCRIPT },
177
+ },
178
+ ],
179
+ outputs: [
180
+ { satoshis: '100000000', script: SCRIPT, address: ADDRESS },
181
+ // Change outputs also need script for the tx to stay legacy-signable.
182
+ { satoshis: '890094182', script: SCRIPT, addressN: CHANGE_PATH },
183
+ ],
184
+ // Without refTxs a legacy-capable tx prefers blind signing instead.
185
+ refTxs: [
186
+ {
187
+ txId: 'aa'.repeat(32),
188
+ version: 0,
189
+ inputs: [],
190
+ outputs: [{ satoshis: '990096458', script: SCRIPT }],
191
+ },
192
+ ],
193
+ });
194
+
195
+ expect(result).toEqual([{ index: 0, signature: 'deadbeef' }]);
196
+
197
+ // Superset first packet: legacy prehash plus streaming metadata.
198
+ const [type, resTypes, first] = mockTypedCall.mock.calls[0];
199
+ expect(type).toBe('KaspaSignTx');
200
+ expect(resTypes).toEqual(['KaspaTxRequest', 'KaspaTxInputRequest', 'KaspaSignedTx']);
201
+ expect(typeof first.raw_message).toBe('string');
202
+ expect(first).toMatchObject({ input_count: 1, output_count: 2, payload_length: 0 });
203
+
204
+ expect(mockTypedCall.mock.calls[1][0]).toBe('KaspaTxAckInput');
205
+ expect(mockTypedCall.mock.calls[1][2]).toMatchObject({
206
+ previous_outpoint: { tx_id: 'aa'.repeat(32), index: 1 },
207
+ amount: '990096458',
208
+ sequence: '0',
209
+ sig_op_count: 1,
210
+ script_type: 'KASPA_SPEND_P2PK_SCHNORR',
211
+ });
212
+ expect(mockTypedCall.mock.calls[2][0]).toBe('KaspaTxAckOutput');
213
+ expect(mockTypedCall.mock.calls[2][2]).toMatchObject({
214
+ script_type: 'KASPA_PAYTOADDRESS',
215
+ amount: '100000000',
216
+ address: ADDRESS,
217
+ address_n: [],
218
+ });
219
+ const changeAck = mockTypedCall.mock.calls[3][2];
220
+ expect(changeAck.script_type).toBe('KASPA_PAYTOCHANGE');
221
+ expect(changeAck.address).toBeUndefined();
222
+ expect(changeAck.address_n.every((n: unknown) => typeof n === 'number')).toBe(true);
223
+ });
224
+
225
+ it('legacy: plain packet without streaming fields, input-by-input loop', async () => {
226
+ mockTypedCall
227
+ .mockResolvedValueOnce({
228
+ type: 'KaspaTxInputRequest',
229
+ message: { request_index: 1, signature: 'sig0' },
230
+ })
231
+ .mockResolvedValueOnce(signedTx('sig1'));
232
+
233
+ const result = await runMethod({ inputs: [buildInput(SCRIPT), buildInput(SCRIPT)] });
234
+
235
+ expect(result).toEqual([
236
+ { index: 0, signature: 'sig0' },
237
+ { index: 1, signature: 'sig1' },
238
+ ]);
239
+
240
+ // No output_count → new firmware falls back to blind signing too.
241
+ const first = mockTypedCall.mock.calls[0][2];
242
+ expect(typeof first.raw_message).toBe('string');
243
+ expect(first.output_count).toBeUndefined();
244
+ expect(mockTypedCall.mock.calls[1][0]).toBe('KaspaTxInputAck');
245
+ expect(typeof mockTypedCall.mock.calls[1][2].raw_message).toBe('string');
246
+ });
247
+
248
+ it('prefers blind signing when refTxs is absent on a legacy-capable tx', async () => {
249
+ mockTypedCall.mockResolvedValueOnce(signedTx('sig0'));
250
+
251
+ const result = await runMethod({
252
+ outputs: [{ satoshis: 100000, script: SCRIPT, address: ADDRESS }],
253
+ });
254
+
255
+ expect(result).toEqual([{ index: 0, signature: 'sig0' }]);
256
+
257
+ // Streaming needs prev-tx data the caller did not provide: send the plain
258
+ // legacy packet so every firmware generation blind-signs instead.
259
+ const first = mockTypedCall.mock.calls[0][2];
260
+ expect(first.output_count).toBeUndefined();
261
+ expect(typeof first.raw_message).toBe('string');
262
+ });
263
+
264
+ it('KRC20-style P2SH input blind-signs even with addresses present', async () => {
265
+ mockTypedCall.mockResolvedValueOnce(signedTx('sig0'));
266
+
267
+ const result = await runMethod({
268
+ inputs: [buildInput(P2SH_SCRIPT)],
269
+ outputs: [{ satoshis: 100000, script: SCRIPT, address: ADDRESS }],
270
+ });
271
+
272
+ expect(result).toEqual([{ index: 0, signature: 'sig0' }]);
273
+ expect(mockTypedCall.mock.calls[0][2].output_count).toBeUndefined();
274
+ });
275
+
276
+ it('collects out-of-order streaming signatures with the last one on FINISHED', async () => {
277
+ mockTypedCall
278
+ .mockResolvedValueOnce(txRequest({ request_type: 'KASPA_TX_INPUT', request_index: 1 }))
279
+ .mockResolvedValueOnce(
280
+ txRequest({
281
+ request_type: 'KASPA_TX_INPUT',
282
+ request_index: 0,
283
+ signature: { signature_index: 1, signature: 'sig1' },
284
+ })
285
+ )
286
+ .mockResolvedValueOnce(txRequest({ request_type: 'KASPA_TX_OUTPUT', request_index: 0 }))
287
+ .mockResolvedValueOnce(
288
+ txRequest({
289
+ request_type: 'KASPA_TX_FINISHED',
290
+ signature: { signature_index: 0, signature: 'sig0' },
291
+ })
292
+ );
293
+
294
+ const result = await runMethod({
295
+ inputs: [buildInput(SCRIPT), buildInput(SCRIPT)],
296
+ outputs: [{ satoshis: 100000, script: SCRIPT, address: ADDRESS }],
297
+ });
298
+
299
+ expect(result).toEqual([
300
+ { index: 0, signature: 'sig0' },
301
+ { index: 1, signature: 'sig1' },
302
+ ]);
303
+ });
304
+
305
+ it('streams the payload in byte-offset chunks', async () => {
306
+ mockTypedCall
307
+ .mockResolvedValueOnce(
308
+ txRequest({ request_type: 'KASPA_TX_PAYLOAD', request_index: 0, request_payload_length: 4 })
309
+ )
310
+ .mockResolvedValueOnce(
311
+ txRequest({ request_type: 'KASPA_TX_PAYLOAD', request_index: 4, request_payload_length: 2 })
312
+ )
313
+ .mockResolvedValueOnce(txRequest({ request_type: 'KASPA_TX_INPUT', request_index: 0 }))
314
+ .mockResolvedValueOnce(
315
+ txRequest({
316
+ request_type: 'KASPA_TX_FINISHED',
317
+ signature: { signature_index: 0, signature: 'sig0' },
318
+ })
319
+ );
320
+
321
+ const result = await runMethod({
322
+ inputs: [buildInput()],
323
+ outputs: [{ satoshis: 100000, address: ADDRESS }],
324
+ payload: 'aabbccddeeff',
325
+ });
326
+
327
+ expect(result).toEqual([{ index: 0, signature: 'sig0' }]);
328
+ expect(mockTypedCall.mock.calls[0][2].payload_length).toBe(6);
329
+ expect(mockTypedCall.mock.calls[1][2]).toEqual({ payload_chunk: 'aabbccdd' });
330
+ expect(mockTypedCall.mock.calls[2][2]).toEqual({ payload_chunk: 'eeff' });
331
+ });
332
+
333
+ it('maps the old-firmware decode failure to 407 but keeps other errors intact', async () => {
334
+ const streamingOnly = {
335
+ inputs: [buildInput()],
336
+ outputs: [{ satoshis: 100000, address: ADDRESS }],
337
+ };
338
+
339
+ // Old firmware cannot decode a packet without raw_message → actionable 407.
340
+ mockTypedCall.mockRejectedValueOnce(
341
+ ERRORS.TypedError(
342
+ HardwareErrorCode.RuntimeError,
343
+ 'Failure_DataError,Failed to decode message'
344
+ )
345
+ );
346
+ await expect(runMethod(streamingOnly)).rejects.toMatchObject({
347
+ errorCode: HardwareErrorCode.CallMethodNeedUpgradeFirmware,
348
+ });
349
+ // A streaming-only tx must not offer a (wrong) legacy prehash.
350
+ expect(mockTypedCall.mock.calls[0][2].raw_message).toBeUndefined();
351
+
352
+ // User cancellation must pass through untouched.
353
+ mockTypedCall.mockRejectedValueOnce(ERRORS.TypedError(HardwareErrorCode.ActionCancelled));
354
+ await expect(runMethod(streamingOnly)).rejects.toMatchObject({
355
+ errorCode: HardwareErrorCode.ActionCancelled,
356
+ });
357
+ });
358
+
359
+ it('fails cleanly on protocol violations from the device', async () => {
360
+ // Streams against a legacy-only packet (no output_count sent).
361
+ mockTypedCall.mockResolvedValueOnce(
362
+ txRequest({ request_type: 'KASPA_TX_INPUT', request_index: 0 })
363
+ );
364
+ await expect(runMethod()).rejects.toMatchObject({
365
+ errorCode: HardwareErrorCode.CallMethodInvalidParameter,
366
+ });
367
+
368
+ // Answers legacy against a streaming-only packet (no prehash material).
369
+ mockTypedCall.mockResolvedValueOnce({
370
+ type: 'KaspaTxInputRequest',
371
+ message: { request_index: 1 },
372
+ });
373
+ await expect(
374
+ runMethod({ inputs: [buildInput()], outputs: [{ satoshis: 100000, address: ADDRESS }] })
375
+ ).rejects.toMatchObject({ errorCode: HardwareErrorCode.RuntimeError });
376
+
377
+ const superset = { outputs: [{ satoshis: 100000, script: SCRIPT, address: ADDRESS }] };
378
+
379
+ // Out-of-range input index.
380
+ mockTypedCall.mockResolvedValueOnce(
381
+ txRequest({ request_type: 'KASPA_TX_INPUT', request_index: 5 })
382
+ );
383
+ await expect(runMethod(superset)).rejects.toMatchObject({
384
+ errorCode: HardwareErrorCode.RuntimeError,
385
+ });
386
+
387
+ // Fewer signatures than inputs.
388
+ mockTypedCall.mockResolvedValueOnce(txRequest({ request_type: 'KASPA_TX_FINISHED' }));
389
+ await expect(runMethod(superset)).rejects.toMatchObject({
390
+ errorCode: HardwareErrorCode.RuntimeError,
391
+ });
392
+
393
+ // Previous-transaction request without the matching refTxs entry: must
394
+ // fail clearly, never answered with current-tx data.
395
+ mockTypedCall.mockResolvedValueOnce(
396
+ txRequest({ request_type: 'KASPA_TX_INPUT', request_index: 0, prev_tx_id: 'bb'.repeat(32) })
397
+ );
398
+ await expect(runMethod(superset)).rejects.toMatchObject({
399
+ errorCode: HardwareErrorCode.CallMethodInvalidParameter,
400
+ });
401
+ });
402
+
403
+ it('answers previous-transaction requests from refTxs', async () => {
404
+ const PREV_ID = 'bb'.repeat(32);
405
+ mockTypedCall
406
+ .mockResolvedValueOnce(txRequest({ request_type: 'KASPA_TX_PREV_META', prev_tx_id: PREV_ID }))
407
+ .mockResolvedValueOnce(
408
+ txRequest({ request_type: 'KASPA_TX_OUTPUT', request_index: 0, prev_tx_id: PREV_ID })
409
+ )
410
+ .mockResolvedValueOnce(
411
+ txRequest({ request_type: 'KASPA_TX_INPUT', request_index: 0, prev_tx_id: PREV_ID })
412
+ )
413
+ .mockResolvedValueOnce(txRequest({ request_type: 'KASPA_TX_INPUT', request_index: 0 }))
414
+ .mockResolvedValueOnce(
415
+ txRequest({
416
+ request_type: 'KASPA_TX_FINISHED',
417
+ signature: { signature_index: 0, signature: 'sig0' },
418
+ })
419
+ );
420
+
421
+ const result = await runMethod({
422
+ outputs: [{ satoshis: 100000, script: SCRIPT, address: ADDRESS }],
423
+ refTxs: [
424
+ {
425
+ txId: PREV_ID,
426
+ version: 0,
427
+ inputs: [{ prevTxId: 'cc'.repeat(32), outputIndex: 2, sequenceNumber: 0 }],
428
+ outputs: [{ satoshis: '200000', script: SCRIPT }],
429
+ },
430
+ ],
431
+ });
432
+
433
+ expect(result).toEqual([{ index: 0, signature: 'sig0' }]);
434
+
435
+ expect(mockTypedCall.mock.calls[1][0]).toBe('KaspaTxAckPrevMeta');
436
+ expect(mockTypedCall.mock.calls[1][2]).toMatchObject({
437
+ version: 0,
438
+ input_count: 1,
439
+ output_count: 1,
440
+ lock_time: 0,
441
+ payload_length: 0,
442
+ });
443
+ expect(mockTypedCall.mock.calls[2][0]).toBe('KaspaTxAckPrevOutput');
444
+ expect(mockTypedCall.mock.calls[2][2]).toMatchObject({
445
+ amount: '200000',
446
+ script_version: 0,
447
+ script_public_key: SCRIPT,
448
+ });
449
+ expect(mockTypedCall.mock.calls[3][0]).toBe('KaspaTxAckPrevInput');
450
+ expect(mockTypedCall.mock.calls[3][2]).toMatchObject({
451
+ previous_outpoint: { tx_id: 'cc'.repeat(32), index: 2 },
452
+ sequence: 0,
453
+ });
454
+ // Back to the current transaction after the previous one is streamed.
455
+ expect(mockTypedCall.mock.calls[4][0]).toBe('KaspaTxAckInput');
456
+ });
457
+ });
458
+
459
+ describe('KaspaSignTransaction wire encoding', () => {
460
+ // Encode through the real protobuf descriptor to lock in the two facts the
461
+ // mocked tests cannot prove: unset optional fields stay off the wire (the
462
+ // output_count protocol discriminator), and string uint64 values encode.
463
+ // eslint-disable-next-line @typescript-eslint/no-var-requires, global-require
464
+ const protobuf = require('protobufjs');
465
+ // eslint-disable-next-line @typescript-eslint/no-var-requires, global-require
466
+ const messagesJson = require('../src/data/messages/messages.json');
467
+
468
+ it('unset streaming fields stay off the wire; string uint64 values encode', () => {
469
+ const root = protobuf.Root.fromJSON(messagesJson);
470
+ const KaspaSignTx = root.lookupType('KaspaSignTx');
471
+
472
+ const wireFieldIds = (buf: Uint8Array) => {
473
+ const reader = protobuf.Reader.create(buf);
474
+ const ids: number[] = [];
475
+ while (reader.pos < reader.len) {
476
+ const tag = reader.uint32();
477
+ // eslint-disable-next-line no-bitwise
478
+ ids.push(tag >>> 3);
479
+ // eslint-disable-next-line no-bitwise
480
+ reader.skipType(tag & 7);
481
+ }
482
+ return ids;
483
+ };
484
+
485
+ const base = {
486
+ address_n: [2147483692, 2147483759, 2147483648, 0, 0],
487
+ scheme: 'schnorr',
488
+ prefix: 'kaspa',
489
+ input_count: 1,
490
+ };
491
+
492
+ const legacyIds = wireFieldIds(
493
+ KaspaSignTx.encode(
494
+ KaspaSignTx.fromObject({ ...base, raw_message: Buffer.from('aabbcc', 'hex') })
495
+ ).finish()
496
+ );
497
+ expect(legacyIds).toContain(2); // raw_message present
498
+ expect(legacyIds.filter((id: number) => id >= 7)).toEqual([]); // no streaming fields
499
+
500
+ const streamingIds = wireFieldIds(
501
+ KaspaSignTx.encode(
502
+ KaspaSignTx.fromObject({ ...base, output_count: 2, lock_time: '0', gas: '0' })
503
+ ).finish()
504
+ );
505
+ expect(streamingIds).toContain(7); // output_count — the protocol discriminator
506
+ expect(streamingIds).not.toContain(2); // no raw_message
507
+ });
508
+
509
+ it('decodes enum fields to string names via the real transport decoder', () => {
510
+ // signTxStream compares request_type against string literals
511
+ // ('KASPA_TX_OUTPUT', ...); pin the decoder behavior that guarantees it.
512
+ // eslint-disable-next-line @typescript-eslint/no-var-requires, global-require
513
+ const { decode } = require('../../hd-transport/src/serialization/protobuf/decode');
514
+ // eslint-disable-next-line @typescript-eslint/no-var-requires, global-require
515
+ const ByteBuffer = require('bytebuffer');
516
+
517
+ const root = protobuf.Root.fromJSON(messagesJson);
518
+ const KaspaTxRequest = root.lookupType('KaspaTxRequest');
519
+
520
+ // Enums travel as numbers on the wire (KASPA_TX_OUTPUT = 1).
521
+ const encoded = KaspaTxRequest.encode(
522
+ KaspaTxRequest.fromObject({ request_type: 1, request_index: 0 })
523
+ ).finish();
524
+
525
+ const message = decode(KaspaTxRequest, ByteBuffer.wrap(Buffer.from(encoded)));
526
+ expect(message.request_type).toBe('KASPA_TX_OUTPUT');
527
+ // Absent optional sub-message must decode to null, not crash the decoder
528
+ // (the device's first request legitimately carries no signature).
529
+ expect(message.signature).toBeNull();
530
+ });
531
+ });
@@ -1,9 +1,11 @@
1
1
  import { BaseMethod } from '../BaseMethod';
2
2
  import type { TypedResponseMessage } from '../../device/DeviceCommands';
3
3
  import type { KaspaSignTransactionParams, KaspaSignature } from '../../types';
4
- import type { TypedCall } from '@onekeyfe/hd-transport';
4
+ import type { KaspaTxRequest, TypedCall } from '@onekeyfe/hd-transport';
5
5
  export default class KaspaSignTransaction extends BaseMethod<KaspaSignTransactionParams> {
6
6
  hasBundle: boolean;
7
+ supportsLegacy: boolean;
8
+ supportsStreaming: boolean;
7
9
  init(): void;
8
10
  getVersionRange(): {
9
11
  model_mini: {
@@ -22,6 +24,8 @@ export default class KaspaSignTransaction extends BaseMethod<KaspaSignTransactio
22
24
  };
23
25
  };
24
26
  processTxRequest(typedCall: TypedCall, res: TypedResponseMessage<'KaspaTxInputRequest'> | TypedResponseMessage<'KaspaSignedTx'>, index: number, signature: KaspaSignature[]): Promise<KaspaSignature[]>;
27
+ ackPrevRequest(typedCall: TypedCall, request: KaspaTxRequest, requestIndex: number): Promise<import("@onekeyfe/hd-transport").MessageResponse<"KaspaTxRequest">>;
28
+ signTxStream(typedCall: TypedCall, firstResponse: TypedResponseMessage<'KaspaTxRequest'>): Promise<KaspaSignature[]>;
25
29
  run(): Promise<KaspaSignature[]>;
26
30
  }
27
31
  //# sourceMappingURL=KaspaSignTransaction.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"KaspaSignTransaction.d.ts","sourceRoot":"","sources":["../../../src/api/kaspa/KaspaSignTransaction.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAK3C,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,6BAA6B,CAAC;AACxE,OAAO,KAAK,EAGV,0BAA0B,EAC1B,cAAc,EACf,MAAM,aAAa,CAAC;AACrB,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AAExD,MAAM,CAAC,OAAO,OAAO,oBAAqB,SAAQ,UAAU,CAAC,0BAA0B,CAAC;IACtF,SAAS,UAAS;IAElB,IAAI;IAiEJ,eAAe;;;;;;;;IAWf,uBAAuB;;;;;;;;IAWjB,gBAAgB,CACpB,SAAS,EAAE,SAAS,EACpB,GAAG,EAAE,oBAAoB,CAAC,qBAAqB,CAAC,GAAG,oBAAoB,CAAC,eAAe,CAAC,EACxF,KAAK,EAAE,MAAM,EACb,SAAS,EAAE,cAAc,EAAE,GAC1B,OAAO,CAAC,cAAc,EAAE,CAAC;IAsCtB,GAAG;CA+BV"}
1
+ {"version":3,"file":"KaspaSignTransaction.d.ts","sourceRoot":"","sources":["../../../src/api/kaspa/KaspaSignTransaction.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAK3C,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,6BAA6B,CAAC;AACxE,OAAO,KAAK,EAGV,0BAA0B,EAC1B,cAAc,EACf,MAAM,aAAa,CAAC;AACrB,OAAO,KAAK,EAEV,cAAc,EAEd,SAAS,EACV,MAAM,wBAAwB,CAAC;AAQhC,MAAM,CAAC,OAAO,OAAO,oBAAqB,SAAQ,UAAU,CAAC,0BAA0B,CAAC;IACtF,SAAS,UAAS;IAGlB,cAAc,UAAS;IAEvB,iBAAiB,UAAS;IAE1B,IAAI;IA4GJ,eAAe;;;;;;;;IAWf,uBAAuB;;;;;;;;IAejB,gBAAgB,CACpB,SAAS,EAAE,SAAS,EACpB,GAAG,EAAE,oBAAoB,CAAC,qBAAqB,CAAC,GAAG,oBAAoB,CAAC,eAAe,CAAC,EACxF,KAAK,EAAE,MAAM,EACb,SAAS,EAAE,cAAc,EAAE,GAC1B,OAAO,CAAC,cAAc,EAAE,CAAC;IA0CtB,cAAc,CAAC,SAAS,EAAE,SAAS,EAAE,OAAO,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM;IA8ElF,YAAY,CAChB,SAAS,EAAE,SAAS,EACpB,aAAa,EAAE,oBAAoB,CAAC,gBAAgB,CAAC,GACpD,OAAO,CAAC,cAAc,EAAE,CAAC;IAmGtB,GAAG;CA4FV"}