@onekeyfe/hd-core 1.2.0-alpha.101 → 1.2.0-alpha.103
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__/base64Data.test.ts +70 -0
- package/__tests__/check-all-firmware-release-protocol-v2.test.ts +2 -0
- package/__tests__/device-pool-state.test.ts +29 -0
- package/__tests__/deviceUploadNft.test.ts +33 -4
- package/__tests__/get-device-state.test.ts +102 -13
- package/__tests__/protocol-v2-legacy-recovery.test.ts +29 -0
- package/__tests__/protocol-v2.test.ts +309 -17
- package/__tests__/refresh-device-state.test.ts +4 -1
- package/__tests__/resourceBase64Boundary.test.ts +48 -0
- package/__tests__/search-devices.test.ts +2 -0
- package/__tests__/ton-sign-message.test.ts +84 -0
- package/dist/api/FirmwareUpdateV4.d.ts +3 -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.map +1 -1
- package/dist/api/UploadPortfolio.d.ts +1 -1
- package/dist/api/UploadPortfolio.d.ts.map +1 -1
- package/dist/api/firmware/protocolV2Release.d.ts.map +1 -1
- package/dist/api/helpers/base64Data.d.ts +16 -0
- package/dist/api/helpers/base64Data.d.ts.map +1 -0
- package/dist/api/protocol-v2/DeviceUploadNft.d.ts +2 -3
- package/dist/api/protocol-v2/DeviceUploadNft.d.ts.map +1 -1
- package/dist/api/protocol-v2/DeviceUploadWallpaper.d.ts +2 -3
- package/dist/api/protocol-v2/DeviceUploadWallpaper.d.ts.map +1 -1
- package/dist/api/ton/TonSignMessage.d.ts.map +1 -1
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/protocolV2LegacyRecovery.d.ts +5 -0
- package/dist/core/protocolV2LegacyRecovery.d.ts.map +1 -0
- package/dist/device/Device.d.ts +9 -2
- package/dist/device/Device.d.ts.map +1 -1
- package/dist/device/DevicePool.d.ts +1 -1
- package/dist/device/DevicePool.d.ts.map +1 -1
- package/dist/index.d.ts +16 -14
- package/dist/index.js +266 -86
- package/dist/protocols/protocol-v2/features.d.ts +8 -0
- package/dist/protocols/protocol-v2/features.d.ts.map +1 -1
- package/dist/protocols/protocol-v2/index.d.ts +2 -2
- package/dist/protocols/protocol-v2/index.d.ts.map +1 -1
- package/dist/types/api/getDeviceState.d.ts +1 -0
- package/dist/types/api/getDeviceState.d.ts.map +1 -1
- package/dist/types/api/protocolV2.d.ts +1 -1
- package/dist/types/api/protocolV2.d.ts.map +1 -1
- package/dist/utils/pro2Nft.d.ts +7 -0
- package/dist/utils/pro2Nft.d.ts.map +1 -1
- package/package.json +6 -4
- package/src/api/FirmwareUpdateV4.ts +70 -23
- package/src/api/GetDeviceState.ts +1 -0
- package/src/api/SearchDevices.ts +2 -0
- package/src/api/UploadPortfolio.ts +9 -2
- package/src/api/firmware/protocolV2Release.ts +1 -0
- package/src/api/helpers/base64Data.ts +85 -0
- package/src/api/protocol-v2/DeviceUploadNft.ts +56 -8
- package/src/api/protocol-v2/DeviceUploadWallpaper.ts +37 -18
- package/src/api/ton/TonSignMessage.ts +5 -3
- package/src/core/index.ts +4 -0
- package/src/core/protocolV2LegacyRecovery.ts +12 -0
- package/src/device/Device.ts +78 -20
- package/src/device/DevicePool.ts +17 -5
- package/src/protocols/protocol-v2/features.ts +10 -0
- package/src/protocols/protocol-v2/index.ts +2 -0
- package/src/types/api/getDeviceState.ts +2 -0
- package/src/types/api/protocolV2.ts +1 -1
- package/src/utils/pro2Nft.ts +31 -8
package/src/core/index.ts
CHANGED
|
@@ -64,6 +64,7 @@ import {
|
|
|
64
64
|
isProtocolV2UiEnabled,
|
|
65
65
|
} from '../protocols/protocol-v2/uiInteraction';
|
|
66
66
|
import { createUiProgressMessageFilter } from '../utils/uiProgressThrottle';
|
|
67
|
+
import { isLegacyProtocolV2FirmwareRecoveryMethod } from './protocolV2LegacyRecovery';
|
|
67
68
|
|
|
68
69
|
import type { ConnectSettings, Features, KnownDevice } from '../types';
|
|
69
70
|
import type { CoreMessage, IFrameCallMessage, UiPromise, UiPromiseResponse } from '../events';
|
|
@@ -109,6 +110,9 @@ const parseInitOptions = (method?: BaseMethod): InitOptions => ({
|
|
|
109
110
|
connectProtocol: method?.payload.connectProtocol,
|
|
110
111
|
forceProtocolDetection: method?.payload.forceProtocolDetection,
|
|
111
112
|
protocolV2DeviceInfoTimeoutMs: method?.payload.protocolV2DeviceInfoTimeoutMs,
|
|
113
|
+
...(isLegacyProtocolV2FirmwareRecoveryMethod(method)
|
|
114
|
+
? { allowLegacyProtocolV2ProtocolInfo: true }
|
|
115
|
+
: {}),
|
|
112
116
|
});
|
|
113
117
|
|
|
114
118
|
let _core: Core | undefined;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { BaseMethod } from '../api/BaseMethod';
|
|
2
|
+
|
|
3
|
+
type CoreMethodDescriptor = Pick<BaseMethod, 'name' | 'payload'>;
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* TEMPORARY COMPATIBILITY: Legacy ProtocolInfo is accepted only by the
|
|
7
|
+
* firmware recovery entry points used before and during Protocol V2 updates.
|
|
8
|
+
*/
|
|
9
|
+
export const isLegacyProtocolV2FirmwareRecoveryMethod = (method?: CoreMethodDescriptor): boolean =>
|
|
10
|
+
method?.name === 'firmwareUpdateV4' ||
|
|
11
|
+
method?.name === 'checkAllFirmwareRelease' ||
|
|
12
|
+
(method?.name === 'getDeviceState' && method.payload.scope === 'firmware');
|
package/src/device/Device.ts
CHANGED
|
@@ -61,6 +61,7 @@ import {
|
|
|
61
61
|
PROTOCOL_V2_VERSIONS_DEVICE_INFO_REQUEST,
|
|
62
62
|
type ProtocolV2RuntimeMode,
|
|
63
63
|
getProtocolV2RuntimeMode,
|
|
64
|
+
isLegacyProtocolV2ProtocolInfo,
|
|
64
65
|
requestProtocolV2DeviceInfo,
|
|
65
66
|
requestProtocolV2DeviceStatus,
|
|
66
67
|
requestProtocolV2ProtocolInfo,
|
|
@@ -99,6 +100,8 @@ export type InitOptions = {
|
|
|
99
100
|
protocolV2DeviceInfoTimeoutMs?: number;
|
|
100
101
|
/** Refresh Protocol V2 runtime state before returning discovery results. */
|
|
101
102
|
refreshRuntimeState?: boolean;
|
|
103
|
+
/** Recovery-only compatibility used by discovery and Protocol V2 firmware updates. */
|
|
104
|
+
allowLegacyProtocolV2ProtocolInfo?: boolean;
|
|
102
105
|
/**
|
|
103
106
|
* Protocol V1 Initialize response timeout override. Reboot-wait polling passes a
|
|
104
107
|
* short value so an unanswered probe settles before the next poll tick.
|
|
@@ -977,10 +980,16 @@ export class Device extends EventEmitter {
|
|
|
977
980
|
});
|
|
978
981
|
// The default request excludes SE/hash data and therefore uses basic scope.
|
|
979
982
|
// Full version and verification data require getDeviceState({ scope: 'firmware' }).
|
|
980
|
-
const features =
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
983
|
+
const features = options?.allowLegacyProtocolV2ProtocolInfo
|
|
984
|
+
? await this.probeProtocolV2RuntimeState(
|
|
985
|
+
deviceInfo,
|
|
986
|
+
options.protocolV2DeviceInfoTimeoutMs,
|
|
987
|
+
{ allowLegacyProtocolV2ProtocolInfo: true }
|
|
988
|
+
)
|
|
989
|
+
: await this.probeProtocolV2RuntimeState(
|
|
990
|
+
deviceInfo,
|
|
991
|
+
options?.protocolV2DeviceInfoTimeoutMs
|
|
992
|
+
);
|
|
984
993
|
Log.debug('Protocol V2 features:', features);
|
|
985
994
|
} catch (error) {
|
|
986
995
|
Log.error('Protocol V2 initialization failed:', error);
|
|
@@ -1017,7 +1026,13 @@ export class Device extends EventEmitter {
|
|
|
1017
1026
|
commands: this.commands,
|
|
1018
1027
|
request: getProtocolV2DeviceInfoRequest(),
|
|
1019
1028
|
});
|
|
1020
|
-
|
|
1029
|
+
if (params.allowLegacyProtocolV2ProtocolInfo) {
|
|
1030
|
+
await this.probeProtocolV2RuntimeState(deviceInfo, undefined, {
|
|
1031
|
+
allowLegacyProtocolV2ProtocolInfo: true,
|
|
1032
|
+
});
|
|
1033
|
+
} else {
|
|
1034
|
+
await this.probeProtocolV2RuntimeState(deviceInfo);
|
|
1035
|
+
}
|
|
1021
1036
|
refreshedDeviceInfo = deviceInfo;
|
|
1022
1037
|
initializedWithDeviceInfo = true;
|
|
1023
1038
|
} else {
|
|
@@ -1053,7 +1068,16 @@ export class Device extends EventEmitter {
|
|
|
1053
1068
|
}
|
|
1054
1069
|
|
|
1055
1070
|
if (refresh.has('status') && !initializedWithDeviceInfo) {
|
|
1056
|
-
|
|
1071
|
+
const cachedMode = this.state?.status.mode;
|
|
1072
|
+
await this.probeProtocolV2RuntimeState(refreshedDeviceInfo, undefined, {
|
|
1073
|
+
// Loader firmware does not support DeviceStatusGet. Renegotiate ProtocolInfo
|
|
1074
|
+
// during an explicit refresh so a device rebooted into application firmware
|
|
1075
|
+
// can leave the cached loader state.
|
|
1076
|
+
forceRuntimeContextRefresh: cachedMode === 'bootloader' || cachedMode === 'romloader',
|
|
1077
|
+
...(params.allowLegacyProtocolV2ProtocolInfo
|
|
1078
|
+
? { allowLegacyProtocolV2ProtocolInfo: true }
|
|
1079
|
+
: {}),
|
|
1080
|
+
});
|
|
1057
1081
|
}
|
|
1058
1082
|
|
|
1059
1083
|
if (refresh.has('settings') && this.state?.status.mode === 'normal') {
|
|
@@ -1124,19 +1148,39 @@ export class Device extends EventEmitter {
|
|
|
1124
1148
|
return this.features;
|
|
1125
1149
|
}
|
|
1126
1150
|
|
|
1127
|
-
async ensureProtocolV2RuntimeContext(
|
|
1151
|
+
async ensureProtocolV2RuntimeContext(
|
|
1152
|
+
timeoutMs?: number,
|
|
1153
|
+
options?: {
|
|
1154
|
+
forceRefresh?: boolean;
|
|
1155
|
+
allowLegacyProtocolV2ProtocolInfo?: boolean;
|
|
1156
|
+
}
|
|
1157
|
+
): Promise<ProtocolInfo> {
|
|
1158
|
+
const assertProtocolInfoAllowed = (protocolInfo: ProtocolInfo) => {
|
|
1159
|
+
if (
|
|
1160
|
+
isLegacyProtocolV2ProtocolInfo(protocolInfo) &&
|
|
1161
|
+
options?.allowLegacyProtocolV2ProtocolInfo !== true
|
|
1162
|
+
) {
|
|
1163
|
+
throw ERRORS.TypedError(
|
|
1164
|
+
HardwareErrorCode.DeviceInitializeFailed,
|
|
1165
|
+
'Legacy Protocol V2 ProtocolInfo is supported only during device discovery and firmware update.'
|
|
1166
|
+
);
|
|
1167
|
+
}
|
|
1168
|
+
return protocolInfo;
|
|
1169
|
+
};
|
|
1128
1170
|
const cachedProtocolInfo =
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1171
|
+
options?.forceRefresh === true
|
|
1172
|
+
? undefined
|
|
1173
|
+
: this.protocolV2RuntimeContext ??
|
|
1174
|
+
(!this.protocolV2StateNeedsReload
|
|
1175
|
+
? this.state?.raw?.protocolV2ProtocolInfo ?? undefined
|
|
1176
|
+
: undefined);
|
|
1133
1177
|
if (cachedProtocolInfo) {
|
|
1134
1178
|
this.protocolV2RuntimeContext = cachedProtocolInfo;
|
|
1135
|
-
return cachedProtocolInfo;
|
|
1179
|
+
return assertProtocolInfoAllowed(cachedProtocolInfo);
|
|
1136
1180
|
}
|
|
1137
1181
|
|
|
1138
1182
|
if (this.protocolV2RuntimeContextPromise) {
|
|
1139
|
-
return this.protocolV2RuntimeContextPromise;
|
|
1183
|
+
return assertProtocolInfoAllowed(await this.protocolV2RuntimeContextPromise);
|
|
1140
1184
|
}
|
|
1141
1185
|
|
|
1142
1186
|
const requestToken = {};
|
|
@@ -1158,7 +1202,7 @@ export class Device extends EventEmitter {
|
|
|
1158
1202
|
this.protocolV2RuntimeContextPromise = pendingRequest;
|
|
1159
1203
|
|
|
1160
1204
|
try {
|
|
1161
|
-
return await pendingRequest;
|
|
1205
|
+
return assertProtocolInfoAllowed(await pendingRequest);
|
|
1162
1206
|
} finally {
|
|
1163
1207
|
if (this.protocolV2RuntimeContextPromise === pendingRequest) {
|
|
1164
1208
|
this.protocolV2RuntimeContextPromise = undefined;
|
|
@@ -1169,9 +1213,20 @@ export class Device extends EventEmitter {
|
|
|
1169
1213
|
}
|
|
1170
1214
|
}
|
|
1171
1215
|
|
|
1172
|
-
async probeProtocolV2RuntimeState(
|
|
1173
|
-
|
|
1216
|
+
async probeProtocolV2RuntimeState(
|
|
1217
|
+
deviceInfo?: ProtocolV2DeviceInfo,
|
|
1218
|
+
timeoutMs?: number,
|
|
1219
|
+
options?: {
|
|
1220
|
+
forceRuntimeContextRefresh?: boolean;
|
|
1221
|
+
allowLegacyProtocolV2ProtocolInfo?: boolean;
|
|
1222
|
+
}
|
|
1223
|
+
) {
|
|
1224
|
+
const protocolInfo = await this.ensureProtocolV2RuntimeContext(timeoutMs, {
|
|
1225
|
+
forceRefresh: options?.forceRuntimeContextRefresh,
|
|
1226
|
+
allowLegacyProtocolV2ProtocolInfo: options?.allowLegacyProtocolV2ProtocolInfo,
|
|
1227
|
+
});
|
|
1174
1228
|
const runtimeMode = getProtocolV2RuntimeMode(protocolInfo);
|
|
1229
|
+
const legacyProtocolInfo = isLegacyProtocolV2ProtocolInfo(protocolInfo);
|
|
1175
1230
|
const runtimeDeviceInfo = deviceInfo ?? this.state?.raw?.protocolV2DeviceInfo;
|
|
1176
1231
|
const protocolV2DeviceType = runtimeDeviceInfo
|
|
1177
1232
|
? resolveProtocolV2DeviceIdentity(runtimeDeviceInfo.hw?.Device_type).deviceType
|
|
@@ -1186,10 +1241,9 @@ export class Device extends EventEmitter {
|
|
|
1186
1241
|
'Protocol V2 romloader mode is only supported for Pro2 and Neo.'
|
|
1187
1242
|
);
|
|
1188
1243
|
}
|
|
1189
|
-
const deviceStatusSupported =
|
|
1190
|
-
|
|
1191
|
-
PROTOCOL_V2_DEVICE_STATUS_GET_MESSAGE_TYPE
|
|
1192
|
-
);
|
|
1244
|
+
const deviceStatusSupported =
|
|
1245
|
+
legacyProtocolInfo ||
|
|
1246
|
+
supportsProtocolV2Message(protocolInfo, PROTOCOL_V2_DEVICE_STATUS_GET_MESSAGE_TYPE);
|
|
1193
1247
|
|
|
1194
1248
|
if (runtimeMode === 'bootloader' || runtimeMode === 'romloader') {
|
|
1195
1249
|
return this.updateProtocolV2Features(deviceInfo, null, runtimeMode, protocolInfo);
|
|
@@ -1491,6 +1545,10 @@ export class Device extends EventEmitter {
|
|
|
1491
1545
|
|
|
1492
1546
|
async interruptionFromUser() {
|
|
1493
1547
|
const error = ERRORS.TypedError(HardwareErrorCode.DeviceInterruptedFromUser);
|
|
1548
|
+
// Cancellation often ends the transport session, especially during firmware
|
|
1549
|
+
// update reboot or exit. Invalidate V2 state before awaiting asynchronous
|
|
1550
|
+
// cancellation callbacks so the next request cannot reuse a stale runtime mode.
|
|
1551
|
+
this.markTransportDisconnected();
|
|
1494
1552
|
await this.cancelableAction?.(error);
|
|
1495
1553
|
await this.commands?.cancel();
|
|
1496
1554
|
|
package/src/device/DevicePool.ts
CHANGED
|
@@ -182,7 +182,7 @@ export class DevicePool extends EventEmitter {
|
|
|
182
182
|
try {
|
|
183
183
|
await device.initialize(initOptions);
|
|
184
184
|
if (initOptions?.refreshRuntimeState && device.isProtocolV2()) {
|
|
185
|
-
await this._refreshProtocolV2DiscoveryState(device);
|
|
185
|
+
await this._refreshProtocolV2DiscoveryState(device, initOptions);
|
|
186
186
|
}
|
|
187
187
|
} finally {
|
|
188
188
|
await device.release();
|
|
@@ -197,7 +197,7 @@ export class DevicePool extends EventEmitter {
|
|
|
197
197
|
await device.run(
|
|
198
198
|
async () => {
|
|
199
199
|
try {
|
|
200
|
-
await this._refreshProtocolV2DiscoveryState(device);
|
|
200
|
+
await this._refreshProtocolV2DiscoveryState(device, initOptions);
|
|
201
201
|
} catch (error) {
|
|
202
202
|
// Device.run releases after the callback; then propagate the actual read error.
|
|
203
203
|
refreshError = error;
|
|
@@ -206,6 +206,9 @@ export class DevicePool extends EventEmitter {
|
|
|
206
206
|
{
|
|
207
207
|
connectProtocol: initOptions.connectProtocol,
|
|
208
208
|
forceProtocolDetection: initOptions.forceProtocolDetection,
|
|
209
|
+
...(initOptions.allowLegacyProtocolV2ProtocolInfo
|
|
210
|
+
? { allowLegacyProtocolV2ProtocolInfo: true }
|
|
211
|
+
: {}),
|
|
209
212
|
}
|
|
210
213
|
);
|
|
211
214
|
if (refreshError instanceof Error) throw refreshError;
|
|
@@ -217,10 +220,19 @@ export class DevicePool extends EventEmitter {
|
|
|
217
220
|
* error; settings only supplies a label, so its failure retains the existing name.
|
|
218
221
|
* Device.getDeviceState skips unsupported status/settings calls in loader mode.
|
|
219
222
|
*/
|
|
220
|
-
static async _refreshProtocolV2DiscoveryState(device: Device) {
|
|
221
|
-
|
|
223
|
+
static async _refreshProtocolV2DiscoveryState(device: Device, initOptions?: InitOptions) {
|
|
224
|
+
const stateReadOptions = initOptions?.allowLegacyProtocolV2ProtocolInfo
|
|
225
|
+
? { allowLegacyProtocolV2ProtocolInfo: true as const }
|
|
226
|
+
: {};
|
|
227
|
+
await device.getDeviceState({
|
|
228
|
+
refreshSections: ['status'],
|
|
229
|
+
...stateReadOptions,
|
|
230
|
+
});
|
|
222
231
|
try {
|
|
223
|
-
await device.getDeviceState({
|
|
232
|
+
await device.getDeviceState({
|
|
233
|
+
refreshSections: ['settings'],
|
|
234
|
+
...stateReadOptions,
|
|
235
|
+
});
|
|
224
236
|
} catch (error) {
|
|
225
237
|
Log.debug('Unable to refresh Protocol V2 device label during discovery', error);
|
|
226
238
|
}
|
|
@@ -56,6 +56,16 @@ export const getProtocolV2SeType = (se?: DeviceSEInfo): string | null =>
|
|
|
56
56
|
|
|
57
57
|
export type ProtocolV2RuntimeMode = 'normal' | 'bootloader' | 'romloader';
|
|
58
58
|
|
|
59
|
+
export type ProtocolV2ProtocolInfo = ProtocolInfo & {
|
|
60
|
+
/** Present only when hd-transport decoded the pre-build-fingerprint wire layout. */
|
|
61
|
+
protobuf_definition?: string | null;
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
export const isLegacyProtocolV2ProtocolInfo = (
|
|
65
|
+
protocolInfo: ProtocolInfo
|
|
66
|
+
): protocolInfo is ProtocolV2ProtocolInfo & { protobuf_definition: string | null } =>
|
|
67
|
+
Object.prototype.hasOwnProperty.call(protocolInfo, 'protobuf_definition');
|
|
68
|
+
|
|
59
69
|
/**
|
|
60
70
|
* Protocol V2 fingerprints use:
|
|
61
71
|
* <binary>__<version>__<commit>__<PROD|DEV>__<DEBUG|RELEASE>
|
|
@@ -7,6 +7,7 @@ export {
|
|
|
7
7
|
getProtocolV2RuntimeMode,
|
|
8
8
|
getProtocolV2SeState,
|
|
9
9
|
getProtocolV2SeType,
|
|
10
|
+
isLegacyProtocolV2ProtocolInfo,
|
|
10
11
|
parseProtocolV2BuildFingerprint,
|
|
11
12
|
requestProtocolV2ProtocolInfo,
|
|
12
13
|
supportsProtocolV2Message,
|
|
@@ -18,6 +19,7 @@ export type {
|
|
|
18
19
|
ProtocolV2SEInfo,
|
|
19
20
|
ProtocolV2SeStateLabel,
|
|
20
21
|
ProtocolV2RuntimeMode,
|
|
22
|
+
ProtocolV2ProtocolInfo,
|
|
21
23
|
} from './features';
|
|
22
24
|
export * from './firmware';
|
|
23
25
|
export * from './walletSession';
|
|
@@ -11,6 +11,8 @@ export type GetDeviceStateParams = CommonParams & {
|
|
|
11
11
|
export type DeviceStateReadOptions = {
|
|
12
12
|
refreshSections?: DeviceStateSection[];
|
|
13
13
|
includeRaw?: boolean;
|
|
14
|
+
/** Internal recovery-only compatibility used by discovery and firmware update flows. */
|
|
15
|
+
allowLegacyProtocolV2ProtocolInfo?: boolean;
|
|
14
16
|
};
|
|
15
17
|
|
|
16
18
|
export declare function getDeviceState(
|
|
@@ -68,7 +68,7 @@ export declare function deviceUploadNft(
|
|
|
68
68
|
export declare function uploadPortfolio(
|
|
69
69
|
connectId: string,
|
|
70
70
|
params: {
|
|
71
|
-
|
|
71
|
+
packageBase64: string;
|
|
72
72
|
timeoutMs?: number | string;
|
|
73
73
|
}
|
|
74
74
|
): Response<FileInfo & { portfolioUpdated: true }>;
|
package/src/utils/pro2Nft.ts
CHANGED
|
@@ -109,6 +109,34 @@ export function buildPro2NftBundle(options: {
|
|
|
109
109
|
const { image, thumbnail, title, subtitle, timestampMs } = options;
|
|
110
110
|
assertImage('image', image, PRO2_NFT_IMAGE_WIDTH, PRO2_NFT_IMAGE_HEIGHT);
|
|
111
111
|
assertImage('thumbnail', thumbnail, PRO2_NFT_THUMBNAIL_WIDTH, PRO2_NFT_THUMBNAIL_HEIGHT);
|
|
112
|
+
const encodedImage = encodePro2Image({ ...image, alphaMode: 'black-background' }).data;
|
|
113
|
+
const encodedThumbnail = encodePro2Image({
|
|
114
|
+
...thumbnail,
|
|
115
|
+
alphaMode: 'black-background',
|
|
116
|
+
}).data;
|
|
117
|
+
return buildPro2NftBundleFromEncodedImages({
|
|
118
|
+
image: encodedImage,
|
|
119
|
+
thumbnail: encodedThumbnail,
|
|
120
|
+
title,
|
|
121
|
+
subtitle,
|
|
122
|
+
timestampMs,
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export function buildPro2NftBundleFromEncodedImages(options: {
|
|
127
|
+
image: Uint8Array;
|
|
128
|
+
thumbnail: Uint8Array;
|
|
129
|
+
title: string;
|
|
130
|
+
subtitle: string;
|
|
131
|
+
timestampMs: number;
|
|
132
|
+
}): Pro2NftBundle {
|
|
133
|
+
const { image, thumbnail, title, subtitle, timestampMs } = options;
|
|
134
|
+
if (!(image instanceof Uint8Array) || image.byteLength === 0) {
|
|
135
|
+
throw invalidParameter('Parameter [image] must contain encoded NFT image data.');
|
|
136
|
+
}
|
|
137
|
+
if (!(thumbnail instanceof Uint8Array) || thumbnail.byteLength === 0) {
|
|
138
|
+
throw invalidParameter('Parameter [thumbnail] must contain encoded NFT thumbnail data.');
|
|
139
|
+
}
|
|
112
140
|
const titleLength = typeof title === 'string' ? utf8Length(title) : 0;
|
|
113
141
|
const subtitleLength =
|
|
114
142
|
typeof subtitle === 'string' ? utf8Length(subtitle) : Number.POSITIVE_INFINITY;
|
|
@@ -122,21 +150,16 @@ export function buildPro2NftBundle(options: {
|
|
|
122
150
|
throw invalidParameter('Parameter [timestampMs] must be a positive safe integer.');
|
|
123
151
|
}
|
|
124
152
|
|
|
125
|
-
const encodedImage = encodePro2Image({ ...image, alphaMode: 'black-background' }).data;
|
|
126
|
-
const encodedThumbnail = encodePro2Image({
|
|
127
|
-
...thumbnail,
|
|
128
|
-
alphaMode: 'black-background',
|
|
129
|
-
}).data;
|
|
130
153
|
const metadata = new TextEncoder().encode(JSON.stringify({ title, subtitle }));
|
|
131
154
|
if (metadata.byteLength === 0 || metadata.byteLength > 512) {
|
|
132
155
|
throw invalidParameter('Pro2 NFT metadata must contain 1 to 512 UTF-8 bytes.');
|
|
133
156
|
}
|
|
134
157
|
|
|
135
|
-
const hash8 = bytesToHex(blake2s(
|
|
158
|
+
const hash8 = bytesToHex(blake2s(image)).slice(0, 8);
|
|
136
159
|
return {
|
|
137
160
|
basename: `nft-${hash8}-${timestampMs}`,
|
|
138
|
-
image
|
|
139
|
-
thumbnail
|
|
161
|
+
image,
|
|
162
|
+
thumbnail,
|
|
140
163
|
metadata,
|
|
141
164
|
};
|
|
142
165
|
}
|