@phantom/embedded-provider-core 1.0.0-beta.9 → 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.js
CHANGED
|
@@ -32,146 +32,33 @@ var src_exports = {};
|
|
|
32
32
|
__export(src_exports, {
|
|
33
33
|
AUTHENTICATOR_EXPIRATION_TIME_MS: () => AUTHENTICATOR_EXPIRATION_TIME_MS,
|
|
34
34
|
AUTHENTICATOR_RENEWAL_WINDOW_MS: () => AUTHENTICATOR_RENEWAL_WINDOW_MS,
|
|
35
|
+
EMBEDDED_PROVIDER_AUTH_TYPES: () => EMBEDDED_PROVIDER_AUTH_TYPES,
|
|
35
36
|
EmbeddedEthereumChain: () => EmbeddedEthereumChain,
|
|
36
37
|
EmbeddedProvider: () => EmbeddedProvider,
|
|
37
38
|
EmbeddedSolanaChain: () => EmbeddedSolanaChain,
|
|
38
|
-
JWTAuth: () => JWTAuth,
|
|
39
39
|
generateSessionId: () => generateSessionId,
|
|
40
40
|
retryWithBackoff: () => retryWithBackoff
|
|
41
41
|
});
|
|
42
42
|
module.exports = __toCommonJS(src_exports);
|
|
43
43
|
|
|
44
44
|
// src/embedded-provider.ts
|
|
45
|
+
var import_base64url = require("@phantom/base64url");
|
|
45
46
|
var import_client = require("@phantom/client");
|
|
47
|
+
var import_parsers2 = require("@phantom/parsers");
|
|
46
48
|
var import_utils = require("@phantom/utils");
|
|
47
|
-
var
|
|
49
|
+
var import_buffer = require("buffer");
|
|
48
50
|
var import_bs582 = __toESM(require("bs58"));
|
|
49
|
-
var import_parsers = require("@phantom/parsers");
|
|
50
51
|
|
|
51
52
|
// src/constants.ts
|
|
52
53
|
var AUTHENTICATOR_EXPIRATION_TIME_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
53
54
|
var AUTHENTICATOR_RENEWAL_WINDOW_MS = 2 * 24 * 60 * 60 * 1e3;
|
|
54
|
-
|
|
55
|
-
// src/auth/jwt-auth.ts
|
|
56
|
-
var JWTAuth = class {
|
|
57
|
-
async authenticate(options) {
|
|
58
|
-
if (!options.jwtToken || typeof options.jwtToken !== "string") {
|
|
59
|
-
throw new Error("Invalid JWT token: token must be a non-empty string");
|
|
60
|
-
}
|
|
61
|
-
const jwtParts = options.jwtToken.split(".");
|
|
62
|
-
if (jwtParts.length !== 3) {
|
|
63
|
-
throw new Error("Invalid JWT token format: token must have 3 parts separated by dots");
|
|
64
|
-
}
|
|
65
|
-
try {
|
|
66
|
-
const response = await fetch("/api/auth/jwt", {
|
|
67
|
-
method: "POST",
|
|
68
|
-
headers: {
|
|
69
|
-
"Content-Type": "application/json",
|
|
70
|
-
Authorization: `Bearer ${options.jwtToken}`,
|
|
71
|
-
"X-PHANTOM-APPID": options.appId
|
|
72
|
-
},
|
|
73
|
-
body: JSON.stringify({
|
|
74
|
-
appId: options.appId,
|
|
75
|
-
customAuthData: options.customAuthData
|
|
76
|
-
})
|
|
77
|
-
});
|
|
78
|
-
if (!response.ok) {
|
|
79
|
-
let errorMessage = `HTTP ${response.status}`;
|
|
80
|
-
try {
|
|
81
|
-
const errorData = await response.json();
|
|
82
|
-
errorMessage = errorData.message || errorData.error || errorMessage;
|
|
83
|
-
} catch {
|
|
84
|
-
errorMessage = response.statusText || errorMessage;
|
|
85
|
-
}
|
|
86
|
-
switch (response.status) {
|
|
87
|
-
case 400:
|
|
88
|
-
throw new Error(`Invalid JWT authentication request: ${errorMessage}`);
|
|
89
|
-
case 401:
|
|
90
|
-
throw new Error(`JWT token is invalid or expired: ${errorMessage}`);
|
|
91
|
-
case 403:
|
|
92
|
-
throw new Error(`JWT authentication forbidden: ${errorMessage}`);
|
|
93
|
-
case 404:
|
|
94
|
-
throw new Error(`JWT authentication endpoint not found: ${errorMessage}`);
|
|
95
|
-
case 429:
|
|
96
|
-
throw new Error(`Too many JWT authentication requests: ${errorMessage}`);
|
|
97
|
-
case 500:
|
|
98
|
-
case 502:
|
|
99
|
-
case 503:
|
|
100
|
-
case 504:
|
|
101
|
-
throw new Error(`JWT authentication server error: ${errorMessage}`);
|
|
102
|
-
default:
|
|
103
|
-
throw new Error(`JWT authentication failed: ${errorMessage}`);
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
let result;
|
|
107
|
-
try {
|
|
108
|
-
result = await response.json();
|
|
109
|
-
} catch (parseError) {
|
|
110
|
-
throw new Error("Invalid response from JWT authentication server: response is not valid JSON");
|
|
111
|
-
}
|
|
112
|
-
if (!result.walletId) {
|
|
113
|
-
throw new Error("Invalid JWT authentication response: missing walletId");
|
|
114
|
-
}
|
|
115
|
-
return {
|
|
116
|
-
walletId: result.walletId,
|
|
117
|
-
provider: "jwt",
|
|
118
|
-
userInfo: result.userInfo || {}
|
|
119
|
-
};
|
|
120
|
-
} catch (error) {
|
|
121
|
-
if (error instanceof TypeError && error.message.includes("fetch")) {
|
|
122
|
-
throw new Error("JWT authentication failed: network error or invalid endpoint");
|
|
123
|
-
}
|
|
124
|
-
if (error instanceof Error) {
|
|
125
|
-
throw error;
|
|
126
|
-
}
|
|
127
|
-
throw new Error(`JWT authentication error: ${String(error)}`);
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
};
|
|
131
|
-
|
|
132
|
-
// src/utils/session.ts
|
|
133
|
-
function generateSessionId() {
|
|
134
|
-
return "session_" + Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15) + "_" + Date.now();
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
// src/utils/retry.ts
|
|
138
|
-
async function retryWithBackoff(operation, operationName, logger, maxRetries = 3, baseDelay = 1e3) {
|
|
139
|
-
let lastError;
|
|
140
|
-
for (let attempt = 1; attempt <= maxRetries; attempt++) {
|
|
141
|
-
try {
|
|
142
|
-
logger.log("EMBEDDED_PROVIDER", `Attempting ${operationName}`, {
|
|
143
|
-
attempt,
|
|
144
|
-
maxRetries
|
|
145
|
-
});
|
|
146
|
-
return await operation();
|
|
147
|
-
} catch (error) {
|
|
148
|
-
lastError = error;
|
|
149
|
-
logger.warn("EMBEDDED_PROVIDER", `${operationName} failed`, {
|
|
150
|
-
attempt,
|
|
151
|
-
maxRetries,
|
|
152
|
-
error: error instanceof Error ? error.message : String(error)
|
|
153
|
-
});
|
|
154
|
-
if (attempt === maxRetries) {
|
|
155
|
-
logger.error("EMBEDDED_PROVIDER", `${operationName} failed after ${maxRetries} attempts`, {
|
|
156
|
-
finalError: error instanceof Error ? error.message : String(error)
|
|
157
|
-
});
|
|
158
|
-
break;
|
|
159
|
-
}
|
|
160
|
-
const delay = baseDelay * Math.pow(2, attempt - 1);
|
|
161
|
-
logger.log("EMBEDDED_PROVIDER", `Retrying ${operationName} in ${delay}ms`, {
|
|
162
|
-
attempt: attempt + 1,
|
|
163
|
-
delay
|
|
164
|
-
});
|
|
165
|
-
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
throw lastError;
|
|
169
|
-
}
|
|
55
|
+
var EMBEDDED_PROVIDER_AUTH_TYPES = ["google", "apple", "phantom"];
|
|
170
56
|
|
|
171
57
|
// src/chains/SolanaChain.ts
|
|
172
58
|
var import_eventemitter3 = require("eventemitter3");
|
|
173
59
|
var import_constants = require("@phantom/constants");
|
|
174
60
|
var import_bs58 = __toESM(require("bs58"));
|
|
61
|
+
var import_parsers = require("@phantom/parsers");
|
|
175
62
|
var EmbeddedSolanaChain = class {
|
|
176
63
|
constructor(provider) {
|
|
177
64
|
this.provider = provider;
|
|
@@ -208,8 +95,17 @@ var EmbeddedSolanaChain = class {
|
|
|
208
95
|
publicKey: this._publicKey || ""
|
|
209
96
|
};
|
|
210
97
|
}
|
|
211
|
-
signTransaction(
|
|
212
|
-
|
|
98
|
+
async signTransaction(transaction) {
|
|
99
|
+
this.ensureConnected();
|
|
100
|
+
const result = await this.provider.signTransaction({
|
|
101
|
+
transaction,
|
|
102
|
+
networkId: this.currentNetworkId
|
|
103
|
+
});
|
|
104
|
+
const signedTransaction = (0, import_parsers.parseSolanaSignedTransaction)(result.rawTransaction);
|
|
105
|
+
if (!signedTransaction) {
|
|
106
|
+
throw new Error("Failed to parse signed transaction");
|
|
107
|
+
}
|
|
108
|
+
return signedTransaction;
|
|
213
109
|
}
|
|
214
110
|
async signAndSendTransaction(transaction) {
|
|
215
111
|
this.ensureConnected();
|
|
@@ -222,8 +118,10 @@ var EmbeddedSolanaChain = class {
|
|
|
222
118
|
}
|
|
223
119
|
return { signature: result.hash };
|
|
224
120
|
}
|
|
225
|
-
signAllTransactions(
|
|
226
|
-
|
|
121
|
+
async signAllTransactions(transactions) {
|
|
122
|
+
this.ensureConnected();
|
|
123
|
+
const results = await Promise.all(transactions.map((tx) => this.signTransaction(tx)));
|
|
124
|
+
return results;
|
|
227
125
|
}
|
|
228
126
|
async signAndSendAllTransactions(transactions) {
|
|
229
127
|
const results = await Promise.all(transactions.map((tx) => this.signAndSendTransaction(tx)));
|
|
@@ -293,10 +191,8 @@ var EmbeddedSolanaChain = class {
|
|
|
293
191
|
};
|
|
294
192
|
|
|
295
193
|
// src/chains/EthereumChain.ts
|
|
296
|
-
var import_eventemitter32 = require("eventemitter3");
|
|
297
194
|
var import_constants2 = require("@phantom/constants");
|
|
298
|
-
var
|
|
299
|
-
var import_buffer = require("buffer");
|
|
195
|
+
var import_eventemitter32 = require("eventemitter3");
|
|
300
196
|
var EmbeddedEthereumChain = class {
|
|
301
197
|
constructor(provider) {
|
|
302
198
|
this.provider = provider;
|
|
@@ -354,18 +250,25 @@ var EmbeddedEthereumChain = class {
|
|
|
354
250
|
});
|
|
355
251
|
}
|
|
356
252
|
async signTransaction(transaction) {
|
|
253
|
+
let networkId = this.currentNetworkId;
|
|
254
|
+
if (transaction.chainId) {
|
|
255
|
+
const numericChainId = typeof transaction.chainId === "number" ? transaction.chainId : parseInt(transaction.chainId, 16);
|
|
256
|
+
const txNetworkId = (0, import_constants2.chainIdToNetworkId)(numericChainId);
|
|
257
|
+
if (txNetworkId) {
|
|
258
|
+
networkId = txNetworkId;
|
|
259
|
+
}
|
|
260
|
+
}
|
|
357
261
|
const result = await this.provider.signTransaction({
|
|
358
262
|
transaction,
|
|
359
|
-
networkId
|
|
263
|
+
networkId
|
|
360
264
|
});
|
|
361
|
-
|
|
362
|
-
const signatureBytes = (0, import_base64url.base64urlDecode)(result.rawTransaction);
|
|
363
|
-
return "0x" + import_buffer.Buffer.from(signatureBytes).toString("hex");
|
|
364
|
-
} catch (error) {
|
|
365
|
-
return result.rawTransaction.startsWith("0x") ? result.rawTransaction : "0x" + result.rawTransaction;
|
|
366
|
-
}
|
|
265
|
+
return result.rawTransaction;
|
|
367
266
|
}
|
|
368
267
|
async sendTransaction(transaction) {
|
|
268
|
+
if (transaction.chainId) {
|
|
269
|
+
const numericChainId = typeof transaction.chainId === "number" ? transaction.chainId : parseInt(transaction.chainId, 16);
|
|
270
|
+
await this.switchChain(numericChainId);
|
|
271
|
+
}
|
|
369
272
|
const result = await this.provider.signAndSendTransaction({
|
|
370
273
|
transaction,
|
|
371
274
|
networkId: this.currentNetworkId
|
|
@@ -376,12 +279,13 @@ var EmbeddedEthereumChain = class {
|
|
|
376
279
|
return result.hash;
|
|
377
280
|
}
|
|
378
281
|
switchChain(chainId) {
|
|
379
|
-
const
|
|
282
|
+
const numericChainId = typeof chainId === "string" ? chainId.toLowerCase().startsWith("0x") ? parseInt(chainId, 16) : parseInt(chainId, 10) : chainId;
|
|
283
|
+
const networkId = (0, import_constants2.chainIdToNetworkId)(numericChainId);
|
|
380
284
|
if (!networkId) {
|
|
381
285
|
throw new Error(`Unsupported chainId: ${chainId}`);
|
|
382
286
|
}
|
|
383
287
|
this.currentNetworkId = networkId;
|
|
384
|
-
this.eventEmitter.emit("chainChanged", `0x${
|
|
288
|
+
this.eventEmitter.emit("chainChanged", `0x${numericChainId.toString(16)}`);
|
|
385
289
|
return Promise.resolve();
|
|
386
290
|
}
|
|
387
291
|
getChainId() {
|
|
@@ -426,7 +330,7 @@ var EmbeddedEthereumChain = class {
|
|
|
426
330
|
switch (args.method) {
|
|
427
331
|
case "personal_sign": {
|
|
428
332
|
const [message, _address] = args.params;
|
|
429
|
-
const result = await this.provider.
|
|
333
|
+
const result = await this.provider.signEthereumMessage({
|
|
430
334
|
message,
|
|
431
335
|
networkId: this.currentNetworkId
|
|
432
336
|
});
|
|
@@ -434,10 +338,9 @@ var EmbeddedEthereumChain = class {
|
|
|
434
338
|
}
|
|
435
339
|
case "eth_signTypedData_v4": {
|
|
436
340
|
const [_typedDataAddress, typedDataStr] = args.params;
|
|
437
|
-
const
|
|
438
|
-
const typedDataResult = await this.provider.
|
|
439
|
-
|
|
440
|
-
// Pass the stringified typed data as message
|
|
341
|
+
const typedData = JSON.parse(typedDataStr);
|
|
342
|
+
const typedDataResult = await this.provider.signTypedDataV4({
|
|
343
|
+
typedData,
|
|
441
344
|
networkId: this.currentNetworkId
|
|
442
345
|
});
|
|
443
346
|
return typedDataResult.signature;
|
|
@@ -491,6 +394,45 @@ var EmbeddedEthereumChain = class {
|
|
|
491
394
|
}
|
|
492
395
|
};
|
|
493
396
|
|
|
397
|
+
// src/utils/retry.ts
|
|
398
|
+
async function retryWithBackoff(operation, operationName, logger, maxRetries = 3, baseDelay = 1e3) {
|
|
399
|
+
let lastError;
|
|
400
|
+
for (let attempt = 1; attempt <= maxRetries; attempt++) {
|
|
401
|
+
try {
|
|
402
|
+
logger.log("EMBEDDED_PROVIDER", `Attempting ${operationName}`, {
|
|
403
|
+
attempt,
|
|
404
|
+
maxRetries
|
|
405
|
+
});
|
|
406
|
+
return await operation();
|
|
407
|
+
} catch (error) {
|
|
408
|
+
lastError = error;
|
|
409
|
+
logger.warn("EMBEDDED_PROVIDER", `${operationName} failed`, {
|
|
410
|
+
attempt,
|
|
411
|
+
maxRetries,
|
|
412
|
+
error: error instanceof Error ? error.message : String(error)
|
|
413
|
+
});
|
|
414
|
+
if (attempt === maxRetries) {
|
|
415
|
+
logger.error("EMBEDDED_PROVIDER", `${operationName} failed after ${maxRetries} attempts`, {
|
|
416
|
+
finalError: error instanceof Error ? error.message : String(error)
|
|
417
|
+
});
|
|
418
|
+
break;
|
|
419
|
+
}
|
|
420
|
+
const delay = baseDelay * Math.pow(2, attempt - 1);
|
|
421
|
+
logger.log("EMBEDDED_PROVIDER", `Retrying ${operationName} in ${delay}ms`, {
|
|
422
|
+
attempt: attempt + 1,
|
|
423
|
+
delay
|
|
424
|
+
});
|
|
425
|
+
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
throw lastError;
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
// src/utils/session.ts
|
|
432
|
+
function generateSessionId() {
|
|
433
|
+
return "session_" + Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15) + "_" + Date.now();
|
|
434
|
+
}
|
|
435
|
+
|
|
494
436
|
// src/embedded-provider.ts
|
|
495
437
|
var EmbeddedProvider = class {
|
|
496
438
|
constructor(config, platform, logger) {
|
|
@@ -507,9 +449,9 @@ var EmbeddedProvider = class {
|
|
|
507
449
|
this.platform = platform;
|
|
508
450
|
this.storage = platform.storage;
|
|
509
451
|
this.authProvider = platform.authProvider;
|
|
452
|
+
this.phantomAppProvider = platform.phantomAppProvider;
|
|
510
453
|
this.urlParamsAccessor = platform.urlParamsAccessor;
|
|
511
454
|
this.stamper = platform.stamper;
|
|
512
|
-
this.jwtAuth = new JWTAuth();
|
|
513
455
|
this.solana = new EmbeddedSolanaChain(this);
|
|
514
456
|
this.ethereum = new EmbeddedEthereumChain(this);
|
|
515
457
|
this.logger.info("EMBEDDED_PROVIDER", "EmbeddedProvider initialized");
|
|
@@ -626,10 +568,16 @@ var EmbeddedProvider = class {
|
|
|
626
568
|
}
|
|
627
569
|
}
|
|
628
570
|
if (session.status === "completed" && !this.isSessionValid(session)) {
|
|
629
|
-
this.logger.warn(
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
571
|
+
this.logger.warn(
|
|
572
|
+
"EMBEDDED_PROVIDER",
|
|
573
|
+
"Session invalid due to authenticator expiration, will regenerate keypair",
|
|
574
|
+
{
|
|
575
|
+
sessionId: session.sessionId,
|
|
576
|
+
authenticatorExpiresAt: session.authenticatorExpiresAt,
|
|
577
|
+
currentTime: Date.now(),
|
|
578
|
+
expired: session.authenticatorExpiresAt < Date.now()
|
|
579
|
+
}
|
|
580
|
+
);
|
|
633
581
|
await this.storage.clearSession();
|
|
634
582
|
return null;
|
|
635
583
|
}
|
|
@@ -644,7 +592,11 @@ var EmbeddedProvider = class {
|
|
|
644
592
|
this.logger.log("EMBEDDED_PROVIDER", "Getting existing session");
|
|
645
593
|
let session = await this.storage.getSession();
|
|
646
594
|
session = await this.validateAndCleanSession(session);
|
|
647
|
-
if (session
|
|
595
|
+
if (!session) {
|
|
596
|
+
this.logger.log("EMBEDDED_PROVIDER", "No existing session found");
|
|
597
|
+
return null;
|
|
598
|
+
}
|
|
599
|
+
if (session.status === "completed") {
|
|
648
600
|
this.logger.info("EMBEDDED_PROVIDER", "Using existing completed session", {
|
|
649
601
|
sessionId: session.sessionId,
|
|
650
602
|
walletId: session.walletId
|
|
@@ -660,18 +612,19 @@ var EmbeddedProvider = class {
|
|
|
660
612
|
const result = {
|
|
661
613
|
walletId: this.walletId,
|
|
662
614
|
addresses: this.addresses,
|
|
663
|
-
status: "completed"
|
|
615
|
+
status: "completed",
|
|
616
|
+
authUserId: session.authUserId,
|
|
617
|
+
authProvider: session.authProvider
|
|
664
618
|
};
|
|
665
619
|
this.emit("connect", {
|
|
666
|
-
|
|
667
|
-
addresses: this.addresses,
|
|
620
|
+
...result,
|
|
668
621
|
source: "existing-session"
|
|
669
622
|
});
|
|
670
623
|
return result;
|
|
671
624
|
}
|
|
672
625
|
this.logger.log("EMBEDDED_PROVIDER", "No completed session found, checking for redirect resume");
|
|
673
626
|
if (this.authProvider.resumeAuthFromRedirect) {
|
|
674
|
-
const authResult = this.authProvider.resumeAuthFromRedirect();
|
|
627
|
+
const authResult = this.authProvider.resumeAuthFromRedirect(session.authProvider);
|
|
675
628
|
if (authResult) {
|
|
676
629
|
this.logger.info("EMBEDDED_PROVIDER", "Resuming from redirect", {
|
|
677
630
|
walletId: authResult.walletId,
|
|
@@ -703,13 +656,10 @@ var EmbeddedProvider = class {
|
|
|
703
656
|
* This ensures only supported auth providers are used and required tokens are present.
|
|
704
657
|
*/
|
|
705
658
|
validateAuthOptions(authOptions) {
|
|
706
|
-
if (!authOptions)
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
}
|
|
711
|
-
if (authOptions.provider === "jwt" && !authOptions.jwtToken) {
|
|
712
|
-
throw new Error("JWT token is required when using JWT authentication");
|
|
659
|
+
if (!EMBEDDED_PROVIDER_AUTH_TYPES.includes(authOptions.provider)) {
|
|
660
|
+
throw new Error(
|
|
661
|
+
`Invalid auth provider: ${authOptions.provider}. Must be ${EMBEDDED_PROVIDER_AUTH_TYPES.join(", ")}`
|
|
662
|
+
);
|
|
713
663
|
}
|
|
714
664
|
}
|
|
715
665
|
/*
|
|
@@ -769,8 +719,7 @@ var EmbeddedProvider = class {
|
|
|
769
719
|
addressCount: result.addresses.length
|
|
770
720
|
});
|
|
771
721
|
this.emit("connect", {
|
|
772
|
-
|
|
773
|
-
addresses: result.addresses,
|
|
722
|
+
...result,
|
|
774
723
|
source: "auto-connect"
|
|
775
724
|
});
|
|
776
725
|
return;
|
|
@@ -788,40 +737,46 @@ var EmbeddedProvider = class {
|
|
|
788
737
|
error: error instanceof Error ? error.message : "Auto-connect failed",
|
|
789
738
|
source: "auto-connect"
|
|
790
739
|
});
|
|
740
|
+
await this.storage.setShouldClearPreviousSession(true);
|
|
791
741
|
}
|
|
792
742
|
}
|
|
793
743
|
/*
|
|
794
744
|
* We use this method to initialize the stamper and create an organization for new sessions.
|
|
795
745
|
* This is the first step when no existing session is found and we need to set up a new wallet.
|
|
796
746
|
*/
|
|
797
|
-
async
|
|
747
|
+
async initializeStamper() {
|
|
798
748
|
this.logger.log("EMBEDDED_PROVIDER", "Initializing stamper");
|
|
799
|
-
|
|
800
|
-
this.logger.log("EMBEDDED_PROVIDER", "
|
|
749
|
+
await this.stamper.init();
|
|
750
|
+
this.logger.log("EMBEDDED_PROVIDER", "Resetting keypair to avoid conflicts with existing keypairs");
|
|
751
|
+
const stamperInfo = await this.stamper.resetKeyPair();
|
|
752
|
+
this.logger.log("EMBEDDED_PROVIDER", "Stamper initialized with fresh keypair", {
|
|
801
753
|
publicKey: stamperInfo.publicKey,
|
|
802
754
|
keyId: stamperInfo.keyId,
|
|
803
755
|
algorithm: this.stamper.algorithm
|
|
804
756
|
});
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
757
|
+
const expiresInMs = AUTHENTICATOR_EXPIRATION_TIME_MS;
|
|
758
|
+
this.logger.info("EMBEDDED_PROVIDER", "Stamper ready for auth flow with fresh keypair", {
|
|
759
|
+
publicKey: stamperInfo.publicKey,
|
|
760
|
+
keyId: stamperInfo.keyId
|
|
761
|
+
});
|
|
762
|
+
return { stamperInfo, expiresInMs };
|
|
763
|
+
}
|
|
764
|
+
async createOrganizationForAppWallet(stamperInfo, expiresInMs) {
|
|
765
|
+
const tempClient = new import_client.PhantomClient({
|
|
766
|
+
apiBaseUrl: this.config.apiBaseUrl,
|
|
767
|
+
headers: {
|
|
768
|
+
...this.platform.analyticsHeaders || {}
|
|
769
|
+
}
|
|
770
|
+
});
|
|
815
771
|
const platformName = this.platform.name || "unknown";
|
|
816
772
|
const shortPubKey = stamperInfo.publicKey.slice(0, 8);
|
|
817
773
|
const organizationName = `${this.config.appId.substring(0, 8)}-${platformName}-${shortPubKey}`;
|
|
818
|
-
this.logger.log("EMBEDDED_PROVIDER", "Creating organization", {
|
|
774
|
+
this.logger.log("EMBEDDED_PROVIDER", "Creating organization for app-wallet", {
|
|
819
775
|
organizationName,
|
|
820
776
|
publicKey: stamperInfo.publicKey,
|
|
821
777
|
platform: platformName
|
|
822
778
|
});
|
|
823
|
-
const base64urlPublicKey = (0,
|
|
824
|
-
const expiresInMs = AUTHENTICATOR_EXPIRATION_TIME_MS;
|
|
779
|
+
const base64urlPublicKey = (0, import_base64url.base64urlEncode)(import_bs582.default.decode(stamperInfo.publicKey));
|
|
825
780
|
const username = `user-${(0, import_utils.randomUUID)()}`;
|
|
826
781
|
const { organizationId } = await tempClient.createOrganization(organizationName, [
|
|
827
782
|
{
|
|
@@ -832,27 +787,25 @@ var EmbeddedProvider = class {
|
|
|
832
787
|
authenticatorName: `auth-${shortPubKey}`,
|
|
833
788
|
authenticatorKind: "keypair",
|
|
834
789
|
publicKey: base64urlPublicKey,
|
|
835
|
-
algorithm: "Ed25519"
|
|
836
|
-
|
|
837
|
-
// expiresInMs: expiresInMs,
|
|
790
|
+
algorithm: "Ed25519",
|
|
791
|
+
expiresInMs
|
|
838
792
|
}
|
|
839
793
|
]
|
|
840
794
|
}
|
|
841
795
|
]);
|
|
842
|
-
this.logger.info("EMBEDDED_PROVIDER", "Organization created", { organizationId });
|
|
843
|
-
return
|
|
796
|
+
this.logger.info("EMBEDDED_PROVIDER", "Organization created for app-wallet", { organizationId });
|
|
797
|
+
return organizationId;
|
|
844
798
|
}
|
|
845
799
|
async connect(authOptions) {
|
|
846
800
|
try {
|
|
847
801
|
this.logger.info("EMBEDDED_PROVIDER", "Starting embedded provider connect", {
|
|
848
|
-
authOptions:
|
|
849
|
-
provider: authOptions.provider
|
|
850
|
-
|
|
851
|
-
} : void 0
|
|
802
|
+
authOptions: {
|
|
803
|
+
provider: authOptions.provider
|
|
804
|
+
}
|
|
852
805
|
});
|
|
853
806
|
this.emit("connect_start", {
|
|
854
807
|
source: "manual-connect",
|
|
855
|
-
authOptions:
|
|
808
|
+
authOptions: { provider: authOptions.provider }
|
|
856
809
|
});
|
|
857
810
|
const existingResult = await this.tryExistingConnection(false);
|
|
858
811
|
if (existingResult) {
|
|
@@ -861,23 +814,26 @@ var EmbeddedProvider = class {
|
|
|
861
814
|
addressCount: existingResult.addresses.length
|
|
862
815
|
});
|
|
863
816
|
this.emit("connect", {
|
|
864
|
-
|
|
865
|
-
addresses: existingResult.addresses,
|
|
817
|
+
...existingResult,
|
|
866
818
|
source: "manual-existing"
|
|
867
819
|
});
|
|
868
820
|
return existingResult;
|
|
869
821
|
}
|
|
870
822
|
this.validateAuthOptions(authOptions);
|
|
871
|
-
this.logger.info(
|
|
872
|
-
|
|
873
|
-
|
|
823
|
+
this.logger.info(
|
|
824
|
+
"EMBEDDED_PROVIDER",
|
|
825
|
+
"No existing connection available, creating new auth flow with fresh keypair"
|
|
826
|
+
);
|
|
827
|
+
const { stamperInfo, expiresInMs } = await this.initializeStamper();
|
|
828
|
+
const session = await this.handleAuthFlow(stamperInfo.publicKey, stamperInfo, authOptions, expiresInMs);
|
|
874
829
|
if (!session) {
|
|
875
830
|
return {
|
|
876
831
|
addresses: [],
|
|
877
|
-
status: "pending"
|
|
832
|
+
status: "pending",
|
|
833
|
+
authProvider: authOptions.provider
|
|
878
834
|
};
|
|
879
835
|
}
|
|
880
|
-
if (
|
|
836
|
+
if (this.config.embeddedWalletType === "app-wallet") {
|
|
881
837
|
session.lastUsed = Date.now();
|
|
882
838
|
await this.storage.saveSession(session);
|
|
883
839
|
}
|
|
@@ -886,11 +842,12 @@ var EmbeddedProvider = class {
|
|
|
886
842
|
const result = {
|
|
887
843
|
walletId: this.walletId,
|
|
888
844
|
addresses: this.addresses,
|
|
889
|
-
status: "completed"
|
|
845
|
+
status: "completed",
|
|
846
|
+
authUserId: session?.authUserId,
|
|
847
|
+
authProvider: session?.authProvider
|
|
890
848
|
};
|
|
891
849
|
this.emit("connect", {
|
|
892
|
-
|
|
893
|
-
addresses: this.addresses,
|
|
850
|
+
...result,
|
|
894
851
|
source: "manual"
|
|
895
852
|
});
|
|
896
853
|
return result;
|
|
@@ -931,8 +888,10 @@ var EmbeddedProvider = class {
|
|
|
931
888
|
throw new Error(`Embedded wallet connection failed: ${String(error)}`);
|
|
932
889
|
}
|
|
933
890
|
}
|
|
934
|
-
async disconnect() {
|
|
891
|
+
async disconnect(shouldClearPreviousSession = true) {
|
|
935
892
|
const wasConnected = this.client !== null;
|
|
893
|
+
await this.storage.setShouldClearPreviousSession(shouldClearPreviousSession);
|
|
894
|
+
this.logger.log("EMBEDDED_PROVIDER", "Set flag to clear previous session on next login");
|
|
936
895
|
await this.storage.clearSession();
|
|
937
896
|
this.client = null;
|
|
938
897
|
this.walletId = null;
|
|
@@ -953,12 +912,11 @@ var EmbeddedProvider = class {
|
|
|
953
912
|
walletId: this.walletId,
|
|
954
913
|
message: params.message
|
|
955
914
|
});
|
|
956
|
-
const parsedMessage = (0, import_parsers.parseMessage)(params.message);
|
|
957
915
|
const session = await this.storage.getSession();
|
|
958
916
|
const derivationIndex = session?.accountDerivationIndex ?? 0;
|
|
959
|
-
const rawResponse = await this.client.
|
|
917
|
+
const rawResponse = await this.client.signUtf8Message({
|
|
960
918
|
walletId: this.walletId,
|
|
961
|
-
message:
|
|
919
|
+
message: params.message,
|
|
962
920
|
networkId: params.networkId,
|
|
963
921
|
derivationIndex
|
|
964
922
|
});
|
|
@@ -966,7 +924,62 @@ var EmbeddedProvider = class {
|
|
|
966
924
|
walletId: this.walletId,
|
|
967
925
|
message: params.message
|
|
968
926
|
});
|
|
969
|
-
return (0,
|
|
927
|
+
return (0, import_parsers2.parseSignMessageResponse)(rawResponse, params.networkId);
|
|
928
|
+
}
|
|
929
|
+
async signEthereumMessage(params) {
|
|
930
|
+
if (!this.client || !this.walletId) {
|
|
931
|
+
throw new Error("Not connected");
|
|
932
|
+
}
|
|
933
|
+
await this.ensureValidAuthenticator();
|
|
934
|
+
this.logger.info("EMBEDDED_PROVIDER", "Signing message", {
|
|
935
|
+
walletId: this.walletId,
|
|
936
|
+
message: params.message
|
|
937
|
+
});
|
|
938
|
+
const looksLikeHex = (str) => /^0x[0-9a-fA-F]+$/.test(str);
|
|
939
|
+
const normalizedMessage = (() => {
|
|
940
|
+
if (looksLikeHex(params.message)) {
|
|
941
|
+
const hexPayload = params.message.slice(2);
|
|
942
|
+
const normalizedHex = hexPayload.length % 2 === 0 ? hexPayload : `0${hexPayload}`;
|
|
943
|
+
return import_buffer.Buffer.from(normalizedHex, "hex").toString("utf8");
|
|
944
|
+
}
|
|
945
|
+
return params.message;
|
|
946
|
+
})();
|
|
947
|
+
const base64UrlMessage = (0, import_base64url.stringToBase64url)(normalizedMessage);
|
|
948
|
+
const session = await this.storage.getSession();
|
|
949
|
+
const derivationIndex = session?.accountDerivationIndex ?? 0;
|
|
950
|
+
const rawResponse = await this.client.ethereumSignMessage({
|
|
951
|
+
walletId: this.walletId,
|
|
952
|
+
message: base64UrlMessage,
|
|
953
|
+
networkId: params.networkId,
|
|
954
|
+
derivationIndex
|
|
955
|
+
});
|
|
956
|
+
this.logger.info("EMBEDDED_PROVIDER", "Message signed successfully", {
|
|
957
|
+
walletId: this.walletId,
|
|
958
|
+
message: params.message
|
|
959
|
+
});
|
|
960
|
+
return (0, import_parsers2.parseSignMessageResponse)(rawResponse, params.networkId);
|
|
961
|
+
}
|
|
962
|
+
async signTypedDataV4(params) {
|
|
963
|
+
if (!this.client || !this.walletId) {
|
|
964
|
+
throw new Error("Not connected");
|
|
965
|
+
}
|
|
966
|
+
await this.ensureValidAuthenticator();
|
|
967
|
+
this.logger.info("EMBEDDED_PROVIDER", "Signing typed data", {
|
|
968
|
+
walletId: this.walletId,
|
|
969
|
+
typedData: params.typedData
|
|
970
|
+
});
|
|
971
|
+
const session = await this.storage.getSession();
|
|
972
|
+
const derivationIndex = session?.accountDerivationIndex ?? 0;
|
|
973
|
+
const rawResponse = await this.client.ethereumSignTypedData({
|
|
974
|
+
walletId: this.walletId,
|
|
975
|
+
typedData: params.typedData,
|
|
976
|
+
networkId: params.networkId,
|
|
977
|
+
derivationIndex
|
|
978
|
+
});
|
|
979
|
+
this.logger.info("EMBEDDED_PROVIDER", "Typed data signed successfully", {
|
|
980
|
+
walletId: this.walletId
|
|
981
|
+
});
|
|
982
|
+
return (0, import_parsers2.parseSignMessageResponse)(rawResponse, params.networkId);
|
|
970
983
|
}
|
|
971
984
|
async signTransaction(params) {
|
|
972
985
|
if (!this.client || !this.walletId) {
|
|
@@ -977,7 +990,7 @@ var EmbeddedProvider = class {
|
|
|
977
990
|
walletId: this.walletId,
|
|
978
991
|
networkId: params.networkId
|
|
979
992
|
});
|
|
980
|
-
const parsedTransaction = await (0,
|
|
993
|
+
const parsedTransaction = await (0, import_parsers2.parseToKmsTransaction)(params.transaction, params.networkId);
|
|
981
994
|
const session = await this.storage.getSession();
|
|
982
995
|
const derivationIndex = session?.accountDerivationIndex ?? 0;
|
|
983
996
|
this.logger.log("EMBEDDED_PROVIDER", "Parsed transaction for signing", {
|
|
@@ -985,19 +998,27 @@ var EmbeddedProvider = class {
|
|
|
985
998
|
transaction: parsedTransaction,
|
|
986
999
|
derivationIndex
|
|
987
1000
|
});
|
|
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
|
+
}
|
|
988
1009
|
const rawResponse = await this.client.signTransaction({
|
|
989
1010
|
walletId: this.walletId,
|
|
990
|
-
transaction:
|
|
1011
|
+
transaction: transactionPayload,
|
|
991
1012
|
networkId: params.networkId,
|
|
992
1013
|
derivationIndex,
|
|
993
|
-
account
|
|
1014
|
+
account
|
|
994
1015
|
});
|
|
995
1016
|
this.logger.info("EMBEDDED_PROVIDER", "Transaction signed successfully", {
|
|
996
1017
|
walletId: this.walletId,
|
|
997
1018
|
networkId: params.networkId,
|
|
998
1019
|
rawTransaction: rawResponse.rawTransaction
|
|
999
1020
|
});
|
|
1000
|
-
return await (0,
|
|
1021
|
+
return await (0, import_parsers2.parseTransactionResponse)(rawResponse.rawTransaction, params.networkId);
|
|
1001
1022
|
}
|
|
1002
1023
|
async signAndSendTransaction(params) {
|
|
1003
1024
|
if (!this.client || !this.walletId) {
|
|
@@ -1008,7 +1029,7 @@ var EmbeddedProvider = class {
|
|
|
1008
1029
|
walletId: this.walletId,
|
|
1009
1030
|
networkId: params.networkId
|
|
1010
1031
|
});
|
|
1011
|
-
const parsedTransaction = await (0,
|
|
1032
|
+
const parsedTransaction = await (0, import_parsers2.parseToKmsTransaction)(params.transaction, params.networkId);
|
|
1012
1033
|
const session = await this.storage.getSession();
|
|
1013
1034
|
const derivationIndex = session?.accountDerivationIndex ?? 0;
|
|
1014
1035
|
this.logger.log("EMBEDDED_PROVIDER", "Parsed transaction for signing", {
|
|
@@ -1016,20 +1037,36 @@ var EmbeddedProvider = class {
|
|
|
1016
1037
|
transaction: parsedTransaction,
|
|
1017
1038
|
derivationIndex
|
|
1018
1039
|
});
|
|
1019
|
-
const
|
|
1020
|
-
|
|
1021
|
-
transaction:
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1040
|
+
const transactionPayload = parsedTransaction.parsed;
|
|
1041
|
+
if (!transactionPayload) {
|
|
1042
|
+
throw new Error("Failed to parse transaction: no valid encoding found");
|
|
1043
|
+
}
|
|
1044
|
+
const account = this.getAddressForNetwork(params.networkId);
|
|
1045
|
+
if (!account) {
|
|
1046
|
+
throw new Error(`No address found for network ${params.networkId}`);
|
|
1047
|
+
}
|
|
1048
|
+
let rawResponse;
|
|
1049
|
+
try {
|
|
1050
|
+
rawResponse = await this.client.signAndSendTransaction({
|
|
1051
|
+
walletId: this.walletId,
|
|
1052
|
+
transaction: transactionPayload,
|
|
1053
|
+
networkId: params.networkId,
|
|
1054
|
+
derivationIndex,
|
|
1055
|
+
account
|
|
1056
|
+
});
|
|
1057
|
+
} catch (error) {
|
|
1058
|
+
if (error instanceof import_client.SpendingLimitError) {
|
|
1059
|
+
this.emit("spending_limit_reached", { error });
|
|
1060
|
+
}
|
|
1061
|
+
throw error;
|
|
1062
|
+
}
|
|
1026
1063
|
this.logger.info("EMBEDDED_PROVIDER", "Transaction signed and sent successfully", {
|
|
1027
1064
|
walletId: this.walletId,
|
|
1028
1065
|
networkId: params.networkId,
|
|
1029
1066
|
hash: rawResponse.hash,
|
|
1030
1067
|
rawTransaction: rawResponse.rawTransaction
|
|
1031
1068
|
});
|
|
1032
|
-
return await (0,
|
|
1069
|
+
return await (0, import_parsers2.parseTransactionResponse)(rawResponse.rawTransaction, params.networkId, rawResponse.hash);
|
|
1033
1070
|
}
|
|
1034
1071
|
getAddresses() {
|
|
1035
1072
|
return this.addresses;
|
|
@@ -1042,24 +1079,25 @@ var EmbeddedProvider = class {
|
|
|
1042
1079
|
* It handles app-wallet creation directly or routes to JWT/redirect authentication for user-wallets.
|
|
1043
1080
|
* Returns null for redirect flows since they don't complete synchronously.
|
|
1044
1081
|
*/
|
|
1045
|
-
async handleAuthFlow(
|
|
1082
|
+
async handleAuthFlow(publicKey, stamperInfo, authOptions, expiresInMs) {
|
|
1046
1083
|
if (this.config.embeddedWalletType === "user-wallet") {
|
|
1047
1084
|
this.logger.info("EMBEDDED_PROVIDER", "Creating user-wallet, routing authentication", {
|
|
1048
|
-
authProvider: authOptions
|
|
1085
|
+
authProvider: authOptions.provider
|
|
1049
1086
|
});
|
|
1050
|
-
if (authOptions
|
|
1051
|
-
return await this.
|
|
1087
|
+
if (authOptions.provider === "phantom") {
|
|
1088
|
+
return await this.handlePhantomAuth(publicKey, stamperInfo, expiresInMs);
|
|
1052
1089
|
} else {
|
|
1053
1090
|
this.logger.info("EMBEDDED_PROVIDER", "Starting redirect-based authentication flow", {
|
|
1054
|
-
|
|
1091
|
+
publicKey,
|
|
1055
1092
|
provider: authOptions?.provider
|
|
1056
1093
|
});
|
|
1057
|
-
return await this.handleRedirectAuth(
|
|
1094
|
+
return await this.handleRedirectAuth(publicKey, stamperInfo, authOptions);
|
|
1058
1095
|
}
|
|
1059
1096
|
} else {
|
|
1060
1097
|
this.logger.info("EMBEDDED_PROVIDER", "Creating app-wallet", {
|
|
1061
|
-
|
|
1098
|
+
publicKey
|
|
1062
1099
|
});
|
|
1100
|
+
const organizationId = await this.createOrganizationForAppWallet(stamperInfo, expiresInMs);
|
|
1063
1101
|
const tempClient = new import_client.PhantomClient(
|
|
1064
1102
|
{
|
|
1065
1103
|
apiBaseUrl: this.config.apiBaseUrl,
|
|
@@ -1079,8 +1117,8 @@ var EmbeddedProvider = class {
|
|
|
1079
1117
|
organizationId,
|
|
1080
1118
|
appId: this.config.appId,
|
|
1081
1119
|
stamperInfo,
|
|
1082
|
-
authProvider: "
|
|
1083
|
-
|
|
1120
|
+
authProvider: "device",
|
|
1121
|
+
// For now app wallets have no auth provider.
|
|
1084
1122
|
accountDerivationIndex: 0,
|
|
1085
1123
|
// App wallets default to index 0
|
|
1086
1124
|
status: "completed",
|
|
@@ -1088,8 +1126,7 @@ var EmbeddedProvider = class {
|
|
|
1088
1126
|
lastUsed: now,
|
|
1089
1127
|
authenticatorCreatedAt: now,
|
|
1090
1128
|
authenticatorExpiresAt: Date.now() + expiresInMs,
|
|
1091
|
-
lastRenewalAttempt: void 0
|
|
1092
|
-
username
|
|
1129
|
+
lastRenewalAttempt: void 0
|
|
1093
1130
|
};
|
|
1094
1131
|
await this.storage.saveSession(session);
|
|
1095
1132
|
this.logger.info("EMBEDDED_PROVIDER", "App-wallet created successfully", { walletId, organizationId });
|
|
@@ -1097,43 +1134,56 @@ var EmbeddedProvider = class {
|
|
|
1097
1134
|
}
|
|
1098
1135
|
}
|
|
1099
1136
|
/*
|
|
1100
|
-
* We use this method to handle
|
|
1101
|
-
*
|
|
1137
|
+
* We use this method to handle Phantom app-based authentication for user-wallets.
|
|
1138
|
+
* This method uses the PhantomAppProvider to authenticate via the browser extension or mobile app.
|
|
1139
|
+
*
|
|
1140
|
+
* NOTE: Mobile deeplink support is not yet implemented. If we wanted to support mobile deeplinks,
|
|
1141
|
+
* we would:
|
|
1142
|
+
* 1. Check if the app provider is available using phantomAppProvider.isAvailable()
|
|
1143
|
+
* 2. If not available, generate a deeplink (phantom://auth?...)
|
|
1144
|
+
* 3. Save a pending session before opening the deeplink
|
|
1145
|
+
* 4. Start a polling mechanism to check for auth completion
|
|
1146
|
+
* 5. Update the session when the mobile app completes the auth
|
|
1102
1147
|
*/
|
|
1103
|
-
async
|
|
1104
|
-
this.logger.info("EMBEDDED_PROVIDER", "
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1148
|
+
async handlePhantomAuth(publicKey, stamperInfo, expiresInMs) {
|
|
1149
|
+
this.logger.info("EMBEDDED_PROVIDER", "Starting Phantom authentication flow");
|
|
1150
|
+
const isAvailable = this.phantomAppProvider.isAvailable();
|
|
1151
|
+
if (!isAvailable) {
|
|
1152
|
+
this.logger.error("EMBEDDED_PROVIDER", "Phantom app not available");
|
|
1153
|
+
throw new Error(
|
|
1154
|
+
"Phantom app is not available. Please install the Phantom browser extension or mobile app to use this authentication method."
|
|
1155
|
+
);
|
|
1108
1156
|
}
|
|
1109
|
-
this.logger.
|
|
1110
|
-
const
|
|
1111
|
-
|
|
1157
|
+
this.logger.info("EMBEDDED_PROVIDER", "Phantom app detected, proceeding with authentication");
|
|
1158
|
+
const sessionId = generateSessionId();
|
|
1159
|
+
const authResult = await this.phantomAppProvider.authenticate({
|
|
1160
|
+
publicKey,
|
|
1112
1161
|
appId: this.config.appId,
|
|
1113
|
-
|
|
1114
|
-
customAuthData: authOptions.customAuthData
|
|
1162
|
+
sessionId
|
|
1115
1163
|
});
|
|
1116
|
-
|
|
1117
|
-
|
|
1164
|
+
this.logger.info("EMBEDDED_PROVIDER", "Phantom authentication completed", {
|
|
1165
|
+
walletId: authResult.walletId,
|
|
1166
|
+
organizationId: authResult.organizationId
|
|
1167
|
+
});
|
|
1168
|
+
const effectiveExpiresInMs = authResult.expiresInMs > 0 ? authResult.expiresInMs : expiresInMs;
|
|
1118
1169
|
const now = Date.now();
|
|
1119
1170
|
const session = {
|
|
1120
|
-
sessionId
|
|
1121
|
-
walletId,
|
|
1122
|
-
organizationId,
|
|
1171
|
+
sessionId,
|
|
1172
|
+
walletId: authResult.walletId,
|
|
1173
|
+
organizationId: authResult.organizationId,
|
|
1123
1174
|
appId: this.config.appId,
|
|
1124
1175
|
stamperInfo,
|
|
1125
|
-
authProvider:
|
|
1126
|
-
userInfo: authResult.userInfo,
|
|
1176
|
+
authProvider: "phantom",
|
|
1127
1177
|
accountDerivationIndex: authResult.accountDerivationIndex,
|
|
1128
1178
|
status: "completed",
|
|
1129
1179
|
createdAt: now,
|
|
1130
1180
|
lastUsed: now,
|
|
1131
1181
|
authenticatorCreatedAt: now,
|
|
1132
|
-
authenticatorExpiresAt:
|
|
1182
|
+
authenticatorExpiresAt: now + effectiveExpiresInMs,
|
|
1133
1183
|
lastRenewalAttempt: void 0,
|
|
1134
|
-
|
|
1184
|
+
authUserId: authResult.authUserId
|
|
1135
1185
|
};
|
|
1136
|
-
this.logger.log("EMBEDDED_PROVIDER", "Saving
|
|
1186
|
+
this.logger.log("EMBEDDED_PROVIDER", "Saving Phantom session");
|
|
1137
1187
|
await this.storage.saveSession(session);
|
|
1138
1188
|
return session;
|
|
1139
1189
|
}
|
|
@@ -1142,9 +1192,9 @@ var EmbeddedProvider = class {
|
|
|
1142
1192
|
* It saves a temporary session before redirecting to prevent losing state during the redirect flow.
|
|
1143
1193
|
* Session timestamp is updated before redirect to prevent race conditions.
|
|
1144
1194
|
*/
|
|
1145
|
-
async handleRedirectAuth(
|
|
1195
|
+
async handleRedirectAuth(publicKey, stamperInfo, authOptions) {
|
|
1146
1196
|
this.logger.info("EMBEDDED_PROVIDER", "Using Phantom Connect authentication flow (redirect-based)", {
|
|
1147
|
-
provider: authOptions
|
|
1197
|
+
provider: authOptions.provider,
|
|
1148
1198
|
hasRedirectUrl: !!this.config.authOptions.redirectUrl,
|
|
1149
1199
|
authUrl: this.config.authOptions.authUrl
|
|
1150
1200
|
});
|
|
@@ -1152,13 +1202,13 @@ var EmbeddedProvider = class {
|
|
|
1152
1202
|
const sessionId = generateSessionId();
|
|
1153
1203
|
const tempSession = {
|
|
1154
1204
|
sessionId,
|
|
1155
|
-
walletId: `temp-${now}`,
|
|
1205
|
+
walletId: `temp-wallet-${now}`,
|
|
1206
|
+
// Temporary ID, will be updated after redirect
|
|
1207
|
+
organizationId: `temp-org-${now}`,
|
|
1156
1208
|
// Temporary ID, will be updated after redirect
|
|
1157
|
-
organizationId,
|
|
1158
1209
|
appId: this.config.appId,
|
|
1159
1210
|
stamperInfo,
|
|
1160
|
-
authProvider:
|
|
1161
|
-
userInfo: { provider: authOptions?.provider },
|
|
1211
|
+
authProvider: authOptions.provider,
|
|
1162
1212
|
accountDerivationIndex: void 0,
|
|
1163
1213
|
// Will be set when redirect completes
|
|
1164
1214
|
status: "pending",
|
|
@@ -1166,8 +1216,7 @@ var EmbeddedProvider = class {
|
|
|
1166
1216
|
lastUsed: now,
|
|
1167
1217
|
authenticatorCreatedAt: now,
|
|
1168
1218
|
authenticatorExpiresAt: now + AUTHENTICATOR_EXPIRATION_TIME_MS,
|
|
1169
|
-
lastRenewalAttempt: void 0
|
|
1170
|
-
username: username || `user-${(0, import_utils.randomUUID)()}`
|
|
1219
|
+
lastRenewalAttempt: void 0
|
|
1171
1220
|
};
|
|
1172
1221
|
this.logger.log("EMBEDDED_PROVIDER", "Saving temporary session before redirect", {
|
|
1173
1222
|
sessionId: tempSession.sessionId,
|
|
@@ -1175,32 +1224,53 @@ var EmbeddedProvider = class {
|
|
|
1175
1224
|
});
|
|
1176
1225
|
tempSession.lastUsed = Date.now();
|
|
1177
1226
|
await this.storage.saveSession(tempSession);
|
|
1227
|
+
const shouldClearPreviousSession = await this.storage.getShouldClearPreviousSession();
|
|
1178
1228
|
this.logger.info("EMBEDDED_PROVIDER", "Starting Phantom Connect redirect", {
|
|
1179
|
-
|
|
1229
|
+
publicKey,
|
|
1180
1230
|
appId: this.config.appId,
|
|
1181
1231
|
provider: authOptions?.provider,
|
|
1182
|
-
authUrl: this.config.authOptions.authUrl
|
|
1232
|
+
authUrl: this.config.authOptions.authUrl,
|
|
1233
|
+
clearPreviousSession: shouldClearPreviousSession,
|
|
1234
|
+
allowRefresh: !shouldClearPreviousSession
|
|
1183
1235
|
});
|
|
1184
1236
|
const authResult = await this.authProvider.authenticate({
|
|
1185
|
-
|
|
1237
|
+
publicKey,
|
|
1186
1238
|
appId: this.config.appId,
|
|
1187
1239
|
provider: authOptions?.provider,
|
|
1188
1240
|
redirectUrl: this.config.authOptions.redirectUrl,
|
|
1189
|
-
customAuthData: authOptions?.customAuthData,
|
|
1190
1241
|
authUrl: this.config.authOptions.authUrl,
|
|
1191
|
-
sessionId
|
|
1242
|
+
sessionId,
|
|
1243
|
+
// OAuth session management - defaults to allowing refresh unless user explicitly logged out
|
|
1244
|
+
clearPreviousSession: shouldClearPreviousSession,
|
|
1245
|
+
// true only after logout
|
|
1246
|
+
allowRefresh: !shouldClearPreviousSession
|
|
1247
|
+
// false only after logout
|
|
1192
1248
|
});
|
|
1193
1249
|
if (authResult && "walletId" in authResult) {
|
|
1194
1250
|
this.logger.info("EMBEDDED_PROVIDER", "Authentication completed after redirect", {
|
|
1195
1251
|
walletId: authResult.walletId,
|
|
1252
|
+
organizationId: authResult.organizationId,
|
|
1196
1253
|
provider: authResult.provider
|
|
1197
1254
|
});
|
|
1198
1255
|
tempSession.walletId = authResult.walletId;
|
|
1256
|
+
tempSession.organizationId = authResult.organizationId;
|
|
1199
1257
|
tempSession.authProvider = authResult.provider || tempSession.authProvider;
|
|
1200
1258
|
tempSession.accountDerivationIndex = authResult.accountDerivationIndex;
|
|
1259
|
+
tempSession.authUserId = authResult.authUserId;
|
|
1201
1260
|
tempSession.status = "completed";
|
|
1202
1261
|
tempSession.lastUsed = Date.now();
|
|
1262
|
+
if (authResult.expiresInMs > 0) {
|
|
1263
|
+
const now2 = Date.now();
|
|
1264
|
+
tempSession.authenticatorCreatedAt = now2;
|
|
1265
|
+
tempSession.authenticatorExpiresAt = now2 + authResult.expiresInMs;
|
|
1266
|
+
this.logger.log("EMBEDDED_PROVIDER", "Updated authenticator expiration from immediate auth response", {
|
|
1267
|
+
expiresInMs: authResult.expiresInMs,
|
|
1268
|
+
expiresAt: new Date(tempSession.authenticatorExpiresAt).toISOString()
|
|
1269
|
+
});
|
|
1270
|
+
}
|
|
1203
1271
|
await this.storage.saveSession(tempSession);
|
|
1272
|
+
await this.storage.setShouldClearPreviousSession(false);
|
|
1273
|
+
this.logger.log("EMBEDDED_PROVIDER", "Cleared logout flag after successful authentication");
|
|
1204
1274
|
return tempSession;
|
|
1205
1275
|
}
|
|
1206
1276
|
this.logger.info("EMBEDDED_PROVIDER", "Redirect authentication initiated, waiting for redirect completion");
|
|
@@ -1213,16 +1283,31 @@ var EmbeddedProvider = class {
|
|
|
1213
1283
|
}
|
|
1214
1284
|
session.walletId = authResult.walletId;
|
|
1215
1285
|
session.authProvider = authResult.provider || session.authProvider;
|
|
1286
|
+
session.organizationId = authResult.organizationId;
|
|
1216
1287
|
session.accountDerivationIndex = authResult.accountDerivationIndex;
|
|
1288
|
+
session.authUserId = authResult.authUserId;
|
|
1217
1289
|
session.status = "completed";
|
|
1218
1290
|
session.lastUsed = Date.now();
|
|
1291
|
+
if (authResult.expiresInMs > 0) {
|
|
1292
|
+
const now = Date.now();
|
|
1293
|
+
session.authenticatorCreatedAt = now;
|
|
1294
|
+
session.authenticatorExpiresAt = now + authResult.expiresInMs;
|
|
1295
|
+
this.logger.log("EMBEDDED_PROVIDER", "Updated authenticator expiration from auth response", {
|
|
1296
|
+
expiresInMs: authResult.expiresInMs,
|
|
1297
|
+
expiresAt: new Date(session.authenticatorExpiresAt).toISOString()
|
|
1298
|
+
});
|
|
1299
|
+
}
|
|
1219
1300
|
await this.storage.saveSession(session);
|
|
1301
|
+
await this.storage.setShouldClearPreviousSession(false);
|
|
1302
|
+
this.logger.log("EMBEDDED_PROVIDER", "Cleared logout flag after successful authentication");
|
|
1220
1303
|
await this.initializeClientFromSession(session);
|
|
1221
1304
|
await this.ensureValidAuthenticator();
|
|
1222
1305
|
return {
|
|
1223
1306
|
walletId: this.walletId,
|
|
1224
1307
|
addresses: this.addresses,
|
|
1225
|
-
status: "completed"
|
|
1308
|
+
status: "completed",
|
|
1309
|
+
authUserId: session.authUserId,
|
|
1310
|
+
authProvider: session.authProvider
|
|
1226
1311
|
};
|
|
1227
1312
|
}
|
|
1228
1313
|
/*
|
|
@@ -1237,7 +1322,7 @@ var EmbeddedProvider = class {
|
|
|
1237
1322
|
const now = Date.now();
|
|
1238
1323
|
if (!session.authenticatorExpiresAt) {
|
|
1239
1324
|
this.logger.warn("EMBEDDED_PROVIDER", "Session missing authenticator timing - treating as invalid session");
|
|
1240
|
-
await this.disconnect();
|
|
1325
|
+
await this.disconnect(false);
|
|
1241
1326
|
throw new Error("Invalid session - missing authenticator timing");
|
|
1242
1327
|
}
|
|
1243
1328
|
const timeUntilExpiry = session.authenticatorExpiresAt - now;
|
|
@@ -1247,86 +1332,9 @@ var EmbeddedProvider = class {
|
|
|
1247
1332
|
});
|
|
1248
1333
|
if (timeUntilExpiry <= 0) {
|
|
1249
1334
|
this.logger.error("EMBEDDED_PROVIDER", "Authenticator has expired, disconnecting");
|
|
1250
|
-
await this.disconnect();
|
|
1335
|
+
await this.disconnect(false);
|
|
1251
1336
|
throw new Error("Authenticator expired");
|
|
1252
1337
|
}
|
|
1253
|
-
const renewalWindow = AUTHENTICATOR_RENEWAL_WINDOW_MS;
|
|
1254
|
-
if (timeUntilExpiry <= renewalWindow) {
|
|
1255
|
-
this.logger.info("EMBEDDED_PROVIDER", "Authenticator needs renewal", {
|
|
1256
|
-
expiresAt: new Date(session.authenticatorExpiresAt).toISOString(),
|
|
1257
|
-
timeUntilExpiry,
|
|
1258
|
-
renewalWindow
|
|
1259
|
-
});
|
|
1260
|
-
try {
|
|
1261
|
-
await this.renewAuthenticator(session);
|
|
1262
|
-
this.logger.info("EMBEDDED_PROVIDER", "Authenticator renewed successfully");
|
|
1263
|
-
} catch (error) {
|
|
1264
|
-
this.logger.error("EMBEDDED_PROVIDER", "Failed to renew authenticator", {
|
|
1265
|
-
error: error instanceof Error ? error.message : String(error)
|
|
1266
|
-
});
|
|
1267
|
-
}
|
|
1268
|
-
}
|
|
1269
|
-
}
|
|
1270
|
-
/*
|
|
1271
|
-
* We use this method to perform silent authenticator renewal.
|
|
1272
|
-
* It generates a new keypair, creates a new authenticator, and switches to it.
|
|
1273
|
-
*/
|
|
1274
|
-
async renewAuthenticator(session) {
|
|
1275
|
-
if (!this.client) {
|
|
1276
|
-
throw new Error("Client not initialized");
|
|
1277
|
-
}
|
|
1278
|
-
this.logger.info("EMBEDDED_PROVIDER", "Starting authenticator renewal");
|
|
1279
|
-
try {
|
|
1280
|
-
const newKeyInfo = await this.stamper.rotateKeyPair();
|
|
1281
|
-
this.logger.log("EMBEDDED_PROVIDER", "Generated new keypair for renewal", {
|
|
1282
|
-
newKeyId: newKeyInfo.keyId,
|
|
1283
|
-
newPublicKey: newKeyInfo.publicKey
|
|
1284
|
-
});
|
|
1285
|
-
const base64urlPublicKey = (0, import_base64url2.base64urlEncode)(import_bs582.default.decode(newKeyInfo.publicKey));
|
|
1286
|
-
const expiresInMs = AUTHENTICATOR_EXPIRATION_TIME_MS;
|
|
1287
|
-
let authenticatorResult;
|
|
1288
|
-
try {
|
|
1289
|
-
authenticatorResult = await this.client.createAuthenticator({
|
|
1290
|
-
organizationId: session.organizationId,
|
|
1291
|
-
username: session.username,
|
|
1292
|
-
authenticatorName: `auth-${newKeyInfo.keyId.substring(0, 8)}`,
|
|
1293
|
-
authenticator: {
|
|
1294
|
-
authenticatorName: `auth-${newKeyInfo.keyId.substring(0, 8)}`,
|
|
1295
|
-
authenticatorKind: "keypair",
|
|
1296
|
-
publicKey: base64urlPublicKey,
|
|
1297
|
-
algorithm: "Ed25519"
|
|
1298
|
-
// Commented for now until KMS supports fully expiring organizations
|
|
1299
|
-
// expiresInMs: expiresInMs,
|
|
1300
|
-
},
|
|
1301
|
-
replaceExpirable: true
|
|
1302
|
-
});
|
|
1303
|
-
} catch (error) {
|
|
1304
|
-
this.logger.error("EMBEDDED_PROVIDER", "Failed to create new authenticator", {
|
|
1305
|
-
error: error instanceof Error ? error.message : String(error)
|
|
1306
|
-
});
|
|
1307
|
-
await this.stamper.rollbackRotation();
|
|
1308
|
-
throw new Error(
|
|
1309
|
-
`Failed to create new authenticator: ${error instanceof Error ? error.message : String(error)}`
|
|
1310
|
-
);
|
|
1311
|
-
}
|
|
1312
|
-
this.logger.info("EMBEDDED_PROVIDER", "Created new authenticator", {
|
|
1313
|
-
authenticatorId: authenticatorResult.id
|
|
1314
|
-
});
|
|
1315
|
-
await this.stamper.commitRotation(authenticatorResult.id || "unknown");
|
|
1316
|
-
const now = Date.now();
|
|
1317
|
-
session.stamperInfo = newKeyInfo;
|
|
1318
|
-
session.authenticatorCreatedAt = now;
|
|
1319
|
-
session.authenticatorExpiresAt = Date.now() + expiresInMs;
|
|
1320
|
-
session.lastRenewalAttempt = now;
|
|
1321
|
-
await this.storage.saveSession(session);
|
|
1322
|
-
this.logger.info("EMBEDDED_PROVIDER", "Authenticator renewal completed successfully", {
|
|
1323
|
-
newKeyId: newKeyInfo.keyId,
|
|
1324
|
-
expiresAt: new Date(Date.now() + expiresInMs).toISOString()
|
|
1325
|
-
});
|
|
1326
|
-
} catch (error) {
|
|
1327
|
-
await this.stamper.rollbackRotation();
|
|
1328
|
-
throw error;
|
|
1329
|
-
}
|
|
1330
1338
|
}
|
|
1331
1339
|
/*
|
|
1332
1340
|
* We use this method to initialize the PhantomClient and fetch wallet addresses from a completed session.
|
|
@@ -1359,10 +1367,10 @@ var EmbeddedProvider = class {
|
|
|
1359
1367
|
0 && (module.exports = {
|
|
1360
1368
|
AUTHENTICATOR_EXPIRATION_TIME_MS,
|
|
1361
1369
|
AUTHENTICATOR_RENEWAL_WINDOW_MS,
|
|
1370
|
+
EMBEDDED_PROVIDER_AUTH_TYPES,
|
|
1362
1371
|
EmbeddedEthereumChain,
|
|
1363
1372
|
EmbeddedProvider,
|
|
1364
1373
|
EmbeddedSolanaChain,
|
|
1365
|
-
JWTAuth,
|
|
1366
1374
|
generateSessionId,
|
|
1367
1375
|
retryWithBackoff
|
|
1368
1376
|
});
|