@portal-hq/web 3.6.2-alpha → 3.7.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/hypernative.d.ts +346 -0
- package/lib/commonjs/index.js +144 -2
- package/lib/commonjs/index.test.js +119 -2
- package/lib/commonjs/integrations/security/hypernative/index.js +101 -0
- package/lib/commonjs/integrations/security/hypernative/index.test.js +151 -0
- package/lib/commonjs/integrations/security/index.js +16 -0
- package/lib/commonjs/integrations/trading/zero-x/index.js +17 -4
- package/lib/commonjs/integrations/trading/zero-x/index.test.js +61 -15
- package/lib/commonjs/mpc/index.js +156 -5
- package/lib/commonjs/mpc/index.test.js +794 -5
- package/lib/commonjs/passkeys/index.js +394 -0
- package/lib/commonjs/passkeys/types.js +2 -0
- package/lib/commonjs/provider/index.js +5 -2
- package/lib/esm/index.js +144 -2
- package/lib/esm/index.test.js +119 -2
- package/lib/esm/integrations/security/hypernative/index.js +98 -0
- package/lib/esm/integrations/security/hypernative/index.test.js +146 -0
- package/lib/esm/integrations/security/index.js +10 -0
- package/lib/esm/integrations/trading/zero-x/index.js +17 -4
- package/lib/esm/integrations/trading/zero-x/index.test.js +62 -16
- package/lib/esm/mpc/index.js +156 -5
- package/lib/esm/mpc/index.test.js +795 -6
- package/lib/esm/passkeys/index.js +390 -0
- package/lib/esm/passkeys/types.js +1 -0
- package/lib/esm/provider/index.js +5 -2
- package/lifi-types.d.ts +1236 -0
- package/package.json +6 -3
- package/src/__mocks/constants.ts +422 -5
- package/src/__mocks/portal/mpc.ts +1 -0
- package/src/index.test.ts +179 -3
- package/src/index.ts +212 -4
- package/src/integrations/security/hypernative/index.test.ts +196 -0
- package/src/integrations/security/hypernative/index.ts +106 -0
- package/src/integrations/security/index.ts +14 -0
- package/src/integrations/trading/zero-x/index.test.ts +98 -19
- package/src/integrations/trading/zero-x/index.ts +29 -9
- package/src/mpc/index.test.ts +944 -7
- package/src/mpc/index.ts +200 -10
- package/src/passkeys/index.ts +536 -0
- package/src/passkeys/types.ts +78 -0
- package/src/provider/index.ts +5 -0
- package/tsconfig.json +7 -1
- package/types.d.ts +45 -12
- package/yieldxyz-types.d.ts +778 -0
- package/zero-x.d.ts +204 -0
package/hypernative.d.ts
ADDED
|
@@ -0,0 +1,346 @@
|
|
|
1
|
+
export type ScreenAddressRequestOptions = {
|
|
2
|
+
screenerPolicyId?: string
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
export interface ScreenAddressApiResponse {
|
|
6
|
+
data: {
|
|
7
|
+
rawResponse: ScreenAddressResponse[]
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface ScanEVMRequest {
|
|
12
|
+
transaction: TransactionObject
|
|
13
|
+
url?: string
|
|
14
|
+
blockNumber?: number
|
|
15
|
+
validateNonce?: boolean
|
|
16
|
+
showFullFindings?: boolean
|
|
17
|
+
policy?: string
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface ScanEVMResponse {
|
|
21
|
+
data: {
|
|
22
|
+
rawResponse: {
|
|
23
|
+
success: boolean
|
|
24
|
+
data: TransactionRiskData
|
|
25
|
+
error: string | null
|
|
26
|
+
version?: string
|
|
27
|
+
service?: string
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface ScanEip712Request {
|
|
33
|
+
walletAddress: string
|
|
34
|
+
chainId: string
|
|
35
|
+
eip712Message: Eip712TypedData
|
|
36
|
+
showFullFindings?: boolean
|
|
37
|
+
policy?: string
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export interface ScanEip712Response {
|
|
41
|
+
data: {
|
|
42
|
+
rawResponse: {
|
|
43
|
+
success: boolean
|
|
44
|
+
data: TypedMessageRiskData
|
|
45
|
+
error: string | null
|
|
46
|
+
version?: string
|
|
47
|
+
service?: string
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export interface ScanSolanaRequest {
|
|
53
|
+
transaction: SolanaTransaction
|
|
54
|
+
url?: string
|
|
55
|
+
validateRecentBlockHash?: boolean
|
|
56
|
+
showFullFindings?: boolean
|
|
57
|
+
policy?: string
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export interface ScanSolanaResponse {
|
|
61
|
+
data: {
|
|
62
|
+
rawResponse: {
|
|
63
|
+
success: boolean
|
|
64
|
+
data: SolanaTransactionRiskData
|
|
65
|
+
error: string | null
|
|
66
|
+
version?: string
|
|
67
|
+
service?: string
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export type ScanNftsRequest = ScanNftRequestItem[]
|
|
73
|
+
|
|
74
|
+
export interface ScanNftsResponse {
|
|
75
|
+
data: {
|
|
76
|
+
rawResponse: {
|
|
77
|
+
success: boolean
|
|
78
|
+
data: {
|
|
79
|
+
nfts: ScanNftResponseItem[]
|
|
80
|
+
}
|
|
81
|
+
error: string | null
|
|
82
|
+
version?: string
|
|
83
|
+
service?: string
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export type ScanTokensRequest = ScanTokenRequestItem[]
|
|
89
|
+
|
|
90
|
+
export interface ScanTokensResponse {
|
|
91
|
+
data: {
|
|
92
|
+
rawResponse: {
|
|
93
|
+
success: boolean
|
|
94
|
+
data: {
|
|
95
|
+
tokens: ScanTokenResponseItem[]
|
|
96
|
+
}
|
|
97
|
+
error: string | null
|
|
98
|
+
version?: string
|
|
99
|
+
service?: string
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export type ScanUrlRequest = string
|
|
105
|
+
|
|
106
|
+
export interface ScanUrlResponse {
|
|
107
|
+
data: {
|
|
108
|
+
rawResponse: {
|
|
109
|
+
success: boolean
|
|
110
|
+
data: {
|
|
111
|
+
isMalicious: boolean
|
|
112
|
+
deepScanTriggered?: boolean
|
|
113
|
+
}
|
|
114
|
+
error: string | null
|
|
115
|
+
version?: string
|
|
116
|
+
service?: string
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export interface ScreenAddressResponse {
|
|
122
|
+
address: string
|
|
123
|
+
recommendation: string
|
|
124
|
+
severity: string
|
|
125
|
+
totalIncomingUsd: number
|
|
126
|
+
policyId: string
|
|
127
|
+
timestamp: string
|
|
128
|
+
flags: Flag[]
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export interface Flag {
|
|
132
|
+
title: string
|
|
133
|
+
flagId: string
|
|
134
|
+
chain: string
|
|
135
|
+
severity: string
|
|
136
|
+
lastUpdate?: string
|
|
137
|
+
events: HypernativeEvent[]
|
|
138
|
+
exposures: Exposure[]
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export interface HypernativeEvent {
|
|
142
|
+
eventId: string
|
|
143
|
+
address: string
|
|
144
|
+
chain: string
|
|
145
|
+
flagId: string
|
|
146
|
+
timestampEvent: string
|
|
147
|
+
txHash: string
|
|
148
|
+
direction: string
|
|
149
|
+
hop: number
|
|
150
|
+
counterpartyAddress: string
|
|
151
|
+
counterpartyAlias?: string
|
|
152
|
+
counterpartyFlagId: string
|
|
153
|
+
tokenSymbol: string
|
|
154
|
+
tokenAmount: number
|
|
155
|
+
tokenUsdValue: number
|
|
156
|
+
reason: string
|
|
157
|
+
source: string
|
|
158
|
+
originalFlaggedAddress: string
|
|
159
|
+
originalFlaggedAlias?: string
|
|
160
|
+
originalFlaggedChain: string
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
export interface Exposure {
|
|
164
|
+
exposurePortion: number
|
|
165
|
+
exposureType?: string
|
|
166
|
+
totalExposureUsd: number
|
|
167
|
+
flaggedInteractions: FlaggedInteraction[]
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
export interface FlaggedInteraction {
|
|
171
|
+
address: string
|
|
172
|
+
chain: string
|
|
173
|
+
alias?: string
|
|
174
|
+
minHop: number
|
|
175
|
+
totalExposureUsd: number
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
export interface TransactionRiskData {
|
|
179
|
+
assessmentId?: string
|
|
180
|
+
assessmentTimestamp?: string
|
|
181
|
+
recommendation: 'accept' | 'notes' | 'warn' | 'deny' | 'autoAccept'
|
|
182
|
+
expectedStatus?: 'success' | 'fail'
|
|
183
|
+
findings?: Finding[]
|
|
184
|
+
involvedAssets?: Asset[]
|
|
185
|
+
balanceChanges?: Record<string, BalanceChange[]> | null
|
|
186
|
+
parsedActions?: Record<string, unknown[]>
|
|
187
|
+
blockNumber?: number
|
|
188
|
+
riIds?: string[]
|
|
189
|
+
signature?: string
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
export interface TypedMessageRiskData {
|
|
193
|
+
assessmentId?: string
|
|
194
|
+
assessmentTimestamp?: string
|
|
195
|
+
blockNumber?: number
|
|
196
|
+
recommendation: 'accept' | 'notes' | 'warn' | 'deny' | 'autoAccept'
|
|
197
|
+
findings?: Finding[]
|
|
198
|
+
involvedAssets?: Asset[]
|
|
199
|
+
parsedActions?: Record<string, unknown[]>
|
|
200
|
+
riIds?: string[]
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
export interface SolanaTransactionRiskData {
|
|
204
|
+
recommendation: 'accept' | 'notes' | 'warn' | 'deny' | 'autoAccept'
|
|
205
|
+
expectedStatus?: 'success' | 'fail'
|
|
206
|
+
findings?: Finding[]
|
|
207
|
+
involvedAssets?: Asset[]
|
|
208
|
+
balanceChanges?: Record<string, BalanceChange[]> | null
|
|
209
|
+
parsedActions?: Record<string, unknown[]>
|
|
210
|
+
blockNumber?: number
|
|
211
|
+
riIds?: string[]
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
export interface Finding {
|
|
215
|
+
typeId: string
|
|
216
|
+
title: string
|
|
217
|
+
description: string
|
|
218
|
+
details?: string
|
|
219
|
+
severity: 'Accept' | 'Notes' | 'Warn' | 'Deny' | 'AutoAccept'
|
|
220
|
+
relatedAssets?: Asset[]
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
export interface Asset {
|
|
224
|
+
chain: string
|
|
225
|
+
evmChainId?: string
|
|
226
|
+
address: string
|
|
227
|
+
type: 'Wallet' | 'Contract'
|
|
228
|
+
involvementTypes: string[]
|
|
229
|
+
tag: string
|
|
230
|
+
alias?: string
|
|
231
|
+
note?: string
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
export interface BalanceChange {
|
|
235
|
+
changeType: 'send' | 'receive'
|
|
236
|
+
tokenSymbol: string
|
|
237
|
+
tokenAddress?: string
|
|
238
|
+
usdValue?: string
|
|
239
|
+
amount: string
|
|
240
|
+
chain: string
|
|
241
|
+
evmChainId?: string
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
export interface ScanNftRequestItem {
|
|
245
|
+
address: string
|
|
246
|
+
chain?: string
|
|
247
|
+
evmChainId?: number | string
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
export interface ScanNftResponseItem {
|
|
251
|
+
address: string
|
|
252
|
+
chain: string
|
|
253
|
+
evmChainId: string
|
|
254
|
+
accept: boolean
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
export interface ScanTokenRequestItem {
|
|
258
|
+
address: string
|
|
259
|
+
chain?: string
|
|
260
|
+
evmChainId?: number | string
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
export interface ScanTokenResponseItem {
|
|
264
|
+
address: string
|
|
265
|
+
chain: string
|
|
266
|
+
reputation?: {
|
|
267
|
+
recommendation: 'accept' | 'deny'
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
export interface TransactionObject {
|
|
272
|
+
chain: string
|
|
273
|
+
fromAddress: string
|
|
274
|
+
toAddress: string
|
|
275
|
+
input?: string
|
|
276
|
+
value?: string | number
|
|
277
|
+
nonce?: string | number
|
|
278
|
+
hash?: string
|
|
279
|
+
gas?: string | number
|
|
280
|
+
gasPrice?: string | number
|
|
281
|
+
maxPriorityFeePerGas?: string | number
|
|
282
|
+
maxFeePerGas?: string | number
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
export interface Eip712TypeProperty {
|
|
286
|
+
name: string
|
|
287
|
+
type: string
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
export type Eip712Value =
|
|
291
|
+
| string
|
|
292
|
+
| number
|
|
293
|
+
| boolean
|
|
294
|
+
| null
|
|
295
|
+
| undefined
|
|
296
|
+
| Eip712Value[]
|
|
297
|
+
| { [key: string]: Eip712Value }
|
|
298
|
+
|
|
299
|
+
export interface Eip712Domain {
|
|
300
|
+
name?: string
|
|
301
|
+
version?: string
|
|
302
|
+
chainId?: number | string
|
|
303
|
+
verifyingContract?: string
|
|
304
|
+
salt?: string
|
|
305
|
+
[key: string]: Eip712Value
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
export interface Eip712TypedData {
|
|
309
|
+
primaryType: string
|
|
310
|
+
types: Record<string, Eip712TypeProperty[]>
|
|
311
|
+
domain: Eip712Domain
|
|
312
|
+
message: Record<string, Eip712Value>
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
export interface SolanaHeader {
|
|
316
|
+
numRequiredSignatures?: number
|
|
317
|
+
numReadonlySignedAccounts?: number
|
|
318
|
+
numReadonlyUnsignedAccounts?: number
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
export interface SolanaInstruction {
|
|
322
|
+
accounts: number[]
|
|
323
|
+
data: string
|
|
324
|
+
programIdIndex: number
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
export interface AddressTableLookup {
|
|
328
|
+
accountKey: string
|
|
329
|
+
writableIndexes: number[]
|
|
330
|
+
readonlyIndexes: number[]
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
export interface SolanaMessage {
|
|
334
|
+
accountKeys: string[]
|
|
335
|
+
header: SolanaHeader
|
|
336
|
+
instructions: SolanaInstruction[]
|
|
337
|
+
addressTableLookups?: AddressTableLookup[]
|
|
338
|
+
recentBlockhash: string
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
export interface SolanaTransaction {
|
|
342
|
+
message?: SolanaMessage
|
|
343
|
+
signatures?: string[]
|
|
344
|
+
rawTransaction?: string
|
|
345
|
+
version?: 'legacy' | '0'
|
|
346
|
+
}
|
package/lib/commonjs/index.js
CHANGED
|
@@ -41,6 +41,8 @@ const mpc_1 = __importDefault(require("./mpc"));
|
|
|
41
41
|
const provider_1 = __importStar(require("./provider"));
|
|
42
42
|
const yield_1 = __importDefault(require("./integrations/yield"));
|
|
43
43
|
const trading_1 = __importDefault(require("./integrations/trading"));
|
|
44
|
+
const security_1 = __importDefault(require("./integrations/security"));
|
|
45
|
+
const passkeys_1 = __importDefault(require("./passkeys"));
|
|
44
46
|
class Portal {
|
|
45
47
|
get ready() {
|
|
46
48
|
return this.mpc.ready;
|
|
@@ -85,6 +87,7 @@ class Portal {
|
|
|
85
87
|
});
|
|
86
88
|
this.yield = new yield_1.default({ mpc: this.mpc });
|
|
87
89
|
this.trading = new trading_1.default({ mpc: this.mpc });
|
|
90
|
+
this.security = new security_1.default({ mpc: this.mpc });
|
|
88
91
|
this.provider = new provider_1.default({
|
|
89
92
|
portal: this,
|
|
90
93
|
chainId: chainId ? Number(chainId) : undefined,
|
|
@@ -148,6 +151,98 @@ class Portal {
|
|
|
148
151
|
return address;
|
|
149
152
|
});
|
|
150
153
|
}
|
|
154
|
+
generateBackupShare(progress = () => {
|
|
155
|
+
// Noop
|
|
156
|
+
}) {
|
|
157
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
158
|
+
const response = yield this.mpc.backup({
|
|
159
|
+
backupMethod: BackupMethods.custom,
|
|
160
|
+
backupConfigs: {},
|
|
161
|
+
host: this.host,
|
|
162
|
+
mpcVersion: this.mpcVersion,
|
|
163
|
+
featureFlags: this.featureFlags,
|
|
164
|
+
}, progress);
|
|
165
|
+
if (!response.encryptionKey) {
|
|
166
|
+
throw new Error('[Portal] Custom backup did not return an encryption key. Please ensure you are using the latest iframe bundle.');
|
|
167
|
+
}
|
|
168
|
+
return {
|
|
169
|
+
cipherText: response.cipherText,
|
|
170
|
+
encryptionKey: response.encryptionKey,
|
|
171
|
+
};
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
registerPasskeyAndStoreEncryptionKey(cipherText, encryptionKey, options = {}) {
|
|
175
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
176
|
+
const { service, customDomain, relyingPartyId, relyingPartyName } = this.resolvePasskeyOptions(options);
|
|
177
|
+
yield service.registerPasskeyAndStoreKey({
|
|
178
|
+
customDomain,
|
|
179
|
+
relyingPartyName,
|
|
180
|
+
encryptionKey,
|
|
181
|
+
relyingPartyId,
|
|
182
|
+
cipherText,
|
|
183
|
+
});
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
authenticatePasskeyAndRetrieveKey(options = {}) {
|
|
187
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
188
|
+
const { service, customDomain, relyingPartyId, relyingPartyName } = this.resolvePasskeyOptions(options);
|
|
189
|
+
return yield service.authenticatePasskeyAndRetrieveKey({
|
|
190
|
+
customDomain,
|
|
191
|
+
relyingPartyName,
|
|
192
|
+
relyingPartyId,
|
|
193
|
+
});
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* Register a passkey without tying it to an encryption key.
|
|
198
|
+
* The encryption key can be stored later using authenticatePasskeyAndWriteKey.
|
|
199
|
+
*/
|
|
200
|
+
registerPasskey(options = {}) {
|
|
201
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
202
|
+
const { service, customDomain, relyingPartyId, relyingPartyName } = this.resolvePasskeyOptions(options);
|
|
203
|
+
yield service.registerPasskey({
|
|
204
|
+
customDomain,
|
|
205
|
+
relyingPartyName,
|
|
206
|
+
relyingPartyId,
|
|
207
|
+
});
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* Authenticate with passkey and store an encryption key.
|
|
212
|
+
* Used after registerPasskey to associate an encryption key with the passkey.
|
|
213
|
+
*/
|
|
214
|
+
authenticatePasskeyAndWriteKey(encryptionKey, options = {}) {
|
|
215
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
216
|
+
const { service, customDomain, relyingPartyId, relyingPartyName } = this.resolvePasskeyOptions(options);
|
|
217
|
+
yield service.authenticatePasskeyAndWriteKey({
|
|
218
|
+
customDomain,
|
|
219
|
+
relyingPartyName,
|
|
220
|
+
relyingPartyId,
|
|
221
|
+
encryptionKey,
|
|
222
|
+
});
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
backupWithPasskey(options = {}, progress = () => {
|
|
226
|
+
// Noop
|
|
227
|
+
}) {
|
|
228
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
229
|
+
const { usePopup = true, customDomain, relyingPartyName, backupMethod = BackupMethods.passkey, } = options;
|
|
230
|
+
if (usePopup) {
|
|
231
|
+
yield this.backupWallet(backupMethod, progress, {});
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
if (backupMethod !== BackupMethods.passkey) {
|
|
235
|
+
throw new Error(`[Portal] Direct passkey backup currently supports only BackupMethods.passkey (received ${backupMethod}).`);
|
|
236
|
+
}
|
|
237
|
+
const { cipherText, encryptionKey } = yield this.generateBackupShare(progress);
|
|
238
|
+
yield this.registerPasskeyAndStoreEncryptionKey(cipherText, encryptionKey, {
|
|
239
|
+
customDomain,
|
|
240
|
+
relyingPartyName,
|
|
241
|
+
usePopup,
|
|
242
|
+
});
|
|
243
|
+
yield this.storedClientBackupShare(true, BackupMethods.passkey);
|
|
244
|
+
});
|
|
245
|
+
}
|
|
151
246
|
backupWallet(backupMethod, progress = () => {
|
|
152
247
|
// Noop
|
|
153
248
|
}, backupConfigs = {}) {
|
|
@@ -361,6 +456,7 @@ class Portal {
|
|
|
361
456
|
chainId,
|
|
362
457
|
method: 'eth_sendTransaction',
|
|
363
458
|
params: [buildTxResponse.transaction],
|
|
459
|
+
sponsorGas: params.sponsorGas,
|
|
364
460
|
});
|
|
365
461
|
break;
|
|
366
462
|
}
|
|
@@ -369,6 +465,7 @@ class Portal {
|
|
|
369
465
|
chainId,
|
|
370
466
|
method: 'sol_signAndSendTransaction',
|
|
371
467
|
params: [buildTxResponse.transaction],
|
|
468
|
+
sponsorGas: params.sponsorGas,
|
|
372
469
|
});
|
|
373
470
|
break;
|
|
374
471
|
}
|
|
@@ -711,7 +808,7 @@ class Portal {
|
|
|
711
808
|
getQuote(apiKey, args, chainId) {
|
|
712
809
|
var _a;
|
|
713
810
|
return __awaiter(this, void 0, void 0, function* () {
|
|
714
|
-
return (_a = this.mpc) === null || _a === void 0 ? void 0 : _a.getQuote(
|
|
811
|
+
return (_a = this.mpc) === null || _a === void 0 ? void 0 : _a.getQuote(chainId, args, apiKey);
|
|
715
812
|
});
|
|
716
813
|
}
|
|
717
814
|
/**
|
|
@@ -720,7 +817,7 @@ class Portal {
|
|
|
720
817
|
getSources(apiKey, chainId) {
|
|
721
818
|
var _a;
|
|
722
819
|
return __awaiter(this, void 0, void 0, function* () {
|
|
723
|
-
return (_a = this.mpc) === null || _a === void 0 ? void 0 : _a.getSources(
|
|
820
|
+
return (_a = this.mpc) === null || _a === void 0 ? void 0 : _a.getSources(chainId, apiKey);
|
|
724
821
|
});
|
|
725
822
|
}
|
|
726
823
|
/*******************************
|
|
@@ -808,6 +905,50 @@ class Portal {
|
|
|
808
905
|
}
|
|
809
906
|
return chainId;
|
|
810
907
|
}
|
|
908
|
+
ensurePasskeyService() {
|
|
909
|
+
const defaultDomain = this.getDefaultPasskeyDomain();
|
|
910
|
+
if (!this.passkeyService ||
|
|
911
|
+
this.passkeyServiceDefaultDomain !== defaultDomain) {
|
|
912
|
+
this.passkeyService = new passkeys_1.default({
|
|
913
|
+
defaultDomain,
|
|
914
|
+
getJwt: () => this.mpc.getPasskeyJwt(),
|
|
915
|
+
});
|
|
916
|
+
this.passkeyServiceDefaultDomain = defaultDomain;
|
|
917
|
+
}
|
|
918
|
+
return this.passkeyService;
|
|
919
|
+
}
|
|
920
|
+
getDefaultPasskeyDomain() {
|
|
921
|
+
var _a, _b;
|
|
922
|
+
return (_b = (_a = this.passkeyConfig) === null || _a === void 0 ? void 0 : _a.webAuthnHost) !== null && _b !== void 0 ? _b : 'backup.web.portalhq.io';
|
|
923
|
+
}
|
|
924
|
+
extractRpId(domain) {
|
|
925
|
+
const targetDomain = domain !== null && domain !== void 0 ? domain : this.getDefaultPasskeyDomain();
|
|
926
|
+
try {
|
|
927
|
+
const normalized = targetDomain.startsWith('http')
|
|
928
|
+
? targetDomain
|
|
929
|
+
: `https://${targetDomain}`;
|
|
930
|
+
return new URL(normalized).hostname;
|
|
931
|
+
}
|
|
932
|
+
catch (_a) {
|
|
933
|
+
return targetDomain;
|
|
934
|
+
}
|
|
935
|
+
}
|
|
936
|
+
resolvePasskeyOptions(options) {
|
|
937
|
+
var _a, _b, _c, _d, _e;
|
|
938
|
+
if (options.usePopup) {
|
|
939
|
+
throw new Error('[Portal] This method does not support the popup flow. Use usePopup: false.');
|
|
940
|
+
}
|
|
941
|
+
const service = this.ensurePasskeyService();
|
|
942
|
+
const domain = (_a = options.customDomain) !== null && _a !== void 0 ? _a : this.getDefaultPasskeyDomain();
|
|
943
|
+
const relyingPartyId = (_b = options.relyingPartyId) !== null && _b !== void 0 ? _b : this.extractRpId(domain);
|
|
944
|
+
const relyingPartyName = (_e = (_c = options.relyingPartyName) !== null && _c !== void 0 ? _c : (_d = this.passkeyConfig) === null || _d === void 0 ? void 0 : _d.relyingParty) !== null && _e !== void 0 ? _e : 'Portal';
|
|
945
|
+
return {
|
|
946
|
+
service,
|
|
947
|
+
customDomain: options.customDomain,
|
|
948
|
+
relyingPartyId,
|
|
949
|
+
relyingPartyName,
|
|
950
|
+
};
|
|
951
|
+
}
|
|
811
952
|
}
|
|
812
953
|
var mpc_2 = require("./mpc");
|
|
813
954
|
Object.defineProperty(exports, "MpcError", { enumerable: true, get: function () { return mpc_2.MpcError; } });
|
|
@@ -833,6 +974,7 @@ var BackupMethods;
|
|
|
833
974
|
BackupMethods["gdrive"] = "GDRIVE";
|
|
834
975
|
BackupMethods["password"] = "PASSWORD";
|
|
835
976
|
BackupMethods["passkey"] = "PASSKEY";
|
|
977
|
+
BackupMethods["custom"] = "CUSTOM";
|
|
836
978
|
BackupMethods["unknown"] = "UNKNOWN";
|
|
837
979
|
})(BackupMethods = exports.BackupMethods || (exports.BackupMethods = {}));
|
|
838
980
|
var GetTransactionsOrder;
|
|
@@ -144,6 +144,123 @@ describe('Portal', () => {
|
|
|
144
144
|
}, mockProgressFn);
|
|
145
145
|
}));
|
|
146
146
|
});
|
|
147
|
+
describe('generateBackupShare', () => {
|
|
148
|
+
it('should request a custom backup and return cipherText and encryptionKey', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
149
|
+
const storageCallback = jest.fn().mockResolvedValue(undefined);
|
|
150
|
+
portal.mpc.backup.mockResolvedValueOnce({
|
|
151
|
+
cipherText: constants_1.mockCipherText,
|
|
152
|
+
encryptionKey: 'manual-key',
|
|
153
|
+
storageCallback,
|
|
154
|
+
});
|
|
155
|
+
const result = yield portal.generateBackupShare();
|
|
156
|
+
expect(result).toEqual({
|
|
157
|
+
cipherText: constants_1.mockCipherText,
|
|
158
|
+
encryptionKey: 'manual-key',
|
|
159
|
+
});
|
|
160
|
+
expect(portal.mpc.backup).toHaveBeenCalledWith({
|
|
161
|
+
backupMethod: _1.BackupMethods.custom,
|
|
162
|
+
backupConfigs: {},
|
|
163
|
+
host: 'web.portalhq.io',
|
|
164
|
+
mpcVersion: 'v6',
|
|
165
|
+
featureFlags: {},
|
|
166
|
+
}, expect.any(Function));
|
|
167
|
+
}));
|
|
168
|
+
it('should throw if the iframe response does not contain an encryption key', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
169
|
+
;
|
|
170
|
+
portal.mpc.backup.mockResolvedValueOnce({
|
|
171
|
+
cipherText: constants_1.mockCipherText,
|
|
172
|
+
storageCallback: jest.fn(),
|
|
173
|
+
});
|
|
174
|
+
yield expect(portal.generateBackupShare()).rejects.toThrow('Custom backup did not return an encryption key');
|
|
175
|
+
}));
|
|
176
|
+
});
|
|
177
|
+
describe('registerPasskeyAndStoreEncryptionKey', () => {
|
|
178
|
+
it('should delegate to the passkey service with computed relying party data', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
179
|
+
const passkeyServiceMock = {
|
|
180
|
+
registerPasskeyAndStoreKey: jest.fn().mockResolvedValue(undefined),
|
|
181
|
+
};
|
|
182
|
+
portal.passkeyService = passkeyServiceMock;
|
|
183
|
+
portal.passkeyServiceDefaultDomain = 'backup.web.portalhq.io';
|
|
184
|
+
portal.passkeyServiceApiKey = portal.apiKey;
|
|
185
|
+
yield portal.registerPasskeyAndStoreEncryptionKey(constants_1.mockCipherText, 'manual-key');
|
|
186
|
+
expect(passkeyServiceMock.registerPasskeyAndStoreKey).toHaveBeenCalledTimes(1);
|
|
187
|
+
expect(passkeyServiceMock.registerPasskeyAndStoreKey).toHaveBeenCalledWith(expect.objectContaining({
|
|
188
|
+
customDomain: undefined,
|
|
189
|
+
encryptionKey: 'manual-key',
|
|
190
|
+
relyingPartyId: 'backup.web.portalhq.io',
|
|
191
|
+
relyingPartyName: 'Portal',
|
|
192
|
+
cipherText: constants_1.mockCipherText,
|
|
193
|
+
}));
|
|
194
|
+
}));
|
|
195
|
+
it('should throw when usePopup is requested', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
196
|
+
yield expect(portal.registerPasskeyAndStoreEncryptionKey(constants_1.mockCipherText, 'manual-key', { usePopup: true })).rejects.toThrow('does not support the popup flow');
|
|
197
|
+
}));
|
|
198
|
+
});
|
|
199
|
+
describe('authenticatePasskeyAndRetrieveKey', () => {
|
|
200
|
+
it('should invoke the passkey service for direct authentication', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
201
|
+
const passkeyServiceMock = {
|
|
202
|
+
authenticatePasskeyAndRetrieveKey: jest
|
|
203
|
+
.fn()
|
|
204
|
+
.mockResolvedValue('retrieved-key'),
|
|
205
|
+
};
|
|
206
|
+
portal.passkeyService = passkeyServiceMock;
|
|
207
|
+
portal.passkeyServiceDefaultDomain = 'backup.web.portalhq.io';
|
|
208
|
+
portal.passkeyServiceApiKey = portal.apiKey;
|
|
209
|
+
const key = yield portal.authenticatePasskeyAndRetrieveKey();
|
|
210
|
+
expect(key).toEqual('retrieved-key');
|
|
211
|
+
expect(passkeyServiceMock.authenticatePasskeyAndRetrieveKey).toHaveBeenCalledWith(expect.objectContaining({
|
|
212
|
+
customDomain: undefined,
|
|
213
|
+
relyingPartyId: 'backup.web.portalhq.io',
|
|
214
|
+
relyingPartyName: 'Portal',
|
|
215
|
+
}));
|
|
216
|
+
}));
|
|
217
|
+
it('should throw when usePopup is true', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
218
|
+
yield expect(portal.authenticatePasskeyAndRetrieveKey({ usePopup: true })).rejects.toThrow('does not support the popup flow');
|
|
219
|
+
}));
|
|
220
|
+
});
|
|
221
|
+
describe('backupWithPasskey', () => {
|
|
222
|
+
it('should orchestrate the direct passkey flow when usePopup is false', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
223
|
+
const directShare = {
|
|
224
|
+
cipherText: constants_1.mockCipherText,
|
|
225
|
+
encryptionKey: 'manual-key',
|
|
226
|
+
};
|
|
227
|
+
const generateSpy = jest
|
|
228
|
+
.spyOn(portal, 'generateBackupShare')
|
|
229
|
+
.mockResolvedValue(directShare);
|
|
230
|
+
const registerSpy = jest
|
|
231
|
+
.spyOn(portal, 'registerPasskeyAndStoreEncryptionKey')
|
|
232
|
+
.mockResolvedValue(undefined);
|
|
233
|
+
const storedClientBackupShareSpy = jest
|
|
234
|
+
.spyOn(portal, 'storedClientBackupShare')
|
|
235
|
+
.mockResolvedValue(undefined);
|
|
236
|
+
yield portal.backupWithPasskey({
|
|
237
|
+
usePopup: false,
|
|
238
|
+
customDomain: 'passkeys.wigwam.app',
|
|
239
|
+
relyingPartyName: 'Wigwam',
|
|
240
|
+
}, undefined);
|
|
241
|
+
expect(generateSpy).toHaveBeenCalledTimes(1);
|
|
242
|
+
expect(registerSpy).toHaveBeenCalledWith(directShare.cipherText, directShare.encryptionKey, expect.objectContaining({
|
|
243
|
+
customDomain: 'passkeys.wigwam.app',
|
|
244
|
+
relyingPartyName: 'Wigwam',
|
|
245
|
+
usePopup: false,
|
|
246
|
+
}));
|
|
247
|
+
expect(storedClientBackupShareSpy).toHaveBeenCalledWith(true, _1.BackupMethods.passkey);
|
|
248
|
+
generateSpy.mockRestore();
|
|
249
|
+
registerSpy.mockRestore();
|
|
250
|
+
storedClientBackupShareSpy.mockRestore();
|
|
251
|
+
}));
|
|
252
|
+
it('should fall back to the legacy popup flow when usePopup is true', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
253
|
+
const progress = jest.fn();
|
|
254
|
+
yield portal.backupWithPasskey({}, progress);
|
|
255
|
+
expect(portal.mpc.backup).toHaveBeenCalledWith({
|
|
256
|
+
backupMethod: _1.BackupMethods.passkey,
|
|
257
|
+
backupConfigs: {},
|
|
258
|
+
host: 'web.portalhq.io',
|
|
259
|
+
mpcVersion: 'v6',
|
|
260
|
+
featureFlags: {},
|
|
261
|
+
}, progress);
|
|
262
|
+
}));
|
|
263
|
+
});
|
|
147
264
|
describe('recoverWallet', () => {
|
|
148
265
|
it('should successfully recover a wallet and call mpc.recover correctly', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
149
266
|
const mockProgressFn = jest.fn();
|
|
@@ -565,14 +682,14 @@ describe('Portal', () => {
|
|
|
565
682
|
it('should correctly call mpc.getQuote', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
566
683
|
yield portal.getQuote('test', constants_1.mockQuoteArgs, 'eip155:1');
|
|
567
684
|
expect(portal.mpc.getQuote).toHaveBeenCalledTimes(1);
|
|
568
|
-
expect(portal.mpc.getQuote).toHaveBeenCalledWith('
|
|
685
|
+
expect(portal.mpc.getQuote).toHaveBeenCalledWith('eip155:1', constants_1.mockQuoteArgs, 'test');
|
|
569
686
|
}));
|
|
570
687
|
});
|
|
571
688
|
describe('getSources', () => {
|
|
572
689
|
it('should correctly call mpc.getSources', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
573
690
|
yield portal.getSources('test', 'eip155:1');
|
|
574
691
|
expect(portal.mpc.getSources).toHaveBeenCalledTimes(1);
|
|
575
|
-
expect(portal.mpc.getSources).toHaveBeenCalledWith('
|
|
692
|
+
expect(portal.mpc.getSources).toHaveBeenCalledWith('eip155:1', 'test');
|
|
576
693
|
}));
|
|
577
694
|
});
|
|
578
695
|
describe('storedClientBackupShare', () => {
|