@portal-hq/provider 4.4.0 → 4.6.0
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 +24 -8
- package/lib/commonjs/signers/enclave.js +150 -16
- package/lib/commonjs/signers/mpc.js +151 -9
- package/lib/esm/providers/index.js +26 -10
- package/lib/esm/signers/enclave.js +151 -14
- package/lib/esm/signers/mpc.js +152 -7
- package/package.json +4 -4
- package/src/providers/index.ts +29 -6
- package/src/signers/enclave.ts +204 -6
- package/src/signers/mpc.ts +227 -5
- package/types.d.ts +25 -1
package/src/signers/enclave.ts
CHANGED
|
@@ -1,21 +1,23 @@
|
|
|
1
1
|
import { PortalCurve } from '@portal-hq/core'
|
|
2
2
|
import { FeatureFlags } from '@portal-hq/core/types'
|
|
3
3
|
import {
|
|
4
|
+
DEFAULT_HOSTS,
|
|
4
5
|
HttpRequester,
|
|
5
6
|
IPortalProvider,
|
|
6
7
|
KeychainAdapter,
|
|
7
8
|
PortalErrorCodes,
|
|
8
9
|
PortalMpcError,
|
|
9
10
|
type SigningRequestArguments,
|
|
11
|
+
generateTraceId,
|
|
10
12
|
getClientPlatformVersion,
|
|
11
|
-
|
|
13
|
+
sdkLogger,
|
|
12
14
|
} from '@portal-hq/utils'
|
|
13
|
-
import UUID from 'react-native-uuid'
|
|
14
15
|
|
|
15
16
|
import {
|
|
17
|
+
type EnclaveSignResponse,
|
|
16
18
|
type MpcSignerOptions,
|
|
17
19
|
PortalMobileMpcMetadata,
|
|
18
|
-
type
|
|
20
|
+
type PresignatureSource,
|
|
19
21
|
} from '../../types'
|
|
20
22
|
import Signer from './abstract'
|
|
21
23
|
|
|
@@ -31,6 +33,7 @@ class EnclaveSigner implements Signer {
|
|
|
31
33
|
private version = 'v6'
|
|
32
34
|
private portalApi?: HttpRequester
|
|
33
35
|
private requests: HttpRequester
|
|
36
|
+
private presignatureSource?: PresignatureSource
|
|
34
37
|
|
|
35
38
|
constructor({
|
|
36
39
|
keychain,
|
|
@@ -38,12 +41,14 @@ class EnclaveSigner implements Signer {
|
|
|
38
41
|
version = 'v6',
|
|
39
42
|
portalApi,
|
|
40
43
|
featureFlags = {},
|
|
44
|
+
presignatureSource,
|
|
41
45
|
}: MpcSignerOptions & { enclaveMPCHost?: string }) {
|
|
42
46
|
this.featureFlags = featureFlags
|
|
43
47
|
this.keychain = keychain
|
|
44
48
|
this.enclaveMPCHost = enclaveMPCHost ?? DEFAULT_HOSTS.ENCLAVE_MPC
|
|
45
49
|
this.version = version
|
|
46
50
|
this.portalApi = portalApi
|
|
51
|
+
this.presignatureSource = presignatureSource
|
|
47
52
|
this.requests = new HttpRequester({
|
|
48
53
|
baseUrl: `https://${this.enclaveMPCHost}`,
|
|
49
54
|
})
|
|
@@ -59,9 +64,11 @@ class EnclaveSigner implements Signer {
|
|
|
59
64
|
const signStartTime = performance.now()
|
|
60
65
|
const preOperationStartTime = performance.now()
|
|
61
66
|
|
|
62
|
-
//
|
|
63
|
-
|
|
64
|
-
|
|
67
|
+
// Use traceId from message if available (e.g. options.traceId), otherwise generate a new UUID
|
|
68
|
+
const traceId = message.traceId ?? generateTraceId()
|
|
69
|
+
sdkLogger.info(
|
|
70
|
+
`[Portal MPC] enclave sign started | method=${message.method} | traceId=${traceId} | chainId=${message.chainId} | curve=${message.curve}`,
|
|
71
|
+
)
|
|
65
72
|
const metrics: Record<string, number | string | boolean> = {
|
|
66
73
|
hasError: false,
|
|
67
74
|
operation: Operation.SIGN,
|
|
@@ -127,6 +134,34 @@ class EnclaveSigner implements Signer {
|
|
|
127
134
|
connectionTracingEnabled: shouldSendMetrics,
|
|
128
135
|
}
|
|
129
136
|
|
|
137
|
+
// Presignature path (SECP256K1 only): try consume before building params; fallback to normal sign.
|
|
138
|
+
if (
|
|
139
|
+
curve === PortalCurve.SECP256K1 &&
|
|
140
|
+
this.featureFlags.usePresignatures === true &&
|
|
141
|
+
this.presignatureSource
|
|
142
|
+
) {
|
|
143
|
+
const presignature = await this.presignatureSource.consumePresignature(
|
|
144
|
+
PortalCurve.SECP256K1,
|
|
145
|
+
)
|
|
146
|
+
if (presignature?.data) {
|
|
147
|
+
sdkLogger.debug(
|
|
148
|
+
'[Portal] Signing with presignature (EnclaveSigner)',
|
|
149
|
+
{ method: message.method, chainId: message.chainId },
|
|
150
|
+
)
|
|
151
|
+
try {
|
|
152
|
+
return await this.signWithPresignature(
|
|
153
|
+
{ ...message, presignatureData: presignature.data, traceId },
|
|
154
|
+
provider,
|
|
155
|
+
)
|
|
156
|
+
} catch (presignError) {
|
|
157
|
+
sdkLogger.warn(
|
|
158
|
+
'[Portal.Provider.EnclaveSigner] signWithPresignature failed, falling back to normal sign:',
|
|
159
|
+
presignError,
|
|
160
|
+
)
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
130
165
|
// Build params
|
|
131
166
|
// Avoid double JSON encoding: if params is already a string (e.g. a hex message), pass it directly; otherwise stringify objects/arrays.
|
|
132
167
|
const params = this.buildParams(method, message.params)
|
|
@@ -172,6 +207,7 @@ class EnclaveSigner implements Signer {
|
|
|
172
207
|
requestBody = {
|
|
173
208
|
params: formattedParams,
|
|
174
209
|
share: JSON.stringify(signingShare),
|
|
210
|
+
metadataStr: JSON.stringify(metadata),
|
|
175
211
|
}
|
|
176
212
|
} else {
|
|
177
213
|
// Standard sign endpoint and body
|
|
@@ -195,6 +231,7 @@ class EnclaveSigner implements Signer {
|
|
|
195
231
|
endpoint,
|
|
196
232
|
apiKey,
|
|
197
233
|
requestBody,
|
|
234
|
+
traceId,
|
|
198
235
|
)
|
|
199
236
|
result = this.processEnclaveResponse(response)
|
|
200
237
|
} catch (error) {
|
|
@@ -310,6 +347,7 @@ class EnclaveSigner implements Signer {
|
|
|
310
347
|
endpoint: string,
|
|
311
348
|
apiKey: string,
|
|
312
349
|
body: Record<string, any>,
|
|
350
|
+
traceId?: string,
|
|
313
351
|
): Promise<EnclaveSignResponse> {
|
|
314
352
|
return await this.requests.post<EnclaveSignResponse>(endpoint, {
|
|
315
353
|
headers: {
|
|
@@ -317,6 +355,7 @@ class EnclaveSigner implements Signer {
|
|
|
317
355
|
'Content-Type': 'application/json',
|
|
318
356
|
},
|
|
319
357
|
body,
|
|
358
|
+
...(traceId ? { traceId } : {}),
|
|
320
359
|
})
|
|
321
360
|
}
|
|
322
361
|
|
|
@@ -456,6 +495,165 @@ class EnclaveSigner implements Signer {
|
|
|
456
495
|
}
|
|
457
496
|
return params
|
|
458
497
|
}
|
|
498
|
+
|
|
499
|
+
/**
|
|
500
|
+
* Sign using a pre-generated presignature via the Enclave REST API.
|
|
501
|
+
* Uses the same endpoints as normal signing (/v1/sign, /v1/raw/sign/:curve)
|
|
502
|
+
* but includes the optional `presignature` field in the request body.
|
|
503
|
+
* NOTE: Only supports SECP256K1 curve.
|
|
504
|
+
*/
|
|
505
|
+
public async signWithPresignature(
|
|
506
|
+
message: SigningRequestArguments & { presignatureData: string },
|
|
507
|
+
provider: IPortalProvider,
|
|
508
|
+
): Promise<string> {
|
|
509
|
+
const { method, chainId, isRaw = false, curve, presignatureData } = message
|
|
510
|
+
|
|
511
|
+
const targetCurve = curve || PortalCurve.SECP256K1
|
|
512
|
+
if (targetCurve !== PortalCurve.SECP256K1) {
|
|
513
|
+
throw new Error(
|
|
514
|
+
'[Portal.Provider.EnclaveSigner] Presignatures are only supported for SECP256K1 curve',
|
|
515
|
+
)
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
const shouldSendMetrics = this.featureFlags.enableSdkPerformanceMetrics
|
|
519
|
+
const traceId = message.traceId ?? generateTraceId()
|
|
520
|
+
const metrics: Record<string, number | string | boolean> = {
|
|
521
|
+
hasError: false,
|
|
522
|
+
operation: 'signWithPresignature',
|
|
523
|
+
signingMethod: method,
|
|
524
|
+
traceId,
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
if (chainId) {
|
|
528
|
+
metrics.chainId = chainId
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
const requestStartTime = performance.now()
|
|
532
|
+
|
|
533
|
+
try {
|
|
534
|
+
const preOperationStartTime = performance.now()
|
|
535
|
+
|
|
536
|
+
const apiKey = provider.apiKey
|
|
537
|
+
if (!apiKey) {
|
|
538
|
+
throw new Error(
|
|
539
|
+
'[Portal.Provider.EnclaveSigner] The API key is missing.',
|
|
540
|
+
)
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
const shares = await this.keychain.getShares()
|
|
544
|
+
const signingShare = shares.secp256k1.share
|
|
545
|
+
|
|
546
|
+
if (!signingShare) {
|
|
547
|
+
throw new Error(
|
|
548
|
+
'[Portal.Provider.EnclaveSigner] The SECP256K1 share is missing from the keychain.',
|
|
549
|
+
)
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
const metadata: PortalMobileMpcMetadata = {
|
|
553
|
+
clientPlatform: 'REACT_NATIVE',
|
|
554
|
+
clientPlatformVersion: getClientPlatformVersion(),
|
|
555
|
+
isMultiBackupEnabled: this.featureFlags.isMultiBackupEnabled,
|
|
556
|
+
mpcServerVersion: this.version,
|
|
557
|
+
optimized: true,
|
|
558
|
+
curve: PortalCurve.SECP256K1,
|
|
559
|
+
chainId,
|
|
560
|
+
isRaw,
|
|
561
|
+
reqId: traceId,
|
|
562
|
+
connectionTracingEnabled: shouldSendMetrics,
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
let endpoint: string
|
|
566
|
+
let requestBody: Record<string, any>
|
|
567
|
+
|
|
568
|
+
if (isRaw) {
|
|
569
|
+
const params = this.buildParams(method, message.params)
|
|
570
|
+
if (typeof params !== 'string') {
|
|
571
|
+
throw new Error(
|
|
572
|
+
'[Portal.Provider.EnclaveSigner] For raw signing with presignature, params must be a string (e.g., a hex-encoded message).',
|
|
573
|
+
)
|
|
574
|
+
}
|
|
575
|
+
endpoint = `/v1/raw/sign/${targetCurve}`
|
|
576
|
+
requestBody = {
|
|
577
|
+
params,
|
|
578
|
+
share: JSON.stringify(signingShare),
|
|
579
|
+
metadataStr: JSON.stringify(metadata),
|
|
580
|
+
presignature: presignatureData,
|
|
581
|
+
}
|
|
582
|
+
metrics.operation = 'raw_signWithPresignature'
|
|
583
|
+
} else {
|
|
584
|
+
const params = this.buildParams(method, message.params)
|
|
585
|
+
const formattedParams =
|
|
586
|
+
typeof params === 'string' ? params : JSON.stringify(params)
|
|
587
|
+
const rpcUrl = provider.getGatewayUrl(chainId)
|
|
588
|
+
endpoint = '/v1/sign'
|
|
589
|
+
requestBody = {
|
|
590
|
+
method: message.method,
|
|
591
|
+
params: formattedParams,
|
|
592
|
+
share: JSON.stringify(signingShare),
|
|
593
|
+
chainId: chainId || '',
|
|
594
|
+
rpcUrl,
|
|
595
|
+
metadataStr: JSON.stringify(metadata),
|
|
596
|
+
clientPlatform: 'REACT_NATIVE',
|
|
597
|
+
clientPlatformVersion: getClientPlatformVersion(),
|
|
598
|
+
presignature: presignatureData,
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
metrics.sdkPreOperationMs = performance.now() - preOperationStartTime
|
|
603
|
+
|
|
604
|
+
const enclaveSignStartTime = performance.now()
|
|
605
|
+
|
|
606
|
+
let result: string
|
|
607
|
+
try {
|
|
608
|
+
const response = await this.makeEnclaveRequest(
|
|
609
|
+
endpoint,
|
|
610
|
+
apiKey,
|
|
611
|
+
requestBody,
|
|
612
|
+
traceId,
|
|
613
|
+
)
|
|
614
|
+
result = this.processEnclaveResponse(response)
|
|
615
|
+
} catch (error) {
|
|
616
|
+
this.handleEnclaveError(error)
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
const postOperationStartTime = performance.now()
|
|
620
|
+
|
|
621
|
+
metrics.enclaveHttpCallMs = performance.now() - enclaveSignStartTime
|
|
622
|
+
|
|
623
|
+
metrics.sdkPostOperationMs = performance.now() - postOperationStartTime
|
|
624
|
+
|
|
625
|
+
metrics.sdkOperationMs = performance.now() - requestStartTime
|
|
626
|
+
|
|
627
|
+
if (shouldSendMetrics && this.portalApi) {
|
|
628
|
+
try {
|
|
629
|
+
await this.sendMetrics(metrics, apiKey)
|
|
630
|
+
} catch (error) {
|
|
631
|
+
// No-op
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
return result
|
|
636
|
+
} catch (error) {
|
|
637
|
+
sdkLogger.error(
|
|
638
|
+
'[Portal.Provider.EnclaveSigner] signWithPresignature error:',
|
|
639
|
+
error,
|
|
640
|
+
)
|
|
641
|
+
|
|
642
|
+
metrics.sdkOperationMs = performance.now() - requestStartTime
|
|
643
|
+
metrics.hasError = true
|
|
644
|
+
|
|
645
|
+
if (shouldSendMetrics && this.portalApi) {
|
|
646
|
+
const apiKey = provider.apiKey
|
|
647
|
+
try {
|
|
648
|
+
await this.sendMetrics(metrics, apiKey)
|
|
649
|
+
} catch (metricsError) {
|
|
650
|
+
// No-op
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
throw error
|
|
655
|
+
}
|
|
656
|
+
}
|
|
459
657
|
}
|
|
460
658
|
|
|
461
659
|
export default EnclaveSigner
|
package/src/signers/mpc.ts
CHANGED
|
@@ -1,21 +1,23 @@
|
|
|
1
1
|
import { PortalCurve } from '@portal-hq/core'
|
|
2
2
|
import { FeatureFlags } from '@portal-hq/core/types'
|
|
3
3
|
import {
|
|
4
|
+
DEFAULT_HOSTS,
|
|
4
5
|
HttpRequester,
|
|
5
6
|
IPortalProvider,
|
|
6
7
|
KeychainAdapter,
|
|
7
8
|
PortalMpcError,
|
|
8
9
|
type SigningRequestArguments,
|
|
10
|
+
generateTraceId,
|
|
9
11
|
getClientPlatformVersion,
|
|
10
|
-
|
|
12
|
+
sdkLogger,
|
|
11
13
|
} from '@portal-hq/utils'
|
|
12
14
|
import { NativeModules } from 'react-native'
|
|
13
|
-
import UUID from 'react-native-uuid'
|
|
14
15
|
|
|
15
16
|
import {
|
|
16
17
|
type MpcSignerOptions,
|
|
17
18
|
type PortalMobileMpc,
|
|
18
19
|
PortalMobileMpcMetadata,
|
|
20
|
+
type PresignatureSource,
|
|
19
21
|
type SigningResponse,
|
|
20
22
|
} from '../../types'
|
|
21
23
|
import Signer from './abstract'
|
|
@@ -32,6 +34,7 @@ class MpcSigner implements Signer {
|
|
|
32
34
|
private mpcHost: string
|
|
33
35
|
private version = 'v6'
|
|
34
36
|
private portalApi?: HttpRequester
|
|
37
|
+
private presignatureSource?: PresignatureSource
|
|
35
38
|
|
|
36
39
|
constructor({
|
|
37
40
|
keychain,
|
|
@@ -39,6 +42,7 @@ class MpcSigner implements Signer {
|
|
|
39
42
|
version = 'v6',
|
|
40
43
|
portalApi,
|
|
41
44
|
featureFlags = {},
|
|
45
|
+
presignatureSource,
|
|
42
46
|
}: MpcSignerOptions) {
|
|
43
47
|
this.featureFlags = featureFlags
|
|
44
48
|
this.keychain = keychain
|
|
@@ -46,6 +50,7 @@ class MpcSigner implements Signer {
|
|
|
46
50
|
this.mpcHost = mpcHost
|
|
47
51
|
this.version = version
|
|
48
52
|
this.portalApi = portalApi
|
|
53
|
+
this.presignatureSource = presignatureSource
|
|
49
54
|
|
|
50
55
|
if (!this.mpc) {
|
|
51
56
|
throw new Error(
|
|
@@ -63,9 +68,11 @@ class MpcSigner implements Signer {
|
|
|
63
68
|
this.featureFlags.enableSdkPerformanceMetrics === true
|
|
64
69
|
const signStartTime = performance.now()
|
|
65
70
|
const preOperationStartTime = performance.now()
|
|
66
|
-
//
|
|
67
|
-
|
|
68
|
-
|
|
71
|
+
// Use traceId from message if available (e.g. options.traceId), otherwise generate a new UUID
|
|
72
|
+
const traceId = message.traceId ?? generateTraceId()
|
|
73
|
+
sdkLogger.info(
|
|
74
|
+
`[Portal MPC] sign started | method=${message.method} | traceId=${traceId} | chainId=${message.chainId} | curve=${message.curve}`,
|
|
75
|
+
)
|
|
69
76
|
const metrics: Record<string, number | string | boolean> = {
|
|
70
77
|
hasError: false,
|
|
71
78
|
operation: Operation.SIGN,
|
|
@@ -149,6 +156,39 @@ class MpcSigner implements Signer {
|
|
|
149
156
|
)
|
|
150
157
|
}
|
|
151
158
|
|
|
159
|
+
// Internal presignature handling (gated by feature flag): when usePresignatures is true and a
|
|
160
|
+
// presignature is available for SECP256K1, use signWithPresignature; otherwise normal sign().
|
|
161
|
+
if (
|
|
162
|
+
curve === PortalCurve.SECP256K1 &&
|
|
163
|
+
this.featureFlags.usePresignatures === true &&
|
|
164
|
+
this.presignatureSource
|
|
165
|
+
) {
|
|
166
|
+
const presignature = await this.presignatureSource.consumePresignature(
|
|
167
|
+
PortalCurve.SECP256K1,
|
|
168
|
+
)
|
|
169
|
+
if (presignature?.data) {
|
|
170
|
+
sdkLogger.debug(
|
|
171
|
+
'[Portal] Signing with presignature (portal.request/sendAsset path)',
|
|
172
|
+
{ method: message.method, chainId: message.chainId },
|
|
173
|
+
)
|
|
174
|
+
try {
|
|
175
|
+
return await this.signWithPresignature(
|
|
176
|
+
{
|
|
177
|
+
...message,
|
|
178
|
+
presignatureData: presignature.data,
|
|
179
|
+
traceId,
|
|
180
|
+
},
|
|
181
|
+
provider,
|
|
182
|
+
)
|
|
183
|
+
} catch (presignError) {
|
|
184
|
+
sdkLogger.warn(
|
|
185
|
+
'[Portal.Provider.MpcSigner] signWithPresignature failed, falling back to normal sign:',
|
|
186
|
+
presignError,
|
|
187
|
+
)
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
152
192
|
// Record pre-operation time
|
|
153
193
|
metrics.sdkPreOperationMs = performance.now() - preOperationStartTime
|
|
154
194
|
|
|
@@ -283,6 +323,188 @@ class MpcSigner implements Signer {
|
|
|
283
323
|
}
|
|
284
324
|
return params
|
|
285
325
|
}
|
|
326
|
+
|
|
327
|
+
/**
|
|
328
|
+
* Sign a transaction using a pre-generated presignature
|
|
329
|
+
* NOTE: Only supports SECP256K1 curve
|
|
330
|
+
*
|
|
331
|
+
* @param message - Signing request with presignatureData
|
|
332
|
+
* @param provider - Portal provider instance
|
|
333
|
+
* @returns Signed transaction hash
|
|
334
|
+
*/
|
|
335
|
+
public async signWithPresignature(
|
|
336
|
+
message: SigningRequestArguments & { presignatureData: string },
|
|
337
|
+
provider: IPortalProvider,
|
|
338
|
+
): Promise<string> {
|
|
339
|
+
const {
|
|
340
|
+
method,
|
|
341
|
+
chainId,
|
|
342
|
+
isRaw = false,
|
|
343
|
+
curve,
|
|
344
|
+
sponsorGas,
|
|
345
|
+
signatureApprovalMemo,
|
|
346
|
+
presignatureData,
|
|
347
|
+
} = message
|
|
348
|
+
|
|
349
|
+
const targetCurve = curve || PortalCurve.SECP256K1
|
|
350
|
+
if (targetCurve !== PortalCurve.SECP256K1) {
|
|
351
|
+
throw new Error(
|
|
352
|
+
'[Portal.Provider.MpcSigner] Presignatures are only supported for SECP256K1 curve',
|
|
353
|
+
)
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
const shouldSendMetrics = this.featureFlags.enableSdkPerformanceMetrics
|
|
357
|
+
const traceId = message.traceId ?? generateTraceId()
|
|
358
|
+
const metrics: Record<string, number | string | boolean> = {
|
|
359
|
+
hasError: false,
|
|
360
|
+
operation: 'signWithPresignature',
|
|
361
|
+
signingMethod: method,
|
|
362
|
+
traceId,
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
if (chainId) {
|
|
366
|
+
metrics.chainId = chainId
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
const requestStartTime = performance.now()
|
|
370
|
+
|
|
371
|
+
try {
|
|
372
|
+
const preOperationStartTime = performance.now()
|
|
373
|
+
|
|
374
|
+
const apiKey = provider.apiKey
|
|
375
|
+
if (!apiKey) {
|
|
376
|
+
throw new Error('[Portal.Provider.MpcSigner] The API key is missing.')
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
const shares = await this.keychain.getShares()
|
|
380
|
+
const signingShare = shares.secp256k1.share
|
|
381
|
+
|
|
382
|
+
if (!signingShare) {
|
|
383
|
+
throw new Error(
|
|
384
|
+
'[Portal.Provider.MpcSigner] The SECP256K1 share is missing from the keychain.',
|
|
385
|
+
)
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
const metadata: PortalMobileMpcMetadata = {
|
|
389
|
+
clientPlatform: 'REACT_NATIVE',
|
|
390
|
+
clientPlatformVersion: getClientPlatformVersion(),
|
|
391
|
+
isMultiBackupEnabled: this.featureFlags.isMultiBackupEnabled,
|
|
392
|
+
mpcServerVersion: this.version,
|
|
393
|
+
optimized: true,
|
|
394
|
+
curve: PortalCurve.SECP256K1,
|
|
395
|
+
chainId,
|
|
396
|
+
isRaw,
|
|
397
|
+
reqId: traceId,
|
|
398
|
+
connectionTracingEnabled: shouldSendMetrics,
|
|
399
|
+
sponsorGas,
|
|
400
|
+
signatureApprovalMemo,
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
const stringifiedMetadata = JSON.stringify(metadata)
|
|
404
|
+
|
|
405
|
+
let formattedParams: string
|
|
406
|
+
let rpcUrl: string
|
|
407
|
+
|
|
408
|
+
if (isRaw) {
|
|
409
|
+
formattedParams = this.buildParams(method, message.params)
|
|
410
|
+
rpcUrl = ''
|
|
411
|
+
metrics.operation = 'raw_signWithPresignature'
|
|
412
|
+
} else {
|
|
413
|
+
formattedParams = JSON.stringify(
|
|
414
|
+
this.buildParams(method, message.params),
|
|
415
|
+
)
|
|
416
|
+
rpcUrl = provider.getGatewayUrl(chainId)
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
if (typeof formattedParams !== 'string') {
|
|
420
|
+
throw new Error(
|
|
421
|
+
`[Portal.Provider.MpcSigner] The formatted params for the signing request could not be converted to a string. The params were: ${formattedParams}`,
|
|
422
|
+
)
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
metrics.sdkPreOperationMs = performance.now() - preOperationStartTime
|
|
426
|
+
|
|
427
|
+
const mpcSignStartTime = performance.now()
|
|
428
|
+
|
|
429
|
+
const result = await this.mpc.signWithPresignature(
|
|
430
|
+
apiKey,
|
|
431
|
+
this.mpcHost,
|
|
432
|
+
JSON.stringify(signingShare),
|
|
433
|
+
presignatureData,
|
|
434
|
+
method,
|
|
435
|
+
formattedParams,
|
|
436
|
+
rpcUrl,
|
|
437
|
+
chainId,
|
|
438
|
+
stringifiedMetadata,
|
|
439
|
+
)
|
|
440
|
+
|
|
441
|
+
const postOperationStartTime = performance.now()
|
|
442
|
+
|
|
443
|
+
metrics.mpcNativeCallMs = performance.now() - mpcSignStartTime
|
|
444
|
+
|
|
445
|
+
const parsedResponse = JSON.parse(String(result)) as SigningResponse
|
|
446
|
+
const { data, error, meta } = parsedResponse
|
|
447
|
+
|
|
448
|
+
if (meta?.metrics) {
|
|
449
|
+
const binaryMetrics = meta.metrics
|
|
450
|
+
if (binaryMetrics.wsConnectDurationMs) {
|
|
451
|
+
metrics.sdkBinaryWSConnectMs = binaryMetrics.wsConnectDurationMs
|
|
452
|
+
}
|
|
453
|
+
if (binaryMetrics.operationDurationMs) {
|
|
454
|
+
metrics.sdkBinaryOperationMs = binaryMetrics.operationDurationMs
|
|
455
|
+
}
|
|
456
|
+
if (binaryMetrics.tlsHandshakeMs) {
|
|
457
|
+
metrics.sdkBinaryTlsHandshakeMs = binaryMetrics.tlsHandshakeMs
|
|
458
|
+
}
|
|
459
|
+
if (binaryMetrics.firstResponseMs) {
|
|
460
|
+
metrics.sdkBinaryFirstResponseMs = binaryMetrics.firstResponseMs
|
|
461
|
+
}
|
|
462
|
+
if (binaryMetrics.dnsLookupMs) {
|
|
463
|
+
metrics.sdkBinaryDnsLookupMs = binaryMetrics.dnsLookupMs
|
|
464
|
+
}
|
|
465
|
+
if (binaryMetrics.connectMs) {
|
|
466
|
+
metrics.sdkBinaryConnectMs = binaryMetrics.connectMs
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
if (error?.id) {
|
|
471
|
+
throw new PortalMpcError(error)
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
metrics.sdkPostOperationMs = performance.now() - postOperationStartTime
|
|
475
|
+
|
|
476
|
+
metrics.sdkOperationMs = performance.now() - requestStartTime
|
|
477
|
+
|
|
478
|
+
if (shouldSendMetrics && this.portalApi) {
|
|
479
|
+
try {
|
|
480
|
+
await this.sendMetrics(metrics, apiKey)
|
|
481
|
+
} catch (error) {
|
|
482
|
+
// No-op
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
return data
|
|
487
|
+
} catch (error) {
|
|
488
|
+
sdkLogger.error(
|
|
489
|
+
'[Portal.Provider.MpcSigner] signWithPresignature error:',
|
|
490
|
+
error,
|
|
491
|
+
)
|
|
492
|
+
|
|
493
|
+
metrics.sdkOperationMs = performance.now() - requestStartTime
|
|
494
|
+
metrics.hasError = true
|
|
495
|
+
|
|
496
|
+
if (shouldSendMetrics && this.portalApi) {
|
|
497
|
+
const apiKey = provider.apiKey
|
|
498
|
+
try {
|
|
499
|
+
await this.sendMetrics(metrics, apiKey)
|
|
500
|
+
} catch (metricsError) {
|
|
501
|
+
// No-op
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
throw error
|
|
506
|
+
}
|
|
507
|
+
}
|
|
286
508
|
}
|
|
287
509
|
|
|
288
510
|
export default MpcSigner
|
package/types.d.ts
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
// Types
|
|
2
2
|
import PortalConnect from '@portal-hq/connect'
|
|
3
|
+
import type { PortalCurve } from '@portal-hq/core'
|
|
3
4
|
import {
|
|
5
|
+
HttpRequester,
|
|
4
6
|
KeychainAdapter,
|
|
5
7
|
type PortalError,
|
|
6
|
-
HttpRequester,
|
|
7
8
|
} from '@portal-hq/utils'
|
|
8
9
|
|
|
9
10
|
export type EventHandler = (data: any) => void
|
|
@@ -18,6 +19,11 @@ export interface GatewayConfig {
|
|
|
18
19
|
[key: string]: string
|
|
19
20
|
}
|
|
20
21
|
|
|
22
|
+
/** Internal SDK only: used by MpcSigner to consume a presignature when signing (SECP256K1). Not exposed to consumers. */
|
|
23
|
+
export interface PresignatureSource {
|
|
24
|
+
consumePresignature(curve: PortalCurve): Promise<{ data: string } | null>
|
|
25
|
+
}
|
|
26
|
+
|
|
21
27
|
export interface MpcSignerOptions extends SignerOptions {
|
|
22
28
|
keychain: KeychainAdapter
|
|
23
29
|
|
|
@@ -28,6 +34,8 @@ export interface MpcSignerOptions extends SignerOptions {
|
|
|
28
34
|
enclaveMPCHost?: string
|
|
29
35
|
version?: string
|
|
30
36
|
featureFlags?: FeatureFlags
|
|
37
|
+
/** Internal: presignature source for SECP256K1 signing (portal.request() uses this automatically). */
|
|
38
|
+
presignatureSource?: PresignatureSource
|
|
31
39
|
}
|
|
32
40
|
|
|
33
41
|
export interface PortalMobileMpcMetadata {
|
|
@@ -47,9 +55,11 @@ export interface PortalMobileMpcMetadata {
|
|
|
47
55
|
optimized: true
|
|
48
56
|
isRaw?: boolean
|
|
49
57
|
reqId?: string
|
|
58
|
+
traceId?: string
|
|
50
59
|
connectionTracingEnabled?: boolean
|
|
51
60
|
sponsorGas?: boolean
|
|
52
61
|
signatureApprovalMemo?: string
|
|
62
|
+
presignatureExpirationTs?: number
|
|
53
63
|
}
|
|
54
64
|
|
|
55
65
|
export interface PortalMobileMpc {
|
|
@@ -75,6 +85,17 @@ export interface PortalMobileMpc {
|
|
|
75
85
|
chainId: string,
|
|
76
86
|
metadata: string,
|
|
77
87
|
) => Promise<string>
|
|
88
|
+
signWithPresignature: (
|
|
89
|
+
clientApiKey: string,
|
|
90
|
+
mpcApiUrl: string,
|
|
91
|
+
dkgResult: string,
|
|
92
|
+
presignatureData: string,
|
|
93
|
+
method: string,
|
|
94
|
+
params: string,
|
|
95
|
+
rpcUrl: string,
|
|
96
|
+
chainId: string,
|
|
97
|
+
metadata: string,
|
|
98
|
+
) => Promise<string>
|
|
78
99
|
}
|
|
79
100
|
|
|
80
101
|
export interface ProviderOptions {
|
|
@@ -92,6 +113,8 @@ export interface ProviderOptions {
|
|
|
92
113
|
enclaveMPCHost?: string
|
|
93
114
|
version?: string
|
|
94
115
|
featureFlags?: FeatureFlags
|
|
116
|
+
/** Internal: presignature source for SECP256K1 signing (portal.request() uses this automatically). */
|
|
117
|
+
presignatureSource?: PresignatureSource
|
|
95
118
|
}
|
|
96
119
|
|
|
97
120
|
export interface RegisteredEventHandler {
|
|
@@ -132,6 +155,7 @@ export interface ProviderOptions {
|
|
|
132
155
|
version?: string
|
|
133
156
|
chainId?: string
|
|
134
157
|
featureFlags?: FeatureFlags
|
|
158
|
+
presignatureSource?: PresignatureSource
|
|
135
159
|
}
|
|
136
160
|
|
|
137
161
|
export interface SignerOptions {}
|