@portal-hq/provider 4.1.2 → 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; } });
@@ -8,10 +8,14 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
8
8
  step((generator = generator.apply(thisArg, _arguments || [])).next());
9
9
  });
10
10
  };
11
+ var __importDefault = (this && this.__importDefault) || function (mod) {
12
+ return (mod && mod.__esModule) ? mod : { "default": mod };
13
+ };
11
14
  Object.defineProperty(exports, "__esModule", { value: true });
12
15
  const core_1 = require("@portal-hq/core");
13
16
  const utils_1 = require("@portal-hq/utils");
14
17
  const react_native_1 = require("react-native");
18
+ const react_native_uuid_1 = __importDefault(require("react-native-uuid"));
15
19
  var Operation;
16
20
  (function (Operation) {
17
21
  Operation["SIGN"] = "sign";
@@ -60,9 +64,14 @@ class MpcSigner {
60
64
  const shouldSendMetrics = this.featureFlags.enableSdkPerformanceMetrics === true;
61
65
  const signStartTime = performance.now();
62
66
  const preOperationStartTime = performance.now();
67
+ // Generate a traceId for this operation
68
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-call
69
+ const traceId = react_native_uuid_1.default.v4();
63
70
  const metrics = {
64
- operation: Operation.SIGN,
65
71
  hasError: false,
72
+ operation: Operation.SIGN,
73
+ signingMethod: message.method,
74
+ traceId,
66
75
  };
67
76
  try {
68
77
  const eip155Address = yield this.keychain.getEip155Address();
@@ -97,6 +106,8 @@ class MpcSigner {
97
106
  curve,
98
107
  chainId,
99
108
  isRaw,
109
+ reqId: traceId,
110
+ connectionTracingEnabled: shouldSendMetrics,
100
111
  };
101
112
  const stringifiedMetadata = JSON.stringify(metadata);
102
113
  let formattedParams;
@@ -127,14 +138,27 @@ class MpcSigner {
127
138
  const { data, error, meta } = parsedResponse;
128
139
  // Add binary metrics to our metrics object
129
140
  if (meta === null || meta === void 0 ? void 0 : meta.metrics) {
130
- if (meta.metrics.wsConnectDurationMs) {
131
- metrics.sdkBinaryWSConnectMs = meta.metrics.wsConnectDurationMs;
141
+ const binaryMetrics = meta.metrics;
142
+ if (binaryMetrics.wsConnectDurationMs) {
143
+ metrics.sdkBinaryWSConnectMs = binaryMetrics.wsConnectDurationMs;
144
+ }
145
+ if (binaryMetrics.operationDurationMs) {
146
+ metrics.sdkBinaryOperationMs = binaryMetrics.operationDurationMs;
147
+ }
148
+ if (binaryMetrics.tlsHandshakeMs) {
149
+ metrics.sdkBinaryTlsHandshakeMs = binaryMetrics.tlsHandshakeMs;
132
150
  }
133
- if (meta.metrics.operationDurationMs) {
134
- metrics.sdkBinaryOperationMs = meta.metrics.operationDurationMs;
151
+ if (binaryMetrics.firstResponseMs) {
152
+ metrics.sdkBinaryFirstResponseMs = binaryMetrics.firstResponseMs;
153
+ }
154
+ if (binaryMetrics.dnsLookupMs) {
155
+ metrics.sdkBinaryDnsLookupMs = binaryMetrics.dnsLookupMs;
156
+ }
157
+ if (binaryMetrics.connectMs) {
158
+ metrics.sdkBinaryConnectMs = binaryMetrics.connectMs;
135
159
  }
136
160
  }
137
- if ((error === null || error === void 0 ? void 0 : error.code) > 0) {
161
+ if (error === null || error === void 0 ? void 0 : error.id) {
138
162
  throw new utils_1.PortalMpcError(error);
139
163
  }
140
164
  // Record post-operation time
@@ -146,7 +170,7 @@ class MpcSigner {
146
170
  try {
147
171
  yield this.sendMetrics(metrics, apiKey);
148
172
  }
149
- catch (_a) {
173
+ catch (err) {
150
174
  // No-op
151
175
  }
152
176
  }
@@ -162,7 +186,7 @@ class MpcSigner {
162
186
  try {
163
187
  yield this.sendMetrics(metrics, apiKey);
164
188
  }
165
- catch (_b) {
189
+ catch (_a) {
166
190
  // No-op
167
191
  }
168
192
  }
@@ -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