@onekeyfe/hd-core 1.2.0-alpha.35 → 1.2.0-alpha.37

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.
Files changed (100) hide show
  1. package/__tests__/AllNetworkGetAddressBase.tracing.test.ts +78 -0
  2. package/__tests__/DeviceEventRegistration.test.ts +6 -0
  3. package/__tests__/deviceUploadNft.test.ts +187 -0
  4. package/__tests__/get-device-state.test.ts +104 -12
  5. package/__tests__/homescreen.test.ts +41 -1
  6. package/__tests__/logBlockEvent.test.ts +1 -1
  7. package/__tests__/open-wallet-session.test.ts +259 -18
  8. package/__tests__/pro2Nft.test.ts +108 -0
  9. package/__tests__/protocol-v2-ui-lifecycle.test.ts +56 -0
  10. package/__tests__/protocol-v2-unlock-policy.test.ts +86 -35
  11. package/__tests__/protocol-v2.test.ts +377 -402
  12. package/__tests__/public-pro2-api-boundary.test.ts +1 -0
  13. package/dist/api/BaseMethod.d.ts +3 -1
  14. package/dist/api/BaseMethod.d.ts.map +1 -1
  15. package/dist/api/FirmwareUpdateV4.d.ts.map +1 -1
  16. package/dist/api/GetPassphraseState.d.ts.map +1 -1
  17. package/dist/api/OpenWalletSession.d.ts.map +1 -1
  18. package/dist/api/allnetwork/AllNetworkGetAddressBase.d.ts.map +1 -1
  19. package/dist/api/helpers/protocolV2FileWrite.d.ts +2 -0
  20. package/dist/api/helpers/protocolV2FileWrite.d.ts.map +1 -1
  21. package/dist/api/index.d.ts +1 -0
  22. package/dist/api/index.d.ts.map +1 -1
  23. package/dist/api/protocol-v2/DeviceUploadNft.d.ts +30 -0
  24. package/dist/api/protocol-v2/DeviceUploadNft.d.ts.map +1 -0
  25. package/dist/api/protocol-v2/ProtocolInfoRequest.d.ts.map +1 -1
  26. package/dist/core/deviceEventRegistration.d.ts +2 -0
  27. package/dist/core/deviceEventRegistration.d.ts.map +1 -1
  28. package/dist/core/index.d.ts.map +1 -1
  29. package/dist/device/Device.d.ts +22 -4
  30. package/dist/device/Device.d.ts.map +1 -1
  31. package/dist/events/device.d.ts +4 -0
  32. package/dist/events/device.d.ts.map +1 -1
  33. package/dist/events/logBlockEvent.d.ts.map +1 -1
  34. package/dist/events/ui-request.d.ts +27 -2
  35. package/dist/events/ui-request.d.ts.map +1 -1
  36. package/dist/index.d.ts +88 -7
  37. package/dist/index.js +937 -604
  38. package/dist/inject.d.ts.map +1 -1
  39. package/dist/protocols/protocol-v2/features.d.ts.map +1 -1
  40. package/dist/protocols/protocol-v2/uiInteraction.d.ts +3 -3
  41. package/dist/protocols/protocol-v2/uiInteraction.d.ts.map +1 -1
  42. package/dist/protocols/protocol-v2/unlockPolicy.d.ts +1 -3
  43. package/dist/protocols/protocol-v2/unlockPolicy.d.ts.map +1 -1
  44. package/dist/protocols/protocol-v2/unlockPolicyRunner.d.ts +22 -0
  45. package/dist/protocols/protocol-v2/unlockPolicyRunner.d.ts.map +1 -0
  46. package/dist/protocols/protocol-v2/walletSession.d.ts +4 -1
  47. package/dist/protocols/protocol-v2/walletSession.d.ts.map +1 -1
  48. package/dist/types/api/index.d.ts +2 -1
  49. package/dist/types/api/index.d.ts.map +1 -1
  50. package/dist/types/api/protocolV2.d.ts +3 -0
  51. package/dist/types/api/protocolV2.d.ts.map +1 -1
  52. package/dist/utils/deviceFeaturesUtils.d.ts +2 -0
  53. package/dist/utils/deviceFeaturesUtils.d.ts.map +1 -1
  54. package/dist/utils/homescreen.d.ts +4 -0
  55. package/dist/utils/homescreen.d.ts.map +1 -1
  56. package/dist/utils/index.d.ts +1 -1
  57. package/dist/utils/index.d.ts.map +1 -1
  58. package/dist/utils/patch.d.ts +1 -1
  59. package/dist/utils/patch.d.ts.map +1 -1
  60. package/dist/utils/pro2Nft.d.ts +29 -0
  61. package/dist/utils/pro2Nft.d.ts.map +1 -0
  62. package/dist/utils/pro2Wallpaper.d.ts +10 -0
  63. package/dist/utils/pro2Wallpaper.d.ts.map +1 -1
  64. package/package.json +4 -4
  65. package/src/api/BaseMethod.ts +8 -10
  66. package/src/api/FirmwareUpdateV4.ts +13 -6
  67. package/src/api/GetPassphraseState.ts +7 -1
  68. package/src/api/OpenWalletSession.ts +54 -17
  69. package/src/api/allnetwork/AllNetworkGetAddressBase.ts +32 -11
  70. package/src/api/device/DeviceUnlock.ts +1 -1
  71. package/src/api/helpers/protocolV2FileWrite.ts +11 -4
  72. package/src/api/index.ts +1 -0
  73. package/src/api/protocol-v2/DeviceUploadNft.ts +166 -0
  74. package/src/api/protocol-v2/ProtocolInfoRequest.ts +3 -1
  75. package/src/core/deviceEventRegistration.ts +4 -0
  76. package/src/core/index.ts +111 -74
  77. package/src/data/messages/messages-protocol-v2.json +10 -0
  78. package/src/device/Device.ts +221 -24
  79. package/src/events/device.ts +4 -0
  80. package/src/events/logBlockEvent.ts +1 -0
  81. package/src/events/ui-request.ts +32 -2
  82. package/src/inject.ts +1 -0
  83. package/src/protocols/protocol-v2/features.ts +9 -2
  84. package/src/protocols/protocol-v2/uiInteraction.ts +31 -5
  85. package/src/protocols/protocol-v2/unlockPolicy.ts +1 -105
  86. package/src/protocols/protocol-v2/unlockPolicyRunner.ts +117 -0
  87. package/src/protocols/protocol-v2/walletSession.ts +93 -18
  88. package/src/types/api/index.ts +2 -0
  89. package/src/types/api/protocolV2.ts +13 -0
  90. package/src/utils/deviceFeaturesUtils.ts +4 -0
  91. package/src/utils/homescreen.ts +32 -6
  92. package/src/utils/index.ts +6 -1
  93. package/src/utils/pro2Nft.ts +91 -0
  94. package/src/utils/pro2Wallpaper.ts +35 -8
  95. package/dist/protocols/protocol-v2/lockedError.d.ts +0 -2
  96. package/dist/protocols/protocol-v2/lockedError.d.ts.map +0 -1
  97. package/dist/protocols/protocol-v2/unlockRetry.d.ts +0 -10
  98. package/dist/protocols/protocol-v2/unlockRetry.d.ts.map +0 -1
  99. package/src/protocols/protocol-v2/lockedError.ts +0 -10
  100. package/src/protocols/protocol-v2/unlockRetry.ts +0 -109
@@ -1,4 +1,5 @@
1
1
  import { ERRORS, HardwareErrorCode } from '@onekeyfe/hd-shared';
2
+ import { DeviceSessionPinType } from '@onekeyfe/hd-transport';
2
3
 
3
4
  import { deviceWalletSessionStore } from '../device/DeviceWalletSessionStore';
4
5
  import { getProtocolV2WalletSession } from '../protocols/protocol-v2/walletSession';
@@ -90,23 +91,40 @@ export default class OpenWalletSession extends BaseMethod<OpenWalletSessionParam
90
91
 
91
92
  async run(): Promise<OpenWalletSessionPayload> {
92
93
  const isProtocolV2 = this.device.isProtocolV2();
93
- const state = await this.device.getDeviceState({ refreshSections: ['status'] });
94
+ let state = await this.device.getDeviceState({ refreshSections: ['status'] });
94
95
  let currentDeviceId = state.identity.deviceId;
96
+ const hasAuthoritativeProtocolV2WalletStatus = (candidate: typeof state) =>
97
+ candidate.status.unlocked === true &&
98
+ typeof candidate.status.passphraseProtection === 'boolean' &&
99
+ typeof candidate.status.unlockedAttachPin === 'boolean';
100
+ const requireAuthoritativeProtocolV2WalletStatus = (candidate: typeof state) => {
101
+ if (!hasAuthoritativeProtocolV2WalletStatus(candidate)) {
102
+ throw ERRORS.TypedError(HardwareErrorCode.DeviceInitializeFailed);
103
+ }
104
+ return candidate;
105
+ };
95
106
  const requireDeviceId = () => {
96
107
  if (!currentDeviceId) {
97
108
  throw ERRORS.TypedError(HardwareErrorCode.DeviceInitializeFailed);
98
109
  }
99
110
  return currentDeviceId;
100
111
  };
101
- const refreshProtocolV2DeviceId = async () => {
102
- const refreshedState = await this.device.getDeviceState({ refreshSections: ['status'] });
103
- currentDeviceId = refreshedState.identity.deviceId;
104
- return requireDeviceId();
112
+ const refreshProtocolV2DeviceState = async () => {
113
+ state = await this.device.getDeviceState({ refreshSections: ['status'] });
114
+ currentDeviceId = state.identity.deviceId;
115
+ return state;
105
116
  };
106
- const unlockProtocolV2IfLocked = async () => {
107
- if (isProtocolV2 && state.status.unlocked === false) {
108
- await this.device.unlockDevice();
117
+ const ensureProtocolV2WalletStatus = async () => {
118
+ if (isProtocolV2 && !hasAuthoritativeProtocolV2WalletStatus(state)) {
119
+ await this.device.unlockDevice(DeviceSessionPinType.Main, {
120
+ source: 'unlock-coordinator',
121
+ reason: 'device-locked',
122
+ deviceOnly: true,
123
+ method: 'openWalletSession',
124
+ });
125
+ requireAuthoritativeProtocolV2WalletStatus(await refreshProtocolV2DeviceState());
109
126
  }
127
+ return state;
110
128
  };
111
129
 
112
130
  const protocol = isProtocolV2 ? 'V2' : 'V1';
@@ -114,13 +132,25 @@ export default class OpenWalletSession extends BaseMethod<OpenWalletSessionParam
114
132
  if (this.params.mode === OpenWalletSessionMode.Standard) {
115
133
  this.device.passphraseState = undefined;
116
134
  const session = isProtocolV2
117
- ? await getProtocolV2WalletSession(this.device, { onlyMainPin: true })
135
+ ? await getProtocolV2WalletSession(this.device, {
136
+ onlyMainPin: true,
137
+ deriveCardano: this.payload.deriveCardano,
138
+ selectMainWalletBeforeRestore:
139
+ !hasAuthoritativeProtocolV2WalletStatus(state) ||
140
+ state.status.unlockedAttachPin === true,
141
+ })
118
142
  : await getPassphraseStateWithRefreshDeviceInfo(this.device, {
119
143
  onlyMainPin: true,
120
144
  initSession: this.payload.initSession,
121
145
  });
122
- const deviceId = isProtocolV2 ? await refreshProtocolV2DeviceId() : requireDeviceId();
123
- if (session.unlockedAttachPin) {
146
+ const refreshedState = isProtocolV2
147
+ ? requireAuthoritativeProtocolV2WalletStatus(await refreshProtocolV2DeviceState())
148
+ : state;
149
+ const deviceId = requireDeviceId();
150
+ if (
151
+ session.unlockedAttachPin ||
152
+ (isProtocolV2 && refreshedState.status.unlockedAttachPin === true)
153
+ ) {
124
154
  try {
125
155
  await this.device.lockDevice();
126
156
  } catch {
@@ -140,8 +170,8 @@ export default class OpenWalletSession extends BaseMethod<OpenWalletSessionParam
140
170
 
141
171
  if (this.params.mode === OpenWalletSessionMode.ResumeHidden) {
142
172
  if (isProtocolV2) {
143
- await unlockProtocolV2IfLocked();
144
- const refreshedDeviceId = await refreshProtocolV2DeviceId();
173
+ await ensureProtocolV2WalletStatus();
174
+ const refreshedDeviceId = requireDeviceId();
145
175
  if (refreshedDeviceId !== this.params.deviceId) {
146
176
  deviceWalletSessionStore.delete(this.params.deviceId, this.params.passphraseState);
147
177
  throw ERRORS.TypedError(HardwareErrorCode.DeviceCheckDeviceIdError);
@@ -166,6 +196,7 @@ export default class OpenWalletSession extends BaseMethod<OpenWalletSessionParam
166
196
  const session = isProtocolV2
167
197
  ? await getProtocolV2WalletSession(this.device, {
168
198
  expectedPassphraseState: this.params.passphraseState,
199
+ deriveCardano: this.payload.deriveCardano,
169
200
  })
170
201
  : await getPassphraseStateWithRefreshDeviceInfo(this.device, {
171
202
  expectPassphraseState: this.params.passphraseState,
@@ -185,12 +216,18 @@ export default class OpenWalletSession extends BaseMethod<OpenWalletSessionParam
185
216
  }
186
217
 
187
218
  this.device.passphraseState = undefined;
188
- await unlockProtocolV2IfLocked();
219
+ await ensureProtocolV2WalletStatus();
189
220
  const session = isProtocolV2
190
- ? await getProtocolV2WalletSession(this.device, { forceWalletSelection: true })
221
+ ? await getProtocolV2WalletSession(this.device, {
222
+ forceWalletSelection: true,
223
+ deriveCardano: this.payload.deriveCardano,
224
+ })
191
225
  : await getPassphraseStateWithRefreshDeviceInfo(this.device, { initSession: true });
192
- const deviceId = isProtocolV2 ? await refreshProtocolV2DeviceId() : requireDeviceId();
193
- if (isProtocolV2 && this.device.getCurrentPassphraseProtection() !== true) {
226
+ const refreshedState = isProtocolV2
227
+ ? requireAuthoritativeProtocolV2WalletStatus(await refreshProtocolV2DeviceState())
228
+ : state;
229
+ const deviceId = requireDeviceId();
230
+ if (isProtocolV2 && refreshedState.status.passphraseProtection !== true) {
194
231
  this.device.clearInternalState();
195
232
  throw ERRORS.TypedError(HardwareErrorCode.DeviceNotOpenedPassphrase);
196
233
  }
@@ -14,6 +14,7 @@ import { findMethod } from '../utils';
14
14
  import { DEVICE, IFRAME, createUiMessage } from '../../events';
15
15
  import { UI_REQUEST } from '../../constants/ui-request';
16
16
  import { onDeviceButtonHandler } from '../../core';
17
+ import { runMethodWithUnlockPolicy } from '../../protocols/protocol-v2/unlockPolicyRunner';
17
18
  import {
18
19
  completeRequestContext,
19
20
  createRequestContext,
@@ -375,16 +376,34 @@ export default abstract class AllNetworkGetAddressBase extends BaseMethod<
375
376
  this.device.on(DEVICE.PASSPHRASE, onSignalAbort);
376
377
 
377
378
  preCheckDeviceSupport(this.device, method);
378
- if (this.temporarySafetyCheckPrompted) {
379
- method.temporarySafetyCheckPrompted = true;
380
- } else {
381
- const appliedTemporarySafetyCheck = await method.checkSafetyLevelOnTestNet();
382
- if (appliedTemporarySafetyCheck) {
383
- this.temporarySafetyCheckPrompted = true;
384
- }
385
- }
386
-
387
- const response = await method.run();
379
+ const response = await runMethodWithUnlockPolicy<any[]>(method, this.device, {
380
+ context: this.protocolV2UnlockContext,
381
+ prepare: async () => {
382
+ if (this.temporarySafetyCheckPrompted) {
383
+ method.temporarySafetyCheckPrompted = true;
384
+ } else {
385
+ const appliedTemporarySafetyCheck = await method.checkSafetyLevelOnTestNet();
386
+ if (appliedTemporarySafetyCheck) {
387
+ this.temporarySafetyCheckPrompted = true;
388
+ }
389
+ }
390
+
391
+ // Protocol V2 hands a wallet session to exactly one blockchain request.
392
+ // The parent all-network call consumes its first handoff while fetching
393
+ // the root fingerprint, so each nested chain method must resume the
394
+ // requested hidden wallet before it sends its own device command.
395
+ if (this.device.isProtocolV2() && this.payload.passphraseState) {
396
+ const passphraseStateSafety = await this.device.checkPassphraseStateSafety(
397
+ this.payload.passphraseState,
398
+ false,
399
+ this.payload.skipPassphraseCheck
400
+ );
401
+ if (!passphraseStateSafety) {
402
+ throw ERRORS.TypedError(HardwareErrorCode.DeviceCheckPassphraseStateError);
403
+ }
404
+ }
405
+ },
406
+ });
388
407
 
389
408
  if (!Array.isArray(response) || response.length === 0) {
390
409
  throw new Error('No response');
@@ -439,7 +458,9 @@ export default abstract class AllNetworkGetAddressBase extends BaseMethod<
439
458
  show_display: false,
440
459
  });
441
460
 
442
- this.postMessage(createUiMessage(UI_REQUEST.CLOSE_UI_PIN_WINDOW));
461
+ if (!this.device.isProtocolV2()) {
462
+ this.postMessage(createUiMessage(UI_REQUEST.CLOSE_UI_PIN_WINDOW));
463
+ }
443
464
 
444
465
  if (res.message.root_fingerprint == null) {
445
466
  throw ERRORS.TypedError(HardwareErrorCode.CallMethodInvalidParameter);
@@ -21,6 +21,6 @@ export default class DeviceUnlock extends BaseMethod<LockDevice> {
21
21
  }
22
22
 
23
23
  async run() {
24
- return this.device.unlockDevice();
24
+ return this.device.unlockDevice(undefined, { emitUiEvent: false });
25
25
  }
26
26
  }
@@ -31,19 +31,21 @@ export type ProtocolV2FileWriteOptions = {
31
31
  uiPercentage?: number;
32
32
  timeoutMs?: number;
33
33
  maxChunkRetries?: number;
34
+ paceMs?: number;
34
35
  throwIfAborted?: () => void;
35
36
  onProgress?: (progress: ProtocolV2FileWriteProgress) => void;
36
37
  };
37
38
 
38
39
  const MIN_FILE_CHUNK_SIZE = 64;
39
40
 
40
- function isRetryableFileWriteTimeout(error: unknown) {
41
+ export function isProtocolV2ResponseTimeout(error: unknown) {
41
42
  if (!error || typeof error !== 'object') return false;
42
- const candidate = error as { errorCode?: number; code?: number; message?: string };
43
+ const candidate = error as { errorCode?: number; code?: number | string; message?: string };
43
44
  const code = candidate.errorCode ?? candidate.code;
44
45
  return (
45
46
  code === HardwareErrorCode.BleTimeoutError ||
46
- /Lowlevel response timeout/i.test(candidate.message ?? '')
47
+ code === 'response-timeout' ||
48
+ /(?:BLE|Lowlevel|Protocol V2) response timeout/i.test(candidate.message ?? '')
47
49
  );
48
50
  }
49
51
 
@@ -147,7 +149,7 @@ export async function writeProtocolV2File(options: ProtocolV2FileWriteOptions) {
147
149
  );
148
150
  isWritePending = false;
149
151
  } catch (error) {
150
- if (retryCount >= maxChunkRetries || !isRetryableFileWriteTimeout(error)) throw error;
152
+ if (retryCount >= maxChunkRetries || !isProtocolV2ResponseTimeout(error)) throw error;
151
153
  retryCount += 1;
152
154
  options.throwIfAborted?.();
153
155
  }
@@ -186,6 +188,11 @@ export async function writeProtocolV2File(options: ProtocolV2FileWriteOptions) {
186
188
  elapsedMs > 0 ? Math.round((transferredBytes / elapsedMs) * 1000) : undefined,
187
189
  elapsedMs,
188
190
  });
191
+ if (options.paceMs && options.paceMs > 0) {
192
+ await new Promise(resolve => {
193
+ setTimeout(resolve, options.paceMs);
194
+ });
195
+ }
189
196
  }
190
197
 
191
198
  return {
package/src/api/index.ts CHANGED
@@ -49,6 +49,7 @@ export { default as promptWebDeviceAccess } from './PromptWebDeviceAccess';
49
49
  export { default as deviceReboot } from './protocol-v2/DeviceReboot';
50
50
  export { default as deviceGetOnboardingStatus } from './protocol-v2/DeviceGetOnboardingStatus';
51
51
  export { default as deviceUploadWallpaper } from './protocol-v2/DeviceUploadWallpaper';
52
+ export { default as deviceUploadNft } from './protocol-v2/DeviceUploadNft';
52
53
  export { default as uploadPortfolio } from './UploadPortfolio';
53
54
 
54
55
  export { default as cipherKeyValue } from './CipherKeyValue';
@@ -0,0 +1,166 @@
1
+ import { createDeviceNotSupportMethodError } from '@onekeyfe/hd-shared';
2
+
3
+ import { UI_REQUEST, createUiMessage } from '../../events/ui-request';
4
+ import { supportsProtocolV2Message } from '../../protocols/protocol-v2/features';
5
+ import {
6
+ PRO2_NFT_DEFAULT_CHUNK_SIZE,
7
+ PRO2_NFT_DEFAULT_PACE_MS,
8
+ PRO2_NFT_DEFAULT_TIMEOUT_MS,
9
+ PRO2_NFT_DIRECTORY,
10
+ PRO2_NFT_MAX_CHUNK_SIZE,
11
+ PRO2_NFT_MIN_CHUNK_SIZE,
12
+ type Pro2NftBundle,
13
+ type Pro2NftImage,
14
+ buildPro2NftBundle,
15
+ } from '../../utils/pro2Nft';
16
+ import { BaseMethod } from '../BaseMethod';
17
+ import { invalidParameter } from '../helpers/filesystemValidation';
18
+ import { isProtocolV2ResponseTimeout, writeProtocolV2File } from '../helpers/protocolV2FileWrite';
19
+
20
+ export type DeviceUploadNftParams = {
21
+ image: Pro2NftImage;
22
+ thumbnail: Pro2NftImage;
23
+ title: string;
24
+ subtitle: string;
25
+ timestampMs?: number;
26
+ chunkSize?: number;
27
+ paceMs?: number;
28
+ timeoutMs?: number;
29
+ };
30
+
31
+ export type DeviceUploadNftResponse = {
32
+ basename: string;
33
+ imagePath: string;
34
+ thumbnailPath: string;
35
+ metadataPath: string;
36
+ totalSize: number;
37
+ nftUpdated: true;
38
+ message?: string;
39
+ };
40
+
41
+ const FILESYSTEM_FILE_WRITE_MESSAGE_TYPE = 60805;
42
+ const NFT_UPDATE_MESSAGE_TYPE = 61500;
43
+
44
+ export default class DeviceUploadNft extends BaseMethod<DeviceUploadNftParams> {
45
+ private bundle?: Pro2NftBundle;
46
+
47
+ getSupportedProtocols() {
48
+ return ['V2'] as const;
49
+ }
50
+
51
+ init() {
52
+ const {
53
+ image,
54
+ thumbnail,
55
+ title,
56
+ subtitle,
57
+ timestampMs = Date.now(),
58
+ chunkSize = PRO2_NFT_DEFAULT_CHUNK_SIZE,
59
+ paceMs = PRO2_NFT_DEFAULT_PACE_MS,
60
+ timeoutMs = PRO2_NFT_DEFAULT_TIMEOUT_MS,
61
+ } = this.payload;
62
+ if (
63
+ !Number.isInteger(chunkSize) ||
64
+ chunkSize < PRO2_NFT_MIN_CHUNK_SIZE ||
65
+ chunkSize > PRO2_NFT_MAX_CHUNK_SIZE
66
+ ) {
67
+ throw invalidParameter(
68
+ `Parameter [chunkSize] must be an integer between ${PRO2_NFT_MIN_CHUNK_SIZE} and ${PRO2_NFT_MAX_CHUNK_SIZE}.`
69
+ );
70
+ }
71
+ if (!Number.isInteger(paceMs) || paceMs < 0) {
72
+ throw invalidParameter('Parameter [paceMs] must be a non-negative integer.');
73
+ }
74
+ if (!Number.isInteger(timeoutMs) || timeoutMs <= 0) {
75
+ throw invalidParameter('Parameter [timeoutMs] must be a positive integer.');
76
+ }
77
+
78
+ this.bundle = buildPro2NftBundle({ image, thumbnail, title, subtitle, timestampMs });
79
+ this.params = { image, thumbnail, title, subtitle, timestampMs, chunkSize, paceMs, timeoutMs };
80
+ this.unlockPolicy = 'none';
81
+ this.skipForceUpdateCheck = true;
82
+ this.useDevicePassphraseState = false;
83
+ }
84
+
85
+ private async assertCapabilities() {
86
+ const protocolInfo = await this.device.ensureProtocolV2RuntimeContext();
87
+ const hasFileWrite = supportsProtocolV2Message(
88
+ protocolInfo,
89
+ FILESYSTEM_FILE_WRITE_MESSAGE_TYPE
90
+ );
91
+ const hasNftUpdate = supportsProtocolV2Message(protocolInfo, NFT_UPDATE_MESSAGE_TYPE);
92
+ if (!hasFileWrite || !hasNftUpdate) {
93
+ throw createDeviceNotSupportMethodError(this.name, this.device.getCurrentFirmwareType());
94
+ }
95
+ }
96
+
97
+ private async updateNft(basename: string) {
98
+ const params = { file_name_no_ext: basename };
99
+ const options = { timeoutMs: this.params.timeoutMs };
100
+ try {
101
+ return await this.device.commands.typedCall('NftUpdate', 'Success', params, options);
102
+ } catch (error) {
103
+ if (!isProtocolV2ResponseTimeout(error)) {
104
+ throw error;
105
+ }
106
+ this.throwIfAborted();
107
+ return this.device.commands.typedCall('NftUpdate', 'Success', params, options);
108
+ }
109
+ }
110
+
111
+ async run(): Promise<DeviceUploadNftResponse> {
112
+ const { bundle } = this;
113
+ if (!bundle) throw invalidParameter('NFT data has not been initialized.');
114
+
115
+ await this.assertCapabilities();
116
+
117
+ const files = [
118
+ { path: `${PRO2_NFT_DIRECTORY}/${bundle.basename}.bin`, data: bundle.image },
119
+ { path: `${PRO2_NFT_DIRECTORY}/${bundle.basename}_m.bin`, data: bundle.thumbnail },
120
+ { path: `${PRO2_NFT_DIRECTORY}/${bundle.basename}.json`, data: bundle.metadata },
121
+ ];
122
+ const totalSize = files.reduce((sum, file) => sum + file.data.byteLength, 0);
123
+ let transferredBeforeFile = 0;
124
+
125
+ for (const file of files) {
126
+ const transferredAtFileStart = transferredBeforeFile;
127
+ await writeProtocolV2File({
128
+ commands: this.device.commands,
129
+ path: file.path,
130
+ data: file.data,
131
+ totalSize: file.data.byteLength,
132
+ chunkSize: this.params.chunkSize,
133
+ timeoutMs: this.params.timeoutMs,
134
+ paceMs: this.params.paceMs,
135
+ overwrite: true,
136
+ append: false,
137
+ throwIfAborted: () => this.throwIfAborted(),
138
+ onProgress: progress => {
139
+ if (typeof this.postMessage !== 'function') return;
140
+ const transferredBytes = transferredAtFileStart + progress.transferredBytes;
141
+ this.postMessage(
142
+ createUiMessage(UI_REQUEST.DEVICE_PROGRESS, {
143
+ ...progress,
144
+ progress: Math.floor((transferredBytes / totalSize) * 100),
145
+ transferredBytes,
146
+ totalBytes: totalSize,
147
+ })
148
+ );
149
+ },
150
+ });
151
+ transferredBeforeFile += file.data.byteLength;
152
+ }
153
+
154
+ this.throwIfAborted();
155
+ const response = await this.updateNft(bundle.basename);
156
+ return {
157
+ basename: bundle.basename,
158
+ imagePath: files[0].path,
159
+ thumbnailPath: files[1].path,
160
+ metadataPath: files[2].path,
161
+ totalSize,
162
+ nftUpdated: true,
163
+ message: response.message?.message,
164
+ };
165
+ }
166
+ }
@@ -13,7 +13,9 @@ export default class ProtocolInfoRequest extends BaseMethod {
13
13
  }
14
14
 
15
15
  async run() {
16
- const res = await this.device.commands.typedCall('ProtocolInfoRequest', 'ProtocolInfo', {});
16
+ const res = await this.device.commands.typedCall('ProtocolInfoRequest', 'ProtocolInfo', {
17
+ eventless_wallet_session: true,
18
+ });
17
19
  return Promise.resolve(res.message);
18
20
  }
19
21
  }
@@ -4,6 +4,8 @@ import type { Device, DeviceEvents } from '../device/Device';
4
4
 
5
5
  export type HardwareUiEventHandlers = {
6
6
  pin: (...event: DeviceEvents[typeof DEVICE.PIN]) => void;
7
+ pinOnDevice: (...event: DeviceEvents[typeof DEVICE.PIN_ON_DEVICE]) => void;
8
+ pinOnDeviceComplete: (...event: DeviceEvents[typeof DEVICE.PIN_ON_DEVICE_COMPLETE]) => void;
7
9
  button: (...event: DeviceEvents[typeof DEVICE.BUTTON]) => void;
8
10
  passphrase: (...event: DeviceEvents[typeof DEVICE.PASSPHRASE]) => void;
9
11
  passphraseOnDevice: (...event: DeviceEvents[typeof DEVICE.PASSPHRASE_ON_DEVICE]) => void;
@@ -15,6 +17,8 @@ export function registerHardwareUiEventListeners(
15
17
  handlers: HardwareUiEventHandlers
16
18
  ) {
17
19
  device.on(DEVICE.PIN, handlers.pin);
20
+ device.on(DEVICE.PIN_ON_DEVICE, handlers.pinOnDevice);
21
+ device.on(DEVICE.PIN_ON_DEVICE_COMPLETE, handlers.pinOnDeviceComplete);
18
22
  device.on(DEVICE.BUTTON, handlers.button);
19
23
  device.on(DEVICE.PASSPHRASE, handlers.passphrase);
20
24
  device.on(DEVICE.PASSPHRASE_ON_DEVICE, handlers.passphraseOnDevice);