@portal-hq/provider 4.1.3 → 4.1.5

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,318 @@
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
+ // Build params
106
+ // Avoid double JSON encoding: if params is already a string (e.g. a hex message), pass it directly; otherwise stringify objects/arrays.
107
+ const params = this.buildParams(method, message.params);
108
+ let formattedParams;
109
+ if (isRaw) {
110
+ if (typeof params !== 'string') {
111
+ throw new Error('[Portal.Provider.EnclaveSigner] For raw signing, params must be a string (e.g., a hex-encoded message).');
112
+ }
113
+ formattedParams = params;
114
+ }
115
+ else {
116
+ formattedParams =
117
+ typeof params === 'string' ? params : JSON.stringify(params);
118
+ }
119
+ // Get RPC URL
120
+ const rpcUrl = isRaw ? '' : provider.getGatewayUrl(chainId);
121
+ // Set metrics operation
122
+ metrics.operation = isRaw ? Operation.RAW_SIGN : Operation.SIGN;
123
+ if (typeof formattedParams !== 'string') {
124
+ throw new Error(`[Portal.Provider.EnclaveSigner] The formatted params for the signing request could not be converted to a string. The params were: ${formattedParams}`);
125
+ }
126
+ // Record pre-operation time
127
+ metrics.sdkPreOperationMs = performance.now() - preOperationStartTime;
128
+ // Measure enclave signing operation time
129
+ const enclaveSignStartTime = performance.now();
130
+ const result = isRaw
131
+ ? yield this.enclaveRawSign(apiKey, JSON.stringify(signingShare), formattedParams, curve || 'SECP256K1')
132
+ : yield this.enclaveSign(apiKey, JSON.stringify(signingShare), message.method, formattedParams, rpcUrl, chainId || '', JSON.stringify(metadata));
133
+ // Post-operation processing time starts
134
+ const postOperationStartTime = performance.now();
135
+ // Record HTTP call time
136
+ metrics.enclaveHttpCallMs = performance.now() - enclaveSignStartTime;
137
+ // Record post-operation time
138
+ metrics.sdkPostOperationMs = performance.now() - postOperationStartTime;
139
+ // Calculate total SDK signing time
140
+ metrics.sdkOperationMs = performance.now() - signStartTime;
141
+ // Log performance timing to console
142
+ const timingMetrics = {};
143
+ if (typeof metrics.sdkPreOperationMs === 'number') {
144
+ timingMetrics['Pre-operation'] = metrics.sdkPreOperationMs;
145
+ }
146
+ if (typeof metrics.enclaveHttpCallMs === 'number') {
147
+ timingMetrics['Enclave HTTP Call'] = metrics.enclaveHttpCallMs;
148
+ }
149
+ if (typeof metrics.sdkPostOperationMs === 'number') {
150
+ timingMetrics['Post-operation'] = metrics.sdkPostOperationMs;
151
+ }
152
+ // Only send metrics if the feature flag is enabled
153
+ if (shouldSendMetrics && this.portalApi) {
154
+ try {
155
+ yield this.sendMetrics(metrics, apiKey);
156
+ }
157
+ catch (err) {
158
+ // No-op
159
+ }
160
+ }
161
+ return result;
162
+ }
163
+ catch (error) {
164
+ // Calculate total time even in error case
165
+ metrics.sdkOperationMs = performance.now() - signStartTime;
166
+ // Log performance timing to console even in error case
167
+ const errorTimingMetrics = {};
168
+ if (typeof metrics.sdkPreOperationMs === 'number') {
169
+ errorTimingMetrics['Pre-operation'] = metrics.sdkPreOperationMs;
170
+ }
171
+ // Only send metrics if the feature flag is enabled
172
+ if (shouldSendMetrics) {
173
+ const apiKey = provider.apiKey;
174
+ metrics.hasError = true;
175
+ try {
176
+ yield this.sendMetrics(metrics, apiKey);
177
+ }
178
+ catch (_a) {
179
+ // No-op
180
+ }
181
+ }
182
+ throw error;
183
+ }
184
+ });
185
+ }
186
+ enclaveRawSign(apiKey, signingShare, params, curve) {
187
+ var _a;
188
+ return __awaiter(this, void 0, void 0, function* () {
189
+ if (!apiKey || !signingShare || !params || !curve) {
190
+ return this.encodeErrorResult('INVALID_PARAMETERS', 'Invalid parameters provided for raw signing');
191
+ }
192
+ const requestBody = {
193
+ params: params,
194
+ share: signingShare,
195
+ };
196
+ const endpoint = `/v1/raw/sign/${curve}`;
197
+ try {
198
+ const response = yield this.requests.post(endpoint, {
199
+ headers: {
200
+ Authorization: `Bearer ${apiKey}`,
201
+ 'Content-Type': 'application/json',
202
+ },
203
+ body: requestBody,
204
+ });
205
+ return this.encodeSuccessResult(response.data);
206
+ }
207
+ catch (error) {
208
+ if ((_a = error === null || error === void 0 ? void 0 : error.response) === null || _a === void 0 ? void 0 : _a.data) {
209
+ const portalError = this.decodePortalError(JSON.stringify(error.response.data));
210
+ return this.encodeErrorResult(portalError === null || portalError === void 0 ? void 0 : portalError.id, portalError === null || portalError === void 0 ? void 0 : portalError.message);
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');
215
+ }
216
+ });
217
+ }
218
+ enclaveSign(apiKey, signingShare, method, params, rpcURL, chainId, metadata) {
219
+ var _a;
220
+ return __awaiter(this, void 0, void 0, function* () {
221
+ if (!apiKey ||
222
+ !signingShare ||
223
+ !method ||
224
+ !params ||
225
+ !chainId ||
226
+ !metadata) {
227
+ return this.encodeErrorResult('INVALID_PARAMETERS', 'Invalid parameters provided');
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
+ }
258
+ });
259
+ }
260
+ // Helper function to encode success results
261
+ encodeSuccessResult(data) {
262
+ const successResult = { data: data, error: undefined };
263
+ return this.encodeJSON(successResult);
264
+ }
265
+ // Helper function to decode Portal errors
266
+ decodePortalError(errorStr) {
267
+ if (!errorStr)
268
+ return null;
269
+ try {
270
+ return JSON.parse(errorStr);
271
+ }
272
+ catch (_a) {
273
+ return null;
274
+ }
275
+ }
276
+ // Helper function to encode error results
277
+ encodeErrorResult(id, message) {
278
+ const errorResult = {
279
+ data: undefined,
280
+ error: { id, message },
281
+ };
282
+ return this.encodeJSON(errorResult);
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;
289
+ }
290
+ catch (error) {
291
+ return JSON.stringify({
292
+ error: {
293
+ id: 'ENCODING_ERROR',
294
+ message: `Failed to encode JSON: ${error.message}`,
295
+ },
296
+ });
297
+ }
298
+ }
299
+ sendMetrics(metrics, apiKey) {
300
+ return __awaiter(this, void 0, void 0, function* () {
301
+ try {
302
+ if (this.portalApi) {
303
+ yield this.portalApi.post('/api/v3/clients/me/sdk/metrics', {
304
+ headers: {
305
+ Authorization: `Bearer ${apiKey}`,
306
+ 'Content-Type': 'application/json',
307
+ },
308
+ body: metrics,
309
+ });
310
+ }
311
+ }
312
+ catch (_a) {
313
+ // No-op
314
+ }
315
+ });
316
+ }
317
+ }
318
+ 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.5",
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.5",
23
+ "@portal-hq/utils": "^4.1.5",
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": "5ba19390f12935fc74b6235a4b2655a1e1c7a07f"
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