@portal-hq/provider 4.1.3 → 4.1.4

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.
@@ -44,7 +44,7 @@ class Provider {
44
44
  // Required
45
45
  apiKey, keychain, gatewayConfig,
46
46
  // Optional
47
- autoApprove = false, apiHost = 'api.portalhq.io', mpcHost = 'mpc.portalhq.io', version = 'v6', chainId = 'eip155:11155111', featureFlags = {}, }) {
47
+ autoApprove = false, apiHost = 'api.portalhq.io', mpcHost = 'mpc.portalhq.io', enclaveMPCHost = 'mpc-client.portalhq.io', version = 'v6', chainId = 'eip155:11155111', featureFlags = {}, }) {
48
48
  // Handle required fields
49
49
  if (!apiKey || apiKey.length === 0) {
50
50
  throw new utils_1.InvalidApiKeyError();
@@ -69,14 +69,26 @@ class Provider {
69
69
  });
70
70
  // Initialize Gateway HttpRequester
71
71
  this.gateway = new utils_1.PortalRequests();
72
- // Initialize an MpcSigner
73
- this.signer = new signers_1.MpcSigner({
74
- mpcHost,
75
- keychain,
76
- version,
77
- portalApi: this.portalApi,
78
- featureFlags,
79
- });
72
+ // Initialize the appropriate signer based on feature flags
73
+ if (featureFlags.useEnclaveMPCApi) {
74
+ this.signer = new signers_1.EnclaveSigner({
75
+ mpcHost,
76
+ enclaveMPCHost,
77
+ keychain,
78
+ version,
79
+ portalApi: this.portalApi,
80
+ featureFlags,
81
+ });
82
+ }
83
+ else {
84
+ this.signer = new signers_1.MpcSigner({
85
+ mpcHost,
86
+ keychain,
87
+ version,
88
+ portalApi: this.portalApi,
89
+ featureFlags,
90
+ });
91
+ }
80
92
  }
81
93
  /**
82
94
  * Invokes all registered event handlers with the data provided
@@ -282,8 +294,7 @@ class Provider {
282
294
  });
283
295
  }
284
296
  /**
285
- * Updates the chainId of this instance and builds a new RPC HttpRequester for
286
- * the gateway used for the new chain
297
+ * Updates the chainId of this instance
287
298
  *
288
299
  * @param chainId The numerical ID of the chain to switch to
289
300
  * @returns BaseProvider
@@ -0,0 +1,316 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ var __importDefault = (this && this.__importDefault) || function (mod) {
12
+ return (mod && mod.__esModule) ? mod : { "default": mod };
13
+ };
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ const core_1 = require("@portal-hq/core");
16
+ const utils_1 = require("@portal-hq/utils");
17
+ const react_native_uuid_1 = __importDefault(require("react-native-uuid"));
18
+ var Operation;
19
+ (function (Operation) {
20
+ Operation["SIGN"] = "sign";
21
+ Operation["RAW_SIGN"] = "raw_sign";
22
+ })(Operation || (Operation = {}));
23
+ class EnclaveSigner {
24
+ constructor({ keychain, enclaveMPCHost = 'mpc-client.portalhq.io', version = 'v6', portalApi, featureFlags = {}, }) {
25
+ this.version = 'v6';
26
+ this.buildParams = (method, txParams) => {
27
+ let params = txParams;
28
+ switch (method) {
29
+ case 'eth_sign':
30
+ case 'personal_sign':
31
+ case 'eth_signTypedData_v3':
32
+ case 'eth_signTypedData_v4':
33
+ case 'sol_signMessage':
34
+ case 'sol_signTransaction':
35
+ case 'sol_signAndSendTransaction':
36
+ case 'sol_signAndConfirmTransaction':
37
+ if (!Array.isArray(txParams)) {
38
+ params = [txParams];
39
+ }
40
+ break;
41
+ default:
42
+ if (Array.isArray(txParams)) {
43
+ if (txParams.length === 1) {
44
+ params = txParams[0];
45
+ }
46
+ }
47
+ }
48
+ return params;
49
+ };
50
+ this.featureFlags = featureFlags;
51
+ this.keychain = keychain;
52
+ this.enclaveMPCHost = enclaveMPCHost;
53
+ this.version = version;
54
+ this.portalApi = portalApi;
55
+ this.requests = new utils_1.HttpRequester({
56
+ baseUrl: `https://${this.enclaveMPCHost}`,
57
+ });
58
+ }
59
+ sign(message, provider) {
60
+ return __awaiter(this, void 0, void 0, function* () {
61
+ // Always track metrics, but only send if feature flag is enabled
62
+ const shouldSendMetrics = this.featureFlags.enableSdkPerformanceMetrics === true;
63
+ const signStartTime = performance.now();
64
+ const preOperationStartTime = performance.now();
65
+ // Generate a traceId for this operation
66
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call
67
+ const traceId = react_native_uuid_1.default.v4();
68
+ const metrics = {
69
+ hasError: false,
70
+ operation: Operation.SIGN,
71
+ signingMethod: message.method,
72
+ traceId,
73
+ };
74
+ try {
75
+ const eip155Address = yield this.keychain.getEip155Address();
76
+ const apiKey = provider.apiKey;
77
+ const { method, chainId, curve, isRaw } = message;
78
+ // Add chainId to metrics
79
+ if (chainId) {
80
+ metrics.chainId = chainId;
81
+ }
82
+ switch (method) {
83
+ case 'eth_requestAccounts':
84
+ return [eip155Address];
85
+ case 'eth_accounts':
86
+ return [eip155Address];
87
+ default:
88
+ break;
89
+ }
90
+ const shares = yield this.keychain.getShares();
91
+ let signingShare = shares.secp256k1.share;
92
+ if (curve === core_1.PortalCurve.ED25519) {
93
+ if (!shares.ed25519) {
94
+ throw new Error('[Portal.Provider.EnclaveSigner] The ED25519 share is missing from the keychain.');
95
+ }
96
+ signingShare = shares.ed25519.share;
97
+ }
98
+ const metadata = {
99
+ clientPlatform: 'REACT_NATIVE',
100
+ clientPlatformVersion: (0, utils_1.getClientPlatformVersion)(),
101
+ isMultiBackupEnabled: this.featureFlags.isMultiBackupEnabled,
102
+ mpcServerVersion: this.version,
103
+ optimized: true,
104
+ curve,
105
+ chainId,
106
+ isRaw,
107
+ reqId: traceId,
108
+ connectionTracingEnabled: shouldSendMetrics,
109
+ };
110
+ let formattedParams;
111
+ let rpcUrl;
112
+ if (isRaw) {
113
+ formattedParams = JSON.stringify(this.buildParams(method, message.params));
114
+ rpcUrl = '';
115
+ metrics.operation = Operation.RAW_SIGN;
116
+ }
117
+ else {
118
+ formattedParams = JSON.stringify(this.buildParams(method, message.params));
119
+ rpcUrl = provider.getGatewayUrl(chainId);
120
+ }
121
+ if (typeof formattedParams !== 'string') {
122
+ 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
+ }
124
+ // Record pre-operation time
125
+ metrics.sdkPreOperationMs = performance.now() - preOperationStartTime;
126
+ // Measure enclave signing operation time
127
+ const enclaveSignStartTime = performance.now();
128
+ const result = isRaw
129
+ ? yield this.enclaveRawSign(apiKey, JSON.stringify(signingShare), formattedParams, curve || 'SECP256K1')
130
+ : yield this.enclaveSign(apiKey, JSON.stringify(signingShare), message.method, formattedParams, rpcUrl, chainId || '', JSON.stringify(metadata));
131
+ // Post-operation processing time starts
132
+ const postOperationStartTime = performance.now();
133
+ // Record HTTP call time
134
+ metrics.enclaveHttpCallMs = performance.now() - enclaveSignStartTime;
135
+ // Record post-operation time
136
+ metrics.sdkPostOperationMs = performance.now() - postOperationStartTime;
137
+ // Calculate total SDK signing time
138
+ metrics.sdkOperationMs = performance.now() - signStartTime;
139
+ // Log performance timing to console
140
+ const timingMetrics = {};
141
+ if (typeof metrics.sdkPreOperationMs === 'number') {
142
+ timingMetrics['Pre-operation'] = metrics.sdkPreOperationMs;
143
+ }
144
+ if (typeof metrics.enclaveHttpCallMs === 'number') {
145
+ timingMetrics['Enclave HTTP Call'] = metrics.enclaveHttpCallMs;
146
+ }
147
+ if (typeof metrics.sdkPostOperationMs === 'number') {
148
+ timingMetrics['Post-operation'] = metrics.sdkPostOperationMs;
149
+ }
150
+ // Only send metrics if the feature flag is enabled
151
+ if (shouldSendMetrics && this.portalApi) {
152
+ try {
153
+ yield this.sendMetrics(metrics, apiKey);
154
+ }
155
+ catch (err) {
156
+ // No-op
157
+ }
158
+ }
159
+ return result;
160
+ }
161
+ catch (error) {
162
+ // Calculate total time even in error case
163
+ metrics.sdkOperationMs = performance.now() - signStartTime;
164
+ // Log performance timing to console even in error case
165
+ const errorTimingMetrics = {};
166
+ if (typeof metrics.sdkPreOperationMs === 'number') {
167
+ errorTimingMetrics['Pre-operation'] = metrics.sdkPreOperationMs;
168
+ }
169
+ // Only send metrics if the feature flag is enabled
170
+ if (shouldSendMetrics) {
171
+ const apiKey = provider.apiKey;
172
+ metrics.hasError = true;
173
+ try {
174
+ yield this.sendMetrics(metrics, apiKey);
175
+ }
176
+ catch (_a) {
177
+ // No-op
178
+ }
179
+ }
180
+ throw error;
181
+ }
182
+ });
183
+ }
184
+ enclaveRawSign(apiKey, signingShare, params, curve) {
185
+ var _a;
186
+ return __awaiter(this, void 0, void 0, function* () {
187
+ if (!apiKey || !signingShare || !params || !curve) {
188
+ return this.encodeErrorResult('INVALID_PARAMETERS', 'Invalid parameters provided for raw signing');
189
+ }
190
+ const requestBody = {
191
+ params: params,
192
+ share: signingShare,
193
+ };
194
+ const endpoint = `/v1/raw/sign/${curve}`;
195
+ try {
196
+ const response = yield this.requests.post(endpoint, {
197
+ headers: {
198
+ Authorization: `Bearer ${apiKey}`,
199
+ 'Content-Type': 'application/json',
200
+ },
201
+ body: requestBody,
202
+ });
203
+ return this.encodeSuccessResult(response.data);
204
+ }
205
+ catch (error) {
206
+ if ((_a = error === null || error === void 0 ? void 0 : error.response) === null || _a === void 0 ? void 0 : _a.data) {
207
+ const portalError = this.decodePortalError(JSON.stringify(error.response.data));
208
+ return this.encodeErrorResult(portalError === null || portalError === void 0 ? void 0 : portalError.id, portalError === null || portalError === void 0 ? void 0 : portalError.message);
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');
213
+ }
214
+ });
215
+ }
216
+ enclaveSign(apiKey, signingShare, method, params, rpcURL, chainId, metadata) {
217
+ var _a;
218
+ return __awaiter(this, void 0, void 0, function* () {
219
+ if (!apiKey ||
220
+ !signingShare ||
221
+ !method ||
222
+ !params ||
223
+ !chainId ||
224
+ !metadata) {
225
+ return this.encodeErrorResult('INVALID_PARAMETERS', 'Invalid parameters provided');
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
+ }
256
+ });
257
+ }
258
+ // Helper function to encode success results
259
+ encodeSuccessResult(data) {
260
+ const successResult = { data: data, error: undefined };
261
+ return this.encodeJSON(successResult);
262
+ }
263
+ // Helper function to decode Portal errors
264
+ decodePortalError(errorStr) {
265
+ if (!errorStr)
266
+ return null;
267
+ try {
268
+ return JSON.parse(errorStr);
269
+ }
270
+ catch (_a) {
271
+ return null;
272
+ }
273
+ }
274
+ // Helper function to encode error results
275
+ encodeErrorResult(id, message) {
276
+ const errorResult = {
277
+ data: undefined,
278
+ error: { id, message },
279
+ };
280
+ return this.encodeJSON(errorResult);
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;
287
+ }
288
+ catch (error) {
289
+ return JSON.stringify({
290
+ error: {
291
+ id: 'ENCODING_ERROR',
292
+ message: `Failed to encode JSON: ${error.message}`,
293
+ },
294
+ });
295
+ }
296
+ }
297
+ sendMetrics(metrics, apiKey) {
298
+ return __awaiter(this, void 0, void 0, function* () {
299
+ try {
300
+ if (this.portalApi) {
301
+ yield this.portalApi.post('/api/v3/clients/me/sdk/metrics', {
302
+ headers: {
303
+ Authorization: `Bearer ${apiKey}`,
304
+ 'Content-Type': 'application/json',
305
+ },
306
+ body: metrics,
307
+ });
308
+ }
309
+ }
310
+ catch (_a) {
311
+ // No-op
312
+ }
313
+ });
314
+ }
315
+ }
316
+ exports.default = EnclaveSigner;
@@ -3,8 +3,10 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.MpcSigner = exports.Signer = void 0;
6
+ exports.EnclaveSigner = exports.MpcSigner = exports.Signer = void 0;
7
7
  var abstract_1 = require("./abstract");
8
8
  Object.defineProperty(exports, "Signer", { enumerable: true, get: function () { return __importDefault(abstract_1).default; } });
9
9
  var mpc_1 = require("./mpc");
10
10
  Object.defineProperty(exports, "MpcSigner", { enumerable: true, get: function () { return __importDefault(mpc_1).default; } });
11
+ var enclave_1 = require("./enclave");
12
+ Object.defineProperty(exports, "EnclaveSigner", { enumerable: true, get: function () { return __importDefault(enclave_1).default; } });
@@ -158,7 +158,7 @@ class MpcSigner {
158
158
  metrics.sdkBinaryConnectMs = binaryMetrics.connectMs;
159
159
  }
160
160
  }
161
- if ((error === null || error === void 0 ? void 0 : error.code) > 0) {
161
+ if (error === null || error === void 0 ? void 0 : error.id) {
162
162
  throw new utils_1.PortalMpcError(error);
163
163
  }
164
164
  // Record post-operation time
@@ -9,7 +9,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
9
9
  };
10
10
  import { PortalCurve } from '@portal-hq/core';
11
11
  import { Events, HttpRequester, InvalidApiKeyError, InvalidGatewayConfigError, PortalRequests, ProviderRpcError, RpcErrorCodes, } from '@portal-hq/utils';
12
- import { MpcSigner } from '../signers';
12
+ import { MpcSigner, EnclaveSigner } from '../signers';
13
13
  const passiveSignerMethods = [
14
14
  'eth_accounts',
15
15
  'eth_chainId',
@@ -42,7 +42,7 @@ class Provider {
42
42
  // Required
43
43
  apiKey, keychain, gatewayConfig,
44
44
  // Optional
45
- autoApprove = false, apiHost = 'api.portalhq.io', mpcHost = 'mpc.portalhq.io', version = 'v6', chainId = 'eip155:11155111', featureFlags = {}, }) {
45
+ autoApprove = false, apiHost = 'api.portalhq.io', mpcHost = 'mpc.portalhq.io', enclaveMPCHost = 'mpc-client.portalhq.io', version = 'v6', chainId = 'eip155:11155111', featureFlags = {}, }) {
46
46
  // Handle required fields
47
47
  if (!apiKey || apiKey.length === 0) {
48
48
  throw new InvalidApiKeyError();
@@ -67,14 +67,26 @@ class Provider {
67
67
  });
68
68
  // Initialize Gateway HttpRequester
69
69
  this.gateway = new PortalRequests();
70
- // Initialize an MpcSigner
71
- this.signer = new MpcSigner({
72
- mpcHost,
73
- keychain,
74
- version,
75
- portalApi: this.portalApi,
76
- featureFlags,
77
- });
70
+ // Initialize the appropriate signer based on feature flags
71
+ if (featureFlags.useEnclaveMPCApi) {
72
+ this.signer = new EnclaveSigner({
73
+ mpcHost,
74
+ enclaveMPCHost,
75
+ keychain,
76
+ version,
77
+ portalApi: this.portalApi,
78
+ featureFlags,
79
+ });
80
+ }
81
+ else {
82
+ this.signer = new MpcSigner({
83
+ mpcHost,
84
+ keychain,
85
+ version,
86
+ portalApi: this.portalApi,
87
+ featureFlags,
88
+ });
89
+ }
78
90
  }
79
91
  /**
80
92
  * Invokes all registered event handlers with the data provided
@@ -280,8 +292,7 @@ class Provider {
280
292
  });
281
293
  }
282
294
  /**
283
- * Updates the chainId of this instance and builds a new RPC HttpRequester for
284
- * the gateway used for the new chain
295
+ * Updates the chainId of this instance
285
296
  *
286
297
  * @param chainId The numerical ID of the chain to switch to
287
298
  * @returns BaseProvider
@@ -0,0 +1,311 @@
1
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
2
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
3
+ return new (P || (P = Promise))(function (resolve, reject) {
4
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
5
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
6
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
7
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
8
+ });
9
+ };
10
+ import { PortalCurve } from '@portal-hq/core';
11
+ import { HttpRequester, getClientPlatformVersion, } from '@portal-hq/utils';
12
+ import UUID from 'react-native-uuid';
13
+ var Operation;
14
+ (function (Operation) {
15
+ Operation["SIGN"] = "sign";
16
+ Operation["RAW_SIGN"] = "raw_sign";
17
+ })(Operation || (Operation = {}));
18
+ class EnclaveSigner {
19
+ constructor({ keychain, enclaveMPCHost = 'mpc-client.portalhq.io', version = 'v6', portalApi, featureFlags = {}, }) {
20
+ this.version = 'v6';
21
+ this.buildParams = (method, txParams) => {
22
+ let params = txParams;
23
+ switch (method) {
24
+ case 'eth_sign':
25
+ case 'personal_sign':
26
+ case 'eth_signTypedData_v3':
27
+ case 'eth_signTypedData_v4':
28
+ case 'sol_signMessage':
29
+ case 'sol_signTransaction':
30
+ case 'sol_signAndSendTransaction':
31
+ case 'sol_signAndConfirmTransaction':
32
+ if (!Array.isArray(txParams)) {
33
+ params = [txParams];
34
+ }
35
+ break;
36
+ default:
37
+ if (Array.isArray(txParams)) {
38
+ if (txParams.length === 1) {
39
+ params = txParams[0];
40
+ }
41
+ }
42
+ }
43
+ return params;
44
+ };
45
+ this.featureFlags = featureFlags;
46
+ this.keychain = keychain;
47
+ this.enclaveMPCHost = enclaveMPCHost;
48
+ this.version = version;
49
+ this.portalApi = portalApi;
50
+ this.requests = new HttpRequester({
51
+ baseUrl: `https://${this.enclaveMPCHost}`,
52
+ });
53
+ }
54
+ sign(message, provider) {
55
+ return __awaiter(this, void 0, void 0, function* () {
56
+ // Always track metrics, but only send if feature flag is enabled
57
+ const shouldSendMetrics = this.featureFlags.enableSdkPerformanceMetrics === true;
58
+ const signStartTime = performance.now();
59
+ const preOperationStartTime = performance.now();
60
+ // Generate a traceId for this operation
61
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call
62
+ const traceId = UUID.v4();
63
+ const metrics = {
64
+ hasError: false,
65
+ operation: Operation.SIGN,
66
+ signingMethod: message.method,
67
+ traceId,
68
+ };
69
+ try {
70
+ const eip155Address = yield this.keychain.getEip155Address();
71
+ const apiKey = provider.apiKey;
72
+ const { method, chainId, curve, isRaw } = message;
73
+ // Add chainId to metrics
74
+ if (chainId) {
75
+ metrics.chainId = chainId;
76
+ }
77
+ switch (method) {
78
+ case 'eth_requestAccounts':
79
+ return [eip155Address];
80
+ case 'eth_accounts':
81
+ return [eip155Address];
82
+ default:
83
+ break;
84
+ }
85
+ const shares = yield this.keychain.getShares();
86
+ let signingShare = shares.secp256k1.share;
87
+ if (curve === PortalCurve.ED25519) {
88
+ if (!shares.ed25519) {
89
+ throw new Error('[Portal.Provider.EnclaveSigner] The ED25519 share is missing from the keychain.');
90
+ }
91
+ signingShare = shares.ed25519.share;
92
+ }
93
+ const metadata = {
94
+ clientPlatform: 'REACT_NATIVE',
95
+ clientPlatformVersion: getClientPlatformVersion(),
96
+ isMultiBackupEnabled: this.featureFlags.isMultiBackupEnabled,
97
+ mpcServerVersion: this.version,
98
+ optimized: true,
99
+ curve,
100
+ chainId,
101
+ isRaw,
102
+ reqId: traceId,
103
+ connectionTracingEnabled: shouldSendMetrics,
104
+ };
105
+ let formattedParams;
106
+ let rpcUrl;
107
+ if (isRaw) {
108
+ formattedParams = JSON.stringify(this.buildParams(method, message.params));
109
+ rpcUrl = '';
110
+ metrics.operation = Operation.RAW_SIGN;
111
+ }
112
+ else {
113
+ formattedParams = JSON.stringify(this.buildParams(method, message.params));
114
+ rpcUrl = provider.getGatewayUrl(chainId);
115
+ }
116
+ if (typeof formattedParams !== 'string') {
117
+ 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
+ }
119
+ // Record pre-operation time
120
+ metrics.sdkPreOperationMs = performance.now() - preOperationStartTime;
121
+ // Measure enclave signing operation time
122
+ const enclaveSignStartTime = performance.now();
123
+ const result = isRaw
124
+ ? yield this.enclaveRawSign(apiKey, JSON.stringify(signingShare), formattedParams, curve || 'SECP256K1')
125
+ : yield this.enclaveSign(apiKey, JSON.stringify(signingShare), message.method, formattedParams, rpcUrl, chainId || '', JSON.stringify(metadata));
126
+ // Post-operation processing time starts
127
+ const postOperationStartTime = performance.now();
128
+ // Record HTTP call time
129
+ metrics.enclaveHttpCallMs = performance.now() - enclaveSignStartTime;
130
+ // Record post-operation time
131
+ metrics.sdkPostOperationMs = performance.now() - postOperationStartTime;
132
+ // Calculate total SDK signing time
133
+ metrics.sdkOperationMs = performance.now() - signStartTime;
134
+ // Log performance timing to console
135
+ const timingMetrics = {};
136
+ if (typeof metrics.sdkPreOperationMs === 'number') {
137
+ timingMetrics['Pre-operation'] = metrics.sdkPreOperationMs;
138
+ }
139
+ if (typeof metrics.enclaveHttpCallMs === 'number') {
140
+ timingMetrics['Enclave HTTP Call'] = metrics.enclaveHttpCallMs;
141
+ }
142
+ if (typeof metrics.sdkPostOperationMs === 'number') {
143
+ timingMetrics['Post-operation'] = metrics.sdkPostOperationMs;
144
+ }
145
+ // Only send metrics if the feature flag is enabled
146
+ if (shouldSendMetrics && this.portalApi) {
147
+ try {
148
+ yield this.sendMetrics(metrics, apiKey);
149
+ }
150
+ catch (err) {
151
+ // No-op
152
+ }
153
+ }
154
+ return result;
155
+ }
156
+ catch (error) {
157
+ // Calculate total time even in error case
158
+ metrics.sdkOperationMs = performance.now() - signStartTime;
159
+ // Log performance timing to console even in error case
160
+ const errorTimingMetrics = {};
161
+ if (typeof metrics.sdkPreOperationMs === 'number') {
162
+ errorTimingMetrics['Pre-operation'] = metrics.sdkPreOperationMs;
163
+ }
164
+ // Only send metrics if the feature flag is enabled
165
+ if (shouldSendMetrics) {
166
+ const apiKey = provider.apiKey;
167
+ metrics.hasError = true;
168
+ try {
169
+ yield this.sendMetrics(metrics, apiKey);
170
+ }
171
+ catch (_a) {
172
+ // No-op
173
+ }
174
+ }
175
+ throw error;
176
+ }
177
+ });
178
+ }
179
+ enclaveRawSign(apiKey, signingShare, params, curve) {
180
+ var _a;
181
+ return __awaiter(this, void 0, void 0, function* () {
182
+ if (!apiKey || !signingShare || !params || !curve) {
183
+ return this.encodeErrorResult('INVALID_PARAMETERS', 'Invalid parameters provided for raw signing');
184
+ }
185
+ const requestBody = {
186
+ params: params,
187
+ share: signingShare,
188
+ };
189
+ const endpoint = `/v1/raw/sign/${curve}`;
190
+ try {
191
+ const response = yield this.requests.post(endpoint, {
192
+ headers: {
193
+ Authorization: `Bearer ${apiKey}`,
194
+ 'Content-Type': 'application/json',
195
+ },
196
+ body: requestBody,
197
+ });
198
+ return this.encodeSuccessResult(response.data);
199
+ }
200
+ catch (error) {
201
+ if ((_a = error === null || error === void 0 ? void 0 : error.response) === null || _a === void 0 ? void 0 : _a.data) {
202
+ const portalError = this.decodePortalError(JSON.stringify(error.response.data));
203
+ return this.encodeErrorResult(portalError === null || portalError === void 0 ? void 0 : portalError.id, portalError === null || portalError === void 0 ? void 0 : portalError.message);
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');
208
+ }
209
+ });
210
+ }
211
+ enclaveSign(apiKey, signingShare, method, params, rpcURL, chainId, metadata) {
212
+ var _a;
213
+ return __awaiter(this, void 0, void 0, function* () {
214
+ if (!apiKey ||
215
+ !signingShare ||
216
+ !method ||
217
+ !params ||
218
+ !chainId ||
219
+ !metadata) {
220
+ return this.encodeErrorResult('INVALID_PARAMETERS', 'Invalid parameters provided');
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
+ }
251
+ });
252
+ }
253
+ // Helper function to encode success results
254
+ encodeSuccessResult(data) {
255
+ const successResult = { data: data, error: undefined };
256
+ return this.encodeJSON(successResult);
257
+ }
258
+ // Helper function to decode Portal errors
259
+ decodePortalError(errorStr) {
260
+ if (!errorStr)
261
+ return null;
262
+ try {
263
+ return JSON.parse(errorStr);
264
+ }
265
+ catch (_a) {
266
+ return null;
267
+ }
268
+ }
269
+ // Helper function to encode error results
270
+ encodeErrorResult(id, message) {
271
+ const errorResult = {
272
+ data: undefined,
273
+ error: { id, message },
274
+ };
275
+ return this.encodeJSON(errorResult);
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;
282
+ }
283
+ catch (error) {
284
+ return JSON.stringify({
285
+ error: {
286
+ id: 'ENCODING_ERROR',
287
+ message: `Failed to encode JSON: ${error.message}`,
288
+ },
289
+ });
290
+ }
291
+ }
292
+ sendMetrics(metrics, apiKey) {
293
+ return __awaiter(this, void 0, void 0, function* () {
294
+ try {
295
+ if (this.portalApi) {
296
+ yield this.portalApi.post('/api/v3/clients/me/sdk/metrics', {
297
+ headers: {
298
+ Authorization: `Bearer ${apiKey}`,
299
+ 'Content-Type': 'application/json',
300
+ },
301
+ body: metrics,
302
+ });
303
+ }
304
+ }
305
+ catch (_a) {
306
+ // No-op
307
+ }
308
+ });
309
+ }
310
+ }
311
+ export default EnclaveSigner;
@@ -1,2 +1,3 @@
1
1
  export { default as Signer } from './abstract';
2
2
  export { default as MpcSigner } from './mpc';
3
+ export { default as EnclaveSigner } from './enclave';
@@ -153,7 +153,7 @@ class MpcSigner {
153
153
  metrics.sdkBinaryConnectMs = binaryMetrics.connectMs;
154
154
  }
155
155
  }
156
- if ((error === null || error === void 0 ? void 0 : error.code) > 0) {
156
+ if (error === null || error === void 0 ? void 0 : error.id) {
157
157
  throw new PortalMpcError(error);
158
158
  }
159
159
  // Record post-operation time
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@portal-hq/provider",
3
- "version": "4.1.3",
3
+ "version": "4.1.4",
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.3",
23
- "@portal-hq/utils": "^4.1.3",
22
+ "@portal-hq/connect": "^4.1.4",
23
+ "@portal-hq/utils": "^4.1.4",
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": "912b03f1152f4dbb7e017919c63bdc950ca867cf"
35
+ "gitHead": "6507173bd858f430a4144bd9e700878a879cf286"
36
36
  }
@@ -21,8 +21,7 @@ import {
21
21
  type RegisteredEventHandler,
22
22
  SwitchEthereumChainParameter,
23
23
  } from '../../types'
24
- import { MpcSigner, Signer } from '../signers'
25
-
24
+ import { MpcSigner, Signer, EnclaveSigner } from '../signers'
26
25
  const passiveSignerMethods = [
27
26
  'eth_accounts',
28
27
  'eth_chainId',
@@ -77,6 +76,7 @@ class Provider implements IPortalProvider {
77
76
  autoApprove = false,
78
77
  apiHost = 'api.portalhq.io',
79
78
  mpcHost = 'mpc.portalhq.io',
79
+ enclaveMPCHost = 'mpc-client.portalhq.io',
80
80
  version = 'v6',
81
81
  chainId = 'eip155:11155111',
82
82
  featureFlags = {},
@@ -110,14 +110,25 @@ class Provider implements IPortalProvider {
110
110
  // Initialize Gateway HttpRequester
111
111
  this.gateway = new PortalRequests()
112
112
 
113
- // Initialize an MpcSigner
114
- this.signer = new MpcSigner({
115
- mpcHost,
116
- keychain,
117
- version,
118
- portalApi: this.portalApi,
119
- featureFlags,
120
- })
113
+ // Initialize the appropriate signer based on feature flags
114
+ if (featureFlags.useEnclaveMPCApi) {
115
+ this.signer = new EnclaveSigner({
116
+ mpcHost,
117
+ enclaveMPCHost,
118
+ keychain,
119
+ version,
120
+ portalApi: this.portalApi,
121
+ featureFlags,
122
+ })
123
+ } else {
124
+ this.signer = new MpcSigner({
125
+ mpcHost,
126
+ keychain,
127
+ version,
128
+ portalApi: this.portalApi,
129
+ featureFlags,
130
+ })
131
+ }
121
132
  }
122
133
 
123
134
  /**
@@ -362,8 +373,7 @@ class Provider implements IPortalProvider {
362
373
  return result
363
374
  }
364
375
  /**
365
- * Updates the chainId of this instance and builds a new RPC HttpRequester for
366
- * the gateway used for the new chain
376
+ * Updates the chainId of this instance
367
377
  *
368
378
  * @param chainId The numerical ID of the chain to switch to
369
379
  * @returns BaseProvider
@@ -0,0 +1,414 @@
1
+ import { PortalCurve } from '@portal-hq/core'
2
+ import { FeatureFlags } from '@portal-hq/core/types'
3
+ import {
4
+ HttpRequester,
5
+ IPortalProvider,
6
+ KeychainAdapter,
7
+ type SigningRequestArguments,
8
+ getClientPlatformVersion,
9
+ } from '@portal-hq/utils'
10
+ import UUID from 'react-native-uuid'
11
+
12
+ import {
13
+ type MpcSignerOptions,
14
+ PortalMobileMpcMetadata,
15
+ type EnclaveSignRequest,
16
+ type EnclaveSignResponse,
17
+ type EnclaveSignResult,
18
+ } from '../../types'
19
+ import Signer from './abstract'
20
+
21
+ enum Operation {
22
+ SIGN = 'sign',
23
+ RAW_SIGN = 'raw_sign',
24
+ }
25
+
26
+ class EnclaveSigner implements Signer {
27
+ private featureFlags: FeatureFlags
28
+ private keychain: KeychainAdapter
29
+ private enclaveMPCHost: string
30
+ private version = 'v6'
31
+ private portalApi?: HttpRequester
32
+ private requests: HttpRequester
33
+
34
+ constructor({
35
+ keychain,
36
+ enclaveMPCHost = 'mpc-client.portalhq.io',
37
+ version = 'v6',
38
+ portalApi,
39
+ featureFlags = {},
40
+ }: MpcSignerOptions & { enclaveMPCHost?: string }) {
41
+ this.featureFlags = featureFlags
42
+ this.keychain = keychain
43
+ this.enclaveMPCHost = enclaveMPCHost
44
+ this.version = version
45
+ this.portalApi = portalApi
46
+ this.requests = new HttpRequester({
47
+ baseUrl: `https://${this.enclaveMPCHost}`,
48
+ })
49
+ }
50
+
51
+ public async sign(
52
+ message: SigningRequestArguments,
53
+ provider: IPortalProvider,
54
+ ): Promise<any> {
55
+ // Always track metrics, but only send if feature flag is enabled
56
+ const shouldSendMetrics =
57
+ this.featureFlags.enableSdkPerformanceMetrics === true
58
+ const signStartTime = performance.now()
59
+ const preOperationStartTime = performance.now()
60
+
61
+ // Generate a traceId for this operation
62
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call
63
+ const traceId = (UUID as any).v4() as string
64
+ const metrics: Record<string, number | string | boolean> = {
65
+ hasError: false,
66
+ operation: Operation.SIGN,
67
+ signingMethod: message.method,
68
+ traceId,
69
+ }
70
+
71
+ try {
72
+ const eip155Address = await this.keychain.getEip155Address()
73
+ const apiKey = provider.apiKey
74
+ const { method, chainId, curve, isRaw } = message
75
+
76
+ // Add chainId to metrics
77
+ if (chainId) {
78
+ metrics.chainId = chainId
79
+ }
80
+
81
+ switch (method) {
82
+ case 'eth_requestAccounts':
83
+ return [eip155Address]
84
+ case 'eth_accounts':
85
+ return [eip155Address]
86
+ default:
87
+ break
88
+ }
89
+
90
+ const shares = await this.keychain.getShares()
91
+ let signingShare = shares.secp256k1.share
92
+
93
+ if (curve === PortalCurve.ED25519) {
94
+ if (!shares.ed25519) {
95
+ throw new Error(
96
+ '[Portal.Provider.EnclaveSigner] The ED25519 share is missing from the keychain.',
97
+ )
98
+ }
99
+ signingShare = shares.ed25519.share
100
+ }
101
+
102
+ const metadata: PortalMobileMpcMetadata = {
103
+ clientPlatform: 'REACT_NATIVE',
104
+ clientPlatformVersion: getClientPlatformVersion(),
105
+ isMultiBackupEnabled: this.featureFlags.isMultiBackupEnabled,
106
+ mpcServerVersion: this.version,
107
+ optimized: true,
108
+ curve,
109
+ chainId,
110
+ isRaw,
111
+ reqId: traceId,
112
+ connectionTracingEnabled: shouldSendMetrics,
113
+ }
114
+
115
+ let formattedParams: string
116
+ let rpcUrl: string
117
+
118
+ if (isRaw) {
119
+ formattedParams = JSON.stringify(
120
+ this.buildParams(method, message.params),
121
+ )
122
+ rpcUrl = ''
123
+ metrics.operation = Operation.RAW_SIGN
124
+ } else {
125
+ formattedParams = JSON.stringify(
126
+ this.buildParams(method, message.params),
127
+ )
128
+ rpcUrl = provider.getGatewayUrl(chainId)
129
+ }
130
+
131
+ if (typeof formattedParams !== 'string') {
132
+ throw new Error(
133
+ `[Portal.Provider.EnclaveSigner] The formatted params for the signing request could not be converted to a string. The params were: ${formattedParams}`,
134
+ )
135
+ }
136
+
137
+ // Record pre-operation time
138
+ metrics.sdkPreOperationMs = performance.now() - preOperationStartTime
139
+
140
+ // Measure enclave signing operation time
141
+ const enclaveSignStartTime = performance.now()
142
+
143
+ const result = isRaw
144
+ ? await this.enclaveRawSign(
145
+ apiKey,
146
+ JSON.stringify(signingShare),
147
+ formattedParams,
148
+ curve || 'SECP256K1',
149
+ )
150
+ : await this.enclaveSign(
151
+ apiKey,
152
+ JSON.stringify(signingShare),
153
+ message.method,
154
+ formattedParams,
155
+ rpcUrl,
156
+ chainId || '',
157
+ JSON.stringify(metadata),
158
+ )
159
+
160
+ // Post-operation processing time starts
161
+ const postOperationStartTime = performance.now()
162
+
163
+ // Record HTTP call time
164
+ metrics.enclaveHttpCallMs = performance.now() - enclaveSignStartTime
165
+
166
+ // Record post-operation time
167
+ metrics.sdkPostOperationMs = performance.now() - postOperationStartTime
168
+
169
+ // Calculate total SDK signing time
170
+ metrics.sdkOperationMs = performance.now() - signStartTime
171
+
172
+ // Log performance timing to console
173
+ const timingMetrics: Record<string, number> = {}
174
+ if (typeof metrics.sdkPreOperationMs === 'number') {
175
+ timingMetrics['Pre-operation'] = metrics.sdkPreOperationMs
176
+ }
177
+ if (typeof metrics.enclaveHttpCallMs === 'number') {
178
+ timingMetrics['Enclave HTTP Call'] = metrics.enclaveHttpCallMs
179
+ }
180
+ if (typeof metrics.sdkPostOperationMs === 'number') {
181
+ timingMetrics['Post-operation'] = metrics.sdkPostOperationMs
182
+ }
183
+
184
+ // Only send metrics if the feature flag is enabled
185
+ if (shouldSendMetrics && this.portalApi) {
186
+ try {
187
+ await this.sendMetrics(metrics, apiKey)
188
+ } catch (err) {
189
+ // No-op
190
+ }
191
+ }
192
+
193
+ return result
194
+ } catch (error) {
195
+ // Calculate total time even in error case
196
+ metrics.sdkOperationMs = performance.now() - signStartTime
197
+
198
+ // Log performance timing to console even in error case
199
+ const errorTimingMetrics: Record<string, number> = {}
200
+ if (typeof metrics.sdkPreOperationMs === 'number') {
201
+ errorTimingMetrics['Pre-operation'] = metrics.sdkPreOperationMs
202
+ }
203
+
204
+ // Only send metrics if the feature flag is enabled
205
+ if (shouldSendMetrics) {
206
+ const apiKey = provider.apiKey
207
+ metrics.hasError = true
208
+ try {
209
+ await this.sendMetrics(metrics, apiKey)
210
+ } catch {
211
+ // No-op
212
+ }
213
+ }
214
+
215
+ throw error
216
+ }
217
+ }
218
+
219
+ private async enclaveRawSign(
220
+ apiKey: string,
221
+ signingShare: string,
222
+ params: string,
223
+ curve: string,
224
+ ): Promise<string> {
225
+ if (!apiKey || !signingShare || !params || !curve) {
226
+ return this.encodeErrorResult(
227
+ 'INVALID_PARAMETERS',
228
+ 'Invalid parameters provided for raw signing',
229
+ )
230
+ }
231
+
232
+ const requestBody = {
233
+ params: params,
234
+ share: signingShare,
235
+ }
236
+
237
+ const endpoint = `/v1/raw/sign/${curve}`
238
+
239
+ try {
240
+ const response = await this.requests.post<EnclaveSignResponse>(endpoint, {
241
+ headers: {
242
+ Authorization: `Bearer ${apiKey}`,
243
+ 'Content-Type': 'application/json',
244
+ },
245
+ body: requestBody,
246
+ })
247
+
248
+ return this.encodeSuccessResult(response.data)
249
+ } catch (error: any) {
250
+ if (error?.response?.data) {
251
+ const portalError = this.decodePortalError(
252
+ JSON.stringify(error.response.data),
253
+ )
254
+ return this.encodeErrorResult(portalError?.id, portalError?.message)
255
+ }
256
+ return this.encodeErrorResult(
257
+ 'SIGNING_NETWORK_ERROR',
258
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-argument
259
+ error.message || 'Network error occurred',
260
+ )
261
+ }
262
+ }
263
+
264
+ private async enclaveSign(
265
+ apiKey: string,
266
+ signingShare: string,
267
+ method: string,
268
+ params: string,
269
+ rpcURL: string,
270
+ chainId: string,
271
+ metadata: string,
272
+ ): Promise<string> {
273
+ if (
274
+ !apiKey ||
275
+ !signingShare ||
276
+ !method ||
277
+ !params ||
278
+ !chainId ||
279
+ !metadata
280
+ ) {
281
+ return this.encodeErrorResult(
282
+ 'INVALID_PARAMETERS',
283
+ 'Invalid parameters provided',
284
+ )
285
+ }
286
+
287
+ const requestBody: EnclaveSignRequest = {
288
+ method: method,
289
+ params: params,
290
+ share: signingShare,
291
+ chainId: chainId,
292
+ rpcUrl: rpcURL,
293
+ metadataStr: metadata,
294
+ clientPlatform: 'REACT_NATIVE',
295
+ clientPlatformVersion: getClientPlatformVersion(),
296
+ }
297
+
298
+ try {
299
+ const response = await this.requests.post<EnclaveSignResponse>(
300
+ '/v1/sign',
301
+ {
302
+ headers: {
303
+ Authorization: `Bearer ${apiKey}`,
304
+ 'Content-Type': 'application/json',
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
+ )
323
+ }
324
+ }
325
+
326
+ // Helper function to encode success results
327
+ private encodeSuccessResult(data: string): string {
328
+ const successResult: EnclaveSignResult = { data: data, error: undefined }
329
+ return this.encodeJSON(successResult)
330
+ }
331
+
332
+ // Helper function to decode Portal errors
333
+ private decodePortalError(
334
+ errorStr?: string,
335
+ ): { id?: string; message?: string } | null {
336
+ if (!errorStr) return null
337
+ try {
338
+ return JSON.parse(errorStr)
339
+ } catch {
340
+ return null
341
+ }
342
+ }
343
+
344
+ // Helper function to encode error results
345
+ private encodeErrorResult(id?: string, message?: string): string {
346
+ const errorResult: EnclaveSignResult = {
347
+ data: undefined,
348
+ error: { id, message },
349
+ }
350
+ return this.encodeJSON(errorResult)
351
+ }
352
+
353
+ // Helper function to encode any object to JSON string
354
+ private encodeJSON<T>(value: T): string {
355
+ try {
356
+ const jsonString = JSON.stringify(value)
357
+ return jsonString
358
+ } catch (error: any) {
359
+ return JSON.stringify({
360
+ error: {
361
+ id: 'ENCODING_ERROR',
362
+ message: `Failed to encode JSON: ${error.message}`,
363
+ },
364
+ })
365
+ }
366
+ }
367
+
368
+ private async sendMetrics(
369
+ metrics: Record<string, number | string | boolean>,
370
+ apiKey: string,
371
+ ): Promise<void> {
372
+ try {
373
+ if (this.portalApi) {
374
+ await this.portalApi.post('/api/v3/clients/me/sdk/metrics', {
375
+ headers: {
376
+ Authorization: `Bearer ${apiKey}`,
377
+ 'Content-Type': 'application/json',
378
+ },
379
+ body: metrics,
380
+ })
381
+ }
382
+ } catch {
383
+ // No-op
384
+ }
385
+ }
386
+
387
+ private buildParams = (method: string, txParams: any) => {
388
+ let params = txParams
389
+
390
+ switch (method) {
391
+ case 'eth_sign':
392
+ case 'personal_sign':
393
+ case 'eth_signTypedData_v3':
394
+ case 'eth_signTypedData_v4':
395
+ case 'sol_signMessage':
396
+ case 'sol_signTransaction':
397
+ case 'sol_signAndSendTransaction':
398
+ case 'sol_signAndConfirmTransaction':
399
+ if (!Array.isArray(txParams)) {
400
+ params = [txParams]
401
+ }
402
+ break
403
+ default:
404
+ if (Array.isArray(txParams)) {
405
+ if (txParams.length === 1) {
406
+ params = txParams[0]
407
+ }
408
+ }
409
+ }
410
+ return params
411
+ }
412
+ }
413
+
414
+ export default EnclaveSigner
@@ -1,2 +1,3 @@
1
1
  export { default as Signer } from './abstract'
2
2
  export { default as MpcSigner } from './mpc'
3
+ export { default as EnclaveSigner } from './enclave'
@@ -189,7 +189,7 @@ class MpcSigner implements Signer {
189
189
  }
190
190
  }
191
191
 
192
- if (error?.code > 0) {
192
+ if (error?.id) {
193
193
  throw new PortalMpcError(error)
194
194
  }
195
195
 
package/types.d.ts CHANGED
@@ -25,6 +25,7 @@ export interface MpcSignerOptions extends SignerOptions {
25
25
  isSimulator?: boolean
26
26
  mpcHost?: string
27
27
  portalApi?: HttpRequester
28
+ enclaveMPCHost?: string
28
29
  version?: string
29
30
  featureFlags?: FeatureFlags
30
31
  }
@@ -86,6 +87,7 @@ export interface ProviderOptions {
86
87
  autoApprove?: boolean
87
88
  apiHost?: string
88
89
  mpcHost?: string
90
+ enclaveMPCHost?: string
89
91
  version?: string
90
92
  featureFlags?: FeatureFlags
91
93
  }
@@ -106,6 +108,30 @@ export interface SignResult {
106
108
  S: string
107
109
  }
108
110
 
111
+ export interface EnclaveSignResult {
112
+ data?: string
113
+ error?: {
114
+ id?: string
115
+ message?: string
116
+ }
117
+ }
118
+
119
+ export interface ProviderOptions {
120
+ // Required
121
+ apiKey: string
122
+ keychain: KeychainAdapter
123
+ gatewayConfig: GatewayLike
124
+
125
+ // Optional
126
+ autoApprove?: boolean
127
+ apiHost?: string
128
+ mpcHost?: string
129
+ enclaveMPCHost?: string
130
+ version?: string
131
+ chainId?: string
132
+ featureFlags?: FeatureFlags
133
+ }
134
+
109
135
  export interface SignerOptions {}
110
136
 
111
137
  export interface Eip1559 {
@@ -141,3 +167,22 @@ export interface SigningResponse {
141
167
  export interface SwitchEthereumChainParameter {
142
168
  chainId: number
143
169
  }
170
+
171
+ export interface EnclaveSignRequest {
172
+ method: string
173
+ params: string
174
+ share: string
175
+ chainId: string
176
+ rpcUrl: string
177
+ metadataStr: string
178
+ clientPlatform: string
179
+ clientPlatformVersion: string
180
+ }
181
+
182
+ export interface EnclaveSignResponse {
183
+ data: string
184
+ error?: {
185
+ id: string
186
+ message: string
187
+ }
188
+ }