@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
|
@@ -0,0 +1,394 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
var __rest = (this && this.__rest) || function (s, e) {
|
|
12
|
+
var t = {};
|
|
13
|
+
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
|
|
14
|
+
t[p] = s[p];
|
|
15
|
+
if (s != null && typeof Object.getOwnPropertySymbols === "function")
|
|
16
|
+
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
|
|
17
|
+
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
|
|
18
|
+
t[p[i]] = s[p[i]];
|
|
19
|
+
}
|
|
20
|
+
return t;
|
|
21
|
+
};
|
|
22
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
23
|
+
exports.PasskeyService = void 0;
|
|
24
|
+
class PasskeyService {
|
|
25
|
+
constructor({ defaultDomain, getJwt, fetchImpl }) {
|
|
26
|
+
this.defaultDomain = defaultDomain;
|
|
27
|
+
this.getJwt = getJwt;
|
|
28
|
+
this.fetchImpl = fetchImpl !== null && fetchImpl !== void 0 ? fetchImpl : fetch.bind(globalThis);
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Generate MPC backup share and register a passkey in the top-level window
|
|
32
|
+
* while storing the provided encryption key with the relying party.
|
|
33
|
+
*/
|
|
34
|
+
registerPasskeyAndStoreKey(params) {
|
|
35
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
36
|
+
const { customDomain, encryptionKey } = params;
|
|
37
|
+
const { origin, relyingPartyOrigins, relyingPartyId, relyingPartyName } = this.resolveParams(params);
|
|
38
|
+
const status = yield this.getStatus(origin, relyingPartyId);
|
|
39
|
+
const normalizedStatus = status === null || status === void 0 ? void 0 : status.toLowerCase();
|
|
40
|
+
const shouldRegister = !normalizedStatus ||
|
|
41
|
+
normalizedStatus === 'not registered' ||
|
|
42
|
+
normalizedStatus === 'registered';
|
|
43
|
+
if (!shouldRegister) {
|
|
44
|
+
yield this.loginAndStoreKey({
|
|
45
|
+
customDomain,
|
|
46
|
+
relyingPartyId,
|
|
47
|
+
relyingPartyOrigins,
|
|
48
|
+
encryptionKey,
|
|
49
|
+
});
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
const begin = yield this.postJson(`${origin}/passkeys/begin-registration`, {
|
|
53
|
+
relyingParty: relyingPartyId,
|
|
54
|
+
relyingPartyName,
|
|
55
|
+
relyingPartyOrigins,
|
|
56
|
+
});
|
|
57
|
+
const credential = yield this.createCredential(begin.options.publicKey);
|
|
58
|
+
const attestation = serializeAttestationCredential(credential);
|
|
59
|
+
yield this.postJsonVoid(`${origin}/passkeys/finish-registration`, {
|
|
60
|
+
sessionId: begin.sessionId,
|
|
61
|
+
relyingParty: relyingPartyId,
|
|
62
|
+
relyingPartyOrigins,
|
|
63
|
+
attestation,
|
|
64
|
+
encryptionKey,
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Authenticate with passkey in the current browsing context and return the encryption key.
|
|
70
|
+
*/
|
|
71
|
+
authenticatePasskeyAndRetrieveKey(params) {
|
|
72
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
73
|
+
const { origin, relyingPartyOrigins, relyingPartyId, relyingPartyName } = this.resolveParams(params);
|
|
74
|
+
const begin = yield this.postJson(`${origin}/passkeys/begin-authentication`, {
|
|
75
|
+
relyingParty: relyingPartyId,
|
|
76
|
+
relyingPartyName,
|
|
77
|
+
relyingPartyOrigins,
|
|
78
|
+
});
|
|
79
|
+
const credential = yield this.getCredential(begin.options.publicKey);
|
|
80
|
+
const assertion = serializeAssertionCredential(credential);
|
|
81
|
+
const finish = yield this.postJson(`${origin}/passkeys/finish-authentication`, {
|
|
82
|
+
sessionId: begin.sessionId,
|
|
83
|
+
relyingParty: relyingPartyId,
|
|
84
|
+
relyingPartyOrigins,
|
|
85
|
+
assertion,
|
|
86
|
+
});
|
|
87
|
+
if (!(finish === null || finish === void 0 ? void 0 : finish.encryptionKey)) {
|
|
88
|
+
throw new Error('Passkey authentication succeeded but no encryption key was returned.');
|
|
89
|
+
}
|
|
90
|
+
return finish.encryptionKey;
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Register a passkey without tying it to an encryption key.
|
|
95
|
+
* The encryption key can be stored later using authenticatePasskeyAndWriteKey.
|
|
96
|
+
*/
|
|
97
|
+
registerPasskey(params) {
|
|
98
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
99
|
+
const { origin, relyingPartyOrigins, relyingPartyId, relyingPartyName } = this.resolveParams(params);
|
|
100
|
+
const status = yield this.getStatus(origin, relyingPartyId);
|
|
101
|
+
const normalizedStatus = status === null || status === void 0 ? void 0 : status.toLowerCase();
|
|
102
|
+
if (normalizedStatus && normalizedStatus !== 'not registered' && normalizedStatus !== 'registered') {
|
|
103
|
+
throw new Error(`Passkey already exists with status: ${status}. Cannot register new credential.`);
|
|
104
|
+
}
|
|
105
|
+
const begin = yield this.postJson(`${origin}/passkeys/begin-credential-registration`, {
|
|
106
|
+
relyingParty: relyingPartyId,
|
|
107
|
+
relyingPartyName,
|
|
108
|
+
relyingPartyOrigins,
|
|
109
|
+
});
|
|
110
|
+
const credential = yield this.createCredential(begin.options.publicKey);
|
|
111
|
+
const attestation = serializeAttestationCredential(credential);
|
|
112
|
+
yield this.postJsonVoid(`${origin}/passkeys/finish-credential-registration`, {
|
|
113
|
+
sessionId: begin.sessionId,
|
|
114
|
+
relyingParty: relyingPartyId,
|
|
115
|
+
relyingPartyOrigins,
|
|
116
|
+
attestation,
|
|
117
|
+
});
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Authenticate with passkey and store an encryption key.
|
|
122
|
+
* Used after registerPasskey to associate an encryption key with the passkey.
|
|
123
|
+
*/
|
|
124
|
+
authenticatePasskeyAndWriteKey(params) {
|
|
125
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
126
|
+
const { encryptionKey } = params;
|
|
127
|
+
const { origin, relyingPartyOrigins, relyingPartyId, relyingPartyName } = this.resolveParams(params);
|
|
128
|
+
const begin = yield this.postJson(`${origin}/passkeys/begin-login`, {
|
|
129
|
+
relyingParty: relyingPartyId,
|
|
130
|
+
relyingPartyName,
|
|
131
|
+
relyingPartyOrigins,
|
|
132
|
+
});
|
|
133
|
+
const credential = yield this.getCredential(begin.options.publicKey);
|
|
134
|
+
const assertion = serializeAssertionCredential(credential);
|
|
135
|
+
yield this.postJsonVoid(`${origin}/passkeys/finish-login/write`, {
|
|
136
|
+
sessionId: begin.sessionId,
|
|
137
|
+
relyingParty: relyingPartyId,
|
|
138
|
+
relyingPartyOrigins,
|
|
139
|
+
assertion,
|
|
140
|
+
encryptionKey,
|
|
141
|
+
});
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Utility used by combined backup flow to produce reusable payloads.
|
|
146
|
+
*/
|
|
147
|
+
generateBackupSharePayload(cipherText, encryptionKey) {
|
|
148
|
+
return {
|
|
149
|
+
cipherText,
|
|
150
|
+
encryptionKey,
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
createCredential(options) {
|
|
154
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
155
|
+
const publicKey = decodeCreationOptions(options);
|
|
156
|
+
const credential = (yield navigator.credentials.create({
|
|
157
|
+
publicKey,
|
|
158
|
+
}));
|
|
159
|
+
if (!credential) {
|
|
160
|
+
throw new Error('Passkey registration was cancelled or failed to create credential.');
|
|
161
|
+
}
|
|
162
|
+
return credential;
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
loginAndStoreKey(params) {
|
|
166
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
167
|
+
const { customDomain, relyingPartyId, relyingPartyOrigins, encryptionKey } = params;
|
|
168
|
+
const origin = this.normalizeDomain(customDomain);
|
|
169
|
+
const begin = yield this.postJson(`${origin}/passkeys/begin-login`, {
|
|
170
|
+
relyingParty: relyingPartyId,
|
|
171
|
+
relyingPartyOrigins,
|
|
172
|
+
});
|
|
173
|
+
const credential = yield this.getCredential(begin.options.publicKey);
|
|
174
|
+
const assertion = serializeAssertionCredential(credential);
|
|
175
|
+
yield this.postJsonVoid(`${origin}/passkeys/finish-login/write`, {
|
|
176
|
+
sessionId: begin.sessionId,
|
|
177
|
+
relyingParty: relyingPartyId,
|
|
178
|
+
relyingPartyOrigins,
|
|
179
|
+
assertion,
|
|
180
|
+
encryptionKey,
|
|
181
|
+
});
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
getStatus(origin, relyingPartyId) {
|
|
185
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
186
|
+
try {
|
|
187
|
+
const query = new URLSearchParams({ relyingParty: relyingPartyId });
|
|
188
|
+
const url = `${origin}/passkeys/status?${query.toString()}`;
|
|
189
|
+
const response = yield this.fetchImpl(url, {
|
|
190
|
+
method: 'GET',
|
|
191
|
+
headers: yield this.buildHeaders(),
|
|
192
|
+
});
|
|
193
|
+
if (!response.ok) {
|
|
194
|
+
return undefined;
|
|
195
|
+
}
|
|
196
|
+
const raw = yield response.text();
|
|
197
|
+
if (!raw) {
|
|
198
|
+
return undefined;
|
|
199
|
+
}
|
|
200
|
+
const data = JSON.parse(raw);
|
|
201
|
+
return data.status;
|
|
202
|
+
}
|
|
203
|
+
catch (error) {
|
|
204
|
+
console.warn('[Portal] Failed to fetch passkey status', error);
|
|
205
|
+
return undefined;
|
|
206
|
+
}
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
getCredential(options) {
|
|
210
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
211
|
+
const publicKey = decodeRequestOptions(options);
|
|
212
|
+
const credential = (yield navigator.credentials.get({
|
|
213
|
+
publicKey,
|
|
214
|
+
}));
|
|
215
|
+
if (!credential) {
|
|
216
|
+
throw new Error('Passkey authentication was cancelled or failed to resolve credential.');
|
|
217
|
+
}
|
|
218
|
+
return credential;
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
normalizeDomain(customDomain) {
|
|
222
|
+
const domain = customDomain !== null && customDomain !== void 0 ? customDomain : this.defaultDomain;
|
|
223
|
+
if (!domain) {
|
|
224
|
+
throw new Error('Passkey domain is not configured.');
|
|
225
|
+
}
|
|
226
|
+
if (domain.startsWith('http://') || domain.startsWith('https://')) {
|
|
227
|
+
return domain;
|
|
228
|
+
}
|
|
229
|
+
return `https://${domain}`;
|
|
230
|
+
}
|
|
231
|
+
buildRelyingPartyOrigins(origin) {
|
|
232
|
+
var _a;
|
|
233
|
+
const set = new Set([origin]);
|
|
234
|
+
if (typeof window !== 'undefined' && ((_a = window.location) === null || _a === void 0 ? void 0 : _a.origin)) {
|
|
235
|
+
set.add(window.location.origin);
|
|
236
|
+
}
|
|
237
|
+
return Array.from(set);
|
|
238
|
+
}
|
|
239
|
+
resolveParams(options) {
|
|
240
|
+
const origin = this.normalizeDomain(options.customDomain);
|
|
241
|
+
const relyingPartyOrigins = this.buildRelyingPartyOrigins(origin);
|
|
242
|
+
return {
|
|
243
|
+
origin,
|
|
244
|
+
relyingPartyOrigins,
|
|
245
|
+
relyingPartyId: options.relyingPartyId,
|
|
246
|
+
relyingPartyName: options.relyingPartyName,
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
postJson(url, body) {
|
|
250
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
251
|
+
const response = yield this.fetchImpl(url, {
|
|
252
|
+
method: 'POST',
|
|
253
|
+
headers: yield this.buildHeaders(),
|
|
254
|
+
body: JSON.stringify(body),
|
|
255
|
+
});
|
|
256
|
+
if (!response.ok) {
|
|
257
|
+
const text = yield safeReadText(response);
|
|
258
|
+
throw new Error(`Passkey request failed (${response.status} ${response.statusText}): ${text}`);
|
|
259
|
+
}
|
|
260
|
+
return response.json();
|
|
261
|
+
});
|
|
262
|
+
}
|
|
263
|
+
postJsonVoid(url, body) {
|
|
264
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
265
|
+
const response = yield this.fetchImpl(url, {
|
|
266
|
+
method: 'POST',
|
|
267
|
+
headers: yield this.buildHeaders(),
|
|
268
|
+
body: JSON.stringify(body),
|
|
269
|
+
});
|
|
270
|
+
if (!response.ok) {
|
|
271
|
+
const text = yield safeReadText(response);
|
|
272
|
+
throw new Error(`Passkey request failed (${response.status} ${response.statusText}): ${text}`);
|
|
273
|
+
}
|
|
274
|
+
});
|
|
275
|
+
}
|
|
276
|
+
buildHeaders() {
|
|
277
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
278
|
+
const jwt = yield this.getJwt();
|
|
279
|
+
return {
|
|
280
|
+
'Content-Type': 'application/json',
|
|
281
|
+
Authorization: `Bearer ${jwt}`,
|
|
282
|
+
};
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
exports.PasskeyService = PasskeyService;
|
|
287
|
+
const decodeCreationOptions = (options) => {
|
|
288
|
+
const { excludeCredentials } = options, rest = __rest(options, ["excludeCredentials"]);
|
|
289
|
+
const publicKey = Object.assign(Object.assign({}, rest), { challenge: base64UrlToArrayBuffer(options.challenge), user: Object.assign(Object.assign({}, options.user), { id: base64UrlToArrayBuffer(options.user.id) }) });
|
|
290
|
+
if (excludeCredentials) {
|
|
291
|
+
publicKey.excludeCredentials = excludeCredentials.map((credential) => ({
|
|
292
|
+
type: credential.type,
|
|
293
|
+
id: base64UrlToArrayBuffer(credential.id),
|
|
294
|
+
transports: credential.transports,
|
|
295
|
+
}));
|
|
296
|
+
}
|
|
297
|
+
return publicKey;
|
|
298
|
+
};
|
|
299
|
+
const decodeRequestOptions = (options) => {
|
|
300
|
+
const { allowCredentials } = options, rest = __rest(options, ["allowCredentials"]);
|
|
301
|
+
const publicKey = Object.assign(Object.assign({}, rest), { challenge: base64UrlToArrayBuffer(options.challenge) });
|
|
302
|
+
if (allowCredentials) {
|
|
303
|
+
publicKey.allowCredentials = allowCredentials.map((credential) => ({
|
|
304
|
+
type: credential.type,
|
|
305
|
+
id: base64UrlToArrayBuffer(credential.id),
|
|
306
|
+
transports: credential.transports,
|
|
307
|
+
}));
|
|
308
|
+
}
|
|
309
|
+
return publicKey;
|
|
310
|
+
};
|
|
311
|
+
const serializeAttestationCredential = (credential) => {
|
|
312
|
+
var _a;
|
|
313
|
+
const { response } = credential;
|
|
314
|
+
const attestationResponse = response;
|
|
315
|
+
const payload = {
|
|
316
|
+
id: credential.id,
|
|
317
|
+
rawId: arrayBufferToBase64Url(credential.rawId),
|
|
318
|
+
type: credential.type,
|
|
319
|
+
response: {
|
|
320
|
+
clientDataJSON: arrayBufferToBase64Url(attestationResponse.clientDataJSON),
|
|
321
|
+
attestationObject: attestationResponse.attestationObject
|
|
322
|
+
? arrayBufferToBase64Url(attestationResponse.attestationObject)
|
|
323
|
+
: undefined,
|
|
324
|
+
transports: (_a = attestationResponse.getTransports) === null || _a === void 0 ? void 0 : _a.call(attestationResponse),
|
|
325
|
+
},
|
|
326
|
+
clientExtensionResults: credential.getClientExtensionResults(),
|
|
327
|
+
};
|
|
328
|
+
return JSON.stringify(payload);
|
|
329
|
+
};
|
|
330
|
+
const serializeAssertionCredential = (credential) => {
|
|
331
|
+
const { response } = credential;
|
|
332
|
+
const assertionResponse = response;
|
|
333
|
+
const payload = {
|
|
334
|
+
id: credential.id,
|
|
335
|
+
rawId: arrayBufferToBase64Url(credential.rawId),
|
|
336
|
+
type: credential.type,
|
|
337
|
+
response: {
|
|
338
|
+
clientDataJSON: arrayBufferToBase64Url(assertionResponse.clientDataJSON),
|
|
339
|
+
authenticatorData: arrayBufferToBase64Url(assertionResponse.authenticatorData),
|
|
340
|
+
signature: arrayBufferToBase64Url(assertionResponse.signature),
|
|
341
|
+
userHandle: assertionResponse.userHandle
|
|
342
|
+
? arrayBufferToBase64Url(assertionResponse.userHandle)
|
|
343
|
+
: null,
|
|
344
|
+
},
|
|
345
|
+
clientExtensionResults: credential.getClientExtensionResults(),
|
|
346
|
+
};
|
|
347
|
+
return JSON.stringify(payload);
|
|
348
|
+
};
|
|
349
|
+
const base64UrlToArrayBuffer = (value) => {
|
|
350
|
+
const normalized = value.replace(/-/g, '+').replace(/_/g, '/');
|
|
351
|
+
const padded = normalized.padEnd(normalized.length + ((4 - (normalized.length % 4)) % 4), '=');
|
|
352
|
+
const binary = typeof atob === 'function'
|
|
353
|
+
? atob(padded)
|
|
354
|
+
: decodeWithBufferFallback(padded);
|
|
355
|
+
const bytes = new Uint8Array(binary.length);
|
|
356
|
+
for (let i = 0; i < binary.length; i++) {
|
|
357
|
+
bytes[i] = binary.charCodeAt(i);
|
|
358
|
+
}
|
|
359
|
+
return bytes.buffer;
|
|
360
|
+
};
|
|
361
|
+
const arrayBufferToBase64Url = (buffer) => {
|
|
362
|
+
const bytes = new Uint8Array(buffer);
|
|
363
|
+
let binary = '';
|
|
364
|
+
for (let i = 0; i < bytes.byteLength; i++) {
|
|
365
|
+
binary += String.fromCharCode(bytes[i]);
|
|
366
|
+
}
|
|
367
|
+
const base64 = typeof btoa === 'function'
|
|
368
|
+
? btoa(binary)
|
|
369
|
+
: encodeWithBufferFallback(binary);
|
|
370
|
+
return base64.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
|
|
371
|
+
};
|
|
372
|
+
const safeReadText = (response) => __awaiter(void 0, void 0, void 0, function* () {
|
|
373
|
+
try {
|
|
374
|
+
return yield response.text();
|
|
375
|
+
}
|
|
376
|
+
catch (error) {
|
|
377
|
+
return error.message;
|
|
378
|
+
}
|
|
379
|
+
});
|
|
380
|
+
exports.default = PasskeyService;
|
|
381
|
+
const decodeWithBufferFallback = (value) => {
|
|
382
|
+
const bufferCtor = globalThis.Buffer;
|
|
383
|
+
if (bufferCtor) {
|
|
384
|
+
return bufferCtor.from(value, 'base64').toString('binary');
|
|
385
|
+
}
|
|
386
|
+
throw new Error('Base64 decoding is not supported in this environment.');
|
|
387
|
+
};
|
|
388
|
+
const encodeWithBufferFallback = (binary) => {
|
|
389
|
+
const bufferCtor = globalThis.Buffer;
|
|
390
|
+
if (bufferCtor) {
|
|
391
|
+
return bufferCtor.from(binary, 'binary').toString('base64');
|
|
392
|
+
}
|
|
393
|
+
throw new Error('Base64 encoding is not supported in this environment.');
|
|
394
|
+
};
|
|
@@ -260,7 +260,7 @@ class Provider {
|
|
|
260
260
|
* @param args The arguments of the request being made
|
|
261
261
|
* @returns Promise<any>
|
|
262
262
|
*/
|
|
263
|
-
request({ chainId: requestChainId, method, params, }) {
|
|
263
|
+
request({ chainId: requestChainId, method, params, sponsorGas, }) {
|
|
264
264
|
return __awaiter(this, void 0, void 0, function* () {
|
|
265
265
|
const isSignerMethod = signerMethods.includes(method);
|
|
266
266
|
const chainId = this.getCAIP2ChainId(requestChainId);
|
|
@@ -288,6 +288,7 @@ class Provider {
|
|
|
288
288
|
chainId,
|
|
289
289
|
method,
|
|
290
290
|
params,
|
|
291
|
+
sponsorGas,
|
|
291
292
|
});
|
|
292
293
|
if (transactionHash) {
|
|
293
294
|
this.emit('portal_signatureReceived', {
|
|
@@ -403,7 +404,7 @@ class Provider {
|
|
|
403
404
|
* @param args The arguments of the request being made
|
|
404
405
|
* @returns Promise<any>
|
|
405
406
|
*/
|
|
406
|
-
handleSigningRequest({ chainId, method, params, }) {
|
|
407
|
+
handleSigningRequest({ chainId, method, params, sponsorGas, }) {
|
|
407
408
|
return __awaiter(this, void 0, void 0, function* () {
|
|
408
409
|
const isApproved = passiveSignerMethods.includes(method)
|
|
409
410
|
? true
|
|
@@ -435,6 +436,7 @@ class Provider {
|
|
|
435
436
|
method,
|
|
436
437
|
params: this.buildParams(method, params),
|
|
437
438
|
rpcUrl: this.portal.getRpcUrl(chainId),
|
|
439
|
+
sponsorGas,
|
|
438
440
|
});
|
|
439
441
|
return result;
|
|
440
442
|
}
|
|
@@ -448,6 +450,7 @@ class Provider {
|
|
|
448
450
|
method,
|
|
449
451
|
params: this.buildParams(method, params),
|
|
450
452
|
rpcUrl: this.portal.getRpcUrl(chainId),
|
|
453
|
+
sponsorGas,
|
|
451
454
|
});
|
|
452
455
|
return result;
|
|
453
456
|
}
|
package/lib/esm/index.js
CHANGED
|
@@ -12,6 +12,8 @@ import Mpc from './mpc';
|
|
|
12
12
|
import Provider, { RequestMethod } from './provider';
|
|
13
13
|
import Yield from './integrations/yield';
|
|
14
14
|
import Trading from './integrations/trading';
|
|
15
|
+
import Security from './integrations/security';
|
|
16
|
+
import PasskeyService from './passkeys';
|
|
15
17
|
class Portal {
|
|
16
18
|
get ready() {
|
|
17
19
|
return this.mpc.ready;
|
|
@@ -56,6 +58,7 @@ class Portal {
|
|
|
56
58
|
});
|
|
57
59
|
this.yield = new Yield({ mpc: this.mpc });
|
|
58
60
|
this.trading = new Trading({ mpc: this.mpc });
|
|
61
|
+
this.security = new Security({ mpc: this.mpc });
|
|
59
62
|
this.provider = new Provider({
|
|
60
63
|
portal: this,
|
|
61
64
|
chainId: chainId ? Number(chainId) : undefined,
|
|
@@ -119,6 +122,98 @@ class Portal {
|
|
|
119
122
|
return address;
|
|
120
123
|
});
|
|
121
124
|
}
|
|
125
|
+
generateBackupShare(progress = () => {
|
|
126
|
+
// Noop
|
|
127
|
+
}) {
|
|
128
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
129
|
+
const response = yield this.mpc.backup({
|
|
130
|
+
backupMethod: BackupMethods.custom,
|
|
131
|
+
backupConfigs: {},
|
|
132
|
+
host: this.host,
|
|
133
|
+
mpcVersion: this.mpcVersion,
|
|
134
|
+
featureFlags: this.featureFlags,
|
|
135
|
+
}, progress);
|
|
136
|
+
if (!response.encryptionKey) {
|
|
137
|
+
throw new Error('[Portal] Custom backup did not return an encryption key. Please ensure you are using the latest iframe bundle.');
|
|
138
|
+
}
|
|
139
|
+
return {
|
|
140
|
+
cipherText: response.cipherText,
|
|
141
|
+
encryptionKey: response.encryptionKey,
|
|
142
|
+
};
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
registerPasskeyAndStoreEncryptionKey(cipherText, encryptionKey, options = {}) {
|
|
146
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
147
|
+
const { service, customDomain, relyingPartyId, relyingPartyName } = this.resolvePasskeyOptions(options);
|
|
148
|
+
yield service.registerPasskeyAndStoreKey({
|
|
149
|
+
customDomain,
|
|
150
|
+
relyingPartyName,
|
|
151
|
+
encryptionKey,
|
|
152
|
+
relyingPartyId,
|
|
153
|
+
cipherText,
|
|
154
|
+
});
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
authenticatePasskeyAndRetrieveKey(options = {}) {
|
|
158
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
159
|
+
const { service, customDomain, relyingPartyId, relyingPartyName } = this.resolvePasskeyOptions(options);
|
|
160
|
+
return yield service.authenticatePasskeyAndRetrieveKey({
|
|
161
|
+
customDomain,
|
|
162
|
+
relyingPartyName,
|
|
163
|
+
relyingPartyId,
|
|
164
|
+
});
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Register a passkey without tying it to an encryption key.
|
|
169
|
+
* The encryption key can be stored later using authenticatePasskeyAndWriteKey.
|
|
170
|
+
*/
|
|
171
|
+
registerPasskey(options = {}) {
|
|
172
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
173
|
+
const { service, customDomain, relyingPartyId, relyingPartyName } = this.resolvePasskeyOptions(options);
|
|
174
|
+
yield service.registerPasskey({
|
|
175
|
+
customDomain,
|
|
176
|
+
relyingPartyName,
|
|
177
|
+
relyingPartyId,
|
|
178
|
+
});
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Authenticate with passkey and store an encryption key.
|
|
183
|
+
* Used after registerPasskey to associate an encryption key with the passkey.
|
|
184
|
+
*/
|
|
185
|
+
authenticatePasskeyAndWriteKey(encryptionKey, options = {}) {
|
|
186
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
187
|
+
const { service, customDomain, relyingPartyId, relyingPartyName } = this.resolvePasskeyOptions(options);
|
|
188
|
+
yield service.authenticatePasskeyAndWriteKey({
|
|
189
|
+
customDomain,
|
|
190
|
+
relyingPartyName,
|
|
191
|
+
relyingPartyId,
|
|
192
|
+
encryptionKey,
|
|
193
|
+
});
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
backupWithPasskey(options = {}, progress = () => {
|
|
197
|
+
// Noop
|
|
198
|
+
}) {
|
|
199
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
200
|
+
const { usePopup = true, customDomain, relyingPartyName, backupMethod = BackupMethods.passkey, } = options;
|
|
201
|
+
if (usePopup) {
|
|
202
|
+
yield this.backupWallet(backupMethod, progress, {});
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
if (backupMethod !== BackupMethods.passkey) {
|
|
206
|
+
throw new Error(`[Portal] Direct passkey backup currently supports only BackupMethods.passkey (received ${backupMethod}).`);
|
|
207
|
+
}
|
|
208
|
+
const { cipherText, encryptionKey } = yield this.generateBackupShare(progress);
|
|
209
|
+
yield this.registerPasskeyAndStoreEncryptionKey(cipherText, encryptionKey, {
|
|
210
|
+
customDomain,
|
|
211
|
+
relyingPartyName,
|
|
212
|
+
usePopup,
|
|
213
|
+
});
|
|
214
|
+
yield this.storedClientBackupShare(true, BackupMethods.passkey);
|
|
215
|
+
});
|
|
216
|
+
}
|
|
122
217
|
backupWallet(backupMethod, progress = () => {
|
|
123
218
|
// Noop
|
|
124
219
|
}, backupConfigs = {}) {
|
|
@@ -332,6 +427,7 @@ class Portal {
|
|
|
332
427
|
chainId,
|
|
333
428
|
method: 'eth_sendTransaction',
|
|
334
429
|
params: [buildTxResponse.transaction],
|
|
430
|
+
sponsorGas: params.sponsorGas,
|
|
335
431
|
});
|
|
336
432
|
break;
|
|
337
433
|
}
|
|
@@ -340,6 +436,7 @@ class Portal {
|
|
|
340
436
|
chainId,
|
|
341
437
|
method: 'sol_signAndSendTransaction',
|
|
342
438
|
params: [buildTxResponse.transaction],
|
|
439
|
+
sponsorGas: params.sponsorGas,
|
|
343
440
|
});
|
|
344
441
|
break;
|
|
345
442
|
}
|
|
@@ -682,7 +779,7 @@ class Portal {
|
|
|
682
779
|
getQuote(apiKey, args, chainId) {
|
|
683
780
|
var _a;
|
|
684
781
|
return __awaiter(this, void 0, void 0, function* () {
|
|
685
|
-
return (_a = this.mpc) === null || _a === void 0 ? void 0 : _a.getQuote(
|
|
782
|
+
return (_a = this.mpc) === null || _a === void 0 ? void 0 : _a.getQuote(chainId, args, apiKey);
|
|
686
783
|
});
|
|
687
784
|
}
|
|
688
785
|
/**
|
|
@@ -691,7 +788,7 @@ class Portal {
|
|
|
691
788
|
getSources(apiKey, chainId) {
|
|
692
789
|
var _a;
|
|
693
790
|
return __awaiter(this, void 0, void 0, function* () {
|
|
694
|
-
return (_a = this.mpc) === null || _a === void 0 ? void 0 : _a.getSources(
|
|
791
|
+
return (_a = this.mpc) === null || _a === void 0 ? void 0 : _a.getSources(chainId, apiKey);
|
|
695
792
|
});
|
|
696
793
|
}
|
|
697
794
|
/*******************************
|
|
@@ -779,6 +876,50 @@ class Portal {
|
|
|
779
876
|
}
|
|
780
877
|
return chainId;
|
|
781
878
|
}
|
|
879
|
+
ensurePasskeyService() {
|
|
880
|
+
const defaultDomain = this.getDefaultPasskeyDomain();
|
|
881
|
+
if (!this.passkeyService ||
|
|
882
|
+
this.passkeyServiceDefaultDomain !== defaultDomain) {
|
|
883
|
+
this.passkeyService = new PasskeyService({
|
|
884
|
+
defaultDomain,
|
|
885
|
+
getJwt: () => this.mpc.getPasskeyJwt(),
|
|
886
|
+
});
|
|
887
|
+
this.passkeyServiceDefaultDomain = defaultDomain;
|
|
888
|
+
}
|
|
889
|
+
return this.passkeyService;
|
|
890
|
+
}
|
|
891
|
+
getDefaultPasskeyDomain() {
|
|
892
|
+
var _a, _b;
|
|
893
|
+
return (_b = (_a = this.passkeyConfig) === null || _a === void 0 ? void 0 : _a.webAuthnHost) !== null && _b !== void 0 ? _b : 'backup.web.portalhq.io';
|
|
894
|
+
}
|
|
895
|
+
extractRpId(domain) {
|
|
896
|
+
const targetDomain = domain !== null && domain !== void 0 ? domain : this.getDefaultPasskeyDomain();
|
|
897
|
+
try {
|
|
898
|
+
const normalized = targetDomain.startsWith('http')
|
|
899
|
+
? targetDomain
|
|
900
|
+
: `https://${targetDomain}`;
|
|
901
|
+
return new URL(normalized).hostname;
|
|
902
|
+
}
|
|
903
|
+
catch (_a) {
|
|
904
|
+
return targetDomain;
|
|
905
|
+
}
|
|
906
|
+
}
|
|
907
|
+
resolvePasskeyOptions(options) {
|
|
908
|
+
var _a, _b, _c, _d, _e;
|
|
909
|
+
if (options.usePopup) {
|
|
910
|
+
throw new Error('[Portal] This method does not support the popup flow. Use usePopup: false.');
|
|
911
|
+
}
|
|
912
|
+
const service = this.ensurePasskeyService();
|
|
913
|
+
const domain = (_a = options.customDomain) !== null && _a !== void 0 ? _a : this.getDefaultPasskeyDomain();
|
|
914
|
+
const relyingPartyId = (_b = options.relyingPartyId) !== null && _b !== void 0 ? _b : this.extractRpId(domain);
|
|
915
|
+
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';
|
|
916
|
+
return {
|
|
917
|
+
service,
|
|
918
|
+
customDomain: options.customDomain,
|
|
919
|
+
relyingPartyId,
|
|
920
|
+
relyingPartyName,
|
|
921
|
+
};
|
|
922
|
+
}
|
|
782
923
|
}
|
|
783
924
|
export { MpcError, MpcErrorCodes } from './mpc';
|
|
784
925
|
export { PortalMpcError } from './mpc/errors';
|
|
@@ -800,6 +941,7 @@ export var BackupMethods;
|
|
|
800
941
|
BackupMethods["gdrive"] = "GDRIVE";
|
|
801
942
|
BackupMethods["password"] = "PASSWORD";
|
|
802
943
|
BackupMethods["passkey"] = "PASSKEY";
|
|
944
|
+
BackupMethods["custom"] = "CUSTOM";
|
|
803
945
|
BackupMethods["unknown"] = "UNKNOWN";
|
|
804
946
|
})(BackupMethods || (BackupMethods = {}));
|
|
805
947
|
export var GetTransactionsOrder;
|