@onekeyfe/hd-core 1.2.8 → 1.2.10-alpha.2
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 +27 -0
- package/__tests__/DeviceCommands.test.ts +66 -13
- package/__tests__/allNetworkGetAddress-privacy-networks.test.ts +77 -0
- package/__tests__/allNetworkGetAddress-skippable.test.ts +41 -0
- package/__tests__/btc-derive-context-hash.test.ts +200 -0
- package/__tests__/core-dispose.test.ts +114 -1
- package/__tests__/core-error-output.test.ts +55 -3
- package/__tests__/device-connector-protocol.test.ts +5 -2
- package/__tests__/device-lifecycle-events.test.ts +191 -27
- package/__tests__/device-pool-state.test.ts +50 -0
- package/__tests__/firmware-update/firmware-artifact-source.test.ts +11 -0
- package/__tests__/logBlockEvent.test.ts +331 -9
- package/__tests__/logger.test.ts +18 -0
- package/__tests__/monero-get-address.test.ts +147 -0
- package/__tests__/monero-key-image-sync.test.ts +272 -0
- package/__tests__/monero-sign-transaction.test.ts +436 -0
- package/__tests__/protocol-v2-bootloader-mode.test.ts +43 -2
- package/__tests__/protocol-v2-key-export-confirmation.test.ts +75 -0
- package/__tests__/protocol-v2-resources.test.ts +14 -0
- package/__tests__/protocol-v2.test.ts +841 -46
- package/__tests__/protocolV2FileWrite.test.ts +454 -7
- package/__tests__/refresh-device-state.test.ts +19 -17
- package/__tests__/search-devices.test.ts +36 -0
- package/__tests__/sol-sign-offchain-message.test.ts +8 -0
- package/__tests__/usb-identity-probe.test.ts +358 -0
- package/__tests__/zcash-protobuf.test.ts +152 -0
- package/__tests__/zcash-sign-pczt.test.ts +525 -0
- package/dist/api/BaseMethod.d.ts.map +1 -1
- package/dist/api/FileWrite.d.ts.map +1 -1
- package/dist/api/FirmwareUpdateV4.d.ts +5 -0
- package/dist/api/FirmwareUpdateV4.d.ts.map +1 -1
- package/dist/api/GetDeviceState.d.ts.map +1 -1
- package/dist/api/SearchDevices.d.ts +21 -1
- package/dist/api/SearchDevices.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/btc/BTCDeriveContextHash.d.ts +16 -0
- package/dist/api/btc/BTCDeriveContextHash.d.ts.map +1 -0
- package/dist/api/firmware/FirmwareArtifactSource.d.ts.map +1 -1
- package/dist/api/helpers/pathUtils.d.ts +1 -0
- package/dist/api/helpers/pathUtils.d.ts.map +1 -1
- package/dist/api/helpers/protocolV2FileWrite.d.ts +5 -1
- package/dist/api/helpers/protocolV2FileWrite.d.ts.map +1 -1
- package/dist/api/index.d.ts +8 -0
- package/dist/api/index.d.ts.map +1 -1
- package/dist/api/monero/MoneroGetAddress.d.ts +11 -0
- package/dist/api/monero/MoneroGetAddress.d.ts.map +1 -0
- package/dist/api/monero/MoneroGetWatchKey.d.ts +11 -0
- package/dist/api/monero/MoneroGetWatchKey.d.ts.map +1 -0
- package/dist/api/monero/MoneroKeyImageSync.d.ts +20 -0
- package/dist/api/monero/MoneroKeyImageSync.d.ts.map +1 -0
- package/dist/api/monero/MoneroSignTransaction.d.ts +17 -0
- package/dist/api/monero/MoneroSignTransaction.d.ts.map +1 -0
- package/dist/api/monero/helpers.d.ts +5 -0
- package/dist/api/monero/helpers.d.ts.map +1 -0
- package/dist/api/protocol-v2/DeviceReboot.d.ts.map +1 -1
- package/dist/api/protocol-v2/DeviceUploadNft.d.ts.map +1 -1
- package/dist/api/protocol-v2/DeviceUploadWallpaper.d.ts.map +1 -1
- package/dist/api/zcash/ZcashGetAddress.d.ts +12 -0
- package/dist/api/zcash/ZcashGetAddress.d.ts.map +1 -0
- package/dist/api/zcash/ZcashGetFullViewingKey.d.ts +12 -0
- package/dist/api/zcash/ZcashGetFullViewingKey.d.ts.map +1 -0
- package/dist/api/zcash/ZcashSignPczt.d.ts +16 -0
- package/dist/api/zcash/ZcashSignPczt.d.ts.map +1 -0
- package/dist/api/zcash/helpers.d.ts +2 -0
- package/dist/api/zcash/helpers.d.ts.map +1 -0
- package/dist/core/index.d.ts +3 -2
- package/dist/core/index.d.ts.map +1 -1
- package/dist/device/Device.d.ts +3 -0
- package/dist/device/Device.d.ts.map +1 -1
- package/dist/device/DeviceCommands.d.ts +4 -4
- package/dist/device/DeviceCommands.d.ts.map +1 -1
- package/dist/device/DevicePool.d.ts.map +1 -1
- package/dist/events/logBlockEvent.d.ts +10 -0
- package/dist/events/logBlockEvent.d.ts.map +1 -1
- package/dist/events/ui-request.d.ts +2 -0
- package/dist/events/ui-request.d.ts.map +1 -1
- package/dist/index.d.ts +206 -7
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4443 -2797
- package/dist/inject.d.ts.map +1 -1
- package/dist/protocols/protocol-v2/resources.d.ts +1 -0
- package/dist/protocols/protocol-v2/resources.d.ts.map +1 -1
- package/dist/protocols/protocol-v2/uiInteraction.d.ts +1 -0
- package/dist/protocols/protocol-v2/uiInteraction.d.ts.map +1 -1
- package/dist/types/api/allNetworkGetAddress.d.ts +4 -1
- package/dist/types/api/allNetworkGetAddress.d.ts.map +1 -1
- package/dist/types/api/btcDeriveContextHash.d.ts +10 -0
- package/dist/types/api/btcDeriveContextHash.d.ts.map +1 -0
- package/dist/types/api/export.d.ts +8 -0
- package/dist/types/api/export.d.ts.map +1 -1
- package/dist/types/api/index.d.ts +16 -0
- package/dist/types/api/index.d.ts.map +1 -1
- package/dist/types/api/moneroGetAddress.d.ts +15 -0
- package/dist/types/api/moneroGetAddress.d.ts.map +1 -0
- package/dist/types/api/moneroGetWatchKey.d.ts +15 -0
- package/dist/types/api/moneroGetWatchKey.d.ts.map +1 -0
- package/dist/types/api/moneroKeyImageSync.d.ts +24 -0
- package/dist/types/api/moneroKeyImageSync.d.ts.map +1 -0
- package/dist/types/api/moneroSignTransaction.d.ts +32 -0
- package/dist/types/api/moneroSignTransaction.d.ts.map +1 -0
- package/dist/types/api/zcashGetAddress.d.ts +20 -0
- package/dist/types/api/zcashGetAddress.d.ts.map +1 -0
- package/dist/types/api/zcashGetFullViewingKey.d.ts +13 -0
- package/dist/types/api/zcashGetFullViewingKey.d.ts.map +1 -0
- package/dist/types/api/zcashSignPczt.d.ts +9 -0
- package/dist/types/api/zcashSignPczt.d.ts.map +1 -0
- package/dist/types/device.d.ts +10 -0
- package/dist/types/device.d.ts.map +1 -1
- package/dist/types/params.d.ts +1 -0
- package/dist/types/params.d.ts.map +1 -1
- package/dist/utils/logger.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/BaseMethod.ts +2 -0
- package/src/api/FileWrite.ts +7 -1
- package/src/api/FirmwareUpdateV4.ts +366 -99
- package/src/api/GetDeviceState.ts +2 -5
- package/src/api/SearchDevices.ts +172 -7
- package/src/api/allnetwork/AllNetworkGetAddressBase.ts +46 -6
- package/src/api/btc/BTCDeriveContextHash.ts +90 -0
- package/src/api/firmware/FirmwareArtifactSource.ts +12 -3
- package/src/api/helpers/pathUtils.ts +23 -0
- package/src/api/helpers/protocolV2FileWrite.ts +382 -111
- package/src/api/index.ts +9 -0
- package/src/api/monero/MoneroGetAddress.ts +71 -0
- package/src/api/monero/MoneroGetWatchKey.ts +75 -0
- package/src/api/monero/MoneroKeyImageSync.ts +170 -0
- package/src/api/monero/MoneroSignTransaction.ts +319 -0
- package/src/api/monero/helpers.ts +28 -0
- package/src/api/protocol-v2/DeviceReboot.ts +2 -0
- package/src/api/protocol-v2/DeviceUploadNft.ts +6 -1
- package/src/api/protocol-v2/DeviceUploadWallpaper.ts +15 -1
- package/src/api/zcash/ZcashGetAddress.ts +108 -0
- package/src/api/zcash/ZcashGetFullViewingKey.ts +78 -0
- package/src/api/zcash/ZcashSignPczt.ts +152 -0
- package/src/api/zcash/helpers.ts +16 -0
- package/src/core/index.ts +52 -24
- package/src/data/messages/messages-protocol-v2.json +152 -0
- package/src/data/messages/messages.json +51 -0
- package/src/device/Device.ts +32 -4
- package/src/device/DeviceCommands.ts +15 -1
- package/src/device/DeviceConnector.ts +4 -4
- package/src/device/DevicePool.ts +12 -2
- package/src/events/core.ts +2 -2
- package/src/events/logBlockEvent.ts +58 -3
- package/src/events/ui-request.ts +2 -0
- package/src/index.ts +1 -0
- package/src/inject.ts +17 -0
- package/src/protocols/protocol-v2/resources.ts +6 -1
- package/src/protocols/protocol-v2/uiInteraction.ts +1 -1
- package/src/types/api/allNetworkGetAddress.ts +8 -1
- package/src/types/api/btcDeriveContextHash.ts +15 -0
- package/src/types/api/export.ts +17 -0
- package/src/types/api/index.ts +24 -0
- package/src/types/api/moneroGetAddress.ts +27 -0
- package/src/types/api/moneroGetWatchKey.ts +28 -0
- package/src/types/api/moneroKeyImageSync.ts +45 -0
- package/src/types/api/moneroSignTransaction.ts +54 -0
- package/src/types/api/zcashGetAddress.ts +32 -0
- package/src/types/api/zcashGetFullViewingKey.ts +24 -0
- package/src/types/api/zcashSignPczt.ts +18 -0
- package/src/types/device.ts +18 -0
- package/src/types/params.ts +1 -1
- package/src/utils/logger.ts +4 -0
|
@@ -1,13 +1,20 @@
|
|
|
1
1
|
import {
|
|
2
2
|
PROTOCOL_V2_BLE_FILE_CHUNK_SIZE,
|
|
3
|
+
PROTOCOL_V2_FILE_WRITE_PIPELINE_WINDOW,
|
|
3
4
|
PROTOCOL_V2_WEBUSB_FILE_CHUNK_SIZE,
|
|
4
5
|
} from '@onekeyfe/hd-transport';
|
|
5
|
-
import { ERRORS, HardwareErrorCode } from '@onekeyfe/hd-shared';
|
|
6
|
+
import { EDeviceType, ERRORS, HardwareErrorCode } from '@onekeyfe/hd-shared';
|
|
7
|
+
import semver from 'semver';
|
|
6
8
|
|
|
7
9
|
import { DataManager } from '../../data-manager';
|
|
8
10
|
import { LoggerNames, getLogger } from '../../utils/logger';
|
|
11
|
+
import { runMethodWithUnlockPolicy } from '../../protocols/protocol-v2/unlockPolicyRunner';
|
|
12
|
+
import { ProtocolV2UiInteractionCoordinator } from '../../protocols/protocol-v2/uiInteraction';
|
|
13
|
+
import { DevicePool } from '../../device/DevicePool';
|
|
14
|
+
import { DEVICE } from '../../events';
|
|
9
15
|
|
|
10
16
|
import type { DeviceCommands } from '../../device/DeviceCommands';
|
|
17
|
+
import type { BaseMethod } from '../BaseMethod';
|
|
11
18
|
|
|
12
19
|
const Log = getLogger(LoggerNames.Core);
|
|
13
20
|
|
|
@@ -19,6 +26,7 @@ export type ProtocolV2FileWriteProgress = {
|
|
|
19
26
|
totalBytes: number;
|
|
20
27
|
rateBytesPerSecond?: number;
|
|
21
28
|
elapsedMs: number;
|
|
29
|
+
retryCount?: number;
|
|
22
30
|
};
|
|
23
31
|
|
|
24
32
|
export type ProtocolV2FileWriteChunkContext = {
|
|
@@ -28,7 +36,7 @@ export type ProtocolV2FileWriteChunkContext = {
|
|
|
28
36
|
};
|
|
29
37
|
|
|
30
38
|
export type ProtocolV2FileWriteOptions = {
|
|
31
|
-
commands: Pick<DeviceCommands, 'typedCall'
|
|
39
|
+
commands: Pick<DeviceCommands, 'typedCall'> & Partial<Pick<DeviceCommands, 'device'>>;
|
|
32
40
|
path: string;
|
|
33
41
|
data: ProtocolV2FileWriteData;
|
|
34
42
|
offset?: number;
|
|
@@ -44,6 +52,8 @@ export type ProtocolV2FileWriteOptions = {
|
|
|
44
52
|
timeoutMs?: number;
|
|
45
53
|
writeWithResponse?: boolean;
|
|
46
54
|
maxChunkRetries?: number;
|
|
55
|
+
/** Reacquire and verify the device before restarting an overwriting asset upload. */
|
|
56
|
+
recoverConnection?: () => Promise<ProtocolV2FileWriteOptions['commands']>;
|
|
47
57
|
paceMs?: number;
|
|
48
58
|
throwIfAborted?: () => void;
|
|
49
59
|
getUiPercentage?: (context: ProtocolV2FileWriteChunkContext) => number | undefined;
|
|
@@ -54,6 +64,109 @@ const MIN_FILE_CHUNK_SIZE = 64;
|
|
|
54
64
|
const FILE_TRANSFER_RATE_WINDOW_MS = 1000;
|
|
55
65
|
const FILE_TRANSFER_LOG_INTERVAL_MS = 10_000;
|
|
56
66
|
const SESSION_ERROR = 'session not found';
|
|
67
|
+
// Application firmware from firmware-pro2 #239 has the 4096-byte UART FIFO required for
|
|
68
|
+
// two 1800-byte chunks. Gate on the running application, not the installed bootloader.
|
|
69
|
+
const ASSET_FILE_WRITE_MIN_APPLICATION_VERSION = '1.0.3';
|
|
70
|
+
const ASSET_FILE_WRITE_RESPONSE_TIMEOUT = 10_000;
|
|
71
|
+
|
|
72
|
+
export function createProtocolV2AssetFileWriteRecovery(
|
|
73
|
+
method: Pick<
|
|
74
|
+
BaseMethod,
|
|
75
|
+
| 'device'
|
|
76
|
+
| 'abortSignal'
|
|
77
|
+
| 'postMessage'
|
|
78
|
+
| 'name'
|
|
79
|
+
| 'unlockPolicy'
|
|
80
|
+
| 'useDevicePassphraseState'
|
|
81
|
+
| 'protocolV2PreUnlockPinType'
|
|
82
|
+
| 'protocolV2UiMode'
|
|
83
|
+
| 'payload'
|
|
84
|
+
>
|
|
85
|
+
): () => Promise<ProtocolV2FileWriteOptions['commands']> {
|
|
86
|
+
const { device } = method;
|
|
87
|
+
const expectedSerialNo = device.state?.identity?.serialNo;
|
|
88
|
+
const expectedDeviceType = device.state?.identity?.deviceType;
|
|
89
|
+
return async () => {
|
|
90
|
+
if (method.abortSignal?.aborted) {
|
|
91
|
+
throw ERRORS.TypedError(HardwareErrorCode.CallQueueActionCancelled);
|
|
92
|
+
}
|
|
93
|
+
if (!expectedSerialNo) {
|
|
94
|
+
throw ERRORS.TypedError(HardwareErrorCode.DeviceCheckDeviceIdError);
|
|
95
|
+
}
|
|
96
|
+
if (DataManager.isBleConnect(DataManager.getSettings('env'))) {
|
|
97
|
+
// Acquire may otherwise reuse a native connection with an uncertain in-flight write.
|
|
98
|
+
await device.deviceConnector?.disconnect(device.originalDescriptor.id);
|
|
99
|
+
if (method.abortSignal?.aborted) {
|
|
100
|
+
throw ERRORS.TypedError(HardwareErrorCode.CallQueueActionCancelled);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
await device.acquire('V2', { throwOnRunPromiseError: true });
|
|
104
|
+
if (method.abortSignal?.aborted) {
|
|
105
|
+
throw ERRORS.TypedError(HardwareErrorCode.CallQueueActionCancelled);
|
|
106
|
+
}
|
|
107
|
+
await device.initialize();
|
|
108
|
+
if (
|
|
109
|
+
!device.isProtocolV2() ||
|
|
110
|
+
device.state?.identity.serialNo !== expectedSerialNo ||
|
|
111
|
+
device.state?.identity.deviceType !== expectedDeviceType ||
|
|
112
|
+
!['normal', 'notInitialized'].includes(device.state?.status.mode ?? '')
|
|
113
|
+
) {
|
|
114
|
+
throw ERRORS.TypedError(HardwareErrorCode.DeviceCheckDeviceIdError);
|
|
115
|
+
}
|
|
116
|
+
if (method.abortSignal?.aborted) {
|
|
117
|
+
throw ERRORS.TypedError(HardwareErrorCode.CallQueueActionCancelled);
|
|
118
|
+
}
|
|
119
|
+
// Re-run the existing unlock policy with a fresh preflight after reconnecting.
|
|
120
|
+
await runMethodWithUnlockPolicy(
|
|
121
|
+
{
|
|
122
|
+
name: method.name,
|
|
123
|
+
unlockPolicy: method.unlockPolicy,
|
|
124
|
+
useDevicePassphraseState: method.useDevicePassphraseState,
|
|
125
|
+
protocolV2PreUnlockPinType: method.protocolV2PreUnlockPinType,
|
|
126
|
+
protocolV2UiMode: method.protocolV2UiMode,
|
|
127
|
+
payload: method.payload,
|
|
128
|
+
params: undefined,
|
|
129
|
+
run: () => Promise.resolve(undefined),
|
|
130
|
+
},
|
|
131
|
+
device,
|
|
132
|
+
{ uiCoordinator: new ProtocolV2UiInteractionCoordinator(device, method.postMessage) }
|
|
133
|
+
);
|
|
134
|
+
if (method.abortSignal?.aborted) {
|
|
135
|
+
throw ERRORS.TypedError(HardwareErrorCode.CallQueueActionCancelled);
|
|
136
|
+
}
|
|
137
|
+
DevicePool.emitter.emit(DEVICE.CONNECT, device);
|
|
138
|
+
return device.commands;
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function shouldPipelineAssetFileWrite(options: ProtocolV2FileWriteOptions) {
|
|
143
|
+
const env = DataManager.getSettings('env');
|
|
144
|
+
if (env !== 'react-native' && env !== 'desktop-web-ble') return false;
|
|
145
|
+
if (
|
|
146
|
+
options.overwrite !== true ||
|
|
147
|
+
options.append ||
|
|
148
|
+
(options.offset ?? 0) !== 0 ||
|
|
149
|
+
(options.paceMs ?? 0) > 0 ||
|
|
150
|
+
options.writeWithResponse === true ||
|
|
151
|
+
!/^vol1:\/(?:wallpapers\/wallpaper\.okpkg|nft\/nft-[0-9a-f]{8}-[1-9][0-9]*\.okpkg|portfolio\/portfolio\.okpkg\.pending)$/.test(
|
|
152
|
+
options.path
|
|
153
|
+
)
|
|
154
|
+
) {
|
|
155
|
+
return false;
|
|
156
|
+
}
|
|
157
|
+
const { device } = options.commands;
|
|
158
|
+
const deviceType = device?.getCurrentDeviceType();
|
|
159
|
+
const firmwareVersion = device?.state?.versions.firmware;
|
|
160
|
+
const mode = device?.state?.status.mode;
|
|
161
|
+
return Boolean(
|
|
162
|
+
device?.isProtocolV2() &&
|
|
163
|
+
(deviceType === EDeviceType.Pro2 || deviceType === EDeviceType.Neo) &&
|
|
164
|
+
(mode === 'normal' || mode === 'notInitialized') &&
|
|
165
|
+
firmwareVersion &&
|
|
166
|
+
semver.valid(firmwareVersion) &&
|
|
167
|
+
semver.gte(firmwareVersion, ASSET_FILE_WRITE_MIN_APPLICATION_VERSION)
|
|
168
|
+
);
|
|
169
|
+
}
|
|
57
170
|
|
|
58
171
|
function formatFileTransferRate(bytesPerSecond: number) {
|
|
59
172
|
return (Math.max(bytesPerSecond, 0) / 1024).toFixed(2);
|
|
@@ -80,6 +193,9 @@ function logFileTransferMetrics({
|
|
|
80
193
|
hostWriteElapsedMs,
|
|
81
194
|
responseWaitElapsedMs,
|
|
82
195
|
measuredAttempts,
|
|
196
|
+
fileWriteWindow,
|
|
197
|
+
chunkSize,
|
|
198
|
+
retryCount,
|
|
83
199
|
}: {
|
|
84
200
|
transport: string;
|
|
85
201
|
status: 'progress' | 'completed' | 'failed';
|
|
@@ -91,6 +207,9 @@ function logFileTransferMetrics({
|
|
|
91
207
|
hostWriteElapsedMs: number;
|
|
92
208
|
responseWaitElapsedMs: number;
|
|
93
209
|
measuredAttempts: number;
|
|
210
|
+
fileWriteWindow: number;
|
|
211
|
+
chunkSize: number;
|
|
212
|
+
retryCount: number;
|
|
94
213
|
}) {
|
|
95
214
|
const segmentedMetrics =
|
|
96
215
|
measuredAttempts > 0
|
|
@@ -101,7 +220,11 @@ function logFileTransferMetrics({
|
|
|
101
220
|
Log.log(
|
|
102
221
|
`[FileWrite] metrics transport=${transport} status=${status} path=${path} bytes=${transferredBytes}/${totalBytes} elapsed=${(
|
|
103
222
|
elapsedMs / 1000
|
|
104
|
-
).toFixed(2)}s speed=${formatFileTransferRate(
|
|
223
|
+
).toFixed(2)}s speed=${formatFileTransferRate(
|
|
224
|
+
rateBytesPerSecond
|
|
225
|
+
)} KiB/s${segmentedMetrics} window=${fileWriteWindow} chunk=${chunkSize} retry=${retryCount} averageSpeed=${formatFileTransferRate(
|
|
226
|
+
getAverageFileTransferRate(transferredBytes, elapsedMs)
|
|
227
|
+
)} KiB/s`
|
|
105
228
|
);
|
|
106
229
|
}
|
|
107
230
|
|
|
@@ -185,16 +308,26 @@ export async function writeProtocolV2File(options: ProtocolV2FileWriteOptions) {
|
|
|
185
308
|
}
|
|
186
309
|
|
|
187
310
|
const defaultChunkSizeLimit = getProtocolV2FileChunkLimit(options.bleChunkSizeLimit);
|
|
311
|
+
const pipelineAsset = totalSize === dataLength && shouldPipelineAssetFileWrite(options);
|
|
188
312
|
const configuredChunkSizeLimit = Number(options.chunkSizeLimit);
|
|
189
313
|
const chunkSizeLimit =
|
|
190
314
|
Number.isFinite(configuredChunkSizeLimit) && configuredChunkSizeLimit > 0
|
|
191
315
|
? Math.floor(configuredChunkSizeLimit)
|
|
192
316
|
: defaultChunkSizeLimit;
|
|
193
|
-
const chunkSize = normalizeChunkSize(
|
|
317
|
+
const chunkSize = normalizeChunkSize(
|
|
318
|
+
options.chunkSize ?? options.chunkLen,
|
|
319
|
+
pipelineAsset ? Math.min(chunkSizeLimit, PROTOCOL_V2_BLE_FILE_CHUNK_SIZE) : chunkSizeLimit
|
|
320
|
+
);
|
|
321
|
+
const fileWriteWindow =
|
|
322
|
+
pipelineAsset && dataLength > chunkSize ? PROTOCOL_V2_FILE_WRITE_PIPELINE_WINDOW : 1;
|
|
194
323
|
let written = 0;
|
|
195
324
|
let chunks = 0;
|
|
196
325
|
let lastMessage: Record<string, unknown> | undefined;
|
|
197
|
-
|
|
326
|
+
let { commands } = options;
|
|
327
|
+
let retryCount = 0;
|
|
328
|
+
const maxChunkRetries = Math.max(Math.floor(options.maxChunkRetries ?? 0), 0);
|
|
329
|
+
const canRecoverPipeline = pipelineAsset && dataLength > chunkSize && options.recoverConnection;
|
|
330
|
+
let startTime = Date.now();
|
|
198
331
|
let rateWindowStartedAt = startTime;
|
|
199
332
|
let rateWindowStartedBytes = 0;
|
|
200
333
|
let rateBytesPerSecond: number | undefined;
|
|
@@ -204,32 +337,182 @@ export async function writeProtocolV2File(options: ProtocolV2FileWriteOptions) {
|
|
|
204
337
|
let hostWriteElapsedMs = 0;
|
|
205
338
|
let responseWaitElapsedMs = 0;
|
|
206
339
|
let measuredAttempts = 0;
|
|
340
|
+
const createRequest = (position: number) => {
|
|
341
|
+
const chunk = data.slice(position, Math.min(position + chunkSize, dataLength));
|
|
342
|
+
const offset = startOffset + position;
|
|
343
|
+
const progress =
|
|
344
|
+
options.uiPercentage ??
|
|
345
|
+
options.getUiPercentage?.({
|
|
346
|
+
offset,
|
|
347
|
+
chunkLength: chunk.byteLength,
|
|
348
|
+
totalSize,
|
|
349
|
+
}) ??
|
|
350
|
+
getDeviceTransferProgress(offset, offset + chunk.byteLength, totalSize);
|
|
351
|
+
return {
|
|
352
|
+
file: { path: options.path, offset, total_size: totalSize, data: chunk },
|
|
353
|
+
overwrite: position === 0 ? options.overwrite ?? false : false,
|
|
354
|
+
append: options.append ?? false,
|
|
355
|
+
ui_percentage: progress,
|
|
356
|
+
};
|
|
357
|
+
};
|
|
358
|
+
|
|
359
|
+
const confirmChunk = (
|
|
360
|
+
response: { type?: string; message?: Record<string, unknown> },
|
|
361
|
+
offset: number,
|
|
362
|
+
chunkLength: number
|
|
363
|
+
) => {
|
|
364
|
+
const responseType = (response as { type?: string }).type;
|
|
365
|
+
if (responseType && responseType !== 'FilesystemFile') {
|
|
366
|
+
const responseError = (response as { message?: { error?: unknown } }).message?.error;
|
|
367
|
+
if (typeof responseError === 'string' && responseError.includes(SESSION_ERROR)) {
|
|
368
|
+
throw ERRORS.TypedError(HardwareErrorCode.RuntimeError, SESSION_ERROR);
|
|
369
|
+
}
|
|
370
|
+
throw ERRORS.TypedError(
|
|
371
|
+
HardwareErrorCode.RuntimeError,
|
|
372
|
+
`FilesystemFileWrite received unexpected response ${responseType}`
|
|
373
|
+
);
|
|
374
|
+
}
|
|
375
|
+
options.throwIfAborted?.();
|
|
376
|
+
lastMessage = response.message;
|
|
377
|
+
const rawProcessedByte = response.message?.processed_byte;
|
|
378
|
+
const processedByte = Number(rawProcessedByte);
|
|
379
|
+
if (
|
|
380
|
+
rawProcessedByte !== undefined &&
|
|
381
|
+
(!Number.isFinite(processedByte) ||
|
|
382
|
+
processedByte <= offset ||
|
|
383
|
+
processedByte > offset + chunkLength)
|
|
384
|
+
) {
|
|
385
|
+
throw ERRORS.TypedError(
|
|
386
|
+
HardwareErrorCode.RuntimeError,
|
|
387
|
+
`FilesystemFileWrite invalid processed_byte ${processedByte}`
|
|
388
|
+
);
|
|
389
|
+
}
|
|
390
|
+
written = rawProcessedByte === undefined ? written + chunkLength : processedByte - startOffset;
|
|
391
|
+
chunks += 1;
|
|
392
|
+
const now = Date.now();
|
|
393
|
+
lastConfirmedAt = now;
|
|
394
|
+
const elapsedMs = now - startTime;
|
|
395
|
+
const transferredBytes = Math.min(written, dataLength);
|
|
396
|
+
const rateWindowElapsedMs = now - rateWindowStartedAt;
|
|
397
|
+
if (rateWindowElapsedMs >= FILE_TRANSFER_RATE_WINDOW_MS) {
|
|
398
|
+
const rateWindowBytes = Math.max(transferredBytes - rateWindowStartedBytes, 0);
|
|
399
|
+
rateBytesPerSecond = Math.round((rateWindowBytes / rateWindowElapsedMs) * 1000);
|
|
400
|
+
rateWindowStartedAt = now;
|
|
401
|
+
rateWindowStartedBytes = transferredBytes;
|
|
402
|
+
} else if (rateBytesPerSecond === undefined && elapsedMs > 0) {
|
|
403
|
+
rateBytesPerSecond = Math.round((transferredBytes / elapsedMs) * 1000);
|
|
404
|
+
}
|
|
405
|
+
options.onProgress?.({
|
|
406
|
+
progress: getConfirmedProgress(startOffset + written, totalSize, written, dataLength),
|
|
407
|
+
transferredBytes,
|
|
408
|
+
totalBytes: dataLength,
|
|
409
|
+
rateBytesPerSecond,
|
|
410
|
+
elapsedMs,
|
|
411
|
+
...(retryCount > 0 ? { retryCount } : {}),
|
|
412
|
+
});
|
|
413
|
+
const logWindowElapsedMs = now - logWindowStartedAt;
|
|
414
|
+
if (logWindowElapsedMs >= FILE_TRANSFER_LOG_INTERVAL_MS && transferredBytes < dataLength) {
|
|
415
|
+
const logWindowBytes = Math.max(transferredBytes - logWindowStartedBytes, 0);
|
|
416
|
+
logFileTransferMetrics({
|
|
417
|
+
transport,
|
|
418
|
+
status: 'progress',
|
|
419
|
+
path: options.path,
|
|
420
|
+
transferredBytes,
|
|
421
|
+
totalBytes: dataLength,
|
|
422
|
+
elapsedMs,
|
|
423
|
+
rateBytesPerSecond: getAverageFileTransferRate(logWindowBytes, logWindowElapsedMs),
|
|
424
|
+
hostWriteElapsedMs,
|
|
425
|
+
responseWaitElapsedMs,
|
|
426
|
+
measuredAttempts,
|
|
427
|
+
fileWriteWindow,
|
|
428
|
+
chunkSize,
|
|
429
|
+
retryCount,
|
|
430
|
+
});
|
|
431
|
+
logWindowStartedAt = now;
|
|
432
|
+
logWindowStartedBytes = transferredBytes;
|
|
433
|
+
}
|
|
434
|
+
};
|
|
435
|
+
|
|
207
436
|
const transport = getFileTransferTransport();
|
|
208
437
|
Log.log(
|
|
209
438
|
`[FileWrite] started transport=${transport} path=${options.path} bytes=${dataLength} offset=${startOffset} chunk=${chunkSize}`
|
|
210
439
|
);
|
|
211
440
|
|
|
212
|
-
|
|
441
|
+
const writeAttempt = async () => {
|
|
442
|
+
options.throwIfAborted?.();
|
|
443
|
+
const attemptRetryCount = retryCount;
|
|
444
|
+
if (fileWriteWindow === PROTOCOL_V2_FILE_WRITE_PIPELINE_WINDOW) {
|
|
445
|
+
Log.debug('[FileWrite] BLE asset pipeline', {
|
|
446
|
+
path: options.path,
|
|
447
|
+
window: fileWriteWindow,
|
|
448
|
+
chunkSize,
|
|
449
|
+
retryCount,
|
|
450
|
+
applicationVersion: commands.device?.state?.versions.firmware,
|
|
451
|
+
});
|
|
452
|
+
let sendOffset = chunkSize;
|
|
453
|
+
let interrupted = false;
|
|
454
|
+
let interruption: unknown;
|
|
455
|
+
const response = await commands.typedCall(
|
|
456
|
+
'FilesystemFileWrite',
|
|
457
|
+
'FilesystemFile',
|
|
458
|
+
createRequest(0),
|
|
459
|
+
{
|
|
460
|
+
timeoutMs: options.timeoutMs ?? ASSET_FILE_WRITE_RESPONSE_TIMEOUT,
|
|
461
|
+
writeWithResponse: false,
|
|
462
|
+
fileWritePipeline: {
|
|
463
|
+
next: () => {
|
|
464
|
+
if (interrupted || attemptRetryCount !== retryCount || sendOffset >= dataLength)
|
|
465
|
+
return Promise.resolve(undefined);
|
|
466
|
+
try {
|
|
467
|
+
options.throwIfAborted?.();
|
|
468
|
+
const request = createRequest(sendOffset);
|
|
469
|
+
sendOffset += request.file.data.byteLength;
|
|
470
|
+
return Promise.resolve(request);
|
|
471
|
+
} catch (error) {
|
|
472
|
+
interrupted = true;
|
|
473
|
+
interruption = error;
|
|
474
|
+
return Promise.resolve(undefined);
|
|
475
|
+
}
|
|
476
|
+
},
|
|
477
|
+
onResponse: confirmed => {
|
|
478
|
+
if (interrupted || attemptRetryCount !== retryCount) return;
|
|
479
|
+
try {
|
|
480
|
+
const offset = startOffset + written;
|
|
481
|
+
const length = Math.min(chunkSize, dataLength - written);
|
|
482
|
+
if (Number(confirmed.message.processed_byte) !== offset + length) {
|
|
483
|
+
throw ERRORS.TypedError(
|
|
484
|
+
HardwareErrorCode.RuntimeError,
|
|
485
|
+
'FilesystemFileWrite pipeline did not confirm the complete chunk'
|
|
486
|
+
);
|
|
487
|
+
}
|
|
488
|
+
confirmChunk(confirmed, offset, length);
|
|
489
|
+
} catch (error) {
|
|
490
|
+
// Drain already-sent responses before raising callback errors or cancellation.
|
|
491
|
+
interrupted = true;
|
|
492
|
+
interruption = error;
|
|
493
|
+
}
|
|
494
|
+
},
|
|
495
|
+
},
|
|
496
|
+
}
|
|
497
|
+
);
|
|
498
|
+
options.throwIfAborted?.();
|
|
499
|
+
if (interrupted) throw interruption;
|
|
500
|
+
if (
|
|
501
|
+
response.type !== 'FilesystemFile' ||
|
|
502
|
+
Number(response.message.processed_byte) !== startOffset + dataLength ||
|
|
503
|
+
written !== dataLength
|
|
504
|
+
) {
|
|
505
|
+
throw ERRORS.TypedError(
|
|
506
|
+
HardwareErrorCode.RuntimeError,
|
|
507
|
+
'FilesystemFileWrite pipeline ended before the complete file was confirmed'
|
|
508
|
+
);
|
|
509
|
+
}
|
|
510
|
+
}
|
|
213
511
|
while (written < dataLength) {
|
|
214
512
|
options.throwIfAborted?.();
|
|
215
|
-
const
|
|
216
|
-
const offset
|
|
217
|
-
|
|
218
|
-
options.uiPercentage ??
|
|
219
|
-
options.getUiPercentage?.({
|
|
220
|
-
offset,
|
|
221
|
-
chunkLength: chunk.byteLength,
|
|
222
|
-
totalSize,
|
|
223
|
-
}) ??
|
|
224
|
-
getDeviceTransferProgress(offset, offset + chunk.byteLength, totalSize);
|
|
225
|
-
const request = {
|
|
226
|
-
file: { path: options.path, offset, total_size: totalSize, data: chunk },
|
|
227
|
-
overwrite: chunks === 0 ? options.overwrite ?? false : false,
|
|
228
|
-
append: options.append ?? false,
|
|
229
|
-
ui_percentage: progress,
|
|
230
|
-
};
|
|
231
|
-
const maxChunkRetries = Math.max(Math.floor(options.maxChunkRetries ?? 0), 0);
|
|
232
|
-
let retryCount = 0;
|
|
513
|
+
const request = createRequest(written);
|
|
514
|
+
const { offset, data: chunk } = request.file;
|
|
515
|
+
let chunkRetryCount = 0;
|
|
233
516
|
let response;
|
|
234
517
|
let isWritePending = true;
|
|
235
518
|
while (isWritePending) {
|
|
@@ -239,7 +522,9 @@ export async function writeProtocolV2File(options: ProtocolV2FileWriteOptions) {
|
|
|
239
522
|
const callOptions =
|
|
240
523
|
options.writeWithResponse === undefined
|
|
241
524
|
? {
|
|
242
|
-
timeoutMs:
|
|
525
|
+
timeoutMs:
|
|
526
|
+
options.timeoutMs ??
|
|
527
|
+
(canRecoverPipeline ? ASSET_FILE_WRITE_RESPONSE_TIMEOUT : undefined),
|
|
243
528
|
onWriteCompleted: ({ elapsedMs }: { elapsedMs: number }) => {
|
|
244
529
|
currentHostWriteElapsedMs = elapsedMs;
|
|
245
530
|
responseWaitStartedAt = Date.now();
|
|
@@ -253,7 +538,7 @@ export async function writeProtocolV2File(options: ProtocolV2FileWriteOptions) {
|
|
|
253
538
|
responseWaitStartedAt = Date.now();
|
|
254
539
|
},
|
|
255
540
|
};
|
|
256
|
-
response = await
|
|
541
|
+
response = await commands.typedCall(
|
|
257
542
|
'FilesystemFileWrite',
|
|
258
543
|
'FilesystemFile',
|
|
259
544
|
request,
|
|
@@ -261,8 +546,13 @@ export async function writeProtocolV2File(options: ProtocolV2FileWriteOptions) {
|
|
|
261
546
|
);
|
|
262
547
|
isWritePending = false;
|
|
263
548
|
} catch (error) {
|
|
264
|
-
if (
|
|
265
|
-
|
|
549
|
+
if (
|
|
550
|
+
canRecoverPipeline ||
|
|
551
|
+
chunkRetryCount >= maxChunkRetries ||
|
|
552
|
+
!isProtocolV2ResponseTimeout(error)
|
|
553
|
+
)
|
|
554
|
+
throw error;
|
|
555
|
+
chunkRetryCount += 1;
|
|
266
556
|
options.throwIfAborted?.();
|
|
267
557
|
} finally {
|
|
268
558
|
if (currentHostWriteElapsedMs !== undefined && responseWaitStartedAt !== undefined) {
|
|
@@ -278,97 +568,75 @@ export async function writeProtocolV2File(options: ProtocolV2FileWriteOptions) {
|
|
|
278
568
|
'FilesystemFileWrite completed without a response'
|
|
279
569
|
);
|
|
280
570
|
}
|
|
281
|
-
|
|
282
|
-
if (
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
}
|
|
287
|
-
throw ERRORS.TypedError(
|
|
288
|
-
HardwareErrorCode.RuntimeError,
|
|
289
|
-
`FilesystemFileWrite received unexpected response ${responseType}`
|
|
290
|
-
);
|
|
571
|
+
confirmChunk(response, offset, chunk.byteLength);
|
|
572
|
+
if (options.paceMs && options.paceMs > 0) {
|
|
573
|
+
await new Promise(resolve => {
|
|
574
|
+
setTimeout(resolve, options.paceMs);
|
|
575
|
+
});
|
|
291
576
|
}
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
577
|
+
}
|
|
578
|
+
};
|
|
579
|
+
while (true) {
|
|
580
|
+
try {
|
|
581
|
+
await writeAttempt();
|
|
582
|
+
break;
|
|
583
|
+
} catch (error) {
|
|
584
|
+
const now = Date.now();
|
|
585
|
+
const elapsedMs = Math.max(now - startTime, 0);
|
|
586
|
+
const logWindowElapsedMs = Math.max(now - logWindowStartedAt, 0);
|
|
587
|
+
const logWindowBytes = Math.max(written - logWindowStartedBytes, 0);
|
|
588
|
+
logFileTransferMetrics({
|
|
589
|
+
transport,
|
|
590
|
+
status: 'failed',
|
|
591
|
+
path: options.path,
|
|
592
|
+
transferredBytes: written,
|
|
593
|
+
totalBytes: dataLength,
|
|
594
|
+
elapsedMs,
|
|
595
|
+
rateBytesPerSecond: getAverageFileTransferRate(logWindowBytes, logWindowElapsedMs),
|
|
596
|
+
hostWriteElapsedMs,
|
|
597
|
+
responseWaitElapsedMs,
|
|
598
|
+
measuredAttempts,
|
|
599
|
+
fileWriteWindow,
|
|
600
|
+
chunkSize,
|
|
601
|
+
retryCount,
|
|
602
|
+
});
|
|
296
603
|
if (
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
processedByte > offset + chunk.byteLength)
|
|
604
|
+
!canRecoverPipeline ||
|
|
605
|
+
retryCount >= maxChunkRetries ||
|
|
606
|
+
!isProtocolV2ResponseTimeout(error)
|
|
301
607
|
) {
|
|
302
|
-
throw
|
|
303
|
-
HardwareErrorCode.RuntimeError,
|
|
304
|
-
`FilesystemFileWrite invalid processed_byte ${processedByte}`
|
|
305
|
-
);
|
|
306
|
-
}
|
|
307
|
-
written =
|
|
308
|
-
rawProcessedByte === undefined ? written + chunk.byteLength : processedByte - startOffset;
|
|
309
|
-
chunks += 1;
|
|
310
|
-
const now = Date.now();
|
|
311
|
-
lastConfirmedAt = now;
|
|
312
|
-
const elapsedMs = now - startTime;
|
|
313
|
-
const transferredBytes = Math.min(written, dataLength);
|
|
314
|
-
const rateWindowElapsedMs = now - rateWindowStartedAt;
|
|
315
|
-
if (rateWindowElapsedMs >= FILE_TRANSFER_RATE_WINDOW_MS) {
|
|
316
|
-
const rateWindowBytes = Math.max(transferredBytes - rateWindowStartedBytes, 0);
|
|
317
|
-
rateBytesPerSecond = Math.round((rateWindowBytes / rateWindowElapsedMs) * 1000);
|
|
318
|
-
rateWindowStartedAt = now;
|
|
319
|
-
rateWindowStartedBytes = transferredBytes;
|
|
320
|
-
} else if (rateBytesPerSecond === undefined && elapsedMs > 0) {
|
|
321
|
-
rateBytesPerSecond = Math.round((transferredBytes / elapsedMs) * 1000);
|
|
608
|
+
throw error;
|
|
322
609
|
}
|
|
610
|
+
options.throwIfAborted?.();
|
|
611
|
+
retryCount += 1;
|
|
612
|
+
Log.log(
|
|
613
|
+
`[FileWrite] retry=${retryCount}/${maxChunkRetries} restarting window=${fileWriteWindow} after reconnect path=${options.path}`
|
|
614
|
+
);
|
|
323
615
|
options.onProgress?.({
|
|
324
|
-
progress:
|
|
325
|
-
transferredBytes,
|
|
616
|
+
progress: 0,
|
|
617
|
+
transferredBytes: 0,
|
|
326
618
|
totalBytes: dataLength,
|
|
327
|
-
|
|
328
|
-
|
|
619
|
+
elapsedMs: 0,
|
|
620
|
+
retryCount,
|
|
329
621
|
});
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
}
|
|
348
|
-
if (options.paceMs && options.paceMs > 0) {
|
|
349
|
-
await new Promise(resolve => {
|
|
350
|
-
setTimeout(resolve, options.paceMs);
|
|
351
|
-
});
|
|
352
|
-
}
|
|
622
|
+
// The timed-out link is invalid. Only a verified reconnect may restart this
|
|
623
|
+
// fixed-path overwriting upload; never replay an uncertain in-flight chunk.
|
|
624
|
+
commands = await canRecoverPipeline();
|
|
625
|
+
options.throwIfAborted?.();
|
|
626
|
+
written = 0;
|
|
627
|
+
chunks = 0;
|
|
628
|
+
lastMessage = undefined;
|
|
629
|
+
startTime = Date.now();
|
|
630
|
+
lastConfirmedAt = startTime;
|
|
631
|
+
rateWindowStartedAt = startTime;
|
|
632
|
+
rateWindowStartedBytes = 0;
|
|
633
|
+
rateBytesPerSecond = undefined;
|
|
634
|
+
logWindowStartedAt = startTime;
|
|
635
|
+
logWindowStartedBytes = 0;
|
|
636
|
+
hostWriteElapsedMs = 0;
|
|
637
|
+
responseWaitElapsedMs = 0;
|
|
638
|
+
measuredAttempts = 0;
|
|
353
639
|
}
|
|
354
|
-
} catch (error) {
|
|
355
|
-
const now = Date.now();
|
|
356
|
-
const elapsedMs = Math.max(now - startTime, 0);
|
|
357
|
-
const logWindowElapsedMs = Math.max(now - logWindowStartedAt, 0);
|
|
358
|
-
const logWindowBytes = Math.max(written - logWindowStartedBytes, 0);
|
|
359
|
-
logFileTransferMetrics({
|
|
360
|
-
transport,
|
|
361
|
-
status: 'failed',
|
|
362
|
-
path: options.path,
|
|
363
|
-
transferredBytes: written,
|
|
364
|
-
totalBytes: dataLength,
|
|
365
|
-
elapsedMs,
|
|
366
|
-
rateBytesPerSecond: getAverageFileTransferRate(logWindowBytes, logWindowElapsedMs),
|
|
367
|
-
hostWriteElapsedMs,
|
|
368
|
-
responseWaitElapsedMs,
|
|
369
|
-
measuredAttempts,
|
|
370
|
-
});
|
|
371
|
-
throw error;
|
|
372
640
|
}
|
|
373
641
|
|
|
374
642
|
const elapsedMs = Math.max(lastConfirmedAt - startTime, 0);
|
|
@@ -385,6 +653,9 @@ export async function writeProtocolV2File(options: ProtocolV2FileWriteOptions) {
|
|
|
385
653
|
hostWriteElapsedMs,
|
|
386
654
|
responseWaitElapsedMs,
|
|
387
655
|
measuredAttempts,
|
|
656
|
+
fileWriteWindow,
|
|
657
|
+
chunkSize,
|
|
658
|
+
retryCount,
|
|
388
659
|
});
|
|
389
660
|
|
|
390
661
|
return {
|
package/src/api/index.ts
CHANGED
|
@@ -60,6 +60,7 @@ export { default as allNetworkGetAddressByLoop } from './allnetwork/AllNetworkGe
|
|
|
60
60
|
|
|
61
61
|
export { default as btcGetAddress } from './btc/BTCGetAddress';
|
|
62
62
|
export { default as btcGetPublicKey } from './btc/BTCGetPublicKey';
|
|
63
|
+
export { default as btcDeriveContextHash } from './btc/BTCDeriveContextHash';
|
|
63
64
|
export { default as btcSignMessage } from './btc/BTCSignMessage';
|
|
64
65
|
export { default as btcSignPsbt } from './btc/BTCSignPsbt';
|
|
65
66
|
export { default as btcSignTransaction } from './btc/BTCSignTransaction';
|
|
@@ -174,3 +175,11 @@ export { default as benfenSignTransaction } from './benfen/BenfenSignTransaction
|
|
|
174
175
|
|
|
175
176
|
export { default as neoGetAddress } from './neo/NeoGetAddress';
|
|
176
177
|
export { default as neoSignTransaction } from './neo/NeoSignTransaction';
|
|
178
|
+
export { default as zcashGetAddress } from './zcash/ZcashGetAddress';
|
|
179
|
+
export { default as zcashGetFullViewingKey } from './zcash/ZcashGetFullViewingKey';
|
|
180
|
+
export { default as zcashSignPczt } from './zcash/ZcashSignPczt';
|
|
181
|
+
|
|
182
|
+
export { default as moneroGetAddress } from './monero/MoneroGetAddress';
|
|
183
|
+
export { default as moneroGetWatchKey } from './monero/MoneroGetWatchKey';
|
|
184
|
+
export { default as moneroKeyImageSync } from './monero/MoneroKeyImageSync';
|
|
185
|
+
export { default as moneroSignTransaction } from './monero/MoneroSignTransaction';
|