@portal-hq/provider 4.3.1 → 4.5.1
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 +25 -9
- package/lib/commonjs/signers/enclave.js +148 -16
- package/lib/commonjs/signers/mpc.js +151 -9
- package/lib/esm/providers/index.js +26 -10
- package/lib/esm/signers/enclave.js +149 -14
- package/lib/esm/signers/mpc.js +152 -7
- package/package.json +4 -4
- package/src/providers/index.ts +26 -3
- package/src/signers/enclave.ts +200 -4
- package/src/signers/mpc.ts +226 -4
- package/types.d.ts +24 -0
|
@@ -44,7 +44,7 @@ class Provider {
|
|
|
44
44
|
// Required
|
|
45
45
|
apiKey, keychain, gatewayConfig,
|
|
46
46
|
// Optional
|
|
47
|
-
autoApprove = false, apiHost, mpcHost, enclaveMPCHost, version = 'v6', chainId = utils_1.DEFAULT_CHAIN_ID_CAIP2, featureFlags = {}, }) {
|
|
47
|
+
autoApprove = false, apiHost, mpcHost, enclaveMPCHost, version = 'v6', chainId = utils_1.DEFAULT_CHAIN_ID_CAIP2, featureFlags = {}, presignatureSource, }) {
|
|
48
48
|
const finalApiHost = apiHost !== null && apiHost !== void 0 ? apiHost : utils_1.DEFAULT_HOSTS.API;
|
|
49
49
|
const finalMpcHost = mpcHost !== null && mpcHost !== void 0 ? mpcHost : utils_1.DEFAULT_HOSTS.MPC;
|
|
50
50
|
const finalEnclaveMPCHost = enclaveMPCHost !== null && enclaveMPCHost !== void 0 ? enclaveMPCHost : utils_1.DEFAULT_HOSTS.ENCLAVE_MPC;
|
|
@@ -81,6 +81,10 @@ class Provider {
|
|
|
81
81
|
version,
|
|
82
82
|
portalApi: this.portalApi,
|
|
83
83
|
featureFlags,
|
|
84
|
+
// Only pass presignatureSource when usePresignatures is enabled (same as MpcSigner).
|
|
85
|
+
presignatureSource: featureFlags.usePresignatures === true
|
|
86
|
+
? presignatureSource
|
|
87
|
+
: undefined,
|
|
84
88
|
});
|
|
85
89
|
}
|
|
86
90
|
else {
|
|
@@ -90,6 +94,10 @@ class Provider {
|
|
|
90
94
|
version,
|
|
91
95
|
portalApi: this.portalApi,
|
|
92
96
|
featureFlags,
|
|
97
|
+
// Only pass presignatureSource when usePresignatures is enabled; otherwise signer always uses normal sign().
|
|
98
|
+
presignatureSource: featureFlags.usePresignatures === true
|
|
99
|
+
? presignatureSource
|
|
100
|
+
: undefined,
|
|
93
101
|
});
|
|
94
102
|
}
|
|
95
103
|
}
|
|
@@ -203,7 +211,7 @@ class Provider {
|
|
|
203
211
|
* @param args The arguments of the request being made
|
|
204
212
|
* @returns Promise<any>
|
|
205
213
|
*/
|
|
206
|
-
request({ method, params, chainId, connect, }) {
|
|
214
|
+
request({ method, params, chainId, connect, options, }) {
|
|
207
215
|
return __awaiter(this, void 0, void 0, function* () {
|
|
208
216
|
chainId = chainId !== null && chainId !== void 0 ? chainId : this.chainId;
|
|
209
217
|
if (!chainId) {
|
|
@@ -254,6 +262,7 @@ class Provider {
|
|
|
254
262
|
params,
|
|
255
263
|
chainId,
|
|
256
264
|
connect,
|
|
265
|
+
options,
|
|
257
266
|
});
|
|
258
267
|
if (transactionHash) {
|
|
259
268
|
try {
|
|
@@ -325,7 +334,7 @@ class Provider {
|
|
|
325
334
|
this.dispatchConnect(chainId);
|
|
326
335
|
}
|
|
327
336
|
else {
|
|
328
|
-
|
|
337
|
+
utils_1.sdkLogger.error(`[PortalProvider] Invalid chainId format. Must be 'namespace:reference', but got ${chainId}`);
|
|
329
338
|
}
|
|
330
339
|
return new Promise((resolve) => resolve(this));
|
|
331
340
|
});
|
|
@@ -422,8 +431,8 @@ class Provider {
|
|
|
422
431
|
* @param args The arguments of the request being made
|
|
423
432
|
* @returns Promise<any>
|
|
424
433
|
*/
|
|
425
|
-
handleSigningRequests({ method, params, chainId, connect, }) {
|
|
426
|
-
var _a, _b;
|
|
434
|
+
handleSigningRequests({ method, params, chainId, connect, options, }) {
|
|
435
|
+
var _a, _b, _c;
|
|
427
436
|
return __awaiter(this, void 0, void 0, function* () {
|
|
428
437
|
const isApproved = passiveSignerMethods.includes(method)
|
|
429
438
|
? true
|
|
@@ -432,6 +441,7 @@ class Provider {
|
|
|
432
441
|
this.log.info(`[PortalProvider] Request for signing method '${method}' could not be completed because it was not approved by the user.`);
|
|
433
442
|
return;
|
|
434
443
|
}
|
|
444
|
+
const traceId = (_a = options === null || options === void 0 ? void 0 : options.traceId) !== null && _a !== void 0 ? _a : (0, utils_1.generateTraceId)();
|
|
435
445
|
let namespace = utils_1.CHAIN_NAMESPACES.EIP155;
|
|
436
446
|
let reference = chainId;
|
|
437
447
|
if (typeof chainId == 'string' && chainId.includes(':')) {
|
|
@@ -458,22 +468,28 @@ class Provider {
|
|
|
458
468
|
case 'sol_signAndSendTransaction':
|
|
459
469
|
case 'sol_signMessage':
|
|
460
470
|
case 'sol_signTransaction': {
|
|
461
|
-
const result = yield ((
|
|
471
|
+
const result = yield ((_b = this.signer) === null || _b === void 0 ? void 0 : _b.sign({
|
|
462
472
|
chainId: `${namespace}:${reference}`,
|
|
463
473
|
method,
|
|
464
474
|
params,
|
|
465
475
|
curve,
|
|
466
476
|
isRaw: false,
|
|
477
|
+
sponsorGas: options === null || options === void 0 ? void 0 : options.sponsorGas,
|
|
478
|
+
signatureApprovalMemo: options === null || options === void 0 ? void 0 : options.signatureApprovalMemo,
|
|
479
|
+
traceId,
|
|
467
480
|
}, this));
|
|
468
481
|
return result;
|
|
469
482
|
}
|
|
470
483
|
case 'raw_sign': {
|
|
471
|
-
const result = yield ((
|
|
472
|
-
chainId: '',
|
|
473
|
-
method: '',
|
|
484
|
+
const result = yield ((_c = this.signer) === null || _c === void 0 ? void 0 : _c.sign({
|
|
485
|
+
chainId: chainId !== null && chainId !== void 0 ? chainId : '',
|
|
486
|
+
method: 'raw_sign',
|
|
474
487
|
params,
|
|
475
488
|
curve,
|
|
476
489
|
isRaw: true,
|
|
490
|
+
sponsorGas: options === null || options === void 0 ? void 0 : options.sponsorGas,
|
|
491
|
+
signatureApprovalMemo: options === null || options === void 0 ? void 0 : options.signatureApprovalMemo,
|
|
492
|
+
traceId,
|
|
477
493
|
}, this));
|
|
478
494
|
return result;
|
|
479
495
|
}
|
|
@@ -8,20 +8,16 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
8
8
|
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
9
|
});
|
|
10
10
|
};
|
|
11
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
12
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
13
|
-
};
|
|
14
11
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
12
|
const core_1 = require("@portal-hq/core");
|
|
16
13
|
const utils_1 = require("@portal-hq/utils");
|
|
17
|
-
const react_native_uuid_1 = __importDefault(require("react-native-uuid"));
|
|
18
14
|
var Operation;
|
|
19
15
|
(function (Operation) {
|
|
20
16
|
Operation["SIGN"] = "sign";
|
|
21
17
|
Operation["RAW_SIGN"] = "raw_sign";
|
|
22
18
|
})(Operation || (Operation = {}));
|
|
23
19
|
class EnclaveSigner {
|
|
24
|
-
constructor({ keychain, enclaveMPCHost, version = 'v6', portalApi, featureFlags = {}, }) {
|
|
20
|
+
constructor({ keychain, enclaveMPCHost, version = 'v6', portalApi, featureFlags = {}, presignatureSource, }) {
|
|
25
21
|
this.version = 'v6';
|
|
26
22
|
this.buildParams = (method, txParams) => {
|
|
27
23
|
let params = txParams;
|
|
@@ -52,19 +48,21 @@ class EnclaveSigner {
|
|
|
52
48
|
this.enclaveMPCHost = enclaveMPCHost !== null && enclaveMPCHost !== void 0 ? enclaveMPCHost : utils_1.DEFAULT_HOSTS.ENCLAVE_MPC;
|
|
53
49
|
this.version = version;
|
|
54
50
|
this.portalApi = portalApi;
|
|
51
|
+
this.presignatureSource = presignatureSource;
|
|
55
52
|
this.requests = new utils_1.HttpRequester({
|
|
56
53
|
baseUrl: `https://${this.enclaveMPCHost}`,
|
|
57
54
|
});
|
|
58
55
|
}
|
|
59
56
|
sign(message, provider) {
|
|
57
|
+
var _a;
|
|
60
58
|
return __awaiter(this, void 0, void 0, function* () {
|
|
61
59
|
// Always track metrics, but only send if feature flag is enabled
|
|
62
60
|
const shouldSendMetrics = this.featureFlags.enableSdkPerformanceMetrics === true;
|
|
63
61
|
const signStartTime = performance.now();
|
|
64
62
|
const preOperationStartTime = performance.now();
|
|
65
|
-
//
|
|
66
|
-
|
|
67
|
-
|
|
63
|
+
// Use traceId from message if available (e.g. options.traceId), otherwise generate a new UUID
|
|
64
|
+
const traceId = (_a = message.traceId) !== null && _a !== void 0 ? _a : (0, utils_1.generateTraceId)();
|
|
65
|
+
utils_1.sdkLogger.info(`[Portal MPC] enclave sign started | method=${message.method} | traceId=${traceId} | chainId=${message.chainId} | curve=${message.curve}`);
|
|
68
66
|
const metrics = {
|
|
69
67
|
hasError: false,
|
|
70
68
|
operation: Operation.SIGN,
|
|
@@ -119,6 +117,21 @@ class EnclaveSigner {
|
|
|
119
117
|
reqId: traceId,
|
|
120
118
|
connectionTracingEnabled: shouldSendMetrics,
|
|
121
119
|
};
|
|
120
|
+
// Presignature path (SECP256K1 only): try consume before building params; fallback to normal sign.
|
|
121
|
+
if (curve === core_1.PortalCurve.SECP256K1 &&
|
|
122
|
+
this.featureFlags.usePresignatures === true &&
|
|
123
|
+
this.presignatureSource) {
|
|
124
|
+
const presignature = yield this.presignatureSource.consumePresignature(core_1.PortalCurve.SECP256K1);
|
|
125
|
+
if (presignature === null || presignature === void 0 ? void 0 : presignature.data) {
|
|
126
|
+
utils_1.sdkLogger.debug('[Portal] Signing with presignature (EnclaveSigner)', { method: message.method, chainId: message.chainId });
|
|
127
|
+
try {
|
|
128
|
+
return yield this.signWithPresignature(Object.assign(Object.assign({}, message), { presignatureData: presignature.data, traceId }), provider);
|
|
129
|
+
}
|
|
130
|
+
catch (presignError) {
|
|
131
|
+
utils_1.sdkLogger.warn('[Portal.Provider.EnclaveSigner] signWithPresignature failed, falling back to normal sign:', presignError);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
122
135
|
// Build params
|
|
123
136
|
// Avoid double JSON encoding: if params is already a string (e.g. a hex message), pass it directly; otherwise stringify objects/arrays.
|
|
124
137
|
const params = this.buildParams(method, message.params);
|
|
@@ -172,7 +185,7 @@ class EnclaveSigner {
|
|
|
172
185
|
// Make API request and process response
|
|
173
186
|
let result;
|
|
174
187
|
try {
|
|
175
|
-
const response = yield this.makeEnclaveRequest(endpoint, apiKey, requestBody);
|
|
188
|
+
const response = yield this.makeEnclaveRequest(endpoint, apiKey, requestBody, traceId);
|
|
176
189
|
result = this.processEnclaveResponse(response);
|
|
177
190
|
}
|
|
178
191
|
catch (error) {
|
|
@@ -223,7 +236,7 @@ class EnclaveSigner {
|
|
|
223
236
|
try {
|
|
224
237
|
yield this.sendMetrics(metrics, apiKey);
|
|
225
238
|
}
|
|
226
|
-
catch (
|
|
239
|
+
catch (_b) {
|
|
227
240
|
// No-op
|
|
228
241
|
}
|
|
229
242
|
}
|
|
@@ -268,15 +281,12 @@ class EnclaveSigner {
|
|
|
268
281
|
* Make HTTP request to Enclave API
|
|
269
282
|
* Returns raw response without processing
|
|
270
283
|
*/
|
|
271
|
-
makeEnclaveRequest(endpoint, apiKey, body) {
|
|
284
|
+
makeEnclaveRequest(endpoint, apiKey, body, traceId) {
|
|
272
285
|
return __awaiter(this, void 0, void 0, function* () {
|
|
273
|
-
return yield this.requests.post(endpoint, {
|
|
274
|
-
headers: {
|
|
286
|
+
return yield this.requests.post(endpoint, Object.assign({ headers: {
|
|
275
287
|
Authorization: `Bearer ${apiKey}`,
|
|
276
288
|
'Content-Type': 'application/json',
|
|
277
|
-
},
|
|
278
|
-
body,
|
|
279
|
-
});
|
|
289
|
+
}, body }, (traceId ? { traceId } : {})));
|
|
280
290
|
});
|
|
281
291
|
}
|
|
282
292
|
/**
|
|
@@ -374,5 +384,127 @@ class EnclaveSigner {
|
|
|
374
384
|
}
|
|
375
385
|
});
|
|
376
386
|
}
|
|
387
|
+
/**
|
|
388
|
+
* Sign using a pre-generated presignature via the Enclave REST API.
|
|
389
|
+
* Uses the same endpoints as normal signing (/v1/sign, /v1/raw/sign/:curve)
|
|
390
|
+
* but includes the optional `presignature` field in the request body.
|
|
391
|
+
* NOTE: Only supports SECP256K1 curve.
|
|
392
|
+
*/
|
|
393
|
+
signWithPresignature(message, provider) {
|
|
394
|
+
var _a;
|
|
395
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
396
|
+
const { method, chainId, isRaw = false, curve, presignatureData } = message;
|
|
397
|
+
const targetCurve = curve || core_1.PortalCurve.SECP256K1;
|
|
398
|
+
if (targetCurve !== core_1.PortalCurve.SECP256K1) {
|
|
399
|
+
throw new Error('[Portal.Provider.EnclaveSigner] Presignatures are only supported for SECP256K1 curve');
|
|
400
|
+
}
|
|
401
|
+
const shouldSendMetrics = this.featureFlags.enableSdkPerformanceMetrics;
|
|
402
|
+
const traceId = (_a = message.traceId) !== null && _a !== void 0 ? _a : (0, utils_1.generateTraceId)();
|
|
403
|
+
const metrics = {
|
|
404
|
+
hasError: false,
|
|
405
|
+
operation: 'signWithPresignature',
|
|
406
|
+
signingMethod: method,
|
|
407
|
+
traceId,
|
|
408
|
+
};
|
|
409
|
+
if (chainId) {
|
|
410
|
+
metrics.chainId = chainId;
|
|
411
|
+
}
|
|
412
|
+
const requestStartTime = performance.now();
|
|
413
|
+
try {
|
|
414
|
+
const preOperationStartTime = performance.now();
|
|
415
|
+
const apiKey = provider.apiKey;
|
|
416
|
+
if (!apiKey) {
|
|
417
|
+
throw new Error('[Portal.Provider.EnclaveSigner] The API key is missing.');
|
|
418
|
+
}
|
|
419
|
+
const shares = yield this.keychain.getShares();
|
|
420
|
+
const signingShare = shares.secp256k1.share;
|
|
421
|
+
if (!signingShare) {
|
|
422
|
+
throw new Error('[Portal.Provider.EnclaveSigner] The SECP256K1 share is missing from the keychain.');
|
|
423
|
+
}
|
|
424
|
+
const metadata = {
|
|
425
|
+
clientPlatform: 'REACT_NATIVE',
|
|
426
|
+
clientPlatformVersion: (0, utils_1.getClientPlatformVersion)(),
|
|
427
|
+
isMultiBackupEnabled: this.featureFlags.isMultiBackupEnabled,
|
|
428
|
+
mpcServerVersion: this.version,
|
|
429
|
+
optimized: true,
|
|
430
|
+
curve: core_1.PortalCurve.SECP256K1,
|
|
431
|
+
chainId,
|
|
432
|
+
isRaw,
|
|
433
|
+
reqId: traceId,
|
|
434
|
+
connectionTracingEnabled: shouldSendMetrics,
|
|
435
|
+
};
|
|
436
|
+
let endpoint;
|
|
437
|
+
let requestBody;
|
|
438
|
+
if (isRaw) {
|
|
439
|
+
const params = this.buildParams(method, message.params);
|
|
440
|
+
if (typeof params !== 'string') {
|
|
441
|
+
throw new Error('[Portal.Provider.EnclaveSigner] For raw signing with presignature, params must be a string (e.g., a hex-encoded message).');
|
|
442
|
+
}
|
|
443
|
+
endpoint = `/v1/raw/sign/${targetCurve}`;
|
|
444
|
+
requestBody = {
|
|
445
|
+
params,
|
|
446
|
+
share: JSON.stringify(signingShare),
|
|
447
|
+
presignature: presignatureData,
|
|
448
|
+
};
|
|
449
|
+
metrics.operation = 'raw_signWithPresignature';
|
|
450
|
+
}
|
|
451
|
+
else {
|
|
452
|
+
const params = this.buildParams(method, message.params);
|
|
453
|
+
const formattedParams = typeof params === 'string' ? params : JSON.stringify(params);
|
|
454
|
+
const rpcUrl = provider.getGatewayUrl(chainId);
|
|
455
|
+
endpoint = '/v1/sign';
|
|
456
|
+
requestBody = {
|
|
457
|
+
method: message.method,
|
|
458
|
+
params: formattedParams,
|
|
459
|
+
share: JSON.stringify(signingShare),
|
|
460
|
+
chainId: chainId || '',
|
|
461
|
+
rpcUrl,
|
|
462
|
+
metadataStr: JSON.stringify(metadata),
|
|
463
|
+
clientPlatform: 'REACT_NATIVE',
|
|
464
|
+
clientPlatformVersion: (0, utils_1.getClientPlatformVersion)(),
|
|
465
|
+
presignature: presignatureData,
|
|
466
|
+
};
|
|
467
|
+
}
|
|
468
|
+
metrics.sdkPreOperationMs = performance.now() - preOperationStartTime;
|
|
469
|
+
const enclaveSignStartTime = performance.now();
|
|
470
|
+
let result;
|
|
471
|
+
try {
|
|
472
|
+
const response = yield this.makeEnclaveRequest(endpoint, apiKey, requestBody, traceId);
|
|
473
|
+
result = this.processEnclaveResponse(response);
|
|
474
|
+
}
|
|
475
|
+
catch (error) {
|
|
476
|
+
this.handleEnclaveError(error);
|
|
477
|
+
}
|
|
478
|
+
const postOperationStartTime = performance.now();
|
|
479
|
+
metrics.enclaveHttpCallMs = performance.now() - enclaveSignStartTime;
|
|
480
|
+
metrics.sdkPostOperationMs = performance.now() - postOperationStartTime;
|
|
481
|
+
metrics.sdkOperationMs = performance.now() - requestStartTime;
|
|
482
|
+
if (shouldSendMetrics && this.portalApi) {
|
|
483
|
+
try {
|
|
484
|
+
yield this.sendMetrics(metrics, apiKey);
|
|
485
|
+
}
|
|
486
|
+
catch (error) {
|
|
487
|
+
// No-op
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
return result;
|
|
491
|
+
}
|
|
492
|
+
catch (error) {
|
|
493
|
+
utils_1.sdkLogger.error('[Portal.Provider.EnclaveSigner] signWithPresignature error:', error);
|
|
494
|
+
metrics.sdkOperationMs = performance.now() - requestStartTime;
|
|
495
|
+
metrics.hasError = true;
|
|
496
|
+
if (shouldSendMetrics && this.portalApi) {
|
|
497
|
+
const apiKey = provider.apiKey;
|
|
498
|
+
try {
|
|
499
|
+
yield this.sendMetrics(metrics, apiKey);
|
|
500
|
+
}
|
|
501
|
+
catch (metricsError) {
|
|
502
|
+
// No-op
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
throw error;
|
|
506
|
+
}
|
|
507
|
+
});
|
|
508
|
+
}
|
|
377
509
|
}
|
|
378
510
|
exports.default = EnclaveSigner;
|
|
@@ -8,21 +8,17 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
8
8
|
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
9
|
});
|
|
10
10
|
};
|
|
11
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
12
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
13
|
-
};
|
|
14
11
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
12
|
const core_1 = require("@portal-hq/core");
|
|
16
13
|
const utils_1 = require("@portal-hq/utils");
|
|
17
14
|
const react_native_1 = require("react-native");
|
|
18
|
-
const react_native_uuid_1 = __importDefault(require("react-native-uuid"));
|
|
19
15
|
var Operation;
|
|
20
16
|
(function (Operation) {
|
|
21
17
|
Operation["SIGN"] = "sign";
|
|
22
18
|
Operation["RAW_SIGN"] = "raw_sign";
|
|
23
19
|
})(Operation || (Operation = {}));
|
|
24
20
|
class MpcSigner {
|
|
25
|
-
constructor({ keychain, mpcHost = utils_1.DEFAULT_HOSTS.MPC, version = 'v6', portalApi, featureFlags = {}, }) {
|
|
21
|
+
constructor({ keychain, mpcHost = utils_1.DEFAULT_HOSTS.MPC, version = 'v6', portalApi, featureFlags = {}, presignatureSource, }) {
|
|
26
22
|
this.version = 'v6';
|
|
27
23
|
this.buildParams = (method, txParams) => {
|
|
28
24
|
let params = txParams;
|
|
@@ -54,19 +50,21 @@ class MpcSigner {
|
|
|
54
50
|
this.mpcHost = mpcHost;
|
|
55
51
|
this.version = version;
|
|
56
52
|
this.portalApi = portalApi;
|
|
53
|
+
this.presignatureSource = presignatureSource;
|
|
57
54
|
if (!this.mpc) {
|
|
58
55
|
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.`);
|
|
59
56
|
}
|
|
60
57
|
}
|
|
61
58
|
sign(message, provider) {
|
|
59
|
+
var _a;
|
|
62
60
|
return __awaiter(this, void 0, void 0, function* () {
|
|
63
61
|
// Always track metrics, but only send if feature flag is enabled
|
|
64
62
|
const shouldSendMetrics = this.featureFlags.enableSdkPerformanceMetrics === true;
|
|
65
63
|
const signStartTime = performance.now();
|
|
66
64
|
const preOperationStartTime = performance.now();
|
|
67
|
-
//
|
|
68
|
-
|
|
69
|
-
|
|
65
|
+
// Use traceId from message if available (e.g. options.traceId), otherwise generate a new UUID
|
|
66
|
+
const traceId = (_a = message.traceId) !== null && _a !== void 0 ? _a : (0, utils_1.generateTraceId)();
|
|
67
|
+
utils_1.sdkLogger.info(`[Portal MPC] sign started | method=${message.method} | traceId=${traceId} | chainId=${message.chainId} | curve=${message.curve}`);
|
|
70
68
|
const metrics = {
|
|
71
69
|
hasError: false,
|
|
72
70
|
operation: Operation.SIGN,
|
|
@@ -126,6 +124,22 @@ class MpcSigner {
|
|
|
126
124
|
if (typeof formattedParams !== 'string') {
|
|
127
125
|
throw new Error(`[Portal.Provider.MpcSigner] The formatted params for the signing request could not be converted to a string. The params were: ${formattedParams}`);
|
|
128
126
|
}
|
|
127
|
+
// Internal presignature handling (gated by feature flag): when usePresignatures is true and a
|
|
128
|
+
// presignature is available for SECP256K1, use signWithPresignature; otherwise normal sign().
|
|
129
|
+
if (curve === core_1.PortalCurve.SECP256K1 &&
|
|
130
|
+
this.featureFlags.usePresignatures === true &&
|
|
131
|
+
this.presignatureSource) {
|
|
132
|
+
const presignature = yield this.presignatureSource.consumePresignature(core_1.PortalCurve.SECP256K1);
|
|
133
|
+
if (presignature === null || presignature === void 0 ? void 0 : presignature.data) {
|
|
134
|
+
utils_1.sdkLogger.debug('[Portal] Signing with presignature (portal.request/sendAsset path)', { method: message.method, chainId: message.chainId });
|
|
135
|
+
try {
|
|
136
|
+
return yield this.signWithPresignature(Object.assign(Object.assign({}, message), { presignatureData: presignature.data, traceId }), provider);
|
|
137
|
+
}
|
|
138
|
+
catch (presignError) {
|
|
139
|
+
utils_1.sdkLogger.warn('[Portal.Provider.MpcSigner] signWithPresignature failed, falling back to normal sign:', presignError);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
129
143
|
// Record pre-operation time
|
|
130
144
|
metrics.sdkPreOperationMs = performance.now() - preOperationStartTime;
|
|
131
145
|
// Measure MPC signing operation time
|
|
@@ -188,7 +202,7 @@ class MpcSigner {
|
|
|
188
202
|
try {
|
|
189
203
|
yield this.sendMetrics(metrics, apiKey);
|
|
190
204
|
}
|
|
191
|
-
catch (
|
|
205
|
+
catch (_b) {
|
|
192
206
|
// No-op
|
|
193
207
|
}
|
|
194
208
|
}
|
|
@@ -215,5 +229,133 @@ class MpcSigner {
|
|
|
215
229
|
}
|
|
216
230
|
});
|
|
217
231
|
}
|
|
232
|
+
/**
|
|
233
|
+
* Sign a transaction using a pre-generated presignature
|
|
234
|
+
* NOTE: Only supports SECP256K1 curve
|
|
235
|
+
*
|
|
236
|
+
* @param message - Signing request with presignatureData
|
|
237
|
+
* @param provider - Portal provider instance
|
|
238
|
+
* @returns Signed transaction hash
|
|
239
|
+
*/
|
|
240
|
+
signWithPresignature(message, provider) {
|
|
241
|
+
var _a;
|
|
242
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
243
|
+
const { method, chainId, isRaw = false, curve, sponsorGas, signatureApprovalMemo, presignatureData, } = message;
|
|
244
|
+
const targetCurve = curve || core_1.PortalCurve.SECP256K1;
|
|
245
|
+
if (targetCurve !== core_1.PortalCurve.SECP256K1) {
|
|
246
|
+
throw new Error('[Portal.Provider.MpcSigner] Presignatures are only supported for SECP256K1 curve');
|
|
247
|
+
}
|
|
248
|
+
const shouldSendMetrics = this.featureFlags.enableSdkPerformanceMetrics;
|
|
249
|
+
const traceId = (_a = message.traceId) !== null && _a !== void 0 ? _a : (0, utils_1.generateTraceId)();
|
|
250
|
+
const metrics = {
|
|
251
|
+
hasError: false,
|
|
252
|
+
operation: 'signWithPresignature',
|
|
253
|
+
signingMethod: method,
|
|
254
|
+
traceId,
|
|
255
|
+
};
|
|
256
|
+
if (chainId) {
|
|
257
|
+
metrics.chainId = chainId;
|
|
258
|
+
}
|
|
259
|
+
const requestStartTime = performance.now();
|
|
260
|
+
try {
|
|
261
|
+
const preOperationStartTime = performance.now();
|
|
262
|
+
const apiKey = provider.apiKey;
|
|
263
|
+
if (!apiKey) {
|
|
264
|
+
throw new Error('[Portal.Provider.MpcSigner] The API key is missing.');
|
|
265
|
+
}
|
|
266
|
+
const shares = yield this.keychain.getShares();
|
|
267
|
+
const signingShare = shares.secp256k1.share;
|
|
268
|
+
if (!signingShare) {
|
|
269
|
+
throw new Error('[Portal.Provider.MpcSigner] The SECP256K1 share is missing from the keychain.');
|
|
270
|
+
}
|
|
271
|
+
const metadata = {
|
|
272
|
+
clientPlatform: 'REACT_NATIVE',
|
|
273
|
+
clientPlatformVersion: (0, utils_1.getClientPlatformVersion)(),
|
|
274
|
+
isMultiBackupEnabled: this.featureFlags.isMultiBackupEnabled,
|
|
275
|
+
mpcServerVersion: this.version,
|
|
276
|
+
optimized: true,
|
|
277
|
+
curve: core_1.PortalCurve.SECP256K1,
|
|
278
|
+
chainId,
|
|
279
|
+
isRaw,
|
|
280
|
+
reqId: traceId,
|
|
281
|
+
connectionTracingEnabled: shouldSendMetrics,
|
|
282
|
+
sponsorGas,
|
|
283
|
+
signatureApprovalMemo,
|
|
284
|
+
};
|
|
285
|
+
const stringifiedMetadata = JSON.stringify(metadata);
|
|
286
|
+
let formattedParams;
|
|
287
|
+
let rpcUrl;
|
|
288
|
+
if (isRaw) {
|
|
289
|
+
formattedParams = this.buildParams(method, message.params);
|
|
290
|
+
rpcUrl = '';
|
|
291
|
+
metrics.operation = 'raw_signWithPresignature';
|
|
292
|
+
}
|
|
293
|
+
else {
|
|
294
|
+
formattedParams = JSON.stringify(this.buildParams(method, message.params));
|
|
295
|
+
rpcUrl = provider.getGatewayUrl(chainId);
|
|
296
|
+
}
|
|
297
|
+
if (typeof formattedParams !== 'string') {
|
|
298
|
+
throw new Error(`[Portal.Provider.MpcSigner] The formatted params for the signing request could not be converted to a string. The params were: ${formattedParams}`);
|
|
299
|
+
}
|
|
300
|
+
metrics.sdkPreOperationMs = performance.now() - preOperationStartTime;
|
|
301
|
+
const mpcSignStartTime = performance.now();
|
|
302
|
+
const result = yield this.mpc.signWithPresignature(apiKey, this.mpcHost, JSON.stringify(signingShare), presignatureData, method, formattedParams, rpcUrl, chainId, stringifiedMetadata);
|
|
303
|
+
const postOperationStartTime = performance.now();
|
|
304
|
+
metrics.mpcNativeCallMs = performance.now() - mpcSignStartTime;
|
|
305
|
+
const parsedResponse = JSON.parse(String(result));
|
|
306
|
+
const { data, error, meta } = parsedResponse;
|
|
307
|
+
if (meta === null || meta === void 0 ? void 0 : meta.metrics) {
|
|
308
|
+
const binaryMetrics = meta.metrics;
|
|
309
|
+
if (binaryMetrics.wsConnectDurationMs) {
|
|
310
|
+
metrics.sdkBinaryWSConnectMs = binaryMetrics.wsConnectDurationMs;
|
|
311
|
+
}
|
|
312
|
+
if (binaryMetrics.operationDurationMs) {
|
|
313
|
+
metrics.sdkBinaryOperationMs = binaryMetrics.operationDurationMs;
|
|
314
|
+
}
|
|
315
|
+
if (binaryMetrics.tlsHandshakeMs) {
|
|
316
|
+
metrics.sdkBinaryTlsHandshakeMs = binaryMetrics.tlsHandshakeMs;
|
|
317
|
+
}
|
|
318
|
+
if (binaryMetrics.firstResponseMs) {
|
|
319
|
+
metrics.sdkBinaryFirstResponseMs = binaryMetrics.firstResponseMs;
|
|
320
|
+
}
|
|
321
|
+
if (binaryMetrics.dnsLookupMs) {
|
|
322
|
+
metrics.sdkBinaryDnsLookupMs = binaryMetrics.dnsLookupMs;
|
|
323
|
+
}
|
|
324
|
+
if (binaryMetrics.connectMs) {
|
|
325
|
+
metrics.sdkBinaryConnectMs = binaryMetrics.connectMs;
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
if (error === null || error === void 0 ? void 0 : error.id) {
|
|
329
|
+
throw new utils_1.PortalMpcError(error);
|
|
330
|
+
}
|
|
331
|
+
metrics.sdkPostOperationMs = performance.now() - postOperationStartTime;
|
|
332
|
+
metrics.sdkOperationMs = performance.now() - requestStartTime;
|
|
333
|
+
if (shouldSendMetrics && this.portalApi) {
|
|
334
|
+
try {
|
|
335
|
+
yield this.sendMetrics(metrics, apiKey);
|
|
336
|
+
}
|
|
337
|
+
catch (error) {
|
|
338
|
+
// No-op
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
return data;
|
|
342
|
+
}
|
|
343
|
+
catch (error) {
|
|
344
|
+
utils_1.sdkLogger.error('[Portal.Provider.MpcSigner] signWithPresignature error:', error);
|
|
345
|
+
metrics.sdkOperationMs = performance.now() - requestStartTime;
|
|
346
|
+
metrics.hasError = true;
|
|
347
|
+
if (shouldSendMetrics && this.portalApi) {
|
|
348
|
+
const apiKey = provider.apiKey;
|
|
349
|
+
try {
|
|
350
|
+
yield this.sendMetrics(metrics, apiKey);
|
|
351
|
+
}
|
|
352
|
+
catch (metricsError) {
|
|
353
|
+
// No-op
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
throw error;
|
|
357
|
+
}
|
|
358
|
+
});
|
|
359
|
+
}
|
|
218
360
|
}
|
|
219
361
|
exports.default = MpcSigner;
|
|
@@ -8,7 +8,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
8
8
|
});
|
|
9
9
|
};
|
|
10
10
|
import { PortalCurve } from '@portal-hq/core';
|
|
11
|
-
import { Events, HttpRequester, InvalidApiKeyError, InvalidGatewayConfigError, PortalRequests, ProviderRpcError, RpcErrorCodes, DEFAULT_CHAIN_ID_CAIP2, CHAIN_NAMESPACES, DEFAULT_HOSTS, } from '@portal-hq/utils';
|
|
11
|
+
import { Events, HttpRequester, InvalidApiKeyError, InvalidGatewayConfigError, PortalRequests, ProviderRpcError, RpcErrorCodes, DEFAULT_CHAIN_ID_CAIP2, CHAIN_NAMESPACES, DEFAULT_HOSTS, generateTraceId, sdkLogger, } from '@portal-hq/utils';
|
|
12
12
|
import { MpcSigner, EnclaveSigner } from '../signers';
|
|
13
13
|
const passiveSignerMethods = [
|
|
14
14
|
'eth_accounts',
|
|
@@ -42,7 +42,7 @@ class Provider {
|
|
|
42
42
|
// Required
|
|
43
43
|
apiKey, keychain, gatewayConfig,
|
|
44
44
|
// Optional
|
|
45
|
-
autoApprove = false, apiHost, mpcHost, enclaveMPCHost, version = 'v6', chainId = DEFAULT_CHAIN_ID_CAIP2, featureFlags = {}, }) {
|
|
45
|
+
autoApprove = false, apiHost, mpcHost, enclaveMPCHost, version = 'v6', chainId = DEFAULT_CHAIN_ID_CAIP2, featureFlags = {}, presignatureSource, }) {
|
|
46
46
|
const finalApiHost = apiHost !== null && apiHost !== void 0 ? apiHost : DEFAULT_HOSTS.API;
|
|
47
47
|
const finalMpcHost = mpcHost !== null && mpcHost !== void 0 ? mpcHost : DEFAULT_HOSTS.MPC;
|
|
48
48
|
const finalEnclaveMPCHost = enclaveMPCHost !== null && enclaveMPCHost !== void 0 ? enclaveMPCHost : DEFAULT_HOSTS.ENCLAVE_MPC;
|
|
@@ -79,6 +79,10 @@ class Provider {
|
|
|
79
79
|
version,
|
|
80
80
|
portalApi: this.portalApi,
|
|
81
81
|
featureFlags,
|
|
82
|
+
// Only pass presignatureSource when usePresignatures is enabled (same as MpcSigner).
|
|
83
|
+
presignatureSource: featureFlags.usePresignatures === true
|
|
84
|
+
? presignatureSource
|
|
85
|
+
: undefined,
|
|
82
86
|
});
|
|
83
87
|
}
|
|
84
88
|
else {
|
|
@@ -88,6 +92,10 @@ class Provider {
|
|
|
88
92
|
version,
|
|
89
93
|
portalApi: this.portalApi,
|
|
90
94
|
featureFlags,
|
|
95
|
+
// Only pass presignatureSource when usePresignatures is enabled; otherwise signer always uses normal sign().
|
|
96
|
+
presignatureSource: featureFlags.usePresignatures === true
|
|
97
|
+
? presignatureSource
|
|
98
|
+
: undefined,
|
|
91
99
|
});
|
|
92
100
|
}
|
|
93
101
|
}
|
|
@@ -201,7 +209,7 @@ class Provider {
|
|
|
201
209
|
* @param args The arguments of the request being made
|
|
202
210
|
* @returns Promise<any>
|
|
203
211
|
*/
|
|
204
|
-
request({ method, params, chainId, connect, }) {
|
|
212
|
+
request({ method, params, chainId, connect, options, }) {
|
|
205
213
|
return __awaiter(this, void 0, void 0, function* () {
|
|
206
214
|
chainId = chainId !== null && chainId !== void 0 ? chainId : this.chainId;
|
|
207
215
|
if (!chainId) {
|
|
@@ -252,6 +260,7 @@ class Provider {
|
|
|
252
260
|
params,
|
|
253
261
|
chainId,
|
|
254
262
|
connect,
|
|
263
|
+
options,
|
|
255
264
|
});
|
|
256
265
|
if (transactionHash) {
|
|
257
266
|
try {
|
|
@@ -323,7 +332,7 @@ class Provider {
|
|
|
323
332
|
this.dispatchConnect(chainId);
|
|
324
333
|
}
|
|
325
334
|
else {
|
|
326
|
-
|
|
335
|
+
sdkLogger.error(`[PortalProvider] Invalid chainId format. Must be 'namespace:reference', but got ${chainId}`);
|
|
327
336
|
}
|
|
328
337
|
return new Promise((resolve) => resolve(this));
|
|
329
338
|
});
|
|
@@ -420,8 +429,8 @@ class Provider {
|
|
|
420
429
|
* @param args The arguments of the request being made
|
|
421
430
|
* @returns Promise<any>
|
|
422
431
|
*/
|
|
423
|
-
handleSigningRequests({ method, params, chainId, connect, }) {
|
|
424
|
-
var _a, _b;
|
|
432
|
+
handleSigningRequests({ method, params, chainId, connect, options, }) {
|
|
433
|
+
var _a, _b, _c;
|
|
425
434
|
return __awaiter(this, void 0, void 0, function* () {
|
|
426
435
|
const isApproved = passiveSignerMethods.includes(method)
|
|
427
436
|
? true
|
|
@@ -430,6 +439,7 @@ class Provider {
|
|
|
430
439
|
this.log.info(`[PortalProvider] Request for signing method '${method}' could not be completed because it was not approved by the user.`);
|
|
431
440
|
return;
|
|
432
441
|
}
|
|
442
|
+
const traceId = (_a = options === null || options === void 0 ? void 0 : options.traceId) !== null && _a !== void 0 ? _a : generateTraceId();
|
|
433
443
|
let namespace = CHAIN_NAMESPACES.EIP155;
|
|
434
444
|
let reference = chainId;
|
|
435
445
|
if (typeof chainId == 'string' && chainId.includes(':')) {
|
|
@@ -456,22 +466,28 @@ class Provider {
|
|
|
456
466
|
case 'sol_signAndSendTransaction':
|
|
457
467
|
case 'sol_signMessage':
|
|
458
468
|
case 'sol_signTransaction': {
|
|
459
|
-
const result = yield ((
|
|
469
|
+
const result = yield ((_b = this.signer) === null || _b === void 0 ? void 0 : _b.sign({
|
|
460
470
|
chainId: `${namespace}:${reference}`,
|
|
461
471
|
method,
|
|
462
472
|
params,
|
|
463
473
|
curve,
|
|
464
474
|
isRaw: false,
|
|
475
|
+
sponsorGas: options === null || options === void 0 ? void 0 : options.sponsorGas,
|
|
476
|
+
signatureApprovalMemo: options === null || options === void 0 ? void 0 : options.signatureApprovalMemo,
|
|
477
|
+
traceId,
|
|
465
478
|
}, this));
|
|
466
479
|
return result;
|
|
467
480
|
}
|
|
468
481
|
case 'raw_sign': {
|
|
469
|
-
const result = yield ((
|
|
470
|
-
chainId: '',
|
|
471
|
-
method: '',
|
|
482
|
+
const result = yield ((_c = this.signer) === null || _c === void 0 ? void 0 : _c.sign({
|
|
483
|
+
chainId: chainId !== null && chainId !== void 0 ? chainId : '',
|
|
484
|
+
method: 'raw_sign',
|
|
472
485
|
params,
|
|
473
486
|
curve,
|
|
474
487
|
isRaw: true,
|
|
488
|
+
sponsorGas: options === null || options === void 0 ? void 0 : options.sponsorGas,
|
|
489
|
+
signatureApprovalMemo: options === null || options === void 0 ? void 0 : options.signatureApprovalMemo,
|
|
490
|
+
traceId,
|
|
475
491
|
}, this));
|
|
476
492
|
return result;
|
|
477
493
|
}
|