@portal-hq/provider 4.1.4 → 4.1.6
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/lib/commonjs/signers/enclave.js +160 -111
- package/lib/esm/signers/enclave.js +161 -112
- package/package.json +4 -4
- package/src/signers/enclave.ts +186 -155
- package/types.d.ts +9 -7
|
@@ -88,10 +88,22 @@ class EnclaveSigner {
|
|
|
88
88
|
break;
|
|
89
89
|
}
|
|
90
90
|
const shares = yield this.keychain.getShares();
|
|
91
|
+
// Validate shares exist
|
|
92
|
+
if (!shares.secp256k1) {
|
|
93
|
+
throw new utils_1.PortalMpcError({
|
|
94
|
+
code: utils_1.PortalErrorCodes.FailedToParseInputShareObject,
|
|
95
|
+
id: 'MissingShare',
|
|
96
|
+
message: '[Portal.Provider.EnclaveSigner] The SECP256K1 share is missing from the keychain.',
|
|
97
|
+
});
|
|
98
|
+
}
|
|
91
99
|
let signingShare = shares.secp256k1.share;
|
|
92
100
|
if (curve === core_1.PortalCurve.ED25519) {
|
|
93
101
|
if (!shares.ed25519) {
|
|
94
|
-
throw new
|
|
102
|
+
throw new utils_1.PortalMpcError({
|
|
103
|
+
code: utils_1.PortalErrorCodes.FailedToParseInputShareObject,
|
|
104
|
+
id: 'MissingShare',
|
|
105
|
+
message: '[Portal.Provider.EnclaveSigner] The ED25519 share is missing from the keychain.',
|
|
106
|
+
});
|
|
95
107
|
}
|
|
96
108
|
signingShare = shares.ed25519.share;
|
|
97
109
|
}
|
|
@@ -107,17 +119,24 @@ class EnclaveSigner {
|
|
|
107
119
|
reqId: traceId,
|
|
108
120
|
connectionTracingEnabled: shouldSendMetrics,
|
|
109
121
|
};
|
|
122
|
+
// Build params
|
|
123
|
+
// Avoid double JSON encoding: if params is already a string (e.g. a hex message), pass it directly; otherwise stringify objects/arrays.
|
|
124
|
+
const params = this.buildParams(method, message.params);
|
|
110
125
|
let formattedParams;
|
|
111
|
-
let rpcUrl;
|
|
112
126
|
if (isRaw) {
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
127
|
+
if (typeof params !== 'string') {
|
|
128
|
+
throw new Error('[Portal.Provider.EnclaveSigner] For raw signing, params must be a string (e.g., a hex-encoded message).');
|
|
129
|
+
}
|
|
130
|
+
formattedParams = params;
|
|
116
131
|
}
|
|
117
132
|
else {
|
|
118
|
-
formattedParams =
|
|
119
|
-
|
|
133
|
+
formattedParams =
|
|
134
|
+
typeof params === 'string' ? params : JSON.stringify(params);
|
|
120
135
|
}
|
|
136
|
+
// Get RPC URL (getGatewayUrl handles undefined chainId)
|
|
137
|
+
const rpcUrl = isRaw ? '' : provider.getGatewayUrl(chainId);
|
|
138
|
+
// Set metrics operation
|
|
139
|
+
metrics.operation = isRaw ? Operation.RAW_SIGN : Operation.SIGN;
|
|
121
140
|
if (typeof formattedParams !== 'string') {
|
|
122
141
|
throw new Error(`[Portal.Provider.EnclaveSigner] The formatted params for the signing request could not be converted to a string. The params were: ${formattedParams}`);
|
|
123
142
|
}
|
|
@@ -125,9 +144,40 @@ class EnclaveSigner {
|
|
|
125
144
|
metrics.sdkPreOperationMs = performance.now() - preOperationStartTime;
|
|
126
145
|
// Measure enclave signing operation time
|
|
127
146
|
const enclaveSignStartTime = performance.now();
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
147
|
+
// Build request based on operation type
|
|
148
|
+
let endpoint;
|
|
149
|
+
let requestBody;
|
|
150
|
+
if (isRaw) {
|
|
151
|
+
// Raw sign endpoint and body
|
|
152
|
+
endpoint = `/v1/raw/sign/${curve || 'SECP256K1'}`;
|
|
153
|
+
requestBody = {
|
|
154
|
+
params: formattedParams,
|
|
155
|
+
share: JSON.stringify(signingShare),
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
else {
|
|
159
|
+
// Standard sign endpoint and body
|
|
160
|
+
endpoint = '/v1/sign';
|
|
161
|
+
requestBody = {
|
|
162
|
+
method: message.method,
|
|
163
|
+
params: formattedParams,
|
|
164
|
+
share: JSON.stringify(signingShare),
|
|
165
|
+
chainId: chainId || '',
|
|
166
|
+
rpcUrl: rpcUrl,
|
|
167
|
+
metadataStr: JSON.stringify(metadata),
|
|
168
|
+
clientPlatform: 'REACT_NATIVE',
|
|
169
|
+
clientPlatformVersion: (0, utils_1.getClientPlatformVersion)(),
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
// Make API request and process response
|
|
173
|
+
let result;
|
|
174
|
+
try {
|
|
175
|
+
const response = yield this.makeEnclaveRequest(endpoint, apiKey, requestBody);
|
|
176
|
+
result = this.processEnclaveResponse(response);
|
|
177
|
+
}
|
|
178
|
+
catch (error) {
|
|
179
|
+
this.handleEnclaveError(error);
|
|
180
|
+
}
|
|
131
181
|
// Post-operation processing time starts
|
|
132
182
|
const postOperationStartTime = performance.now();
|
|
133
183
|
// Record HTTP call time
|
|
@@ -181,118 +231,117 @@ class EnclaveSigner {
|
|
|
181
231
|
}
|
|
182
232
|
});
|
|
183
233
|
}
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
}
|
|
210
|
-
return this.encodeErrorResult('SIGNING_NETWORK_ERROR',
|
|
211
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-argument
|
|
212
|
-
error.message || 'Network error occurred');
|
|
234
|
+
/**
|
|
235
|
+
* Parse API error from HttpError message
|
|
236
|
+
* HttpError format: "{status} - {statusText}: {responseBody}"
|
|
237
|
+
* API returns: {"id":"ERROR_ID","message":"error text","code":216}
|
|
238
|
+
*/
|
|
239
|
+
parseApiError(errorMessage) {
|
|
240
|
+
// Extract response body after the colon
|
|
241
|
+
const colonIndex = errorMessage.indexOf(':');
|
|
242
|
+
if (colonIndex === -1) {
|
|
243
|
+
return null;
|
|
244
|
+
}
|
|
245
|
+
const responseBody = errorMessage.substring(colonIndex + 1).trim();
|
|
246
|
+
// Try to extract JSON object from response (handles prefixed JSON)
|
|
247
|
+
const jsonMatch = responseBody.match(/\{.*\}$/);
|
|
248
|
+
const jsonString = jsonMatch ? jsonMatch[0] : responseBody;
|
|
249
|
+
try {
|
|
250
|
+
const parsed = JSON.parse(jsonString);
|
|
251
|
+
// Ensure all required fields are present
|
|
252
|
+
if (parsed.id &&
|
|
253
|
+
parsed.message !== undefined &&
|
|
254
|
+
parsed.code !== undefined) {
|
|
255
|
+
return {
|
|
256
|
+
id: parsed.id,
|
|
257
|
+
message: parsed.message,
|
|
258
|
+
code: parsed.code,
|
|
259
|
+
};
|
|
213
260
|
}
|
|
214
|
-
}
|
|
261
|
+
}
|
|
262
|
+
catch (_a) {
|
|
263
|
+
// JSON parsing failed
|
|
264
|
+
}
|
|
265
|
+
return null;
|
|
215
266
|
}
|
|
216
|
-
|
|
217
|
-
|
|
267
|
+
/**
|
|
268
|
+
* Make HTTP request to Enclave API
|
|
269
|
+
* Returns raw response without processing
|
|
270
|
+
*/
|
|
271
|
+
makeEnclaveRequest(endpoint, apiKey, body) {
|
|
218
272
|
return __awaiter(this, void 0, void 0, function* () {
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
}
|
|
227
|
-
const requestBody = {
|
|
228
|
-
method: method,
|
|
229
|
-
params: params,
|
|
230
|
-
share: signingShare,
|
|
231
|
-
chainId: chainId,
|
|
232
|
-
rpcUrl: rpcURL,
|
|
233
|
-
metadataStr: metadata,
|
|
234
|
-
clientPlatform: 'REACT_NATIVE',
|
|
235
|
-
clientPlatformVersion: (0, utils_1.getClientPlatformVersion)(),
|
|
236
|
-
};
|
|
237
|
-
try {
|
|
238
|
-
const response = yield this.requests.post('/v1/sign', {
|
|
239
|
-
headers: {
|
|
240
|
-
Authorization: `Bearer ${apiKey}`,
|
|
241
|
-
'Content-Type': 'application/json',
|
|
242
|
-
},
|
|
243
|
-
body: requestBody,
|
|
244
|
-
});
|
|
245
|
-
return this.encodeSuccessResult(response.data);
|
|
246
|
-
}
|
|
247
|
-
catch (error) {
|
|
248
|
-
if ((_a = error === null || error === void 0 ? void 0 : error.response) === null || _a === void 0 ? void 0 : _a.data) {
|
|
249
|
-
const portalError = this.decodePortalError(JSON.stringify(error.response.data));
|
|
250
|
-
return this.encodeErrorResult(portalError === null || portalError === void 0 ? void 0 : portalError.id, portalError === null || portalError === void 0 ? void 0 : portalError.message);
|
|
251
|
-
}
|
|
252
|
-
return this.encodeErrorResult('SIGNING_NETWORK_ERROR',
|
|
253
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-argument
|
|
254
|
-
error.message || 'Network error occurred');
|
|
255
|
-
}
|
|
273
|
+
return yield this.requests.post(endpoint, {
|
|
274
|
+
headers: {
|
|
275
|
+
Authorization: `Bearer ${apiKey}`,
|
|
276
|
+
'Content-Type': 'application/json',
|
|
277
|
+
},
|
|
278
|
+
body,
|
|
279
|
+
});
|
|
256
280
|
});
|
|
257
281
|
}
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
if (
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
282
|
+
/**
|
|
283
|
+
* Process Enclave API response
|
|
284
|
+
* Checks for error format and extracts data
|
|
285
|
+
*/
|
|
286
|
+
processEnclaveResponse(response) {
|
|
287
|
+
// Check for API-level errors first
|
|
288
|
+
// API returns errors at top level: {id, message, code}
|
|
289
|
+
if ('id' in response && 'message' in response && 'code' in response) {
|
|
290
|
+
const errorResponse = response;
|
|
291
|
+
throw new utils_1.PortalMpcError({
|
|
292
|
+
code: errorResponse.code,
|
|
293
|
+
id: errorResponse.id,
|
|
294
|
+
message: errorResponse.message,
|
|
295
|
+
});
|
|
269
296
|
}
|
|
270
|
-
|
|
271
|
-
|
|
297
|
+
// API returns success as: {data: string}
|
|
298
|
+
const successResponse = response;
|
|
299
|
+
if (!successResponse.data) {
|
|
300
|
+
throw new utils_1.PortalMpcError({
|
|
301
|
+
code: utils_1.PortalErrorCodes.BadRequest,
|
|
302
|
+
id: 'InvalidResponse',
|
|
303
|
+
message: 'API response missing data field',
|
|
304
|
+
});
|
|
272
305
|
}
|
|
306
|
+
return successResponse.data;
|
|
273
307
|
}
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
// Helper function to encode any object to JSON string
|
|
283
|
-
encodeJSON(value) {
|
|
284
|
-
try {
|
|
285
|
-
const jsonString = JSON.stringify(value);
|
|
286
|
-
return jsonString;
|
|
308
|
+
/**
|
|
309
|
+
* Handle errors from Enclave API calls
|
|
310
|
+
* Parses and transforms to PortalMpcError
|
|
311
|
+
*/
|
|
312
|
+
handleEnclaveError(error) {
|
|
313
|
+
// If it's already a PortalMpcError, re-throw it
|
|
314
|
+
if (error instanceof utils_1.PortalMpcError) {
|
|
315
|
+
throw error;
|
|
287
316
|
}
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
317
|
+
// Handle HTTP error responses
|
|
318
|
+
// HttpError format: "{status} - {statusText}: {responseBody}"
|
|
319
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-argument
|
|
320
|
+
const errorMessage = typeof error.message === 'string' ? error.message : '';
|
|
321
|
+
// Try to parse API error from HttpError message
|
|
322
|
+
const apiError = this.parseApiError(errorMessage);
|
|
323
|
+
if (apiError) {
|
|
324
|
+
throw new utils_1.PortalMpcError({
|
|
325
|
+
code: apiError.code,
|
|
326
|
+
id: apiError.id,
|
|
327
|
+
message: apiError.message,
|
|
294
328
|
});
|
|
295
329
|
}
|
|
330
|
+
// Check for 401 Unauthorized
|
|
331
|
+
if (errorMessage.startsWith('401 -')) {
|
|
332
|
+
throw new utils_1.PortalMpcError({
|
|
333
|
+
code: utils_1.PortalErrorCodes.InvalidApiKey,
|
|
334
|
+
id: 'Unauthorized',
|
|
335
|
+
message: 'Unauthorized request',
|
|
336
|
+
});
|
|
337
|
+
}
|
|
338
|
+
// Handle network errors
|
|
339
|
+
throw new utils_1.PortalMpcError({
|
|
340
|
+
code: utils_1.PortalErrorCodes.SigningNetworkError,
|
|
341
|
+
id: 'SIGNING_NETWORK_ERROR',
|
|
342
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-argument
|
|
343
|
+
message: errorMessage || 'Network error occurred',
|
|
344
|
+
});
|
|
296
345
|
}
|
|
297
346
|
sendMetrics(metrics, apiKey) {
|
|
298
347
|
return __awaiter(this, void 0, void 0, function* () {
|
|
@@ -8,7 +8,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
8
8
|
});
|
|
9
9
|
};
|
|
10
10
|
import { PortalCurve } from '@portal-hq/core';
|
|
11
|
-
import { HttpRequester, getClientPlatformVersion, } from '@portal-hq/utils';
|
|
11
|
+
import { HttpRequester, PortalErrorCodes, PortalMpcError, getClientPlatformVersion, } from '@portal-hq/utils';
|
|
12
12
|
import UUID from 'react-native-uuid';
|
|
13
13
|
var Operation;
|
|
14
14
|
(function (Operation) {
|
|
@@ -83,10 +83,22 @@ class EnclaveSigner {
|
|
|
83
83
|
break;
|
|
84
84
|
}
|
|
85
85
|
const shares = yield this.keychain.getShares();
|
|
86
|
+
// Validate shares exist
|
|
87
|
+
if (!shares.secp256k1) {
|
|
88
|
+
throw new PortalMpcError({
|
|
89
|
+
code: PortalErrorCodes.FailedToParseInputShareObject,
|
|
90
|
+
id: 'MissingShare',
|
|
91
|
+
message: '[Portal.Provider.EnclaveSigner] The SECP256K1 share is missing from the keychain.',
|
|
92
|
+
});
|
|
93
|
+
}
|
|
86
94
|
let signingShare = shares.secp256k1.share;
|
|
87
95
|
if (curve === PortalCurve.ED25519) {
|
|
88
96
|
if (!shares.ed25519) {
|
|
89
|
-
throw new
|
|
97
|
+
throw new PortalMpcError({
|
|
98
|
+
code: PortalErrorCodes.FailedToParseInputShareObject,
|
|
99
|
+
id: 'MissingShare',
|
|
100
|
+
message: '[Portal.Provider.EnclaveSigner] The ED25519 share is missing from the keychain.',
|
|
101
|
+
});
|
|
90
102
|
}
|
|
91
103
|
signingShare = shares.ed25519.share;
|
|
92
104
|
}
|
|
@@ -102,17 +114,24 @@ class EnclaveSigner {
|
|
|
102
114
|
reqId: traceId,
|
|
103
115
|
connectionTracingEnabled: shouldSendMetrics,
|
|
104
116
|
};
|
|
117
|
+
// Build params
|
|
118
|
+
// Avoid double JSON encoding: if params is already a string (e.g. a hex message), pass it directly; otherwise stringify objects/arrays.
|
|
119
|
+
const params = this.buildParams(method, message.params);
|
|
105
120
|
let formattedParams;
|
|
106
|
-
let rpcUrl;
|
|
107
121
|
if (isRaw) {
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
122
|
+
if (typeof params !== 'string') {
|
|
123
|
+
throw new Error('[Portal.Provider.EnclaveSigner] For raw signing, params must be a string (e.g., a hex-encoded message).');
|
|
124
|
+
}
|
|
125
|
+
formattedParams = params;
|
|
111
126
|
}
|
|
112
127
|
else {
|
|
113
|
-
formattedParams =
|
|
114
|
-
|
|
128
|
+
formattedParams =
|
|
129
|
+
typeof params === 'string' ? params : JSON.stringify(params);
|
|
115
130
|
}
|
|
131
|
+
// Get RPC URL (getGatewayUrl handles undefined chainId)
|
|
132
|
+
const rpcUrl = isRaw ? '' : provider.getGatewayUrl(chainId);
|
|
133
|
+
// Set metrics operation
|
|
134
|
+
metrics.operation = isRaw ? Operation.RAW_SIGN : Operation.SIGN;
|
|
116
135
|
if (typeof formattedParams !== 'string') {
|
|
117
136
|
throw new Error(`[Portal.Provider.EnclaveSigner] The formatted params for the signing request could not be converted to a string. The params were: ${formattedParams}`);
|
|
118
137
|
}
|
|
@@ -120,9 +139,40 @@ class EnclaveSigner {
|
|
|
120
139
|
metrics.sdkPreOperationMs = performance.now() - preOperationStartTime;
|
|
121
140
|
// Measure enclave signing operation time
|
|
122
141
|
const enclaveSignStartTime = performance.now();
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
142
|
+
// Build request based on operation type
|
|
143
|
+
let endpoint;
|
|
144
|
+
let requestBody;
|
|
145
|
+
if (isRaw) {
|
|
146
|
+
// Raw sign endpoint and body
|
|
147
|
+
endpoint = `/v1/raw/sign/${curve || 'SECP256K1'}`;
|
|
148
|
+
requestBody = {
|
|
149
|
+
params: formattedParams,
|
|
150
|
+
share: JSON.stringify(signingShare),
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
else {
|
|
154
|
+
// Standard sign endpoint and body
|
|
155
|
+
endpoint = '/v1/sign';
|
|
156
|
+
requestBody = {
|
|
157
|
+
method: message.method,
|
|
158
|
+
params: formattedParams,
|
|
159
|
+
share: JSON.stringify(signingShare),
|
|
160
|
+
chainId: chainId || '',
|
|
161
|
+
rpcUrl: rpcUrl,
|
|
162
|
+
metadataStr: JSON.stringify(metadata),
|
|
163
|
+
clientPlatform: 'REACT_NATIVE',
|
|
164
|
+
clientPlatformVersion: getClientPlatformVersion(),
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
// Make API request and process response
|
|
168
|
+
let result;
|
|
169
|
+
try {
|
|
170
|
+
const response = yield this.makeEnclaveRequest(endpoint, apiKey, requestBody);
|
|
171
|
+
result = this.processEnclaveResponse(response);
|
|
172
|
+
}
|
|
173
|
+
catch (error) {
|
|
174
|
+
this.handleEnclaveError(error);
|
|
175
|
+
}
|
|
126
176
|
// Post-operation processing time starts
|
|
127
177
|
const postOperationStartTime = performance.now();
|
|
128
178
|
// Record HTTP call time
|
|
@@ -176,118 +226,117 @@ class EnclaveSigner {
|
|
|
176
226
|
}
|
|
177
227
|
});
|
|
178
228
|
}
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
}
|
|
205
|
-
return this.encodeErrorResult('SIGNING_NETWORK_ERROR',
|
|
206
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-argument
|
|
207
|
-
error.message || 'Network error occurred');
|
|
229
|
+
/**
|
|
230
|
+
* Parse API error from HttpError message
|
|
231
|
+
* HttpError format: "{status} - {statusText}: {responseBody}"
|
|
232
|
+
* API returns: {"id":"ERROR_ID","message":"error text","code":216}
|
|
233
|
+
*/
|
|
234
|
+
parseApiError(errorMessage) {
|
|
235
|
+
// Extract response body after the colon
|
|
236
|
+
const colonIndex = errorMessage.indexOf(':');
|
|
237
|
+
if (colonIndex === -1) {
|
|
238
|
+
return null;
|
|
239
|
+
}
|
|
240
|
+
const responseBody = errorMessage.substring(colonIndex + 1).trim();
|
|
241
|
+
// Try to extract JSON object from response (handles prefixed JSON)
|
|
242
|
+
const jsonMatch = responseBody.match(/\{.*\}$/);
|
|
243
|
+
const jsonString = jsonMatch ? jsonMatch[0] : responseBody;
|
|
244
|
+
try {
|
|
245
|
+
const parsed = JSON.parse(jsonString);
|
|
246
|
+
// Ensure all required fields are present
|
|
247
|
+
if (parsed.id &&
|
|
248
|
+
parsed.message !== undefined &&
|
|
249
|
+
parsed.code !== undefined) {
|
|
250
|
+
return {
|
|
251
|
+
id: parsed.id,
|
|
252
|
+
message: parsed.message,
|
|
253
|
+
code: parsed.code,
|
|
254
|
+
};
|
|
208
255
|
}
|
|
209
|
-
}
|
|
256
|
+
}
|
|
257
|
+
catch (_a) {
|
|
258
|
+
// JSON parsing failed
|
|
259
|
+
}
|
|
260
|
+
return null;
|
|
210
261
|
}
|
|
211
|
-
|
|
212
|
-
|
|
262
|
+
/**
|
|
263
|
+
* Make HTTP request to Enclave API
|
|
264
|
+
* Returns raw response without processing
|
|
265
|
+
*/
|
|
266
|
+
makeEnclaveRequest(endpoint, apiKey, body) {
|
|
213
267
|
return __awaiter(this, void 0, void 0, function* () {
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
}
|
|
222
|
-
const requestBody = {
|
|
223
|
-
method: method,
|
|
224
|
-
params: params,
|
|
225
|
-
share: signingShare,
|
|
226
|
-
chainId: chainId,
|
|
227
|
-
rpcUrl: rpcURL,
|
|
228
|
-
metadataStr: metadata,
|
|
229
|
-
clientPlatform: 'REACT_NATIVE',
|
|
230
|
-
clientPlatformVersion: getClientPlatformVersion(),
|
|
231
|
-
};
|
|
232
|
-
try {
|
|
233
|
-
const response = yield this.requests.post('/v1/sign', {
|
|
234
|
-
headers: {
|
|
235
|
-
Authorization: `Bearer ${apiKey}`,
|
|
236
|
-
'Content-Type': 'application/json',
|
|
237
|
-
},
|
|
238
|
-
body: requestBody,
|
|
239
|
-
});
|
|
240
|
-
return this.encodeSuccessResult(response.data);
|
|
241
|
-
}
|
|
242
|
-
catch (error) {
|
|
243
|
-
if ((_a = error === null || error === void 0 ? void 0 : error.response) === null || _a === void 0 ? void 0 : _a.data) {
|
|
244
|
-
const portalError = this.decodePortalError(JSON.stringify(error.response.data));
|
|
245
|
-
return this.encodeErrorResult(portalError === null || portalError === void 0 ? void 0 : portalError.id, portalError === null || portalError === void 0 ? void 0 : portalError.message);
|
|
246
|
-
}
|
|
247
|
-
return this.encodeErrorResult('SIGNING_NETWORK_ERROR',
|
|
248
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-argument
|
|
249
|
-
error.message || 'Network error occurred');
|
|
250
|
-
}
|
|
268
|
+
return yield this.requests.post(endpoint, {
|
|
269
|
+
headers: {
|
|
270
|
+
Authorization: `Bearer ${apiKey}`,
|
|
271
|
+
'Content-Type': 'application/json',
|
|
272
|
+
},
|
|
273
|
+
body,
|
|
274
|
+
});
|
|
251
275
|
});
|
|
252
276
|
}
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
if (
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
277
|
+
/**
|
|
278
|
+
* Process Enclave API response
|
|
279
|
+
* Checks for error format and extracts data
|
|
280
|
+
*/
|
|
281
|
+
processEnclaveResponse(response) {
|
|
282
|
+
// Check for API-level errors first
|
|
283
|
+
// API returns errors at top level: {id, message, code}
|
|
284
|
+
if ('id' in response && 'message' in response && 'code' in response) {
|
|
285
|
+
const errorResponse = response;
|
|
286
|
+
throw new PortalMpcError({
|
|
287
|
+
code: errorResponse.code,
|
|
288
|
+
id: errorResponse.id,
|
|
289
|
+
message: errorResponse.message,
|
|
290
|
+
});
|
|
264
291
|
}
|
|
265
|
-
|
|
266
|
-
|
|
292
|
+
// API returns success as: {data: string}
|
|
293
|
+
const successResponse = response;
|
|
294
|
+
if (!successResponse.data) {
|
|
295
|
+
throw new PortalMpcError({
|
|
296
|
+
code: PortalErrorCodes.BadRequest,
|
|
297
|
+
id: 'InvalidResponse',
|
|
298
|
+
message: 'API response missing data field',
|
|
299
|
+
});
|
|
267
300
|
}
|
|
301
|
+
return successResponse.data;
|
|
268
302
|
}
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
// Helper function to encode any object to JSON string
|
|
278
|
-
encodeJSON(value) {
|
|
279
|
-
try {
|
|
280
|
-
const jsonString = JSON.stringify(value);
|
|
281
|
-
return jsonString;
|
|
303
|
+
/**
|
|
304
|
+
* Handle errors from Enclave API calls
|
|
305
|
+
* Parses and transforms to PortalMpcError
|
|
306
|
+
*/
|
|
307
|
+
handleEnclaveError(error) {
|
|
308
|
+
// If it's already a PortalMpcError, re-throw it
|
|
309
|
+
if (error instanceof PortalMpcError) {
|
|
310
|
+
throw error;
|
|
282
311
|
}
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
312
|
+
// Handle HTTP error responses
|
|
313
|
+
// HttpError format: "{status} - {statusText}: {responseBody}"
|
|
314
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-argument
|
|
315
|
+
const errorMessage = typeof error.message === 'string' ? error.message : '';
|
|
316
|
+
// Try to parse API error from HttpError message
|
|
317
|
+
const apiError = this.parseApiError(errorMessage);
|
|
318
|
+
if (apiError) {
|
|
319
|
+
throw new PortalMpcError({
|
|
320
|
+
code: apiError.code,
|
|
321
|
+
id: apiError.id,
|
|
322
|
+
message: apiError.message,
|
|
289
323
|
});
|
|
290
324
|
}
|
|
325
|
+
// Check for 401 Unauthorized
|
|
326
|
+
if (errorMessage.startsWith('401 -')) {
|
|
327
|
+
throw new PortalMpcError({
|
|
328
|
+
code: PortalErrorCodes.InvalidApiKey,
|
|
329
|
+
id: 'Unauthorized',
|
|
330
|
+
message: 'Unauthorized request',
|
|
331
|
+
});
|
|
332
|
+
}
|
|
333
|
+
// Handle network errors
|
|
334
|
+
throw new PortalMpcError({
|
|
335
|
+
code: PortalErrorCodes.SigningNetworkError,
|
|
336
|
+
id: 'SIGNING_NETWORK_ERROR',
|
|
337
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-argument
|
|
338
|
+
message: errorMessage || 'Network error occurred',
|
|
339
|
+
});
|
|
291
340
|
}
|
|
292
341
|
sendMetrics(metrics, apiKey) {
|
|
293
342
|
return __awaiter(this, void 0, void 0, function* () {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@portal-hq/provider",
|
|
3
|
-
"version": "4.1.
|
|
3
|
+
"version": "4.1.6",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"main": "lib/commonjs/index",
|
|
6
6
|
"module": "lib/esm/index",
|
|
@@ -19,8 +19,8 @@
|
|
|
19
19
|
"test": "jest"
|
|
20
20
|
},
|
|
21
21
|
"dependencies": {
|
|
22
|
-
"@portal-hq/connect": "^4.1.
|
|
23
|
-
"@portal-hq/utils": "^4.1.
|
|
22
|
+
"@portal-hq/connect": "^4.1.6",
|
|
23
|
+
"@portal-hq/utils": "^4.1.6",
|
|
24
24
|
"@types/react-native-uuid": "^2.0.0",
|
|
25
25
|
"react-native-uuid": "^2.0.3"
|
|
26
26
|
},
|
|
@@ -32,5 +32,5 @@
|
|
|
32
32
|
"ts-jest": "^29.0.3",
|
|
33
33
|
"typescript": "^4.8.4"
|
|
34
34
|
},
|
|
35
|
-
"gitHead": "
|
|
35
|
+
"gitHead": "6906f8cc1dbd92d87effa174b8fa73b74bf63532"
|
|
36
36
|
}
|
package/src/signers/enclave.ts
CHANGED
|
@@ -4,6 +4,8 @@ import {
|
|
|
4
4
|
HttpRequester,
|
|
5
5
|
IPortalProvider,
|
|
6
6
|
KeychainAdapter,
|
|
7
|
+
PortalErrorCodes,
|
|
8
|
+
PortalMpcError,
|
|
7
9
|
type SigningRequestArguments,
|
|
8
10
|
getClientPlatformVersion,
|
|
9
11
|
} from '@portal-hq/utils'
|
|
@@ -12,9 +14,7 @@ import UUID from 'react-native-uuid'
|
|
|
12
14
|
import {
|
|
13
15
|
type MpcSignerOptions,
|
|
14
16
|
PortalMobileMpcMetadata,
|
|
15
|
-
type EnclaveSignRequest,
|
|
16
17
|
type EnclaveSignResponse,
|
|
17
|
-
type EnclaveSignResult,
|
|
18
18
|
} from '../../types'
|
|
19
19
|
import Signer from './abstract'
|
|
20
20
|
|
|
@@ -88,13 +88,27 @@ class EnclaveSigner implements Signer {
|
|
|
88
88
|
}
|
|
89
89
|
|
|
90
90
|
const shares = await this.keychain.getShares()
|
|
91
|
+
|
|
92
|
+
// Validate shares exist
|
|
93
|
+
if (!shares.secp256k1) {
|
|
94
|
+
throw new PortalMpcError({
|
|
95
|
+
code: PortalErrorCodes.FailedToParseInputShareObject,
|
|
96
|
+
id: 'MissingShare',
|
|
97
|
+
message:
|
|
98
|
+
'[Portal.Provider.EnclaveSigner] The SECP256K1 share is missing from the keychain.',
|
|
99
|
+
})
|
|
100
|
+
}
|
|
101
|
+
|
|
91
102
|
let signingShare = shares.secp256k1.share
|
|
92
103
|
|
|
93
104
|
if (curve === PortalCurve.ED25519) {
|
|
94
105
|
if (!shares.ed25519) {
|
|
95
|
-
throw new
|
|
96
|
-
|
|
97
|
-
|
|
106
|
+
throw new PortalMpcError({
|
|
107
|
+
code: PortalErrorCodes.FailedToParseInputShareObject,
|
|
108
|
+
id: 'MissingShare',
|
|
109
|
+
message:
|
|
110
|
+
'[Portal.Provider.EnclaveSigner] The ED25519 share is missing from the keychain.',
|
|
111
|
+
})
|
|
98
112
|
}
|
|
99
113
|
signingShare = shares.ed25519.share
|
|
100
114
|
}
|
|
@@ -112,22 +126,29 @@ class EnclaveSigner implements Signer {
|
|
|
112
126
|
connectionTracingEnabled: shouldSendMetrics,
|
|
113
127
|
}
|
|
114
128
|
|
|
129
|
+
// Build params
|
|
130
|
+
// Avoid double JSON encoding: if params is already a string (e.g. a hex message), pass it directly; otherwise stringify objects/arrays.
|
|
131
|
+
const params = this.buildParams(method, message.params)
|
|
115
132
|
let formattedParams: string
|
|
116
|
-
let rpcUrl: string
|
|
117
133
|
|
|
118
134
|
if (isRaw) {
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
135
|
+
if (typeof params !== 'string') {
|
|
136
|
+
throw new Error(
|
|
137
|
+
'[Portal.Provider.EnclaveSigner] For raw signing, params must be a string (e.g., a hex-encoded message).',
|
|
138
|
+
)
|
|
139
|
+
}
|
|
140
|
+
formattedParams = params
|
|
124
141
|
} else {
|
|
125
|
-
formattedParams =
|
|
126
|
-
|
|
127
|
-
)
|
|
128
|
-
rpcUrl = provider.getGatewayUrl(chainId)
|
|
142
|
+
formattedParams =
|
|
143
|
+
typeof params === 'string' ? params : JSON.stringify(params)
|
|
129
144
|
}
|
|
130
145
|
|
|
146
|
+
// Get RPC URL (getGatewayUrl handles undefined chainId)
|
|
147
|
+
const rpcUrl = isRaw ? '' : provider.getGatewayUrl(chainId)
|
|
148
|
+
|
|
149
|
+
// Set metrics operation
|
|
150
|
+
metrics.operation = isRaw ? Operation.RAW_SIGN : Operation.SIGN
|
|
151
|
+
|
|
131
152
|
if (typeof formattedParams !== 'string') {
|
|
132
153
|
throw new Error(
|
|
133
154
|
`[Portal.Provider.EnclaveSigner] The formatted params for the signing request could not be converted to a string. The params were: ${formattedParams}`,
|
|
@@ -140,22 +161,44 @@ class EnclaveSigner implements Signer {
|
|
|
140
161
|
// Measure enclave signing operation time
|
|
141
162
|
const enclaveSignStartTime = performance.now()
|
|
142
163
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
164
|
+
// Build request based on operation type
|
|
165
|
+
let endpoint: string
|
|
166
|
+
let requestBody: Record<string, any>
|
|
167
|
+
|
|
168
|
+
if (isRaw) {
|
|
169
|
+
// Raw sign endpoint and body
|
|
170
|
+
endpoint = `/v1/raw/sign/${curve || 'SECP256K1'}`
|
|
171
|
+
requestBody = {
|
|
172
|
+
params: formattedParams,
|
|
173
|
+
share: JSON.stringify(signingShare),
|
|
174
|
+
}
|
|
175
|
+
} else {
|
|
176
|
+
// Standard sign endpoint and body
|
|
177
|
+
endpoint = '/v1/sign'
|
|
178
|
+
requestBody = {
|
|
179
|
+
method: message.method,
|
|
180
|
+
params: formattedParams,
|
|
181
|
+
share: JSON.stringify(signingShare),
|
|
182
|
+
chainId: chainId || '',
|
|
183
|
+
rpcUrl: rpcUrl,
|
|
184
|
+
metadataStr: JSON.stringify(metadata),
|
|
185
|
+
clientPlatform: 'REACT_NATIVE',
|
|
186
|
+
clientPlatformVersion: getClientPlatformVersion(),
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// Make API request and process response
|
|
191
|
+
let result: string
|
|
192
|
+
try {
|
|
193
|
+
const response = await this.makeEnclaveRequest(
|
|
194
|
+
endpoint,
|
|
195
|
+
apiKey,
|
|
196
|
+
requestBody,
|
|
197
|
+
)
|
|
198
|
+
result = this.processEnclaveResponse(response)
|
|
199
|
+
} catch (error) {
|
|
200
|
+
this.handleEnclaveError(error)
|
|
201
|
+
}
|
|
159
202
|
|
|
160
203
|
// Post-operation processing time starts
|
|
161
204
|
const postOperationStartTime = performance.now()
|
|
@@ -216,153 +259,141 @@ class EnclaveSigner implements Signer {
|
|
|
216
259
|
}
|
|
217
260
|
}
|
|
218
261
|
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
262
|
+
/**
|
|
263
|
+
* Parse API error from HttpError message
|
|
264
|
+
* HttpError format: "{status} - {statusText}: {responseBody}"
|
|
265
|
+
* API returns: {"id":"ERROR_ID","message":"error text","code":216}
|
|
266
|
+
*/
|
|
267
|
+
private parseApiError(
|
|
268
|
+
errorMessage: string,
|
|
269
|
+
): { id: string; message: string; code: number } | null {
|
|
270
|
+
// Extract response body after the colon
|
|
271
|
+
const colonIndex = errorMessage.indexOf(':')
|
|
272
|
+
if (colonIndex === -1) {
|
|
273
|
+
return null
|
|
230
274
|
}
|
|
231
275
|
|
|
232
|
-
const
|
|
233
|
-
params: params,
|
|
234
|
-
share: signingShare,
|
|
235
|
-
}
|
|
276
|
+
const responseBody = errorMessage.substring(colonIndex + 1).trim()
|
|
236
277
|
|
|
237
|
-
|
|
278
|
+
// Try to extract JSON object from response (handles prefixed JSON)
|
|
279
|
+
const jsonMatch = responseBody.match(/\{.*\}$/)
|
|
280
|
+
const jsonString = jsonMatch ? jsonMatch[0] : responseBody
|
|
238
281
|
|
|
239
282
|
try {
|
|
240
|
-
const
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
)
|
|
254
|
-
return this.encodeErrorResult(portalError?.id, portalError?.message)
|
|
283
|
+
const parsed = JSON.parse(jsonString)
|
|
284
|
+
|
|
285
|
+
// Ensure all required fields are present
|
|
286
|
+
if (
|
|
287
|
+
parsed.id &&
|
|
288
|
+
parsed.message !== undefined &&
|
|
289
|
+
parsed.code !== undefined
|
|
290
|
+
) {
|
|
291
|
+
return {
|
|
292
|
+
id: parsed.id,
|
|
293
|
+
message: parsed.message,
|
|
294
|
+
code: parsed.code,
|
|
295
|
+
}
|
|
255
296
|
}
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-argument
|
|
259
|
-
error.message || 'Network error occurred',
|
|
260
|
-
)
|
|
297
|
+
} catch {
|
|
298
|
+
// JSON parsing failed
|
|
261
299
|
}
|
|
300
|
+
|
|
301
|
+
return null
|
|
262
302
|
}
|
|
263
303
|
|
|
264
|
-
|
|
304
|
+
/**
|
|
305
|
+
* Make HTTP request to Enclave API
|
|
306
|
+
* Returns raw response without processing
|
|
307
|
+
*/
|
|
308
|
+
private async makeEnclaveRequest(
|
|
309
|
+
endpoint: string,
|
|
265
310
|
apiKey: string,
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
!method ||
|
|
277
|
-
!params ||
|
|
278
|
-
!chainId ||
|
|
279
|
-
!metadata
|
|
280
|
-
) {
|
|
281
|
-
return this.encodeErrorResult(
|
|
282
|
-
'INVALID_PARAMETERS',
|
|
283
|
-
'Invalid parameters provided',
|
|
284
|
-
)
|
|
285
|
-
}
|
|
311
|
+
body: Record<string, any>,
|
|
312
|
+
): Promise<EnclaveSignResponse> {
|
|
313
|
+
return await this.requests.post<EnclaveSignResponse>(endpoint, {
|
|
314
|
+
headers: {
|
|
315
|
+
Authorization: `Bearer ${apiKey}`,
|
|
316
|
+
'Content-Type': 'application/json',
|
|
317
|
+
},
|
|
318
|
+
body,
|
|
319
|
+
})
|
|
320
|
+
}
|
|
286
321
|
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
322
|
+
/**
|
|
323
|
+
* Process Enclave API response
|
|
324
|
+
* Checks for error format and extracts data
|
|
325
|
+
*/
|
|
326
|
+
private processEnclaveResponse(response: EnclaveSignResponse): string {
|
|
327
|
+
// Check for API-level errors first
|
|
328
|
+
// API returns errors at top level: {id, message, code}
|
|
329
|
+
if ('id' in response && 'message' in response && 'code' in response) {
|
|
330
|
+
const errorResponse = response as {
|
|
331
|
+
id: string
|
|
332
|
+
message: string
|
|
333
|
+
code: number
|
|
334
|
+
}
|
|
335
|
+
throw new PortalMpcError({
|
|
336
|
+
code: errorResponse.code,
|
|
337
|
+
id: errorResponse.id,
|
|
338
|
+
message: errorResponse.message,
|
|
339
|
+
})
|
|
296
340
|
}
|
|
297
341
|
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
body: requestBody,
|
|
307
|
-
},
|
|
308
|
-
)
|
|
309
|
-
|
|
310
|
-
return this.encodeSuccessResult(response.data)
|
|
311
|
-
} catch (error: any) {
|
|
312
|
-
if (error?.response?.data) {
|
|
313
|
-
const portalError = this.decodePortalError(
|
|
314
|
-
JSON.stringify(error.response.data),
|
|
315
|
-
)
|
|
316
|
-
return this.encodeErrorResult(portalError?.id, portalError?.message)
|
|
317
|
-
}
|
|
318
|
-
return this.encodeErrorResult(
|
|
319
|
-
'SIGNING_NETWORK_ERROR',
|
|
320
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-argument
|
|
321
|
-
error.message || 'Network error occurred',
|
|
322
|
-
)
|
|
342
|
+
// API returns success as: {data: string}
|
|
343
|
+
const successResponse = response as { data: string }
|
|
344
|
+
if (!successResponse.data) {
|
|
345
|
+
throw new PortalMpcError({
|
|
346
|
+
code: PortalErrorCodes.BadRequest,
|
|
347
|
+
id: 'InvalidResponse',
|
|
348
|
+
message: 'API response missing data field',
|
|
349
|
+
})
|
|
323
350
|
}
|
|
324
|
-
}
|
|
325
351
|
|
|
326
|
-
|
|
327
|
-
private encodeSuccessResult(data: string): string {
|
|
328
|
-
const successResult: EnclaveSignResult = { data: data, error: undefined }
|
|
329
|
-
return this.encodeJSON(successResult)
|
|
352
|
+
return successResponse.data
|
|
330
353
|
}
|
|
331
354
|
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
return null
|
|
355
|
+
/**
|
|
356
|
+
* Handle errors from Enclave API calls
|
|
357
|
+
* Parses and transforms to PortalMpcError
|
|
358
|
+
*/
|
|
359
|
+
private handleEnclaveError(error: any): never {
|
|
360
|
+
// If it's already a PortalMpcError, re-throw it
|
|
361
|
+
if (error instanceof PortalMpcError) {
|
|
362
|
+
throw error
|
|
341
363
|
}
|
|
342
|
-
}
|
|
343
364
|
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
error
|
|
365
|
+
// Handle HTTP error responses
|
|
366
|
+
// HttpError format: "{status} - {statusText}: {responseBody}"
|
|
367
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-argument
|
|
368
|
+
const errorMessage: string =
|
|
369
|
+
typeof error.message === 'string' ? error.message : ''
|
|
370
|
+
|
|
371
|
+
// Try to parse API error from HttpError message
|
|
372
|
+
const apiError = this.parseApiError(errorMessage)
|
|
373
|
+
if (apiError) {
|
|
374
|
+
throw new PortalMpcError({
|
|
375
|
+
code: apiError.code,
|
|
376
|
+
id: apiError.id,
|
|
377
|
+
message: apiError.message,
|
|
378
|
+
})
|
|
349
379
|
}
|
|
350
|
-
return this.encodeJSON(errorResult)
|
|
351
|
-
}
|
|
352
380
|
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
return JSON.stringify({
|
|
360
|
-
error: {
|
|
361
|
-
id: 'ENCODING_ERROR',
|
|
362
|
-
message: `Failed to encode JSON: ${error.message}`,
|
|
363
|
-
},
|
|
381
|
+
// Check for 401 Unauthorized
|
|
382
|
+
if (errorMessage.startsWith('401 -')) {
|
|
383
|
+
throw new PortalMpcError({
|
|
384
|
+
code: PortalErrorCodes.InvalidApiKey,
|
|
385
|
+
id: 'Unauthorized',
|
|
386
|
+
message: 'Unauthorized request',
|
|
364
387
|
})
|
|
365
388
|
}
|
|
389
|
+
|
|
390
|
+
// Handle network errors
|
|
391
|
+
throw new PortalMpcError({
|
|
392
|
+
code: PortalErrorCodes.SigningNetworkError,
|
|
393
|
+
id: 'SIGNING_NETWORK_ERROR',
|
|
394
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-argument
|
|
395
|
+
message: errorMessage || 'Network error occurred',
|
|
396
|
+
})
|
|
366
397
|
}
|
|
367
398
|
|
|
368
399
|
private async sendMetrics(
|
package/types.d.ts
CHANGED
|
@@ -179,10 +179,12 @@ export interface EnclaveSignRequest {
|
|
|
179
179
|
clientPlatformVersion: string
|
|
180
180
|
}
|
|
181
181
|
|
|
182
|
-
export
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
182
|
+
export type EnclaveSignResponse =
|
|
183
|
+
| {
|
|
184
|
+
data: string
|
|
185
|
+
}
|
|
186
|
+
| {
|
|
187
|
+
id: string
|
|
188
|
+
message: string
|
|
189
|
+
code: number
|
|
190
|
+
}
|