@phantom/browser-sdk 0.2.3 → 0.3.1

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
@@ -37,7 +37,7 @@ import { BrowserSDK, AddressType, NetworkId } from "@phantom/browser-sdk";
37
37
  const sdk = new BrowserSDK({
38
38
  providerType: "embedded",
39
39
  addressTypes: [AddressType.solana, AddressType.ethereum],
40
- apiBaseUrl: "https://api.phantom.com",
40
+ apiBaseUrl: "https://api.phantom.app/v1/wallets",
41
41
  organizationId: "your-org-id",
42
42
  });
43
43
 
@@ -80,7 +80,7 @@ Creates a non-custodial wallet embedded in your application. Requires API config
80
80
  const sdk = new BrowserSDK({
81
81
  providerType: "embedded",
82
82
  addressTypes: [AddressType.solana, AddressType.ethereum],
83
- apiBaseUrl: "https://api.phantom.com",
83
+ apiBaseUrl: "https://api.phantom.app/v1/wallets",
84
84
  organizationId: "your-org-id",
85
85
  embeddedWalletType: "app-wallet", // or 'user-wallet'
86
86
  authOptions: {
@@ -237,7 +237,6 @@ const result = await sdk.connect({
237
237
  **Authentication Flow Types:**
238
238
 
239
239
  1. **Phantom Connect (Redirect-based)**: Used when `provider` is undefined, `"google"`, or `"apple"`
240
-
241
240
  - Redirects to `https://connect.phantom.app` (or custom `authOptions.authUrl` from config)
242
241
  - Handles OAuth flow with selected provider
243
242
  - Returns to your app with authentication result using `authOptions.redirectUrl` or current page
@@ -317,17 +316,35 @@ npm install @solana/web3.js
317
316
  ```
318
317
 
319
318
  ```typescript
320
- import { Transaction, SystemProgram, PublicKey, LAMPORTS_PER_SOL } from "@solana/web3.js";
319
+ import {
320
+ VersionedTransaction,
321
+ TransactionMessage,
322
+ SystemProgram,
323
+ PublicKey,
324
+ LAMPORTS_PER_SOL,
325
+ Connection,
326
+ } from "@solana/web3.js";
321
327
  import { BrowserSDK, NetworkId } from "@phantom/browser-sdk";
322
328
 
323
- // Create native Solana transaction
324
- const transaction = new Transaction().add(
325
- SystemProgram.transfer({
326
- fromPubkey: new PublicKey(fromAddress),
327
- toPubkey: new PublicKey(toAddress),
328
- lamports: 0.001 * LAMPORTS_PER_SOL,
329
- }),
330
- );
329
+ // Get recent blockhash
330
+ const connection = new Connection("https://api.mainnet-beta.solana.com");
331
+ const { blockhash } = await connection.getLatestBlockhash();
332
+
333
+ // Create transfer instruction
334
+ const transferInstruction = SystemProgram.transfer({
335
+ fromPubkey: new PublicKey(fromAddress),
336
+ toPubkey: new PublicKey(toAddress),
337
+ lamports: 0.001 * LAMPORTS_PER_SOL,
338
+ });
339
+
340
+ // Create VersionedTransaction
341
+ const messageV0 = new TransactionMessage({
342
+ payerKey: new PublicKey(fromAddress),
343
+ recentBlockhash: blockhash,
344
+ instructions: [transferInstruction],
345
+ }).compileToV0Message();
346
+
347
+ const transaction = new VersionedTransaction(messageV0);
331
348
 
332
349
  // Send native transaction object - no encoding needed!
333
350
  const result = await sdk.signAndSendTransaction({
@@ -553,19 +570,31 @@ import { BrowserSDK, AddressType } from "@phantom/browser-sdk";
553
570
 
554
571
  const sdk = new BrowserSDK({
555
572
  addressTypes: [AddressType.solana, AddressType.ethereum, AddressType.sui],
556
- apiBaseUrl: "https://api.phantom.com",
573
+ apiBaseUrl: "https://api.phantom.app/v1/wallets",
557
574
  organizationId: "your-org-id",
558
575
  });
559
576
 
560
577
  class MultiChainWallet {
561
578
  async sendSolana(amount: number, recipient: string) {
562
- const transaction = new Transaction().add(
563
- SystemProgram.transfer({
564
- fromPubkey: new PublicKey(this.solanaAddress),
565
- toPubkey: new PublicKey(recipient),
566
- lamports: amount * LAMPORTS_PER_SOL,
567
- }),
568
- );
579
+ // Get recent blockhash
580
+ const connection = new Connection("https://api.mainnet-beta.solana.com");
581
+ const { blockhash } = await connection.getLatestBlockhash();
582
+
583
+ // Create transfer instruction
584
+ const transferInstruction = SystemProgram.transfer({
585
+ fromPubkey: new PublicKey(this.solanaAddress),
586
+ toPubkey: new PublicKey(recipient),
587
+ lamports: amount * LAMPORTS_PER_SOL,
588
+ });
589
+
590
+ // Create VersionedTransaction
591
+ const messageV0 = new TransactionMessage({
592
+ payerKey: new PublicKey(this.solanaAddress),
593
+ recentBlockhash: blockhash,
594
+ instructions: [transferInstruction],
595
+ }).compileToV0Message();
596
+
597
+ const transaction = new VersionedTransaction(messageV0);
569
598
 
570
599
  return await sdk.signAndSendTransaction({
571
600
  networkId: NetworkId.SOLANA_MAINNET,
@@ -728,7 +757,7 @@ For embedded wallets, you need to set up a backend endpoint. Add the `serverUrl`
728
757
  const sdk = new BrowserSDK({
729
758
  providerType: "embedded",
730
759
  addressTypes: [AddressType.solana],
731
- apiBaseUrl: "https://api.phantom.com",
760
+ apiBaseUrl: "https://api.phantom.app/v1/wallets",
732
761
  organizationId: "your-org-id",
733
762
  serverUrl: "http://localhost:3000/api",
734
763
  });
package/dist/index.d.ts CHANGED
@@ -1,7 +1,8 @@
1
1
  import { AddressType } from '@phantom/client';
2
- export { AddressType, NetworkId } from '@phantom/client';
3
- import { AuthOptions, ConnectResult, SignMessageParams, SignAndSendTransactionParams, SignedTransaction, WalletAddress } from '@phantom/embedded-provider-core';
4
- export { AuthOptions, ConnectResult, SignAndSendTransactionParams, SignMessageParams, SignedTransaction, WalletAddress } from '@phantom/embedded-provider-core';
2
+ export { AddressType } from '@phantom/client';
3
+ import { AuthOptions, ConnectResult, SignMessageParams, SignMessageResult, SignAndSendTransactionParams, SignedTransaction, WalletAddress } from '@phantom/embedded-provider-core';
4
+ export { AuthOptions, ConnectResult, SignAndSendTransactionParams, SignMessageParams, SignMessageResult, SignedTransaction, WalletAddress } from '@phantom/embedded-provider-core';
5
+ export { NetworkId } from '@phantom/constants';
5
6
 
6
7
  declare enum DebugLevel {
7
8
  ERROR = 0,
@@ -77,7 +78,7 @@ interface CreateUserOrganizationResult {
77
78
  interface Provider {
78
79
  connect(authOptions?: AuthOptions): Promise<ConnectResult>;
79
80
  disconnect(): Promise<void>;
80
- signMessage(params: SignMessageParams): Promise<string>;
81
+ signMessage(params: SignMessageParams): Promise<SignMessageResult>;
81
82
  signAndSendTransaction(params: SignAndSendTransactionParams): Promise<SignedTransaction>;
82
83
  getAddresses(): WalletAddress[];
83
84
  isConnected(): boolean;
@@ -125,7 +126,7 @@ declare class BrowserSDK {
125
126
  * @param networkId - Network identifier
126
127
  * @returns Signature string
127
128
  */
128
- signMessage(params: SignMessageParams): Promise<string>;
129
+ signMessage(params: SignMessageParams): Promise<SignMessageResult>;
129
130
  /**
130
131
  * Sign and send a transaction
131
132
  * @param params - Transaction parameters with native transaction object
@@ -158,4 +159,31 @@ declare class BrowserSDK {
158
159
  declare const DEFAULT_AUTH_URL = "https://connect.phantom.app";
159
160
  declare const DEFAULT_WALLET_API_URL = "https://api.phantom.app/v1/wallets";
160
161
 
161
- export { BrowserSDK, BrowserSDKConfig, CreateUserOrganizationParams, CreateUserOrganizationResult, DEFAULT_AUTH_URL, DEFAULT_WALLET_API_URL, DebugCallback, DebugCategory, DebugLevel, DebugMessage, Provider, debug };
162
+ /**
163
+ * Browser detection utility to identify browser name and version
164
+ */
165
+ interface BrowserInfo {
166
+ name: string;
167
+ version: string;
168
+ }
169
+ /**
170
+ * Parse browser information from a user agent string
171
+ * This is the core parsing logic that can be unit tested independently
172
+ */
173
+ declare function parseBrowserFromUserAgent(userAgent: string, hasBraveAPI?: boolean): BrowserInfo;
174
+ /**
175
+ * Detect the current browser and version from the user agent string
176
+ */
177
+ declare function detectBrowser(): BrowserInfo;
178
+ /**
179
+ * Get a formatted platform name for use in authenticator names
180
+ * Format: "browsername-v123" (e.g., "chrome-v120", "firefox-v119")
181
+ */
182
+ declare function getPlatformName(): string;
183
+ /**
184
+ * Get detailed browser information as a string
185
+ * Format: "Chrome 120.0" or "Firefox 119.0"
186
+ */
187
+ declare function getBrowserDisplayName(): string;
188
+
189
+ export { BrowserInfo, BrowserSDK, BrowserSDKConfig, CreateUserOrganizationParams, CreateUserOrganizationResult, DEFAULT_AUTH_URL, DEFAULT_WALLET_API_URL, DebugCallback, DebugCategory, DebugLevel, DebugMessage, Provider, debug, detectBrowser, getBrowserDisplayName, getPlatformName, parseBrowserFromUserAgent };
package/dist/index.js CHANGED
@@ -1,7 +1,9 @@
1
1
  "use strict";
2
+ var __create = Object.create;
2
3
  var __defProp = Object.defineProperty;
3
4
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
5
  var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
5
7
  var __hasOwnProp = Object.prototype.hasOwnProperty;
6
8
  var __export = (target, all) => {
7
9
  for (var name in all)
@@ -15,6 +17,14 @@ var __copyProps = (to, from, except, desc) => {
15
17
  }
16
18
  return to;
17
19
  };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
18
28
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
29
 
20
30
  // src/index.ts
@@ -26,8 +36,12 @@ __export(src_exports, {
26
36
  DEFAULT_WALLET_API_URL: () => DEFAULT_WALLET_API_URL,
27
37
  DebugCategory: () => DebugCategory,
28
38
  DebugLevel: () => DebugLevel,
29
- NetworkId: () => import_client2.NetworkId,
30
- debug: () => debug
39
+ NetworkId: () => import_constants3.NetworkId,
40
+ debug: () => debug,
41
+ detectBrowser: () => detectBrowser,
42
+ getBrowserDisplayName: () => getBrowserDisplayName,
43
+ getPlatformName: () => getPlatformName,
44
+ parseBrowserFromUserAgent: () => parseBrowserFromUserAgent
31
45
  });
32
46
  module.exports = __toCommonJS(src_exports);
33
47
 
@@ -112,6 +126,9 @@ var DebugCategory = {
112
126
  };
113
127
 
114
128
  // src/providers/injected/index.ts
129
+ var import_base64url = require("@phantom/base64url");
130
+ var import_constants = require("@phantom/constants");
131
+ var import_bs58 = __toESM(require("bs58"));
115
132
  var InjectedProvider = class {
116
133
  constructor(config) {
117
134
  this.connected = false;
@@ -210,18 +227,24 @@ var InjectedProvider = class {
210
227
  throw new Error("Wallet not connected");
211
228
  }
212
229
  const networkPrefix = params.networkId.split(":")[0].toLowerCase();
230
+ let signatureResult;
213
231
  if (networkPrefix === "solana") {
214
232
  const { signature } = await this.phantom.solana.signMessage(new TextEncoder().encode(params.message));
215
- return Array.from(signature).map((b) => b.toString(16).padStart(2, "0")).join("");
233
+ signatureResult = import_bs58.default.encode(signature);
216
234
  } else if (networkPrefix === "ethereum" || networkPrefix === "polygon" || networkPrefix === "eip155") {
217
235
  const address = this.addresses.find((addr) => addr.addressType === import_client.AddressType.ethereum)?.address;
218
236
  if (!address) {
219
237
  throw new Error("No address available");
220
238
  }
221
239
  const signature = await this.phantom.ethereum.signPersonalMessage(params.message, address);
222
- return signature;
240
+ signatureResult = signature;
241
+ } else {
242
+ throw new Error(`Network ${params.networkId} is not supported for injected wallets`);
223
243
  }
224
- throw new Error(`Network ${params.networkId} is not supported for injected wallets`);
244
+ return {
245
+ signature: signatureResult,
246
+ rawSignature: (0, import_base64url.base64urlEncode)(signatureResult)
247
+ };
225
248
  }
226
249
  async signAndSendTransaction(params) {
227
250
  if (!this.connected) {
@@ -232,7 +255,9 @@ var InjectedProvider = class {
232
255
  const transaction = params.transaction;
233
256
  const result = await this.phantom.solana.signAndSendTransaction(transaction);
234
257
  return {
235
- rawTransaction: result.signature
258
+ hash: result.signature,
259
+ rawTransaction: (0, import_base64url.base64urlEncode)(result.signature),
260
+ blockExplorer: (0, import_constants.getExplorerUrl)(params.networkId, "transaction", result.signature)
236
261
  };
237
262
  } else if (networkPrefix === "ethereum" || networkPrefix === "polygon" || networkPrefix === "eip155") {
238
263
  const toHex = (value) => {
@@ -255,7 +280,9 @@ var InjectedProvider = class {
255
280
  };
256
281
  const txHash = await this.phantom.ethereum.sendTransaction(txRequest);
257
282
  return {
258
- rawTransaction: txHash
283
+ hash: txHash,
284
+ rawTransaction: (0, import_base64url.base64urlEncode)(txHash),
285
+ blockExplorer: (0, import_constants.getExplorerUrl)(params.networkId, "transaction", txHash)
259
286
  };
260
287
  }
261
288
  throw new Error(`Network ${params.networkId} is not supported for injected wallets`);
@@ -270,6 +297,7 @@ var InjectedProvider = class {
270
297
 
271
298
  // src/providers/embedded/index.ts
272
299
  var import_embedded_provider_core = require("@phantom/embedded-provider-core");
300
+ var import_indexed_db_stamper = require("@phantom/indexed-db-stamper");
273
301
 
274
302
  // src/providers/embedded/adapters/storage.ts
275
303
  var BrowserStorage = class {
@@ -495,16 +523,133 @@ var BrowserLogger = class {
495
523
  }
496
524
  };
497
525
 
526
+ // src/utils/browser-detection.ts
527
+ function parseBrowserFromUserAgent(userAgent, hasBraveAPI) {
528
+ let name = "unknown";
529
+ let version = "unknown";
530
+ if (!userAgent || typeof userAgent !== "string") {
531
+ return { name, version };
532
+ }
533
+ try {
534
+ if (userAgent.includes("Edg/")) {
535
+ name = "edge";
536
+ const match = userAgent.match(/Edg\/([0-9]+(?:\.[0-9]+)*)/);
537
+ if (match)
538
+ version = match[1].split(".")[0];
539
+ } else if (userAgent.includes("OPR/") || userAgent.includes("Opera/")) {
540
+ name = "opera";
541
+ const match = userAgent.match(/(?:OPR|Opera)\/([0-9]+(?:\.[0-9]+)*)/);
542
+ if (match)
543
+ version = match[1].split(".")[0];
544
+ } else if (userAgent.includes("SamsungBrowser/")) {
545
+ name = "samsung";
546
+ const match = userAgent.match(/SamsungBrowser\/([0-9]+(?:\.[0-9]+)*)/);
547
+ if (match)
548
+ version = match[1].split(".")[0];
549
+ } else if (userAgent.includes("DuckDuckGo/")) {
550
+ name = "duckduckgo";
551
+ const match = userAgent.match(/DuckDuckGo\/([0-9]+(?:\.[0-9]+)*)/);
552
+ if (match)
553
+ version = match[1].split(".")[0];
554
+ } else if (userAgent.includes("Chrome/") && hasBraveAPI) {
555
+ name = "brave";
556
+ const match = userAgent.match(/Chrome\/([0-9]+(?:\.[0-9]+)*)/);
557
+ if (match)
558
+ version = match[1].split(".")[0];
559
+ } else if (userAgent.includes("Mobile/") || userAgent.includes("Android")) {
560
+ if (userAgent.includes("Chrome/")) {
561
+ name = "chrome-mobile";
562
+ const match = userAgent.match(/Chrome\/([0-9]+(?:\.[0-9]+)*)/);
563
+ if (match)
564
+ version = match[1].split(".")[0];
565
+ } else if (userAgent.includes("Firefox/")) {
566
+ name = "firefox-mobile";
567
+ const match = userAgent.match(/Firefox\/([0-9]+(?:\.[0-9]+)*)/);
568
+ if (match)
569
+ version = match[1].split(".")[0];
570
+ } else if (userAgent.includes("Safari/") && userAgent.includes("Mobile/")) {
571
+ name = "safari-mobile";
572
+ const match = userAgent.match(/Version\/([0-9]+(?:\.[0-9]+)*)/);
573
+ if (match)
574
+ version = match[1].split(".")[0];
575
+ } else {
576
+ name = "mobile";
577
+ }
578
+ } else if (userAgent.includes("Chrome/")) {
579
+ name = "chrome";
580
+ const match = userAgent.match(/Chrome\/([0-9]+(?:\.[0-9]+)*)/);
581
+ if (match)
582
+ version = match[1].split(".")[0];
583
+ } else if (userAgent.includes("Firefox/")) {
584
+ name = "firefox";
585
+ const match = userAgent.match(/Firefox\/([0-9]+(?:\.[0-9]+)*)/);
586
+ if (match)
587
+ version = match[1].split(".")[0];
588
+ } else if (userAgent.includes("Safari/") && !userAgent.includes("Chrome/")) {
589
+ name = "safari";
590
+ const match = userAgent.match(/Version\/([0-9]+(?:\.[0-9]+)*)/);
591
+ if (match)
592
+ version = match[1].split(".")[0];
593
+ }
594
+ if (name === "unknown") {
595
+ const patterns = [
596
+ { regex: /Chrome\/([0-9]+)/, name: "chrome" },
597
+ { regex: /Firefox\/([0-9]+)/, name: "firefox" },
598
+ { regex: /Safari\/([0-9]+)/, name: "safari" },
599
+ { regex: /Edge\/([0-9]+)/, name: "edge" },
600
+ { regex: /Opera\/([0-9]+)/, name: "opera" }
601
+ ];
602
+ for (const pattern of patterns) {
603
+ const match = userAgent.match(pattern.regex);
604
+ if (match) {
605
+ name = pattern.name;
606
+ version = match[1];
607
+ break;
608
+ }
609
+ }
610
+ }
611
+ } catch (error) {
612
+ }
613
+ return { name, version };
614
+ }
615
+ function detectBrowser() {
616
+ if (typeof window === "undefined" || !window.navigator?.userAgent) {
617
+ return { name: "unknown", version: "unknown" };
618
+ }
619
+ const userAgent = window.navigator.userAgent;
620
+ const hasBraveAPI = !!navigator.brave;
621
+ return parseBrowserFromUserAgent(userAgent, hasBraveAPI);
622
+ }
623
+ function getPlatformName() {
624
+ const { name, version } = detectBrowser();
625
+ return version !== "unknown" ? `${name}-v${version}` : name;
626
+ }
627
+ function getBrowserDisplayName() {
628
+ const { name, version } = detectBrowser();
629
+ const capitalizedName = name.charAt(0).toUpperCase() + name.slice(1);
630
+ return version !== "unknown" ? `${capitalizedName} ${version}` : capitalizedName;
631
+ }
632
+
498
633
  // src/providers/embedded/index.ts
499
634
  var EmbeddedProvider = class extends import_embedded_provider_core.EmbeddedProvider {
500
635
  constructor(config) {
501
636
  debug.log(DebugCategory.EMBEDDED_PROVIDER, "Initializing Browser EmbeddedProvider", { config });
502
637
  const urlParamsAccessor = new BrowserURLParamsAccessor();
638
+ const stamper = new import_indexed_db_stamper.IndexedDbStamper({
639
+ dbName: `phantom-embedded-sdk-${config.organizationId}`,
640
+ storeName: "crypto-keys",
641
+ keyName: "signing-key"
642
+ });
643
+ const platformName = getPlatformName();
503
644
  const platform = {
504
645
  storage: new BrowserStorage(),
505
646
  authProvider: new BrowserAuthProvider(urlParamsAccessor),
506
- urlParamsAccessor
647
+ urlParamsAccessor,
648
+ stamper,
649
+ name: platformName
650
+ // Use detected browser name and version for identification
507
651
  };
652
+ debug.log(DebugCategory.EMBEDDED_PROVIDER, "Detected platform", { platformName });
508
653
  const logger = new BrowserLogger();
509
654
  super(config, platform, logger);
510
655
  debug.info(DebugCategory.EMBEDDED_PROVIDER, "Browser EmbeddedProvider initialized");
@@ -841,7 +986,17 @@ var BrowserSDK = class {
841
986
  * @returns Signature string
842
987
  */
843
988
  async signMessage(params) {
844
- return this.providerManager.signMessage(params);
989
+ debug.info(DebugCategory.BROWSER_SDK, "Signing message", {
990
+ message: params.message,
991
+ networkId: params.networkId
992
+ });
993
+ const result = await this.providerManager.signMessage(params);
994
+ debug.info(DebugCategory.BROWSER_SDK, "Message signed successfully", {
995
+ message: params.message,
996
+ networkId: params.networkId,
997
+ result
998
+ });
999
+ return result;
845
1000
  }
846
1001
  /**
847
1002
  * Sign and send a transaction
@@ -849,7 +1004,15 @@ var BrowserSDK = class {
849
1004
  * @returns Transaction result
850
1005
  */
851
1006
  async signAndSendTransaction(params) {
852
- return this.providerManager.signAndSendTransaction(params);
1007
+ debug.info(DebugCategory.BROWSER_SDK, "Signing and sending transaction", {
1008
+ networkId: params.networkId
1009
+ });
1010
+ const result = await this.providerManager.signAndSendTransaction(params);
1011
+ debug.info(DebugCategory.BROWSER_SDK, "Transaction signed and sent successfully", {
1012
+ networkId: params.networkId,
1013
+ result
1014
+ });
1015
+ return result;
853
1016
  }
854
1017
  /**
855
1018
  * Get wallet addresses
@@ -898,4 +1061,5 @@ var BrowserSDK = class {
898
1061
  };
899
1062
 
900
1063
  // src/index.ts
1064
+ var import_constants3 = require("@phantom/constants");
901
1065
  var import_client2 = require("@phantom/client");
package/dist/index.mjs CHANGED
@@ -79,6 +79,9 @@ var DebugCategory = {
79
79
  };
80
80
 
81
81
  // src/providers/injected/index.ts
82
+ import { base64urlEncode } from "@phantom/base64url";
83
+ import { getExplorerUrl } from "@phantom/constants";
84
+ import bs58 from "bs58";
82
85
  var InjectedProvider = class {
83
86
  constructor(config) {
84
87
  this.connected = false;
@@ -177,18 +180,24 @@ var InjectedProvider = class {
177
180
  throw new Error("Wallet not connected");
178
181
  }
179
182
  const networkPrefix = params.networkId.split(":")[0].toLowerCase();
183
+ let signatureResult;
180
184
  if (networkPrefix === "solana") {
181
185
  const { signature } = await this.phantom.solana.signMessage(new TextEncoder().encode(params.message));
182
- return Array.from(signature).map((b) => b.toString(16).padStart(2, "0")).join("");
186
+ signatureResult = bs58.encode(signature);
183
187
  } else if (networkPrefix === "ethereum" || networkPrefix === "polygon" || networkPrefix === "eip155") {
184
188
  const address = this.addresses.find((addr) => addr.addressType === AddressType.ethereum)?.address;
185
189
  if (!address) {
186
190
  throw new Error("No address available");
187
191
  }
188
192
  const signature = await this.phantom.ethereum.signPersonalMessage(params.message, address);
189
- return signature;
193
+ signatureResult = signature;
194
+ } else {
195
+ throw new Error(`Network ${params.networkId} is not supported for injected wallets`);
190
196
  }
191
- throw new Error(`Network ${params.networkId} is not supported for injected wallets`);
197
+ return {
198
+ signature: signatureResult,
199
+ rawSignature: base64urlEncode(signatureResult)
200
+ };
192
201
  }
193
202
  async signAndSendTransaction(params) {
194
203
  if (!this.connected) {
@@ -199,7 +208,9 @@ var InjectedProvider = class {
199
208
  const transaction = params.transaction;
200
209
  const result = await this.phantom.solana.signAndSendTransaction(transaction);
201
210
  return {
202
- rawTransaction: result.signature
211
+ hash: result.signature,
212
+ rawTransaction: base64urlEncode(result.signature),
213
+ blockExplorer: getExplorerUrl(params.networkId, "transaction", result.signature)
203
214
  };
204
215
  } else if (networkPrefix === "ethereum" || networkPrefix === "polygon" || networkPrefix === "eip155") {
205
216
  const toHex = (value) => {
@@ -222,7 +233,9 @@ var InjectedProvider = class {
222
233
  };
223
234
  const txHash = await this.phantom.ethereum.sendTransaction(txRequest);
224
235
  return {
225
- rawTransaction: txHash
236
+ hash: txHash,
237
+ rawTransaction: base64urlEncode(txHash),
238
+ blockExplorer: getExplorerUrl(params.networkId, "transaction", txHash)
226
239
  };
227
240
  }
228
241
  throw new Error(`Network ${params.networkId} is not supported for injected wallets`);
@@ -237,6 +250,7 @@ var InjectedProvider = class {
237
250
 
238
251
  // src/providers/embedded/index.ts
239
252
  import { EmbeddedProvider as CoreEmbeddedProvider } from "@phantom/embedded-provider-core";
253
+ import { IndexedDbStamper } from "@phantom/indexed-db-stamper";
240
254
 
241
255
  // src/providers/embedded/adapters/storage.ts
242
256
  var BrowserStorage = class {
@@ -462,16 +476,133 @@ var BrowserLogger = class {
462
476
  }
463
477
  };
464
478
 
479
+ // src/utils/browser-detection.ts
480
+ function parseBrowserFromUserAgent(userAgent, hasBraveAPI) {
481
+ let name = "unknown";
482
+ let version = "unknown";
483
+ if (!userAgent || typeof userAgent !== "string") {
484
+ return { name, version };
485
+ }
486
+ try {
487
+ if (userAgent.includes("Edg/")) {
488
+ name = "edge";
489
+ const match = userAgent.match(/Edg\/([0-9]+(?:\.[0-9]+)*)/);
490
+ if (match)
491
+ version = match[1].split(".")[0];
492
+ } else if (userAgent.includes("OPR/") || userAgent.includes("Opera/")) {
493
+ name = "opera";
494
+ const match = userAgent.match(/(?:OPR|Opera)\/([0-9]+(?:\.[0-9]+)*)/);
495
+ if (match)
496
+ version = match[1].split(".")[0];
497
+ } else if (userAgent.includes("SamsungBrowser/")) {
498
+ name = "samsung";
499
+ const match = userAgent.match(/SamsungBrowser\/([0-9]+(?:\.[0-9]+)*)/);
500
+ if (match)
501
+ version = match[1].split(".")[0];
502
+ } else if (userAgent.includes("DuckDuckGo/")) {
503
+ name = "duckduckgo";
504
+ const match = userAgent.match(/DuckDuckGo\/([0-9]+(?:\.[0-9]+)*)/);
505
+ if (match)
506
+ version = match[1].split(".")[0];
507
+ } else if (userAgent.includes("Chrome/") && hasBraveAPI) {
508
+ name = "brave";
509
+ const match = userAgent.match(/Chrome\/([0-9]+(?:\.[0-9]+)*)/);
510
+ if (match)
511
+ version = match[1].split(".")[0];
512
+ } else if (userAgent.includes("Mobile/") || userAgent.includes("Android")) {
513
+ if (userAgent.includes("Chrome/")) {
514
+ name = "chrome-mobile";
515
+ const match = userAgent.match(/Chrome\/([0-9]+(?:\.[0-9]+)*)/);
516
+ if (match)
517
+ version = match[1].split(".")[0];
518
+ } else if (userAgent.includes("Firefox/")) {
519
+ name = "firefox-mobile";
520
+ const match = userAgent.match(/Firefox\/([0-9]+(?:\.[0-9]+)*)/);
521
+ if (match)
522
+ version = match[1].split(".")[0];
523
+ } else if (userAgent.includes("Safari/") && userAgent.includes("Mobile/")) {
524
+ name = "safari-mobile";
525
+ const match = userAgent.match(/Version\/([0-9]+(?:\.[0-9]+)*)/);
526
+ if (match)
527
+ version = match[1].split(".")[0];
528
+ } else {
529
+ name = "mobile";
530
+ }
531
+ } else if (userAgent.includes("Chrome/")) {
532
+ name = "chrome";
533
+ const match = userAgent.match(/Chrome\/([0-9]+(?:\.[0-9]+)*)/);
534
+ if (match)
535
+ version = match[1].split(".")[0];
536
+ } else if (userAgent.includes("Firefox/")) {
537
+ name = "firefox";
538
+ const match = userAgent.match(/Firefox\/([0-9]+(?:\.[0-9]+)*)/);
539
+ if (match)
540
+ version = match[1].split(".")[0];
541
+ } else if (userAgent.includes("Safari/") && !userAgent.includes("Chrome/")) {
542
+ name = "safari";
543
+ const match = userAgent.match(/Version\/([0-9]+(?:\.[0-9]+)*)/);
544
+ if (match)
545
+ version = match[1].split(".")[0];
546
+ }
547
+ if (name === "unknown") {
548
+ const patterns = [
549
+ { regex: /Chrome\/([0-9]+)/, name: "chrome" },
550
+ { regex: /Firefox\/([0-9]+)/, name: "firefox" },
551
+ { regex: /Safari\/([0-9]+)/, name: "safari" },
552
+ { regex: /Edge\/([0-9]+)/, name: "edge" },
553
+ { regex: /Opera\/([0-9]+)/, name: "opera" }
554
+ ];
555
+ for (const pattern of patterns) {
556
+ const match = userAgent.match(pattern.regex);
557
+ if (match) {
558
+ name = pattern.name;
559
+ version = match[1];
560
+ break;
561
+ }
562
+ }
563
+ }
564
+ } catch (error) {
565
+ }
566
+ return { name, version };
567
+ }
568
+ function detectBrowser() {
569
+ if (typeof window === "undefined" || !window.navigator?.userAgent) {
570
+ return { name: "unknown", version: "unknown" };
571
+ }
572
+ const userAgent = window.navigator.userAgent;
573
+ const hasBraveAPI = !!navigator.brave;
574
+ return parseBrowserFromUserAgent(userAgent, hasBraveAPI);
575
+ }
576
+ function getPlatformName() {
577
+ const { name, version } = detectBrowser();
578
+ return version !== "unknown" ? `${name}-v${version}` : name;
579
+ }
580
+ function getBrowserDisplayName() {
581
+ const { name, version } = detectBrowser();
582
+ const capitalizedName = name.charAt(0).toUpperCase() + name.slice(1);
583
+ return version !== "unknown" ? `${capitalizedName} ${version}` : capitalizedName;
584
+ }
585
+
465
586
  // src/providers/embedded/index.ts
466
587
  var EmbeddedProvider = class extends CoreEmbeddedProvider {
467
588
  constructor(config) {
468
589
  debug.log(DebugCategory.EMBEDDED_PROVIDER, "Initializing Browser EmbeddedProvider", { config });
469
590
  const urlParamsAccessor = new BrowserURLParamsAccessor();
591
+ const stamper = new IndexedDbStamper({
592
+ dbName: `phantom-embedded-sdk-${config.organizationId}`,
593
+ storeName: "crypto-keys",
594
+ keyName: "signing-key"
595
+ });
596
+ const platformName = getPlatformName();
470
597
  const platform = {
471
598
  storage: new BrowserStorage(),
472
599
  authProvider: new BrowserAuthProvider(urlParamsAccessor),
473
- urlParamsAccessor
600
+ urlParamsAccessor,
601
+ stamper,
602
+ name: platformName
603
+ // Use detected browser name and version for identification
474
604
  };
605
+ debug.log(DebugCategory.EMBEDDED_PROVIDER, "Detected platform", { platformName });
475
606
  const logger = new BrowserLogger();
476
607
  super(config, platform, logger);
477
608
  debug.info(DebugCategory.EMBEDDED_PROVIDER, "Browser EmbeddedProvider initialized");
@@ -808,7 +939,17 @@ var BrowserSDK = class {
808
939
  * @returns Signature string
809
940
  */
810
941
  async signMessage(params) {
811
- return this.providerManager.signMessage(params);
942
+ debug.info(DebugCategory.BROWSER_SDK, "Signing message", {
943
+ message: params.message,
944
+ networkId: params.networkId
945
+ });
946
+ const result = await this.providerManager.signMessage(params);
947
+ debug.info(DebugCategory.BROWSER_SDK, "Message signed successfully", {
948
+ message: params.message,
949
+ networkId: params.networkId,
950
+ result
951
+ });
952
+ return result;
812
953
  }
813
954
  /**
814
955
  * Sign and send a transaction
@@ -816,7 +957,15 @@ var BrowserSDK = class {
816
957
  * @returns Transaction result
817
958
  */
818
959
  async signAndSendTransaction(params) {
819
- return this.providerManager.signAndSendTransaction(params);
960
+ debug.info(DebugCategory.BROWSER_SDK, "Signing and sending transaction", {
961
+ networkId: params.networkId
962
+ });
963
+ const result = await this.providerManager.signAndSendTransaction(params);
964
+ debug.info(DebugCategory.BROWSER_SDK, "Transaction signed and sent successfully", {
965
+ networkId: params.networkId,
966
+ result
967
+ });
968
+ return result;
820
969
  }
821
970
  /**
822
971
  * Get wallet addresses
@@ -865,7 +1014,8 @@ var BrowserSDK = class {
865
1014
  };
866
1015
 
867
1016
  // src/index.ts
868
- import { NetworkId, AddressType as AddressType2 } from "@phantom/client";
1017
+ import { NetworkId } from "@phantom/constants";
1018
+ import { AddressType as AddressType2 } from "@phantom/client";
869
1019
  export {
870
1020
  AddressType2 as AddressType,
871
1021
  BrowserSDK,
@@ -874,5 +1024,9 @@ export {
874
1024
  DebugCategory,
875
1025
  DebugLevel,
876
1026
  NetworkId,
877
- debug
1027
+ debug,
1028
+ detectBrowser,
1029
+ getBrowserDisplayName,
1030
+ getPlatformName,
1031
+ parseBrowserFromUserAgent
878
1032
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@phantom/browser-sdk",
3
- "version": "0.2.3",
3
+ "version": "0.3.1",
4
4
  "description": "Browser SDK for Phantom Wallet with unified interface",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",
@@ -24,15 +24,17 @@
24
24
  "test": "jest",
25
25
  "test:watch": "jest --watch",
26
26
  "lint": "tsc --noEmit && eslint --cache . --ext .ts,.tsx",
27
+ "check-types": "tsc --noEmit",
27
28
  "prettier": "prettier --write \"src/**/*.{ts,tsx}\""
28
29
  },
29
30
  "dependencies": {
30
- "@phantom/api-key-stamper": "^0.1.1",
31
31
  "@phantom/base64url": "^0.1.0",
32
32
  "@phantom/browser-injected-sdk": "^0.0.9",
33
- "@phantom/client": "^0.1.3",
34
- "@phantom/embedded-provider-core": "^0.1.1",
35
- "@phantom/parsers": "^0.0.4",
33
+ "@phantom/client": "^0.1.5",
34
+ "@phantom/constants": "^0.0.2",
35
+ "@phantom/embedded-provider-core": "^0.1.2",
36
+ "@phantom/indexed-db-stamper": "^0.1.1",
37
+ "@phantom/parsers": "^0.0.5",
36
38
  "axios": "^1.10.0",
37
39
  "bs58": "^6.0.0",
38
40
  "buffer": "^6.0.3",