@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.
@@ -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';
@@ -10,6 +10,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
10
10
  import { PortalCurve } from '@portal-hq/core';
11
11
  import { PortalMpcError, getClientPlatformVersion, } from '@portal-hq/utils';
12
12
  import { NativeModules } from 'react-native';
13
+ import UUID from 'react-native-uuid';
13
14
  var Operation;
14
15
  (function (Operation) {
15
16
  Operation["SIGN"] = "sign";
@@ -58,9 +59,14 @@ class MpcSigner {
58
59
  const shouldSendMetrics = this.featureFlags.enableSdkPerformanceMetrics === true;
59
60
  const signStartTime = performance.now();
60
61
  const preOperationStartTime = performance.now();
62
+ // Generate a traceId for this operation
63
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-call
64
+ const traceId = UUID.v4();
61
65
  const metrics = {
62
- operation: Operation.SIGN,
63
66
  hasError: false,
67
+ operation: Operation.SIGN,
68
+ signingMethod: message.method,
69
+ traceId,
64
70
  };
65
71
  try {
66
72
  const eip155Address = yield this.keychain.getEip155Address();
@@ -95,6 +101,8 @@ class MpcSigner {
95
101
  curve,
96
102
  chainId,
97
103
  isRaw,
104
+ reqId: traceId,
105
+ connectionTracingEnabled: shouldSendMetrics,
98
106
  };
99
107
  const stringifiedMetadata = JSON.stringify(metadata);
100
108
  let formattedParams;
@@ -125,14 +133,27 @@ class MpcSigner {
125
133
  const { data, error, meta } = parsedResponse;
126
134
  // Add binary metrics to our metrics object
127
135
  if (meta === null || meta === void 0 ? void 0 : meta.metrics) {
128
- if (meta.metrics.wsConnectDurationMs) {
129
- metrics.sdkBinaryWSConnectMs = meta.metrics.wsConnectDurationMs;
136
+ const binaryMetrics = meta.metrics;
137
+ if (binaryMetrics.wsConnectDurationMs) {
138
+ metrics.sdkBinaryWSConnectMs = binaryMetrics.wsConnectDurationMs;
139
+ }
140
+ if (binaryMetrics.operationDurationMs) {
141
+ metrics.sdkBinaryOperationMs = binaryMetrics.operationDurationMs;
142
+ }
143
+ if (binaryMetrics.tlsHandshakeMs) {
144
+ metrics.sdkBinaryTlsHandshakeMs = binaryMetrics.tlsHandshakeMs;
130
145
  }
131
- if (meta.metrics.operationDurationMs) {
132
- metrics.sdkBinaryOperationMs = meta.metrics.operationDurationMs;
146
+ if (binaryMetrics.firstResponseMs) {
147
+ metrics.sdkBinaryFirstResponseMs = binaryMetrics.firstResponseMs;
148
+ }
149
+ if (binaryMetrics.dnsLookupMs) {
150
+ metrics.sdkBinaryDnsLookupMs = binaryMetrics.dnsLookupMs;
151
+ }
152
+ if (binaryMetrics.connectMs) {
153
+ metrics.sdkBinaryConnectMs = binaryMetrics.connectMs;
133
154
  }
134
155
  }
135
- if ((error === null || error === void 0 ? void 0 : error.code) > 0) {
156
+ if (error === null || error === void 0 ? void 0 : error.id) {
136
157
  throw new PortalMpcError(error);
137
158
  }
138
159
  // Record post-operation time
@@ -144,7 +165,7 @@ class MpcSigner {
144
165
  try {
145
166
  yield this.sendMetrics(metrics, apiKey);
146
167
  }
147
- catch (_a) {
168
+ catch (err) {
148
169
  // No-op
149
170
  }
150
171
  }
@@ -160,7 +181,7 @@ class MpcSigner {
160
181
  try {
161
182
  yield this.sendMetrics(metrics, apiKey);
162
183
  }
163
- catch (_b) {
184
+ catch (_a) {
164
185
  // No-op
165
186
  }
166
187
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@portal-hq/provider",
3
- "version": "4.1.2",
3
+ "version": "4.1.4",
4
4
  "license": "MIT",
5
5
  "main": "lib/commonjs/index",
6
6
  "module": "lib/esm/index",
@@ -19,8 +19,10 @@
19
19
  "test": "jest"
20
20
  },
21
21
  "dependencies": {
22
- "@portal-hq/connect": "^4.1.2",
23
- "@portal-hq/utils": "^4.1.2"
22
+ "@portal-hq/connect": "^4.1.4",
23
+ "@portal-hq/utils": "^4.1.4",
24
+ "@types/react-native-uuid": "^2.0.0",
25
+ "react-native-uuid": "^2.0.3"
24
26
  },
25
27
  "devDependencies": {
26
28
  "@babel/preset-typescript": "^7.18.6",
@@ -30,5 +32,5 @@
30
32
  "ts-jest": "^29.0.3",
31
33
  "typescript": "^4.8.4"
32
34
  },
33
- "gitHead": "4504ed5bdfde36df2b9c1acab9916ad412ba6fd9"
35
+ "gitHead": "6507173bd858f430a4144bd9e700878a879cf286"
34
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