@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.
- package/__tests__/AllNetworkGetAddressBase.tracing.test.ts +78 -0
- package/__tests__/DeviceEventRegistration.test.ts +6 -0
- package/__tests__/deviceUploadNft.test.ts +187 -0
- package/__tests__/get-device-state.test.ts +104 -12
- package/__tests__/homescreen.test.ts +41 -1
- package/__tests__/logBlockEvent.test.ts +1 -1
- package/__tests__/open-wallet-session.test.ts +259 -18
- package/__tests__/pro2Nft.test.ts +108 -0
- package/__tests__/protocol-v2-ui-lifecycle.test.ts +56 -0
- package/__tests__/protocol-v2-unlock-policy.test.ts +86 -35
- package/__tests__/protocol-v2.test.ts +377 -402
- package/__tests__/public-pro2-api-boundary.test.ts +1 -0
- package/dist/api/BaseMethod.d.ts +3 -1
- package/dist/api/BaseMethod.d.ts.map +1 -1
- package/dist/api/FirmwareUpdateV4.d.ts.map +1 -1
- package/dist/api/GetPassphraseState.d.ts.map +1 -1
- package/dist/api/OpenWalletSession.d.ts.map +1 -1
- package/dist/api/allnetwork/AllNetworkGetAddressBase.d.ts.map +1 -1
- package/dist/api/helpers/protocolV2FileWrite.d.ts +2 -0
- package/dist/api/helpers/protocolV2FileWrite.d.ts.map +1 -1
- package/dist/api/index.d.ts +1 -0
- package/dist/api/index.d.ts.map +1 -1
- package/dist/api/protocol-v2/DeviceUploadNft.d.ts +30 -0
- package/dist/api/protocol-v2/DeviceUploadNft.d.ts.map +1 -0
- package/dist/api/protocol-v2/ProtocolInfoRequest.d.ts.map +1 -1
- package/dist/core/deviceEventRegistration.d.ts +2 -0
- package/dist/core/deviceEventRegistration.d.ts.map +1 -1
- package/dist/core/index.d.ts.map +1 -1
- package/dist/device/Device.d.ts +22 -4
- package/dist/device/Device.d.ts.map +1 -1
- package/dist/events/device.d.ts +4 -0
- package/dist/events/device.d.ts.map +1 -1
- package/dist/events/logBlockEvent.d.ts.map +1 -1
- package/dist/events/ui-request.d.ts +27 -2
- package/dist/events/ui-request.d.ts.map +1 -1
- package/dist/index.d.ts +88 -7
- package/dist/index.js +937 -604
- package/dist/inject.d.ts.map +1 -1
- package/dist/protocols/protocol-v2/features.d.ts.map +1 -1
- package/dist/protocols/protocol-v2/uiInteraction.d.ts +3 -3
- package/dist/protocols/protocol-v2/uiInteraction.d.ts.map +1 -1
- package/dist/protocols/protocol-v2/unlockPolicy.d.ts +1 -3
- package/dist/protocols/protocol-v2/unlockPolicy.d.ts.map +1 -1
- package/dist/protocols/protocol-v2/unlockPolicyRunner.d.ts +22 -0
- package/dist/protocols/protocol-v2/unlockPolicyRunner.d.ts.map +1 -0
- package/dist/protocols/protocol-v2/walletSession.d.ts +4 -1
- package/dist/protocols/protocol-v2/walletSession.d.ts.map +1 -1
- package/dist/types/api/index.d.ts +2 -1
- package/dist/types/api/index.d.ts.map +1 -1
- package/dist/types/api/protocolV2.d.ts +3 -0
- package/dist/types/api/protocolV2.d.ts.map +1 -1
- package/dist/utils/deviceFeaturesUtils.d.ts +2 -0
- package/dist/utils/deviceFeaturesUtils.d.ts.map +1 -1
- package/dist/utils/homescreen.d.ts +4 -0
- package/dist/utils/homescreen.d.ts.map +1 -1
- package/dist/utils/index.d.ts +1 -1
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/patch.d.ts +1 -1
- package/dist/utils/patch.d.ts.map +1 -1
- package/dist/utils/pro2Nft.d.ts +29 -0
- package/dist/utils/pro2Nft.d.ts.map +1 -0
- package/dist/utils/pro2Wallpaper.d.ts +10 -0
- package/dist/utils/pro2Wallpaper.d.ts.map +1 -1
- package/package.json +4 -4
- package/src/api/BaseMethod.ts +8 -10
- package/src/api/FirmwareUpdateV4.ts +13 -6
- package/src/api/GetPassphraseState.ts +7 -1
- package/src/api/OpenWalletSession.ts +54 -17
- package/src/api/allnetwork/AllNetworkGetAddressBase.ts +32 -11
- package/src/api/device/DeviceUnlock.ts +1 -1
- package/src/api/helpers/protocolV2FileWrite.ts +11 -4
- package/src/api/index.ts +1 -0
- package/src/api/protocol-v2/DeviceUploadNft.ts +166 -0
- package/src/api/protocol-v2/ProtocolInfoRequest.ts +3 -1
- package/src/core/deviceEventRegistration.ts +4 -0
- package/src/core/index.ts +111 -74
- package/src/data/messages/messages-protocol-v2.json +10 -0
- package/src/device/Device.ts +221 -24
- package/src/events/device.ts +4 -0
- package/src/events/logBlockEvent.ts +1 -0
- package/src/events/ui-request.ts +32 -2
- package/src/inject.ts +1 -0
- package/src/protocols/protocol-v2/features.ts +9 -2
- package/src/protocols/protocol-v2/uiInteraction.ts +31 -5
- package/src/protocols/protocol-v2/unlockPolicy.ts +1 -105
- package/src/protocols/protocol-v2/unlockPolicyRunner.ts +117 -0
- package/src/protocols/protocol-v2/walletSession.ts +93 -18
- package/src/types/api/index.ts +2 -0
- package/src/types/api/protocolV2.ts +13 -0
- package/src/utils/deviceFeaturesUtils.ts +4 -0
- package/src/utils/homescreen.ts +32 -6
- package/src/utils/index.ts +6 -1
- package/src/utils/pro2Nft.ts +91 -0
- package/src/utils/pro2Wallpaper.ts +35 -8
- package/dist/protocols/protocol-v2/lockedError.d.ts +0 -2
- package/dist/protocols/protocol-v2/lockedError.d.ts.map +0 -1
- package/dist/protocols/protocol-v2/unlockRetry.d.ts +0 -10
- package/dist/protocols/protocol-v2/unlockRetry.d.ts.map +0 -1
- package/src/protocols/protocol-v2/lockedError.ts +0 -10
- 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
|
-
|
|
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
|
|
102
|
-
|
|
103
|
-
currentDeviceId =
|
|
104
|
-
return
|
|
112
|
+
const refreshProtocolV2DeviceState = async () => {
|
|
113
|
+
state = await this.device.getDeviceState({ refreshSections: ['status'] });
|
|
114
|
+
currentDeviceId = state.identity.deviceId;
|
|
115
|
+
return state;
|
|
105
116
|
};
|
|
106
|
-
const
|
|
107
|
-
if (isProtocolV2 && state
|
|
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, {
|
|
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
|
|
123
|
-
|
|
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
|
|
144
|
-
const refreshedDeviceId =
|
|
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
|
|
219
|
+
await ensureProtocolV2WalletStatus();
|
|
189
220
|
const session = isProtocolV2
|
|
190
|
-
? await getProtocolV2WalletSession(this.device, {
|
|
221
|
+
? await getProtocolV2WalletSession(this.device, {
|
|
222
|
+
forceWalletSelection: true,
|
|
223
|
+
deriveCardano: this.payload.deriveCardano,
|
|
224
|
+
})
|
|
191
225
|
: await getPassphraseStateWithRefreshDeviceInfo(this.device, { initSession: true });
|
|
192
|
-
const
|
|
193
|
-
|
|
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
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
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.
|
|
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);
|
|
@@ -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
|
|
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
|
-
|
|
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 || !
|
|
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);
|