@phantom/browser-sdk
Advanced tools
Comparing version
import { ChainPlugin } from '../index.js'; | ||
import { PublicKey, Transaction, VersionedTransaction, SendOptions } from '@solana/web3.js'; | ||
import { Transaction as Transaction$1 } from '@solana/kit'; | ||
type Transaction = { | ||
message: Uint8Array; | ||
recentBlockhash: string; | ||
feePayer: string; | ||
instructions: any[]; | ||
signers: string[]; | ||
version: number; | ||
}; | ||
type VersionedTransaction = { | ||
signatures: Uint8Array[]; | ||
message: { | ||
deserialize: (serializedTransaction: Uint8Array) => VersionedTransaction; | ||
serialize: (transaction: VersionedTransaction) => Uint8Array; | ||
}; | ||
}; | ||
type SendOptions = { | ||
skipPreflight?: boolean; | ||
preflightCommitment?: string; | ||
maxRetries?: number; | ||
minContextSlot?: number; | ||
}; | ||
type PublicKey = { | ||
toString: () => string; | ||
toBase58: () => string; | ||
}; | ||
type SolanaSignInData = { | ||
@@ -19,2 +44,3 @@ domain?: string; | ||
type DisplayEncoding = "utf8" | "hex"; | ||
type PhantomEventType = "connect" | "disconnect" | "accountChanged"; | ||
interface PhantomSolanaProvider { | ||
@@ -45,6 +71,5 @@ isPhantom: boolean; | ||
signTransaction: (transaction: Transaction | VersionedTransaction) => Promise<Transaction | VersionedTransaction>; | ||
on: (event: "connect" | "disconnect" | "accountChanged", handler: (publicKey?: PublicKey) => void) => void; | ||
off: (event: "connect" | "disconnect" | "accountChanged", handler: (publicKey?: PublicKey) => void) => void; | ||
} | ||
interface SolanaOperationOptions { | ||
getProvider?: () => PhantomSolanaProvider | null; | ||
} | ||
@@ -55,9 +80,8 @@ /** | ||
* @param display The display encoding for the message (optional, defaults to utf8). | ||
* @param options Optional parameters, including a custom getProvider function. | ||
* @returns A promise that resolves with the signature and public key. | ||
* @throws Error if Phantom provider is not found or if the operation fails. | ||
*/ | ||
declare function signMessage(message: Uint8Array, display?: DisplayEncoding, options?: SolanaOperationOptions): Promise<{ | ||
declare function signMessage(message: Uint8Array, display?: DisplayEncoding): Promise<{ | ||
signature: Uint8Array; | ||
publicKey: PublicKey; | ||
address: string; | ||
}>; | ||
@@ -68,8 +92,7 @@ | ||
* @param signInData The sign-in data. | ||
* @param options Optional parameters, including a custom getProvider function. | ||
* @returns A promise that resolves with the address, signature, and signed message. | ||
* @throws Error if Phantom provider is not found or if the operation fails. | ||
*/ | ||
declare function signIn(signInData: SolanaSignInData, options?: SolanaOperationOptions): Promise<{ | ||
address: PublicKey; | ||
declare function signIn(signInData: SolanaSignInData): Promise<{ | ||
address: string; | ||
signature: Uint8Array; | ||
@@ -82,16 +105,38 @@ signedMessage: Uint8Array; | ||
* @param transaction The transaction to sign and send. | ||
* @param options Options for sending the transaction and other operations. | ||
* @returns A promise that resolves with the transaction signature and optionally the public key. | ||
* @throws Error if Phantom provider is not found or if the operation fails. | ||
*/ | ||
declare function signAndSendTransaction(transaction: Transaction | VersionedTransaction, operationOptions?: SolanaOperationOptions): Promise<{ | ||
declare function signAndSendTransaction(transaction: Transaction$1): Promise<{ | ||
signature: string; | ||
publicKey?: string; | ||
address?: string; | ||
}>; | ||
declare function connect(): Promise<string | undefined>; | ||
declare function disconnect(): Promise<void>; | ||
type ConnectCallback = (publicKey: string) => void; | ||
type DisconnectCallback = () => void; | ||
type AccountChangedCallback = (publicKey: string) => void; | ||
type PhantomEventCallback = ConnectCallback | DisconnectCallback | AccountChangedCallback; | ||
type GetAccountResult = { | ||
status: "connected"; | ||
address: string; | ||
} | { | ||
status: "disconnected"; | ||
address: null; | ||
}; | ||
declare function getAccount(): GetAccountResult; | ||
type Solana = { | ||
getProvider: () => PhantomSolanaProvider | null; | ||
connect: typeof connect; | ||
disconnect: typeof disconnect; | ||
getAccount: typeof getAccount; | ||
signMessage: typeof signMessage; | ||
signIn: typeof signIn; | ||
signAndSendTransaction: typeof signAndSendTransaction; | ||
addEventListener: (event: PhantomEventType, callback: PhantomEventCallback) => () => void; | ||
removeEventListener: (event: PhantomEventType, callback: PhantomEventCallback) => void; | ||
}; | ||
@@ -106,2 +151,2 @@ declare function createSolanaPlugin(): ChainPlugin<Solana>; | ||
export { createSolanaPlugin }; | ||
export { PhantomSolanaProvider, SolanaSignInData, createSolanaPlugin }; |
@@ -32,9 +32,74 @@ "use strict"; | ||
// src/solana/eventListeners.ts | ||
var eventCallbacks = /* @__PURE__ */ new Map(); | ||
function addEventListener(event, callback) { | ||
if (!eventCallbacks.has(event)) { | ||
eventCallbacks.set(event, /* @__PURE__ */ new Set()); | ||
} | ||
eventCallbacks.get(event).add(callback); | ||
return () => { | ||
removeEventListener(event, callback); | ||
}; | ||
} | ||
function removeEventListener(event, callback) { | ||
if (eventCallbacks.has(event)) { | ||
eventCallbacks.get(event).delete(callback); | ||
if (eventCallbacks.get(event).size === 0) { | ||
eventCallbacks.delete(event); | ||
} | ||
} | ||
} | ||
function triggerEvent(event, ...args) { | ||
if (eventCallbacks.has(event)) { | ||
eventCallbacks.get(event).forEach((cb) => { | ||
if (event === "connect" && args[0] && typeof args[0] === "string") { | ||
cb(args[0]); | ||
} else if (event === "disconnect") { | ||
cb(); | ||
} else if (event === "accountChanged" && args[0] && typeof args[0] === "string") { | ||
cb(args[0]); | ||
} | ||
}); | ||
} | ||
} | ||
// src/solana/connect.ts | ||
async function connect() { | ||
const provider = getProvider(); | ||
if (!provider) { | ||
throw new Error("Phantom provider not found."); | ||
} | ||
if (provider.isConnected) { | ||
return provider.publicKey?.toString(); | ||
} | ||
try { | ||
const eagerConnectResult = await provider.connect({ onlyIfTrusted: true }); | ||
if (eagerConnectResult.publicKey) { | ||
const publicKeyStr = eagerConnectResult.publicKey.toString(); | ||
triggerEvent("connect", publicKeyStr); | ||
return publicKeyStr; | ||
} | ||
} catch (error) { | ||
} | ||
try { | ||
const connectResult = await provider.connect({ onlyIfTrusted: false }); | ||
if (connectResult.publicKey) { | ||
const publicKeyStr = connectResult.publicKey.toString(); | ||
triggerEvent("connect", publicKeyStr); | ||
return publicKeyStr; | ||
} | ||
} catch (error) { | ||
} | ||
throw new Error("Failed to connect to Phantom."); | ||
} | ||
// src/solana/signMessage.ts | ||
async function signMessage(message, display, options) { | ||
const getProviderFn = options?.getProvider || getProvider; | ||
const provider = getProviderFn(); | ||
async function signMessage(message, display) { | ||
const provider = getProvider(); | ||
if (!provider) { | ||
throw new Error("Phantom provider not found."); | ||
} | ||
if (!provider.isConnected) { | ||
await connect(); | ||
} | ||
if (!provider.signMessage) { | ||
@@ -44,11 +109,14 @@ throw new Error("The connected provider does not support signMessage."); | ||
if (!provider.isConnected) { | ||
throw new Error("Provider is not connected."); | ||
throw new Error("Provider is not connected even after attempting to connect."); | ||
} | ||
return provider.signMessage(message, display); | ||
const result = await provider.signMessage(message, display); | ||
return { | ||
signature: result.signature, | ||
address: result.publicKey.toString() | ||
}; | ||
} | ||
// src/solana/signIn.ts | ||
async function signIn(signInData, options) { | ||
const getProviderFn = options?.getProvider || getProvider; | ||
const provider = getProviderFn(); | ||
async function signIn(signInData) { | ||
const provider = getProvider(); | ||
if (!provider) { | ||
@@ -60,12 +128,31 @@ throw new Error("Phantom provider not found."); | ||
} | ||
return provider.signIn(signInData); | ||
const result = await provider.signIn(signInData); | ||
return { | ||
address: result.address.toString(), | ||
signature: result.signature, | ||
signedMessage: result.signedMessage | ||
}; | ||
} | ||
// src/solana/utils/transactionToVersionedTransaction.ts | ||
var import_transactions = require("@solana/transactions"); | ||
function transactionToVersionedTransaction(transaction) { | ||
const serialized = (0, import_transactions.getTransactionEncoder)().encode(transaction); | ||
const fakeVersioned = { | ||
serialize() { | ||
return new Uint8Array(serialized); | ||
} | ||
}; | ||
return fakeVersioned; | ||
} | ||
// src/solana/signAndSendTransaction.ts | ||
async function signAndSendTransaction(transaction, operationOptions) { | ||
const getProviderFn = operationOptions?.getProvider || getProvider; | ||
const provider = getProviderFn(); | ||
async function signAndSendTransaction(transaction) { | ||
const provider = getProvider(); | ||
if (!provider) { | ||
throw new Error("Phantom provider not found."); | ||
} | ||
if (!provider.isConnected) { | ||
await connect(); | ||
} | ||
if (!provider.signAndSendTransaction) { | ||
@@ -75,13 +162,48 @@ throw new Error("The connected provider does not support signAndSendTransaction."); | ||
if (!provider.isConnected) { | ||
throw new Error("Provider is not connected."); | ||
throw new Error("Provider is not connected even after attempting to connect."); | ||
} | ||
return provider.signAndSendTransaction(transaction); | ||
const versionedTransaction = transactionToVersionedTransaction(transaction); | ||
const result = await provider.signAndSendTransaction(versionedTransaction); | ||
return { | ||
signature: result.signature, | ||
address: result.publicKey | ||
}; | ||
} | ||
// src/solana/disconnect.ts | ||
async function disconnect() { | ||
const provider = getProvider(); | ||
if (!provider) { | ||
throw new Error("Phantom provider not found."); | ||
} | ||
await provider.disconnect(); | ||
triggerEvent("disconnect"); | ||
} | ||
// src/solana/getAccount.ts | ||
function getAccount() { | ||
const provider = getProvider(); | ||
if (provider && provider.isConnected && provider.publicKey) { | ||
return { | ||
status: "connected", | ||
address: provider.publicKey.toString() | ||
}; | ||
} | ||
return { | ||
status: "disconnected", | ||
address: null | ||
}; | ||
} | ||
// src/solana/plugin.ts | ||
var solana = { | ||
getProvider, | ||
connect, | ||
disconnect, | ||
getAccount, | ||
signMessage, | ||
signIn, | ||
signAndSendTransaction | ||
signAndSendTransaction, | ||
addEventListener, | ||
removeEventListener | ||
}; | ||
@@ -88,0 +210,0 @@ function createSolanaPlugin() { |
{ | ||
"name": "@phantom/browser-sdk", | ||
"version": "0.0.3", | ||
"version": "0.0.4", | ||
"main": "dist/index.js", | ||
@@ -25,2 +25,3 @@ "module": "dist/index.mjs", | ||
"build": "rm -rf dist && tsup src/index.ts src/solana/index.ts --format cjs,esm --dts", | ||
"build:watch": "rm -rf dist && tsup src/index.ts src/solana/index.ts --format cjs,esm --dts --watch", | ||
"dev": "rm -rf dist && tsup src/index.ts src/solana/index.ts --format cjs,esm --dts --watch", | ||
@@ -31,2 +32,3 @@ "lint": "tsc --noEmit && eslint --cache . --ext .ts,.tsx", | ||
"devDependencies": { | ||
"@solana/web3.js": "^1.98.2", | ||
"eslint": "8.53.0", | ||
@@ -37,4 +39,6 @@ "tsup": "^6.7.0", | ||
"dependencies": { | ||
"@solana/web3.js": "^1.98.2" | ||
"@solana/compat": "2.1.1", | ||
"@solana/kit": "^2.1.1", | ||
"@solana/transactions": "^2.1.1" | ||
} | ||
} |
169
README.md
# Phantom Browser SDK | ||
WIP | ||
The Phantom Browser SDK allows you to interact with the Phantom wallet from your web application. It uses a plugin system to support different blockchains. | ||
## Installation | ||
You can install the SDK using npm or yarn: | ||
**npm:** | ||
```bash | ||
npm install @phantom/browser-sdk | ||
``` | ||
**yarn:** | ||
```bash | ||
yarn add @phantom/browser-sdk | ||
``` | ||
## Usage | ||
Here's an example of how to import and use the SDK with the Solana plugin: | ||
```typescript | ||
import { createPhantom } from "@phantom/browser-sdk"; | ||
import { createSolanaPlugin } from "@phantom/browser-sdk/solana"; // Import the solana plugin | ||
// Create a Phantom instance with the Solana plugin | ||
const phantom = createPhantom({ | ||
chainPlugins: [createSolanaPlugin()], | ||
}); | ||
// Now you can use the Solana-specific methods | ||
async function connectAndSign() { | ||
try { | ||
// Get the Solana provider (Phantom wallet instance) | ||
const provider = phantom.solana.getProvider(); | ||
if (!provider) { | ||
console.error("Phantom wallet not found. Please install Phantom."); | ||
return; | ||
} | ||
// Attempt to connect to the wallet | ||
const connectionResult = await phantom.solana.connect(); | ||
console.log("Connection Result:", connectionResult.address); | ||
// Example: Sign in (if supported by the specific provider/plugin) | ||
// Construct SolanaSignInData according to your needs | ||
const signInData = { domain: window.location.host, statement: "Please sign in to access this dApp." }; | ||
const signInResult = await phantom.solana.signIn(signInData); | ||
console.log("Sign In Result:", signInResult.address); | ||
// Example: Sign a message | ||
const message = new TextEncoder().encode("Hello from Phantom Browser SDK!"); | ||
const signedMessage = await phantom.solana.signMessage(message, "utf8"); | ||
console.log("Signed Message:", signedMessage); | ||
} catch (error) { | ||
console.error("Error interacting with Phantom:", error); | ||
} | ||
} | ||
connectAndSign(); | ||
``` | ||
### Available Solana Methods | ||
Once the `phantom.solana` object is initialized, you can access the following methods: | ||
- `getProvider(): PhantomSolanaProvider | null` | ||
- Retrieves the Phantom Solana provider instance. | ||
- `connect(opts?: { onlyIfTrusted?: boolean }): Promise<string>` | ||
- Connects to the Phantom wallet. Optionally, `onlyIfTrusted` can be set to true to only connect if the dApp is already trusted. | ||
- `disconnect(): Promise<void>` | ||
- Disconnects from the Phantom wallet. | ||
- `getAccount(): { status: "connected" | "disconnected"; address: string | null }` | ||
- Gets the current connected account state. When account is connected returns a public key, when it's not returns it as null. | ||
- `signIn(): Promise<SignInResult>` | ||
- Initiates a sign-in request to the wallet. | ||
- `signMessage(message: Uint8Array | string, display?: 'utf8' | 'hex'): Promise<SignedMessage>` | ||
- Prompts the user to sign a given message. | ||
- `signAndSendTransaction(transaction: Transaction): Promise<{ signature: string; address?: string }>` | ||
- Prompts the user to sign **and send** a Kit `Transaction` and returns the confirmed signature. | ||
### Event Handling | ||
The SDK also allows you to listen for `connect`, `disconnect`, and `accountChanged` events: | ||
- `addEventListener(event: PhantomEventType, callback: PhantomEventCallback): () => void` | ||
- Registers a callback that will be invoked when the specified event occurs. | ||
- For the `connect` event, the callback receives the public key (as a string) of the connected account. | ||
- For the `disconnect` event, the callback receives no arguments. | ||
- For the `accountChanged` event, the callback receives the new public key (as a string). | ||
- Returns a function that, when called, will unregister the callback. | ||
- Multiple callbacks can be registered for the same event. | ||
**Example:** | ||
```typescript | ||
const phantom = createPhantom({ chainPlugins: [createSolanaPlugin()] }); | ||
const handleConnect = (address: string) => { | ||
console.log(`Wallet connected with public key: ${address}`); | ||
}; | ||
const clearConnectListener = phantom.solana.addEventListener("connect", handleConnect); | ||
const handleAccountChanged = (newAddress: string) => { | ||
console.log(`Account changed to: ${newAddress}`); | ||
}; | ||
const clearAccountChangedListener = phantom.solana.addEventListener("accountChanged", handleAccountChanged); | ||
// To stop listening for a specific event: | ||
// clearConnectListener(); | ||
// clearAccountChangedListener(); | ||
``` | ||
- `removeEventListener(event: PhantomEventType, callback: PhantomEventCallback): void` | ||
- Unregisters a previously registered callback for the specified event. | ||
**Example:** | ||
```typescript | ||
const phantom = createPhantom({ chainPlugins: [createSolanaPlugin()] }); | ||
const handleDisconnect = () => { | ||
console.log("Wallet disconnected"); | ||
}; | ||
phantom.solana.addEventListener("disconnect", handleDisconnect); | ||
// To stop listening for this specific disconnect event: | ||
// phantom.solana.removeEventListener("disconnect", handleDisconnect); | ||
``` | ||
### Creating a transaction | ||
Phantom's SDK uses the `@solana/kit` library to create transactions. You can use the `createTransactionMessage` function to create a transaction message. | ||
```typescript | ||
import { | ||
createSolanaRpc, | ||
pipe, | ||
createTransactionMessage, | ||
setTransactionMessageFeePayer, | ||
setTransactionMessageLifetimeUsingBlockhash, | ||
address, | ||
compileTransaction, | ||
} from "@solana/kit"; | ||
// Example: Sign and send a transaction | ||
const rpc = createSolanaRpc("https://my-rpc-url.com"); // Replace with your own RPC URL | ||
const { value: latestBlockhash } = await rpc.getLatestBlockhash().send(); | ||
const transactionMessage = pipe( | ||
createTransactionMessage({ version: 0 }), | ||
tx => setTransactionMessageFeePayer(address(userPublicKey as string), tx), | ||
tx => setTransactionMessageLifetimeUsingBlockhash(latestBlockhash, tx), | ||
); | ||
const transaction = compileTransaction(transactionMessage); | ||
const { signature } = await phantomInstance.solana.signAndSendTransaction(transaction); | ||
``` |
Sorry, the diff of this file is not supported yet
24985
123.72%579
90.46%171
4175%3
200%4
33.33%+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed