Expand description
§Wallet-Adapter
A lightweight Rust Solana Wallet that can be used in Rust based frontends and WebAssembly.
Read the book at: https://jamiidao.github.io/SolanaWalletAdapter/
§Documentation Links
- Usage - How to add this library and required features for
web-sys
crate - Initializing - How
AppReady
andRegister
wallet events are initialized - Wallet Storage - How the wallets registered are stored in memory within the dapp
- Connect and Check for Supported Features - How to connect to a browser wallet and check which features the connected wallet supports
- Disconnect - Disconnected an account from the connected wallet
- Sign In With Solana - Sign In With Solana (SIWS)
- Sign Message - Signing a message with a browser wallet
- Sign Transaction - Signing a transaction with a browser wallet
- Sign and Send Transaction - Sign and Send Transaction with a browser wallet
- License - Licensed under Apache-2.0 or MIT
- Features - What features of the wallet standard are supported by this library
- Templates - Which Rust frontend framework templates have been implemented
§Usage
Building the project requires a web-assembly environment.
See Template Usage for more details
§Initializing Register
and AppReady
This is done automatically when calling WalletAdapter::init()
. The Register
and AppReady
events are registered to the browser window and document in the current page allowing browser extension wallet to register themselves as specified in the wallet standard.
use wallet_adapter::{WalletAdapter, WalletResult};
async fn foo() -> WalletResult<()>{
// Initializing the wallet adapter with default channel capacity of `5`.
// This library uses async-channel in order to listen for background tasks like
// connect, disconnect and re-connect
let adapter = WalletAdapter::init()?;
// Initializing the wallet adapter with a custom channel
let adapter = WalletAdapter::init_with_channel_capacity(
10, // Custom capacity
)?;
// Initializing the wallet adapter with a
// `Window` and `Document` that might have been initialized
// elsewhere.
let window = web_sys::window().unwrap();
let document = window.document().unwrap();
let adapter = WalletAdapter::init_custom(
window, // web_sys::Window,
document, // web_sys::Document
)?;
// Initializing the wallet adapter with a custom channel,
// and `Window` and `Document` that might have been initialized
// elsewhere.
let window = web_sys::window().unwrap();
let document = window.document().unwrap();
let adapter = WalletAdapter::init_with_channel_capacity_window_and_document(
10, // Custom capacity
window, // web_sys::Window,
document, // web_sys::Document
)?;
// Get the event receiver that you can use to listen
// for `Register`, `Disconnected` and other wallet events.
if let Ok(wallet_event) = adapter.events().recv().await {
// Do something with the wallet event
}
// Get wallets registered
adapter.wallets();
// Get a wallet by name
adapter.get_wallet("Phantom");
// Get connection information.
// Returns a `ConnectionInfo` struct which contains the
// connected wallet and connected account. It is wrapped in an
// async `RwLock` so you can safely pass it around in background
// browser tasks
adapter.connection_info();
// Get the storage where the registered wallets are stored
adapter.storage();
// Expose the browser window
adapter.window();
// Expose the browser document
adapter.document();
Ok(())
}
§In-memory storage for registered wallets.
wallet_adapter::WalletStorage
handles storage of registered wallets. The in-memory storage is a HashMap<hash, Wallet>
where the hash
is the hash of the wallet name.
use wallet_adapter::WalletStorage;
let storage = WalletStorage::default();
// Get all registered wallets
storage.get_wallets();
// Get a wallet by its name
storage.get_wallet("Phantom");
// Clone the storage inside a closure, method or function that moves variables out of their environment
// `WalletStorage` internally representation is `Rc<RefCell<HashMap<hash, Wallet>>>`
// this makes it cheap to clone `WalletStorage` where one needs to access `HashMap<hash, Wallet>`
storage.clone_inner();
§Connecting to a browser extension wallet and checking for features
use wallet_adapter::{WalletAdapter, WalletResult};
async fn foo() -> WalletResult<()> {
let mut adapter = WalletAdapter::init()?;
// Lookup a wallet from the registered wallet by it's name
// and then use that wallet entry to perform a connection request
adapter.connect_by_name("Phantom").await?;
// Assuming in a frontend application you have a list of
// registered wallets to iterate through in order to show a modal
// or a dropdown of registered wallets and you are building an
// onclick event to do a connection, you cal use the `WalletAdapter::connect()` method instead
for wallet in adapter.wallets() {
// An onclick event from any frontend framework
// onclick:move|_| {
adapter.connect(wallet).await?; // The `wallet` can be used inside the onclick event
// }
}
// Get all clusters supported by a connected wallet
adapter.clusters().await?;
// Is MainNet cluster supported
adapter.mainnet().await?;
// Is DevNet cluster supported
adapter.devnet().await?;
// Is TestNet cluster supported
adapter.testnet().await?;
// Is LocalNet cluster supported
adapter.localnet().await?;
// Is `standard:connect` feature specified in wallet standard supported
adapter.standard_connect().await?;
// Is `standard:disconnect` feature specified in wallet standard supported
adapter.standard_disconnect().await;
// Is `standard:events` feature specified in wallet standard supported
adapter.standard_events().await?;
// Is `solana:signIn` feature specified in wallet standard supported
adapter.solana_signin().await?;
// Is `solana:signMessage` feature specified in wallet standard supported
adapter.solana_sign_message().await?;
// Is `solana:signTransaction` feature specified in wallet standard supported
adapter.solana_sign_transaction().await?;
// Is `solana:signAndSendTransaction` feature specified in wallet standard supported
adapter.solana_sign_and_send_transaction().await?;
Ok(())
}
§Disconnecting from the wallet
use wallet_adapter::{WalletAdapter, WalletResult, SigninInput};
async fn foo() -> WalletResult<()> {
let mut adapter = WalletAdapter::init()?;
adapter.connect_by_name("Phantom").await?;
// Disconnect from the wallet
adapter.disconnect().await;
Ok(())
}
§Sign In With Solana (SIWS)
use wallet_adapter::{WalletAdapter, WalletResult, SigninInput};
async fn foo() -> WalletResult<()> {
let mut adapter = WalletAdapter::init()?;
adapter.connect_by_name("Phantom").await?;
// The message to show the user
let statement = "Login To Dev Website";
// Get the public key bytes of the connected account within the connected wallet
let public_key = adapter.connection_info().await.connected_account()?.public_key();
// Get the address of the connected account within the connected wallet
let address = adapter.connection_info().await.connected_account()?.address().to_string();
let mut signin_input = SigninInput::new();
signin_input
.set_domain(&adapter.window())?
.set_statement(statement)
.set_chain_id(wallet_adapter::Cluster::DevNet)
// NOTE: Some wallets require this field or the wallet adapter
// will return an error `MessageResponseMismatch` which is as
// a result of the sent message not corresponding with the signed message
.set_address(&address)?;
// Get the public key in bytes of the connected
let signin_output = adapter.sign_in(&signin_input, public_key).await.unwrap();
Ok(())
}
Sign In With Solana (SIWS) supports more options for the Sign In With Solana Standard. Check the methods on the SigninInput struct. NOTE that an error is thrown by the library in case the message signed, public key don’t match or if the signature is not valid for the signing public key.
§Sign Message
All messages must be UTF-8 encoded string of bytes
use wallet_adapter::{WalletAdapter, WalletResult, SigninInput};
async fn foo() -> WalletResult<()> {
let mut adapter = WalletAdapter::init()?;
adapter.connect_by_name("Phantom").await?;
// Check if the wallet supports signing a message
if adapter.solana_sign_message().await? {
adapter.sign_message(b"SOLANA ROCKS!!!").await?;
}else {
// Tell user Sign message is not supported
}
Ok(())
}
NOTE that an error is thrown by the library in case the message, public key don’t match or if the signature is not valid for the signing public key.
§Sign Transaction
Here, we simulate signing a SOL transfer instruction
use wallet_adapter::{WalletAdapter, WalletResult, Cluster, Utils,};
use solana_sdk::{
native_token::LAMPORTS_PER_SOL, pubkey::Pubkey, system_instruction, transaction::Transaction,
};
async fn foo() -> WalletResult<()> {
let mut adapter = WalletAdapter::init()?;
adapter.connect_by_name("Phantom").await?;
// Construct a transaction in a manner that the browser wallet extension
// can deserialize the transaction from bytes.
// Here we will use `solana-sdk` crate since it can be converted to
// bytes using a crate like `bincode` that understands serializing
// and deserializing the transaction to and from bytes.
//
// Get the public key bytes from the connected account
let public_key = adapter.connection_info().await.connected_account()?.public_key();
// Convert the public key bytes of the sender to a `solana_sdk::pubkey::Pubkey`
let pubkey = Pubkey::new_from_array(public_key);
// Convert the public key bytes of the recipient to a `solana_sdk::pubkey::Pubkey`.
// Here we use `wallet_adapter::Utils::public_key_rand()` to generate unique public key bytes
// for testing. Make sure you use a valid public key with corresponding private key
// or your funds will be lost.
let recipient_pubkey = Pubkey::new_from_array(Utils::public_key_rand());
// How many SOL to send.
// The `solana_sdk::native_token::LAMPORTS_PER_SOL` constant contains the number of lamports
// equal to `1 SOL` so calculating `2 SOL` can be achieved using `2 * LAMPORTS_PER_SOL`
let sol = LAMPORTS_PER_SOL;
// Create an instruction to transfer the SOL
let instr = system_instruction::transfer(&pubkey, &recipient_pubkey, sol);
// Create a new unsigned transaction
let tx = Transaction::new_with_payer(&[instr], Some(&pubkey));
// Serialize the transaction into bytes using `bincode`
let tx_bytes = bincode::serialize(&tx).unwrap();
// Specify to use devnet cluster
let cluster = Cluster::DevNet;
// You can check if a wallet is connected first to display
// a certain view to a user or make a user connect first if the account was disconnected
if adapter.is_connected().await {
// Request the browser wallet to sign the transaction.
let output = adapter.sign_transaction(&tx_bytes, Some(cluster)).await?;
// Deserialize the signed transaction bytes back into a transaction
let deser_tx_output = bincode::deserialize::<Transaction>(&output[0]).unwrap();
}
Ok(())
}
Remember to add the necessary dependencies for this part in the Cargo.toml
manifest.
[dependencies]
# Add latest versions of these crates
solana-sdk = "^2.1.2"
bincode = "^1.3.3"
NOTE that if the signed transaction is verified by the library and an error is thrown in case of signature mismatch.
§Sign And Send Transaction
Here, we simulate signing and sending a SOL transfer instruction
use std::str::FromStr;
use wallet_adapter::{WalletAdapter, WalletResult, Cluster, Utils, SendOptions};
use serde::Deserialize;
use solana_sdk::{
native_token::LAMPORTS_PER_SOL, pubkey::Pubkey, system_instruction, transaction::Transaction,
};
use wasm_bindgen_futures::JsFuture;
use web_sys::{wasm_bindgen::JsCast, Headers, Request, RequestInit, Response};
async fn foo() -> WalletResult<()> {
let mut adapter = WalletAdapter::init()?;
adapter.connect_by_name("Phantom").await?;
// The variables for the code is the same as the one for Sign Transaction
let public_key = adapter.connection_info().await.connected_account()?.public_key();
let pubkey = Pubkey::new_from_array(public_key);
let recipient_pubkey = Pubkey::new_from_array(Utils::public_key_rand());
let sol = LAMPORTS_PER_SOL;
let instr = system_instruction::transfer(&pubkey, &recipient_pubkey, sol);
// This part is different from Sign Transaction above since we need a valid recent blockhash
// as part of the `SendAndSignTransaction` specification.
// First let's construct structs from serde that will allow us to deserialize the
// response of the recent blockhash from a Solana cluster
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct GetBlockHashResponse<'a> {
#[serde(borrow)]
pub jsonrpc: &'a str,
pub id: u8,
pub result: ResponseResult<'a>,
}
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ResponseResult<'a> {
#[serde(borrow)]
pub context: Context<'a>,
#[serde(borrow)]
pub value: ResponseValue<'a>,
}
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Context<'a> {
#[serde(borrow)]
pub api_version: &'a str,
pub slot: u64,
}
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ResponseValue<'a> {
#[serde(borrow)]
pub blockhash: &'a str,
pub last_valid_block_height: u64,
}
// Create code to use browser fetch API to request a recent blockhash from the
// Solana cluster, in our case Devnet cluster.
// NOTE: You can use other crates like `reqwest` and `gloo-net` do this. However,
// this example will use browser fetch API to give an example of how you would use
// Fetch API or when you don't want to add external dependencies to do this
async fn get_blockhash() -> solana_sdk::hash::Hash {
let devnet_uri = Cluster::DevNet.endpoint();
let body = jzon::object! {
"id":1,
"jsonrpc":"2.0",
"method":"getLatestBlockhash",
"params":[
]
};
// NOTE: You can use Reqwest crate instead to fetch the blockhash but
// this code shows how to use the browser `fetch` api
let headers = Headers::new().unwrap();
headers.append("content-type", "application/json").unwrap();
headers.append("Accept", "application/json").unwrap();
let opts = RequestInit::new();
opts.set_method("POST");
opts.set_headers(&headers);
opts.set_body(&body.to_string().as_str().into());
let request = Request::new_with_str_and_init(&devnet_uri, &opts).unwrap();
let window = web_sys::window().unwrap();
let fetch_promise = window.fetch_with_request(&request);
// Await the fetch promise to get a `Response` object
let resp_value = JsFuture::from(fetch_promise).await.unwrap();
let resp = resp_value.dyn_into::<Response>().unwrap();
let body_as_str = JsFuture::from(resp.text().unwrap())
.await
.unwrap()
.as_string()
.unwrap();
let deser = serde_json::from_str::<GetBlockHashResponse>(&body_as_str).unwrap();
solana_sdk::hash::Hash::from_str(deser.result.value.blockhash).unwrap()
}
// Create a new mutable unsigned transaction
let mut tx = Transaction::new_with_payer(&[instr], Some(&pubkey));
// You can check if a wallet is connected first to display
// a certain view to a user or make a user connect first if the account was disconnected
if adapter.is_connected().await {
// Get the blockhash
let blockhash = get_blockhash().await;
// Add the blockhash to the transaction
tx.message.recent_blockhash = blockhash;
// Serialize the transaction into bytes
let tx_bytes = bincode::serialize(&tx).unwrap();
// Specify which options to pass to the browser wallet.
// Here we use default options
let send_options = SendOptions::default();
// Request the wallet to sign and send the transaction, returning the signature
let signature = adapter.sign_and_send_transaction(&tx_bytes, Cluster::DevNet, send_options).await?;
let signature_with_link = String::from("https://explorer.solana.com/tx/") + &Utils::base58_signature(signature).as_str() + "?cluster=devnet";
}
Ok(())
}
Remember to add the necessary dependencies for this part in the Cargo.toml
manifest.
[dependencies]
# Add latest versions of these crates
solana-sdk = "^2.1.2"
bincode = "^1.3.3"
jzon = "^0.12.5"
serde_json = "1.0.133"
serde = { version = "^1.0.215", features = ["derive"] }
NOTE that if the signed transaction is verified by the library and an error is thrown in case of signature mismatch.
§LICENSE
Apache-2.0 OR MIT
§Features
-
Register
wallet-standard:register-wallet
custom event -
App Ready
wallet-standard:app-ready
custom event - Wallet Info
- Wallet Account parsing
- Wallet Icon
- Chains
- Clusters
- Version (Semver Versionin)
- Features
-
Connect Wallet
standard:connect
-
Disconnect Wallet
standard:disconnect
- SignIn (Sign In With Solana SIWS)
- Sign Message
- Sign Transaction
- Sign and Send Transaction
- Standard Events like Connect, Disconnect and (Re-connect & Account Changed (for wallets that support this)
§Templates
All templates can be found at the templates directory.
Learn how the templates work from https://github.com/JamiiDao/SolanaWalletAdapter/blob/master/templates/README.md
§Code of conduct for contributions
All conversations and contributors must agree to Rust Code of Conduct
Re-exports§
pub use async_channel;
pub use blake3;
pub use bs58;
pub use ed25519_dalek;
pub use getrandom;
pub use humantime;
pub use rand_chacha;
pub use rand_core;
pub use thiserror;
pub use wasm_bindgen_futures;
pub use web_sys;
Structs§
- Chain
Support - Used as a helper struct to contain all the chains supported by a wallet as defined by the wallet standard
- Connect
- The
standard:connect
struct containing aversion
andcallback
within StandardFunction field - Connection
Info - Contains the connected wallet and account.
Containing them in the same struct allows passing of this type
by containing it in types like Arc and RwLock when moving the type
out of it’s scope like in background tasks or async functions (
async move
). - Disconnect
standard:disconnect
struct containing theversion
andcallback
in the field StandardFunction- Feature
Support - Used as a helper struct to contain all the features supported by a wallet as defined by the wallet standard
- Features
- All the features of
standard
andsolana
events as specified in the wallet standard. These features are contained within a crate::Wallet struct allowing to check if a wallet supports a certain feature and also calling the callback functions to make requests to a browser wallet. - Init
Events - Used to initialize the
Register
andAppReady
events to the browser window - Reflection
- Perform reflection on a JsValue
- Semver
Version - Semver Versioning struct
- Send
Options - Options used in the
solana:signAndSendTransaction
method on a crate::Wallet. These options are: - SignIn
- A
solana:signin
struct containing theversion
andcallback
within StandardFunction - Sign
InOutput - The output of Sign In With Solana (SIWS) response from a wallet
- Sign
Message solana:signMessage
containing theversion
andcallback
within the StandardFunction field- Sign
Transaction - Used in
solana:SignTransaction
andsolana:SignAndSendTransaction
. - Signed
Message Output - The output of a signed message
- Signin
Input - The Sign In input used as parameters when performing
SignInWithSolana (SIWS)
requests as defined by the SIWS standard. A backup fork can be found at https://github.com/JamiiDao/sign-in-with-solana - Standard
Events standard:events
struct containing theversion
andcallback
within the StandardFunction field- Standard
Function - A struct containing the semver version
and callback function within the
standard:
namespace as defined by the wallet standard - Utils
- Helper utilities
- Wallet
- A wallet implementing wallet standard
- Wallet
Account - Interface of a WalletAccount, also referred to as an Account.
An account is a read-only data object that is provided from the Wallet to the app,
authorizing the app to use it.
The app can use an account to display and query information from a chain.
The app can also act using an account by passing it to
features
field of the Wallet. - Wallet
Adapter - Operations on a browser window.
Window
andDocument
object must be present otherwise an error is thrown. - Wallet
Icon - A data URI containing a base64-encoded SVG, WebP, PNG, or GIF image.
- Wallet
Storage - Storage used by the crate::WalletAdapter
Enums§
- Cluster
- Solana Clusters
- Commitment
- The commitment level of a Solana transaction.
- Wallet
Error - Error handling enum
- Wallet
Event - Events emitted by connected browser extensions
when an account is connected, disconnected or changed.
Wallets implementing the wallet standard emit these events
from the
standard:events
events namespace specifically,wallet.features[standard:events].on
Constants§
- DEVNET
- Solana Devnet cluster, e.g. https://api.devnet.solana.com
- DEVNET_
ENDPOINT - Solana Devnet cluster
- DEVNET_
IDENTIFIER - Solana Devnet cluster, e.g. https://api.devnet.solana.com
- LOCALNET
- Solana Localnet cluster, e.g. http://localhost:8899
- LOCALNET_
ENDPOINT - Solana Localnet cluster
- LOCALNET_
IDENTIFIER - Solana Localnet cluster, e.g. http://localhost:8899
- MAINNET
- Solana Mainnet cluster, https://api.mainnet-beta.solana.com
- MAINNET_
ENDPOINT - Solana Mainnet cluster
- MAINNET_
IDENTIFIER - Solana Mainnet cluster, https://api.mainnet-beta.solana.com
- SOLANA_
SIGN_ AND_ SEND_ TRANSACTION_ IDENTIFIER - Identifier for
solana:signAndSendTransaction
- SOLANA_
SIGN_ IN_ IDENTIFIER - Identifier for
solana:signIn
- SOLANA_
SIGN_ MESSAGE_ IDENTIFIER - Identifier for
solana:signMessage
- SOLANA_
SIGN_ TRANSACTION_ IDENTIFIER - Identifier for
solana:signTransaction
- STANDARD_
CONNECT_ IDENTIFIER - Identifier for
standard:connect
- STANDARD_
DISCONNECT_ IDENTIFIER - Identifier for
standard:disconnect
- STANDARD_
EVENTS_ IDENTIFIER - Identifier for
standard:events
- TESTNET
- Solana Testnet cluster, e.g. https://api.testnet.solana.com
- TESTNET_
ENDPOINT - Solana Testnet cluster
- TESTNET_
IDENTIFIER - Solana Testnet cluster, e.g. https://api.testnet.solana.com
- WALLET_
STANDARD_ VERSION - The Version of the Wallet Standard currently implemented. This may be used by the app to determine compatibility and feature detect.
- WINDOW_
APP_ READY_ EVENT_ TYPE - Standard App Ready Wallet Event Identifier
- WINDOW_
REGISTER_ WALLET_ EVENT_ TYPE - Standard Register Wallet Event Identifier
Type Aliases§
- Connection
Info Inner - The ConnectionInfo wrapped in an
Arc<RwLock<T>>
- Public
KeyBytes - A 32 byte array representing a Public Key
- Signature
Bytes - A 64 byte array representing a Signature
- Storage
Schema - Convenience type for
HashMap<blake3::Hash, Wallet>;
- Storage
Type - Convenience type for
Rc<RefCell<StorageSchema>>;
- Wallet
Event Receiver - The
Receiver
part of an async_channel::bounded channel - Wallet
Event Sender - The
Sender
part of an async_channel::bounded channel - Wallet
Result - A Result<T, WalletError>