@onekeyfe/hd-core 1.2.2-alpha.11 → 1.2.2-alpha.110
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/README.md +1 -1
- package/__tests__/AllNetworkGetAddressBase.tracing.test.ts +155 -2
- package/__tests__/DeviceCommands.test.ts +254 -1
- package/__tests__/core-error-output.test.ts +92 -1
- package/__tests__/device-lifecycle-events.test.ts +379 -15
- package/__tests__/open-wallet-session-error-response.test.ts +2 -2
- package/__tests__/open-wallet-session.test.ts +8 -411
- package/__tests__/protocol-v2.test.ts +86 -0
- package/__tests__/public-device-state-api.test.ts +2 -7
- package/__tests__/sol-sign-offchain-message.test.ts +64 -0
- package/dist/api/GetFeatures.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/UploadPortfolio.d.ts +1 -0
- package/dist/api/UploadPortfolio.d.ts.map +1 -1
- package/dist/api/allnetwork/AllNetworkGetAddressBase.d.ts +3 -0
- package/dist/api/allnetwork/AllNetworkGetAddressBase.d.ts.map +1 -1
- package/dist/api/allnetwork/AllNetworkGetAddressByLoop.d.ts.map +1 -1
- package/dist/api/device/DeviceVerify.d.ts.map +1 -1
- package/dist/api/solana/SolSignOffchainMessage.d.ts.map +1 -1
- package/dist/core/RequestQueue.d.ts +1 -0
- package/dist/core/RequestQueue.d.ts.map +1 -1
- package/dist/core/index.d.ts +3 -1
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/uiPromiseRegistry.d.ts +1 -1
- package/dist/device/Device.d.ts +2 -0
- package/dist/device/Device.d.ts.map +1 -1
- package/dist/device/DeviceCommands.d.ts +5 -4
- package/dist/device/DeviceCommands.d.ts.map +1 -1
- package/dist/index.d.ts +35 -30
- package/dist/index.js +352 -188
- package/dist/protocols/protocol-v2/walletSession.d.ts.map +1 -1
- package/dist/types/api/getFeatures.d.ts.map +1 -1
- package/dist/types/api/getPassphraseState.d.ts.map +1 -1
- package/dist/types/api/openWalletSession.d.ts +1 -10
- package/dist/types/api/openWalletSession.d.ts.map +1 -1
- package/dist/types/api/protocolV2.d.ts +3 -4
- package/dist/types/api/protocolV2.d.ts.map +1 -1
- package/dist/types/api/solSignOffchainMessage.d.ts +1 -0
- package/dist/types/api/solSignOffchainMessage.d.ts.map +1 -1
- package/dist/types/params.d.ts.map +1 -1
- package/dist/utils/patch.d.ts +1 -1
- package/dist/utils/patch.d.ts.map +1 -1
- package/package.json +4 -4
- package/src/api/GetFeatures.ts +1 -0
- package/src/api/GetPassphraseState.ts +1 -0
- package/src/api/OpenWalletSession.ts +7 -77
- package/src/api/UploadPortfolio.ts +5 -4
- package/src/api/allnetwork/AllNetworkGetAddressBase.ts +56 -15
- package/src/api/allnetwork/AllNetworkGetAddressByLoop.ts +3 -0
- package/src/api/device/DeviceVerify.ts +8 -0
- package/src/api/solana/SolSignOffchainMessage.ts +44 -4
- package/src/core/RequestQueue.ts +20 -0
- package/src/core/index.ts +116 -49
- package/src/data/messages/messages-protocol-v2.json +49 -33
- package/src/data/messages/messages.json +8 -5
- package/src/device/Device.ts +51 -27
- package/src/device/DeviceCommands.ts +29 -2
- package/src/protocols/protocol-v2/walletSession.ts +7 -0
- package/src/types/api/getFeatures.ts +2 -1
- package/src/types/api/getPassphraseState.ts +2 -5
- package/src/types/api/openWalletSession.ts +7 -19
- package/src/types/api/protocolV2.ts +3 -4
- package/src/types/api/solSignOffchainMessage.ts +2 -0
- package/src/types/params.ts +7 -0
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { ERRORS, HardwareErrorCode } from '@onekeyfe/hd-shared';
|
|
2
2
|
import { DeviceSessionPinType } from '@onekeyfe/hd-transport';
|
|
3
3
|
|
|
4
|
-
import { deviceWalletSessionStore } from '../device/DeviceWalletSessionStore';
|
|
5
4
|
import { getProtocolV2WalletSession } from '../protocols/protocol-v2/walletSession';
|
|
6
5
|
import { getPassphraseStateWithRefreshDeviceInfo } from '../utils/deviceFeaturesUtils';
|
|
7
6
|
import { BaseMethod } from './BaseMethod';
|
|
@@ -13,16 +12,6 @@ import type {
|
|
|
13
12
|
OpenWalletSessionPayload,
|
|
14
13
|
} from '../types/api/openWalletSession';
|
|
15
14
|
|
|
16
|
-
const requiredString = (value: unknown, name: string) => {
|
|
17
|
-
if (value === undefined || value === null) {
|
|
18
|
-
throw invalidParameter(`Missing required parameter: ${name}`);
|
|
19
|
-
}
|
|
20
|
-
if (typeof value !== 'string' || !value.trim()) {
|
|
21
|
-
throw invalidParameter(`Parameter [${name}] must be a non-empty string.`);
|
|
22
|
-
}
|
|
23
|
-
return value.trim();
|
|
24
|
-
};
|
|
25
|
-
|
|
26
15
|
const wasResumed = (session: unknown) =>
|
|
27
16
|
!!session &&
|
|
28
17
|
typeof session === 'object' &&
|
|
@@ -53,34 +42,21 @@ const normalizeParams = (payload: Record<string, unknown>): OpenWalletSessionPar
|
|
|
53
42
|
}
|
|
54
43
|
if (
|
|
55
44
|
payload.mode !== OpenWalletSessionMode.Standard &&
|
|
56
|
-
payload.mode !== OpenWalletSessionMode.SelectHidden
|
|
57
|
-
payload.mode !== OpenWalletSessionMode.ResumeHidden
|
|
45
|
+
payload.mode !== OpenWalletSessionMode.SelectHidden
|
|
58
46
|
) {
|
|
59
|
-
throw invalidParameter(
|
|
60
|
-
'Parameter [mode] must be one of standard, select-hidden, or resume-hidden.'
|
|
61
|
-
);
|
|
47
|
+
throw invalidParameter('Parameter [mode] must be one of standard or select-hidden.');
|
|
62
48
|
}
|
|
63
49
|
if (payload.useEmptyPassphrase !== undefined || payload.initSession !== undefined) {
|
|
64
50
|
throw invalidParameter(
|
|
65
51
|
'Legacy parameters [useEmptyPassphrase] and [initSession] are not supported by openWalletSession.'
|
|
66
52
|
);
|
|
67
53
|
}
|
|
68
|
-
if (
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
if (payload.deviceId !== undefined || payload.passphraseState !== undefined) {
|
|
73
|
-
throw invalidParameter(
|
|
74
|
-
'Parameters [deviceId] and [passphraseState] are only allowed with mode [resume-hidden].'
|
|
75
|
-
);
|
|
76
|
-
}
|
|
77
|
-
return { mode: payload.mode };
|
|
54
|
+
if (payload.deviceId !== undefined || payload.passphraseState !== undefined) {
|
|
55
|
+
throw invalidParameter(
|
|
56
|
+
'Parameters [deviceId] and [passphraseState] are not supported by openWalletSession. Pass passphraseState on later address and signing calls.'
|
|
57
|
+
);
|
|
78
58
|
}
|
|
79
|
-
return {
|
|
80
|
-
mode: OpenWalletSessionMode.ResumeHidden,
|
|
81
|
-
deviceId: requiredString(payload.deviceId, 'deviceId'),
|
|
82
|
-
passphraseState: requiredString(payload.passphraseState, 'passphraseState'),
|
|
83
|
-
};
|
|
59
|
+
return { mode: payload.mode };
|
|
84
60
|
};
|
|
85
61
|
|
|
86
62
|
export default class OpenWalletSession extends BaseMethod<OpenWalletSessionParams> {
|
|
@@ -196,52 +172,6 @@ export default class OpenWalletSession extends BaseMethod<OpenWalletSessionParam
|
|
|
196
172
|
};
|
|
197
173
|
}
|
|
198
174
|
|
|
199
|
-
if (this.params.mode === OpenWalletSessionMode.ResumeHidden) {
|
|
200
|
-
if (isProtocolV2) {
|
|
201
|
-
await ensureProtocolV2WalletStatus();
|
|
202
|
-
const refreshedDeviceId = requireDeviceId();
|
|
203
|
-
if (refreshedDeviceId !== this.params.deviceId) {
|
|
204
|
-
deviceWalletSessionStore.delete(this.params.deviceId, this.params.passphraseState);
|
|
205
|
-
throw ERRORS.TypedError(HardwareErrorCode.DeviceCheckDeviceIdError);
|
|
206
|
-
}
|
|
207
|
-
} else if (requireDeviceId() !== this.params.deviceId) {
|
|
208
|
-
throw ERRORS.TypedError(HardwareErrorCode.DeviceCheckDeviceIdError);
|
|
209
|
-
}
|
|
210
|
-
this.device.passphraseState = this.params.passphraseState;
|
|
211
|
-
const cachedSessionId = deviceWalletSessionStore.get(
|
|
212
|
-
this.params.deviceId,
|
|
213
|
-
this.params.passphraseState
|
|
214
|
-
);
|
|
215
|
-
if (!cachedSessionId && !isProtocolV2) {
|
|
216
|
-
throw ERRORS.TypedError(HardwareErrorCode.WalletSessionInvalid);
|
|
217
|
-
}
|
|
218
|
-
if (!isProtocolV2) {
|
|
219
|
-
await this.device.initialize({
|
|
220
|
-
deviceId: this.params.deviceId,
|
|
221
|
-
passphraseState: this.params.passphraseState,
|
|
222
|
-
});
|
|
223
|
-
}
|
|
224
|
-
const session = isProtocolV2
|
|
225
|
-
? await getProtocolV2WalletSession(this.device, {
|
|
226
|
-
expectedPassphraseState: this.params.passphraseState,
|
|
227
|
-
})
|
|
228
|
-
: await getPassphraseStateWithRefreshDeviceInfo(this.device, {
|
|
229
|
-
expectPassphraseState: this.params.passphraseState,
|
|
230
|
-
});
|
|
231
|
-
const deviceId = requireDeviceId();
|
|
232
|
-
if (session.passphraseState !== this.params.passphraseState) {
|
|
233
|
-
this.device.clearInternalState();
|
|
234
|
-
throw ERRORS.TypedError(HardwareErrorCode.DeviceCheckPassphraseStateError);
|
|
235
|
-
}
|
|
236
|
-
return {
|
|
237
|
-
protocol,
|
|
238
|
-
walletType: 'hidden',
|
|
239
|
-
deviceId,
|
|
240
|
-
...requireHiddenWalletResponse(session),
|
|
241
|
-
resumed: wasResumed(session) || (!isProtocolV2 && session.newSession === cachedSessionId),
|
|
242
|
-
};
|
|
243
|
-
}
|
|
244
|
-
|
|
245
175
|
this.device.passphraseState = undefined;
|
|
246
176
|
const walletStatus = await ensureProtocolV2WalletStatus();
|
|
247
177
|
if (isProtocolV2 && walletStatus.status.passphraseProtection !== true) {
|
|
@@ -7,6 +7,8 @@ import FileWrite from './FileWrite';
|
|
|
7
7
|
export type UploadPortfolioParams = {
|
|
8
8
|
packageBase64: string;
|
|
9
9
|
timeoutMs?: number | string;
|
|
10
|
+
/** Controls transfer progress UI events. Defaults to `silent`. */
|
|
11
|
+
uiMode?: 'silent' | 'progress';
|
|
10
12
|
};
|
|
11
13
|
|
|
12
14
|
const PORTFOLIO_PENDING_PATH = 'vol1:/portfolio/portfolio.okpkg.pending';
|
|
@@ -17,7 +19,7 @@ const PORTFOLIO_UPDATE_MESSAGE_TYPE = 61400;
|
|
|
17
19
|
|
|
18
20
|
export default class UploadPortfolio extends FileWrite {
|
|
19
21
|
init() {
|
|
20
|
-
const { packageBase64, timeoutMs } = this.payload as UploadPortfolioParams;
|
|
22
|
+
const { packageBase64, timeoutMs, uiMode = 'silent' } = this.payload as UploadPortfolioParams;
|
|
21
23
|
const packageBytes = decodeCanonicalBase64({
|
|
22
24
|
value: packageBase64,
|
|
23
25
|
parameterName: 'packageBase64',
|
|
@@ -31,13 +33,12 @@ export default class UploadPortfolio extends FileWrite {
|
|
|
31
33
|
chunkSize: PORTFOLIO_CHUNK_SIZE,
|
|
32
34
|
overwrite: true,
|
|
33
35
|
append: false,
|
|
34
|
-
emitProgress:
|
|
36
|
+
emitProgress: uiMode === 'progress',
|
|
35
37
|
timeoutMs,
|
|
36
38
|
};
|
|
37
39
|
super.init();
|
|
38
40
|
this.unlockPolicy = 'none';
|
|
39
|
-
|
|
40
|
-
this.protocolV2UiMode = 'none';
|
|
41
|
+
this.protocolV2UiMode = uiMode === 'progress' ? 'auto' : 'none';
|
|
41
42
|
}
|
|
42
43
|
|
|
43
44
|
async run() {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import semver from 'semver';
|
|
2
2
|
import {
|
|
3
|
+
EDeviceType,
|
|
3
4
|
ERRORS,
|
|
4
5
|
HardwareError,
|
|
5
6
|
HardwareErrorCode,
|
|
@@ -15,6 +16,7 @@ import { DEVICE, IFRAME, createUiMessage } from '../../events';
|
|
|
15
16
|
import { UI_REQUEST } from '../../constants/ui-request';
|
|
16
17
|
import { onDeviceButtonHandler } from '../../core';
|
|
17
18
|
import { runMethodWithUnlockPolicy } from '../../protocols/protocol-v2/unlockPolicyRunner';
|
|
19
|
+
import { supportsProtocolV2Message } from '../../protocols/protocol-v2/features';
|
|
18
20
|
import {
|
|
19
21
|
completeRequestContext,
|
|
20
22
|
createRequestContext,
|
|
@@ -22,6 +24,7 @@ import {
|
|
|
22
24
|
} from '../../utils/tracing';
|
|
23
25
|
|
|
24
26
|
import type { Device, DeviceEvents } from '../../device/Device';
|
|
27
|
+
import type { DeviceCommands } from '../../device/DeviceCommands';
|
|
25
28
|
import type { CoreApi } from '../../types';
|
|
26
29
|
import type {
|
|
27
30
|
AllNetworkAddress,
|
|
@@ -266,6 +269,10 @@ export default abstract class AllNetworkGetAddressBase extends BaseMethod<
|
|
|
266
269
|
|
|
267
270
|
abortController: AbortController | null = null;
|
|
268
271
|
|
|
272
|
+
protected loadingCleanupInBackground = false;
|
|
273
|
+
|
|
274
|
+
private loadingCommands?: DeviceCommands;
|
|
275
|
+
|
|
269
276
|
init() {
|
|
270
277
|
this.checkDeviceId = true;
|
|
271
278
|
this.allowDeviceMode = [...this.allowDeviceMode, UI_REQUEST.NOT_INITIALIZE];
|
|
@@ -457,30 +464,64 @@ export default abstract class AllNetworkGetAddressBase extends BaseMethod<
|
|
|
457
464
|
|
|
458
465
|
abstract getAllNetworkAddress(rootFingerprint: number): Promise<AllNetworkAddress[]>;
|
|
459
466
|
|
|
467
|
+
protected async stopAllNetworkLoading(canSend = true) {
|
|
468
|
+
const commands = this.loadingCommands;
|
|
469
|
+
this.loadingCommands = undefined;
|
|
470
|
+
// Never send cleanup through a replacement or disposed connection.
|
|
471
|
+
if (!canSend || !commands || commands.disposed || commands !== this.device.commands) return;
|
|
472
|
+
try {
|
|
473
|
+
await commands.typedCall('DeviceAnimationControl', 'Success', {
|
|
474
|
+
action: PROTO.DeviceAnimationAction.AnimationAction_Stop,
|
|
475
|
+
});
|
|
476
|
+
} catch {
|
|
477
|
+
// Cleanup must not mask the operation result. Firmware also has an idle timeout.
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
|
|
460
481
|
async run() {
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
482
|
+
this.loadingCleanupInBackground = false;
|
|
483
|
+
try {
|
|
484
|
+
if (this.device.isProtocolV2() && this.device.getCurrentDeviceType() === EDeviceType.Pro2) {
|
|
485
|
+
const protocolInfo = await this.device.ensureProtocolV2RuntimeContext();
|
|
486
|
+
if (supportsProtocolV2Message(protocolInfo, 60461)) {
|
|
487
|
+
const { commands } = this.device;
|
|
488
|
+
await commands.typedCall('DeviceAnimationControl', 'Success', {
|
|
489
|
+
action: PROTO.DeviceAnimationAction.AnimationAction_Start,
|
|
490
|
+
});
|
|
491
|
+
this.loadingCommands = commands;
|
|
492
|
+
}
|
|
493
|
+
}
|
|
467
494
|
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
495
|
+
const res = await this.device.commands.typedCall('GetPublicKey', 'PublicKey', {
|
|
496
|
+
address_n: [toHardened(44), toHardened(1), toHardened(0)],
|
|
497
|
+
coin_name: 'Testnet',
|
|
498
|
+
script_type: 'SPENDADDRESS',
|
|
499
|
+
show_display: false,
|
|
500
|
+
});
|
|
471
501
|
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
502
|
+
if (!this.device.isProtocolV2()) {
|
|
503
|
+
this.postMessage(createUiMessage(UI_REQUEST.CLOSE_UI_PIN_WINDOW));
|
|
504
|
+
}
|
|
475
505
|
|
|
476
|
-
|
|
506
|
+
if (res.message.root_fingerprint == null) {
|
|
507
|
+
throw ERRORS.TypedError(HardwareErrorCode.CallMethodInvalidParameter);
|
|
508
|
+
}
|
|
477
509
|
|
|
478
|
-
|
|
510
|
+
this.abortController = new AbortController();
|
|
511
|
+
|
|
512
|
+
return await this.getAllNetworkAddress(res.message.root_fingerprint);
|
|
513
|
+
} catch (e) {
|
|
514
|
+
// A failed call may have invalidated the transport link without disposing
|
|
515
|
+
// DeviceCommands. Let firmware time out instead of reconnecting for Stop.
|
|
516
|
+
await this.stopAllNetworkLoading(false);
|
|
479
517
|
if (e instanceof HardwareError && e.errorCode === HardwareErrorCode.RepeatUnlocking) {
|
|
480
518
|
throw ERRORS.TypedError(HardwareErrorCode.RepeatUnlocking, e.message);
|
|
481
519
|
}
|
|
482
520
|
throw e;
|
|
483
|
-
}
|
|
521
|
+
} finally {
|
|
522
|
+
// The callback API returns before its chain requests finish.
|
|
523
|
+
if (!this.loadingCleanupInBackground) await this.stopAllNetworkLoading();
|
|
524
|
+
}
|
|
484
525
|
}
|
|
485
526
|
}
|
|
486
527
|
|
|
@@ -28,6 +28,7 @@ export default class AllNetworkGetAddressByLoop extends AllNetworkGetAddressBase
|
|
|
28
28
|
const bundle = this.payload.bundle || [this.payload];
|
|
29
29
|
|
|
30
30
|
// process callbacks in background
|
|
31
|
+
this.loadingCleanupInBackground = true;
|
|
31
32
|
const callbackPromise = this.processCallbacksInBackground(
|
|
32
33
|
bundle,
|
|
33
34
|
rootFingerprint,
|
|
@@ -95,6 +96,7 @@ export default class AllNetworkGetAddressByLoop extends AllNetworkGetAddressBase
|
|
|
95
96
|
data: allResults,
|
|
96
97
|
});
|
|
97
98
|
} catch (error: any) {
|
|
99
|
+
await this.stopAllNetworkLoading(false);
|
|
98
100
|
let errorCode = error.errorCode || error.code;
|
|
99
101
|
let errorMessage = error.message;
|
|
100
102
|
|
|
@@ -121,6 +123,7 @@ export default class AllNetworkGetAddressByLoop extends AllNetworkGetAddressBase
|
|
|
121
123
|
},
|
|
122
124
|
});
|
|
123
125
|
} finally {
|
|
126
|
+
await this.stopAllNetworkLoading();
|
|
124
127
|
this.context?.cancelCallbackTasks(this.payload.connectId);
|
|
125
128
|
this.abortController = null;
|
|
126
129
|
}
|
|
@@ -22,6 +22,14 @@ export default class DeviceVerify extends BaseMethod<BixinVerifyDeviceRequest> {
|
|
|
22
22
|
// the main PIN or an Attach PIN may authorize them.
|
|
23
23
|
this.protocolV2PreUnlockPinType = DeviceSessionPinType.Any;
|
|
24
24
|
this.useDevicePassphraseState = false;
|
|
25
|
+
this.protocolV2UiInteraction = {
|
|
26
|
+
request: 'button',
|
|
27
|
+
source: 'method-lifecycle',
|
|
28
|
+
reason: 'device-management',
|
|
29
|
+
completion: 'operation-completed',
|
|
30
|
+
deviceOnly: true,
|
|
31
|
+
operation: 'deviceVerify',
|
|
32
|
+
};
|
|
25
33
|
|
|
26
34
|
// check payload
|
|
27
35
|
validateParams(this.payload, [{ name: 'dataHex', type: 'hexString' }]);
|
|
@@ -1,11 +1,36 @@
|
|
|
1
1
|
import { UI_REQUEST } from '../../constants/ui-request';
|
|
2
2
|
import { validatePath } from '../helpers/pathUtils';
|
|
3
3
|
import { BaseMethod } from '../BaseMethod';
|
|
4
|
-
import { validateParams } from '../helpers/paramsValidator';
|
|
5
|
-
import { stripHexPrefix } from '../helpers/hexUtils';
|
|
4
|
+
import { invalidParameter, validateParams } from '../helpers/paramsValidator';
|
|
5
|
+
import { addHexPrefix, isHexString, stripHexPrefix } from '../helpers/hexUtils';
|
|
6
6
|
|
|
7
7
|
import type { SolanaSignOffChainMessage as HardwareSolSignOffChainMessage } from '@onekeyfe/hd-transport';
|
|
8
8
|
|
|
9
|
+
const SOLANA_PUBLIC_KEY_LENGTH = 32;
|
|
10
|
+
const SOLANA_APPLICATION_DOMAIN_LENGTH = 32;
|
|
11
|
+
|
|
12
|
+
const normalizeRequiredSigners = (requiredSigners: unknown[] = []): string[] => {
|
|
13
|
+
const normalized = requiredSigners.map((signer, index) => {
|
|
14
|
+
if (
|
|
15
|
+
typeof signer !== 'string' ||
|
|
16
|
+
!isHexString(addHexPrefix(signer), SOLANA_PUBLIC_KEY_LENGTH)
|
|
17
|
+
) {
|
|
18
|
+
throw invalidParameter(
|
|
19
|
+
`Parameter [requiredSigners][${index}] must be a ${SOLANA_PUBLIC_KEY_LENGTH}-byte hex public key.`
|
|
20
|
+
);
|
|
21
|
+
}
|
|
22
|
+
return stripHexPrefix(signer).toLowerCase();
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
for (let index = 1; index < normalized.length; index += 1) {
|
|
26
|
+
if (normalized[index - 1] >= normalized[index]) {
|
|
27
|
+
throw invalidParameter('Parameter [requiredSigners] must be strictly sorted and unique.');
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return normalized;
|
|
32
|
+
};
|
|
33
|
+
|
|
9
34
|
export default class SolSignOffchainMessage extends BaseMethod<HardwareSolSignOffChainMessage> {
|
|
10
35
|
getSupportedProtocols() {
|
|
11
36
|
return ['V1', 'V2'] as const;
|
|
@@ -23,10 +48,24 @@ export default class SolSignOffchainMessage extends BaseMethod<HardwareSolSignOf
|
|
|
23
48
|
{ name: 'messageVersion', type: 'number', required: false },
|
|
24
49
|
{ name: 'messageFormat', type: 'number', required: false },
|
|
25
50
|
{ name: 'applicationDomainHex', type: 'hexString', required: false },
|
|
51
|
+
{ name: 'requiredSigners', type: 'array', required: false, allowEmpty: true },
|
|
26
52
|
]);
|
|
27
53
|
|
|
28
|
-
const {
|
|
54
|
+
const {
|
|
55
|
+
path,
|
|
56
|
+
messageHex,
|
|
57
|
+
messageVersion,
|
|
58
|
+
messageFormat,
|
|
59
|
+
applicationDomainHex,
|
|
60
|
+
requiredSigners,
|
|
61
|
+
} = this.payload;
|
|
29
62
|
const addressN = validatePath(path, 3);
|
|
63
|
+
if (
|
|
64
|
+
applicationDomainHex !== undefined &&
|
|
65
|
+
!isHexString(addHexPrefix(applicationDomainHex), SOLANA_APPLICATION_DOMAIN_LENGTH)
|
|
66
|
+
) {
|
|
67
|
+
throw invalidParameter('Parameter [applicationDomainHex] must be 32 bytes.');
|
|
68
|
+
}
|
|
30
69
|
|
|
31
70
|
// init params
|
|
32
71
|
this.params = {
|
|
@@ -34,7 +73,8 @@ export default class SolSignOffchainMessage extends BaseMethod<HardwareSolSignOf
|
|
|
34
73
|
message: stripHexPrefix(messageHex),
|
|
35
74
|
message_version: messageVersion ?? undefined,
|
|
36
75
|
message_format: messageFormat ?? undefined,
|
|
37
|
-
application_domain: applicationDomainHex
|
|
76
|
+
application_domain: applicationDomainHex ? stripHexPrefix(applicationDomainHex) : undefined,
|
|
77
|
+
required_signers: normalizeRequiredSigners(requiredSigners),
|
|
38
78
|
};
|
|
39
79
|
}
|
|
40
80
|
|
package/src/core/RequestQueue.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { ERRORS, HardwareErrorCode } from '@onekeyfe/hd-shared';
|
|
2
|
+
|
|
1
3
|
import { LoggerNames, getLogger } from '../utils';
|
|
2
4
|
|
|
3
5
|
import type { Deferred } from '@onekeyfe/hd-shared';
|
|
@@ -40,6 +42,24 @@ export default class RequestQueue {
|
|
|
40
42
|
return this.requestQueue.get(requestId);
|
|
41
43
|
}
|
|
42
44
|
|
|
45
|
+
public async waitForTask<T>(task: RequestTask, pending: () => Promise<T>): Promise<T> {
|
|
46
|
+
const signal = task.method.abortSignal;
|
|
47
|
+
const cancellationError = () => ERRORS.TypedError(HardwareErrorCode.CallQueueActionCancelled);
|
|
48
|
+
if (signal?.aborted) throw cancellationError();
|
|
49
|
+
let onAbort: (() => void) | undefined;
|
|
50
|
+
try {
|
|
51
|
+
const cancelled = new Promise<never>((_, reject) => {
|
|
52
|
+
onAbort = () => reject(cancellationError());
|
|
53
|
+
signal?.addEventListener('abort', onAbort, { once: true });
|
|
54
|
+
});
|
|
55
|
+
const result = await Promise.race([pending(), cancelled]);
|
|
56
|
+
if (signal?.aborted) throw cancellationError();
|
|
57
|
+
return result;
|
|
58
|
+
} finally {
|
|
59
|
+
if (onAbort) signal?.removeEventListener('abort', onAbort);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
43
63
|
// 获取请求的AbortController
|
|
44
64
|
public getAbortController(requestId: number) {
|
|
45
65
|
return this.requestQueue.get(requestId)?.abortController;
|