@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
package/src/events/ui-request.ts
CHANGED
|
@@ -56,6 +56,16 @@ export type ProtocolV2UiEventSource =
|
|
|
56
56
|
|
|
57
57
|
export type ProtocolV2UiCompletion = 'page-accepted' | 'operation-completed';
|
|
58
58
|
|
|
59
|
+
export type HardwareUiInteractionMeta = {
|
|
60
|
+
interactionId: string;
|
|
61
|
+
phaseId: string;
|
|
62
|
+
sequence: number;
|
|
63
|
+
phase: 'pin' | 'passphrase' | 'passphrase-on-device' | 'button' | 'processing';
|
|
64
|
+
transition: 'start' | 'complete' | 'finish';
|
|
65
|
+
outcome?: 'submitted' | 'succeeded' | 'failed' | 'cancelled' | 'disconnected';
|
|
66
|
+
protocol: 'V2';
|
|
67
|
+
};
|
|
68
|
+
|
|
59
69
|
export type ProtocolV2UiEventMetadata = {
|
|
60
70
|
source?: ProtocolV2UiEventSource;
|
|
61
71
|
reason?: string;
|
|
@@ -64,12 +74,29 @@ export type ProtocolV2UiEventMetadata = {
|
|
|
64
74
|
method?: string;
|
|
65
75
|
page?: string | number;
|
|
66
76
|
operation?: string;
|
|
77
|
+
interaction?: HardwareUiInteractionMeta;
|
|
67
78
|
};
|
|
68
79
|
|
|
80
|
+
export type UiRequestWindowClose =
|
|
81
|
+
| {
|
|
82
|
+
type: typeof UI_REQUEST.CLOSE_UI_WINDOW;
|
|
83
|
+
payload: HardwareUiInteractionMeta;
|
|
84
|
+
}
|
|
85
|
+
| {
|
|
86
|
+
type: typeof UI_REQUEST.CLOSE_UI_PIN_WINDOW;
|
|
87
|
+
payload: HardwareUiInteractionMeta;
|
|
88
|
+
}
|
|
89
|
+
| {
|
|
90
|
+
type: typeof UI_REQUEST.CLOSE_UI_WINDOW;
|
|
91
|
+
payload?: undefined;
|
|
92
|
+
}
|
|
93
|
+
| {
|
|
94
|
+
type: typeof UI_REQUEST.CLOSE_UI_PIN_WINDOW;
|
|
95
|
+
payload?: undefined;
|
|
96
|
+
};
|
|
97
|
+
|
|
69
98
|
export interface UiRequestWithoutPayload {
|
|
70
99
|
type:
|
|
71
|
-
| typeof UI_REQUEST.CLOSE_UI_WINDOW
|
|
72
|
-
| typeof UI_REQUEST.CLOSE_UI_PIN_WINDOW
|
|
73
100
|
| typeof UI_REQUEST.BLUETOOTH_PERMISSION
|
|
74
101
|
| typeof UI_REQUEST.BLUETOOTH_UNSUPPORTED
|
|
75
102
|
| typeof UI_REQUEST.BLUETOOTH_POWERED_OFF
|
|
@@ -111,6 +138,7 @@ export interface UiRequestPassphrase {
|
|
|
111
138
|
source?: 'wallet-session-coordinator';
|
|
112
139
|
reason?: 'open-wallet' | 'session-recovery';
|
|
113
140
|
expectedPassphraseState?: string;
|
|
141
|
+
interaction?: HardwareUiInteractionMeta;
|
|
114
142
|
};
|
|
115
143
|
}
|
|
116
144
|
|
|
@@ -121,6 +149,7 @@ export interface UiRequestPassphraseOnDevice {
|
|
|
121
149
|
passphraseState?: string;
|
|
122
150
|
source?: 'wallet-session-coordinator';
|
|
123
151
|
reason?: 'open-wallet' | 'session-recovery';
|
|
152
|
+
interaction?: HardwareUiInteractionMeta;
|
|
124
153
|
};
|
|
125
154
|
}
|
|
126
155
|
|
|
@@ -191,6 +220,7 @@ export interface PreviousAddressResult {
|
|
|
191
220
|
|
|
192
221
|
export type UiEvent =
|
|
193
222
|
| UiRequestWithoutPayload
|
|
223
|
+
| UiRequestWindowClose
|
|
194
224
|
| UiRequestDeviceAction
|
|
195
225
|
| UiRequestButton
|
|
196
226
|
| UiRequestPassphraseOnDevice
|
package/src/inject.ts
CHANGED
|
@@ -162,6 +162,7 @@ export const createCoreApi = (
|
|
|
162
162
|
call({ ...params, connectId, method: 'deviceGetOnboardingStatus' }),
|
|
163
163
|
deviceUploadWallpaper: (connectId, params) =>
|
|
164
164
|
call({ ...params, connectId, method: 'deviceUploadWallpaper' }),
|
|
165
|
+
deviceUploadNft: (connectId, params) => call({ ...params, connectId, method: 'deviceUploadNft' }),
|
|
165
166
|
uploadPortfolio: (connectId, params) => call({ ...params, connectId, method: 'uploadPortfolio' }),
|
|
166
167
|
deviceRecovery: (connectId, params) => call({ ...params, connectId, method: 'deviceRecovery' }),
|
|
167
168
|
deviceReset: (connectId, params) => call({ ...params, connectId, method: 'deviceReset' }),
|
|
@@ -168,8 +168,15 @@ export async function requestProtocolV2ProtocolInfo({
|
|
|
168
168
|
}): Promise<ProtocolInfo> {
|
|
169
169
|
const response =
|
|
170
170
|
timeoutMs === undefined
|
|
171
|
-
? await commands.typedCall('ProtocolInfoRequest', 'ProtocolInfo', {
|
|
172
|
-
|
|
171
|
+
? await commands.typedCall('ProtocolInfoRequest', 'ProtocolInfo', {
|
|
172
|
+
eventless_wallet_session: true,
|
|
173
|
+
})
|
|
174
|
+
: await commands.typedCall(
|
|
175
|
+
'ProtocolInfoRequest',
|
|
176
|
+
'ProtocolInfo',
|
|
177
|
+
{ eventless_wallet_session: true },
|
|
178
|
+
{ timeoutMs }
|
|
179
|
+
);
|
|
173
180
|
return response.message;
|
|
174
181
|
}
|
|
175
182
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { UI_REQUEST, createUiMessage } from '../../events';
|
|
2
2
|
|
|
3
|
-
import type { CoreMessage, ProtocolV2UiCompletion } from '../../events';
|
|
3
|
+
import type { CoreMessage, HardwareUiInteractionMeta, ProtocolV2UiCompletion } from '../../events';
|
|
4
4
|
import type { Device } from '../../device/Device';
|
|
5
5
|
import type { KnownDevice } from '../../types';
|
|
6
6
|
|
|
@@ -28,7 +28,15 @@ type ProtocolV2InteractionMethod = {
|
|
|
28
28
|
protocolV2UiInteraction?: ProtocolV2InteractionDescriptor;
|
|
29
29
|
};
|
|
30
30
|
|
|
31
|
-
type InteractionDevice = Pick<Device, 'isProtocolV2' | 'toMessageObject'
|
|
31
|
+
type InteractionDevice = Pick<Device, 'isProtocolV2' | 'toMessageObject'> &
|
|
32
|
+
Partial<
|
|
33
|
+
Pick<
|
|
34
|
+
Device,
|
|
35
|
+
| 'createProtocolV2UiPhaseMetadata'
|
|
36
|
+
| 'finishProtocolV2UiInteraction'
|
|
37
|
+
| 'hasOpenProtocolV2UiInteraction'
|
|
38
|
+
>
|
|
39
|
+
>;
|
|
32
40
|
type PostMessage = (message: CoreMessage) => void;
|
|
33
41
|
|
|
34
42
|
export const isProtocolV2UiEnabled = (method: { protocolV2UiMode?: 'auto' | 'none' }) =>
|
|
@@ -156,12 +164,17 @@ export class ProtocolV2UiInteractionCoordinator {
|
|
|
156
164
|
this.methodInteraction = interaction;
|
|
157
165
|
if (!interaction) return;
|
|
158
166
|
const { request, ...metadata } = interaction;
|
|
167
|
+
const interactionMeta = this.device.createProtocolV2UiPhaseMetadata?.(
|
|
168
|
+
request === 'pin' ? 'pin' : 'button',
|
|
169
|
+
'start'
|
|
170
|
+
);
|
|
159
171
|
|
|
160
172
|
this.emit(
|
|
161
173
|
request === 'pin' ? UI_REQUEST.REQUEST_PIN : UI_REQUEST.REQUEST_BUTTON,
|
|
162
174
|
{
|
|
163
175
|
device: this.device.toMessageObject() as KnownDevice,
|
|
164
176
|
...metadata,
|
|
177
|
+
...(interactionMeta ? { interaction: interactionMeta } : {}),
|
|
165
178
|
},
|
|
166
179
|
`method:${request}:${interaction.reason}:${interaction.page ?? ''}:${
|
|
167
180
|
interaction.operation ?? ''
|
|
@@ -169,7 +182,8 @@ export class ProtocolV2UiInteractionCoordinator {
|
|
|
169
182
|
);
|
|
170
183
|
}
|
|
171
184
|
|
|
172
|
-
enterUnlockInteraction(method?: string) {
|
|
185
|
+
enterUnlockInteraction(method?: string): HardwareUiInteractionMeta | undefined {
|
|
186
|
+
const interaction = this.device.createProtocolV2UiPhaseMetadata?.('pin', 'start');
|
|
173
187
|
this.emit(
|
|
174
188
|
UI_REQUEST.REQUEST_PIN,
|
|
175
189
|
{
|
|
@@ -178,9 +192,11 @@ export class ProtocolV2UiInteractionCoordinator {
|
|
|
178
192
|
reason: 'device-locked',
|
|
179
193
|
deviceOnly: true,
|
|
180
194
|
method,
|
|
195
|
+
...(interaction ? { interaction } : {}),
|
|
181
196
|
},
|
|
182
197
|
`unlock:${method ?? ''}`
|
|
183
198
|
);
|
|
199
|
+
return interaction;
|
|
184
200
|
}
|
|
185
201
|
|
|
186
202
|
resumeMethodInteraction() {
|
|
@@ -188,9 +204,19 @@ export class ProtocolV2UiInteractionCoordinator {
|
|
|
188
204
|
}
|
|
189
205
|
|
|
190
206
|
close() {
|
|
191
|
-
if (
|
|
207
|
+
if (
|
|
208
|
+
!this.device.isProtocolV2() ||
|
|
209
|
+
(!this.opened && !this.device.hasOpenProtocolV2UiInteraction?.()) ||
|
|
210
|
+
this.closed
|
|
211
|
+
)
|
|
212
|
+
return;
|
|
192
213
|
this.closed = true;
|
|
193
|
-
this.
|
|
214
|
+
const interaction = this.device.finishProtocolV2UiInteraction?.();
|
|
215
|
+
this.postMessage(
|
|
216
|
+
interaction
|
|
217
|
+
? createUiMessage(UI_REQUEST.CLOSE_UI_WINDOW, interaction)
|
|
218
|
+
: createUiMessage(UI_REQUEST.CLOSE_UI_WINDOW)
|
|
219
|
+
);
|
|
194
220
|
}
|
|
195
221
|
|
|
196
222
|
private emit(
|
|
@@ -1,105 +1 @@
|
|
|
1
|
-
export type UnlockPolicy = 'none' | 'unlock-before-run'
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Protocol V2 methods that are safe to replay after a locked response.
|
|
5
|
-
*
|
|
6
|
-
* Keep this as an exact allowlist. Name-pattern matching can silently make a new
|
|
7
|
-
* state-changing method replayable when it happens to share a suffix.
|
|
8
|
-
*/
|
|
9
|
-
export const PROTOCOL_V2_RETRY_ON_LOCKED_METHODS = [
|
|
10
|
-
'cipherKeyValue',
|
|
11
|
-
'allNetworkGetAddress',
|
|
12
|
-
'allNetworkGetAddressByLoop',
|
|
13
|
-
'btcGetAddress',
|
|
14
|
-
'btcGetPublicKey',
|
|
15
|
-
'btcSignMessage',
|
|
16
|
-
'btcSignPsbt',
|
|
17
|
-
'btcSignTransaction',
|
|
18
|
-
'btcVerifyMessage',
|
|
19
|
-
'confluxGetAddress',
|
|
20
|
-
'confluxSignMessage',
|
|
21
|
-
'confluxSignTransaction',
|
|
22
|
-
'evmGetAddress',
|
|
23
|
-
'evmGetPublicKey',
|
|
24
|
-
'evmSignMessage',
|
|
25
|
-
'evmSignTransaction',
|
|
26
|
-
'evmSignTypedData',
|
|
27
|
-
'evmVerifyMessage',
|
|
28
|
-
'starcoinGetAddress',
|
|
29
|
-
'starcoinGetPublicKey',
|
|
30
|
-
'starcoinSignMessage',
|
|
31
|
-
'starcoinSignTransaction',
|
|
32
|
-
'starcoinVerifyMessage',
|
|
33
|
-
'nemGetAddress',
|
|
34
|
-
'nemSignTransaction',
|
|
35
|
-
'solGetAddress',
|
|
36
|
-
'solSignTransaction',
|
|
37
|
-
'solSignOffchainMessage',
|
|
38
|
-
'solSignMessage',
|
|
39
|
-
'stellarGetAddress',
|
|
40
|
-
'stellarSignTransaction',
|
|
41
|
-
'tronGetAddress',
|
|
42
|
-
'tronSignMessage',
|
|
43
|
-
'tronSignTransaction',
|
|
44
|
-
'nearGetAddress',
|
|
45
|
-
'nearSignTransaction',
|
|
46
|
-
'aptosGetAddress',
|
|
47
|
-
'aptosGetPublicKey',
|
|
48
|
-
'aptosSignTransaction',
|
|
49
|
-
'aptosSignMessage',
|
|
50
|
-
'aptosSignInMessage',
|
|
51
|
-
'algoGetAddress',
|
|
52
|
-
'algoSignTransaction',
|
|
53
|
-
'cosmosGetAddress',
|
|
54
|
-
'cosmosGetPublicKey',
|
|
55
|
-
'cosmosSignTransaction',
|
|
56
|
-
'xrpGetAddress',
|
|
57
|
-
'xrpSignTransaction',
|
|
58
|
-
'suiGetAddress',
|
|
59
|
-
'suiGetPublicKey',
|
|
60
|
-
'suiSignMessage',
|
|
61
|
-
'suiSignTransaction',
|
|
62
|
-
'cardanoGetAddress',
|
|
63
|
-
'cardanoGetPublicKey',
|
|
64
|
-
'cardanoSignTransaction',
|
|
65
|
-
'cardanoSignMessage',
|
|
66
|
-
'filecoinGetAddress',
|
|
67
|
-
'filecoinSignTransaction',
|
|
68
|
-
'polkadotGetAddress',
|
|
69
|
-
'polkadotSignTransaction',
|
|
70
|
-
'kaspaGetAddress',
|
|
71
|
-
'kaspaSignTransaction',
|
|
72
|
-
'nexaGetAddress',
|
|
73
|
-
'nexaSignTransaction',
|
|
74
|
-
'nostrGetPublicKey',
|
|
75
|
-
'nostrSignEvent',
|
|
76
|
-
'nostrEncryptMessage',
|
|
77
|
-
'nostrDecryptMessage',
|
|
78
|
-
'nostrSignSchnorr',
|
|
79
|
-
'lnurlAuth',
|
|
80
|
-
'nervosGetAddress',
|
|
81
|
-
'nervosSignTransaction',
|
|
82
|
-
'dnxGetAddress',
|
|
83
|
-
'dnxSignTransaction',
|
|
84
|
-
'tonGetAddress',
|
|
85
|
-
'tonSignMessage',
|
|
86
|
-
'tonSignProof',
|
|
87
|
-
'tonSignData',
|
|
88
|
-
'scdoGetAddress',
|
|
89
|
-
'scdoSignTransaction',
|
|
90
|
-
'scdoSignMessage',
|
|
91
|
-
'alephiumGetAddress',
|
|
92
|
-
'alephiumSignTransaction',
|
|
93
|
-
'alephiumSignMessage',
|
|
94
|
-
'benfenGetAddress',
|
|
95
|
-
'benfenGetPublicKey',
|
|
96
|
-
'benfenSignMessage',
|
|
97
|
-
'benfenSignTransaction',
|
|
98
|
-
'neoGetAddress',
|
|
99
|
-
'neoSignTransaction',
|
|
100
|
-
] as const;
|
|
101
|
-
|
|
102
|
-
const retryOnLockedMethods = new Set<string>(PROTOCOL_V2_RETRY_ON_LOCKED_METHODS);
|
|
103
|
-
|
|
104
|
-
export const getProtocolV2UnlockPolicy = (methodName: string): UnlockPolicy =>
|
|
105
|
-
retryOnLockedMethods.has(methodName) ? 'retry-on-locked' : 'none';
|
|
1
|
+
export type UnlockPolicy = 'none' | 'unlock-before-run';
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { ERRORS, HardwareErrorCode } from '@onekeyfe/hd-shared';
|
|
2
|
+
|
|
3
|
+
import { LoggerNames, getLogger } from '../../utils';
|
|
4
|
+
import { isProtocolV2UiEnabled, resolveProtocolV2UiInteraction } from './uiInteraction';
|
|
5
|
+
import { refreshProtocolV2DeviceStatus } from './walletSession';
|
|
6
|
+
|
|
7
|
+
import type { BaseMethod } from '../../api/BaseMethod';
|
|
8
|
+
import type { Device } from '../../device/Device';
|
|
9
|
+
import type { HardwareUiInteractionMeta } from '../../events';
|
|
10
|
+
import type { ProtocolV2UiInteractionCoordinator } from './uiInteraction';
|
|
11
|
+
|
|
12
|
+
const Log = getLogger(LoggerNames.Core);
|
|
13
|
+
|
|
14
|
+
export type ProtocolV2UnlockContext = {
|
|
15
|
+
preflightCompleted: boolean;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export const createProtocolV2UnlockContext = (): ProtocolV2UnlockContext => ({
|
|
19
|
+
preflightCompleted: false,
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
type RunnableMethod = Pick<
|
|
23
|
+
BaseMethod,
|
|
24
|
+
| 'run'
|
|
25
|
+
| 'unlockPolicy'
|
|
26
|
+
| 'useDevicePassphraseState'
|
|
27
|
+
| 'protocolV2UiInteraction'
|
|
28
|
+
| 'protocolV2UiMode'
|
|
29
|
+
| 'params'
|
|
30
|
+
| 'payload'
|
|
31
|
+
> & {
|
|
32
|
+
name?: string;
|
|
33
|
+
protocolV2UnlockContext?: ProtocolV2UnlockContext;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
type UiInteractionCoordinator = Pick<
|
|
37
|
+
ProtocolV2UiInteractionCoordinator,
|
|
38
|
+
'enterMethodInteraction' | 'enterUnlockInteraction'
|
|
39
|
+
>;
|
|
40
|
+
|
|
41
|
+
type RunMethodWithUnlockPolicyOptions<T> = {
|
|
42
|
+
context?: ProtocolV2UnlockContext;
|
|
43
|
+
uiCoordinator?: UiInteractionCoordinator;
|
|
44
|
+
afterStatusBeforeUnlock?: () => void | Promise<void>;
|
|
45
|
+
prepare?: () => Promise<void>;
|
|
46
|
+
run?: () => Promise<T>;
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
export async function runMethodWithUnlockPolicy<T = unknown>(
|
|
50
|
+
method: RunnableMethod,
|
|
51
|
+
device: Device,
|
|
52
|
+
options: RunMethodWithUnlockPolicyOptions<T> = {}
|
|
53
|
+
): Promise<T> {
|
|
54
|
+
const {
|
|
55
|
+
context = createProtocolV2UnlockContext(),
|
|
56
|
+
uiCoordinator,
|
|
57
|
+
afterStatusBeforeUnlock,
|
|
58
|
+
prepare,
|
|
59
|
+
run: configuredRun,
|
|
60
|
+
} = options;
|
|
61
|
+
const run = configuredRun ?? (() => method.run() as Promise<T>);
|
|
62
|
+
method.protocolV2UnlockContext = context;
|
|
63
|
+
|
|
64
|
+
const shouldEmitUi = isProtocolV2UiEnabled(method);
|
|
65
|
+
const shouldCoordinateUi = shouldEmitUi && uiCoordinator !== undefined;
|
|
66
|
+
const requiresPreUnlock =
|
|
67
|
+
device.isProtocolV2() &&
|
|
68
|
+
(method.useDevicePassphraseState || method.unlockPolicy === 'unlock-before-run') &&
|
|
69
|
+
!device.isBootloader?.() &&
|
|
70
|
+
!device.isRomloader?.();
|
|
71
|
+
|
|
72
|
+
if (requiresPreUnlock && !context.preflightCompleted) {
|
|
73
|
+
const status = await refreshProtocolV2DeviceStatus(device);
|
|
74
|
+
if (typeof status?.unlocked !== 'boolean') {
|
|
75
|
+
throw ERRORS.TypedError(
|
|
76
|
+
HardwareErrorCode.RuntimeError,
|
|
77
|
+
'Protocol V2 DeviceStatus did not report an explicit unlock state.'
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
await afterStatusBeforeUnlock?.();
|
|
82
|
+
|
|
83
|
+
if (!status.unlocked) {
|
|
84
|
+
const unlockInteraction: HardwareUiInteractionMeta | undefined = shouldCoordinateUi
|
|
85
|
+
? uiCoordinator.enterUnlockInteraction(method.name)
|
|
86
|
+
: undefined;
|
|
87
|
+
const unlockedStatus = await device.unlockDevice(
|
|
88
|
+
undefined,
|
|
89
|
+
shouldCoordinateUi
|
|
90
|
+
? { emitUiEvent: false, interaction: unlockInteraction }
|
|
91
|
+
: {
|
|
92
|
+
source: 'unlock-coordinator',
|
|
93
|
+
reason: 'device-locked',
|
|
94
|
+
deviceOnly: true,
|
|
95
|
+
method: method.name,
|
|
96
|
+
}
|
|
97
|
+
);
|
|
98
|
+
if (unlockedStatus?.unlocked !== true) {
|
|
99
|
+
throw ERRORS.TypedError(
|
|
100
|
+
HardwareErrorCode.RuntimeError,
|
|
101
|
+
'Protocol V2 device remained locked after the unlock flow.'
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
Log.debug('Protocol V2 pre-unlock completed', { method: method.name });
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
context.preflightCompleted = true;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
await prepare?.();
|
|
111
|
+
|
|
112
|
+
if (shouldCoordinateUi) {
|
|
113
|
+
uiCoordinator.enterMethodInteraction(resolveProtocolV2UiInteraction(method));
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
return run();
|
|
117
|
+
}
|
|
@@ -38,10 +38,26 @@ export async function refreshProtocolV2DeviceStatus(device: Device) {
|
|
|
38
38
|
return device.updateProtocolV2Status(status);
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
41
|
+
export async function ensureProtocolV2WalletSessionUnlocked(device: Device) {
|
|
42
|
+
if (!device.isProtocolV2() || device.isBootloader?.() || device.isRomloader?.()) {
|
|
43
|
+
return false;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
await refreshProtocolV2DeviceStatus(device);
|
|
47
|
+
if (device.state?.status.unlocked !== false) {
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
await device.unlockDevice(DeviceSessionPinType.Main, {
|
|
52
|
+
source: 'unlock-coordinator',
|
|
53
|
+
reason: 'device-locked',
|
|
54
|
+
deviceOnly: true,
|
|
44
55
|
});
|
|
56
|
+
return true;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const negotiateEventlessWalletSession = async (device: Device) => {
|
|
60
|
+
await device.ensureProtocolV2RuntimeContext();
|
|
45
61
|
};
|
|
46
62
|
|
|
47
63
|
const getDeviceSession = async (device: Device, request: DeviceSessionGet) =>
|
|
@@ -59,11 +75,13 @@ const selectDeviceSession = async (device: Device, expectedPassphraseState?: str
|
|
|
59
75
|
reason: expectedPassphraseState ? ('session-recovery' as const) : ('open-wallet' as const),
|
|
60
76
|
...(expectedPassphraseState ? { expectedPassphraseState } : {}),
|
|
61
77
|
};
|
|
78
|
+
const passphraseInteraction = device.createProtocolV2UiPhaseMetadata?.('passphrase', 'start');
|
|
62
79
|
const response = await device.commands.promptPassphrase(
|
|
63
80
|
{
|
|
64
81
|
existsAttachPinUser,
|
|
65
82
|
deviceOnly: false,
|
|
66
83
|
...metadata,
|
|
84
|
+
...(passphraseInteraction ? { interaction: passphraseInteraction } : {}),
|
|
67
85
|
},
|
|
68
86
|
{ cancelDeviceOnReject: false }
|
|
69
87
|
);
|
|
@@ -102,8 +120,15 @@ const selectDeviceSession = async (device: Device, expectedPassphraseState?: str
|
|
|
102
120
|
'Attach PIN wallet selection is unavailable on this device.'
|
|
103
121
|
);
|
|
104
122
|
}
|
|
105
|
-
device.
|
|
106
|
-
|
|
123
|
+
const attachPinInteraction = device.createProtocolV2UiPhaseMetadata?.('pin', 'start');
|
|
124
|
+
device.emit(DEVICE.ATTACH_PIN_ON_DEVICE, device, {
|
|
125
|
+
...metadata,
|
|
126
|
+
...(attachPinInteraction ? { interaction: attachPinInteraction } : {}),
|
|
127
|
+
});
|
|
128
|
+
await device.unlockDevice(DeviceSessionPinType.AttachToPin, {
|
|
129
|
+
emitUiEvent: false,
|
|
130
|
+
interaction: attachPinInteraction,
|
|
131
|
+
});
|
|
107
132
|
return getDeviceSession(device, {});
|
|
108
133
|
}
|
|
109
134
|
|
|
@@ -115,7 +140,10 @@ const selectDeviceSession = async (device: Device, expectedPassphraseState?: str
|
|
|
115
140
|
return getDeviceSession(device, {});
|
|
116
141
|
}
|
|
117
142
|
|
|
118
|
-
device.emit(DEVICE.PASSPHRASE_ON_DEVICE, device,
|
|
143
|
+
device.emit(DEVICE.PASSPHRASE_ON_DEVICE, device, {
|
|
144
|
+
...metadata,
|
|
145
|
+
...(passphraseInteraction ? { interaction: passphraseInteraction } : {}),
|
|
146
|
+
});
|
|
119
147
|
await askDevicePassphrase(device, { on_device: true });
|
|
120
148
|
return getDeviceSession(device, {});
|
|
121
149
|
};
|
|
@@ -128,7 +156,9 @@ export async function getProtocolV2WalletSession(
|
|
|
128
156
|
initSession?: boolean;
|
|
129
157
|
expectedPassphraseState?: string;
|
|
130
158
|
onlyMainPin?: boolean;
|
|
159
|
+
selectMainWalletBeforeRestore?: boolean;
|
|
131
160
|
resumeOnly?: boolean;
|
|
161
|
+
deriveCardano?: boolean;
|
|
132
162
|
}
|
|
133
163
|
) {
|
|
134
164
|
const forceWalletSelection =
|
|
@@ -161,13 +191,48 @@ export async function getProtocolV2WalletSession(
|
|
|
161
191
|
: undefined;
|
|
162
192
|
let response;
|
|
163
193
|
let resumed = false;
|
|
194
|
+
let mainWalletSelected = false;
|
|
195
|
+
|
|
196
|
+
const clearCurrentWalletSession = () => {
|
|
197
|
+
if (options?.onlyMainPin) {
|
|
198
|
+
device.clearStandardInternalState?.();
|
|
199
|
+
} else {
|
|
200
|
+
device.clearInternalState();
|
|
201
|
+
}
|
|
202
|
+
};
|
|
203
|
+
|
|
204
|
+
const rejectMismatchedAttachPinWallet = async () => {
|
|
205
|
+
const features = await refreshProtocolV2DeviceStatus(device);
|
|
206
|
+
if (features.unlockedAttachPin !== true) {
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
try {
|
|
211
|
+
await device.lockDevice();
|
|
212
|
+
} catch {
|
|
213
|
+
// Reject the mismatched Attach PIN wallet even when older firmware cannot lock.
|
|
214
|
+
}
|
|
215
|
+
clearCurrentWalletSession();
|
|
216
|
+
throw ERRORS.TypedError(HardwareErrorCode.DeviceCheckUnlockTypeError);
|
|
217
|
+
};
|
|
218
|
+
|
|
219
|
+
const selectMainWallet = async (force = false) => {
|
|
220
|
+
if (force || !mainWalletSelected) {
|
|
221
|
+
await device.unlockDevice(DeviceSessionPinType.Main, {
|
|
222
|
+
source: 'wallet-session-coordinator',
|
|
223
|
+
reason: expectedPassphraseState ? 'session-recovery' : 'open-wallet',
|
|
224
|
+
deviceOnly: true,
|
|
225
|
+
});
|
|
226
|
+
mainWalletSelected = true;
|
|
227
|
+
}
|
|
228
|
+
};
|
|
164
229
|
|
|
165
230
|
if (options?.onlyMainPin) {
|
|
166
231
|
expectedPassphraseState = cachedStandardSession?.passphraseState;
|
|
167
232
|
if (cachedStandardSession) {
|
|
168
233
|
try {
|
|
169
|
-
if (
|
|
170
|
-
await
|
|
234
|
+
if (options?.selectMainWalletBeforeRestore) {
|
|
235
|
+
await selectMainWallet();
|
|
171
236
|
}
|
|
172
237
|
response = await getDeviceSession(device, {
|
|
173
238
|
session_id: cachedStandardSession.sessionId,
|
|
@@ -183,12 +248,14 @@ export async function getProtocolV2WalletSession(
|
|
|
183
248
|
}
|
|
184
249
|
|
|
185
250
|
if (!response) {
|
|
186
|
-
await
|
|
251
|
+
await selectMainWallet();
|
|
187
252
|
response = await getDeviceSession(device, {});
|
|
188
253
|
}
|
|
189
254
|
} else if (cachedSessionId && expectedPassphraseState) {
|
|
190
255
|
try {
|
|
191
|
-
response = await getDeviceSession(device, {
|
|
256
|
+
response = await getDeviceSession(device, {
|
|
257
|
+
session_id: cachedSessionId,
|
|
258
|
+
});
|
|
192
259
|
resumed = true;
|
|
193
260
|
} catch (error) {
|
|
194
261
|
device.clearInternalState();
|
|
@@ -197,6 +264,14 @@ export async function getProtocolV2WalletSession(
|
|
|
197
264
|
}
|
|
198
265
|
resumed = false;
|
|
199
266
|
}
|
|
267
|
+
} else if (expectedPassphraseState) {
|
|
268
|
+
try {
|
|
269
|
+
response = await getDeviceSession(device, {});
|
|
270
|
+
} catch (error) {
|
|
271
|
+
if (options?.resumeOnly || !isWalletSessionInvalidError(error)) {
|
|
272
|
+
throw error;
|
|
273
|
+
}
|
|
274
|
+
}
|
|
200
275
|
}
|
|
201
276
|
|
|
202
277
|
if (!response) {
|
|
@@ -220,13 +295,14 @@ export async function getProtocolV2WalletSession(
|
|
|
220
295
|
}
|
|
221
296
|
if (expectedPassphraseState && expectedPassphraseState !== message.btc_test_address) {
|
|
222
297
|
resumed = false;
|
|
298
|
+
await rejectMismatchedAttachPinWallet();
|
|
223
299
|
if (options?.resumeOnly) {
|
|
224
300
|
device.clearInternalState();
|
|
225
301
|
throw ERRORS.TypedError(HardwareErrorCode.WalletSessionInvalid);
|
|
226
302
|
}
|
|
227
303
|
if (options?.onlyMainPin) {
|
|
228
304
|
device.clearStandardInternalState?.();
|
|
229
|
-
await
|
|
305
|
+
await selectMainWallet(true);
|
|
230
306
|
response = await getDeviceSession(device, {});
|
|
231
307
|
} else {
|
|
232
308
|
device.clearInternalState();
|
|
@@ -244,11 +320,8 @@ export async function getProtocolV2WalletSession(
|
|
|
244
320
|
throw error;
|
|
245
321
|
}
|
|
246
322
|
if (expectedPassphraseState !== message.btc_test_address) {
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
} else {
|
|
250
|
-
device.clearInternalState();
|
|
251
|
-
}
|
|
323
|
+
await rejectMismatchedAttachPinWallet();
|
|
324
|
+
clearCurrentWalletSession();
|
|
252
325
|
throw ERRORS.TypedError(HardwareErrorCode.DeviceCheckPassphraseStateError);
|
|
253
326
|
}
|
|
254
327
|
}
|
|
@@ -269,17 +342,19 @@ export async function getProtocolV2WalletSession(
|
|
|
269
342
|
return {
|
|
270
343
|
passphraseState: message.btc_test_address,
|
|
271
344
|
newSession: message.session_id,
|
|
272
|
-
unlockedAttachPin:
|
|
345
|
+
unlockedAttachPin: mainWalletSelected ? false : undefined,
|
|
273
346
|
resumed,
|
|
274
347
|
};
|
|
275
348
|
}
|
|
276
349
|
|
|
277
350
|
export async function restoreProtocolV2WalletSession(
|
|
278
351
|
device: Device,
|
|
279
|
-
expectedPassphraseState: string
|
|
352
|
+
expectedPassphraseState: string,
|
|
353
|
+
deriveCardano?: boolean
|
|
280
354
|
) {
|
|
281
355
|
return getProtocolV2WalletSession(device, {
|
|
282
356
|
expectedPassphraseState,
|
|
283
357
|
resumeOnly: true,
|
|
358
|
+
deriveCardano,
|
|
284
359
|
});
|
|
285
360
|
}
|
package/src/types/api/index.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type {
|
|
2
2
|
deviceGetOnboardingStatus,
|
|
3
3
|
deviceReboot,
|
|
4
|
+
deviceUploadNft,
|
|
4
5
|
deviceUploadWallpaper,
|
|
5
6
|
testProtocolV2Ping,
|
|
6
7
|
uploadPortfolio,
|
|
@@ -234,6 +235,7 @@ export type CoreApi = {
|
|
|
234
235
|
*/
|
|
235
236
|
deviceReboot: typeof deviceReboot;
|
|
236
237
|
deviceGetOnboardingStatus: typeof deviceGetOnboardingStatus;
|
|
238
|
+
deviceUploadNft: typeof deviceUploadNft;
|
|
237
239
|
deviceUploadWallpaper: typeof deviceUploadWallpaper;
|
|
238
240
|
uploadPortfolio: typeof uploadPortfolio;
|
|
239
241
|
|
|
@@ -5,6 +5,10 @@ import type {
|
|
|
5
5
|
DeviceUploadWallpaperParams,
|
|
6
6
|
DeviceUploadWallpaperResponse,
|
|
7
7
|
} from '../../api/protocol-v2/DeviceUploadWallpaper';
|
|
8
|
+
import type {
|
|
9
|
+
DeviceUploadNftParams,
|
|
10
|
+
DeviceUploadNftResponse,
|
|
11
|
+
} from '../../api/protocol-v2/DeviceUploadNft';
|
|
8
12
|
|
|
9
13
|
// Re-export implementation parameter types as the single source of truth.
|
|
10
14
|
export type { DeviceRebootParams, RebootTypeInput } from '../../api/protocol-v2/helpers';
|
|
@@ -12,6 +16,10 @@ export type {
|
|
|
12
16
|
DeviceUploadWallpaperParams,
|
|
13
17
|
DeviceUploadWallpaperResponse,
|
|
14
18
|
} from '../../api/protocol-v2/DeviceUploadWallpaper';
|
|
19
|
+
export type {
|
|
20
|
+
DeviceUploadNftParams,
|
|
21
|
+
DeviceUploadNftResponse,
|
|
22
|
+
} from '../../api/protocol-v2/DeviceUploadNft';
|
|
15
23
|
|
|
16
24
|
// ── Shared response shapes (Protocol V2 file system) ────────────────────
|
|
17
25
|
|
|
@@ -52,6 +60,11 @@ export declare function deviceUploadWallpaper(
|
|
|
52
60
|
params: CommonParams & DeviceUploadWallpaperParams
|
|
53
61
|
): Response<DeviceUploadWallpaperResponse>;
|
|
54
62
|
|
|
63
|
+
export declare function deviceUploadNft(
|
|
64
|
+
connectId: string,
|
|
65
|
+
params: CommonParams & DeviceUploadNftParams
|
|
66
|
+
): Response<DeviceUploadNftResponse>;
|
|
67
|
+
|
|
55
68
|
export declare function uploadPortfolio(
|
|
56
69
|
connectId: string,
|
|
57
70
|
params: {
|