@phantom/browser-sdk 1.0.2 → 1.0.4
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 +9 -15
- package/dist/index.d.ts +8 -3
- package/dist/index.js +518 -193
- package/dist/index.mjs +503 -172
- package/package.json +11 -10
package/dist/index.mjs
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { AddressType } from "@phantom/client";
|
|
3
3
|
|
|
4
4
|
// src/providers/injected/index.ts
|
|
5
|
-
import { AddressType as
|
|
5
|
+
import { AddressType as AddressType3 } from "@phantom/client";
|
|
6
6
|
|
|
7
7
|
// src/debug.ts
|
|
8
8
|
var DebugLevel = /* @__PURE__ */ ((DebugLevel2) => {
|
|
@@ -79,12 +79,27 @@ var DebugCategory = {
|
|
|
79
79
|
};
|
|
80
80
|
|
|
81
81
|
// src/wallets/discovery.ts
|
|
82
|
+
import { PHANTOM_ICON } from "@phantom/constants";
|
|
82
83
|
import { AddressType as ClientAddressType } from "@phantom/client";
|
|
83
84
|
import { isPhantomExtensionInstalled } from "@phantom/browser-injected-sdk";
|
|
84
85
|
import { createPhantom, createExtensionPlugin } from "@phantom/browser-injected-sdk";
|
|
85
86
|
import { createSolanaPlugin } from "@phantom/browser-injected-sdk/solana";
|
|
86
87
|
import { createEthereumPlugin } from "@phantom/browser-injected-sdk/ethereum";
|
|
87
88
|
import { createAutoConfirmPlugin } from "@phantom/browser-injected-sdk/auto-confirm";
|
|
89
|
+
|
|
90
|
+
// src/wallets/custom-wallets.ts
|
|
91
|
+
import { AddressType as AddressType2 } from "@phantom/client";
|
|
92
|
+
var CUSTOM_WALLET_CONFIGS = [
|
|
93
|
+
{
|
|
94
|
+
id: "coinbase-wallet",
|
|
95
|
+
name: "Coinbase Wallet",
|
|
96
|
+
icon: "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTYiIGhlaWdodD0iNTYiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZD0iTTI4IDU2YzE1LjQ2NCAwIDI4LTEyLjUzNiAyOC0yOFM0My40NjQgMCAyOCAwIDAgMTIuNTM2IDAgMjhzMTIuNTM2IDI4IDI4IDI4WiIgZmlsbD0iIzFCNTNFNCIvPjxwYXRoIGZpbGwtcnVsZT0iZXZlbm9kZCIgY2xpcC1ydWxlPSJldmVub2RkIiBkPSJNNyAyOGMwIDExLjU5OCA5LjQwMiAyMSAyMSAyMXMyMS05LjQwMiAyMS0yMVMzOS41OTggNyAyOCA3IDcgMTYuNDAyIDcgMjhabTE3LjIzNC02Ljc2NmEzIDMgMCAwIDAtMyAzdjcuNTMzYTMgMyAwIDAgMCAzIDNoNy41MzNhMyAzIDAgMCAwIDMtM3YtNy41MzNhMyAzIDAgMCAwLTMtM2gtNy41MzNaIiBmaWxsPSIjZmZmIi8+PC9zdmc+",
|
|
97
|
+
windowProperty: "coinbaseSolana",
|
|
98
|
+
addressTypes: [AddressType2.solana]
|
|
99
|
+
}
|
|
100
|
+
];
|
|
101
|
+
|
|
102
|
+
// src/wallets/discovery.ts
|
|
88
103
|
function generateWalletIdFromEIP6963(info) {
|
|
89
104
|
if (info.rdns) {
|
|
90
105
|
return info.rdns.split(".").reverse().join("-");
|
|
@@ -348,6 +363,51 @@ async function discoverSolanaWallets() {
|
|
|
348
363
|
walletNames: wallets.map((w) => w.name)
|
|
349
364
|
};
|
|
350
365
|
debug.log(DebugCategory.BROWSER_SDK, "Wallet Standard Solana discovery completed", finalLogData);
|
|
366
|
+
const customWallets = discoverCustomSolanaWallets();
|
|
367
|
+
wallets.push(...customWallets);
|
|
368
|
+
return wallets;
|
|
369
|
+
}
|
|
370
|
+
function discoverCustomSolanaWallets() {
|
|
371
|
+
const wallets = [];
|
|
372
|
+
if (typeof window === "undefined") {
|
|
373
|
+
debug.log(DebugCategory.BROWSER_SDK, "Custom wallet discovery skipped (not in browser environment)");
|
|
374
|
+
return wallets;
|
|
375
|
+
}
|
|
376
|
+
debug.log(DebugCategory.BROWSER_SDK, "Starting custom Solana wallet discovery", {
|
|
377
|
+
configCount: CUSTOM_WALLET_CONFIGS.length
|
|
378
|
+
});
|
|
379
|
+
for (const config of CUSTOM_WALLET_CONFIGS) {
|
|
380
|
+
if (!config.addressTypes.includes(ClientAddressType.solana)) {
|
|
381
|
+
continue;
|
|
382
|
+
}
|
|
383
|
+
const provider = window[config.windowProperty];
|
|
384
|
+
if (!provider) {
|
|
385
|
+
debug.log(DebugCategory.BROWSER_SDK, "Custom wallet not found", {
|
|
386
|
+
walletId: config.id,
|
|
387
|
+
windowProperty: config.windowProperty
|
|
388
|
+
});
|
|
389
|
+
continue;
|
|
390
|
+
}
|
|
391
|
+
debug.log(DebugCategory.BROWSER_SDK, "Discovered custom Solana wallet", {
|
|
392
|
+
walletId: config.id,
|
|
393
|
+
walletName: config.name,
|
|
394
|
+
windowProperty: config.windowProperty
|
|
395
|
+
});
|
|
396
|
+
wallets.push({
|
|
397
|
+
id: config.id,
|
|
398
|
+
name: config.name,
|
|
399
|
+
icon: config.icon,
|
|
400
|
+
addressTypes: config.addressTypes,
|
|
401
|
+
providers: {
|
|
402
|
+
solana: provider
|
|
403
|
+
},
|
|
404
|
+
discovery: "custom"
|
|
405
|
+
});
|
|
406
|
+
}
|
|
407
|
+
debug.log(DebugCategory.BROWSER_SDK, "Custom Solana wallet discovery completed", {
|
|
408
|
+
discoveredCount: wallets.length,
|
|
409
|
+
walletIds: wallets.map((w) => w.id)
|
|
410
|
+
});
|
|
351
411
|
return wallets;
|
|
352
412
|
}
|
|
353
413
|
function discoverPhantomWallet(addressTypes) {
|
|
@@ -369,8 +429,7 @@ function discoverPhantomWallet(addressTypes) {
|
|
|
369
429
|
return {
|
|
370
430
|
id: "phantom",
|
|
371
431
|
name: "Phantom",
|
|
372
|
-
icon:
|
|
373
|
-
// Icon will be rendered from icons package in UI components
|
|
432
|
+
icon: PHANTOM_ICON,
|
|
374
433
|
addressTypes,
|
|
375
434
|
providers: {
|
|
376
435
|
solana: addressTypes.includes(ClientAddressType.solana) ? phantomInstance.solana : void 0,
|
|
@@ -439,6 +498,9 @@ async function discoverWallets(addressTypes) {
|
|
|
439
498
|
return Array.from(walletMap.values());
|
|
440
499
|
}
|
|
441
500
|
|
|
501
|
+
// src/wallets/registry.ts
|
|
502
|
+
import { PHANTOM_ICON as PHANTOM_ICON2 } from "@phantom/constants";
|
|
503
|
+
|
|
442
504
|
// src/providers/injected/chains/InjectedWalletSolanaChain.ts
|
|
443
505
|
import { EventEmitter } from "eventemitter3";
|
|
444
506
|
import { Buffer } from "buffer";
|
|
@@ -446,61 +508,55 @@ var InjectedWalletSolanaChain = class {
|
|
|
446
508
|
constructor(provider, walletId, walletName) {
|
|
447
509
|
// Expose eventEmitter for testing - allows tests to trigger events directly
|
|
448
510
|
this.eventEmitter = new EventEmitter();
|
|
449
|
-
this._connected = false;
|
|
450
511
|
this._publicKey = null;
|
|
451
512
|
this.provider = provider;
|
|
452
513
|
this.walletId = walletId;
|
|
453
514
|
this.walletName = walletName;
|
|
454
515
|
this.setupEventListeners();
|
|
455
516
|
}
|
|
456
|
-
get connected() {
|
|
457
|
-
return this._connected;
|
|
458
|
-
}
|
|
459
517
|
get publicKey() {
|
|
460
518
|
return this._publicKey;
|
|
461
519
|
}
|
|
520
|
+
get isConnected() {
|
|
521
|
+
return this.provider.isConnected || !!this._publicKey;
|
|
522
|
+
}
|
|
462
523
|
async connect(options) {
|
|
463
|
-
debug.log(DebugCategory.INJECTED_PROVIDER, "External wallet Solana connect", {
|
|
464
|
-
walletId: this.walletId,
|
|
465
|
-
walletName: this.walletName,
|
|
466
|
-
onlyIfTrusted: options?.onlyIfTrusted
|
|
467
|
-
});
|
|
468
524
|
try {
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
debug.info(DebugCategory.INJECTED_PROVIDER, "External wallet Solana connected", {
|
|
525
|
+
await this.provider.connect(options);
|
|
526
|
+
const isConnected = this.provider.isConnected;
|
|
527
|
+
if (!isConnected || this.provider.publicKey === null) {
|
|
528
|
+
debug.error(DebugCategory.INJECTED_PROVIDER, "Provider not connected after connect() call", {
|
|
474
529
|
walletId: this.walletId,
|
|
475
530
|
walletName: this.walletName,
|
|
476
|
-
|
|
531
|
+
providerConnected: isConnected,
|
|
532
|
+
providerPublicKey: this.provider.publicKey
|
|
477
533
|
});
|
|
478
|
-
|
|
479
|
-
}
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
534
|
+
throw new Error("Provider not connected after connect() call");
|
|
535
|
+
}
|
|
536
|
+
let publicKey;
|
|
537
|
+
const providerPublicKey = this.provider.publicKey;
|
|
538
|
+
if (typeof providerPublicKey === "string") {
|
|
539
|
+
publicKey = providerPublicKey;
|
|
540
|
+
} else if (providerPublicKey !== null && providerPublicKey !== void 0 && typeof providerPublicKey.toString === "function") {
|
|
541
|
+
publicKey = providerPublicKey.toString();
|
|
542
|
+
} else {
|
|
543
|
+
debug.error(DebugCategory.INJECTED_PROVIDER, "Invalid publicKey format in provider state", {
|
|
484
544
|
walletId: this.walletId,
|
|
485
545
|
walletName: this.walletName,
|
|
486
|
-
|
|
546
|
+
publicKeyType: typeof providerPublicKey
|
|
487
547
|
});
|
|
488
|
-
|
|
489
|
-
}
|
|
490
|
-
if (Array.isArray(result) && result.length > 0) {
|
|
491
|
-
const firstAccount = result[0];
|
|
492
|
-
if (typeof firstAccount === "object" && firstAccount !== null && "address" in firstAccount) {
|
|
493
|
-
this._connected = true;
|
|
494
|
-
this._publicKey = firstAccount.address;
|
|
495
|
-
debug.info(DebugCategory.INJECTED_PROVIDER, "External wallet Solana connected", {
|
|
496
|
-
walletId: this.walletId,
|
|
497
|
-
walletName: this.walletName,
|
|
498
|
-
publicKey: firstAccount.address
|
|
499
|
-
});
|
|
500
|
-
return { publicKey: firstAccount.address };
|
|
501
|
-
}
|
|
548
|
+
throw new Error("Invalid publicKey format in provider state");
|
|
502
549
|
}
|
|
503
|
-
|
|
550
|
+
if (!publicKey || publicKey.length === 0) {
|
|
551
|
+
throw new Error("Empty publicKey from provider");
|
|
552
|
+
}
|
|
553
|
+
this._publicKey = publicKey;
|
|
554
|
+
debug.info(DebugCategory.INJECTED_PROVIDER, "External wallet Solana connected", {
|
|
555
|
+
walletId: this.walletId,
|
|
556
|
+
walletName: this.walletName,
|
|
557
|
+
publicKey
|
|
558
|
+
});
|
|
559
|
+
return { publicKey };
|
|
504
560
|
} catch (error) {
|
|
505
561
|
debug.error(DebugCategory.INJECTED_PROVIDER, "External wallet Solana connect failed", {
|
|
506
562
|
walletId: this.walletId,
|
|
@@ -517,7 +573,6 @@ var InjectedWalletSolanaChain = class {
|
|
|
517
573
|
});
|
|
518
574
|
try {
|
|
519
575
|
await this.provider.disconnect();
|
|
520
|
-
this._connected = false;
|
|
521
576
|
this._publicKey = null;
|
|
522
577
|
debug.info(DebugCategory.INJECTED_PROVIDER, "External wallet Solana disconnected", {
|
|
523
578
|
walletId: this.walletId,
|
|
@@ -653,28 +708,19 @@ var InjectedWalletSolanaChain = class {
|
|
|
653
708
|
switchNetwork(_network) {
|
|
654
709
|
return Promise.resolve();
|
|
655
710
|
}
|
|
656
|
-
getPublicKey() {
|
|
657
|
-
return Promise.resolve(this._publicKey);
|
|
658
|
-
}
|
|
659
|
-
isConnected() {
|
|
660
|
-
return this._connected;
|
|
661
|
-
}
|
|
662
711
|
setupEventListeners() {
|
|
663
712
|
if (typeof this.provider.on === "function") {
|
|
664
713
|
this.provider.on("connect", (publicKey) => {
|
|
665
|
-
this._connected = true;
|
|
666
714
|
this._publicKey = publicKey;
|
|
667
715
|
this.eventEmitter.emit("connect", publicKey);
|
|
668
716
|
});
|
|
669
717
|
this.provider.on("disconnect", () => {
|
|
670
|
-
this._connected = false;
|
|
671
718
|
this._publicKey = null;
|
|
672
719
|
this.eventEmitter.emit("disconnect");
|
|
673
720
|
});
|
|
674
721
|
this.provider.on("accountChanged", (publicKey) => {
|
|
675
|
-
this._publicKey = publicKey;
|
|
676
|
-
this.
|
|
677
|
-
this.eventEmitter.emit("accountChanged", publicKey);
|
|
722
|
+
this._publicKey = publicKey ? publicKey : null;
|
|
723
|
+
this.eventEmitter.emit("accountChanged", this._publicKey);
|
|
678
724
|
});
|
|
679
725
|
}
|
|
680
726
|
}
|
|
@@ -700,12 +746,12 @@ var WalletStandardSolanaAdapter = class {
|
|
|
700
746
|
this.walletName = walletName;
|
|
701
747
|
this.setupEventListeners();
|
|
702
748
|
}
|
|
703
|
-
get connected() {
|
|
704
|
-
return this._publicKey !== null;
|
|
705
|
-
}
|
|
706
749
|
get publicKey() {
|
|
707
750
|
return this._publicKey;
|
|
708
751
|
}
|
|
752
|
+
get isConnected() {
|
|
753
|
+
return this._publicKey !== null;
|
|
754
|
+
}
|
|
709
755
|
async connect(_options) {
|
|
710
756
|
try {
|
|
711
757
|
const connectFeature = this.wallet.features["standard:connect"];
|
|
@@ -917,12 +963,6 @@ var WalletStandardSolanaAdapter = class {
|
|
|
917
963
|
async switchNetwork(_network) {
|
|
918
964
|
return Promise.resolve();
|
|
919
965
|
}
|
|
920
|
-
getPublicKey() {
|
|
921
|
-
return Promise.resolve(this._publicKey);
|
|
922
|
-
}
|
|
923
|
-
isConnected() {
|
|
924
|
-
return this._publicKey !== null;
|
|
925
|
-
}
|
|
926
966
|
/**
|
|
927
967
|
* Set up event listeners for Wallet Standard events
|
|
928
968
|
* Maps Wallet Standard "change" events to "accountChanged" events
|
|
@@ -1057,9 +1097,6 @@ var InjectedWalletEthereumChain = class {
|
|
|
1057
1097
|
this.walletName = walletName;
|
|
1058
1098
|
this.setupEventListeners();
|
|
1059
1099
|
}
|
|
1060
|
-
get connected() {
|
|
1061
|
-
return this._connected;
|
|
1062
|
-
}
|
|
1063
1100
|
get chainId() {
|
|
1064
1101
|
return this._chainId;
|
|
1065
1102
|
}
|
|
@@ -1189,7 +1226,8 @@ var InjectedWalletEthereumChain = class {
|
|
|
1189
1226
|
address
|
|
1190
1227
|
});
|
|
1191
1228
|
try {
|
|
1192
|
-
const
|
|
1229
|
+
const providerAny = this.provider;
|
|
1230
|
+
const providerConnected = (typeof providerAny.isConnected === "function" ? providerAny.isConnected() : false) || (typeof providerAny.connected === "boolean" ? providerAny.connected : false);
|
|
1193
1231
|
if (!this._connected || this._accounts.length === 0 || !providerConnected) {
|
|
1194
1232
|
debug.log(DebugCategory.INJECTED_PROVIDER, "Not connected, attempting to connect before signing", {
|
|
1195
1233
|
walletId: this.walletId,
|
|
@@ -1503,8 +1541,7 @@ var InjectedWalletRegistry = class {
|
|
|
1503
1541
|
const phantomWallet = {
|
|
1504
1542
|
id: "phantom",
|
|
1505
1543
|
name: "Phantom",
|
|
1506
|
-
icon:
|
|
1507
|
-
// Icon will be rendered from icons package in UI components
|
|
1544
|
+
icon: PHANTOM_ICON2,
|
|
1508
1545
|
addressTypes,
|
|
1509
1546
|
providers: wrappedProviders,
|
|
1510
1547
|
isPhantom: true,
|
|
@@ -1650,13 +1687,13 @@ var InjectedProvider = class {
|
|
|
1650
1687
|
return provider;
|
|
1651
1688
|
}
|
|
1652
1689
|
get solana() {
|
|
1653
|
-
return this.getChainProvider(
|
|
1690
|
+
return this.getChainProvider(AddressType3.solana, "solana", "Solana");
|
|
1654
1691
|
}
|
|
1655
1692
|
/**
|
|
1656
1693
|
* Access to Ethereum chain operations
|
|
1657
1694
|
*/
|
|
1658
1695
|
get ethereum() {
|
|
1659
|
-
return this.getChainProvider(
|
|
1696
|
+
return this.getChainProvider(AddressType3.ethereum, "ethereum", "Ethereum");
|
|
1660
1697
|
}
|
|
1661
1698
|
validateAndSelectWallet(requestedWalletId) {
|
|
1662
1699
|
if (!this.walletRegistry.has(requestedWalletId)) {
|
|
@@ -1697,7 +1734,7 @@ var InjectedProvider = class {
|
|
|
1697
1734
|
this.setupEventListeners(walletInfo);
|
|
1698
1735
|
}
|
|
1699
1736
|
const connectedAddresses = [];
|
|
1700
|
-
if (this.addressTypes.includes(
|
|
1737
|
+
if (this.addressTypes.includes(AddressType3.solana) && walletInfo.providers?.solana) {
|
|
1701
1738
|
debug.log(DebugCategory.INJECTED_PROVIDER, "Attempting Solana connection", {
|
|
1702
1739
|
walletId: this.selectedWalletId,
|
|
1703
1740
|
walletName: walletInfo.name,
|
|
@@ -1709,7 +1746,7 @@ var InjectedProvider = class {
|
|
|
1709
1746
|
);
|
|
1710
1747
|
const address = result.publicKey;
|
|
1711
1748
|
connectedAddresses.push({
|
|
1712
|
-
addressType:
|
|
1749
|
+
addressType: AddressType3.solana,
|
|
1713
1750
|
address
|
|
1714
1751
|
});
|
|
1715
1752
|
debug.info(DebugCategory.INJECTED_PROVIDER, "Solana connected successfully", {
|
|
@@ -1718,19 +1755,14 @@ var InjectedProvider = class {
|
|
|
1718
1755
|
walletName: walletInfo.name
|
|
1719
1756
|
});
|
|
1720
1757
|
} catch (err) {
|
|
1721
|
-
debug.warn(DebugCategory.INJECTED_PROVIDER, "Failed to connect Solana,
|
|
1758
|
+
debug.warn(DebugCategory.INJECTED_PROVIDER, "Failed to connect Solana, continuing with other chains", {
|
|
1722
1759
|
error: err,
|
|
1723
1760
|
walletId: this.selectedWalletId,
|
|
1724
1761
|
walletName: walletInfo.name
|
|
1725
1762
|
});
|
|
1726
|
-
this.emit("connect_error", {
|
|
1727
|
-
error: err instanceof Error ? err.message : "Failed to connect",
|
|
1728
|
-
source: options?.skipEventListeners ? "auto-connect" : "manual-connect"
|
|
1729
|
-
});
|
|
1730
|
-
throw err;
|
|
1731
1763
|
}
|
|
1732
1764
|
}
|
|
1733
|
-
if (this.addressTypes.includes(
|
|
1765
|
+
if (this.addressTypes.includes(AddressType3.ethereum) && walletInfo.providers?.ethereum) {
|
|
1734
1766
|
debug.log(DebugCategory.INJECTED_PROVIDER, "Attempting Ethereum connection", {
|
|
1735
1767
|
walletId: this.selectedWalletId,
|
|
1736
1768
|
walletName: walletInfo.name,
|
|
@@ -1746,7 +1778,7 @@ var InjectedProvider = class {
|
|
|
1746
1778
|
if (accounts.length > 0) {
|
|
1747
1779
|
connectedAddresses.push(
|
|
1748
1780
|
...accounts.map((address) => ({
|
|
1749
|
-
addressType:
|
|
1781
|
+
addressType: AddressType3.ethereum,
|
|
1750
1782
|
address
|
|
1751
1783
|
}))
|
|
1752
1784
|
);
|
|
@@ -1757,16 +1789,11 @@ var InjectedProvider = class {
|
|
|
1757
1789
|
});
|
|
1758
1790
|
}
|
|
1759
1791
|
} catch (err) {
|
|
1760
|
-
debug.warn(DebugCategory.INJECTED_PROVIDER, "Failed to connect Ethereum,
|
|
1792
|
+
debug.warn(DebugCategory.INJECTED_PROVIDER, "Failed to connect Ethereum, continuing with other chains", {
|
|
1761
1793
|
error: err,
|
|
1762
1794
|
walletId: this.selectedWalletId,
|
|
1763
1795
|
walletName: walletInfo.name
|
|
1764
1796
|
});
|
|
1765
|
-
this.emit("connect_error", {
|
|
1766
|
-
error: err instanceof Error ? err.message : "Failed to connect",
|
|
1767
|
-
source: options?.skipEventListeners ? "auto-connect" : "manual-connect"
|
|
1768
|
-
});
|
|
1769
|
-
throw err;
|
|
1770
1797
|
}
|
|
1771
1798
|
}
|
|
1772
1799
|
return connectedAddresses;
|
|
@@ -1826,7 +1853,8 @@ var InjectedProvider = class {
|
|
|
1826
1853
|
this.emit("connect", {
|
|
1827
1854
|
addresses: connectedAddresses,
|
|
1828
1855
|
source: "manual-connect",
|
|
1829
|
-
authUserId
|
|
1856
|
+
authUserId,
|
|
1857
|
+
walletId
|
|
1830
1858
|
});
|
|
1831
1859
|
return result;
|
|
1832
1860
|
}
|
|
@@ -1838,12 +1866,13 @@ var InjectedProvider = class {
|
|
|
1838
1866
|
if (authOptions.provider !== "injected") {
|
|
1839
1867
|
throw new Error(`Invalid provider for injected connection: ${authOptions.provider}. Must be "injected"`);
|
|
1840
1868
|
}
|
|
1869
|
+
const requestedWalletId = authOptions.walletId || "phantom";
|
|
1841
1870
|
this.emit("connect_start", {
|
|
1842
1871
|
source: "manual-connect",
|
|
1843
|
-
providerType: "injected"
|
|
1872
|
+
providerType: "injected",
|
|
1873
|
+
walletId: requestedWalletId
|
|
1844
1874
|
});
|
|
1845
1875
|
try {
|
|
1846
|
-
const requestedWalletId = authOptions.walletId || "phantom";
|
|
1847
1876
|
const walletInfo = this.validateAndSelectWallet(requestedWalletId);
|
|
1848
1877
|
const connectedAddresses = await this.connectToWallet(walletInfo);
|
|
1849
1878
|
return await this.finalizeConnection(connectedAddresses, "injected", this.selectedWalletId || void 0);
|
|
@@ -1859,7 +1888,7 @@ var InjectedProvider = class {
|
|
|
1859
1888
|
debug.info(DebugCategory.INJECTED_PROVIDER, "Starting injected provider disconnect");
|
|
1860
1889
|
const walletInfo = this.walletRegistry.getById(this.selectedWalletId || "phantom");
|
|
1861
1890
|
if (walletInfo?.providers) {
|
|
1862
|
-
if (this.addressTypes.includes(
|
|
1891
|
+
if (this.addressTypes.includes(AddressType3.solana) && walletInfo.providers.solana) {
|
|
1863
1892
|
try {
|
|
1864
1893
|
await walletInfo.providers.solana.disconnect();
|
|
1865
1894
|
debug.log(DebugCategory.INJECTED_PROVIDER, "Solana disconnected successfully");
|
|
@@ -1867,7 +1896,7 @@ var InjectedProvider = class {
|
|
|
1867
1896
|
debug.warn(DebugCategory.INJECTED_PROVIDER, "Failed to disconnect Solana", { error: err });
|
|
1868
1897
|
}
|
|
1869
1898
|
}
|
|
1870
|
-
if (this.addressTypes.includes(
|
|
1899
|
+
if (this.addressTypes.includes(AddressType3.ethereum) && walletInfo.providers.ethereum) {
|
|
1871
1900
|
try {
|
|
1872
1901
|
await walletInfo.providers.ethereum.disconnect();
|
|
1873
1902
|
debug.log(DebugCategory.INJECTED_PROVIDER, "Ethereum disconnected successfully");
|
|
@@ -1963,7 +1992,8 @@ var InjectedProvider = class {
|
|
|
1963
1992
|
this.emit("connect", {
|
|
1964
1993
|
addresses: connectedAddresses,
|
|
1965
1994
|
source: "auto-connect",
|
|
1966
|
-
authUserId
|
|
1995
|
+
authUserId,
|
|
1996
|
+
walletId: this.selectedWalletId
|
|
1967
1997
|
});
|
|
1968
1998
|
debug.info(DebugCategory.INJECTED_PROVIDER, "Auto-connect successful", {
|
|
1969
1999
|
addressCount: connectedAddresses.length,
|
|
@@ -2024,12 +2054,13 @@ var InjectedProvider = class {
|
|
|
2024
2054
|
createSolanaConnectHandler(walletId, source) {
|
|
2025
2055
|
return async (publicKey) => {
|
|
2026
2056
|
debug.log(DebugCategory.INJECTED_PROVIDER, "Solana connect event received", { publicKey, walletId });
|
|
2027
|
-
const newAddresses = this.updateWalletAddresses(walletId, [publicKey],
|
|
2057
|
+
const newAddresses = this.updateWalletAddresses(walletId, [publicKey], AddressType3.solana);
|
|
2028
2058
|
const authUserId = await this.getAuthUserId("Solana connect event");
|
|
2029
2059
|
this.emit("connect", {
|
|
2030
2060
|
addresses: newAddresses,
|
|
2031
2061
|
source,
|
|
2032
|
-
authUserId
|
|
2062
|
+
authUserId,
|
|
2063
|
+
walletId: this.selectedWalletId
|
|
2033
2064
|
});
|
|
2034
2065
|
};
|
|
2035
2066
|
}
|
|
@@ -2040,7 +2071,7 @@ var InjectedProvider = class {
|
|
|
2040
2071
|
return () => {
|
|
2041
2072
|
debug.log(DebugCategory.INJECTED_PROVIDER, "Solana disconnect event received", { walletId });
|
|
2042
2073
|
const state = this.getWalletState(walletId);
|
|
2043
|
-
const filteredAddresses = state.addresses.filter((addr) => addr.addressType !==
|
|
2074
|
+
const filteredAddresses = state.addresses.filter((addr) => addr.addressType !== AddressType3.solana);
|
|
2044
2075
|
this.setWalletState(walletId, {
|
|
2045
2076
|
connected: filteredAddresses.length > 0,
|
|
2046
2077
|
addresses: filteredAddresses
|
|
@@ -2058,16 +2089,17 @@ var InjectedProvider = class {
|
|
|
2058
2089
|
return async (publicKey) => {
|
|
2059
2090
|
debug.log(DebugCategory.INJECTED_PROVIDER, "Solana account changed event received", { publicKey, walletId });
|
|
2060
2091
|
if (publicKey) {
|
|
2061
|
-
const newAddresses = this.updateWalletAddresses(walletId, [publicKey],
|
|
2092
|
+
const newAddresses = this.updateWalletAddresses(walletId, [publicKey], AddressType3.solana);
|
|
2062
2093
|
const authUserId = await this.getAuthUserId("Solana account changed event");
|
|
2063
2094
|
this.emit("connect", {
|
|
2064
2095
|
addresses: newAddresses,
|
|
2065
2096
|
source: this.getAccountChangeSource(source),
|
|
2066
|
-
authUserId
|
|
2097
|
+
authUserId,
|
|
2098
|
+
walletId: this.selectedWalletId
|
|
2067
2099
|
});
|
|
2068
2100
|
} else {
|
|
2069
2101
|
const state = this.getWalletState(walletId);
|
|
2070
|
-
const otherAddresses = state.addresses.filter((addr) => addr.addressType !==
|
|
2102
|
+
const otherAddresses = state.addresses.filter((addr) => addr.addressType !== AddressType3.solana);
|
|
2071
2103
|
this.setWalletState(walletId, {
|
|
2072
2104
|
connected: otherAddresses.length > 0,
|
|
2073
2105
|
addresses: otherAddresses
|
|
@@ -2099,12 +2131,13 @@ var InjectedProvider = class {
|
|
|
2099
2131
|
}
|
|
2100
2132
|
debug.log(DebugCategory.INJECTED_PROVIDER, "Ethereum connect event received", { accounts, walletId });
|
|
2101
2133
|
if (accounts.length > 0) {
|
|
2102
|
-
const newAddresses = this.updateWalletAddresses(walletId, accounts,
|
|
2134
|
+
const newAddresses = this.updateWalletAddresses(walletId, accounts, AddressType3.ethereum);
|
|
2103
2135
|
const authUserId = await this.getAuthUserId("Ethereum connect event");
|
|
2104
2136
|
this.emit("connect", {
|
|
2105
2137
|
addresses: newAddresses,
|
|
2106
2138
|
source,
|
|
2107
|
-
authUserId
|
|
2139
|
+
authUserId,
|
|
2140
|
+
walletId: this.selectedWalletId
|
|
2108
2141
|
});
|
|
2109
2142
|
}
|
|
2110
2143
|
};
|
|
@@ -2116,7 +2149,7 @@ var InjectedProvider = class {
|
|
|
2116
2149
|
return () => {
|
|
2117
2150
|
debug.log(DebugCategory.INJECTED_PROVIDER, "Ethereum disconnect event received", { walletId });
|
|
2118
2151
|
const state = this.getWalletState(walletId);
|
|
2119
|
-
const filteredAddresses = state.addresses.filter((addr) => addr.addressType !==
|
|
2152
|
+
const filteredAddresses = state.addresses.filter((addr) => addr.addressType !== AddressType3.ethereum);
|
|
2120
2153
|
this.setWalletState(walletId, {
|
|
2121
2154
|
connected: filteredAddresses.length > 0,
|
|
2122
2155
|
addresses: filteredAddresses
|
|
@@ -2133,16 +2166,17 @@ var InjectedProvider = class {
|
|
|
2133
2166
|
return async (accounts) => {
|
|
2134
2167
|
debug.log(DebugCategory.INJECTED_PROVIDER, "Ethereum accounts changed event received", { accounts, walletId });
|
|
2135
2168
|
if (accounts && accounts.length > 0) {
|
|
2136
|
-
const newAddresses = this.updateWalletAddresses(walletId, accounts,
|
|
2169
|
+
const newAddresses = this.updateWalletAddresses(walletId, accounts, AddressType3.ethereum);
|
|
2137
2170
|
const authUserId = await this.getAuthUserId("Ethereum accounts changed event");
|
|
2138
2171
|
this.emit("connect", {
|
|
2139
2172
|
addresses: newAddresses,
|
|
2140
2173
|
source: this.getAccountChangeSource(source),
|
|
2141
|
-
authUserId
|
|
2174
|
+
authUserId,
|
|
2175
|
+
walletId: this.selectedWalletId
|
|
2142
2176
|
});
|
|
2143
2177
|
} else {
|
|
2144
2178
|
const state = this.getWalletState(walletId);
|
|
2145
|
-
const otherAddresses = state.addresses.filter((addr) => addr.addressType !==
|
|
2179
|
+
const otherAddresses = state.addresses.filter((addr) => addr.addressType !== AddressType3.ethereum);
|
|
2146
2180
|
this.setWalletState(walletId, {
|
|
2147
2181
|
connected: otherAddresses.length > 0,
|
|
2148
2182
|
addresses: otherAddresses
|
|
@@ -2336,7 +2370,9 @@ var InjectedProvider = class {
|
|
|
2336
2370
|
this.eventListenerCleanups.set(walletId, [...existingCleanups, ...cleanups]);
|
|
2337
2371
|
}
|
|
2338
2372
|
/**
|
|
2339
|
-
* Unified event listener setup for all wallet types (Phantom and external)
|
|
2373
|
+
* Unified event listener setup for all wallet types (Phantom and external).
|
|
2374
|
+
* Cleans up listeners for previously selected wallets to prevent stale events
|
|
2375
|
+
* from causing walletId flicker during connections.
|
|
2340
2376
|
*/
|
|
2341
2377
|
setupEventListeners(walletInfo) {
|
|
2342
2378
|
const walletId = this.selectedWalletId || "phantom";
|
|
@@ -2344,11 +2380,22 @@ var InjectedProvider = class {
|
|
|
2344
2380
|
debug.log(DebugCategory.INJECTED_PROVIDER, "Event listeners already set up for wallet", { walletId });
|
|
2345
2381
|
return;
|
|
2346
2382
|
}
|
|
2383
|
+
for (const existingWalletId of this.eventListenersSetup) {
|
|
2384
|
+
if (existingWalletId === walletId) {
|
|
2385
|
+
continue;
|
|
2386
|
+
}
|
|
2387
|
+
const cleanups = this.eventListenerCleanups.get(existingWalletId);
|
|
2388
|
+
if (cleanups) {
|
|
2389
|
+
debug.log(DebugCategory.INJECTED_PROVIDER, "Cleaning up event listeners for wallet", { existingWalletId });
|
|
2390
|
+
cleanups.forEach((cleanup) => cleanup());
|
|
2391
|
+
}
|
|
2392
|
+
this.eventListenersSetup.delete(existingWalletId);
|
|
2393
|
+
}
|
|
2347
2394
|
debug.log(DebugCategory.INJECTED_PROVIDER, "Setting up event listeners", { walletId });
|
|
2348
|
-
if (this.addressTypes.includes(
|
|
2395
|
+
if (this.addressTypes.includes(AddressType3.solana) && walletInfo.providers?.solana) {
|
|
2349
2396
|
this.setupSolanaEventListeners(walletInfo.providers.solana, walletId, "wallet");
|
|
2350
2397
|
}
|
|
2351
|
-
if (this.addressTypes.includes(
|
|
2398
|
+
if (this.addressTypes.includes(AddressType3.ethereum) && walletInfo.providers?.ethereum) {
|
|
2352
2399
|
this.setupEthereumEventListeners(walletInfo.providers.ethereum, walletId, "wallet");
|
|
2353
2400
|
}
|
|
2354
2401
|
this.eventListenersSetup.add(walletId);
|
|
@@ -2492,7 +2539,7 @@ var BrowserURLParamsAccessor = class {
|
|
|
2492
2539
|
var browserUrlParamsAccessor = new BrowserURLParamsAccessor();
|
|
2493
2540
|
|
|
2494
2541
|
// src/providers/embedded/adapters/auth.ts
|
|
2495
|
-
import { DEFAULT_AUTH_URL } from "@phantom/constants";
|
|
2542
|
+
import { DEFAULT_AUTH_URL, DEFAULT_AUTHENTICATOR_ALGORITHM } from "@phantom/constants";
|
|
2496
2543
|
|
|
2497
2544
|
// src/utils/browser-detection.ts
|
|
2498
2545
|
function parseBrowserFromUserAgent(userAgent, hasBraveAPI) {
|
|
@@ -2667,9 +2714,10 @@ var BrowserAuthProvider = class {
|
|
|
2667
2714
|
// OAuth session management - defaults to allow refresh unless explicitly clearing after logout
|
|
2668
2715
|
clear_previous_session: (phantomOptions.clearPreviousSession ?? false).toString(),
|
|
2669
2716
|
allow_refresh: (phantomOptions.allowRefresh ?? true).toString(),
|
|
2670
|
-
sdk_version: "1.0.
|
|
2717
|
+
sdk_version: "1.0.4",
|
|
2671
2718
|
sdk_type: "browser",
|
|
2672
|
-
platform: detectBrowser().name
|
|
2719
|
+
platform: detectBrowser().name,
|
|
2720
|
+
algorithm: phantomOptions.algorithm || DEFAULT_AUTHENTICATOR_ALGORITHM
|
|
2673
2721
|
});
|
|
2674
2722
|
if (phantomOptions.provider) {
|
|
2675
2723
|
debug.log(DebugCategory.PHANTOM_CONNECT_AUTH, "Provider specified, will skip selection", {
|
|
@@ -2697,7 +2745,7 @@ var BrowserAuthProvider = class {
|
|
|
2697
2745
|
resolve();
|
|
2698
2746
|
});
|
|
2699
2747
|
}
|
|
2700
|
-
resumeAuthFromRedirect(provider) {
|
|
2748
|
+
async resumeAuthFromRedirect(provider) {
|
|
2701
2749
|
try {
|
|
2702
2750
|
const walletId = this.urlParamsAccessor.getParam("wallet_id");
|
|
2703
2751
|
const sessionId = this.urlParamsAccessor.getParam("session_id");
|
|
@@ -2772,14 +2820,14 @@ var BrowserAuthProvider = class {
|
|
|
2772
2820
|
}
|
|
2773
2821
|
);
|
|
2774
2822
|
}
|
|
2775
|
-
return {
|
|
2823
|
+
return Promise.resolve({
|
|
2776
2824
|
walletId,
|
|
2777
2825
|
organizationId,
|
|
2778
2826
|
accountDerivationIndex: accountDerivationIndex ? parseInt(accountDerivationIndex) : 0,
|
|
2779
2827
|
expiresInMs: expiresInMs ? parseInt(expiresInMs) : 0,
|
|
2780
2828
|
authUserId: authUserId || void 0,
|
|
2781
2829
|
provider
|
|
2782
|
-
};
|
|
2830
|
+
});
|
|
2783
2831
|
} catch (error) {
|
|
2784
2832
|
sessionStorage.removeItem("phantom-auth-context");
|
|
2785
2833
|
throw error;
|
|
@@ -2787,31 +2835,292 @@ var BrowserAuthProvider = class {
|
|
|
2787
2835
|
}
|
|
2788
2836
|
};
|
|
2789
2837
|
|
|
2790
|
-
// src/providers/embedded/adapters/
|
|
2791
|
-
import {
|
|
2792
|
-
|
|
2793
|
-
|
|
2794
|
-
|
|
2795
|
-
|
|
2796
|
-
|
|
2797
|
-
|
|
2798
|
-
|
|
2838
|
+
// src/providers/embedded/adapters/Auth2AuthProvider.ts
|
|
2839
|
+
import {
|
|
2840
|
+
createCodeVerifier,
|
|
2841
|
+
createSalt,
|
|
2842
|
+
createConnectStartUrl,
|
|
2843
|
+
exchangeAuthCode,
|
|
2844
|
+
Auth2KmsRpcClient
|
|
2845
|
+
} from "@phantom/auth2";
|
|
2846
|
+
var Auth2AuthProvider = class {
|
|
2847
|
+
constructor(stamper, storage, urlParamsAccessor, auth2ProviderOptions, kmsClientOptions) {
|
|
2848
|
+
this.stamper = stamper;
|
|
2849
|
+
this.storage = storage;
|
|
2850
|
+
this.urlParamsAccessor = urlParamsAccessor;
|
|
2851
|
+
this.auth2ProviderOptions = auth2ProviderOptions;
|
|
2852
|
+
this.kms = new Auth2KmsRpcClient(stamper, kmsClientOptions);
|
|
2799
2853
|
}
|
|
2800
|
-
|
|
2801
|
-
|
|
2802
|
-
|
|
2854
|
+
/** Redirect the browser. Extracted as a static method so tests can spy on it. */
|
|
2855
|
+
static navigate(url) {
|
|
2856
|
+
window.location.href = url;
|
|
2857
|
+
}
|
|
2858
|
+
/**
|
|
2859
|
+
* Builds the Auth2 /login/start URL and redirects the browser.
|
|
2860
|
+
*
|
|
2861
|
+
* Called by EmbeddedProvider.handleRedirectAuth() after the stamper has
|
|
2862
|
+
* already been initialized and a pending Session has been saved to storage.
|
|
2863
|
+
* We store the PKCE code_verifier and salt into that session so they survive
|
|
2864
|
+
* the page redirect without ever touching sessionStorage.
|
|
2865
|
+
*/
|
|
2866
|
+
async authenticate(options) {
|
|
2867
|
+
if (!this.stamper.getKeyInfo()) {
|
|
2868
|
+
await this.stamper.init();
|
|
2869
|
+
}
|
|
2870
|
+
const keyPair = this.stamper.getCryptoKeyPair();
|
|
2871
|
+
if (!keyPair) {
|
|
2872
|
+
throw new Error("Stamper key pair not found.");
|
|
2873
|
+
}
|
|
2874
|
+
const codeVerifier = createCodeVerifier();
|
|
2875
|
+
const salt = createSalt();
|
|
2876
|
+
const session = await this.storage.getSession();
|
|
2877
|
+
if (!session) {
|
|
2878
|
+
throw new Error("Session not found.");
|
|
2879
|
+
}
|
|
2880
|
+
await this.storage.saveSession({ ...session, pkceCodeVerifier: codeVerifier, salt });
|
|
2881
|
+
const url = await createConnectStartUrl({
|
|
2882
|
+
keyPair,
|
|
2883
|
+
connectLoginUrl: this.auth2ProviderOptions.connectLoginUrl,
|
|
2884
|
+
clientId: this.auth2ProviderOptions.clientId,
|
|
2885
|
+
redirectUri: this.auth2ProviderOptions.redirectUri,
|
|
2886
|
+
sessionId: options.sessionId,
|
|
2887
|
+
provider: options.provider,
|
|
2888
|
+
codeVerifier,
|
|
2889
|
+
salt
|
|
2890
|
+
});
|
|
2891
|
+
Auth2AuthProvider.navigate(url);
|
|
2892
|
+
}
|
|
2893
|
+
/**
|
|
2894
|
+
* Processes the Auth2 callback after the browser returns from /login/start.
|
|
2895
|
+
*
|
|
2896
|
+
* Exchanges the authorization code for tokens, discovers the organization
|
|
2897
|
+
* and wallet via KMS RPC, then returns a completed AuthResult.
|
|
2898
|
+
*/
|
|
2899
|
+
async resumeAuthFromRedirect(provider) {
|
|
2900
|
+
const code = this.urlParamsAccessor.getParam("code");
|
|
2901
|
+
if (!code) {
|
|
2902
|
+
return null;
|
|
2803
2903
|
}
|
|
2804
|
-
|
|
2805
|
-
|
|
2806
|
-
return false;
|
|
2904
|
+
if (!this.stamper.getKeyInfo()) {
|
|
2905
|
+
await this.stamper.init();
|
|
2807
2906
|
}
|
|
2808
|
-
|
|
2809
|
-
|
|
2810
|
-
|
|
2811
|
-
|
|
2907
|
+
const session = await this.storage.getSession();
|
|
2908
|
+
if (!session) {
|
|
2909
|
+
throw new Error("Session not found.");
|
|
2910
|
+
}
|
|
2911
|
+
const codeVerifier = session?.pkceCodeVerifier;
|
|
2912
|
+
if (!codeVerifier) {
|
|
2913
|
+
return null;
|
|
2914
|
+
}
|
|
2915
|
+
const state = this.urlParamsAccessor.getParam("state");
|
|
2916
|
+
if (!state || state !== session.sessionId) {
|
|
2917
|
+
throw new Error("Missing or invalid Auth2 state parameter \u2014 possible CSRF attack.");
|
|
2918
|
+
}
|
|
2919
|
+
const error = this.urlParamsAccessor.getParam("error");
|
|
2920
|
+
if (error) {
|
|
2921
|
+
const description = this.urlParamsAccessor.getParam("error_description");
|
|
2922
|
+
throw new Error(`Auth2 callback error: ${description ?? error}`);
|
|
2923
|
+
}
|
|
2924
|
+
const { idToken, bearerToken, authUserId, expiresInMs } = await exchangeAuthCode({
|
|
2925
|
+
authApiBaseUrl: this.auth2ProviderOptions.authApiBaseUrl,
|
|
2926
|
+
clientId: this.auth2ProviderOptions.clientId,
|
|
2927
|
+
redirectUri: this.auth2ProviderOptions.redirectUri,
|
|
2928
|
+
code,
|
|
2929
|
+
codeVerifier
|
|
2930
|
+
});
|
|
2931
|
+
this.stamper.idToken = idToken;
|
|
2932
|
+
this.stamper.salt = session?.salt;
|
|
2933
|
+
await this.storage.saveSession({
|
|
2934
|
+
...session,
|
|
2935
|
+
status: "completed",
|
|
2936
|
+
bearerToken,
|
|
2937
|
+
authUserId,
|
|
2938
|
+
pkceCodeVerifier: void 0,
|
|
2939
|
+
// no longer needed after code exchange
|
|
2940
|
+
salt: void 0
|
|
2941
|
+
// no longer needed after nonce binding is complete
|
|
2942
|
+
});
|
|
2943
|
+
const { organizationId, walletId } = await this.kms.discoverOrganizationAndWalletId(bearerToken, authUserId);
|
|
2944
|
+
return {
|
|
2945
|
+
walletId,
|
|
2946
|
+
organizationId,
|
|
2947
|
+
provider,
|
|
2948
|
+
accountDerivationIndex: 0,
|
|
2949
|
+
// discoverWalletId uses derivation index of 0.
|
|
2950
|
+
expiresInMs,
|
|
2951
|
+
authUserId,
|
|
2952
|
+
bearerToken
|
|
2953
|
+
};
|
|
2812
2954
|
}
|
|
2813
|
-
}
|
|
2814
|
-
|
|
2955
|
+
};
|
|
2956
|
+
|
|
2957
|
+
// src/providers/embedded/adapters/Auth2Stamper.ts
|
|
2958
|
+
import bs582 from "bs58";
|
|
2959
|
+
import { base64urlEncode } from "@phantom/base64url";
|
|
2960
|
+
import { Algorithm } from "@phantom/sdk-types";
|
|
2961
|
+
var STORE_NAME = "crypto-keys";
|
|
2962
|
+
var ACTIVE_KEY = "auth2-p256-signing-key";
|
|
2963
|
+
var Auth2Stamper = class {
|
|
2964
|
+
/**
|
|
2965
|
+
* @param dbName - IndexedDB database name (use a unique name per app to
|
|
2966
|
+
* avoid key collisions with other stampers, e.g. `phantom-auth2-<appId>`).
|
|
2967
|
+
*/
|
|
2968
|
+
constructor(dbName) {
|
|
2969
|
+
this.dbName = dbName;
|
|
2970
|
+
this.db = null;
|
|
2971
|
+
this.keyPair = null;
|
|
2972
|
+
this._keyInfo = null;
|
|
2973
|
+
this.algorithm = Algorithm.secp256r1;
|
|
2974
|
+
this.type = "PKI";
|
|
2975
|
+
}
|
|
2976
|
+
async init() {
|
|
2977
|
+
await this.openDB();
|
|
2978
|
+
const stored = await this.loadKeyPair();
|
|
2979
|
+
if (stored) {
|
|
2980
|
+
this.keyPair = stored.keyPair;
|
|
2981
|
+
this._keyInfo = stored.keyInfo;
|
|
2982
|
+
return this._keyInfo;
|
|
2983
|
+
}
|
|
2984
|
+
return this.generateAndStore();
|
|
2985
|
+
}
|
|
2986
|
+
getKeyInfo() {
|
|
2987
|
+
return this._keyInfo;
|
|
2988
|
+
}
|
|
2989
|
+
getCryptoKeyPair() {
|
|
2990
|
+
return this.keyPair;
|
|
2991
|
+
}
|
|
2992
|
+
async stamp(params) {
|
|
2993
|
+
if (!this.keyPair || !this._keyInfo) {
|
|
2994
|
+
throw new Error("Auth2Stamper not initialized. Call init() first.");
|
|
2995
|
+
}
|
|
2996
|
+
const signatureRaw = await crypto.subtle.sign(
|
|
2997
|
+
{ name: "ECDSA", hash: "SHA-256" },
|
|
2998
|
+
this.keyPair.privateKey,
|
|
2999
|
+
new Uint8Array(params.data)
|
|
3000
|
+
);
|
|
3001
|
+
const rawPublicKey = bs582.decode(this._keyInfo.publicKey);
|
|
3002
|
+
if (this.idToken === void 0 || this.salt === void 0) {
|
|
3003
|
+
throw new Error("Auth2Stamper not initialized with idToken or salt.");
|
|
3004
|
+
}
|
|
3005
|
+
const stampData = {
|
|
3006
|
+
kind: "OIDC",
|
|
3007
|
+
idToken: this.idToken,
|
|
3008
|
+
publicKey: base64urlEncode(rawPublicKey),
|
|
3009
|
+
algorithm: "Secp256r1",
|
|
3010
|
+
salt: this.salt,
|
|
3011
|
+
signature: base64urlEncode(new Uint8Array(signatureRaw))
|
|
3012
|
+
};
|
|
3013
|
+
return base64urlEncode(new TextEncoder().encode(JSON.stringify(stampData)));
|
|
3014
|
+
}
|
|
3015
|
+
async resetKeyPair() {
|
|
3016
|
+
await this.clearStoredKey();
|
|
3017
|
+
this.keyPair = null;
|
|
3018
|
+
this._keyInfo = null;
|
|
3019
|
+
return this.generateAndStore();
|
|
3020
|
+
}
|
|
3021
|
+
async clear() {
|
|
3022
|
+
await this.clearStoredKey();
|
|
3023
|
+
this.keyPair = null;
|
|
3024
|
+
this._keyInfo = null;
|
|
3025
|
+
}
|
|
3026
|
+
// Auth2 doesn't use key rotation; provide minimal no-op implementations.
|
|
3027
|
+
async rotateKeyPair() {
|
|
3028
|
+
return this.init();
|
|
3029
|
+
}
|
|
3030
|
+
// eslint-disable-next-line @typescript-eslint/require-await
|
|
3031
|
+
async commitRotation(authenticatorId) {
|
|
3032
|
+
if (this._keyInfo) {
|
|
3033
|
+
this._keyInfo.authenticatorId = authenticatorId;
|
|
3034
|
+
}
|
|
3035
|
+
}
|
|
3036
|
+
async rollbackRotation() {
|
|
3037
|
+
}
|
|
3038
|
+
async generateAndStore() {
|
|
3039
|
+
const keyPair = await crypto.subtle.generateKey(
|
|
3040
|
+
{ name: "ECDSA", namedCurve: "P-256" },
|
|
3041
|
+
false,
|
|
3042
|
+
// non-extractable — private key never leaves Web Crypto
|
|
3043
|
+
["sign", "verify"]
|
|
3044
|
+
);
|
|
3045
|
+
const rawPublicKey = new Uint8Array(await crypto.subtle.exportKey("raw", keyPair.publicKey));
|
|
3046
|
+
const publicKeyBase58 = bs582.encode(rawPublicKey);
|
|
3047
|
+
const keyIdBuffer = await crypto.subtle.digest("SHA-256", rawPublicKey.buffer);
|
|
3048
|
+
const keyId = base64urlEncode(new Uint8Array(keyIdBuffer)).substring(0, 16);
|
|
3049
|
+
this.keyPair = keyPair;
|
|
3050
|
+
this._keyInfo = {
|
|
3051
|
+
keyId,
|
|
3052
|
+
publicKey: publicKeyBase58,
|
|
3053
|
+
createdAt: Date.now()
|
|
3054
|
+
};
|
|
3055
|
+
await this.storeKeyPair(keyPair, this._keyInfo);
|
|
3056
|
+
return this._keyInfo;
|
|
3057
|
+
}
|
|
3058
|
+
async openDB() {
|
|
3059
|
+
return new Promise((resolve, reject) => {
|
|
3060
|
+
const request = indexedDB.open(this.dbName, 1);
|
|
3061
|
+
request.onsuccess = () => {
|
|
3062
|
+
this.db = request.result;
|
|
3063
|
+
resolve();
|
|
3064
|
+
};
|
|
3065
|
+
request.onerror = () => reject(request.error);
|
|
3066
|
+
request.onupgradeneeded = (event) => {
|
|
3067
|
+
const db = event.target.result;
|
|
3068
|
+
if (!db.objectStoreNames.contains(STORE_NAME)) {
|
|
3069
|
+
db.createObjectStore(STORE_NAME);
|
|
3070
|
+
}
|
|
3071
|
+
};
|
|
3072
|
+
});
|
|
3073
|
+
}
|
|
3074
|
+
async loadKeyPair() {
|
|
3075
|
+
return new Promise((resolve, reject) => {
|
|
3076
|
+
if (!this.db) {
|
|
3077
|
+
throw new Error("Database not initialized");
|
|
3078
|
+
}
|
|
3079
|
+
const request = this.db.transaction([STORE_NAME], "readonly").objectStore(STORE_NAME).get(ACTIVE_KEY);
|
|
3080
|
+
request.onsuccess = () => {
|
|
3081
|
+
resolve(request.result ?? null);
|
|
3082
|
+
};
|
|
3083
|
+
request.onerror = () => {
|
|
3084
|
+
reject(request.error);
|
|
3085
|
+
};
|
|
3086
|
+
});
|
|
3087
|
+
}
|
|
3088
|
+
async storeKeyPair(keyPair, keyInfo) {
|
|
3089
|
+
return new Promise((resolve, reject) => {
|
|
3090
|
+
if (!this.db) {
|
|
3091
|
+
throw new Error("Database not initialized");
|
|
3092
|
+
}
|
|
3093
|
+
const request = this.db.transaction([STORE_NAME], "readwrite").objectStore(STORE_NAME).put({ keyPair, keyInfo }, ACTIVE_KEY);
|
|
3094
|
+
request.onsuccess = () => {
|
|
3095
|
+
resolve();
|
|
3096
|
+
};
|
|
3097
|
+
request.onerror = () => {
|
|
3098
|
+
reject(request.error);
|
|
3099
|
+
};
|
|
3100
|
+
});
|
|
3101
|
+
}
|
|
3102
|
+
async clearStoredKey() {
|
|
3103
|
+
return new Promise((resolve, reject) => {
|
|
3104
|
+
if (!this.db) {
|
|
3105
|
+
throw new Error("Database not initialized");
|
|
3106
|
+
}
|
|
3107
|
+
const request = this.db.transaction([STORE_NAME], "readwrite").objectStore(STORE_NAME).delete(ACTIVE_KEY);
|
|
3108
|
+
request.onsuccess = () => {
|
|
3109
|
+
resolve();
|
|
3110
|
+
};
|
|
3111
|
+
request.onerror = () => {
|
|
3112
|
+
reject(request.error);
|
|
3113
|
+
};
|
|
3114
|
+
});
|
|
3115
|
+
}
|
|
3116
|
+
};
|
|
3117
|
+
|
|
3118
|
+
// src/providers/embedded/adapters/phantom-app.ts
|
|
3119
|
+
import { isPhantomExtensionInstalled as isPhantomExtensionInstalled3 } from "@phantom/browser-injected-sdk";
|
|
3120
|
+
|
|
3121
|
+
// src/waitForPhantomExtension.ts
|
|
3122
|
+
import { isPhantomExtensionInstalled as isPhantomExtensionInstalled2 } from "@phantom/browser-injected-sdk";
|
|
3123
|
+
async function waitForPhantomExtension(timeoutMs = 3e3) {
|
|
2815
3124
|
return new Promise((resolve) => {
|
|
2816
3125
|
const startTime = Date.now();
|
|
2817
3126
|
const checkInterval = 100;
|
|
@@ -2834,6 +3143,27 @@ async function waitForExtension(timeoutMs) {
|
|
|
2834
3143
|
});
|
|
2835
3144
|
}
|
|
2836
3145
|
|
|
3146
|
+
// src/isPhantomLoginAvailable.ts
|
|
3147
|
+
async function isPhantomLoginAvailable(timeoutMs = 3e3) {
|
|
3148
|
+
const extensionInstalled = await waitForPhantomExtension(timeoutMs);
|
|
3149
|
+
if (!extensionInstalled) {
|
|
3150
|
+
return false;
|
|
3151
|
+
}
|
|
3152
|
+
try {
|
|
3153
|
+
if (!window.phantom?.app?.features || typeof window.phantom.app.features !== "function") {
|
|
3154
|
+
return false;
|
|
3155
|
+
}
|
|
3156
|
+
const response = await window.phantom.app.features();
|
|
3157
|
+
if (!Array.isArray(response.features)) {
|
|
3158
|
+
return false;
|
|
3159
|
+
}
|
|
3160
|
+
return response.features.includes("phantom_login");
|
|
3161
|
+
} catch (error) {
|
|
3162
|
+
console.error("Error checking Phantom extension features", error);
|
|
3163
|
+
return false;
|
|
3164
|
+
}
|
|
3165
|
+
}
|
|
3166
|
+
|
|
2837
3167
|
// src/providers/embedded/adapters/phantom-app.ts
|
|
2838
3168
|
var BrowserPhantomAppProvider = class {
|
|
2839
3169
|
/**
|
|
@@ -2908,16 +3238,32 @@ var EmbeddedProvider = class extends CoreEmbeddedProvider {
|
|
|
2908
3238
|
constructor(config) {
|
|
2909
3239
|
debug.log(DebugCategory.EMBEDDED_PROVIDER, "Initializing Browser EmbeddedProvider", { config });
|
|
2910
3240
|
const urlParamsAccessor = new BrowserURLParamsAccessor();
|
|
2911
|
-
const
|
|
3241
|
+
const storage = new BrowserStorage();
|
|
3242
|
+
const stamper = config.unstable__auth2Options ? new Auth2Stamper(`phantom-auth2-${config.appId}`) : new IndexedDbStamper({
|
|
2912
3243
|
dbName: `phantom-embedded-sdk-${config.appId}`,
|
|
2913
3244
|
storeName: "crypto-keys",
|
|
2914
3245
|
keyName: "signing-key"
|
|
2915
3246
|
});
|
|
2916
3247
|
const platformName = getPlatformName();
|
|
2917
3248
|
const { name: browserName, version } = detectBrowser();
|
|
3249
|
+
const authProvider = config.unstable__auth2Options && config.authOptions?.authUrl && config.authOptions?.redirectUrl && stamper instanceof Auth2Stamper ? new Auth2AuthProvider(
|
|
3250
|
+
stamper,
|
|
3251
|
+
storage,
|
|
3252
|
+
urlParamsAccessor,
|
|
3253
|
+
{
|
|
3254
|
+
redirectUri: config.authOptions.redirectUrl,
|
|
3255
|
+
connectLoginUrl: config.authOptions.authUrl,
|
|
3256
|
+
clientId: config.unstable__auth2Options.clientId,
|
|
3257
|
+
authApiBaseUrl: config.unstable__auth2Options.authApiBaseUrl
|
|
3258
|
+
},
|
|
3259
|
+
{
|
|
3260
|
+
apiBaseUrl: config.apiBaseUrl,
|
|
3261
|
+
appId: config.appId
|
|
3262
|
+
}
|
|
3263
|
+
) : new BrowserAuthProvider(urlParamsAccessor);
|
|
2918
3264
|
const platform = {
|
|
2919
|
-
storage
|
|
2920
|
-
authProvider
|
|
3265
|
+
storage,
|
|
3266
|
+
authProvider,
|
|
2921
3267
|
phantomAppProvider: new BrowserPhantomAppProvider(),
|
|
2922
3268
|
urlParamsAccessor,
|
|
2923
3269
|
stamper,
|
|
@@ -2925,13 +3271,12 @@ var EmbeddedProvider = class extends CoreEmbeddedProvider {
|
|
|
2925
3271
|
// Use detected browser name and version for identification
|
|
2926
3272
|
analyticsHeaders: {
|
|
2927
3273
|
[ANALYTICS_HEADERS.SDK_TYPE]: "browser",
|
|
2928
|
-
[ANALYTICS_HEADERS.PLATFORM]:
|
|
2929
|
-
// firefox, chrome, safari, etc.
|
|
3274
|
+
[ANALYTICS_HEADERS.PLATFORM]: "ext-sdk",
|
|
2930
3275
|
[ANALYTICS_HEADERS.PLATFORM_VERSION]: version,
|
|
2931
|
-
|
|
3276
|
+
[ANALYTICS_HEADERS.CLIENT]: browserName,
|
|
2932
3277
|
[ANALYTICS_HEADERS.APP_ID]: config.appId,
|
|
2933
3278
|
[ANALYTICS_HEADERS.WALLET_TYPE]: config.embeddedWalletType,
|
|
2934
|
-
[ANALYTICS_HEADERS.SDK_VERSION]: "1.0.
|
|
3279
|
+
[ANALYTICS_HEADERS.SDK_VERSION]: "1.0.4"
|
|
2935
3280
|
// Replaced at build time
|
|
2936
3281
|
}
|
|
2937
3282
|
};
|
|
@@ -2970,11 +3315,12 @@ function isAuthCallbackUrl(searchParams) {
|
|
|
2970
3315
|
}
|
|
2971
3316
|
|
|
2972
3317
|
// src/utils/deeplink.ts
|
|
2973
|
-
function getDeeplinkToPhantom(ref) {
|
|
2974
|
-
|
|
3318
|
+
function getDeeplinkToPhantom(ref, currentHref) {
|
|
3319
|
+
const resolvedHref = currentHref ?? window.location.href;
|
|
3320
|
+
if (!resolvedHref.startsWith("http:") && !resolvedHref.startsWith("https:")) {
|
|
2975
3321
|
throw new Error("Invalid URL protocol - only HTTP/HTTPS URLs are supported for deeplinks");
|
|
2976
3322
|
}
|
|
2977
|
-
const currentUrl = encodeURIComponent(
|
|
3323
|
+
const currentUrl = encodeURIComponent(resolvedHref);
|
|
2978
3324
|
const refParam = ref ? `?ref=${encodeURIComponent(ref)}` : "";
|
|
2979
3325
|
return `https://phantom.app/ul/browse/${currentUrl}${refParam}`;
|
|
2980
3326
|
}
|
|
@@ -3070,8 +3416,14 @@ var ProviderManager = class {
|
|
|
3070
3416
|
} else if (requestedProvider === "deeplink") {
|
|
3071
3417
|
try {
|
|
3072
3418
|
const deeplinkUrl = getDeeplinkToPhantom();
|
|
3073
|
-
if (typeof window !== "undefined") {
|
|
3074
|
-
|
|
3419
|
+
if (typeof window !== "undefined" && window.location) {
|
|
3420
|
+
try {
|
|
3421
|
+
window.location.href = deeplinkUrl;
|
|
3422
|
+
} catch (error) {
|
|
3423
|
+
debug.warn(DebugCategory.PROVIDER_MANAGER, "Failed to set deeplink location", {
|
|
3424
|
+
error: error instanceof Error ? error.message : String(error)
|
|
3425
|
+
});
|
|
3426
|
+
}
|
|
3075
3427
|
}
|
|
3076
3428
|
return {
|
|
3077
3429
|
addresses: [],
|
|
@@ -3344,6 +3696,7 @@ var ProviderManager = class {
|
|
|
3344
3696
|
authUrl,
|
|
3345
3697
|
redirectUrl: this.config.authOptions?.redirectUrl || this.getValidatedCurrentUrl()
|
|
3346
3698
|
},
|
|
3699
|
+
unstable__auth2Options: this.config.unstable__auth2Options,
|
|
3347
3700
|
embeddedWalletType: embeddedWalletType || DEFAULT_EMBEDDED_WALLET_TYPE,
|
|
3348
3701
|
addressTypes: this.config.addressTypes || [AddressType.solana]
|
|
3349
3702
|
});
|
|
@@ -3542,6 +3895,7 @@ var BrowserSDK = class {
|
|
|
3542
3895
|
*/
|
|
3543
3896
|
async autoConnect() {
|
|
3544
3897
|
debug.log(DebugCategory.BROWSER_SDK, "Attempting auto-connect with fallback strategy");
|
|
3898
|
+
await this.discoverWallets();
|
|
3545
3899
|
const result = await this.providerManager.autoConnect();
|
|
3546
3900
|
if (result) {
|
|
3547
3901
|
debug.info(DebugCategory.BROWSER_SDK, "Auto-connect successful", {
|
|
@@ -3689,40 +4043,17 @@ var BrowserSDK = class {
|
|
|
3689
4043
|
}
|
|
3690
4044
|
};
|
|
3691
4045
|
|
|
3692
|
-
// src/waitForPhantomExtension.ts
|
|
3693
|
-
import { isPhantomExtensionInstalled as isPhantomExtensionInstalled4 } from "@phantom/browser-injected-sdk";
|
|
3694
|
-
async function waitForPhantomExtension(timeoutMs = 3e3) {
|
|
3695
|
-
return new Promise((resolve) => {
|
|
3696
|
-
const startTime = Date.now();
|
|
3697
|
-
const checkInterval = 100;
|
|
3698
|
-
const checkForExtension = () => {
|
|
3699
|
-
try {
|
|
3700
|
-
if (isPhantomExtensionInstalled4()) {
|
|
3701
|
-
resolve(true);
|
|
3702
|
-
return;
|
|
3703
|
-
}
|
|
3704
|
-
} catch (error) {
|
|
3705
|
-
}
|
|
3706
|
-
const elapsed = Date.now() - startTime;
|
|
3707
|
-
if (elapsed >= timeoutMs) {
|
|
3708
|
-
resolve(false);
|
|
3709
|
-
return;
|
|
3710
|
-
}
|
|
3711
|
-
setTimeout(checkForExtension, checkInterval);
|
|
3712
|
-
};
|
|
3713
|
-
checkForExtension();
|
|
3714
|
-
});
|
|
3715
|
-
}
|
|
3716
|
-
|
|
3717
4046
|
// src/index.ts
|
|
3718
4047
|
import { NetworkId } from "@phantom/constants";
|
|
3719
|
-
import { AddressType as
|
|
4048
|
+
import { AddressType as AddressType4 } from "@phantom/client";
|
|
4049
|
+
import { PHANTOM_ICON as PHANTOM_ICON3 } from "@phantom/constants";
|
|
3720
4050
|
export {
|
|
3721
|
-
|
|
4051
|
+
AddressType4 as AddressType,
|
|
3722
4052
|
BrowserSDK,
|
|
3723
4053
|
DebugCategory,
|
|
3724
4054
|
DebugLevel,
|
|
3725
4055
|
NetworkId,
|
|
4056
|
+
PHANTOM_ICON3 as PHANTOM_ICON,
|
|
3726
4057
|
debug,
|
|
3727
4058
|
detectBrowser,
|
|
3728
4059
|
getBrowserDisplayName,
|