@phantom/browser-sdk 1.0.6 → 2.0.0-beta.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 CHANGED
@@ -216,6 +216,7 @@ const sdk = new BrowserSDK({
216
216
  authOptions: {
217
217
  authUrl: "https://connect.phantom.app/login", // optional
218
218
  redirectUrl: "https://yourapp.com/callback", // optional, defaults to current page
219
+ authApiBaseUrl: "https://auth.phantom.app", // optional
219
220
  },
220
221
  autoConnect: true, // optional, auto-connect to existing session (default: true when embedded providers are used)
221
222
  });
@@ -231,8 +232,9 @@ const sdk = new BrowserSDK({
231
232
  addressTypes: [AddressType.solana, AddressType.ethereum],
232
233
  appId: "your-app-id", // Required for deeplink
233
234
  authOptions: {
234
- authUrl: "https://connect.phantom.app/login",
235
+ authUrl: "https://connect.phantom.app/login/start",
235
236
  redirectUrl: "https://yourapp.com/callback",
237
+ authApiBaseUrl: "https://auth.phantom.app",
236
238
  },
237
239
  });
238
240
  ```
@@ -325,9 +327,11 @@ interface BrowserSDKConfig {
325
327
 
326
328
  // Optional configuration
327
329
  apiBaseUrl?: string; // Phantom API base URL (optional, has default)
330
+
328
331
  authOptions?: {
329
- authUrl?: string; // Custom auth URL (optional, defaults to "https://connect.phantom.app/login")
332
+ authUrl?: string; // Custom auth URL (optional, defaults to "https://connect.phantom.app/login/start")
330
333
  redirectUrl?: string; // Custom redirect URL after authentication (optional)
334
+ authApiBaseUrl?: string; // Custom OAuth URL (optional, defaults to "https://auth.phantom.app")
331
335
  };
332
336
  embeddedWalletType?: "user-wallet"; // Wallet type (optional, defaults to "user-wallet")
333
337
  autoConnect?: boolean; // Auto-connect to existing session (default: true when embedded providers used)
@@ -985,6 +989,46 @@ function toggleDebug(enabled: boolean) {
985
989
  }
986
990
  ```
987
991
 
992
+ ## Dapp-Sponsored Transactions (presignTransaction)
993
+
994
+ Pass `presignTransaction` directly to `signAndSendTransaction` for calls that need co-signing. Calls without it proceed normally — it is never applied globally.
995
+
996
+ > **Important:** Phantom embedded wallets do not accept pre-signed transactions. If your use case requires a second signer (e.g. your app as the fee payer), that signing must happen via this option, after Phantom has constructed and validated the transaction. This restriction does not apply to injected providers (e.g. the Phantom browser extension).
997
+
998
+ > **Note:** `presignTransaction` only fires for Solana transactions via the embedded provider. EVM transactions and injected providers are unaffected.
999
+
1000
+ ### Transaction format
1001
+
1002
+ The `transaction` string is **base64url-encoded** (URL-safe base64 without `=` padding, using `-` and `_` instead of `+` and `/`).
1003
+
1004
+ The SDK exports `base64urlDecode` and `base64urlEncode` utilities:
1005
+
1006
+ ```ts
1007
+ import { base64urlDecode, base64urlEncode } from "@phantom/browser-sdk";
1008
+ ```
1009
+
1010
+ ### Example: Dapp fee payer
1011
+
1012
+ ```ts
1013
+ import { base64urlDecode, base64urlEncode } from "@phantom/browser-sdk";
1014
+ import { Keypair, VersionedTransaction } from "@solana/web3.js";
1015
+
1016
+ const feePayerKeypair = Keypair.fromSecretKey(/* your fee payer secret key */);
1017
+
1018
+ // This call co-signs as fee payer
1019
+ const result = await sdk.solana.signAndSendTransaction(transaction, {
1020
+ presignTransaction: async (tx, context) => {
1021
+ const txBytes = base64urlDecode(tx);
1022
+ const versionedTx = VersionedTransaction.deserialize(txBytes);
1023
+ versionedTx.sign([feePayerKeypair]);
1024
+ return base64urlEncode(versionedTx.serialize());
1025
+ },
1026
+ });
1027
+
1028
+ // This call has no co-signer
1029
+ const result2 = await sdk.solana.signAndSendTransaction(otherTransaction);
1030
+ ```
1031
+
988
1032
  ## Transaction Examples
989
1033
 
990
1034
  ### Solana Transactions
package/dist/index.d.ts CHANGED
@@ -1,12 +1,13 @@
1
1
  import { EmbeddedProviderConfig, EmbeddedProviderAuthType, ConnectResult as ConnectResult$1, WalletAddress, EmbeddedProviderEvent, EventCallback } from '@phantom/embedded-provider-core';
2
2
  export { ConnectErrorEventData, ConnectEventData, ConnectStartEventData, DisconnectEventData, EmbeddedProviderEvent, EmbeddedProviderEventMap, EventCallback, SignAndSendTransactionParams, SignMessageParams, SignMessageResult, SignedTransaction, WalletAddress } from '@phantom/embedded-provider-core';
3
3
  import { IEthereumChain, ISolanaChain } from '@phantom/chain-interfaces';
4
- export { EthTransactionRequest, IEthereumChain, ISolanaChain } from '@phantom/chain-interfaces';
4
+ export { EthTransactionRequest, IEthereumChain, ISolanaChain, SignAndSendTransactionOptions } from '@phantom/chain-interfaces';
5
5
  import { AddressType } from '@phantom/client';
6
- export { AddressType } from '@phantom/client';
6
+ export { AddressType, PresignTransactionContext } from '@phantom/client';
7
7
  import { AutoConfirmEnableParams, AutoConfirmResult, AutoConfirmSupportedChainsResult } from '@phantom/browser-injected-sdk/auto-confirm';
8
8
  export { AutoConfirmEnableParams, AutoConfirmResult, AutoConfirmSupportedChainsResult } from '@phantom/browser-injected-sdk/auto-confirm';
9
9
  export { NetworkId, PHANTOM_ICON } from '@phantom/constants';
10
+ export { base64urlDecode, base64urlEncode } from '@phantom/base64url';
10
11
 
11
12
  declare enum DebugLevel {
12
13
  ERROR = 0,
@@ -118,11 +119,7 @@ type BrowserSDKConfig = Prettify<Omit<EmbeddedProviderConfig, "authOptions" | "a
118
119
  authOptions?: {
119
120
  authUrl?: string;
120
121
  redirectUrl?: string;
121
- };
122
- /** When also provided, the Auth2 PKCE flow is used instead of the legacy Phantom Connect flow. */
123
- unstable__auth2Options?: {
124
- authApiBaseUrl: string;
125
- clientId: string;
122
+ authApiBaseUrl?: string;
126
123
  };
127
124
  }>;
128
125
  type Prettify<T> = {
package/dist/index.js CHANGED
@@ -34,8 +34,10 @@ __export(src_exports, {
34
34
  BrowserSDK: () => BrowserSDK,
35
35
  DebugCategory: () => DebugCategory,
36
36
  DebugLevel: () => DebugLevel,
37
- NetworkId: () => import_constants8.NetworkId,
38
- PHANTOM_ICON: () => import_constants9.PHANTOM_ICON,
37
+ NetworkId: () => import_constants7.NetworkId,
38
+ PHANTOM_ICON: () => import_constants8.PHANTOM_ICON,
39
+ base64urlDecode: () => import_base64url.base64urlDecode,
40
+ base64urlEncode: () => import_base64url.base64urlEncode,
39
41
  debug: () => debug,
40
42
  detectBrowser: () => detectBrowser,
41
43
  getBrowserDisplayName: () => getBrowserDisplayName,
@@ -2455,7 +2457,7 @@ var InjectedProvider = class {
2455
2457
 
2456
2458
  // src/providers/embedded/index.ts
2457
2459
  var import_embedded_provider_core = require("@phantom/embedded-provider-core");
2458
- var import_indexed_db_stamper = require("@phantom/indexed-db-stamper");
2460
+ var import_auth22 = require("@phantom/auth2");
2459
2461
 
2460
2462
  // src/providers/embedded/adapters/storage.ts
2461
2463
  var BrowserStorage = class {
@@ -2730,161 +2732,6 @@ function isMobileDevice() {
2730
2732
  return isMobileUA || isSmallScreen && isTouchDevice;
2731
2733
  }
2732
2734
 
2733
- // src/providers/embedded/adapters/auth.ts
2734
- var BrowserAuthProvider = class {
2735
- constructor(urlParamsAccessor) {
2736
- this.urlParamsAccessor = urlParamsAccessor;
2737
- }
2738
- getValidatedCurrentUrl() {
2739
- const currentUrl = window.location.href;
2740
- if (!currentUrl.startsWith("http:") && !currentUrl.startsWith("https:")) {
2741
- throw new Error("Invalid URL protocol - only HTTP/HTTPS URLs are supported");
2742
- }
2743
- return currentUrl;
2744
- }
2745
- authenticate(options) {
2746
- return new Promise((resolve) => {
2747
- if ("jwtToken" in options) {
2748
- throw new Error("JWT authentication should be handled by the core JWTAuth class");
2749
- }
2750
- const phantomOptions = options;
2751
- debug.info(DebugCategory.PHANTOM_CONNECT_AUTH, "Starting Phantom Connect authentication", {
2752
- publicKey: phantomOptions.publicKey,
2753
- appId: phantomOptions.appId,
2754
- provider: phantomOptions.provider,
2755
- authUrl: phantomOptions.authUrl
2756
- });
2757
- const baseUrl = phantomOptions.authUrl || import_constants3.DEFAULT_AUTH_URL;
2758
- debug.log(DebugCategory.PHANTOM_CONNECT_AUTH, "Using auth URL", { baseUrl });
2759
- const params = new URLSearchParams({
2760
- public_key: phantomOptions.publicKey,
2761
- app_id: phantomOptions.appId,
2762
- redirect_uri: phantomOptions.redirectUrl || (typeof window !== "undefined" ? this.getValidatedCurrentUrl() : ""),
2763
- session_id: phantomOptions.sessionId,
2764
- // OAuth session management - defaults to allow refresh unless explicitly clearing after logout
2765
- clear_previous_session: (phantomOptions.clearPreviousSession ?? false).toString(),
2766
- allow_refresh: (phantomOptions.allowRefresh ?? true).toString(),
2767
- sdk_version: "1.0.6",
2768
- sdk_type: "browser",
2769
- platform: detectBrowser().name,
2770
- algorithm: phantomOptions.algorithm || import_constants3.DEFAULT_AUTHENTICATOR_ALGORITHM
2771
- });
2772
- if (phantomOptions.provider) {
2773
- debug.log(DebugCategory.PHANTOM_CONNECT_AUTH, "Provider specified, will skip selection", {
2774
- provider: phantomOptions.provider
2775
- });
2776
- params.append("provider", phantomOptions.provider);
2777
- } else {
2778
- debug.log(DebugCategory.PHANTOM_CONNECT_AUTH, "No provider specified, defaulting to Google");
2779
- params.append("provider", "google");
2780
- }
2781
- const authContext = {
2782
- publicKey: phantomOptions.publicKey,
2783
- appId: phantomOptions.appId,
2784
- provider: phantomOptions.provider,
2785
- sessionId: phantomOptions.sessionId
2786
- };
2787
- sessionStorage.setItem("phantom-auth-context", JSON.stringify(authContext));
2788
- debug.log(DebugCategory.PHANTOM_CONNECT_AUTH, "Stored auth context in session storage", { authContext });
2789
- const authUrl = `${baseUrl}?${params.toString()}`;
2790
- debug.info(DebugCategory.PHANTOM_CONNECT_AUTH, "Redirecting to Phantom Connect", { authUrl });
2791
- if (!authUrl.startsWith("https:") && !authUrl.startsWith("http://localhost")) {
2792
- throw new Error("Invalid auth URL - only HTTPS URLs are allowed for authentication");
2793
- }
2794
- window.location.href = authUrl;
2795
- resolve();
2796
- });
2797
- }
2798
- async resumeAuthFromRedirect(provider) {
2799
- try {
2800
- const walletId = this.urlParamsAccessor.getParam("wallet_id");
2801
- const sessionId = this.urlParamsAccessor.getParam("session_id");
2802
- const accountDerivationIndex = this.urlParamsAccessor.getParam("selected_account_index");
2803
- const error = this.urlParamsAccessor.getParam("error");
2804
- const errorDescription = this.urlParamsAccessor.getParam("error_description");
2805
- if (error) {
2806
- const errorMsg = errorDescription || error;
2807
- switch (error) {
2808
- case "access_denied":
2809
- throw new Error(`Authentication cancelled: ${errorMsg}`);
2810
- case "invalid_request":
2811
- throw new Error(`Invalid authentication request: ${errorMsg}`);
2812
- case "server_error":
2813
- throw new Error(`Authentication server error: ${errorMsg}`);
2814
- case "temporarily_unavailable":
2815
- throw new Error(`Authentication service temporarily unavailable: ${errorMsg}`);
2816
- default:
2817
- throw new Error(`Authentication failed: ${errorMsg}`);
2818
- }
2819
- }
2820
- if (!walletId || !sessionId) {
2821
- debug.log(DebugCategory.PHANTOM_CONNECT_AUTH, "Missing auth parameters in URL", {
2822
- hasWalletId: !!walletId,
2823
- hasSessionId: !!sessionId
2824
- });
2825
- return null;
2826
- }
2827
- const contextStr = sessionStorage.getItem("phantom-auth-context");
2828
- let context = {};
2829
- if (contextStr) {
2830
- try {
2831
- context = JSON.parse(contextStr);
2832
- } catch (parseError) {
2833
- debug.warn(DebugCategory.PHANTOM_CONNECT_AUTH, "Failed to parse stored auth context", { error: parseError });
2834
- }
2835
- }
2836
- if (context.sessionId && sessionId !== context.sessionId) {
2837
- debug.error(DebugCategory.PHANTOM_CONNECT_AUTH, "Session ID mismatch", {
2838
- urlSessionId: sessionId,
2839
- storedSessionId: context.sessionId
2840
- });
2841
- throw new Error("Session ID mismatch - possible session corruption or replay attack");
2842
- }
2843
- sessionStorage.removeItem("phantom-auth-context");
2844
- debug.info(DebugCategory.PHANTOM_CONNECT_AUTH, "Successfully resumed auth from redirect", {
2845
- walletId,
2846
- sessionId,
2847
- accountDerivationIndex: accountDerivationIndex ? parseInt(accountDerivationIndex) : void 0
2848
- });
2849
- const organizationId = this.urlParamsAccessor.getParam("organization_id");
2850
- const expiresInMs = this.urlParamsAccessor.getParam("expires_in_ms");
2851
- const authUserId = this.urlParamsAccessor.getParam("auth_user_id");
2852
- debug.log(DebugCategory.PHANTOM_CONNECT_AUTH, "Auth redirect parameters", {
2853
- walletId,
2854
- organizationId,
2855
- sessionId,
2856
- accountDerivationIndex,
2857
- expiresInMs,
2858
- authUserId
2859
- });
2860
- if (!organizationId) {
2861
- debug.error(DebugCategory.PHANTOM_CONNECT_AUTH, "Missing organization_id in auth response");
2862
- throw new Error("Missing organization_id in auth response");
2863
- }
2864
- if (organizationId.startsWith("temp-")) {
2865
- debug.warn(
2866
- DebugCategory.PHANTOM_CONNECT_AUTH,
2867
- "Received temporary organization_id, server may not be configured properly",
2868
- {
2869
- organizationId
2870
- }
2871
- );
2872
- }
2873
- return Promise.resolve({
2874
- walletId,
2875
- organizationId,
2876
- accountDerivationIndex: accountDerivationIndex ? parseInt(accountDerivationIndex) : 0,
2877
- expiresInMs: expiresInMs ? parseInt(expiresInMs) : 0,
2878
- authUserId: authUserId || void 0,
2879
- provider
2880
- });
2881
- } catch (error) {
2882
- sessionStorage.removeItem("phantom-auth-context");
2883
- throw error;
2884
- }
2885
- }
2886
- };
2887
-
2888
2735
  // src/providers/embedded/adapters/Auth2AuthProvider.ts
2889
2736
  var import_auth2 = require("@phantom/auth2");
2890
2737
  var Auth2AuthProvider = class {
@@ -2908,30 +2755,17 @@ var Auth2AuthProvider = class {
2908
2755
  * redirect without ever touching sessionStorage.
2909
2756
  */
2910
2757
  async authenticate(options) {
2911
- if (!this.stamper.getKeyInfo()) {
2912
- await this.stamper.init();
2913
- }
2914
- const keyPair = this.stamper.getCryptoKeyPair();
2915
- if (!keyPair) {
2916
- throw new Error("Stamper key pair not found.");
2917
- }
2918
- const codeVerifier = (0, import_auth2.createCodeVerifier)();
2919
2758
  const session = await this.storage.getSession();
2920
2759
  if (!session) {
2921
2760
  throw new Error("Session not found.");
2922
2761
  }
2923
- await this.storage.saveSession({ ...session, pkceCodeVerifier: codeVerifier });
2924
- const url = await (0, import_auth2.createConnectStartUrl)({
2925
- keyPair,
2926
- connectLoginUrl: this.auth2ProviderOptions.connectLoginUrl,
2927
- clientId: this.auth2ProviderOptions.clientId,
2928
- redirectUri: this.auth2ProviderOptions.redirectUri,
2762
+ const { url, codeVerifier } = await (0, import_auth2.prepareAuth2Flow)({
2763
+ stamper: this.stamper,
2764
+ auth2Options: this.auth2ProviderOptions,
2929
2765
  sessionId: options.sessionId,
2930
- provider: options.provider,
2931
- codeVerifier,
2932
- // The P-256 ephemeral key is unique per wallet, so no additional salt is needed.
2933
- salt: ""
2766
+ provider: options.provider
2934
2767
  });
2768
+ await this.storage.saveSession({ ...session, pkceCodeVerifier: codeVerifier });
2935
2769
  Auth2AuthProvider.navigate(url);
2936
2770
  }
2937
2771
  /**
@@ -2941,8 +2775,7 @@ var Auth2AuthProvider = class {
2941
2775
  * and wallet via KMS RPC, then returns a completed AuthResult.
2942
2776
  */
2943
2777
  async resumeAuthFromRedirect(provider) {
2944
- const code = this.urlParamsAccessor.getParam("code");
2945
- if (!code) {
2778
+ if (!this.urlParamsAccessor.getParam("code")) {
2946
2779
  return null;
2947
2780
  }
2948
2781
  if (!this.stamper.getKeyInfo()) {
@@ -2956,226 +2789,39 @@ var Auth2AuthProvider = class {
2956
2789
  if (!codeVerifier) {
2957
2790
  return null;
2958
2791
  }
2959
- const state = this.urlParamsAccessor.getParam("state");
2960
- if (!state || state !== session.sessionId) {
2961
- throw new Error("Missing or invalid Auth2 state parameter \u2014 possible CSRF attack.");
2962
- }
2963
- const error = this.urlParamsAccessor.getParam("error");
2964
- if (error) {
2965
- const description = this.urlParamsAccessor.getParam("error_description");
2966
- throw new Error(`Auth2 callback error: ${description ?? error}`);
2967
- }
2968
- const { idToken, bearerToken, authUserId, expiresInMs, refreshToken } = await (0, import_auth2.exchangeAuthCode)({
2969
- authApiBaseUrl: this.auth2ProviderOptions.authApiBaseUrl,
2970
- clientId: this.auth2ProviderOptions.clientId,
2971
- redirectUri: this.auth2ProviderOptions.redirectUri,
2792
+ const code = (0, import_auth2.validateAuth2Callback)({
2793
+ getParam: (key) => this.urlParamsAccessor.getParam(key),
2794
+ expectedSessionId: session.sessionId
2795
+ });
2796
+ const result = await (0, import_auth2.completeAuth2Exchange)({
2797
+ stamper: this.stamper,
2798
+ kms: this.kms,
2799
+ auth2Options: this.auth2ProviderOptions,
2972
2800
  code,
2973
- codeVerifier
2801
+ codeVerifier,
2802
+ provider
2974
2803
  });
2975
- await this.stamper.setTokens({ idToken, bearerToken, refreshToken, expiresInMs });
2976
2804
  await this.storage.saveSession({
2977
2805
  ...session,
2978
2806
  status: "completed",
2979
- bearerToken,
2980
- authUserId,
2807
+ bearerToken: result.bearerToken,
2808
+ authUserId: result.authUserId,
2981
2809
  pkceCodeVerifier: void 0
2982
- // no longer needed after code exchange
2983
2810
  });
2984
- const { organizationId, walletId } = await this.kms.discoverOrganizationAndWalletId(bearerToken, authUserId);
2985
- return {
2986
- walletId,
2987
- organizationId,
2988
- provider,
2989
- accountDerivationIndex: 0,
2990
- // discoverWalletId uses derivation index of 0.
2991
- expiresInMs,
2992
- authUserId,
2993
- bearerToken
2994
- };
2811
+ return result;
2995
2812
  }
2996
2813
  };
2997
2814
 
2998
- // src/providers/embedded/adapters/Auth2Stamper.ts
2999
- var import_bs582 = __toESM(require("bs58"));
3000
- var import_base64url = require("@phantom/base64url");
3001
- var import_sdk_types = require("@phantom/sdk-types");
3002
- var import_auth22 = require("@phantom/auth2");
3003
- var import_constants4 = require("@phantom/constants");
2815
+ // src/providers/embedded/adapters/IndexedDBAuth2StamperStorage.ts
3004
2816
  var STORE_NAME = "crypto-keys";
3005
2817
  var ACTIVE_KEY = "auth2-p256-signing-key";
3006
- var Auth2Stamper = class {
3007
- /**
3008
- * @param dbName - IndexedDB database name (use a unique name per app to
3009
- * avoid key collisions with other stampers, e.g. `phantom-auth2-<appId>`).
3010
- * @param refreshConfig - When provided, the stamper will automatically refresh
3011
- * the id_token using the refresh_token before it expires.
3012
- */
3013
- constructor(dbName, refreshConfig) {
2818
+ var IndexedDBAuth2StamperStorage = class {
2819
+ constructor(dbName) {
3014
2820
  this.dbName = dbName;
3015
- this.refreshConfig = refreshConfig;
3016
2821
  this.db = null;
3017
- this._keyPair = null;
3018
- this._keyInfo = null;
3019
- this._idToken = null;
3020
- this._bearerToken = null;
3021
- this._refreshToken = null;
3022
- this._tokenExpiresAt = null;
3023
- this.algorithm = import_sdk_types.Algorithm.secp256r1;
3024
- this.type = "OIDC";
3025
- }
3026
- async init() {
3027
- await this.openDB();
3028
- const stored = await this.loadRecord();
3029
- if (stored) {
3030
- this._keyPair = stored.keyPair;
3031
- this._keyInfo = stored.keyInfo;
3032
- if (stored.idToken) {
3033
- this._idToken = stored.idToken;
3034
- }
3035
- if (stored.bearerToken) {
3036
- this._bearerToken = stored.bearerToken;
3037
- }
3038
- if (stored.refreshToken) {
3039
- this._refreshToken = stored.refreshToken;
3040
- }
3041
- if (stored.tokenExpiresAt) {
3042
- this._tokenExpiresAt = stored.tokenExpiresAt;
3043
- }
3044
- return this._keyInfo;
3045
- }
3046
- return this.generateAndStore();
3047
- }
3048
- getKeyInfo() {
3049
- return this._keyInfo;
3050
- }
3051
- getCryptoKeyPair() {
3052
- return this._keyPair;
3053
- }
3054
- /**
3055
- * Returns the current token state (refreshing proactively if near expiry),
3056
- * or null if no token has been set yet.
3057
- */
3058
- async getTokens() {
3059
- if (this.refreshConfig && this._refreshToken && this._tokenExpiresAt !== null && Date.now() >= this._tokenExpiresAt - import_constants4.TOKEN_REFRESH_BUFFER_MS) {
3060
- const refreshed = await (0, import_auth22.refreshToken)({
3061
- authApiBaseUrl: this.refreshConfig.authApiBaseUrl,
3062
- clientId: this.refreshConfig.clientId,
3063
- redirectUri: this.refreshConfig.redirectUri,
3064
- refreshToken: this._refreshToken
3065
- });
3066
- await this.setTokens(refreshed);
3067
- }
3068
- if (!this._idToken || !this._bearerToken) {
3069
- return null;
3070
- }
3071
- return {
3072
- idToken: this._idToken,
3073
- bearerToken: this._bearerToken,
3074
- refreshToken: this._refreshToken ?? void 0
3075
- };
3076
- }
3077
- /**
3078
- * Arms the stamper with the OIDC token data for subsequent KMS stamp() calls.
3079
- *
3080
- * Persists the tokens to IndexedDB alongside the key pair so that
3081
- * auto-connect can restore them on the next page load without a new login.
3082
- *
3083
- * @param refreshToken - When provided alongside a `refreshConfig`, enables
3084
- * silent token refresh before the token expires.
3085
- * @param expiresInMs - Token lifetime in milliseconds (from `expires_in * 1000`).
3086
- * Used to compute the absolute expiry time for proactive refresh.
3087
- */
3088
- async setTokens({
3089
- idToken,
3090
- bearerToken,
3091
- refreshToken,
3092
- expiresInMs
3093
- }) {
3094
- if (!this.db) {
3095
- await this.openDB();
3096
- }
3097
- this._idToken = idToken;
3098
- this._bearerToken = bearerToken;
3099
- this._refreshToken = refreshToken ?? null;
3100
- this._tokenExpiresAt = expiresInMs != null ? Date.now() + expiresInMs : null;
3101
- const existing = await this.loadRecord();
3102
- if (existing) {
3103
- await this.storeRecord({
3104
- ...existing,
3105
- idToken,
3106
- bearerToken,
3107
- refreshToken,
3108
- tokenExpiresAt: this._tokenExpiresAt ?? void 0
3109
- });
3110
- }
2822
+ this.requiresExtractableKeys = false;
3111
2823
  }
3112
- async stamp(params) {
3113
- if (!this._keyPair || !this._keyInfo || this._idToken === null) {
3114
- throw new Error("Auth2Stamper not initialized. Call init() first.");
3115
- }
3116
- const signatureRaw = await crypto.subtle.sign(
3117
- { name: "ECDSA", hash: "SHA-256" },
3118
- this._keyPair.privateKey,
3119
- new Uint8Array(params.data)
3120
- );
3121
- const rawPublicKey = import_bs582.default.decode(this._keyInfo.publicKey);
3122
- const stampData = {
3123
- kind: this.type,
3124
- idToken: this._idToken,
3125
- publicKey: (0, import_base64url.base64urlEncode)(rawPublicKey),
3126
- algorithm: this.algorithm,
3127
- // The P-256 ephemeral key is unique per wallet, so no additional salt is needed.
3128
- salt: "",
3129
- signature: (0, import_base64url.base64urlEncode)(new Uint8Array(signatureRaw))
3130
- };
3131
- return (0, import_base64url.base64urlEncode)(new TextEncoder().encode(JSON.stringify(stampData)));
3132
- }
3133
- async resetKeyPair() {
3134
- await this.clear();
3135
- return this.generateAndStore();
3136
- }
3137
- async clear() {
3138
- await this.clearStoredRecord();
3139
- this._keyPair = null;
3140
- this._keyInfo = null;
3141
- this._idToken = null;
3142
- this._bearerToken = null;
3143
- this._refreshToken = null;
3144
- this._tokenExpiresAt = null;
3145
- }
3146
- // Auth2 doesn't use key rotation; provide minimal no-op implementations.
3147
- async rotateKeyPair() {
3148
- return this.init();
3149
- }
3150
- // eslint-disable-next-line @typescript-eslint/require-await
3151
- async commitRotation(authenticatorId) {
3152
- if (this._keyInfo) {
3153
- this._keyInfo.authenticatorId = authenticatorId;
3154
- }
3155
- }
3156
- async rollbackRotation() {
3157
- }
3158
- async generateAndStore() {
3159
- const keyPair = await crypto.subtle.generateKey(
3160
- { name: "ECDSA", namedCurve: "P-256" },
3161
- false,
3162
- // non-extractable — private key never leaves Web Crypto
3163
- ["sign", "verify"]
3164
- );
3165
- const rawPublicKey = new Uint8Array(await crypto.subtle.exportKey("raw", keyPair.publicKey));
3166
- const publicKeyBase58 = import_bs582.default.encode(rawPublicKey);
3167
- const keyIdBuffer = await crypto.subtle.digest("SHA-256", rawPublicKey.buffer);
3168
- const keyId = (0, import_base64url.base64urlEncode)(new Uint8Array(keyIdBuffer)).substring(0, 16);
3169
- this._keyPair = keyPair;
3170
- this._keyInfo = {
3171
- keyId,
3172
- publicKey: publicKeyBase58,
3173
- createdAt: Date.now()
3174
- };
3175
- await this.storeRecord({ keyPair, keyInfo: this._keyInfo });
3176
- return this._keyInfo;
3177
- }
3178
- async openDB() {
2824
+ async open() {
3179
2825
  return new Promise((resolve, reject) => {
3180
2826
  const request = indexedDB.open(this.dbName, 1);
3181
2827
  request.onsuccess = () => {
@@ -3191,7 +2837,7 @@ var Auth2Stamper = class {
3191
2837
  };
3192
2838
  });
3193
2839
  }
3194
- async loadRecord() {
2840
+ async load() {
3195
2841
  return new Promise((resolve, reject) => {
3196
2842
  if (!this.db) {
3197
2843
  throw new Error("Database not initialized");
@@ -3205,7 +2851,7 @@ var Auth2Stamper = class {
3205
2851
  };
3206
2852
  });
3207
2853
  }
3208
- async storeRecord(record) {
2854
+ async save(record) {
3209
2855
  return new Promise((resolve, reject) => {
3210
2856
  if (!this.db) {
3211
2857
  throw new Error("Database not initialized");
@@ -3219,7 +2865,7 @@ var Auth2Stamper = class {
3219
2865
  };
3220
2866
  });
3221
2867
  }
3222
- async clearStoredRecord() {
2868
+ async clear() {
3223
2869
  return new Promise((resolve, reject) => {
3224
2870
  if (!this.db) {
3225
2871
  throw new Error("Database not initialized");
@@ -3353,38 +2999,34 @@ var BrowserLogger = class {
3353
2999
  };
3354
3000
 
3355
3001
  // src/providers/embedded/index.ts
3356
- var import_constants5 = require("@phantom/constants");
3002
+ var import_constants4 = require("@phantom/constants");
3357
3003
  var EmbeddedProvider = class extends import_embedded_provider_core.EmbeddedProvider {
3358
3004
  constructor(config) {
3359
3005
  debug.log(DebugCategory.EMBEDDED_PROVIDER, "Initializing Browser EmbeddedProvider", { config });
3360
3006
  const urlParamsAccessor = new BrowserURLParamsAccessor();
3361
3007
  const storage = new BrowserStorage();
3362
- const stamper = config.unstable__auth2Options ? new Auth2Stamper(`phantom-auth2-${config.appId}`, {
3363
- authApiBaseUrl: config.unstable__auth2Options.authApiBaseUrl,
3364
- clientId: config.unstable__auth2Options.clientId,
3365
- redirectUri: config.authOptions?.redirectUrl ?? ""
3366
- }) : new import_indexed_db_stamper.IndexedDbStamper({
3367
- dbName: `phantom-embedded-sdk-${config.appId}`,
3368
- storeName: "crypto-keys",
3369
- keyName: "signing-key"
3008
+ const stamper = new import_auth22.Auth2Stamper(new IndexedDBAuth2StamperStorage(`phantom-auth2-${config.appId}`), {
3009
+ authApiBaseUrl: config.authOptions.authApiBaseUrl,
3010
+ clientId: config.appId,
3011
+ redirectUri: config.authOptions.redirectUrl
3370
3012
  });
3371
3013
  const platformName = getPlatformName();
3372
3014
  const { name: browserName, version } = detectBrowser();
3373
- const authProvider = config.unstable__auth2Options && config.authOptions?.authUrl && config.authOptions?.redirectUrl && stamper instanceof Auth2Stamper ? new Auth2AuthProvider(
3015
+ const authProvider = new Auth2AuthProvider(
3374
3016
  stamper,
3375
3017
  storage,
3376
3018
  urlParamsAccessor,
3377
3019
  {
3378
3020
  redirectUri: config.authOptions.redirectUrl,
3379
3021
  connectLoginUrl: config.authOptions.authUrl,
3380
- clientId: config.unstable__auth2Options.clientId,
3381
- authApiBaseUrl: config.unstable__auth2Options.authApiBaseUrl
3022
+ clientId: config.appId,
3023
+ authApiBaseUrl: config.authOptions.authApiBaseUrl
3382
3024
  },
3383
3025
  {
3384
3026
  apiBaseUrl: config.apiBaseUrl,
3385
3027
  appId: config.appId
3386
3028
  }
3387
- ) : new BrowserAuthProvider(urlParamsAccessor);
3029
+ );
3388
3030
  const platform = {
3389
3031
  storage,
3390
3032
  authProvider,
@@ -3394,13 +3036,13 @@ var EmbeddedProvider = class extends import_embedded_provider_core.EmbeddedProvi
3394
3036
  name: platformName,
3395
3037
  // Use detected browser name and version for identification
3396
3038
  analyticsHeaders: {
3397
- [import_constants5.ANALYTICS_HEADERS.SDK_TYPE]: "browser",
3398
- [import_constants5.ANALYTICS_HEADERS.PLATFORM]: "ext-sdk",
3399
- [import_constants5.ANALYTICS_HEADERS.PLATFORM_VERSION]: version,
3400
- [import_constants5.ANALYTICS_HEADERS.CLIENT]: browserName,
3401
- [import_constants5.ANALYTICS_HEADERS.APP_ID]: config.appId,
3402
- [import_constants5.ANALYTICS_HEADERS.WALLET_TYPE]: config.embeddedWalletType,
3403
- [import_constants5.ANALYTICS_HEADERS.SDK_VERSION]: "1.0.6"
3039
+ [import_constants4.ANALYTICS_HEADERS.SDK_TYPE]: "browser",
3040
+ [import_constants4.ANALYTICS_HEADERS.PLATFORM]: "ext-sdk",
3041
+ [import_constants4.ANALYTICS_HEADERS.PLATFORM_VERSION]: version,
3042
+ [import_constants4.ANALYTICS_HEADERS.CLIENT]: browserName,
3043
+ [import_constants4.ANALYTICS_HEADERS.APP_ID]: config.appId,
3044
+ [import_constants4.ANALYTICS_HEADERS.WALLET_TYPE]: config.embeddedWalletType,
3045
+ [import_constants4.ANALYTICS_HEADERS.SDK_VERSION]: "2.0.0-beta.0"
3404
3046
  // Replaced at build time
3405
3047
  }
3406
3048
  };
@@ -3417,7 +3059,7 @@ var EmbeddedProvider = class extends import_embedded_provider_core.EmbeddedProvi
3417
3059
 
3418
3060
  // src/ProviderManager.ts
3419
3061
  var import_embedded_provider_core2 = require("@phantom/embedded-provider-core");
3420
- var import_constants6 = require("@phantom/constants");
3062
+ var import_constants5 = require("@phantom/constants");
3421
3063
 
3422
3064
  // src/utils/auth-callback.ts
3423
3065
  function isAuthFailureCallback(searchParams) {
@@ -3808,18 +3450,19 @@ var ProviderManager = class {
3808
3450
  if (!this.config.appId) {
3809
3451
  throw new Error("appId is required for embedded provider");
3810
3452
  }
3811
- const apiBaseUrl = this.config.apiBaseUrl || import_constants6.DEFAULT_WALLET_API_URL;
3812
- const authUrl = this.config.authOptions?.authUrl || import_constants6.DEFAULT_AUTH_URL;
3453
+ const apiBaseUrl = this.config.apiBaseUrl || import_constants5.DEFAULT_WALLET_API_URL;
3454
+ const authUrl = this.config.authOptions?.authUrl || import_constants5.DEFAULT_AUTH_URL;
3455
+ const authApiBaseUrl = this.config.authOptions?.authApiBaseUrl || import_constants5.DEFAULT_AUTH_API_BASE_URL;
3813
3456
  provider = new EmbeddedProvider({
3814
3457
  apiBaseUrl,
3815
3458
  appId: this.config.appId,
3816
3459
  authOptions: {
3817
3460
  ...this.config.authOptions || {},
3818
3461
  authUrl,
3819
- redirectUrl: this.config.authOptions?.redirectUrl || this.getValidatedCurrentUrl()
3462
+ redirectUrl: this.config.authOptions?.redirectUrl || this.getValidatedCurrentUrl(),
3463
+ authApiBaseUrl
3820
3464
  },
3821
- unstable__auth2Options: this.config.unstable__auth2Options,
3822
- embeddedWalletType: embeddedWalletType || import_constants6.DEFAULT_EMBEDDED_WALLET_TYPE,
3465
+ embeddedWalletType: embeddedWalletType || import_constants5.DEFAULT_EMBEDDED_WALLET_TYPE,
3823
3466
  addressTypes: this.config.addressTypes || [import_client.AddressType.solana]
3824
3467
  });
3825
3468
  } else {
@@ -3855,7 +3498,7 @@ var ProviderManager = class {
3855
3498
 
3856
3499
  // src/BrowserSDK.ts
3857
3500
  var import_embedded_provider_core3 = require("@phantom/embedded-provider-core");
3858
- var import_constants7 = require("@phantom/constants");
3501
+ var import_constants6 = require("@phantom/constants");
3859
3502
  var BROWSER_SDK_PROVIDER_TYPES = [
3860
3503
  ...import_embedded_provider_core3.EMBEDDED_PROVIDER_AUTH_TYPES,
3861
3504
  "injected",
@@ -3891,7 +3534,7 @@ var BrowserSDK = class {
3891
3534
  });
3892
3535
  throw new Error("appId is required when using embedded providers (google, apple, phantom, etc.)");
3893
3536
  }
3894
- const embeddedWalletType = config.embeddedWalletType || import_constants7.DEFAULT_EMBEDDED_WALLET_TYPE;
3537
+ const embeddedWalletType = config.embeddedWalletType || import_constants6.DEFAULT_EMBEDDED_WALLET_TYPE;
3895
3538
  if (!["app-wallet", "user-wallet"].includes(embeddedWalletType)) {
3896
3539
  debug.error(DebugCategory.BROWSER_SDK, "Invalid embeddedWalletType", {
3897
3540
  embeddedWalletType: config.embeddedWalletType
@@ -4166,6 +3809,7 @@ var BrowserSDK = class {
4166
3809
  };
4167
3810
 
4168
3811
  // src/index.ts
4169
- var import_constants8 = require("@phantom/constants");
3812
+ var import_constants7 = require("@phantom/constants");
4170
3813
  var import_client5 = require("@phantom/client");
4171
- var import_constants9 = require("@phantom/constants");
3814
+ var import_base64url = require("@phantom/base64url");
3815
+ var import_constants8 = require("@phantom/constants");
package/dist/index.mjs CHANGED
@@ -2405,7 +2405,7 @@ var InjectedProvider = class {
2405
2405
 
2406
2406
  // src/providers/embedded/index.ts
2407
2407
  import { EmbeddedProvider as CoreEmbeddedProvider } from "@phantom/embedded-provider-core";
2408
- import { IndexedDbStamper } from "@phantom/indexed-db-stamper";
2408
+ import { Auth2Stamper } from "@phantom/auth2";
2409
2409
 
2410
2410
  // src/providers/embedded/adapters/storage.ts
2411
2411
  var BrowserStorage = class {
@@ -2680,167 +2680,12 @@ function isMobileDevice() {
2680
2680
  return isMobileUA || isSmallScreen && isTouchDevice;
2681
2681
  }
2682
2682
 
2683
- // src/providers/embedded/adapters/auth.ts
2684
- var BrowserAuthProvider = class {
2685
- constructor(urlParamsAccessor) {
2686
- this.urlParamsAccessor = urlParamsAccessor;
2687
- }
2688
- getValidatedCurrentUrl() {
2689
- const currentUrl = window.location.href;
2690
- if (!currentUrl.startsWith("http:") && !currentUrl.startsWith("https:")) {
2691
- throw new Error("Invalid URL protocol - only HTTP/HTTPS URLs are supported");
2692
- }
2693
- return currentUrl;
2694
- }
2695
- authenticate(options) {
2696
- return new Promise((resolve) => {
2697
- if ("jwtToken" in options) {
2698
- throw new Error("JWT authentication should be handled by the core JWTAuth class");
2699
- }
2700
- const phantomOptions = options;
2701
- debug.info(DebugCategory.PHANTOM_CONNECT_AUTH, "Starting Phantom Connect authentication", {
2702
- publicKey: phantomOptions.publicKey,
2703
- appId: phantomOptions.appId,
2704
- provider: phantomOptions.provider,
2705
- authUrl: phantomOptions.authUrl
2706
- });
2707
- const baseUrl = phantomOptions.authUrl || DEFAULT_AUTH_URL;
2708
- debug.log(DebugCategory.PHANTOM_CONNECT_AUTH, "Using auth URL", { baseUrl });
2709
- const params = new URLSearchParams({
2710
- public_key: phantomOptions.publicKey,
2711
- app_id: phantomOptions.appId,
2712
- redirect_uri: phantomOptions.redirectUrl || (typeof window !== "undefined" ? this.getValidatedCurrentUrl() : ""),
2713
- session_id: phantomOptions.sessionId,
2714
- // OAuth session management - defaults to allow refresh unless explicitly clearing after logout
2715
- clear_previous_session: (phantomOptions.clearPreviousSession ?? false).toString(),
2716
- allow_refresh: (phantomOptions.allowRefresh ?? true).toString(),
2717
- sdk_version: "1.0.6",
2718
- sdk_type: "browser",
2719
- platform: detectBrowser().name,
2720
- algorithm: phantomOptions.algorithm || DEFAULT_AUTHENTICATOR_ALGORITHM
2721
- });
2722
- if (phantomOptions.provider) {
2723
- debug.log(DebugCategory.PHANTOM_CONNECT_AUTH, "Provider specified, will skip selection", {
2724
- provider: phantomOptions.provider
2725
- });
2726
- params.append("provider", phantomOptions.provider);
2727
- } else {
2728
- debug.log(DebugCategory.PHANTOM_CONNECT_AUTH, "No provider specified, defaulting to Google");
2729
- params.append("provider", "google");
2730
- }
2731
- const authContext = {
2732
- publicKey: phantomOptions.publicKey,
2733
- appId: phantomOptions.appId,
2734
- provider: phantomOptions.provider,
2735
- sessionId: phantomOptions.sessionId
2736
- };
2737
- sessionStorage.setItem("phantom-auth-context", JSON.stringify(authContext));
2738
- debug.log(DebugCategory.PHANTOM_CONNECT_AUTH, "Stored auth context in session storage", { authContext });
2739
- const authUrl = `${baseUrl}?${params.toString()}`;
2740
- debug.info(DebugCategory.PHANTOM_CONNECT_AUTH, "Redirecting to Phantom Connect", { authUrl });
2741
- if (!authUrl.startsWith("https:") && !authUrl.startsWith("http://localhost")) {
2742
- throw new Error("Invalid auth URL - only HTTPS URLs are allowed for authentication");
2743
- }
2744
- window.location.href = authUrl;
2745
- resolve();
2746
- });
2747
- }
2748
- async resumeAuthFromRedirect(provider) {
2749
- try {
2750
- const walletId = this.urlParamsAccessor.getParam("wallet_id");
2751
- const sessionId = this.urlParamsAccessor.getParam("session_id");
2752
- const accountDerivationIndex = this.urlParamsAccessor.getParam("selected_account_index");
2753
- const error = this.urlParamsAccessor.getParam("error");
2754
- const errorDescription = this.urlParamsAccessor.getParam("error_description");
2755
- if (error) {
2756
- const errorMsg = errorDescription || error;
2757
- switch (error) {
2758
- case "access_denied":
2759
- throw new Error(`Authentication cancelled: ${errorMsg}`);
2760
- case "invalid_request":
2761
- throw new Error(`Invalid authentication request: ${errorMsg}`);
2762
- case "server_error":
2763
- throw new Error(`Authentication server error: ${errorMsg}`);
2764
- case "temporarily_unavailable":
2765
- throw new Error(`Authentication service temporarily unavailable: ${errorMsg}`);
2766
- default:
2767
- throw new Error(`Authentication failed: ${errorMsg}`);
2768
- }
2769
- }
2770
- if (!walletId || !sessionId) {
2771
- debug.log(DebugCategory.PHANTOM_CONNECT_AUTH, "Missing auth parameters in URL", {
2772
- hasWalletId: !!walletId,
2773
- hasSessionId: !!sessionId
2774
- });
2775
- return null;
2776
- }
2777
- const contextStr = sessionStorage.getItem("phantom-auth-context");
2778
- let context = {};
2779
- if (contextStr) {
2780
- try {
2781
- context = JSON.parse(contextStr);
2782
- } catch (parseError) {
2783
- debug.warn(DebugCategory.PHANTOM_CONNECT_AUTH, "Failed to parse stored auth context", { error: parseError });
2784
- }
2785
- }
2786
- if (context.sessionId && sessionId !== context.sessionId) {
2787
- debug.error(DebugCategory.PHANTOM_CONNECT_AUTH, "Session ID mismatch", {
2788
- urlSessionId: sessionId,
2789
- storedSessionId: context.sessionId
2790
- });
2791
- throw new Error("Session ID mismatch - possible session corruption or replay attack");
2792
- }
2793
- sessionStorage.removeItem("phantom-auth-context");
2794
- debug.info(DebugCategory.PHANTOM_CONNECT_AUTH, "Successfully resumed auth from redirect", {
2795
- walletId,
2796
- sessionId,
2797
- accountDerivationIndex: accountDerivationIndex ? parseInt(accountDerivationIndex) : void 0
2798
- });
2799
- const organizationId = this.urlParamsAccessor.getParam("organization_id");
2800
- const expiresInMs = this.urlParamsAccessor.getParam("expires_in_ms");
2801
- const authUserId = this.urlParamsAccessor.getParam("auth_user_id");
2802
- debug.log(DebugCategory.PHANTOM_CONNECT_AUTH, "Auth redirect parameters", {
2803
- walletId,
2804
- organizationId,
2805
- sessionId,
2806
- accountDerivationIndex,
2807
- expiresInMs,
2808
- authUserId
2809
- });
2810
- if (!organizationId) {
2811
- debug.error(DebugCategory.PHANTOM_CONNECT_AUTH, "Missing organization_id in auth response");
2812
- throw new Error("Missing organization_id in auth response");
2813
- }
2814
- if (organizationId.startsWith("temp-")) {
2815
- debug.warn(
2816
- DebugCategory.PHANTOM_CONNECT_AUTH,
2817
- "Received temporary organization_id, server may not be configured properly",
2818
- {
2819
- organizationId
2820
- }
2821
- );
2822
- }
2823
- return Promise.resolve({
2824
- walletId,
2825
- organizationId,
2826
- accountDerivationIndex: accountDerivationIndex ? parseInt(accountDerivationIndex) : 0,
2827
- expiresInMs: expiresInMs ? parseInt(expiresInMs) : 0,
2828
- authUserId: authUserId || void 0,
2829
- provider
2830
- });
2831
- } catch (error) {
2832
- sessionStorage.removeItem("phantom-auth-context");
2833
- throw error;
2834
- }
2835
- }
2836
- };
2837
-
2838
2683
  // src/providers/embedded/adapters/Auth2AuthProvider.ts
2839
2684
  import {
2840
- createCodeVerifier,
2841
- createConnectStartUrl,
2842
- exchangeAuthCode,
2843
- Auth2KmsRpcClient
2685
+ Auth2KmsRpcClient,
2686
+ prepareAuth2Flow,
2687
+ validateAuth2Callback,
2688
+ completeAuth2Exchange
2844
2689
  } from "@phantom/auth2";
2845
2690
  var Auth2AuthProvider = class {
2846
2691
  constructor(stamper, storage, urlParamsAccessor, auth2ProviderOptions, kmsClientOptions) {
@@ -2863,30 +2708,17 @@ var Auth2AuthProvider = class {
2863
2708
  * redirect without ever touching sessionStorage.
2864
2709
  */
2865
2710
  async authenticate(options) {
2866
- if (!this.stamper.getKeyInfo()) {
2867
- await this.stamper.init();
2868
- }
2869
- const keyPair = this.stamper.getCryptoKeyPair();
2870
- if (!keyPair) {
2871
- throw new Error("Stamper key pair not found.");
2872
- }
2873
- const codeVerifier = createCodeVerifier();
2874
2711
  const session = await this.storage.getSession();
2875
2712
  if (!session) {
2876
2713
  throw new Error("Session not found.");
2877
2714
  }
2878
- await this.storage.saveSession({ ...session, pkceCodeVerifier: codeVerifier });
2879
- const url = await createConnectStartUrl({
2880
- keyPair,
2881
- connectLoginUrl: this.auth2ProviderOptions.connectLoginUrl,
2882
- clientId: this.auth2ProviderOptions.clientId,
2883
- redirectUri: this.auth2ProviderOptions.redirectUri,
2715
+ const { url, codeVerifier } = await prepareAuth2Flow({
2716
+ stamper: this.stamper,
2717
+ auth2Options: this.auth2ProviderOptions,
2884
2718
  sessionId: options.sessionId,
2885
- provider: options.provider,
2886
- codeVerifier,
2887
- // The P-256 ephemeral key is unique per wallet, so no additional salt is needed.
2888
- salt: ""
2719
+ provider: options.provider
2889
2720
  });
2721
+ await this.storage.saveSession({ ...session, pkceCodeVerifier: codeVerifier });
2890
2722
  Auth2AuthProvider.navigate(url);
2891
2723
  }
2892
2724
  /**
@@ -2896,8 +2728,7 @@ var Auth2AuthProvider = class {
2896
2728
  * and wallet via KMS RPC, then returns a completed AuthResult.
2897
2729
  */
2898
2730
  async resumeAuthFromRedirect(provider) {
2899
- const code = this.urlParamsAccessor.getParam("code");
2900
- if (!code) {
2731
+ if (!this.urlParamsAccessor.getParam("code")) {
2901
2732
  return null;
2902
2733
  }
2903
2734
  if (!this.stamper.getKeyInfo()) {
@@ -2911,226 +2742,39 @@ var Auth2AuthProvider = class {
2911
2742
  if (!codeVerifier) {
2912
2743
  return null;
2913
2744
  }
2914
- const state = this.urlParamsAccessor.getParam("state");
2915
- if (!state || state !== session.sessionId) {
2916
- throw new Error("Missing or invalid Auth2 state parameter \u2014 possible CSRF attack.");
2917
- }
2918
- const error = this.urlParamsAccessor.getParam("error");
2919
- if (error) {
2920
- const description = this.urlParamsAccessor.getParam("error_description");
2921
- throw new Error(`Auth2 callback error: ${description ?? error}`);
2922
- }
2923
- const { idToken, bearerToken, authUserId, expiresInMs, refreshToken } = await exchangeAuthCode({
2924
- authApiBaseUrl: this.auth2ProviderOptions.authApiBaseUrl,
2925
- clientId: this.auth2ProviderOptions.clientId,
2926
- redirectUri: this.auth2ProviderOptions.redirectUri,
2745
+ const code = validateAuth2Callback({
2746
+ getParam: (key) => this.urlParamsAccessor.getParam(key),
2747
+ expectedSessionId: session.sessionId
2748
+ });
2749
+ const result = await completeAuth2Exchange({
2750
+ stamper: this.stamper,
2751
+ kms: this.kms,
2752
+ auth2Options: this.auth2ProviderOptions,
2927
2753
  code,
2928
- codeVerifier
2754
+ codeVerifier,
2755
+ provider
2929
2756
  });
2930
- await this.stamper.setTokens({ idToken, bearerToken, refreshToken, expiresInMs });
2931
2757
  await this.storage.saveSession({
2932
2758
  ...session,
2933
2759
  status: "completed",
2934
- bearerToken,
2935
- authUserId,
2760
+ bearerToken: result.bearerToken,
2761
+ authUserId: result.authUserId,
2936
2762
  pkceCodeVerifier: void 0
2937
- // no longer needed after code exchange
2938
2763
  });
2939
- const { organizationId, walletId } = await this.kms.discoverOrganizationAndWalletId(bearerToken, authUserId);
2940
- return {
2941
- walletId,
2942
- organizationId,
2943
- provider,
2944
- accountDerivationIndex: 0,
2945
- // discoverWalletId uses derivation index of 0.
2946
- expiresInMs,
2947
- authUserId,
2948
- bearerToken
2949
- };
2764
+ return result;
2950
2765
  }
2951
2766
  };
2952
2767
 
2953
- // src/providers/embedded/adapters/Auth2Stamper.ts
2954
- import bs582 from "bs58";
2955
- import { base64urlEncode } from "@phantom/base64url";
2956
- import { Algorithm } from "@phantom/sdk-types";
2957
- import { refreshToken as refreshTokenRequest } from "@phantom/auth2";
2958
- import { TOKEN_REFRESH_BUFFER_MS } from "@phantom/constants";
2768
+ // src/providers/embedded/adapters/IndexedDBAuth2StamperStorage.ts
2959
2769
  var STORE_NAME = "crypto-keys";
2960
2770
  var ACTIVE_KEY = "auth2-p256-signing-key";
2961
- var Auth2Stamper = class {
2962
- /**
2963
- * @param dbName - IndexedDB database name (use a unique name per app to
2964
- * avoid key collisions with other stampers, e.g. `phantom-auth2-<appId>`).
2965
- * @param refreshConfig - When provided, the stamper will automatically refresh
2966
- * the id_token using the refresh_token before it expires.
2967
- */
2968
- constructor(dbName, refreshConfig) {
2771
+ var IndexedDBAuth2StamperStorage = class {
2772
+ constructor(dbName) {
2969
2773
  this.dbName = dbName;
2970
- this.refreshConfig = refreshConfig;
2971
2774
  this.db = null;
2972
- this._keyPair = null;
2973
- this._keyInfo = null;
2974
- this._idToken = null;
2975
- this._bearerToken = null;
2976
- this._refreshToken = null;
2977
- this._tokenExpiresAt = null;
2978
- this.algorithm = Algorithm.secp256r1;
2979
- this.type = "OIDC";
2980
- }
2981
- async init() {
2982
- await this.openDB();
2983
- const stored = await this.loadRecord();
2984
- if (stored) {
2985
- this._keyPair = stored.keyPair;
2986
- this._keyInfo = stored.keyInfo;
2987
- if (stored.idToken) {
2988
- this._idToken = stored.idToken;
2989
- }
2990
- if (stored.bearerToken) {
2991
- this._bearerToken = stored.bearerToken;
2992
- }
2993
- if (stored.refreshToken) {
2994
- this._refreshToken = stored.refreshToken;
2995
- }
2996
- if (stored.tokenExpiresAt) {
2997
- this._tokenExpiresAt = stored.tokenExpiresAt;
2998
- }
2999
- return this._keyInfo;
3000
- }
3001
- return this.generateAndStore();
3002
- }
3003
- getKeyInfo() {
3004
- return this._keyInfo;
3005
- }
3006
- getCryptoKeyPair() {
3007
- return this._keyPair;
3008
- }
3009
- /**
3010
- * Returns the current token state (refreshing proactively if near expiry),
3011
- * or null if no token has been set yet.
3012
- */
3013
- async getTokens() {
3014
- if (this.refreshConfig && this._refreshToken && this._tokenExpiresAt !== null && Date.now() >= this._tokenExpiresAt - TOKEN_REFRESH_BUFFER_MS) {
3015
- const refreshed = await refreshTokenRequest({
3016
- authApiBaseUrl: this.refreshConfig.authApiBaseUrl,
3017
- clientId: this.refreshConfig.clientId,
3018
- redirectUri: this.refreshConfig.redirectUri,
3019
- refreshToken: this._refreshToken
3020
- });
3021
- await this.setTokens(refreshed);
3022
- }
3023
- if (!this._idToken || !this._bearerToken) {
3024
- return null;
3025
- }
3026
- return {
3027
- idToken: this._idToken,
3028
- bearerToken: this._bearerToken,
3029
- refreshToken: this._refreshToken ?? void 0
3030
- };
3031
- }
3032
- /**
3033
- * Arms the stamper with the OIDC token data for subsequent KMS stamp() calls.
3034
- *
3035
- * Persists the tokens to IndexedDB alongside the key pair so that
3036
- * auto-connect can restore them on the next page load without a new login.
3037
- *
3038
- * @param refreshToken - When provided alongside a `refreshConfig`, enables
3039
- * silent token refresh before the token expires.
3040
- * @param expiresInMs - Token lifetime in milliseconds (from `expires_in * 1000`).
3041
- * Used to compute the absolute expiry time for proactive refresh.
3042
- */
3043
- async setTokens({
3044
- idToken,
3045
- bearerToken,
3046
- refreshToken,
3047
- expiresInMs
3048
- }) {
3049
- if (!this.db) {
3050
- await this.openDB();
3051
- }
3052
- this._idToken = idToken;
3053
- this._bearerToken = bearerToken;
3054
- this._refreshToken = refreshToken ?? null;
3055
- this._tokenExpiresAt = expiresInMs != null ? Date.now() + expiresInMs : null;
3056
- const existing = await this.loadRecord();
3057
- if (existing) {
3058
- await this.storeRecord({
3059
- ...existing,
3060
- idToken,
3061
- bearerToken,
3062
- refreshToken,
3063
- tokenExpiresAt: this._tokenExpiresAt ?? void 0
3064
- });
3065
- }
2775
+ this.requiresExtractableKeys = false;
3066
2776
  }
3067
- async stamp(params) {
3068
- if (!this._keyPair || !this._keyInfo || this._idToken === null) {
3069
- throw new Error("Auth2Stamper not initialized. Call init() first.");
3070
- }
3071
- const signatureRaw = await crypto.subtle.sign(
3072
- { name: "ECDSA", hash: "SHA-256" },
3073
- this._keyPair.privateKey,
3074
- new Uint8Array(params.data)
3075
- );
3076
- const rawPublicKey = bs582.decode(this._keyInfo.publicKey);
3077
- const stampData = {
3078
- kind: this.type,
3079
- idToken: this._idToken,
3080
- publicKey: base64urlEncode(rawPublicKey),
3081
- algorithm: this.algorithm,
3082
- // The P-256 ephemeral key is unique per wallet, so no additional salt is needed.
3083
- salt: "",
3084
- signature: base64urlEncode(new Uint8Array(signatureRaw))
3085
- };
3086
- return base64urlEncode(new TextEncoder().encode(JSON.stringify(stampData)));
3087
- }
3088
- async resetKeyPair() {
3089
- await this.clear();
3090
- return this.generateAndStore();
3091
- }
3092
- async clear() {
3093
- await this.clearStoredRecord();
3094
- this._keyPair = null;
3095
- this._keyInfo = null;
3096
- this._idToken = null;
3097
- this._bearerToken = null;
3098
- this._refreshToken = null;
3099
- this._tokenExpiresAt = null;
3100
- }
3101
- // Auth2 doesn't use key rotation; provide minimal no-op implementations.
3102
- async rotateKeyPair() {
3103
- return this.init();
3104
- }
3105
- // eslint-disable-next-line @typescript-eslint/require-await
3106
- async commitRotation(authenticatorId) {
3107
- if (this._keyInfo) {
3108
- this._keyInfo.authenticatorId = authenticatorId;
3109
- }
3110
- }
3111
- async rollbackRotation() {
3112
- }
3113
- async generateAndStore() {
3114
- const keyPair = await crypto.subtle.generateKey(
3115
- { name: "ECDSA", namedCurve: "P-256" },
3116
- false,
3117
- // non-extractable — private key never leaves Web Crypto
3118
- ["sign", "verify"]
3119
- );
3120
- const rawPublicKey = new Uint8Array(await crypto.subtle.exportKey("raw", keyPair.publicKey));
3121
- const publicKeyBase58 = bs582.encode(rawPublicKey);
3122
- const keyIdBuffer = await crypto.subtle.digest("SHA-256", rawPublicKey.buffer);
3123
- const keyId = base64urlEncode(new Uint8Array(keyIdBuffer)).substring(0, 16);
3124
- this._keyPair = keyPair;
3125
- this._keyInfo = {
3126
- keyId,
3127
- publicKey: publicKeyBase58,
3128
- createdAt: Date.now()
3129
- };
3130
- await this.storeRecord({ keyPair, keyInfo: this._keyInfo });
3131
- return this._keyInfo;
3132
- }
3133
- async openDB() {
2777
+ async open() {
3134
2778
  return new Promise((resolve, reject) => {
3135
2779
  const request = indexedDB.open(this.dbName, 1);
3136
2780
  request.onsuccess = () => {
@@ -3146,7 +2790,7 @@ var Auth2Stamper = class {
3146
2790
  };
3147
2791
  });
3148
2792
  }
3149
- async loadRecord() {
2793
+ async load() {
3150
2794
  return new Promise((resolve, reject) => {
3151
2795
  if (!this.db) {
3152
2796
  throw new Error("Database not initialized");
@@ -3160,7 +2804,7 @@ var Auth2Stamper = class {
3160
2804
  };
3161
2805
  });
3162
2806
  }
3163
- async storeRecord(record) {
2807
+ async save(record) {
3164
2808
  return new Promise((resolve, reject) => {
3165
2809
  if (!this.db) {
3166
2810
  throw new Error("Database not initialized");
@@ -3174,7 +2818,7 @@ var Auth2Stamper = class {
3174
2818
  };
3175
2819
  });
3176
2820
  }
3177
- async clearStoredRecord() {
2821
+ async clear() {
3178
2822
  return new Promise((resolve, reject) => {
3179
2823
  if (!this.db) {
3180
2824
  throw new Error("Database not initialized");
@@ -3314,32 +2958,28 @@ var EmbeddedProvider = class extends CoreEmbeddedProvider {
3314
2958
  debug.log(DebugCategory.EMBEDDED_PROVIDER, "Initializing Browser EmbeddedProvider", { config });
3315
2959
  const urlParamsAccessor = new BrowserURLParamsAccessor();
3316
2960
  const storage = new BrowserStorage();
3317
- const stamper = config.unstable__auth2Options ? new Auth2Stamper(`phantom-auth2-${config.appId}`, {
3318
- authApiBaseUrl: config.unstable__auth2Options.authApiBaseUrl,
3319
- clientId: config.unstable__auth2Options.clientId,
3320
- redirectUri: config.authOptions?.redirectUrl ?? ""
3321
- }) : new IndexedDbStamper({
3322
- dbName: `phantom-embedded-sdk-${config.appId}`,
3323
- storeName: "crypto-keys",
3324
- keyName: "signing-key"
2961
+ const stamper = new Auth2Stamper(new IndexedDBAuth2StamperStorage(`phantom-auth2-${config.appId}`), {
2962
+ authApiBaseUrl: config.authOptions.authApiBaseUrl,
2963
+ clientId: config.appId,
2964
+ redirectUri: config.authOptions.redirectUrl
3325
2965
  });
3326
2966
  const platformName = getPlatformName();
3327
2967
  const { name: browserName, version } = detectBrowser();
3328
- const authProvider = config.unstable__auth2Options && config.authOptions?.authUrl && config.authOptions?.redirectUrl && stamper instanceof Auth2Stamper ? new Auth2AuthProvider(
2968
+ const authProvider = new Auth2AuthProvider(
3329
2969
  stamper,
3330
2970
  storage,
3331
2971
  urlParamsAccessor,
3332
2972
  {
3333
2973
  redirectUri: config.authOptions.redirectUrl,
3334
2974
  connectLoginUrl: config.authOptions.authUrl,
3335
- clientId: config.unstable__auth2Options.clientId,
3336
- authApiBaseUrl: config.unstable__auth2Options.authApiBaseUrl
2975
+ clientId: config.appId,
2976
+ authApiBaseUrl: config.authOptions.authApiBaseUrl
3337
2977
  },
3338
2978
  {
3339
2979
  apiBaseUrl: config.apiBaseUrl,
3340
2980
  appId: config.appId
3341
2981
  }
3342
- ) : new BrowserAuthProvider(urlParamsAccessor);
2982
+ );
3343
2983
  const platform = {
3344
2984
  storage,
3345
2985
  authProvider,
@@ -3355,7 +2995,7 @@ var EmbeddedProvider = class extends CoreEmbeddedProvider {
3355
2995
  [ANALYTICS_HEADERS.CLIENT]: browserName,
3356
2996
  [ANALYTICS_HEADERS.APP_ID]: config.appId,
3357
2997
  [ANALYTICS_HEADERS.WALLET_TYPE]: config.embeddedWalletType,
3358
- [ANALYTICS_HEADERS.SDK_VERSION]: "1.0.6"
2998
+ [ANALYTICS_HEADERS.SDK_VERSION]: "2.0.0-beta.0"
3359
2999
  // Replaced at build time
3360
3000
  }
3361
3001
  };
@@ -3374,7 +3014,12 @@ var EmbeddedProvider = class extends CoreEmbeddedProvider {
3374
3014
  import {
3375
3015
  EMBEDDED_PROVIDER_AUTH_TYPES
3376
3016
  } from "@phantom/embedded-provider-core";
3377
- import { DEFAULT_WALLET_API_URL, DEFAULT_EMBEDDED_WALLET_TYPE, DEFAULT_AUTH_URL as DEFAULT_AUTH_URL2 } from "@phantom/constants";
3017
+ import {
3018
+ DEFAULT_WALLET_API_URL,
3019
+ DEFAULT_EMBEDDED_WALLET_TYPE,
3020
+ DEFAULT_AUTH_URL as DEFAULT_AUTH_URL2,
3021
+ DEFAULT_AUTH_API_BASE_URL
3022
+ } from "@phantom/constants";
3378
3023
 
3379
3024
  // src/utils/auth-callback.ts
3380
3025
  function isAuthFailureCallback(searchParams) {
@@ -3767,15 +3412,16 @@ var ProviderManager = class {
3767
3412
  }
3768
3413
  const apiBaseUrl = this.config.apiBaseUrl || DEFAULT_WALLET_API_URL;
3769
3414
  const authUrl = this.config.authOptions?.authUrl || DEFAULT_AUTH_URL2;
3415
+ const authApiBaseUrl = this.config.authOptions?.authApiBaseUrl || DEFAULT_AUTH_API_BASE_URL;
3770
3416
  provider = new EmbeddedProvider({
3771
3417
  apiBaseUrl,
3772
3418
  appId: this.config.appId,
3773
3419
  authOptions: {
3774
3420
  ...this.config.authOptions || {},
3775
3421
  authUrl,
3776
- redirectUrl: this.config.authOptions?.redirectUrl || this.getValidatedCurrentUrl()
3422
+ redirectUrl: this.config.authOptions?.redirectUrl || this.getValidatedCurrentUrl(),
3423
+ authApiBaseUrl
3777
3424
  },
3778
- unstable__auth2Options: this.config.unstable__auth2Options,
3779
3425
  embeddedWalletType: embeddedWalletType || DEFAULT_EMBEDDED_WALLET_TYPE,
3780
3426
  addressTypes: this.config.addressTypes || [AddressType.solana]
3781
3427
  });
@@ -4125,6 +3771,7 @@ var BrowserSDK = class {
4125
3771
  // src/index.ts
4126
3772
  import { NetworkId } from "@phantom/constants";
4127
3773
  import { AddressType as AddressType4 } from "@phantom/client";
3774
+ import { base64urlEncode, base64urlDecode } from "@phantom/base64url";
4128
3775
  import { PHANTOM_ICON as PHANTOM_ICON3 } from "@phantom/constants";
4129
3776
  export {
4130
3777
  AddressType4 as AddressType,
@@ -4133,6 +3780,8 @@ export {
4133
3780
  DebugLevel,
4134
3781
  NetworkId,
4135
3782
  PHANTOM_ICON3 as PHANTOM_ICON,
3783
+ base64urlDecode,
3784
+ base64urlEncode,
4136
3785
  debug,
4137
3786
  detectBrowser,
4138
3787
  getBrowserDisplayName,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@phantom/browser-sdk",
3
- "version": "1.0.6",
3
+ "version": "2.0.0-beta.0",
4
4
  "description": "Browser SDK for Phantom Wallet",
5
5
  "repository": {
6
6
  "type": "git",
@@ -33,16 +33,16 @@
33
33
  "prettier": "prettier --write \"src/**/*.{ts,tsx}\""
34
34
  },
35
35
  "dependencies": {
36
- "@phantom/auth2": "^1.0.2",
37
- "@phantom/base64url": "^1.0.6",
38
- "@phantom/browser-injected-sdk": "^1.0.6",
39
- "@phantom/chain-interfaces": "^1.0.6",
40
- "@phantom/client": "^1.0.6",
41
- "@phantom/constants": "^1.0.6",
42
- "@phantom/embedded-provider-core": "^1.0.6",
43
- "@phantom/indexed-db-stamper": "^1.0.6",
44
- "@phantom/parsers": "^1.0.6",
45
- "@phantom/sdk-types": "^1.0.6",
36
+ "@phantom/auth2": "^2.0.0-beta.0",
37
+ "@phantom/base64url": "^2.0.0-beta.0",
38
+ "@phantom/browser-injected-sdk": "^2.0.0-beta.0",
39
+ "@phantom/chain-interfaces": "^2.0.0-beta.0",
40
+ "@phantom/client": "^2.0.0-beta.0",
41
+ "@phantom/constants": "^2.0.0-beta.0",
42
+ "@phantom/embedded-provider-core": "^2.0.0-beta.0",
43
+ "@phantom/indexed-db-stamper": "^2.0.0-beta.0",
44
+ "@phantom/parsers": "^2.0.0-beta.0",
45
+ "@phantom/sdk-types": "^2.0.0-beta.0",
46
46
  "axios": "^1.10.0",
47
47
  "bs58": "^6.0.0",
48
48
  "buffer": "^6.0.3",