@phantom/embedded-provider-core 1.0.0-beta.8 → 1.0.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/README.md +3 -5
- package/dist/index.d.mts +111 -75
- package/dist/index.d.ts +111 -75
- package/dist/index.js +348 -340
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +347 -340
- package/dist/index.mjs.map +1 -1
- package/package.json +14 -9
package/dist/index.mjs
CHANGED
|
@@ -1,139 +1,25 @@
|
|
|
1
1
|
// src/embedded-provider.ts
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import { base64urlEncode } from "@phantom/base64url";
|
|
5
|
-
import bs582 from "bs58";
|
|
2
|
+
import { base64urlEncode, stringToBase64url } from "@phantom/base64url";
|
|
3
|
+
import { AddressType, PhantomClient, SpendingLimitError } from "@phantom/client";
|
|
6
4
|
import {
|
|
7
|
-
parseMessage,
|
|
8
|
-
parseTransactionToBase64Url,
|
|
9
5
|
parseSignMessageResponse,
|
|
10
|
-
parseTransactionResponse
|
|
6
|
+
parseTransactionResponse,
|
|
7
|
+
parseToKmsTransaction
|
|
11
8
|
} from "@phantom/parsers";
|
|
9
|
+
import { randomUUID } from "@phantom/utils";
|
|
10
|
+
import { Buffer } from "buffer";
|
|
11
|
+
import bs582 from "bs58";
|
|
12
12
|
|
|
13
13
|
// src/constants.ts
|
|
14
14
|
var AUTHENTICATOR_EXPIRATION_TIME_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
15
15
|
var AUTHENTICATOR_RENEWAL_WINDOW_MS = 2 * 24 * 60 * 60 * 1e3;
|
|
16
|
-
|
|
17
|
-
// src/auth/jwt-auth.ts
|
|
18
|
-
var JWTAuth = class {
|
|
19
|
-
async authenticate(options) {
|
|
20
|
-
if (!options.jwtToken || typeof options.jwtToken !== "string") {
|
|
21
|
-
throw new Error("Invalid JWT token: token must be a non-empty string");
|
|
22
|
-
}
|
|
23
|
-
const jwtParts = options.jwtToken.split(".");
|
|
24
|
-
if (jwtParts.length !== 3) {
|
|
25
|
-
throw new Error("Invalid JWT token format: token must have 3 parts separated by dots");
|
|
26
|
-
}
|
|
27
|
-
try {
|
|
28
|
-
const response = await fetch("/api/auth/jwt", {
|
|
29
|
-
method: "POST",
|
|
30
|
-
headers: {
|
|
31
|
-
"Content-Type": "application/json",
|
|
32
|
-
Authorization: `Bearer ${options.jwtToken}`,
|
|
33
|
-
"X-PHANTOM-APPID": options.appId
|
|
34
|
-
},
|
|
35
|
-
body: JSON.stringify({
|
|
36
|
-
appId: options.appId,
|
|
37
|
-
customAuthData: options.customAuthData
|
|
38
|
-
})
|
|
39
|
-
});
|
|
40
|
-
if (!response.ok) {
|
|
41
|
-
let errorMessage = `HTTP ${response.status}`;
|
|
42
|
-
try {
|
|
43
|
-
const errorData = await response.json();
|
|
44
|
-
errorMessage = errorData.message || errorData.error || errorMessage;
|
|
45
|
-
} catch {
|
|
46
|
-
errorMessage = response.statusText || errorMessage;
|
|
47
|
-
}
|
|
48
|
-
switch (response.status) {
|
|
49
|
-
case 400:
|
|
50
|
-
throw new Error(`Invalid JWT authentication request: ${errorMessage}`);
|
|
51
|
-
case 401:
|
|
52
|
-
throw new Error(`JWT token is invalid or expired: ${errorMessage}`);
|
|
53
|
-
case 403:
|
|
54
|
-
throw new Error(`JWT authentication forbidden: ${errorMessage}`);
|
|
55
|
-
case 404:
|
|
56
|
-
throw new Error(`JWT authentication endpoint not found: ${errorMessage}`);
|
|
57
|
-
case 429:
|
|
58
|
-
throw new Error(`Too many JWT authentication requests: ${errorMessage}`);
|
|
59
|
-
case 500:
|
|
60
|
-
case 502:
|
|
61
|
-
case 503:
|
|
62
|
-
case 504:
|
|
63
|
-
throw new Error(`JWT authentication server error: ${errorMessage}`);
|
|
64
|
-
default:
|
|
65
|
-
throw new Error(`JWT authentication failed: ${errorMessage}`);
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
let result;
|
|
69
|
-
try {
|
|
70
|
-
result = await response.json();
|
|
71
|
-
} catch (parseError) {
|
|
72
|
-
throw new Error("Invalid response from JWT authentication server: response is not valid JSON");
|
|
73
|
-
}
|
|
74
|
-
if (!result.walletId) {
|
|
75
|
-
throw new Error("Invalid JWT authentication response: missing walletId");
|
|
76
|
-
}
|
|
77
|
-
return {
|
|
78
|
-
walletId: result.walletId,
|
|
79
|
-
provider: "jwt",
|
|
80
|
-
userInfo: result.userInfo || {}
|
|
81
|
-
};
|
|
82
|
-
} catch (error) {
|
|
83
|
-
if (error instanceof TypeError && error.message.includes("fetch")) {
|
|
84
|
-
throw new Error("JWT authentication failed: network error or invalid endpoint");
|
|
85
|
-
}
|
|
86
|
-
if (error instanceof Error) {
|
|
87
|
-
throw error;
|
|
88
|
-
}
|
|
89
|
-
throw new Error(`JWT authentication error: ${String(error)}`);
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
};
|
|
93
|
-
|
|
94
|
-
// src/utils/session.ts
|
|
95
|
-
function generateSessionId() {
|
|
96
|
-
return "session_" + Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15) + "_" + Date.now();
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
// src/utils/retry.ts
|
|
100
|
-
async function retryWithBackoff(operation, operationName, logger, maxRetries = 3, baseDelay = 1e3) {
|
|
101
|
-
let lastError;
|
|
102
|
-
for (let attempt = 1; attempt <= maxRetries; attempt++) {
|
|
103
|
-
try {
|
|
104
|
-
logger.log("EMBEDDED_PROVIDER", `Attempting ${operationName}`, {
|
|
105
|
-
attempt,
|
|
106
|
-
maxRetries
|
|
107
|
-
});
|
|
108
|
-
return await operation();
|
|
109
|
-
} catch (error) {
|
|
110
|
-
lastError = error;
|
|
111
|
-
logger.warn("EMBEDDED_PROVIDER", `${operationName} failed`, {
|
|
112
|
-
attempt,
|
|
113
|
-
maxRetries,
|
|
114
|
-
error: error instanceof Error ? error.message : String(error)
|
|
115
|
-
});
|
|
116
|
-
if (attempt === maxRetries) {
|
|
117
|
-
logger.error("EMBEDDED_PROVIDER", `${operationName} failed after ${maxRetries} attempts`, {
|
|
118
|
-
finalError: error instanceof Error ? error.message : String(error)
|
|
119
|
-
});
|
|
120
|
-
break;
|
|
121
|
-
}
|
|
122
|
-
const delay = baseDelay * Math.pow(2, attempt - 1);
|
|
123
|
-
logger.log("EMBEDDED_PROVIDER", `Retrying ${operationName} in ${delay}ms`, {
|
|
124
|
-
attempt: attempt + 1,
|
|
125
|
-
delay
|
|
126
|
-
});
|
|
127
|
-
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
throw lastError;
|
|
131
|
-
}
|
|
16
|
+
var EMBEDDED_PROVIDER_AUTH_TYPES = ["google", "apple", "phantom"];
|
|
132
17
|
|
|
133
18
|
// src/chains/SolanaChain.ts
|
|
134
19
|
import { EventEmitter } from "eventemitter3";
|
|
135
20
|
import { NetworkId } from "@phantom/constants";
|
|
136
21
|
import bs58 from "bs58";
|
|
22
|
+
import { parseSolanaSignedTransaction } from "@phantom/parsers";
|
|
137
23
|
var EmbeddedSolanaChain = class {
|
|
138
24
|
constructor(provider) {
|
|
139
25
|
this.provider = provider;
|
|
@@ -170,8 +56,17 @@ var EmbeddedSolanaChain = class {
|
|
|
170
56
|
publicKey: this._publicKey || ""
|
|
171
57
|
};
|
|
172
58
|
}
|
|
173
|
-
signTransaction(
|
|
174
|
-
|
|
59
|
+
async signTransaction(transaction) {
|
|
60
|
+
this.ensureConnected();
|
|
61
|
+
const result = await this.provider.signTransaction({
|
|
62
|
+
transaction,
|
|
63
|
+
networkId: this.currentNetworkId
|
|
64
|
+
});
|
|
65
|
+
const signedTransaction = parseSolanaSignedTransaction(result.rawTransaction);
|
|
66
|
+
if (!signedTransaction) {
|
|
67
|
+
throw new Error("Failed to parse signed transaction");
|
|
68
|
+
}
|
|
69
|
+
return signedTransaction;
|
|
175
70
|
}
|
|
176
71
|
async signAndSendTransaction(transaction) {
|
|
177
72
|
this.ensureConnected();
|
|
@@ -184,8 +79,10 @@ var EmbeddedSolanaChain = class {
|
|
|
184
79
|
}
|
|
185
80
|
return { signature: result.hash };
|
|
186
81
|
}
|
|
187
|
-
signAllTransactions(
|
|
188
|
-
|
|
82
|
+
async signAllTransactions(transactions) {
|
|
83
|
+
this.ensureConnected();
|
|
84
|
+
const results = await Promise.all(transactions.map((tx) => this.signTransaction(tx)));
|
|
85
|
+
return results;
|
|
189
86
|
}
|
|
190
87
|
async signAndSendAllTransactions(transactions) {
|
|
191
88
|
const results = await Promise.all(transactions.map((tx) => this.signAndSendTransaction(tx)));
|
|
@@ -255,10 +152,8 @@ var EmbeddedSolanaChain = class {
|
|
|
255
152
|
};
|
|
256
153
|
|
|
257
154
|
// src/chains/EthereumChain.ts
|
|
258
|
-
import { EventEmitter as EventEmitter2 } from "eventemitter3";
|
|
259
155
|
import { NetworkId as NetworkId2, chainIdToNetworkId, networkIdToChainId } from "@phantom/constants";
|
|
260
|
-
import {
|
|
261
|
-
import { Buffer } from "buffer";
|
|
156
|
+
import { EventEmitter as EventEmitter2 } from "eventemitter3";
|
|
262
157
|
var EmbeddedEthereumChain = class {
|
|
263
158
|
constructor(provider) {
|
|
264
159
|
this.provider = provider;
|
|
@@ -316,18 +211,25 @@ var EmbeddedEthereumChain = class {
|
|
|
316
211
|
});
|
|
317
212
|
}
|
|
318
213
|
async signTransaction(transaction) {
|
|
214
|
+
let networkId = this.currentNetworkId;
|
|
215
|
+
if (transaction.chainId) {
|
|
216
|
+
const numericChainId = typeof transaction.chainId === "number" ? transaction.chainId : parseInt(transaction.chainId, 16);
|
|
217
|
+
const txNetworkId = chainIdToNetworkId(numericChainId);
|
|
218
|
+
if (txNetworkId) {
|
|
219
|
+
networkId = txNetworkId;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
319
222
|
const result = await this.provider.signTransaction({
|
|
320
223
|
transaction,
|
|
321
|
-
networkId
|
|
224
|
+
networkId
|
|
322
225
|
});
|
|
323
|
-
|
|
324
|
-
const signatureBytes = base64urlDecode(result.rawTransaction);
|
|
325
|
-
return "0x" + Buffer.from(signatureBytes).toString("hex");
|
|
326
|
-
} catch (error) {
|
|
327
|
-
return result.rawTransaction.startsWith("0x") ? result.rawTransaction : "0x" + result.rawTransaction;
|
|
328
|
-
}
|
|
226
|
+
return result.rawTransaction;
|
|
329
227
|
}
|
|
330
228
|
async sendTransaction(transaction) {
|
|
229
|
+
if (transaction.chainId) {
|
|
230
|
+
const numericChainId = typeof transaction.chainId === "number" ? transaction.chainId : parseInt(transaction.chainId, 16);
|
|
231
|
+
await this.switchChain(numericChainId);
|
|
232
|
+
}
|
|
331
233
|
const result = await this.provider.signAndSendTransaction({
|
|
332
234
|
transaction,
|
|
333
235
|
networkId: this.currentNetworkId
|
|
@@ -338,12 +240,13 @@ var EmbeddedEthereumChain = class {
|
|
|
338
240
|
return result.hash;
|
|
339
241
|
}
|
|
340
242
|
switchChain(chainId) {
|
|
341
|
-
const
|
|
243
|
+
const numericChainId = typeof chainId === "string" ? chainId.toLowerCase().startsWith("0x") ? parseInt(chainId, 16) : parseInt(chainId, 10) : chainId;
|
|
244
|
+
const networkId = chainIdToNetworkId(numericChainId);
|
|
342
245
|
if (!networkId) {
|
|
343
246
|
throw new Error(`Unsupported chainId: ${chainId}`);
|
|
344
247
|
}
|
|
345
248
|
this.currentNetworkId = networkId;
|
|
346
|
-
this.eventEmitter.emit("chainChanged", `0x${
|
|
249
|
+
this.eventEmitter.emit("chainChanged", `0x${numericChainId.toString(16)}`);
|
|
347
250
|
return Promise.resolve();
|
|
348
251
|
}
|
|
349
252
|
getChainId() {
|
|
@@ -388,7 +291,7 @@ var EmbeddedEthereumChain = class {
|
|
|
388
291
|
switch (args.method) {
|
|
389
292
|
case "personal_sign": {
|
|
390
293
|
const [message, _address] = args.params;
|
|
391
|
-
const result = await this.provider.
|
|
294
|
+
const result = await this.provider.signEthereumMessage({
|
|
392
295
|
message,
|
|
393
296
|
networkId: this.currentNetworkId
|
|
394
297
|
});
|
|
@@ -396,10 +299,9 @@ var EmbeddedEthereumChain = class {
|
|
|
396
299
|
}
|
|
397
300
|
case "eth_signTypedData_v4": {
|
|
398
301
|
const [_typedDataAddress, typedDataStr] = args.params;
|
|
399
|
-
const
|
|
400
|
-
const typedDataResult = await this.provider.
|
|
401
|
-
|
|
402
|
-
// Pass the stringified typed data as message
|
|
302
|
+
const typedData = JSON.parse(typedDataStr);
|
|
303
|
+
const typedDataResult = await this.provider.signTypedDataV4({
|
|
304
|
+
typedData,
|
|
403
305
|
networkId: this.currentNetworkId
|
|
404
306
|
});
|
|
405
307
|
return typedDataResult.signature;
|
|
@@ -453,6 +355,45 @@ var EmbeddedEthereumChain = class {
|
|
|
453
355
|
}
|
|
454
356
|
};
|
|
455
357
|
|
|
358
|
+
// src/utils/retry.ts
|
|
359
|
+
async function retryWithBackoff(operation, operationName, logger, maxRetries = 3, baseDelay = 1e3) {
|
|
360
|
+
let lastError;
|
|
361
|
+
for (let attempt = 1; attempt <= maxRetries; attempt++) {
|
|
362
|
+
try {
|
|
363
|
+
logger.log("EMBEDDED_PROVIDER", `Attempting ${operationName}`, {
|
|
364
|
+
attempt,
|
|
365
|
+
maxRetries
|
|
366
|
+
});
|
|
367
|
+
return await operation();
|
|
368
|
+
} catch (error) {
|
|
369
|
+
lastError = error;
|
|
370
|
+
logger.warn("EMBEDDED_PROVIDER", `${operationName} failed`, {
|
|
371
|
+
attempt,
|
|
372
|
+
maxRetries,
|
|
373
|
+
error: error instanceof Error ? error.message : String(error)
|
|
374
|
+
});
|
|
375
|
+
if (attempt === maxRetries) {
|
|
376
|
+
logger.error("EMBEDDED_PROVIDER", `${operationName} failed after ${maxRetries} attempts`, {
|
|
377
|
+
finalError: error instanceof Error ? error.message : String(error)
|
|
378
|
+
});
|
|
379
|
+
break;
|
|
380
|
+
}
|
|
381
|
+
const delay = baseDelay * Math.pow(2, attempt - 1);
|
|
382
|
+
logger.log("EMBEDDED_PROVIDER", `Retrying ${operationName} in ${delay}ms`, {
|
|
383
|
+
attempt: attempt + 1,
|
|
384
|
+
delay
|
|
385
|
+
});
|
|
386
|
+
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
throw lastError;
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
// src/utils/session.ts
|
|
393
|
+
function generateSessionId() {
|
|
394
|
+
return "session_" + Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15) + "_" + Date.now();
|
|
395
|
+
}
|
|
396
|
+
|
|
456
397
|
// src/embedded-provider.ts
|
|
457
398
|
var EmbeddedProvider = class {
|
|
458
399
|
constructor(config, platform, logger) {
|
|
@@ -469,9 +410,9 @@ var EmbeddedProvider = class {
|
|
|
469
410
|
this.platform = platform;
|
|
470
411
|
this.storage = platform.storage;
|
|
471
412
|
this.authProvider = platform.authProvider;
|
|
413
|
+
this.phantomAppProvider = platform.phantomAppProvider;
|
|
472
414
|
this.urlParamsAccessor = platform.urlParamsAccessor;
|
|
473
415
|
this.stamper = platform.stamper;
|
|
474
|
-
this.jwtAuth = new JWTAuth();
|
|
475
416
|
this.solana = new EmbeddedSolanaChain(this);
|
|
476
417
|
this.ethereum = new EmbeddedEthereumChain(this);
|
|
477
418
|
this.logger.info("EMBEDDED_PROVIDER", "EmbeddedProvider initialized");
|
|
@@ -588,10 +529,16 @@ var EmbeddedProvider = class {
|
|
|
588
529
|
}
|
|
589
530
|
}
|
|
590
531
|
if (session.status === "completed" && !this.isSessionValid(session)) {
|
|
591
|
-
this.logger.warn(
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
532
|
+
this.logger.warn(
|
|
533
|
+
"EMBEDDED_PROVIDER",
|
|
534
|
+
"Session invalid due to authenticator expiration, will regenerate keypair",
|
|
535
|
+
{
|
|
536
|
+
sessionId: session.sessionId,
|
|
537
|
+
authenticatorExpiresAt: session.authenticatorExpiresAt,
|
|
538
|
+
currentTime: Date.now(),
|
|
539
|
+
expired: session.authenticatorExpiresAt < Date.now()
|
|
540
|
+
}
|
|
541
|
+
);
|
|
595
542
|
await this.storage.clearSession();
|
|
596
543
|
return null;
|
|
597
544
|
}
|
|
@@ -606,7 +553,11 @@ var EmbeddedProvider = class {
|
|
|
606
553
|
this.logger.log("EMBEDDED_PROVIDER", "Getting existing session");
|
|
607
554
|
let session = await this.storage.getSession();
|
|
608
555
|
session = await this.validateAndCleanSession(session);
|
|
609
|
-
if (session
|
|
556
|
+
if (!session) {
|
|
557
|
+
this.logger.log("EMBEDDED_PROVIDER", "No existing session found");
|
|
558
|
+
return null;
|
|
559
|
+
}
|
|
560
|
+
if (session.status === "completed") {
|
|
610
561
|
this.logger.info("EMBEDDED_PROVIDER", "Using existing completed session", {
|
|
611
562
|
sessionId: session.sessionId,
|
|
612
563
|
walletId: session.walletId
|
|
@@ -622,18 +573,19 @@ var EmbeddedProvider = class {
|
|
|
622
573
|
const result = {
|
|
623
574
|
walletId: this.walletId,
|
|
624
575
|
addresses: this.addresses,
|
|
625
|
-
status: "completed"
|
|
576
|
+
status: "completed",
|
|
577
|
+
authUserId: session.authUserId,
|
|
578
|
+
authProvider: session.authProvider
|
|
626
579
|
};
|
|
627
580
|
this.emit("connect", {
|
|
628
|
-
|
|
629
|
-
addresses: this.addresses,
|
|
581
|
+
...result,
|
|
630
582
|
source: "existing-session"
|
|
631
583
|
});
|
|
632
584
|
return result;
|
|
633
585
|
}
|
|
634
586
|
this.logger.log("EMBEDDED_PROVIDER", "No completed session found, checking for redirect resume");
|
|
635
587
|
if (this.authProvider.resumeAuthFromRedirect) {
|
|
636
|
-
const authResult = this.authProvider.resumeAuthFromRedirect();
|
|
588
|
+
const authResult = this.authProvider.resumeAuthFromRedirect(session.authProvider);
|
|
637
589
|
if (authResult) {
|
|
638
590
|
this.logger.info("EMBEDDED_PROVIDER", "Resuming from redirect", {
|
|
639
591
|
walletId: authResult.walletId,
|
|
@@ -665,13 +617,10 @@ var EmbeddedProvider = class {
|
|
|
665
617
|
* This ensures only supported auth providers are used and required tokens are present.
|
|
666
618
|
*/
|
|
667
619
|
validateAuthOptions(authOptions) {
|
|
668
|
-
if (!authOptions)
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
}
|
|
673
|
-
if (authOptions.provider === "jwt" && !authOptions.jwtToken) {
|
|
674
|
-
throw new Error("JWT token is required when using JWT authentication");
|
|
620
|
+
if (!EMBEDDED_PROVIDER_AUTH_TYPES.includes(authOptions.provider)) {
|
|
621
|
+
throw new Error(
|
|
622
|
+
`Invalid auth provider: ${authOptions.provider}. Must be ${EMBEDDED_PROVIDER_AUTH_TYPES.join(", ")}`
|
|
623
|
+
);
|
|
675
624
|
}
|
|
676
625
|
}
|
|
677
626
|
/*
|
|
@@ -731,8 +680,7 @@ var EmbeddedProvider = class {
|
|
|
731
680
|
addressCount: result.addresses.length
|
|
732
681
|
});
|
|
733
682
|
this.emit("connect", {
|
|
734
|
-
|
|
735
|
-
addresses: result.addresses,
|
|
683
|
+
...result,
|
|
736
684
|
source: "auto-connect"
|
|
737
685
|
});
|
|
738
686
|
return;
|
|
@@ -750,40 +698,46 @@ var EmbeddedProvider = class {
|
|
|
750
698
|
error: error instanceof Error ? error.message : "Auto-connect failed",
|
|
751
699
|
source: "auto-connect"
|
|
752
700
|
});
|
|
701
|
+
await this.storage.setShouldClearPreviousSession(true);
|
|
753
702
|
}
|
|
754
703
|
}
|
|
755
704
|
/*
|
|
756
705
|
* We use this method to initialize the stamper and create an organization for new sessions.
|
|
757
706
|
* This is the first step when no existing session is found and we need to set up a new wallet.
|
|
758
707
|
*/
|
|
759
|
-
async
|
|
708
|
+
async initializeStamper() {
|
|
760
709
|
this.logger.log("EMBEDDED_PROVIDER", "Initializing stamper");
|
|
761
|
-
|
|
762
|
-
this.logger.log("EMBEDDED_PROVIDER", "
|
|
710
|
+
await this.stamper.init();
|
|
711
|
+
this.logger.log("EMBEDDED_PROVIDER", "Resetting keypair to avoid conflicts with existing keypairs");
|
|
712
|
+
const stamperInfo = await this.stamper.resetKeyPair();
|
|
713
|
+
this.logger.log("EMBEDDED_PROVIDER", "Stamper initialized with fresh keypair", {
|
|
763
714
|
publicKey: stamperInfo.publicKey,
|
|
764
715
|
keyId: stamperInfo.keyId,
|
|
765
716
|
algorithm: this.stamper.algorithm
|
|
766
717
|
});
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
718
|
+
const expiresInMs = AUTHENTICATOR_EXPIRATION_TIME_MS;
|
|
719
|
+
this.logger.info("EMBEDDED_PROVIDER", "Stamper ready for auth flow with fresh keypair", {
|
|
720
|
+
publicKey: stamperInfo.publicKey,
|
|
721
|
+
keyId: stamperInfo.keyId
|
|
722
|
+
});
|
|
723
|
+
return { stamperInfo, expiresInMs };
|
|
724
|
+
}
|
|
725
|
+
async createOrganizationForAppWallet(stamperInfo, expiresInMs) {
|
|
726
|
+
const tempClient = new PhantomClient({
|
|
727
|
+
apiBaseUrl: this.config.apiBaseUrl,
|
|
728
|
+
headers: {
|
|
729
|
+
...this.platform.analyticsHeaders || {}
|
|
730
|
+
}
|
|
731
|
+
});
|
|
777
732
|
const platformName = this.platform.name || "unknown";
|
|
778
733
|
const shortPubKey = stamperInfo.publicKey.slice(0, 8);
|
|
779
734
|
const organizationName = `${this.config.appId.substring(0, 8)}-${platformName}-${shortPubKey}`;
|
|
780
|
-
this.logger.log("EMBEDDED_PROVIDER", "Creating organization", {
|
|
735
|
+
this.logger.log("EMBEDDED_PROVIDER", "Creating organization for app-wallet", {
|
|
781
736
|
organizationName,
|
|
782
737
|
publicKey: stamperInfo.publicKey,
|
|
783
738
|
platform: platformName
|
|
784
739
|
});
|
|
785
740
|
const base64urlPublicKey = base64urlEncode(bs582.decode(stamperInfo.publicKey));
|
|
786
|
-
const expiresInMs = AUTHENTICATOR_EXPIRATION_TIME_MS;
|
|
787
741
|
const username = `user-${randomUUID()}`;
|
|
788
742
|
const { organizationId } = await tempClient.createOrganization(organizationName, [
|
|
789
743
|
{
|
|
@@ -794,27 +748,25 @@ var EmbeddedProvider = class {
|
|
|
794
748
|
authenticatorName: `auth-${shortPubKey}`,
|
|
795
749
|
authenticatorKind: "keypair",
|
|
796
750
|
publicKey: base64urlPublicKey,
|
|
797
|
-
algorithm: "Ed25519"
|
|
798
|
-
|
|
799
|
-
// expiresInMs: expiresInMs,
|
|
751
|
+
algorithm: "Ed25519",
|
|
752
|
+
expiresInMs
|
|
800
753
|
}
|
|
801
754
|
]
|
|
802
755
|
}
|
|
803
756
|
]);
|
|
804
|
-
this.logger.info("EMBEDDED_PROVIDER", "Organization created", { organizationId });
|
|
805
|
-
return
|
|
757
|
+
this.logger.info("EMBEDDED_PROVIDER", "Organization created for app-wallet", { organizationId });
|
|
758
|
+
return organizationId;
|
|
806
759
|
}
|
|
807
760
|
async connect(authOptions) {
|
|
808
761
|
try {
|
|
809
762
|
this.logger.info("EMBEDDED_PROVIDER", "Starting embedded provider connect", {
|
|
810
|
-
authOptions:
|
|
811
|
-
provider: authOptions.provider
|
|
812
|
-
|
|
813
|
-
} : void 0
|
|
763
|
+
authOptions: {
|
|
764
|
+
provider: authOptions.provider
|
|
765
|
+
}
|
|
814
766
|
});
|
|
815
767
|
this.emit("connect_start", {
|
|
816
768
|
source: "manual-connect",
|
|
817
|
-
authOptions:
|
|
769
|
+
authOptions: { provider: authOptions.provider }
|
|
818
770
|
});
|
|
819
771
|
const existingResult = await this.tryExistingConnection(false);
|
|
820
772
|
if (existingResult) {
|
|
@@ -823,23 +775,26 @@ var EmbeddedProvider = class {
|
|
|
823
775
|
addressCount: existingResult.addresses.length
|
|
824
776
|
});
|
|
825
777
|
this.emit("connect", {
|
|
826
|
-
|
|
827
|
-
addresses: existingResult.addresses,
|
|
778
|
+
...existingResult,
|
|
828
779
|
source: "manual-existing"
|
|
829
780
|
});
|
|
830
781
|
return existingResult;
|
|
831
782
|
}
|
|
832
783
|
this.validateAuthOptions(authOptions);
|
|
833
|
-
this.logger.info(
|
|
834
|
-
|
|
835
|
-
|
|
784
|
+
this.logger.info(
|
|
785
|
+
"EMBEDDED_PROVIDER",
|
|
786
|
+
"No existing connection available, creating new auth flow with fresh keypair"
|
|
787
|
+
);
|
|
788
|
+
const { stamperInfo, expiresInMs } = await this.initializeStamper();
|
|
789
|
+
const session = await this.handleAuthFlow(stamperInfo.publicKey, stamperInfo, authOptions, expiresInMs);
|
|
836
790
|
if (!session) {
|
|
837
791
|
return {
|
|
838
792
|
addresses: [],
|
|
839
|
-
status: "pending"
|
|
793
|
+
status: "pending",
|
|
794
|
+
authProvider: authOptions.provider
|
|
840
795
|
};
|
|
841
796
|
}
|
|
842
|
-
if (
|
|
797
|
+
if (this.config.embeddedWalletType === "app-wallet") {
|
|
843
798
|
session.lastUsed = Date.now();
|
|
844
799
|
await this.storage.saveSession(session);
|
|
845
800
|
}
|
|
@@ -848,11 +803,12 @@ var EmbeddedProvider = class {
|
|
|
848
803
|
const result = {
|
|
849
804
|
walletId: this.walletId,
|
|
850
805
|
addresses: this.addresses,
|
|
851
|
-
status: "completed"
|
|
806
|
+
status: "completed",
|
|
807
|
+
authUserId: session?.authUserId,
|
|
808
|
+
authProvider: session?.authProvider
|
|
852
809
|
};
|
|
853
810
|
this.emit("connect", {
|
|
854
|
-
|
|
855
|
-
addresses: this.addresses,
|
|
811
|
+
...result,
|
|
856
812
|
source: "manual"
|
|
857
813
|
});
|
|
858
814
|
return result;
|
|
@@ -893,8 +849,10 @@ var EmbeddedProvider = class {
|
|
|
893
849
|
throw new Error(`Embedded wallet connection failed: ${String(error)}`);
|
|
894
850
|
}
|
|
895
851
|
}
|
|
896
|
-
async disconnect() {
|
|
852
|
+
async disconnect(shouldClearPreviousSession = true) {
|
|
897
853
|
const wasConnected = this.client !== null;
|
|
854
|
+
await this.storage.setShouldClearPreviousSession(shouldClearPreviousSession);
|
|
855
|
+
this.logger.log("EMBEDDED_PROVIDER", "Set flag to clear previous session on next login");
|
|
898
856
|
await this.storage.clearSession();
|
|
899
857
|
this.client = null;
|
|
900
858
|
this.walletId = null;
|
|
@@ -915,12 +873,44 @@ var EmbeddedProvider = class {
|
|
|
915
873
|
walletId: this.walletId,
|
|
916
874
|
message: params.message
|
|
917
875
|
});
|
|
918
|
-
const parsedMessage = parseMessage(params.message);
|
|
919
876
|
const session = await this.storage.getSession();
|
|
920
877
|
const derivationIndex = session?.accountDerivationIndex ?? 0;
|
|
921
|
-
const rawResponse = await this.client.
|
|
878
|
+
const rawResponse = await this.client.signUtf8Message({
|
|
879
|
+
walletId: this.walletId,
|
|
880
|
+
message: params.message,
|
|
881
|
+
networkId: params.networkId,
|
|
882
|
+
derivationIndex
|
|
883
|
+
});
|
|
884
|
+
this.logger.info("EMBEDDED_PROVIDER", "Message signed successfully", {
|
|
885
|
+
walletId: this.walletId,
|
|
886
|
+
message: params.message
|
|
887
|
+
});
|
|
888
|
+
return parseSignMessageResponse(rawResponse, params.networkId);
|
|
889
|
+
}
|
|
890
|
+
async signEthereumMessage(params) {
|
|
891
|
+
if (!this.client || !this.walletId) {
|
|
892
|
+
throw new Error("Not connected");
|
|
893
|
+
}
|
|
894
|
+
await this.ensureValidAuthenticator();
|
|
895
|
+
this.logger.info("EMBEDDED_PROVIDER", "Signing message", {
|
|
896
|
+
walletId: this.walletId,
|
|
897
|
+
message: params.message
|
|
898
|
+
});
|
|
899
|
+
const looksLikeHex = (str) => /^0x[0-9a-fA-F]+$/.test(str);
|
|
900
|
+
const normalizedMessage = (() => {
|
|
901
|
+
if (looksLikeHex(params.message)) {
|
|
902
|
+
const hexPayload = params.message.slice(2);
|
|
903
|
+
const normalizedHex = hexPayload.length % 2 === 0 ? hexPayload : `0${hexPayload}`;
|
|
904
|
+
return Buffer.from(normalizedHex, "hex").toString("utf8");
|
|
905
|
+
}
|
|
906
|
+
return params.message;
|
|
907
|
+
})();
|
|
908
|
+
const base64UrlMessage = stringToBase64url(normalizedMessage);
|
|
909
|
+
const session = await this.storage.getSession();
|
|
910
|
+
const derivationIndex = session?.accountDerivationIndex ?? 0;
|
|
911
|
+
const rawResponse = await this.client.ethereumSignMessage({
|
|
922
912
|
walletId: this.walletId,
|
|
923
|
-
message:
|
|
913
|
+
message: base64UrlMessage,
|
|
924
914
|
networkId: params.networkId,
|
|
925
915
|
derivationIndex
|
|
926
916
|
});
|
|
@@ -930,6 +920,28 @@ var EmbeddedProvider = class {
|
|
|
930
920
|
});
|
|
931
921
|
return parseSignMessageResponse(rawResponse, params.networkId);
|
|
932
922
|
}
|
|
923
|
+
async signTypedDataV4(params) {
|
|
924
|
+
if (!this.client || !this.walletId) {
|
|
925
|
+
throw new Error("Not connected");
|
|
926
|
+
}
|
|
927
|
+
await this.ensureValidAuthenticator();
|
|
928
|
+
this.logger.info("EMBEDDED_PROVIDER", "Signing typed data", {
|
|
929
|
+
walletId: this.walletId,
|
|
930
|
+
typedData: params.typedData
|
|
931
|
+
});
|
|
932
|
+
const session = await this.storage.getSession();
|
|
933
|
+
const derivationIndex = session?.accountDerivationIndex ?? 0;
|
|
934
|
+
const rawResponse = await this.client.ethereumSignTypedData({
|
|
935
|
+
walletId: this.walletId,
|
|
936
|
+
typedData: params.typedData,
|
|
937
|
+
networkId: params.networkId,
|
|
938
|
+
derivationIndex
|
|
939
|
+
});
|
|
940
|
+
this.logger.info("EMBEDDED_PROVIDER", "Typed data signed successfully", {
|
|
941
|
+
walletId: this.walletId
|
|
942
|
+
});
|
|
943
|
+
return parseSignMessageResponse(rawResponse, params.networkId);
|
|
944
|
+
}
|
|
933
945
|
async signTransaction(params) {
|
|
934
946
|
if (!this.client || !this.walletId) {
|
|
935
947
|
throw new Error("Not connected");
|
|
@@ -939,7 +951,7 @@ var EmbeddedProvider = class {
|
|
|
939
951
|
walletId: this.walletId,
|
|
940
952
|
networkId: params.networkId
|
|
941
953
|
});
|
|
942
|
-
const parsedTransaction = await
|
|
954
|
+
const parsedTransaction = await parseToKmsTransaction(params.transaction, params.networkId);
|
|
943
955
|
const session = await this.storage.getSession();
|
|
944
956
|
const derivationIndex = session?.accountDerivationIndex ?? 0;
|
|
945
957
|
this.logger.log("EMBEDDED_PROVIDER", "Parsed transaction for signing", {
|
|
@@ -947,12 +959,20 @@ var EmbeddedProvider = class {
|
|
|
947
959
|
transaction: parsedTransaction,
|
|
948
960
|
derivationIndex
|
|
949
961
|
});
|
|
962
|
+
const transactionPayload = parsedTransaction.parsed;
|
|
963
|
+
if (!transactionPayload) {
|
|
964
|
+
throw new Error("Failed to parse transaction: no valid encoding found");
|
|
965
|
+
}
|
|
966
|
+
const account = this.getAddressForNetwork(params.networkId);
|
|
967
|
+
if (!account) {
|
|
968
|
+
throw new Error(`No address found for network ${params.networkId}`);
|
|
969
|
+
}
|
|
950
970
|
const rawResponse = await this.client.signTransaction({
|
|
951
971
|
walletId: this.walletId,
|
|
952
|
-
transaction:
|
|
972
|
+
transaction: transactionPayload,
|
|
953
973
|
networkId: params.networkId,
|
|
954
974
|
derivationIndex,
|
|
955
|
-
account
|
|
975
|
+
account
|
|
956
976
|
});
|
|
957
977
|
this.logger.info("EMBEDDED_PROVIDER", "Transaction signed successfully", {
|
|
958
978
|
walletId: this.walletId,
|
|
@@ -970,7 +990,7 @@ var EmbeddedProvider = class {
|
|
|
970
990
|
walletId: this.walletId,
|
|
971
991
|
networkId: params.networkId
|
|
972
992
|
});
|
|
973
|
-
const parsedTransaction = await
|
|
993
|
+
const parsedTransaction = await parseToKmsTransaction(params.transaction, params.networkId);
|
|
974
994
|
const session = await this.storage.getSession();
|
|
975
995
|
const derivationIndex = session?.accountDerivationIndex ?? 0;
|
|
976
996
|
this.logger.log("EMBEDDED_PROVIDER", "Parsed transaction for signing", {
|
|
@@ -978,13 +998,29 @@ var EmbeddedProvider = class {
|
|
|
978
998
|
transaction: parsedTransaction,
|
|
979
999
|
derivationIndex
|
|
980
1000
|
});
|
|
981
|
-
const
|
|
982
|
-
|
|
983
|
-
transaction:
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
1001
|
+
const transactionPayload = parsedTransaction.parsed;
|
|
1002
|
+
if (!transactionPayload) {
|
|
1003
|
+
throw new Error("Failed to parse transaction: no valid encoding found");
|
|
1004
|
+
}
|
|
1005
|
+
const account = this.getAddressForNetwork(params.networkId);
|
|
1006
|
+
if (!account) {
|
|
1007
|
+
throw new Error(`No address found for network ${params.networkId}`);
|
|
1008
|
+
}
|
|
1009
|
+
let rawResponse;
|
|
1010
|
+
try {
|
|
1011
|
+
rawResponse = await this.client.signAndSendTransaction({
|
|
1012
|
+
walletId: this.walletId,
|
|
1013
|
+
transaction: transactionPayload,
|
|
1014
|
+
networkId: params.networkId,
|
|
1015
|
+
derivationIndex,
|
|
1016
|
+
account
|
|
1017
|
+
});
|
|
1018
|
+
} catch (error) {
|
|
1019
|
+
if (error instanceof SpendingLimitError) {
|
|
1020
|
+
this.emit("spending_limit_reached", { error });
|
|
1021
|
+
}
|
|
1022
|
+
throw error;
|
|
1023
|
+
}
|
|
988
1024
|
this.logger.info("EMBEDDED_PROVIDER", "Transaction signed and sent successfully", {
|
|
989
1025
|
walletId: this.walletId,
|
|
990
1026
|
networkId: params.networkId,
|
|
@@ -1004,24 +1040,25 @@ var EmbeddedProvider = class {
|
|
|
1004
1040
|
* It handles app-wallet creation directly or routes to JWT/redirect authentication for user-wallets.
|
|
1005
1041
|
* Returns null for redirect flows since they don't complete synchronously.
|
|
1006
1042
|
*/
|
|
1007
|
-
async handleAuthFlow(
|
|
1043
|
+
async handleAuthFlow(publicKey, stamperInfo, authOptions, expiresInMs) {
|
|
1008
1044
|
if (this.config.embeddedWalletType === "user-wallet") {
|
|
1009
1045
|
this.logger.info("EMBEDDED_PROVIDER", "Creating user-wallet, routing authentication", {
|
|
1010
|
-
authProvider: authOptions
|
|
1046
|
+
authProvider: authOptions.provider
|
|
1011
1047
|
});
|
|
1012
|
-
if (authOptions
|
|
1013
|
-
return await this.
|
|
1048
|
+
if (authOptions.provider === "phantom") {
|
|
1049
|
+
return await this.handlePhantomAuth(publicKey, stamperInfo, expiresInMs);
|
|
1014
1050
|
} else {
|
|
1015
1051
|
this.logger.info("EMBEDDED_PROVIDER", "Starting redirect-based authentication flow", {
|
|
1016
|
-
|
|
1052
|
+
publicKey,
|
|
1017
1053
|
provider: authOptions?.provider
|
|
1018
1054
|
});
|
|
1019
|
-
return await this.handleRedirectAuth(
|
|
1055
|
+
return await this.handleRedirectAuth(publicKey, stamperInfo, authOptions);
|
|
1020
1056
|
}
|
|
1021
1057
|
} else {
|
|
1022
1058
|
this.logger.info("EMBEDDED_PROVIDER", "Creating app-wallet", {
|
|
1023
|
-
|
|
1059
|
+
publicKey
|
|
1024
1060
|
});
|
|
1061
|
+
const organizationId = await this.createOrganizationForAppWallet(stamperInfo, expiresInMs);
|
|
1025
1062
|
const tempClient = new PhantomClient(
|
|
1026
1063
|
{
|
|
1027
1064
|
apiBaseUrl: this.config.apiBaseUrl,
|
|
@@ -1041,8 +1078,8 @@ var EmbeddedProvider = class {
|
|
|
1041
1078
|
organizationId,
|
|
1042
1079
|
appId: this.config.appId,
|
|
1043
1080
|
stamperInfo,
|
|
1044
|
-
authProvider: "
|
|
1045
|
-
|
|
1081
|
+
authProvider: "device",
|
|
1082
|
+
// For now app wallets have no auth provider.
|
|
1046
1083
|
accountDerivationIndex: 0,
|
|
1047
1084
|
// App wallets default to index 0
|
|
1048
1085
|
status: "completed",
|
|
@@ -1050,8 +1087,7 @@ var EmbeddedProvider = class {
|
|
|
1050
1087
|
lastUsed: now,
|
|
1051
1088
|
authenticatorCreatedAt: now,
|
|
1052
1089
|
authenticatorExpiresAt: Date.now() + expiresInMs,
|
|
1053
|
-
lastRenewalAttempt: void 0
|
|
1054
|
-
username
|
|
1090
|
+
lastRenewalAttempt: void 0
|
|
1055
1091
|
};
|
|
1056
1092
|
await this.storage.saveSession(session);
|
|
1057
1093
|
this.logger.info("EMBEDDED_PROVIDER", "App-wallet created successfully", { walletId, organizationId });
|
|
@@ -1059,43 +1095,56 @@ var EmbeddedProvider = class {
|
|
|
1059
1095
|
}
|
|
1060
1096
|
}
|
|
1061
1097
|
/*
|
|
1062
|
-
* We use this method to handle
|
|
1063
|
-
*
|
|
1098
|
+
* We use this method to handle Phantom app-based authentication for user-wallets.
|
|
1099
|
+
* This method uses the PhantomAppProvider to authenticate via the browser extension or mobile app.
|
|
1100
|
+
*
|
|
1101
|
+
* NOTE: Mobile deeplink support is not yet implemented. If we wanted to support mobile deeplinks,
|
|
1102
|
+
* we would:
|
|
1103
|
+
* 1. Check if the app provider is available using phantomAppProvider.isAvailable()
|
|
1104
|
+
* 2. If not available, generate a deeplink (phantom://auth?...)
|
|
1105
|
+
* 3. Save a pending session before opening the deeplink
|
|
1106
|
+
* 4. Start a polling mechanism to check for auth completion
|
|
1107
|
+
* 5. Update the session when the mobile app completes the auth
|
|
1064
1108
|
*/
|
|
1065
|
-
async
|
|
1066
|
-
this.logger.info("EMBEDDED_PROVIDER", "
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1109
|
+
async handlePhantomAuth(publicKey, stamperInfo, expiresInMs) {
|
|
1110
|
+
this.logger.info("EMBEDDED_PROVIDER", "Starting Phantom authentication flow");
|
|
1111
|
+
const isAvailable = this.phantomAppProvider.isAvailable();
|
|
1112
|
+
if (!isAvailable) {
|
|
1113
|
+
this.logger.error("EMBEDDED_PROVIDER", "Phantom app not available");
|
|
1114
|
+
throw new Error(
|
|
1115
|
+
"Phantom app is not available. Please install the Phantom browser extension or mobile app to use this authentication method."
|
|
1116
|
+
);
|
|
1070
1117
|
}
|
|
1071
|
-
this.logger.
|
|
1072
|
-
const
|
|
1073
|
-
|
|
1118
|
+
this.logger.info("EMBEDDED_PROVIDER", "Phantom app detected, proceeding with authentication");
|
|
1119
|
+
const sessionId = generateSessionId();
|
|
1120
|
+
const authResult = await this.phantomAppProvider.authenticate({
|
|
1121
|
+
publicKey,
|
|
1074
1122
|
appId: this.config.appId,
|
|
1075
|
-
|
|
1076
|
-
customAuthData: authOptions.customAuthData
|
|
1123
|
+
sessionId
|
|
1077
1124
|
});
|
|
1078
|
-
|
|
1079
|
-
|
|
1125
|
+
this.logger.info("EMBEDDED_PROVIDER", "Phantom authentication completed", {
|
|
1126
|
+
walletId: authResult.walletId,
|
|
1127
|
+
organizationId: authResult.organizationId
|
|
1128
|
+
});
|
|
1129
|
+
const effectiveExpiresInMs = authResult.expiresInMs > 0 ? authResult.expiresInMs : expiresInMs;
|
|
1080
1130
|
const now = Date.now();
|
|
1081
1131
|
const session = {
|
|
1082
|
-
sessionId
|
|
1083
|
-
walletId,
|
|
1084
|
-
organizationId,
|
|
1132
|
+
sessionId,
|
|
1133
|
+
walletId: authResult.walletId,
|
|
1134
|
+
organizationId: authResult.organizationId,
|
|
1085
1135
|
appId: this.config.appId,
|
|
1086
1136
|
stamperInfo,
|
|
1087
|
-
authProvider:
|
|
1088
|
-
userInfo: authResult.userInfo,
|
|
1137
|
+
authProvider: "phantom",
|
|
1089
1138
|
accountDerivationIndex: authResult.accountDerivationIndex,
|
|
1090
1139
|
status: "completed",
|
|
1091
1140
|
createdAt: now,
|
|
1092
1141
|
lastUsed: now,
|
|
1093
1142
|
authenticatorCreatedAt: now,
|
|
1094
|
-
authenticatorExpiresAt:
|
|
1143
|
+
authenticatorExpiresAt: now + effectiveExpiresInMs,
|
|
1095
1144
|
lastRenewalAttempt: void 0,
|
|
1096
|
-
|
|
1145
|
+
authUserId: authResult.authUserId
|
|
1097
1146
|
};
|
|
1098
|
-
this.logger.log("EMBEDDED_PROVIDER", "Saving
|
|
1147
|
+
this.logger.log("EMBEDDED_PROVIDER", "Saving Phantom session");
|
|
1099
1148
|
await this.storage.saveSession(session);
|
|
1100
1149
|
return session;
|
|
1101
1150
|
}
|
|
@@ -1104,9 +1153,9 @@ var EmbeddedProvider = class {
|
|
|
1104
1153
|
* It saves a temporary session before redirecting to prevent losing state during the redirect flow.
|
|
1105
1154
|
* Session timestamp is updated before redirect to prevent race conditions.
|
|
1106
1155
|
*/
|
|
1107
|
-
async handleRedirectAuth(
|
|
1156
|
+
async handleRedirectAuth(publicKey, stamperInfo, authOptions) {
|
|
1108
1157
|
this.logger.info("EMBEDDED_PROVIDER", "Using Phantom Connect authentication flow (redirect-based)", {
|
|
1109
|
-
provider: authOptions
|
|
1158
|
+
provider: authOptions.provider,
|
|
1110
1159
|
hasRedirectUrl: !!this.config.authOptions.redirectUrl,
|
|
1111
1160
|
authUrl: this.config.authOptions.authUrl
|
|
1112
1161
|
});
|
|
@@ -1114,13 +1163,13 @@ var EmbeddedProvider = class {
|
|
|
1114
1163
|
const sessionId = generateSessionId();
|
|
1115
1164
|
const tempSession = {
|
|
1116
1165
|
sessionId,
|
|
1117
|
-
walletId: `temp-${now}`,
|
|
1166
|
+
walletId: `temp-wallet-${now}`,
|
|
1167
|
+
// Temporary ID, will be updated after redirect
|
|
1168
|
+
organizationId: `temp-org-${now}`,
|
|
1118
1169
|
// Temporary ID, will be updated after redirect
|
|
1119
|
-
organizationId,
|
|
1120
1170
|
appId: this.config.appId,
|
|
1121
1171
|
stamperInfo,
|
|
1122
|
-
authProvider:
|
|
1123
|
-
userInfo: { provider: authOptions?.provider },
|
|
1172
|
+
authProvider: authOptions.provider,
|
|
1124
1173
|
accountDerivationIndex: void 0,
|
|
1125
1174
|
// Will be set when redirect completes
|
|
1126
1175
|
status: "pending",
|
|
@@ -1128,8 +1177,7 @@ var EmbeddedProvider = class {
|
|
|
1128
1177
|
lastUsed: now,
|
|
1129
1178
|
authenticatorCreatedAt: now,
|
|
1130
1179
|
authenticatorExpiresAt: now + AUTHENTICATOR_EXPIRATION_TIME_MS,
|
|
1131
|
-
lastRenewalAttempt: void 0
|
|
1132
|
-
username: username || `user-${randomUUID()}`
|
|
1180
|
+
lastRenewalAttempt: void 0
|
|
1133
1181
|
};
|
|
1134
1182
|
this.logger.log("EMBEDDED_PROVIDER", "Saving temporary session before redirect", {
|
|
1135
1183
|
sessionId: tempSession.sessionId,
|
|
@@ -1137,32 +1185,53 @@ var EmbeddedProvider = class {
|
|
|
1137
1185
|
});
|
|
1138
1186
|
tempSession.lastUsed = Date.now();
|
|
1139
1187
|
await this.storage.saveSession(tempSession);
|
|
1188
|
+
const shouldClearPreviousSession = await this.storage.getShouldClearPreviousSession();
|
|
1140
1189
|
this.logger.info("EMBEDDED_PROVIDER", "Starting Phantom Connect redirect", {
|
|
1141
|
-
|
|
1190
|
+
publicKey,
|
|
1142
1191
|
appId: this.config.appId,
|
|
1143
1192
|
provider: authOptions?.provider,
|
|
1144
|
-
authUrl: this.config.authOptions.authUrl
|
|
1193
|
+
authUrl: this.config.authOptions.authUrl,
|
|
1194
|
+
clearPreviousSession: shouldClearPreviousSession,
|
|
1195
|
+
allowRefresh: !shouldClearPreviousSession
|
|
1145
1196
|
});
|
|
1146
1197
|
const authResult = await this.authProvider.authenticate({
|
|
1147
|
-
|
|
1198
|
+
publicKey,
|
|
1148
1199
|
appId: this.config.appId,
|
|
1149
1200
|
provider: authOptions?.provider,
|
|
1150
1201
|
redirectUrl: this.config.authOptions.redirectUrl,
|
|
1151
|
-
customAuthData: authOptions?.customAuthData,
|
|
1152
1202
|
authUrl: this.config.authOptions.authUrl,
|
|
1153
|
-
sessionId
|
|
1203
|
+
sessionId,
|
|
1204
|
+
// OAuth session management - defaults to allowing refresh unless user explicitly logged out
|
|
1205
|
+
clearPreviousSession: shouldClearPreviousSession,
|
|
1206
|
+
// true only after logout
|
|
1207
|
+
allowRefresh: !shouldClearPreviousSession
|
|
1208
|
+
// false only after logout
|
|
1154
1209
|
});
|
|
1155
1210
|
if (authResult && "walletId" in authResult) {
|
|
1156
1211
|
this.logger.info("EMBEDDED_PROVIDER", "Authentication completed after redirect", {
|
|
1157
1212
|
walletId: authResult.walletId,
|
|
1213
|
+
organizationId: authResult.organizationId,
|
|
1158
1214
|
provider: authResult.provider
|
|
1159
1215
|
});
|
|
1160
1216
|
tempSession.walletId = authResult.walletId;
|
|
1217
|
+
tempSession.organizationId = authResult.organizationId;
|
|
1161
1218
|
tempSession.authProvider = authResult.provider || tempSession.authProvider;
|
|
1162
1219
|
tempSession.accountDerivationIndex = authResult.accountDerivationIndex;
|
|
1220
|
+
tempSession.authUserId = authResult.authUserId;
|
|
1163
1221
|
tempSession.status = "completed";
|
|
1164
1222
|
tempSession.lastUsed = Date.now();
|
|
1223
|
+
if (authResult.expiresInMs > 0) {
|
|
1224
|
+
const now2 = Date.now();
|
|
1225
|
+
tempSession.authenticatorCreatedAt = now2;
|
|
1226
|
+
tempSession.authenticatorExpiresAt = now2 + authResult.expiresInMs;
|
|
1227
|
+
this.logger.log("EMBEDDED_PROVIDER", "Updated authenticator expiration from immediate auth response", {
|
|
1228
|
+
expiresInMs: authResult.expiresInMs,
|
|
1229
|
+
expiresAt: new Date(tempSession.authenticatorExpiresAt).toISOString()
|
|
1230
|
+
});
|
|
1231
|
+
}
|
|
1165
1232
|
await this.storage.saveSession(tempSession);
|
|
1233
|
+
await this.storage.setShouldClearPreviousSession(false);
|
|
1234
|
+
this.logger.log("EMBEDDED_PROVIDER", "Cleared logout flag after successful authentication");
|
|
1166
1235
|
return tempSession;
|
|
1167
1236
|
}
|
|
1168
1237
|
this.logger.info("EMBEDDED_PROVIDER", "Redirect authentication initiated, waiting for redirect completion");
|
|
@@ -1175,16 +1244,31 @@ var EmbeddedProvider = class {
|
|
|
1175
1244
|
}
|
|
1176
1245
|
session.walletId = authResult.walletId;
|
|
1177
1246
|
session.authProvider = authResult.provider || session.authProvider;
|
|
1247
|
+
session.organizationId = authResult.organizationId;
|
|
1178
1248
|
session.accountDerivationIndex = authResult.accountDerivationIndex;
|
|
1249
|
+
session.authUserId = authResult.authUserId;
|
|
1179
1250
|
session.status = "completed";
|
|
1180
1251
|
session.lastUsed = Date.now();
|
|
1252
|
+
if (authResult.expiresInMs > 0) {
|
|
1253
|
+
const now = Date.now();
|
|
1254
|
+
session.authenticatorCreatedAt = now;
|
|
1255
|
+
session.authenticatorExpiresAt = now + authResult.expiresInMs;
|
|
1256
|
+
this.logger.log("EMBEDDED_PROVIDER", "Updated authenticator expiration from auth response", {
|
|
1257
|
+
expiresInMs: authResult.expiresInMs,
|
|
1258
|
+
expiresAt: new Date(session.authenticatorExpiresAt).toISOString()
|
|
1259
|
+
});
|
|
1260
|
+
}
|
|
1181
1261
|
await this.storage.saveSession(session);
|
|
1262
|
+
await this.storage.setShouldClearPreviousSession(false);
|
|
1263
|
+
this.logger.log("EMBEDDED_PROVIDER", "Cleared logout flag after successful authentication");
|
|
1182
1264
|
await this.initializeClientFromSession(session);
|
|
1183
1265
|
await this.ensureValidAuthenticator();
|
|
1184
1266
|
return {
|
|
1185
1267
|
walletId: this.walletId,
|
|
1186
1268
|
addresses: this.addresses,
|
|
1187
|
-
status: "completed"
|
|
1269
|
+
status: "completed",
|
|
1270
|
+
authUserId: session.authUserId,
|
|
1271
|
+
authProvider: session.authProvider
|
|
1188
1272
|
};
|
|
1189
1273
|
}
|
|
1190
1274
|
/*
|
|
@@ -1199,7 +1283,7 @@ var EmbeddedProvider = class {
|
|
|
1199
1283
|
const now = Date.now();
|
|
1200
1284
|
if (!session.authenticatorExpiresAt) {
|
|
1201
1285
|
this.logger.warn("EMBEDDED_PROVIDER", "Session missing authenticator timing - treating as invalid session");
|
|
1202
|
-
await this.disconnect();
|
|
1286
|
+
await this.disconnect(false);
|
|
1203
1287
|
throw new Error("Invalid session - missing authenticator timing");
|
|
1204
1288
|
}
|
|
1205
1289
|
const timeUntilExpiry = session.authenticatorExpiresAt - now;
|
|
@@ -1209,86 +1293,9 @@ var EmbeddedProvider = class {
|
|
|
1209
1293
|
});
|
|
1210
1294
|
if (timeUntilExpiry <= 0) {
|
|
1211
1295
|
this.logger.error("EMBEDDED_PROVIDER", "Authenticator has expired, disconnecting");
|
|
1212
|
-
await this.disconnect();
|
|
1296
|
+
await this.disconnect(false);
|
|
1213
1297
|
throw new Error("Authenticator expired");
|
|
1214
1298
|
}
|
|
1215
|
-
const renewalWindow = AUTHENTICATOR_RENEWAL_WINDOW_MS;
|
|
1216
|
-
if (timeUntilExpiry <= renewalWindow) {
|
|
1217
|
-
this.logger.info("EMBEDDED_PROVIDER", "Authenticator needs renewal", {
|
|
1218
|
-
expiresAt: new Date(session.authenticatorExpiresAt).toISOString(),
|
|
1219
|
-
timeUntilExpiry,
|
|
1220
|
-
renewalWindow
|
|
1221
|
-
});
|
|
1222
|
-
try {
|
|
1223
|
-
await this.renewAuthenticator(session);
|
|
1224
|
-
this.logger.info("EMBEDDED_PROVIDER", "Authenticator renewed successfully");
|
|
1225
|
-
} catch (error) {
|
|
1226
|
-
this.logger.error("EMBEDDED_PROVIDER", "Failed to renew authenticator", {
|
|
1227
|
-
error: error instanceof Error ? error.message : String(error)
|
|
1228
|
-
});
|
|
1229
|
-
}
|
|
1230
|
-
}
|
|
1231
|
-
}
|
|
1232
|
-
/*
|
|
1233
|
-
* We use this method to perform silent authenticator renewal.
|
|
1234
|
-
* It generates a new keypair, creates a new authenticator, and switches to it.
|
|
1235
|
-
*/
|
|
1236
|
-
async renewAuthenticator(session) {
|
|
1237
|
-
if (!this.client) {
|
|
1238
|
-
throw new Error("Client not initialized");
|
|
1239
|
-
}
|
|
1240
|
-
this.logger.info("EMBEDDED_PROVIDER", "Starting authenticator renewal");
|
|
1241
|
-
try {
|
|
1242
|
-
const newKeyInfo = await this.stamper.rotateKeyPair();
|
|
1243
|
-
this.logger.log("EMBEDDED_PROVIDER", "Generated new keypair for renewal", {
|
|
1244
|
-
newKeyId: newKeyInfo.keyId,
|
|
1245
|
-
newPublicKey: newKeyInfo.publicKey
|
|
1246
|
-
});
|
|
1247
|
-
const base64urlPublicKey = base64urlEncode(bs582.decode(newKeyInfo.publicKey));
|
|
1248
|
-
const expiresInMs = AUTHENTICATOR_EXPIRATION_TIME_MS;
|
|
1249
|
-
let authenticatorResult;
|
|
1250
|
-
try {
|
|
1251
|
-
authenticatorResult = await this.client.createAuthenticator({
|
|
1252
|
-
organizationId: session.organizationId,
|
|
1253
|
-
username: session.username,
|
|
1254
|
-
authenticatorName: `auth-${newKeyInfo.keyId.substring(0, 8)}`,
|
|
1255
|
-
authenticator: {
|
|
1256
|
-
authenticatorName: `auth-${newKeyInfo.keyId.substring(0, 8)}`,
|
|
1257
|
-
authenticatorKind: "keypair",
|
|
1258
|
-
publicKey: base64urlPublicKey,
|
|
1259
|
-
algorithm: "Ed25519"
|
|
1260
|
-
// Commented for now until KMS supports fully expiring organizations
|
|
1261
|
-
// expiresInMs: expiresInMs,
|
|
1262
|
-
},
|
|
1263
|
-
replaceExpirable: true
|
|
1264
|
-
});
|
|
1265
|
-
} catch (error) {
|
|
1266
|
-
this.logger.error("EMBEDDED_PROVIDER", "Failed to create new authenticator", {
|
|
1267
|
-
error: error instanceof Error ? error.message : String(error)
|
|
1268
|
-
});
|
|
1269
|
-
await this.stamper.rollbackRotation();
|
|
1270
|
-
throw new Error(
|
|
1271
|
-
`Failed to create new authenticator: ${error instanceof Error ? error.message : String(error)}`
|
|
1272
|
-
);
|
|
1273
|
-
}
|
|
1274
|
-
this.logger.info("EMBEDDED_PROVIDER", "Created new authenticator", {
|
|
1275
|
-
authenticatorId: authenticatorResult.id
|
|
1276
|
-
});
|
|
1277
|
-
await this.stamper.commitRotation(authenticatorResult.id || "unknown");
|
|
1278
|
-
const now = Date.now();
|
|
1279
|
-
session.stamperInfo = newKeyInfo;
|
|
1280
|
-
session.authenticatorCreatedAt = now;
|
|
1281
|
-
session.authenticatorExpiresAt = Date.now() + expiresInMs;
|
|
1282
|
-
session.lastRenewalAttempt = now;
|
|
1283
|
-
await this.storage.saveSession(session);
|
|
1284
|
-
this.logger.info("EMBEDDED_PROVIDER", "Authenticator renewal completed successfully", {
|
|
1285
|
-
newKeyId: newKeyInfo.keyId,
|
|
1286
|
-
expiresAt: new Date(Date.now() + expiresInMs).toISOString()
|
|
1287
|
-
});
|
|
1288
|
-
} catch (error) {
|
|
1289
|
-
await this.stamper.rollbackRotation();
|
|
1290
|
-
throw error;
|
|
1291
|
-
}
|
|
1292
1299
|
}
|
|
1293
1300
|
/*
|
|
1294
1301
|
* We use this method to initialize the PhantomClient and fetch wallet addresses from a completed session.
|
|
@@ -1320,10 +1327,10 @@ var EmbeddedProvider = class {
|
|
|
1320
1327
|
export {
|
|
1321
1328
|
AUTHENTICATOR_EXPIRATION_TIME_MS,
|
|
1322
1329
|
AUTHENTICATOR_RENEWAL_WINDOW_MS,
|
|
1330
|
+
EMBEDDED_PROVIDER_AUTH_TYPES,
|
|
1323
1331
|
EmbeddedEthereumChain,
|
|
1324
1332
|
EmbeddedProvider,
|
|
1325
1333
|
EmbeddedSolanaChain,
|
|
1326
|
-
JWTAuth,
|
|
1327
1334
|
generateSessionId,
|
|
1328
1335
|
retryWithBackoff
|
|
1329
1336
|
};
|