@portal-hq/provider 4.1.1 → 4.1.2

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.
@@ -74,6 +74,7 @@ class Provider {
74
74
  mpcHost,
75
75
  keychain,
76
76
  version,
77
+ portalApi: this.portalApi,
77
78
  featureFlags,
78
79
  });
79
80
  }
@@ -187,8 +188,8 @@ class Provider {
187
188
  * @param args The arguments of the request being made
188
189
  * @returns Promise<any>
189
190
  */
190
- request(_a) {
191
- return __awaiter(this, arguments, void 0, function* ({ method, params, chainId, connect, }) {
191
+ request({ method, params, chainId, connect, }) {
192
+ return __awaiter(this, void 0, void 0, function* () {
192
193
  chainId = chainId !== null && chainId !== void 0 ? chainId : this.chainId;
193
194
  if (!chainId) {
194
195
  throw new Error('[PortalProvider] No chainId provided');
@@ -320,8 +321,8 @@ class Provider {
320
321
  *
321
322
  * @param args The arguments of the request being made
322
323
  */
323
- getApproval(_a) {
324
- return __awaiter(this, arguments, void 0, function* ({ method, params, chainId, connect, }) {
324
+ getApproval({ method, params, chainId, connect, }) {
325
+ return __awaiter(this, void 0, void 0, function* () {
325
326
  // If autoApprove is enabled, just resolve to true
326
327
  if (this.autoApprove) {
327
328
  return true;
@@ -386,8 +387,8 @@ class Provider {
386
387
  * @param args The arguments of the request being made
387
388
  * @returns Promise<any>
388
389
  */
389
- handleGatewayRequests(_a) {
390
- return __awaiter(this, arguments, void 0, function* ({ method, params, chainId, }) {
390
+ handleGatewayRequests({ method, params, chainId, }) {
391
+ return __awaiter(this, void 0, void 0, function* () {
391
392
  const gatewayUrl = this.getGatewayUrl(chainId);
392
393
  // Pass request off to the gateway
393
394
  // eslint-disable-next-line @typescript-eslint/no-unsafe-return
@@ -407,9 +408,9 @@ class Provider {
407
408
  * @param args The arguments of the request being made
408
409
  * @returns Promise<any>
409
410
  */
410
- handleSigningRequests(_a) {
411
- return __awaiter(this, arguments, void 0, function* ({ method, params, chainId, connect, }) {
412
- var _b, _c;
411
+ handleSigningRequests({ method, params, chainId, connect, }) {
412
+ var _a, _b;
413
+ return __awaiter(this, void 0, void 0, function* () {
413
414
  const isApproved = passiveSignerMethods.includes(method)
414
415
  ? true
415
416
  : yield this.getApproval({ method, params, chainId, connect });
@@ -440,7 +441,7 @@ class Provider {
440
441
  case 'sol_signAndSendTransaction':
441
442
  case 'sol_signMessage':
442
443
  case 'sol_signTransaction': {
443
- const result = yield ((_b = this.signer) === null || _b === void 0 ? void 0 : _b.sign({
444
+ const result = yield ((_a = this.signer) === null || _a === void 0 ? void 0 : _a.sign({
444
445
  chainId: `${namespace}:${reference}`,
445
446
  method,
446
447
  params,
@@ -450,7 +451,7 @@ class Provider {
450
451
  return result;
451
452
  }
452
453
  case 'raw_sign': {
453
- const result = yield ((_c = this.signer) === null || _c === void 0 ? void 0 : _c.sign({
454
+ const result = yield ((_b = this.signer) === null || _b === void 0 ? void 0 : _b.sign({
454
455
  chainId: '',
455
456
  method: '',
456
457
  params,
@@ -12,8 +12,13 @@ Object.defineProperty(exports, "__esModule", { value: true });
12
12
  const core_1 = require("@portal-hq/core");
13
13
  const utils_1 = require("@portal-hq/utils");
14
14
  const react_native_1 = require("react-native");
15
+ var Operation;
16
+ (function (Operation) {
17
+ Operation["SIGN"] = "sign";
18
+ Operation["RAW_SIGN"] = "raw_sign";
19
+ })(Operation || (Operation = {}));
15
20
  class MpcSigner {
16
- constructor({ keychain, mpcHost = 'mpc.portalhq.io', version = 'v6', featureFlags = {}, }) {
21
+ constructor({ keychain, mpcHost = 'mpc.portalhq.io', version = 'v6', portalApi, featureFlags = {}, }) {
17
22
  this.version = 'v6';
18
23
  this.buildParams = (method, txParams) => {
19
24
  let params = txParams;
@@ -44,61 +49,144 @@ class MpcSigner {
44
49
  this.mpc = react_native_1.NativeModules.PortalMobileMpc;
45
50
  this.mpcHost = mpcHost;
46
51
  this.version = version;
52
+ this.portalApi = portalApi;
47
53
  if (!this.mpc) {
48
54
  throw new Error(`[Portal.Provider.MpcSigner] The MPC module could not be found by the signer. This is usually an issue with React Native linking. Please verify that the 'PortalReactNative' module is properly linked to this project.`);
49
55
  }
50
56
  }
51
57
  sign(message, provider) {
52
58
  return __awaiter(this, void 0, void 0, function* () {
53
- const eip155Address = yield this.keychain.getEip155Address();
54
- const apiKey = provider.apiKey;
55
- const { method, chainId, curve, isRaw } = message;
56
- switch (method) {
57
- case 'eth_requestAccounts':
58
- return [eip155Address];
59
- case 'eth_accounts':
60
- return [eip155Address];
61
- default:
62
- break;
63
- }
64
- const shares = yield this.keychain.getShares();
65
- let signingShare = shares.secp256k1.share;
66
- if (curve === core_1.PortalCurve.ED25519) {
67
- if (!shares.ed25519) {
68
- throw new Error('[Portal.Provider.MpcSigner] The ED25519 share is missing from the keychain.');
69
- }
70
- signingShare = shares.ed25519.share;
71
- }
72
- const metadata = {
73
- clientPlatform: 'REACT_NATIVE',
74
- clientPlatformVersion: (0, utils_1.getClientPlatformVersion)(),
75
- isMultiBackupEnabled: this.featureFlags.isMultiBackupEnabled,
76
- mpcServerVersion: this.version,
77
- optimized: true,
78
- curve,
79
- chainId,
80
- isRaw,
59
+ // Always track metrics, but only send if feature flag is enabled
60
+ const shouldSendMetrics = this.featureFlags.enableSdkPerformanceMetrics === true;
61
+ const signStartTime = performance.now();
62
+ const preOperationStartTime = performance.now();
63
+ const metrics = {
64
+ operation: Operation.SIGN,
65
+ hasError: false,
81
66
  };
82
- const stringifiedMetadata = JSON.stringify(metadata);
83
- let formattedParams;
84
- let rpcUrl;
85
- if (isRaw) {
86
- formattedParams = this.buildParams(method, message.params);
87
- rpcUrl = '';
67
+ try {
68
+ const eip155Address = yield this.keychain.getEip155Address();
69
+ const apiKey = provider.apiKey;
70
+ const { method, chainId, curve, isRaw } = message;
71
+ // Add chainId to metrics
72
+ if (chainId) {
73
+ metrics.chainId = chainId;
74
+ }
75
+ switch (method) {
76
+ case 'eth_requestAccounts':
77
+ return [eip155Address];
78
+ case 'eth_accounts':
79
+ return [eip155Address];
80
+ default:
81
+ break;
82
+ }
83
+ const shares = yield this.keychain.getShares();
84
+ let signingShare = shares.secp256k1.share;
85
+ if (curve === core_1.PortalCurve.ED25519) {
86
+ if (!shares.ed25519) {
87
+ throw new Error('[Portal.Provider.MpcSigner] The ED25519 share is missing from the keychain.');
88
+ }
89
+ signingShare = shares.ed25519.share;
90
+ }
91
+ const metadata = {
92
+ clientPlatform: 'REACT_NATIVE',
93
+ clientPlatformVersion: (0, utils_1.getClientPlatformVersion)(),
94
+ isMultiBackupEnabled: this.featureFlags.isMultiBackupEnabled,
95
+ mpcServerVersion: this.version,
96
+ optimized: true,
97
+ curve,
98
+ chainId,
99
+ isRaw,
100
+ };
101
+ const stringifiedMetadata = JSON.stringify(metadata);
102
+ let formattedParams;
103
+ let rpcUrl;
104
+ if (isRaw) {
105
+ formattedParams = this.buildParams(method, message.params);
106
+ rpcUrl = '';
107
+ metrics.operation = Operation.RAW_SIGN;
108
+ }
109
+ else {
110
+ formattedParams = JSON.stringify(this.buildParams(method, message.params));
111
+ rpcUrl = provider.getGatewayUrl(chainId);
112
+ }
113
+ if (typeof formattedParams !== 'string') {
114
+ throw new Error(`[Portal.Provider.MpcSigner] The formatted params for the signing request could not be converted to a string. The params were: ${formattedParams}`);
115
+ }
116
+ // Record pre-operation time
117
+ metrics.sdkPreOperationMs = performance.now() - preOperationStartTime;
118
+ // Measure MPC signing operation time
119
+ const mpcSignStartTime = performance.now();
120
+ const result = yield this.mpc.sign(apiKey, this.mpcHost, JSON.stringify(signingShare), message.method, formattedParams, rpcUrl, chainId, stringifiedMetadata);
121
+ // Post-operation processing time starts
122
+ const postOperationStartTime = performance.now();
123
+ // Record native call time
124
+ metrics.mpcNativeCallMs = performance.now() - mpcSignStartTime;
125
+ // Parse result and extract binary metrics if available
126
+ const parsedResponse = JSON.parse(String(result));
127
+ const { data, error, meta } = parsedResponse;
128
+ // Add binary metrics to our metrics object
129
+ if (meta === null || meta === void 0 ? void 0 : meta.metrics) {
130
+ if (meta.metrics.wsConnectDurationMs) {
131
+ metrics.sdkBinaryWSConnectMs = meta.metrics.wsConnectDurationMs;
132
+ }
133
+ if (meta.metrics.operationDurationMs) {
134
+ metrics.sdkBinaryOperationMs = meta.metrics.operationDurationMs;
135
+ }
136
+ }
137
+ if ((error === null || error === void 0 ? void 0 : error.code) > 0) {
138
+ throw new utils_1.PortalMpcError(error);
139
+ }
140
+ // Record post-operation time
141
+ metrics.sdkPostOperationMs = performance.now() - postOperationStartTime;
142
+ // Calculate total SDK signing time
143
+ metrics.sdkOperationMs = performance.now() - signStartTime;
144
+ // Only send metrics if the feature flag is enabled
145
+ if (shouldSendMetrics && this.portalApi) {
146
+ try {
147
+ yield this.sendMetrics(metrics, apiKey);
148
+ }
149
+ catch (_a) {
150
+ // No-op
151
+ }
152
+ }
153
+ return data;
88
154
  }
89
- else {
90
- formattedParams = JSON.stringify(this.buildParams(method, message.params));
91
- rpcUrl = provider.getGatewayUrl(chainId);
155
+ catch (error) {
156
+ // Calculate total time even in error case
157
+ metrics.sdkOperationMs = performance.now() - signStartTime;
158
+ // Only send metrics if the feature flag is enabled
159
+ if (shouldSendMetrics) {
160
+ const apiKey = provider.apiKey;
161
+ metrics.hasError = true;
162
+ try {
163
+ yield this.sendMetrics(metrics, apiKey);
164
+ }
165
+ catch (_b) {
166
+ // No-op
167
+ }
168
+ }
169
+ throw error;
92
170
  }
93
- if (typeof formattedParams !== 'string') {
94
- throw new Error(`[Portal.Provider.MpcSigner] The formatted params for the signing request could not be converted to a string. The params were: ${formattedParams}`);
171
+ });
172
+ }
173
+ // Add debug logs to sendMetrics method too
174
+ sendMetrics(metrics, apiKey) {
175
+ return __awaiter(this, void 0, void 0, function* () {
176
+ try {
177
+ if (this.portalApi) {
178
+ yield this.portalApi.post('/api/v3/clients/me/sdk/metrics', {
179
+ headers: {
180
+ Authorization: `Bearer ${apiKey}`,
181
+ 'Content-Type': 'application/json',
182
+ },
183
+ body: metrics,
184
+ });
185
+ }
95
186
  }
96
- const result = yield this.mpc.sign(apiKey, this.mpcHost, JSON.stringify(signingShare), message.method, formattedParams, rpcUrl, chainId, stringifiedMetadata);
97
- const { data, error } = JSON.parse(String(result));
98
- if (error && error.code > 0) {
99
- throw new utils_1.PortalMpcError(error);
187
+ catch (_a) {
188
+ // No-op
100
189
  }
101
- return data;
102
190
  });
103
191
  }
104
192
  }
@@ -72,6 +72,7 @@ class Provider {
72
72
  mpcHost,
73
73
  keychain,
74
74
  version,
75
+ portalApi: this.portalApi,
75
76
  featureFlags,
76
77
  });
77
78
  }
@@ -185,8 +186,8 @@ class Provider {
185
186
  * @param args The arguments of the request being made
186
187
  * @returns Promise<any>
187
188
  */
188
- request(_a) {
189
- return __awaiter(this, arguments, void 0, function* ({ method, params, chainId, connect, }) {
189
+ request({ method, params, chainId, connect, }) {
190
+ return __awaiter(this, void 0, void 0, function* () {
190
191
  chainId = chainId !== null && chainId !== void 0 ? chainId : this.chainId;
191
192
  if (!chainId) {
192
193
  throw new Error('[PortalProvider] No chainId provided');
@@ -318,8 +319,8 @@ class Provider {
318
319
  *
319
320
  * @param args The arguments of the request being made
320
321
  */
321
- getApproval(_a) {
322
- return __awaiter(this, arguments, void 0, function* ({ method, params, chainId, connect, }) {
322
+ getApproval({ method, params, chainId, connect, }) {
323
+ return __awaiter(this, void 0, void 0, function* () {
323
324
  // If autoApprove is enabled, just resolve to true
324
325
  if (this.autoApprove) {
325
326
  return true;
@@ -384,8 +385,8 @@ class Provider {
384
385
  * @param args The arguments of the request being made
385
386
  * @returns Promise<any>
386
387
  */
387
- handleGatewayRequests(_a) {
388
- return __awaiter(this, arguments, void 0, function* ({ method, params, chainId, }) {
388
+ handleGatewayRequests({ method, params, chainId, }) {
389
+ return __awaiter(this, void 0, void 0, function* () {
389
390
  const gatewayUrl = this.getGatewayUrl(chainId);
390
391
  // Pass request off to the gateway
391
392
  // eslint-disable-next-line @typescript-eslint/no-unsafe-return
@@ -405,9 +406,9 @@ class Provider {
405
406
  * @param args The arguments of the request being made
406
407
  * @returns Promise<any>
407
408
  */
408
- handleSigningRequests(_a) {
409
- return __awaiter(this, arguments, void 0, function* ({ method, params, chainId, connect, }) {
410
- var _b, _c;
409
+ handleSigningRequests({ method, params, chainId, connect, }) {
410
+ var _a, _b;
411
+ return __awaiter(this, void 0, void 0, function* () {
411
412
  const isApproved = passiveSignerMethods.includes(method)
412
413
  ? true
413
414
  : yield this.getApproval({ method, params, chainId, connect });
@@ -438,7 +439,7 @@ class Provider {
438
439
  case 'sol_signAndSendTransaction':
439
440
  case 'sol_signMessage':
440
441
  case 'sol_signTransaction': {
441
- const result = yield ((_b = this.signer) === null || _b === void 0 ? void 0 : _b.sign({
442
+ const result = yield ((_a = this.signer) === null || _a === void 0 ? void 0 : _a.sign({
442
443
  chainId: `${namespace}:${reference}`,
443
444
  method,
444
445
  params,
@@ -448,7 +449,7 @@ class Provider {
448
449
  return result;
449
450
  }
450
451
  case 'raw_sign': {
451
- const result = yield ((_c = this.signer) === null || _c === void 0 ? void 0 : _c.sign({
452
+ const result = yield ((_b = this.signer) === null || _b === void 0 ? void 0 : _b.sign({
452
453
  chainId: '',
453
454
  method: '',
454
455
  params,
@@ -10,8 +10,13 @@ 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
+ var Operation;
14
+ (function (Operation) {
15
+ Operation["SIGN"] = "sign";
16
+ Operation["RAW_SIGN"] = "raw_sign";
17
+ })(Operation || (Operation = {}));
13
18
  class MpcSigner {
14
- constructor({ keychain, mpcHost = 'mpc.portalhq.io', version = 'v6', featureFlags = {}, }) {
19
+ constructor({ keychain, mpcHost = 'mpc.portalhq.io', version = 'v6', portalApi, featureFlags = {}, }) {
15
20
  this.version = 'v6';
16
21
  this.buildParams = (method, txParams) => {
17
22
  let params = txParams;
@@ -42,61 +47,144 @@ class MpcSigner {
42
47
  this.mpc = NativeModules.PortalMobileMpc;
43
48
  this.mpcHost = mpcHost;
44
49
  this.version = version;
50
+ this.portalApi = portalApi;
45
51
  if (!this.mpc) {
46
52
  throw new Error(`[Portal.Provider.MpcSigner] The MPC module could not be found by the signer. This is usually an issue with React Native linking. Please verify that the 'PortalReactNative' module is properly linked to this project.`);
47
53
  }
48
54
  }
49
55
  sign(message, provider) {
50
56
  return __awaiter(this, void 0, void 0, function* () {
51
- const eip155Address = yield this.keychain.getEip155Address();
52
- const apiKey = provider.apiKey;
53
- const { method, chainId, curve, isRaw } = message;
54
- switch (method) {
55
- case 'eth_requestAccounts':
56
- return [eip155Address];
57
- case 'eth_accounts':
58
- return [eip155Address];
59
- default:
60
- break;
61
- }
62
- const shares = yield this.keychain.getShares();
63
- let signingShare = shares.secp256k1.share;
64
- if (curve === PortalCurve.ED25519) {
65
- if (!shares.ed25519) {
66
- throw new Error('[Portal.Provider.MpcSigner] The ED25519 share is missing from the keychain.');
67
- }
68
- signingShare = shares.ed25519.share;
69
- }
70
- const metadata = {
71
- clientPlatform: 'REACT_NATIVE',
72
- clientPlatformVersion: getClientPlatformVersion(),
73
- isMultiBackupEnabled: this.featureFlags.isMultiBackupEnabled,
74
- mpcServerVersion: this.version,
75
- optimized: true,
76
- curve,
77
- chainId,
78
- isRaw,
57
+ // Always track metrics, but only send if feature flag is enabled
58
+ const shouldSendMetrics = this.featureFlags.enableSdkPerformanceMetrics === true;
59
+ const signStartTime = performance.now();
60
+ const preOperationStartTime = performance.now();
61
+ const metrics = {
62
+ operation: Operation.SIGN,
63
+ hasError: false,
79
64
  };
80
- const stringifiedMetadata = JSON.stringify(metadata);
81
- let formattedParams;
82
- let rpcUrl;
83
- if (isRaw) {
84
- formattedParams = this.buildParams(method, message.params);
85
- rpcUrl = '';
65
+ try {
66
+ const eip155Address = yield this.keychain.getEip155Address();
67
+ const apiKey = provider.apiKey;
68
+ const { method, chainId, curve, isRaw } = message;
69
+ // Add chainId to metrics
70
+ if (chainId) {
71
+ metrics.chainId = chainId;
72
+ }
73
+ switch (method) {
74
+ case 'eth_requestAccounts':
75
+ return [eip155Address];
76
+ case 'eth_accounts':
77
+ return [eip155Address];
78
+ default:
79
+ break;
80
+ }
81
+ const shares = yield this.keychain.getShares();
82
+ let signingShare = shares.secp256k1.share;
83
+ if (curve === PortalCurve.ED25519) {
84
+ if (!shares.ed25519) {
85
+ throw new Error('[Portal.Provider.MpcSigner] The ED25519 share is missing from the keychain.');
86
+ }
87
+ signingShare = shares.ed25519.share;
88
+ }
89
+ const metadata = {
90
+ clientPlatform: 'REACT_NATIVE',
91
+ clientPlatformVersion: getClientPlatformVersion(),
92
+ isMultiBackupEnabled: this.featureFlags.isMultiBackupEnabled,
93
+ mpcServerVersion: this.version,
94
+ optimized: true,
95
+ curve,
96
+ chainId,
97
+ isRaw,
98
+ };
99
+ const stringifiedMetadata = JSON.stringify(metadata);
100
+ let formattedParams;
101
+ let rpcUrl;
102
+ if (isRaw) {
103
+ formattedParams = this.buildParams(method, message.params);
104
+ rpcUrl = '';
105
+ metrics.operation = Operation.RAW_SIGN;
106
+ }
107
+ else {
108
+ formattedParams = JSON.stringify(this.buildParams(method, message.params));
109
+ rpcUrl = provider.getGatewayUrl(chainId);
110
+ }
111
+ if (typeof formattedParams !== 'string') {
112
+ throw new Error(`[Portal.Provider.MpcSigner] The formatted params for the signing request could not be converted to a string. The params were: ${formattedParams}`);
113
+ }
114
+ // Record pre-operation time
115
+ metrics.sdkPreOperationMs = performance.now() - preOperationStartTime;
116
+ // Measure MPC signing operation time
117
+ const mpcSignStartTime = performance.now();
118
+ const result = yield this.mpc.sign(apiKey, this.mpcHost, JSON.stringify(signingShare), message.method, formattedParams, rpcUrl, chainId, stringifiedMetadata);
119
+ // Post-operation processing time starts
120
+ const postOperationStartTime = performance.now();
121
+ // Record native call time
122
+ metrics.mpcNativeCallMs = performance.now() - mpcSignStartTime;
123
+ // Parse result and extract binary metrics if available
124
+ const parsedResponse = JSON.parse(String(result));
125
+ const { data, error, meta } = parsedResponse;
126
+ // Add binary metrics to our metrics object
127
+ if (meta === null || meta === void 0 ? void 0 : meta.metrics) {
128
+ if (meta.metrics.wsConnectDurationMs) {
129
+ metrics.sdkBinaryWSConnectMs = meta.metrics.wsConnectDurationMs;
130
+ }
131
+ if (meta.metrics.operationDurationMs) {
132
+ metrics.sdkBinaryOperationMs = meta.metrics.operationDurationMs;
133
+ }
134
+ }
135
+ if ((error === null || error === void 0 ? void 0 : error.code) > 0) {
136
+ throw new PortalMpcError(error);
137
+ }
138
+ // Record post-operation time
139
+ metrics.sdkPostOperationMs = performance.now() - postOperationStartTime;
140
+ // Calculate total SDK signing time
141
+ metrics.sdkOperationMs = performance.now() - signStartTime;
142
+ // Only send metrics if the feature flag is enabled
143
+ if (shouldSendMetrics && this.portalApi) {
144
+ try {
145
+ yield this.sendMetrics(metrics, apiKey);
146
+ }
147
+ catch (_a) {
148
+ // No-op
149
+ }
150
+ }
151
+ return data;
86
152
  }
87
- else {
88
- formattedParams = JSON.stringify(this.buildParams(method, message.params));
89
- rpcUrl = provider.getGatewayUrl(chainId);
153
+ catch (error) {
154
+ // Calculate total time even in error case
155
+ metrics.sdkOperationMs = performance.now() - signStartTime;
156
+ // Only send metrics if the feature flag is enabled
157
+ if (shouldSendMetrics) {
158
+ const apiKey = provider.apiKey;
159
+ metrics.hasError = true;
160
+ try {
161
+ yield this.sendMetrics(metrics, apiKey);
162
+ }
163
+ catch (_b) {
164
+ // No-op
165
+ }
166
+ }
167
+ throw error;
90
168
  }
91
- if (typeof formattedParams !== 'string') {
92
- throw new Error(`[Portal.Provider.MpcSigner] The formatted params for the signing request could not be converted to a string. The params were: ${formattedParams}`);
169
+ });
170
+ }
171
+ // Add debug logs to sendMetrics method too
172
+ sendMetrics(metrics, apiKey) {
173
+ return __awaiter(this, void 0, void 0, function* () {
174
+ try {
175
+ if (this.portalApi) {
176
+ yield this.portalApi.post('/api/v3/clients/me/sdk/metrics', {
177
+ headers: {
178
+ Authorization: `Bearer ${apiKey}`,
179
+ 'Content-Type': 'application/json',
180
+ },
181
+ body: metrics,
182
+ });
183
+ }
93
184
  }
94
- const result = yield this.mpc.sign(apiKey, this.mpcHost, JSON.stringify(signingShare), message.method, formattedParams, rpcUrl, chainId, stringifiedMetadata);
95
- const { data, error } = JSON.parse(String(result));
96
- if (error && error.code > 0) {
97
- throw new PortalMpcError(error);
185
+ catch (_a) {
186
+ // No-op
98
187
  }
99
- return data;
100
188
  });
101
189
  }
102
190
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@portal-hq/provider",
3
- "version": "4.1.1",
3
+ "version": "4.1.2",
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.1",
23
- "@portal-hq/utils": "^4.1.1"
22
+ "@portal-hq/connect": "^4.1.2",
23
+ "@portal-hq/utils": "^4.1.2"
24
24
  },
25
25
  "devDependencies": {
26
26
  "@babel/preset-typescript": "^7.18.6",
@@ -30,5 +30,5 @@
30
30
  "ts-jest": "^29.0.3",
31
31
  "typescript": "^4.8.4"
32
32
  },
33
- "gitHead": "3c03bd4a6573b23865f655856cd0161706f3f30d"
33
+ "gitHead": "4504ed5bdfde36df2b9c1acab9916ad412ba6fd9"
34
34
  }
@@ -115,6 +115,7 @@ class Provider implements IPortalProvider {
115
115
  mpcHost,
116
116
  keychain,
117
117
  version,
118
+ portalApi: this.portalApi,
118
119
  featureFlags,
119
120
  })
120
121
  }
@@ -1,6 +1,7 @@
1
1
  import { PortalCurve } from '@portal-hq/core'
2
2
  import { FeatureFlags } from '@portal-hq/core/types'
3
3
  import {
4
+ HttpRequester,
4
5
  IPortalProvider,
5
6
  KeychainAdapter,
6
7
  PortalMpcError,
@@ -17,17 +18,24 @@ import {
17
18
  } from '../../types'
18
19
  import Signer from './abstract'
19
20
 
21
+ enum Operation {
22
+ SIGN = 'sign',
23
+ RAW_SIGN = 'raw_sign',
24
+ }
25
+
20
26
  class MpcSigner implements Signer {
21
27
  private featureFlags: FeatureFlags
22
28
  private keychain: KeychainAdapter
23
29
  private mpc: PortalMobileMpc
24
- private mpcHost: string // should we add a default here mpc.portalhq.io
30
+ private mpcHost: string
25
31
  private version = 'v6'
32
+ private portalApi?: HttpRequester
26
33
 
27
34
  constructor({
28
35
  keychain,
29
36
  mpcHost = 'mpc.portalhq.io',
30
37
  version = 'v6',
38
+ portalApi,
31
39
  featureFlags = {},
32
40
  }: MpcSignerOptions) {
33
41
  this.featureFlags = featureFlags
@@ -35,6 +43,7 @@ class MpcSigner implements Signer {
35
43
  this.mpc = NativeModules.PortalMobileMpc
36
44
  this.mpcHost = mpcHost
37
45
  this.version = version
46
+ this.portalApi = portalApi
38
47
 
39
48
  if (!this.mpc) {
40
49
  throw new Error(
@@ -47,82 +56,175 @@ class MpcSigner implements Signer {
47
56
  message: SigningRequestArguments,
48
57
  provider: IPortalProvider,
49
58
  ): Promise<any> {
50
- const eip155Address = await this.keychain.getEip155Address()
59
+ // Always track metrics, but only send if feature flag is enabled
60
+ const shouldSendMetrics =
61
+ this.featureFlags.enableSdkPerformanceMetrics === true
62
+ const signStartTime = performance.now()
63
+ const preOperationStartTime = performance.now()
64
+ const metrics: Record<string, number | string | boolean> = {
65
+ operation: Operation.SIGN,
66
+ hasError: false,
67
+ }
68
+ try {
69
+ const eip155Address = await this.keychain.getEip155Address()
51
70
 
52
- const apiKey = provider.apiKey
71
+ const apiKey = provider.apiKey
53
72
 
54
- const { method, chainId, curve, isRaw } = message
73
+ const { method, chainId, curve, isRaw } = message
74
+ // Add chainId to metrics
75
+ if (chainId) {
76
+ metrics.chainId = chainId
77
+ }
55
78
 
56
- switch (method) {
57
- case 'eth_requestAccounts':
58
- return [eip155Address]
59
- case 'eth_accounts':
60
- return [eip155Address]
61
- default:
62
- break
63
- }
79
+ switch (method) {
80
+ case 'eth_requestAccounts':
81
+ return [eip155Address]
82
+ case 'eth_accounts':
83
+ return [eip155Address]
84
+ default:
85
+ break
86
+ }
64
87
 
65
- const shares = await this.keychain.getShares()
88
+ const shares = await this.keychain.getShares()
66
89
 
67
- let signingShare = shares.secp256k1.share
90
+ let signingShare = shares.secp256k1.share
91
+
92
+ if (curve === PortalCurve.ED25519) {
93
+ if (!shares.ed25519) {
94
+ throw new Error(
95
+ '[Portal.Provider.MpcSigner] The ED25519 share is missing from the keychain.',
96
+ )
97
+ }
98
+ signingShare = shares.ed25519.share
99
+ }
68
100
 
69
- if (curve === PortalCurve.ED25519) {
70
- if (!shares.ed25519) {
101
+ const metadata: PortalMobileMpcMetadata = {
102
+ clientPlatform: 'REACT_NATIVE',
103
+ clientPlatformVersion: getClientPlatformVersion(),
104
+ isMultiBackupEnabled: this.featureFlags.isMultiBackupEnabled,
105
+ mpcServerVersion: this.version,
106
+ optimized: true,
107
+ curve,
108
+ chainId,
109
+ isRaw,
110
+ }
111
+
112
+ const stringifiedMetadata = JSON.stringify(metadata)
113
+
114
+ let formattedParams: string
115
+ let rpcUrl: string
116
+
117
+ if (isRaw) {
118
+ formattedParams = this.buildParams(method, message.params)
119
+ rpcUrl = ''
120
+ metrics.operation = Operation.RAW_SIGN
121
+ } else {
122
+ formattedParams = JSON.stringify(
123
+ this.buildParams(method, message.params),
124
+ )
125
+ rpcUrl = provider.getGatewayUrl(chainId)
126
+ }
127
+
128
+ if (typeof formattedParams !== 'string') {
71
129
  throw new Error(
72
- '[Portal.Provider.MpcSigner] The ED25519 share is missing from the keychain.',
130
+ `[Portal.Provider.MpcSigner] The formatted params for the signing request could not be converted to a string. The params were: ${formattedParams}`,
73
131
  )
74
132
  }
75
- signingShare = shares.ed25519.share
76
- }
77
133
 
78
- const metadata: PortalMobileMpcMetadata = {
79
- clientPlatform: 'REACT_NATIVE',
80
- clientPlatformVersion: getClientPlatformVersion(),
81
- isMultiBackupEnabled: this.featureFlags.isMultiBackupEnabled,
82
- mpcServerVersion: this.version,
83
- optimized: true,
84
- curve,
85
- chainId,
86
- isRaw,
87
- }
134
+ // Record pre-operation time
135
+ metrics.sdkPreOperationMs = performance.now() - preOperationStartTime
88
136
 
89
- const stringifiedMetadata = JSON.stringify(metadata)
137
+ // Measure MPC signing operation time
138
+ const mpcSignStartTime = performance.now()
90
139
 
91
- let formattedParams: string
92
- let rpcUrl: string
140
+ const result = await this.mpc.sign(
141
+ apiKey,
142
+ this.mpcHost,
143
+ JSON.stringify(signingShare),
144
+ message.method,
145
+ formattedParams,
146
+ rpcUrl,
147
+ chainId,
148
+ stringifiedMetadata,
149
+ )
93
150
 
94
- if (isRaw) {
95
- formattedParams = this.buildParams(method, message.params)
96
- rpcUrl = ''
97
- } else {
98
- formattedParams = JSON.stringify(this.buildParams(method, message.params))
99
- rpcUrl = provider.getGatewayUrl(chainId)
100
- }
151
+ // Post-operation processing time starts
152
+ const postOperationStartTime = performance.now()
101
153
 
102
- if (typeof formattedParams !== 'string') {
103
- throw new Error(
104
- `[Portal.Provider.MpcSigner] The formatted params for the signing request could not be converted to a string. The params were: ${formattedParams}`,
105
- )
106
- }
154
+ // Record native call time
155
+ metrics.mpcNativeCallMs = performance.now() - mpcSignStartTime
156
+
157
+ // Parse result and extract binary metrics if available
158
+ const parsedResponse = JSON.parse(String(result)) as SigningResponse
159
+ const { data, error, meta } = parsedResponse
160
+
161
+ // Add binary metrics to our metrics object
162
+ if (meta?.metrics) {
163
+ if (meta.metrics.wsConnectDurationMs) {
164
+ metrics.sdkBinaryWSConnectMs = meta.metrics.wsConnectDurationMs
165
+ }
166
+ if (meta.metrics.operationDurationMs) {
167
+ metrics.sdkBinaryOperationMs = meta.metrics.operationDurationMs
168
+ }
169
+ }
170
+
171
+ if (error?.code > 0) {
172
+ throw new PortalMpcError(error)
173
+ }
174
+
175
+ // Record post-operation time
176
+ metrics.sdkPostOperationMs = performance.now() - postOperationStartTime
107
177
 
108
- const result = await this.mpc.sign(
109
- apiKey,
110
- this.mpcHost,
111
- JSON.stringify(signingShare),
112
- message.method,
113
- formattedParams,
114
- rpcUrl,
115
- chainId,
116
- stringifiedMetadata,
117
- )
118
-
119
- const { data, error } = JSON.parse(String(result)) as SigningResponse
120
-
121
- if (error && error.code > 0) {
122
- throw new PortalMpcError(error)
178
+ // Calculate total SDK signing time
179
+ metrics.sdkOperationMs = performance.now() - signStartTime
180
+
181
+ // Only send metrics if the feature flag is enabled
182
+ if (shouldSendMetrics && this.portalApi) {
183
+ try {
184
+ await this.sendMetrics(metrics, apiKey)
185
+ } catch {
186
+ // No-op
187
+ }
188
+ }
189
+
190
+ return data
191
+ } catch (error) {
192
+ // Calculate total time even in error case
193
+ metrics.sdkOperationMs = performance.now() - signStartTime
194
+
195
+ // Only send metrics if the feature flag is enabled
196
+ if (shouldSendMetrics) {
197
+ const apiKey = provider.apiKey
198
+ metrics.hasError = true
199
+ try {
200
+ await this.sendMetrics(metrics, apiKey)
201
+ } catch {
202
+ // No-op
203
+ }
204
+ }
205
+
206
+ throw error
123
207
  }
208
+ }
124
209
 
125
- return data
210
+ // Add debug logs to sendMetrics method too
211
+ private async sendMetrics(
212
+ metrics: Record<string, number | string | boolean>,
213
+ apiKey: string,
214
+ ): Promise<void> {
215
+ try {
216
+ if (this.portalApi) {
217
+ await this.portalApi.post('/api/v3/clients/me/sdk/metrics', {
218
+ headers: {
219
+ Authorization: `Bearer ${apiKey}`,
220
+ 'Content-Type': 'application/json',
221
+ },
222
+ body: metrics,
223
+ })
224
+ }
225
+ } catch {
226
+ // No-op
227
+ }
126
228
  }
127
229
 
128
230
  private buildParams = (method: string, txParams: any) => {
package/types.d.ts CHANGED
@@ -1,7 +1,10 @@
1
1
  // Types
2
2
  import PortalConnect from '@portal-hq/connect'
3
- import { BackupMethods } from '@portal-hq/core'
4
- import { KeychainAdapter, type PortalError } from '@portal-hq/utils'
3
+ import {
4
+ KeychainAdapter,
5
+ type PortalError,
6
+ HttpRequester,
7
+ } from '@portal-hq/utils'
5
8
 
6
9
  export type EventHandler = (data: any) => void
7
10
 
@@ -21,6 +24,7 @@ export interface MpcSignerOptions extends SignerOptions {
21
24
  // Optional
22
25
  isSimulator?: boolean
23
26
  mpcHost?: string
27
+ portalApi?: HttpRequester
24
28
  version?: string
25
29
  featureFlags?: FeatureFlags
26
30
  }
@@ -120,6 +124,12 @@ export type SigningRequestParams = Eip1559 | LegacyTx
120
124
  export interface SigningResponse {
121
125
  data: string
122
126
  error: PortalError
127
+ meta?: {
128
+ metrics?: {
129
+ wsConnectDurationMs?: number
130
+ operationDurationMs?: number
131
+ }
132
+ }
123
133
  }
124
134
 
125
135
  export interface SwitchEthereumChainParameter {