@portal-hq/provider 4.1.5 → 4.1.7
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/providers/index.js +1 -1
- package/lib/commonjs/signers/enclave.js +161 -106
- package/lib/esm/providers/index.js +1 -1
- package/lib/esm/signers/enclave.js +162 -107
- package/package.json +4 -4
- package/src/providers/index.ts +1 -1
- package/src/signers/enclave.ts +183 -144
- package/types.d.ts +9 -7
|
@@ -38,7 +38,7 @@ class Provider {
|
|
|
38
38
|
return this.keychain.getAddresses();
|
|
39
39
|
}
|
|
40
40
|
get address() {
|
|
41
|
-
return this.keychain.
|
|
41
|
+
return this.keychain.getAddresses().then((addrs) => addrs === null || addrs === void 0 ? void 0 : addrs.eip155);
|
|
42
42
|
}
|
|
43
43
|
constructor({
|
|
44
44
|
// Required
|
|
@@ -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
|
}
|
|
@@ -121,7 +133,7 @@ class EnclaveSigner {
|
|
|
121
133
|
formattedParams =
|
|
122
134
|
typeof params === 'string' ? params : JSON.stringify(params);
|
|
123
135
|
}
|
|
124
|
-
// Get RPC URL
|
|
136
|
+
// Get RPC URL (getGatewayUrl handles undefined chainId)
|
|
125
137
|
const rpcUrl = isRaw ? '' : provider.getGatewayUrl(chainId);
|
|
126
138
|
// Set metrics operation
|
|
127
139
|
metrics.operation = isRaw ? Operation.RAW_SIGN : Operation.SIGN;
|
|
@@ -132,9 +144,40 @@ class EnclaveSigner {
|
|
|
132
144
|
metrics.sdkPreOperationMs = performance.now() - preOperationStartTime;
|
|
133
145
|
// Measure enclave signing operation time
|
|
134
146
|
const enclaveSignStartTime = performance.now();
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
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
|
+
}
|
|
138
181
|
// Post-operation processing time starts
|
|
139
182
|
const postOperationStartTime = performance.now();
|
|
140
183
|
// Record HTTP call time
|
|
@@ -188,118 +231,130 @@ class EnclaveSigner {
|
|
|
188
231
|
}
|
|
189
232
|
});
|
|
190
233
|
}
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
}
|
|
217
|
-
return this.encodeErrorResult('SIGNING_NETWORK_ERROR',
|
|
218
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-argument
|
|
219
|
-
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
|
+
};
|
|
220
260
|
}
|
|
221
|
-
}
|
|
261
|
+
}
|
|
262
|
+
catch (_a) {
|
|
263
|
+
// JSON parsing failed
|
|
264
|
+
}
|
|
265
|
+
return null;
|
|
222
266
|
}
|
|
223
|
-
|
|
224
|
-
|
|
267
|
+
/**
|
|
268
|
+
* Make HTTP request to Enclave API
|
|
269
|
+
* Returns raw response without processing
|
|
270
|
+
*/
|
|
271
|
+
makeEnclaveRequest(endpoint, apiKey, body) {
|
|
225
272
|
return __awaiter(this, void 0, void 0, function* () {
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
}
|
|
234
|
-
const requestBody = {
|
|
235
|
-
method: method,
|
|
236
|
-
params: params,
|
|
237
|
-
share: signingShare,
|
|
238
|
-
chainId: chainId,
|
|
239
|
-
rpcUrl: rpcURL,
|
|
240
|
-
metadataStr: metadata,
|
|
241
|
-
clientPlatform: 'REACT_NATIVE',
|
|
242
|
-
clientPlatformVersion: (0, utils_1.getClientPlatformVersion)(),
|
|
243
|
-
};
|
|
244
|
-
try {
|
|
245
|
-
const response = yield this.requests.post('/v1/sign', {
|
|
246
|
-
headers: {
|
|
247
|
-
Authorization: `Bearer ${apiKey}`,
|
|
248
|
-
'Content-Type': 'application/json',
|
|
249
|
-
},
|
|
250
|
-
body: requestBody,
|
|
251
|
-
});
|
|
252
|
-
return this.encodeSuccessResult(response.data);
|
|
253
|
-
}
|
|
254
|
-
catch (error) {
|
|
255
|
-
if ((_a = error === null || error === void 0 ? void 0 : error.response) === null || _a === void 0 ? void 0 : _a.data) {
|
|
256
|
-
const portalError = this.decodePortalError(JSON.stringify(error.response.data));
|
|
257
|
-
return this.encodeErrorResult(portalError === null || portalError === void 0 ? void 0 : portalError.id, portalError === null || portalError === void 0 ? void 0 : portalError.message);
|
|
258
|
-
}
|
|
259
|
-
return this.encodeErrorResult('SIGNING_NETWORK_ERROR',
|
|
260
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-argument
|
|
261
|
-
error.message || 'Network error occurred');
|
|
262
|
-
}
|
|
273
|
+
return yield this.requests.post(endpoint, {
|
|
274
|
+
headers: {
|
|
275
|
+
Authorization: `Bearer ${apiKey}`,
|
|
276
|
+
'Content-Type': 'application/json',
|
|
277
|
+
},
|
|
278
|
+
body,
|
|
279
|
+
});
|
|
263
280
|
});
|
|
264
281
|
}
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
if (
|
|
273
|
-
return
|
|
274
|
-
try {
|
|
275
|
-
return JSON.parse(errorStr);
|
|
282
|
+
/**
|
|
283
|
+
* Process Enclave API response
|
|
284
|
+
* Checks for error format and extracts data
|
|
285
|
+
*/
|
|
286
|
+
processEnclaveResponse(response) {
|
|
287
|
+
// Handle case where API returns signature directly as a string
|
|
288
|
+
// This can happen if JSON parsing fails in HttpRequest.buildResponse()
|
|
289
|
+
if (typeof response === 'string') {
|
|
290
|
+
return response;
|
|
276
291
|
}
|
|
277
|
-
|
|
278
|
-
|
|
292
|
+
// Ensure response is an object before using 'in' operator
|
|
293
|
+
if (!response || typeof response !== 'object') {
|
|
294
|
+
throw new utils_1.PortalMpcError({
|
|
295
|
+
code: utils_1.PortalErrorCodes.BadRequest,
|
|
296
|
+
id: 'InvalidResponse',
|
|
297
|
+
message: `Invalid API response format: expected object, got ${typeof response}`,
|
|
298
|
+
});
|
|
279
299
|
}
|
|
300
|
+
// Check for API-level errors first
|
|
301
|
+
// API returns errors at top level: {id, message, code}
|
|
302
|
+
if ('id' in response && 'message' in response && 'code' in response) {
|
|
303
|
+
const errorResponse = response;
|
|
304
|
+
throw new utils_1.PortalMpcError({
|
|
305
|
+
code: errorResponse.code,
|
|
306
|
+
id: errorResponse.id,
|
|
307
|
+
message: errorResponse.message,
|
|
308
|
+
});
|
|
309
|
+
}
|
|
310
|
+
// API returns success as: {data: string}
|
|
311
|
+
const successResponse = response;
|
|
312
|
+
if (!successResponse.data) {
|
|
313
|
+
throw new utils_1.PortalMpcError({
|
|
314
|
+
code: utils_1.PortalErrorCodes.BadRequest,
|
|
315
|
+
id: 'InvalidResponse',
|
|
316
|
+
message: 'API response missing data field',
|
|
317
|
+
});
|
|
318
|
+
}
|
|
319
|
+
return successResponse.data;
|
|
280
320
|
}
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
// Helper function to encode any object to JSON string
|
|
290
|
-
encodeJSON(value) {
|
|
291
|
-
try {
|
|
292
|
-
const jsonString = JSON.stringify(value);
|
|
293
|
-
return jsonString;
|
|
321
|
+
/**
|
|
322
|
+
* Handle errors from Enclave API calls
|
|
323
|
+
* Parses and transforms to PortalMpcError
|
|
324
|
+
*/
|
|
325
|
+
handleEnclaveError(error) {
|
|
326
|
+
// If it's already a PortalMpcError, re-throw it
|
|
327
|
+
if (error instanceof utils_1.PortalMpcError) {
|
|
328
|
+
throw error;
|
|
294
329
|
}
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
330
|
+
// Handle HTTP error responses
|
|
331
|
+
// HttpError format: "{status} - {statusText}: {responseBody}"
|
|
332
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-argument
|
|
333
|
+
const errorMessage = typeof error.message === 'string' ? error.message : '';
|
|
334
|
+
// Try to parse API error from HttpError message
|
|
335
|
+
const apiError = this.parseApiError(errorMessage);
|
|
336
|
+
if (apiError) {
|
|
337
|
+
throw new utils_1.PortalMpcError({
|
|
338
|
+
code: apiError.code,
|
|
339
|
+
id: apiError.id,
|
|
340
|
+
message: apiError.message,
|
|
301
341
|
});
|
|
302
342
|
}
|
|
343
|
+
// Check for 401 Unauthorized
|
|
344
|
+
if (errorMessage.startsWith('401 -')) {
|
|
345
|
+
throw new utils_1.PortalMpcError({
|
|
346
|
+
code: utils_1.PortalErrorCodes.InvalidApiKey,
|
|
347
|
+
id: 'Unauthorized',
|
|
348
|
+
message: 'Unauthorized request',
|
|
349
|
+
});
|
|
350
|
+
}
|
|
351
|
+
// Handle network errors
|
|
352
|
+
throw new utils_1.PortalMpcError({
|
|
353
|
+
code: utils_1.PortalErrorCodes.SigningNetworkError,
|
|
354
|
+
id: 'SIGNING_NETWORK_ERROR',
|
|
355
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-argument
|
|
356
|
+
message: errorMessage || 'Network error occurred',
|
|
357
|
+
});
|
|
303
358
|
}
|
|
304
359
|
sendMetrics(metrics, apiKey) {
|
|
305
360
|
return __awaiter(this, void 0, void 0, function* () {
|
|
@@ -36,7 +36,7 @@ class Provider {
|
|
|
36
36
|
return this.keychain.getAddresses();
|
|
37
37
|
}
|
|
38
38
|
get address() {
|
|
39
|
-
return this.keychain.
|
|
39
|
+
return this.keychain.getAddresses().then((addrs) => addrs === null || addrs === void 0 ? void 0 : addrs.eip155);
|
|
40
40
|
}
|
|
41
41
|
constructor({
|
|
42
42
|
// Required
|
|
@@ -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
|
}
|
|
@@ -116,7 +128,7 @@ class EnclaveSigner {
|
|
|
116
128
|
formattedParams =
|
|
117
129
|
typeof params === 'string' ? params : JSON.stringify(params);
|
|
118
130
|
}
|
|
119
|
-
// Get RPC URL
|
|
131
|
+
// Get RPC URL (getGatewayUrl handles undefined chainId)
|
|
120
132
|
const rpcUrl = isRaw ? '' : provider.getGatewayUrl(chainId);
|
|
121
133
|
// Set metrics operation
|
|
122
134
|
metrics.operation = isRaw ? Operation.RAW_SIGN : Operation.SIGN;
|
|
@@ -127,9 +139,40 @@ class EnclaveSigner {
|
|
|
127
139
|
metrics.sdkPreOperationMs = performance.now() - preOperationStartTime;
|
|
128
140
|
// Measure enclave signing operation time
|
|
129
141
|
const enclaveSignStartTime = performance.now();
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
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
|
+
}
|
|
133
176
|
// Post-operation processing time starts
|
|
134
177
|
const postOperationStartTime = performance.now();
|
|
135
178
|
// Record HTTP call time
|
|
@@ -183,118 +226,130 @@ class EnclaveSigner {
|
|
|
183
226
|
}
|
|
184
227
|
});
|
|
185
228
|
}
|
|
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
|
-
|
|
211
|
-
}
|
|
212
|
-
return this.encodeErrorResult('SIGNING_NETWORK_ERROR',
|
|
213
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-argument
|
|
214
|
-
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
|
+
};
|
|
215
255
|
}
|
|
216
|
-
}
|
|
256
|
+
}
|
|
257
|
+
catch (_a) {
|
|
258
|
+
// JSON parsing failed
|
|
259
|
+
}
|
|
260
|
+
return null;
|
|
217
261
|
}
|
|
218
|
-
|
|
219
|
-
|
|
262
|
+
/**
|
|
263
|
+
* Make HTTP request to Enclave API
|
|
264
|
+
* Returns raw response without processing
|
|
265
|
+
*/
|
|
266
|
+
makeEnclaveRequest(endpoint, apiKey, body) {
|
|
220
267
|
return __awaiter(this, void 0, void 0, function* () {
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
}
|
|
229
|
-
const requestBody = {
|
|
230
|
-
method: method,
|
|
231
|
-
params: params,
|
|
232
|
-
share: signingShare,
|
|
233
|
-
chainId: chainId,
|
|
234
|
-
rpcUrl: rpcURL,
|
|
235
|
-
metadataStr: metadata,
|
|
236
|
-
clientPlatform: 'REACT_NATIVE',
|
|
237
|
-
clientPlatformVersion: getClientPlatformVersion(),
|
|
238
|
-
};
|
|
239
|
-
try {
|
|
240
|
-
const response = yield this.requests.post('/v1/sign', {
|
|
241
|
-
headers: {
|
|
242
|
-
Authorization: `Bearer ${apiKey}`,
|
|
243
|
-
'Content-Type': 'application/json',
|
|
244
|
-
},
|
|
245
|
-
body: requestBody,
|
|
246
|
-
});
|
|
247
|
-
return this.encodeSuccessResult(response.data);
|
|
248
|
-
}
|
|
249
|
-
catch (error) {
|
|
250
|
-
if ((_a = error === null || error === void 0 ? void 0 : error.response) === null || _a === void 0 ? void 0 : _a.data) {
|
|
251
|
-
const portalError = this.decodePortalError(JSON.stringify(error.response.data));
|
|
252
|
-
return this.encodeErrorResult(portalError === null || portalError === void 0 ? void 0 : portalError.id, portalError === null || portalError === void 0 ? void 0 : portalError.message);
|
|
253
|
-
}
|
|
254
|
-
return this.encodeErrorResult('SIGNING_NETWORK_ERROR',
|
|
255
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-argument
|
|
256
|
-
error.message || 'Network error occurred');
|
|
257
|
-
}
|
|
268
|
+
return yield this.requests.post(endpoint, {
|
|
269
|
+
headers: {
|
|
270
|
+
Authorization: `Bearer ${apiKey}`,
|
|
271
|
+
'Content-Type': 'application/json',
|
|
272
|
+
},
|
|
273
|
+
body,
|
|
274
|
+
});
|
|
258
275
|
});
|
|
259
276
|
}
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
if (
|
|
268
|
-
return
|
|
269
|
-
try {
|
|
270
|
-
return JSON.parse(errorStr);
|
|
277
|
+
/**
|
|
278
|
+
* Process Enclave API response
|
|
279
|
+
* Checks for error format and extracts data
|
|
280
|
+
*/
|
|
281
|
+
processEnclaveResponse(response) {
|
|
282
|
+
// Handle case where API returns signature directly as a string
|
|
283
|
+
// This can happen if JSON parsing fails in HttpRequest.buildResponse()
|
|
284
|
+
if (typeof response === 'string') {
|
|
285
|
+
return response;
|
|
271
286
|
}
|
|
272
|
-
|
|
273
|
-
|
|
287
|
+
// Ensure response is an object before using 'in' operator
|
|
288
|
+
if (!response || typeof response !== 'object') {
|
|
289
|
+
throw new PortalMpcError({
|
|
290
|
+
code: PortalErrorCodes.BadRequest,
|
|
291
|
+
id: 'InvalidResponse',
|
|
292
|
+
message: `Invalid API response format: expected object, got ${typeof response}`,
|
|
293
|
+
});
|
|
274
294
|
}
|
|
295
|
+
// Check for API-level errors first
|
|
296
|
+
// API returns errors at top level: {id, message, code}
|
|
297
|
+
if ('id' in response && 'message' in response && 'code' in response) {
|
|
298
|
+
const errorResponse = response;
|
|
299
|
+
throw new PortalMpcError({
|
|
300
|
+
code: errorResponse.code,
|
|
301
|
+
id: errorResponse.id,
|
|
302
|
+
message: errorResponse.message,
|
|
303
|
+
});
|
|
304
|
+
}
|
|
305
|
+
// API returns success as: {data: string}
|
|
306
|
+
const successResponse = response;
|
|
307
|
+
if (!successResponse.data) {
|
|
308
|
+
throw new PortalMpcError({
|
|
309
|
+
code: PortalErrorCodes.BadRequest,
|
|
310
|
+
id: 'InvalidResponse',
|
|
311
|
+
message: 'API response missing data field',
|
|
312
|
+
});
|
|
313
|
+
}
|
|
314
|
+
return successResponse.data;
|
|
275
315
|
}
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
// Helper function to encode any object to JSON string
|
|
285
|
-
encodeJSON(value) {
|
|
286
|
-
try {
|
|
287
|
-
const jsonString = JSON.stringify(value);
|
|
288
|
-
return jsonString;
|
|
316
|
+
/**
|
|
317
|
+
* Handle errors from Enclave API calls
|
|
318
|
+
* Parses and transforms to PortalMpcError
|
|
319
|
+
*/
|
|
320
|
+
handleEnclaveError(error) {
|
|
321
|
+
// If it's already a PortalMpcError, re-throw it
|
|
322
|
+
if (error instanceof PortalMpcError) {
|
|
323
|
+
throw error;
|
|
289
324
|
}
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
325
|
+
// Handle HTTP error responses
|
|
326
|
+
// HttpError format: "{status} - {statusText}: {responseBody}"
|
|
327
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-argument
|
|
328
|
+
const errorMessage = typeof error.message === 'string' ? error.message : '';
|
|
329
|
+
// Try to parse API error from HttpError message
|
|
330
|
+
const apiError = this.parseApiError(errorMessage);
|
|
331
|
+
if (apiError) {
|
|
332
|
+
throw new PortalMpcError({
|
|
333
|
+
code: apiError.code,
|
|
334
|
+
id: apiError.id,
|
|
335
|
+
message: apiError.message,
|
|
296
336
|
});
|
|
297
337
|
}
|
|
338
|
+
// Check for 401 Unauthorized
|
|
339
|
+
if (errorMessage.startsWith('401 -')) {
|
|
340
|
+
throw new PortalMpcError({
|
|
341
|
+
code: PortalErrorCodes.InvalidApiKey,
|
|
342
|
+
id: 'Unauthorized',
|
|
343
|
+
message: 'Unauthorized request',
|
|
344
|
+
});
|
|
345
|
+
}
|
|
346
|
+
// Handle network errors
|
|
347
|
+
throw new PortalMpcError({
|
|
348
|
+
code: PortalErrorCodes.SigningNetworkError,
|
|
349
|
+
id: 'SIGNING_NETWORK_ERROR',
|
|
350
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-argument
|
|
351
|
+
message: errorMessage || 'Network error occurred',
|
|
352
|
+
});
|
|
298
353
|
}
|
|
299
354
|
sendMetrics(metrics, apiKey) {
|
|
300
355
|
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.7",
|
|
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.7",
|
|
23
|
+
"@portal-hq/utils": "^4.1.7",
|
|
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": "d8be728d9b04f0c3e2577fd98b1e38282cc950ba"
|
|
36
36
|
}
|
package/src/providers/index.ts
CHANGED
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
|
}
|
|
@@ -129,7 +143,7 @@ class EnclaveSigner implements Signer {
|
|
|
129
143
|
typeof params === 'string' ? params : JSON.stringify(params)
|
|
130
144
|
}
|
|
131
145
|
|
|
132
|
-
// Get RPC URL
|
|
146
|
+
// Get RPC URL (getGatewayUrl handles undefined chainId)
|
|
133
147
|
const rpcUrl = isRaw ? '' : provider.getGatewayUrl(chainId)
|
|
134
148
|
|
|
135
149
|
// Set metrics operation
|
|
@@ -147,22 +161,44 @@ class EnclaveSigner implements Signer {
|
|
|
147
161
|
// Measure enclave signing operation time
|
|
148
162
|
const enclaveSignStartTime = performance.now()
|
|
149
163
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
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
|
+
}
|
|
166
202
|
|
|
167
203
|
// Post-operation processing time starts
|
|
168
204
|
const postOperationStartTime = performance.now()
|
|
@@ -223,153 +259,156 @@ class EnclaveSigner implements Signer {
|
|
|
223
259
|
}
|
|
224
260
|
}
|
|
225
261
|
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
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
|
|
237
274
|
}
|
|
238
275
|
|
|
239
|
-
const
|
|
240
|
-
params: params,
|
|
241
|
-
share: signingShare,
|
|
242
|
-
}
|
|
276
|
+
const responseBody = errorMessage.substring(colonIndex + 1).trim()
|
|
243
277
|
|
|
244
|
-
|
|
278
|
+
// Try to extract JSON object from response (handles prefixed JSON)
|
|
279
|
+
const jsonMatch = responseBody.match(/\{.*\}$/)
|
|
280
|
+
const jsonString = jsonMatch ? jsonMatch[0] : responseBody
|
|
245
281
|
|
|
246
282
|
try {
|
|
247
|
-
const
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
)
|
|
261
|
-
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
|
+
}
|
|
262
296
|
}
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-argument
|
|
266
|
-
error.message || 'Network error occurred',
|
|
267
|
-
)
|
|
297
|
+
} catch {
|
|
298
|
+
// JSON parsing failed
|
|
268
299
|
}
|
|
300
|
+
|
|
301
|
+
return null
|
|
269
302
|
}
|
|
270
303
|
|
|
271
|
-
|
|
304
|
+
/**
|
|
305
|
+
* Make HTTP request to Enclave API
|
|
306
|
+
* Returns raw response without processing
|
|
307
|
+
*/
|
|
308
|
+
private async makeEnclaveRequest(
|
|
309
|
+
endpoint: string,
|
|
272
310
|
apiKey: string,
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
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
|
+
}
|
|
321
|
+
|
|
322
|
+
/**
|
|
323
|
+
* Process Enclave API response
|
|
324
|
+
* Checks for error format and extracts data
|
|
325
|
+
*/
|
|
326
|
+
private processEnclaveResponse(response: EnclaveSignResponse): string {
|
|
327
|
+
// Handle case where API returns signature directly as a string
|
|
328
|
+
// This can happen if JSON parsing fails in HttpRequest.buildResponse()
|
|
329
|
+
if (typeof response === 'string') {
|
|
330
|
+
return response
|
|
292
331
|
}
|
|
293
332
|
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
clientPlatform: 'REACT_NATIVE',
|
|
302
|
-
clientPlatformVersion: getClientPlatformVersion(),
|
|
333
|
+
// Ensure response is an object before using 'in' operator
|
|
334
|
+
if (!response || typeof response !== 'object') {
|
|
335
|
+
throw new PortalMpcError({
|
|
336
|
+
code: PortalErrorCodes.BadRequest,
|
|
337
|
+
id: 'InvalidResponse',
|
|
338
|
+
message: `Invalid API response format: expected object, got ${typeof response}`,
|
|
339
|
+
})
|
|
303
340
|
}
|
|
304
341
|
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
},
|
|
313
|
-
body: requestBody,
|
|
314
|
-
},
|
|
315
|
-
)
|
|
316
|
-
|
|
317
|
-
return this.encodeSuccessResult(response.data)
|
|
318
|
-
} catch (error: any) {
|
|
319
|
-
if (error?.response?.data) {
|
|
320
|
-
const portalError = this.decodePortalError(
|
|
321
|
-
JSON.stringify(error.response.data),
|
|
322
|
-
)
|
|
323
|
-
return this.encodeErrorResult(portalError?.id, portalError?.message)
|
|
342
|
+
// Check for API-level errors first
|
|
343
|
+
// API returns errors at top level: {id, message, code}
|
|
344
|
+
if ('id' in response && 'message' in response && 'code' in response) {
|
|
345
|
+
const errorResponse = response as {
|
|
346
|
+
id: string
|
|
347
|
+
message: string
|
|
348
|
+
code: number
|
|
324
349
|
}
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
)
|
|
350
|
+
throw new PortalMpcError({
|
|
351
|
+
code: errorResponse.code,
|
|
352
|
+
id: errorResponse.id,
|
|
353
|
+
message: errorResponse.message,
|
|
354
|
+
})
|
|
330
355
|
}
|
|
331
|
-
}
|
|
332
356
|
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
357
|
+
// API returns success as: {data: string}
|
|
358
|
+
const successResponse = response as { data: string }
|
|
359
|
+
if (!successResponse.data) {
|
|
360
|
+
throw new PortalMpcError({
|
|
361
|
+
code: PortalErrorCodes.BadRequest,
|
|
362
|
+
id: 'InvalidResponse',
|
|
363
|
+
message: 'API response missing data field',
|
|
364
|
+
})
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
return successResponse.data
|
|
337
368
|
}
|
|
338
369
|
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
return null
|
|
370
|
+
/**
|
|
371
|
+
* Handle errors from Enclave API calls
|
|
372
|
+
* Parses and transforms to PortalMpcError
|
|
373
|
+
*/
|
|
374
|
+
private handleEnclaveError(error: any): never {
|
|
375
|
+
// If it's already a PortalMpcError, re-throw it
|
|
376
|
+
if (error instanceof PortalMpcError) {
|
|
377
|
+
throw error
|
|
348
378
|
}
|
|
349
|
-
}
|
|
350
379
|
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
error
|
|
380
|
+
// Handle HTTP error responses
|
|
381
|
+
// HttpError format: "{status} - {statusText}: {responseBody}"
|
|
382
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-argument
|
|
383
|
+
const errorMessage: string =
|
|
384
|
+
typeof error.message === 'string' ? error.message : ''
|
|
385
|
+
|
|
386
|
+
// Try to parse API error from HttpError message
|
|
387
|
+
const apiError = this.parseApiError(errorMessage)
|
|
388
|
+
if (apiError) {
|
|
389
|
+
throw new PortalMpcError({
|
|
390
|
+
code: apiError.code,
|
|
391
|
+
id: apiError.id,
|
|
392
|
+
message: apiError.message,
|
|
393
|
+
})
|
|
356
394
|
}
|
|
357
|
-
return this.encodeJSON(errorResult)
|
|
358
|
-
}
|
|
359
395
|
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
return JSON.stringify({
|
|
367
|
-
error: {
|
|
368
|
-
id: 'ENCODING_ERROR',
|
|
369
|
-
message: `Failed to encode JSON: ${error.message}`,
|
|
370
|
-
},
|
|
396
|
+
// Check for 401 Unauthorized
|
|
397
|
+
if (errorMessage.startsWith('401 -')) {
|
|
398
|
+
throw new PortalMpcError({
|
|
399
|
+
code: PortalErrorCodes.InvalidApiKey,
|
|
400
|
+
id: 'Unauthorized',
|
|
401
|
+
message: 'Unauthorized request',
|
|
371
402
|
})
|
|
372
403
|
}
|
|
404
|
+
|
|
405
|
+
// Handle network errors
|
|
406
|
+
throw new PortalMpcError({
|
|
407
|
+
code: PortalErrorCodes.SigningNetworkError,
|
|
408
|
+
id: 'SIGNING_NETWORK_ERROR',
|
|
409
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-argument
|
|
410
|
+
message: errorMessage || 'Network error occurred',
|
|
411
|
+
})
|
|
373
412
|
}
|
|
374
413
|
|
|
375
414
|
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
|
+
}
|