@portal-hq/provider 4.1.0 → 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.
- package/lib/commonjs/providers/index.js +1 -0
- package/lib/commonjs/signers/mpc.js +133 -45
- package/lib/esm/providers/index.js +1 -0
- package/lib/esm/signers/mpc.js +133 -45
- package/package.json +4 -4
- package/src/providers/index.ts +1 -0
- package/src/signers/mpc.ts +162 -60
- package/types.d.ts +12 -2
|
@@ -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
|
-
|
|
54
|
-
const
|
|
55
|
-
const
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
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
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
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
|
-
|
|
90
|
-
|
|
91
|
-
|
|
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
|
-
|
|
94
|
-
|
|
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
|
-
|
|
97
|
-
|
|
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
|
}
|
package/lib/esm/signers/mpc.js
CHANGED
|
@@ -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
|
-
|
|
52
|
-
const
|
|
53
|
-
const
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
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
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
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
|
-
|
|
88
|
-
|
|
89
|
-
|
|
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
|
-
|
|
92
|
-
|
|
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
|
-
|
|
95
|
-
|
|
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.
|
|
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.
|
|
23
|
-
"@portal-hq/utils": "^4.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": "
|
|
33
|
+
"gitHead": "4504ed5bdfde36df2b9c1acab9916ad412ba6fd9"
|
|
34
34
|
}
|
package/src/providers/index.ts
CHANGED
package/src/signers/mpc.ts
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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
|
-
|
|
71
|
+
const apiKey = provider.apiKey
|
|
53
72
|
|
|
54
|
-
|
|
73
|
+
const { method, chainId, curve, isRaw } = message
|
|
74
|
+
// Add chainId to metrics
|
|
75
|
+
if (chainId) {
|
|
76
|
+
metrics.chainId = chainId
|
|
77
|
+
}
|
|
55
78
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
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
|
-
|
|
88
|
+
const shares = await this.keychain.getShares()
|
|
66
89
|
|
|
67
|
-
|
|
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
|
-
|
|
70
|
-
|
|
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
|
-
|
|
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
|
-
|
|
79
|
-
|
|
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
|
-
|
|
137
|
+
// Measure MPC signing operation time
|
|
138
|
+
const mpcSignStartTime = performance.now()
|
|
90
139
|
|
|
91
|
-
|
|
92
|
-
|
|
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
|
-
|
|
95
|
-
|
|
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
|
-
|
|
103
|
-
|
|
104
|
-
|
|
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
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
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
|
-
|
|
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 {
|
|
4
|
-
|
|
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 {
|