@phantom/browser-sdk 1.0.7 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md 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)
package/dist/index.d.ts CHANGED
@@ -119,11 +119,7 @@ type BrowserSDKConfig = Prettify<Omit<EmbeddedProviderConfig, "authOptions" | "a
119
119
  authOptions?: {
120
120
  authUrl?: string;
121
121
  redirectUrl?: string;
122
- };
123
- /** When also provided, the Auth2 PKCE flow is used instead of the legacy Phantom Connect flow. */
124
- unstable__auth2Options?: {
125
- authApiBaseUrl: string;
126
- clientId: string;
122
+ authApiBaseUrl?: string;
127
123
  };
128
124
  }>;
129
125
  type Prettify<T> = {
package/dist/index.js CHANGED
@@ -34,10 +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,
39
- base64urlDecode: () => import_base64url2.base64urlDecode,
40
- base64urlEncode: () => import_base64url2.base64urlEncode,
37
+ NetworkId: () => import_constants7.NetworkId,
38
+ PHANTOM_ICON: () => import_constants8.PHANTOM_ICON,
39
+ base64urlDecode: () => import_base64url.base64urlDecode,
40
+ base64urlEncode: () => import_base64url.base64urlEncode,
41
41
  debug: () => debug,
42
42
  detectBrowser: () => detectBrowser,
43
43
  getBrowserDisplayName: () => getBrowserDisplayName,
@@ -2457,7 +2457,7 @@ var InjectedProvider = class {
2457
2457
 
2458
2458
  // src/providers/embedded/index.ts
2459
2459
  var import_embedded_provider_core = require("@phantom/embedded-provider-core");
2460
- var import_indexed_db_stamper = require("@phantom/indexed-db-stamper");
2460
+ var import_auth22 = require("@phantom/auth2");
2461
2461
 
2462
2462
  // src/providers/embedded/adapters/storage.ts
2463
2463
  var BrowserStorage = class {
@@ -2732,161 +2732,6 @@ function isMobileDevice() {
2732
2732
  return isMobileUA || isSmallScreen && isTouchDevice;
2733
2733
  }
2734
2734
 
2735
- // src/providers/embedded/adapters/auth.ts
2736
- var BrowserAuthProvider = class {
2737
- constructor(urlParamsAccessor) {
2738
- this.urlParamsAccessor = urlParamsAccessor;
2739
- }
2740
- getValidatedCurrentUrl() {
2741
- const currentUrl = window.location.href;
2742
- if (!currentUrl.startsWith("http:") && !currentUrl.startsWith("https:")) {
2743
- throw new Error("Invalid URL protocol - only HTTP/HTTPS URLs are supported");
2744
- }
2745
- return currentUrl;
2746
- }
2747
- authenticate(options) {
2748
- return new Promise((resolve) => {
2749
- if ("jwtToken" in options) {
2750
- throw new Error("JWT authentication should be handled by the core JWTAuth class");
2751
- }
2752
- const phantomOptions = options;
2753
- debug.info(DebugCategory.PHANTOM_CONNECT_AUTH, "Starting Phantom Connect authentication", {
2754
- publicKey: phantomOptions.publicKey,
2755
- appId: phantomOptions.appId,
2756
- provider: phantomOptions.provider,
2757
- authUrl: phantomOptions.authUrl
2758
- });
2759
- const baseUrl = phantomOptions.authUrl || import_constants3.DEFAULT_AUTH_URL;
2760
- debug.log(DebugCategory.PHANTOM_CONNECT_AUTH, "Using auth URL", { baseUrl });
2761
- const params = new URLSearchParams({
2762
- public_key: phantomOptions.publicKey,
2763
- app_id: phantomOptions.appId,
2764
- redirect_uri: phantomOptions.redirectUrl || (typeof window !== "undefined" ? this.getValidatedCurrentUrl() : ""),
2765
- session_id: phantomOptions.sessionId,
2766
- // OAuth session management - defaults to allow refresh unless explicitly clearing after logout
2767
- clear_previous_session: (phantomOptions.clearPreviousSession ?? false).toString(),
2768
- allow_refresh: (phantomOptions.allowRefresh ?? true).toString(),
2769
- sdk_version: "1.0.7",
2770
- sdk_type: "browser",
2771
- platform: detectBrowser().name,
2772
- algorithm: phantomOptions.algorithm || import_constants3.DEFAULT_AUTHENTICATOR_ALGORITHM
2773
- });
2774
- if (phantomOptions.provider) {
2775
- debug.log(DebugCategory.PHANTOM_CONNECT_AUTH, "Provider specified, will skip selection", {
2776
- provider: phantomOptions.provider
2777
- });
2778
- params.append("provider", phantomOptions.provider);
2779
- } else {
2780
- debug.log(DebugCategory.PHANTOM_CONNECT_AUTH, "No provider specified, defaulting to Google");
2781
- params.append("provider", "google");
2782
- }
2783
- const authContext = {
2784
- publicKey: phantomOptions.publicKey,
2785
- appId: phantomOptions.appId,
2786
- provider: phantomOptions.provider,
2787
- sessionId: phantomOptions.sessionId
2788
- };
2789
- sessionStorage.setItem("phantom-auth-context", JSON.stringify(authContext));
2790
- debug.log(DebugCategory.PHANTOM_CONNECT_AUTH, "Stored auth context in session storage", { authContext });
2791
- const authUrl = `${baseUrl}?${params.toString()}`;
2792
- debug.info(DebugCategory.PHANTOM_CONNECT_AUTH, "Redirecting to Phantom Connect", { authUrl });
2793
- if (!authUrl.startsWith("https:") && !authUrl.startsWith("http://localhost")) {
2794
- throw new Error("Invalid auth URL - only HTTPS URLs are allowed for authentication");
2795
- }
2796
- window.location.href = authUrl;
2797
- resolve();
2798
- });
2799
- }
2800
- async resumeAuthFromRedirect(provider) {
2801
- try {
2802
- const walletId = this.urlParamsAccessor.getParam("wallet_id");
2803
- const sessionId = this.urlParamsAccessor.getParam("session_id");
2804
- const accountDerivationIndex = this.urlParamsAccessor.getParam("selected_account_index");
2805
- const error = this.urlParamsAccessor.getParam("error");
2806
- const errorDescription = this.urlParamsAccessor.getParam("error_description");
2807
- if (error) {
2808
- const errorMsg = errorDescription || error;
2809
- switch (error) {
2810
- case "access_denied":
2811
- throw new Error(`Authentication cancelled: ${errorMsg}`);
2812
- case "invalid_request":
2813
- throw new Error(`Invalid authentication request: ${errorMsg}`);
2814
- case "server_error":
2815
- throw new Error(`Authentication server error: ${errorMsg}`);
2816
- case "temporarily_unavailable":
2817
- throw new Error(`Authentication service temporarily unavailable: ${errorMsg}`);
2818
- default:
2819
- throw new Error(`Authentication failed: ${errorMsg}`);
2820
- }
2821
- }
2822
- if (!walletId || !sessionId) {
2823
- debug.log(DebugCategory.PHANTOM_CONNECT_AUTH, "Missing auth parameters in URL", {
2824
- hasWalletId: !!walletId,
2825
- hasSessionId: !!sessionId
2826
- });
2827
- return null;
2828
- }
2829
- const contextStr = sessionStorage.getItem("phantom-auth-context");
2830
- let context = {};
2831
- if (contextStr) {
2832
- try {
2833
- context = JSON.parse(contextStr);
2834
- } catch (parseError) {
2835
- debug.warn(DebugCategory.PHANTOM_CONNECT_AUTH, "Failed to parse stored auth context", { error: parseError });
2836
- }
2837
- }
2838
- if (context.sessionId && sessionId !== context.sessionId) {
2839
- debug.error(DebugCategory.PHANTOM_CONNECT_AUTH, "Session ID mismatch", {
2840
- urlSessionId: sessionId,
2841
- storedSessionId: context.sessionId
2842
- });
2843
- throw new Error("Session ID mismatch - possible session corruption or replay attack");
2844
- }
2845
- sessionStorage.removeItem("phantom-auth-context");
2846
- debug.info(DebugCategory.PHANTOM_CONNECT_AUTH, "Successfully resumed auth from redirect", {
2847
- walletId,
2848
- sessionId,
2849
- accountDerivationIndex: accountDerivationIndex ? parseInt(accountDerivationIndex) : void 0
2850
- });
2851
- const organizationId = this.urlParamsAccessor.getParam("organization_id");
2852
- const expiresInMs = this.urlParamsAccessor.getParam("expires_in_ms");
2853
- const authUserId = this.urlParamsAccessor.getParam("auth_user_id");
2854
- debug.log(DebugCategory.PHANTOM_CONNECT_AUTH, "Auth redirect parameters", {
2855
- walletId,
2856
- organizationId,
2857
- sessionId,
2858
- accountDerivationIndex,
2859
- expiresInMs,
2860
- authUserId
2861
- });
2862
- if (!organizationId) {
2863
- debug.error(DebugCategory.PHANTOM_CONNECT_AUTH, "Missing organization_id in auth response");
2864
- throw new Error("Missing organization_id in auth response");
2865
- }
2866
- if (organizationId.startsWith("temp-")) {
2867
- debug.warn(
2868
- DebugCategory.PHANTOM_CONNECT_AUTH,
2869
- "Received temporary organization_id, server may not be configured properly",
2870
- {
2871
- organizationId
2872
- }
2873
- );
2874
- }
2875
- return Promise.resolve({
2876
- walletId,
2877
- organizationId,
2878
- accountDerivationIndex: accountDerivationIndex ? parseInt(accountDerivationIndex) : 0,
2879
- expiresInMs: expiresInMs ? parseInt(expiresInMs) : 0,
2880
- authUserId: authUserId || void 0,
2881
- provider
2882
- });
2883
- } catch (error) {
2884
- sessionStorage.removeItem("phantom-auth-context");
2885
- throw error;
2886
- }
2887
- }
2888
- };
2889
-
2890
2735
  // src/providers/embedded/adapters/Auth2AuthProvider.ts
2891
2736
  var import_auth2 = require("@phantom/auth2");
2892
2737
  var Auth2AuthProvider = class {
@@ -2910,30 +2755,17 @@ var Auth2AuthProvider = class {
2910
2755
  * redirect without ever touching sessionStorage.
2911
2756
  */
2912
2757
  async authenticate(options) {
2913
- if (!this.stamper.getKeyInfo()) {
2914
- await this.stamper.init();
2915
- }
2916
- const keyPair = this.stamper.getCryptoKeyPair();
2917
- if (!keyPair) {
2918
- throw new Error("Stamper key pair not found.");
2919
- }
2920
- const codeVerifier = (0, import_auth2.createCodeVerifier)();
2921
2758
  const session = await this.storage.getSession();
2922
2759
  if (!session) {
2923
2760
  throw new Error("Session not found.");
2924
2761
  }
2925
- await this.storage.saveSession({ ...session, pkceCodeVerifier: codeVerifier });
2926
- const url = await (0, import_auth2.createConnectStartUrl)({
2927
- keyPair,
2928
- connectLoginUrl: this.auth2ProviderOptions.connectLoginUrl,
2929
- clientId: this.auth2ProviderOptions.clientId,
2930
- redirectUri: this.auth2ProviderOptions.redirectUri,
2762
+ const { url, codeVerifier } = await (0, import_auth2.prepareAuth2Flow)({
2763
+ stamper: this.stamper,
2764
+ auth2Options: this.auth2ProviderOptions,
2931
2765
  sessionId: options.sessionId,
2932
- provider: options.provider,
2933
- codeVerifier,
2934
- // The P-256 ephemeral key is unique per wallet, so no additional salt is needed.
2935
- salt: ""
2766
+ provider: options.provider
2936
2767
  });
2768
+ await this.storage.saveSession({ ...session, pkceCodeVerifier: codeVerifier });
2937
2769
  Auth2AuthProvider.navigate(url);
2938
2770
  }
2939
2771
  /**
@@ -2943,8 +2775,7 @@ var Auth2AuthProvider = class {
2943
2775
  * and wallet via KMS RPC, then returns a completed AuthResult.
2944
2776
  */
2945
2777
  async resumeAuthFromRedirect(provider) {
2946
- const code = this.urlParamsAccessor.getParam("code");
2947
- if (!code) {
2778
+ if (!this.urlParamsAccessor.getParam("code")) {
2948
2779
  return null;
2949
2780
  }
2950
2781
  if (!this.stamper.getKeyInfo()) {
@@ -2958,226 +2789,39 @@ var Auth2AuthProvider = class {
2958
2789
  if (!codeVerifier) {
2959
2790
  return null;
2960
2791
  }
2961
- const state = this.urlParamsAccessor.getParam("state");
2962
- if (!state || state !== session.sessionId) {
2963
- throw new Error("Missing or invalid Auth2 state parameter \u2014 possible CSRF attack.");
2964
- }
2965
- const error = this.urlParamsAccessor.getParam("error");
2966
- if (error) {
2967
- const description = this.urlParamsAccessor.getParam("error_description");
2968
- throw new Error(`Auth2 callback error: ${description ?? error}`);
2969
- }
2970
- const { idToken, bearerToken, authUserId, expiresInMs, refreshToken } = await (0, import_auth2.exchangeAuthCode)({
2971
- authApiBaseUrl: this.auth2ProviderOptions.authApiBaseUrl,
2972
- clientId: this.auth2ProviderOptions.clientId,
2973
- 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,
2974
2800
  code,
2975
- codeVerifier
2801
+ codeVerifier,
2802
+ provider
2976
2803
  });
2977
- await this.stamper.setTokens({ idToken, bearerToken, refreshToken, expiresInMs });
2978
2804
  await this.storage.saveSession({
2979
2805
  ...session,
2980
2806
  status: "completed",
2981
- bearerToken,
2982
- authUserId,
2807
+ bearerToken: result.bearerToken,
2808
+ authUserId: result.authUserId,
2983
2809
  pkceCodeVerifier: void 0
2984
- // no longer needed after code exchange
2985
2810
  });
2986
- const { organizationId, walletId } = await this.kms.discoverOrganizationAndWalletId(bearerToken, authUserId);
2987
- return {
2988
- walletId,
2989
- organizationId,
2990
- provider,
2991
- accountDerivationIndex: 0,
2992
- // discoverWalletId uses derivation index of 0.
2993
- expiresInMs,
2994
- authUserId,
2995
- bearerToken
2996
- };
2811
+ return result;
2997
2812
  }
2998
2813
  };
2999
2814
 
3000
- // src/providers/embedded/adapters/Auth2Stamper.ts
3001
- var import_bs582 = __toESM(require("bs58"));
3002
- var import_base64url = require("@phantom/base64url");
3003
- var import_sdk_types = require("@phantom/sdk-types");
3004
- var import_auth22 = require("@phantom/auth2");
3005
- var import_constants4 = require("@phantom/constants");
2815
+ // src/providers/embedded/adapters/IndexedDBAuth2StamperStorage.ts
3006
2816
  var STORE_NAME = "crypto-keys";
3007
2817
  var ACTIVE_KEY = "auth2-p256-signing-key";
3008
- var Auth2Stamper = class {
3009
- /**
3010
- * @param dbName - IndexedDB database name (use a unique name per app to
3011
- * avoid key collisions with other stampers, e.g. `phantom-auth2-<appId>`).
3012
- * @param refreshConfig - When provided, the stamper will automatically refresh
3013
- * the id_token using the refresh_token before it expires.
3014
- */
3015
- constructor(dbName, refreshConfig) {
2818
+ var IndexedDBAuth2StamperStorage = class {
2819
+ constructor(dbName) {
3016
2820
  this.dbName = dbName;
3017
- this.refreshConfig = refreshConfig;
3018
2821
  this.db = null;
3019
- this._keyPair = null;
3020
- this._keyInfo = null;
3021
- this._idToken = null;
3022
- this._bearerToken = null;
3023
- this._refreshToken = null;
3024
- this._tokenExpiresAt = null;
3025
- this.algorithm = import_sdk_types.Algorithm.secp256r1;
3026
- this.type = "OIDC";
3027
- }
3028
- async init() {
3029
- await this.openDB();
3030
- const stored = await this.loadRecord();
3031
- if (stored) {
3032
- this._keyPair = stored.keyPair;
3033
- this._keyInfo = stored.keyInfo;
3034
- if (stored.idToken) {
3035
- this._idToken = stored.idToken;
3036
- }
3037
- if (stored.bearerToken) {
3038
- this._bearerToken = stored.bearerToken;
3039
- }
3040
- if (stored.refreshToken) {
3041
- this._refreshToken = stored.refreshToken;
3042
- }
3043
- if (stored.tokenExpiresAt) {
3044
- this._tokenExpiresAt = stored.tokenExpiresAt;
3045
- }
3046
- return this._keyInfo;
3047
- }
3048
- return this.generateAndStore();
3049
- }
3050
- getKeyInfo() {
3051
- return this._keyInfo;
3052
- }
3053
- getCryptoKeyPair() {
3054
- return this._keyPair;
3055
- }
3056
- /**
3057
- * Returns the current token state (refreshing proactively if near expiry),
3058
- * or null if no token has been set yet.
3059
- */
3060
- async getTokens() {
3061
- if (this.refreshConfig && this._refreshToken && this._tokenExpiresAt !== null && Date.now() >= this._tokenExpiresAt - import_constants4.TOKEN_REFRESH_BUFFER_MS) {
3062
- const refreshed = await (0, import_auth22.refreshToken)({
3063
- authApiBaseUrl: this.refreshConfig.authApiBaseUrl,
3064
- clientId: this.refreshConfig.clientId,
3065
- redirectUri: this.refreshConfig.redirectUri,
3066
- refreshToken: this._refreshToken
3067
- });
3068
- await this.setTokens(refreshed);
3069
- }
3070
- if (!this._idToken || !this._bearerToken) {
3071
- return null;
3072
- }
3073
- return {
3074
- idToken: this._idToken,
3075
- bearerToken: this._bearerToken,
3076
- refreshToken: this._refreshToken ?? void 0
3077
- };
3078
- }
3079
- /**
3080
- * Arms the stamper with the OIDC token data for subsequent KMS stamp() calls.
3081
- *
3082
- * Persists the tokens to IndexedDB alongside the key pair so that
3083
- * auto-connect can restore them on the next page load without a new login.
3084
- *
3085
- * @param refreshToken - When provided alongside a `refreshConfig`, enables
3086
- * silent token refresh before the token expires.
3087
- * @param expiresInMs - Token lifetime in milliseconds (from `expires_in * 1000`).
3088
- * Used to compute the absolute expiry time for proactive refresh.
3089
- */
3090
- async setTokens({
3091
- idToken,
3092
- bearerToken,
3093
- refreshToken,
3094
- expiresInMs
3095
- }) {
3096
- if (!this.db) {
3097
- await this.openDB();
3098
- }
3099
- this._idToken = idToken;
3100
- this._bearerToken = bearerToken;
3101
- this._refreshToken = refreshToken ?? null;
3102
- this._tokenExpiresAt = expiresInMs != null ? Date.now() + expiresInMs : null;
3103
- const existing = await this.loadRecord();
3104
- if (existing) {
3105
- await this.storeRecord({
3106
- ...existing,
3107
- idToken,
3108
- bearerToken,
3109
- refreshToken,
3110
- tokenExpiresAt: this._tokenExpiresAt ?? void 0
3111
- });
3112
- }
2822
+ this.requiresExtractableKeys = false;
3113
2823
  }
3114
- async stamp(params) {
3115
- if (!this._keyPair || !this._keyInfo || this._idToken === null) {
3116
- throw new Error("Auth2Stamper not initialized. Call init() first.");
3117
- }
3118
- const signatureRaw = await crypto.subtle.sign(
3119
- { name: "ECDSA", hash: "SHA-256" },
3120
- this._keyPair.privateKey,
3121
- new Uint8Array(params.data)
3122
- );
3123
- const rawPublicKey = import_bs582.default.decode(this._keyInfo.publicKey);
3124
- const stampData = {
3125
- kind: this.type,
3126
- idToken: this._idToken,
3127
- publicKey: (0, import_base64url.base64urlEncode)(rawPublicKey),
3128
- algorithm: this.algorithm,
3129
- // The P-256 ephemeral key is unique per wallet, so no additional salt is needed.
3130
- salt: "",
3131
- signature: (0, import_base64url.base64urlEncode)(new Uint8Array(signatureRaw))
3132
- };
3133
- return (0, import_base64url.base64urlEncode)(new TextEncoder().encode(JSON.stringify(stampData)));
3134
- }
3135
- async resetKeyPair() {
3136
- await this.clear();
3137
- return this.generateAndStore();
3138
- }
3139
- async clear() {
3140
- await this.clearStoredRecord();
3141
- this._keyPair = null;
3142
- this._keyInfo = null;
3143
- this._idToken = null;
3144
- this._bearerToken = null;
3145
- this._refreshToken = null;
3146
- this._tokenExpiresAt = null;
3147
- }
3148
- // Auth2 doesn't use key rotation; provide minimal no-op implementations.
3149
- async rotateKeyPair() {
3150
- return this.init();
3151
- }
3152
- // eslint-disable-next-line @typescript-eslint/require-await
3153
- async commitRotation(authenticatorId) {
3154
- if (this._keyInfo) {
3155
- this._keyInfo.authenticatorId = authenticatorId;
3156
- }
3157
- }
3158
- async rollbackRotation() {
3159
- }
3160
- async generateAndStore() {
3161
- const keyPair = await crypto.subtle.generateKey(
3162
- { name: "ECDSA", namedCurve: "P-256" },
3163
- false,
3164
- // non-extractable — private key never leaves Web Crypto
3165
- ["sign", "verify"]
3166
- );
3167
- const rawPublicKey = new Uint8Array(await crypto.subtle.exportKey("raw", keyPair.publicKey));
3168
- const publicKeyBase58 = import_bs582.default.encode(rawPublicKey);
3169
- const keyIdBuffer = await crypto.subtle.digest("SHA-256", rawPublicKey.buffer);
3170
- const keyId = (0, import_base64url.base64urlEncode)(new Uint8Array(keyIdBuffer)).substring(0, 16);
3171
- this._keyPair = keyPair;
3172
- this._keyInfo = {
3173
- keyId,
3174
- publicKey: publicKeyBase58,
3175
- createdAt: Date.now()
3176
- };
3177
- await this.storeRecord({ keyPair, keyInfo: this._keyInfo });
3178
- return this._keyInfo;
3179
- }
3180
- async openDB() {
2824
+ async open() {
3181
2825
  return new Promise((resolve, reject) => {
3182
2826
  const request = indexedDB.open(this.dbName, 1);
3183
2827
  request.onsuccess = () => {
@@ -3193,7 +2837,7 @@ var Auth2Stamper = class {
3193
2837
  };
3194
2838
  });
3195
2839
  }
3196
- async loadRecord() {
2840
+ async load() {
3197
2841
  return new Promise((resolve, reject) => {
3198
2842
  if (!this.db) {
3199
2843
  throw new Error("Database not initialized");
@@ -3207,7 +2851,7 @@ var Auth2Stamper = class {
3207
2851
  };
3208
2852
  });
3209
2853
  }
3210
- async storeRecord(record) {
2854
+ async save(record) {
3211
2855
  return new Promise((resolve, reject) => {
3212
2856
  if (!this.db) {
3213
2857
  throw new Error("Database not initialized");
@@ -3221,7 +2865,7 @@ var Auth2Stamper = class {
3221
2865
  };
3222
2866
  });
3223
2867
  }
3224
- async clearStoredRecord() {
2868
+ async clear() {
3225
2869
  return new Promise((resolve, reject) => {
3226
2870
  if (!this.db) {
3227
2871
  throw new Error("Database not initialized");
@@ -3355,38 +2999,34 @@ var BrowserLogger = class {
3355
2999
  };
3356
3000
 
3357
3001
  // src/providers/embedded/index.ts
3358
- var import_constants5 = require("@phantom/constants");
3002
+ var import_constants4 = require("@phantom/constants");
3359
3003
  var EmbeddedProvider = class extends import_embedded_provider_core.EmbeddedProvider {
3360
3004
  constructor(config) {
3361
3005
  debug.log(DebugCategory.EMBEDDED_PROVIDER, "Initializing Browser EmbeddedProvider", { config });
3362
3006
  const urlParamsAccessor = new BrowserURLParamsAccessor();
3363
3007
  const storage = new BrowserStorage();
3364
- const stamper = config.unstable__auth2Options ? new Auth2Stamper(`phantom-auth2-${config.appId}`, {
3365
- authApiBaseUrl: config.unstable__auth2Options.authApiBaseUrl,
3366
- clientId: config.unstable__auth2Options.clientId,
3367
- redirectUri: config.authOptions?.redirectUrl ?? ""
3368
- }) : new import_indexed_db_stamper.IndexedDbStamper({
3369
- dbName: `phantom-embedded-sdk-${config.appId}`,
3370
- storeName: "crypto-keys",
3371
- 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
3372
3012
  });
3373
3013
  const platformName = getPlatformName();
3374
3014
  const { name: browserName, version } = detectBrowser();
3375
- const authProvider = config.unstable__auth2Options && config.authOptions?.authUrl && config.authOptions?.redirectUrl && stamper instanceof Auth2Stamper ? new Auth2AuthProvider(
3015
+ const authProvider = new Auth2AuthProvider(
3376
3016
  stamper,
3377
3017
  storage,
3378
3018
  urlParamsAccessor,
3379
3019
  {
3380
3020
  redirectUri: config.authOptions.redirectUrl,
3381
3021
  connectLoginUrl: config.authOptions.authUrl,
3382
- clientId: config.unstable__auth2Options.clientId,
3383
- authApiBaseUrl: config.unstable__auth2Options.authApiBaseUrl
3022
+ clientId: config.appId,
3023
+ authApiBaseUrl: config.authOptions.authApiBaseUrl
3384
3024
  },
3385
3025
  {
3386
3026
  apiBaseUrl: config.apiBaseUrl,
3387
3027
  appId: config.appId
3388
3028
  }
3389
- ) : new BrowserAuthProvider(urlParamsAccessor);
3029
+ );
3390
3030
  const platform = {
3391
3031
  storage,
3392
3032
  authProvider,
@@ -3396,13 +3036,13 @@ var EmbeddedProvider = class extends import_embedded_provider_core.EmbeddedProvi
3396
3036
  name: platformName,
3397
3037
  // Use detected browser name and version for identification
3398
3038
  analyticsHeaders: {
3399
- [import_constants5.ANALYTICS_HEADERS.SDK_TYPE]: "browser",
3400
- [import_constants5.ANALYTICS_HEADERS.PLATFORM]: "ext-sdk",
3401
- [import_constants5.ANALYTICS_HEADERS.PLATFORM_VERSION]: version,
3402
- [import_constants5.ANALYTICS_HEADERS.CLIENT]: browserName,
3403
- [import_constants5.ANALYTICS_HEADERS.APP_ID]: config.appId,
3404
- [import_constants5.ANALYTICS_HEADERS.WALLET_TYPE]: config.embeddedWalletType,
3405
- [import_constants5.ANALYTICS_HEADERS.SDK_VERSION]: "1.0.7"
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"
3406
3046
  // Replaced at build time
3407
3047
  }
3408
3048
  };
@@ -3419,7 +3059,7 @@ var EmbeddedProvider = class extends import_embedded_provider_core.EmbeddedProvi
3419
3059
 
3420
3060
  // src/ProviderManager.ts
3421
3061
  var import_embedded_provider_core2 = require("@phantom/embedded-provider-core");
3422
- var import_constants6 = require("@phantom/constants");
3062
+ var import_constants5 = require("@phantom/constants");
3423
3063
 
3424
3064
  // src/utils/auth-callback.ts
3425
3065
  function isAuthFailureCallback(searchParams) {
@@ -3810,18 +3450,19 @@ var ProviderManager = class {
3810
3450
  if (!this.config.appId) {
3811
3451
  throw new Error("appId is required for embedded provider");
3812
3452
  }
3813
- const apiBaseUrl = this.config.apiBaseUrl || import_constants6.DEFAULT_WALLET_API_URL;
3814
- 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;
3815
3456
  provider = new EmbeddedProvider({
3816
3457
  apiBaseUrl,
3817
3458
  appId: this.config.appId,
3818
3459
  authOptions: {
3819
3460
  ...this.config.authOptions || {},
3820
3461
  authUrl,
3821
- redirectUrl: this.config.authOptions?.redirectUrl || this.getValidatedCurrentUrl()
3462
+ redirectUrl: this.config.authOptions?.redirectUrl || this.getValidatedCurrentUrl(),
3463
+ authApiBaseUrl
3822
3464
  },
3823
- unstable__auth2Options: this.config.unstable__auth2Options,
3824
- embeddedWalletType: embeddedWalletType || import_constants6.DEFAULT_EMBEDDED_WALLET_TYPE,
3465
+ embeddedWalletType: embeddedWalletType || import_constants5.DEFAULT_EMBEDDED_WALLET_TYPE,
3825
3466
  addressTypes: this.config.addressTypes || [import_client.AddressType.solana]
3826
3467
  });
3827
3468
  } else {
@@ -3857,7 +3498,7 @@ var ProviderManager = class {
3857
3498
 
3858
3499
  // src/BrowserSDK.ts
3859
3500
  var import_embedded_provider_core3 = require("@phantom/embedded-provider-core");
3860
- var import_constants7 = require("@phantom/constants");
3501
+ var import_constants6 = require("@phantom/constants");
3861
3502
  var BROWSER_SDK_PROVIDER_TYPES = [
3862
3503
  ...import_embedded_provider_core3.EMBEDDED_PROVIDER_AUTH_TYPES,
3863
3504
  "injected",
@@ -3893,7 +3534,7 @@ var BrowserSDK = class {
3893
3534
  });
3894
3535
  throw new Error("appId is required when using embedded providers (google, apple, phantom, etc.)");
3895
3536
  }
3896
- const embeddedWalletType = config.embeddedWalletType || import_constants7.DEFAULT_EMBEDDED_WALLET_TYPE;
3537
+ const embeddedWalletType = config.embeddedWalletType || import_constants6.DEFAULT_EMBEDDED_WALLET_TYPE;
3897
3538
  if (!["app-wallet", "user-wallet"].includes(embeddedWalletType)) {
3898
3539
  debug.error(DebugCategory.BROWSER_SDK, "Invalid embeddedWalletType", {
3899
3540
  embeddedWalletType: config.embeddedWalletType
@@ -4168,7 +3809,7 @@ var BrowserSDK = class {
4168
3809
  };
4169
3810
 
4170
3811
  // src/index.ts
4171
- var import_constants8 = require("@phantom/constants");
3812
+ var import_constants7 = require("@phantom/constants");
4172
3813
  var import_client5 = require("@phantom/client");
4173
- var import_base64url2 = require("@phantom/base64url");
4174
- 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.7",
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.7"
2998
+ [ANALYTICS_HEADERS.SDK_VERSION]: "2.0.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,7 +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";
4128
- import { base64urlEncode as base64urlEncode2, base64urlDecode } from "@phantom/base64url";
3774
+ import { base64urlEncode, base64urlDecode } from "@phantom/base64url";
4129
3775
  import { PHANTOM_ICON as PHANTOM_ICON3 } from "@phantom/constants";
4130
3776
  export {
4131
3777
  AddressType4 as AddressType,
@@ -4135,7 +3781,7 @@ export {
4135
3781
  NetworkId,
4136
3782
  PHANTOM_ICON3 as PHANTOM_ICON,
4137
3783
  base64urlDecode,
4138
- base64urlEncode2 as base64urlEncode,
3784
+ base64urlEncode,
4139
3785
  debug,
4140
3786
  detectBrowser,
4141
3787
  getBrowserDisplayName,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@phantom/browser-sdk",
3
- "version": "1.0.7",
3
+ "version": "2.0.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.7",
38
- "@phantom/browser-injected-sdk": "^1.0.7",
39
- "@phantom/chain-interfaces": "^1.0.7",
40
- "@phantom/client": "^1.0.7",
41
- "@phantom/constants": "^1.0.7",
42
- "@phantom/embedded-provider-core": "^1.0.7",
43
- "@phantom/indexed-db-stamper": "^1.0.7",
44
- "@phantom/parsers": "^1.0.7",
45
- "@phantom/sdk-types": "^1.0.7",
36
+ "@phantom/auth2": "^2.0.0",
37
+ "@phantom/base64url": "^2.0.0",
38
+ "@phantom/browser-injected-sdk": "^2.0.0",
39
+ "@phantom/chain-interfaces": "^2.0.0",
40
+ "@phantom/client": "^2.0.0",
41
+ "@phantom/constants": "^2.0.0",
42
+ "@phantom/embedded-provider-core": "^2.0.0",
43
+ "@phantom/indexed-db-stamper": "^2.0.0",
44
+ "@phantom/parsers": "^2.0.0",
45
+ "@phantom/sdk-types": "^2.0.0",
46
46
  "axios": "^1.10.0",
47
47
  "bs58": "^6.0.0",
48
48
  "buffer": "^6.0.3",