Wallet-Adapter





A lightweight Rust Solana Wallet that can be used in Rust based frontends and WebAssembly.
This project was supported by the Solana Foundation.
Read the book at: https://jamiidao.github.io/SolanaWalletAdapter/
Documentation Links
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<()>{
let adapter = WalletAdapter::init()?;
let adapter = WalletAdapter::init_with_channel_capacity(
10, )?;
let window = web_sys::window().unwrap();
let document = window.document().unwrap();
let adapter = WalletAdapter::init_custom(
window, document, )?;
let window = web_sys::window().unwrap();
let document = window.document().unwrap();
let adapter = WalletAdapter::init_with_channel_capacity_window_and_document(
10, window, document, )?;
if let Ok(wallet_event) = adapter.events().recv().await {
}
adapter.wallets();
adapter.get_wallet("Phantom");
adapter.connection_info();
adapter.storage();
adapter.window();
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();
storage.get_wallets();
storage.get_wallet("Phantom");
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()?;
adapter.connect_by_name("Phantom").await?;
for wallet in adapter.wallets() {
adapter.connect(wallet).await?; }
adapter.clusters().await?;
adapter.mainnet().await?;
adapter.devnet().await?;
adapter.testnet().await?;
adapter.localnet().await?;
adapter.standard_connect().await?;
adapter.standard_disconnect().await;
adapter.standard_events().await?;
adapter.solana_signin().await?;
adapter.solana_sign_message().await?;
adapter.solana_sign_transaction().await?;
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?;
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?;
let statement = "Login To Dev Website";
let public_key = adapter.connection_info().await.connected_account()?.public_key();
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)
.set_address(&address)?;
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?;
if adapter.solana_sign_message().await? {
adapter.sign_message(b"SOLANA ROCKS!!!").await?;
}else {
}
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?;
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);
let tx = Transaction::new_with_payer(&[instr], Some(&pubkey));
let tx_bytes = bincode::serialize(&tx).unwrap();
let cluster = Cluster::DevNet;
if adapter.is_connected().await {
let output = adapter.sign_transaction(&tx_bytes, Some(cluster)).await?;
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]
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?;
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);
#[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,
}
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":[
]
};
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);
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()
}
let mut tx = Transaction::new_with_payer(&[instr], Some(&pubkey));
if adapter.is_connected().await {
let blockhash = get_blockhash().await;
tx.message.recent_blockhash = blockhash;
let tx_bytes = bincode::serialize(&tx).unwrap();
let send_options = SendOptions::default();
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]
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
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