Expand description
§wallet_standard
An implementation of the Solana wallet standard in Rust.
§Overview
The wallet_standard crate provides a Rust implementation of the Wallet Standard for Solana. It defines a set of traits and types that create a consistent interface for wallets and dApps to interact with the Solana blockchain.
§Installation
To install you can use the following command:
cargo add wallet_standardOr directly add the following to your Cargo.toml:
[dependencies]
wallet_standard = "0.5.1"§Features
| Feature | Description |
|---|---|
browser | Enables browser-specific functionality with wasm-bindgen support |
solana | Enables Solana-specific functionality |
§Core Concepts
The Wallet Standard defines several key concepts:
- Wallet: A wallet is a software application that manages accounts and can perform operations like signing transactions.
- Account: An account represents a user’s identity on the blockchain, typically associated with a public/private key pair.
- Features: Capabilities that a wallet provides, such as connecting, signing messages, or signing transactions.
§Key Traits
§Core Traits
Wallet: The base trait for all wallet implementationsWalletInfo: Provides information about the wallet (name, icon, supported chains)WalletAccountInfo: Provides information about wallet accountsWalletStandard: Combines the core wallet functionality
§Standard Features
WalletStandardConnect: For connecting to a wallet and authorizing accountsWalletStandardDisconnect: For disconnecting from a walletConnectedWalletStandardEvents: For listening to wallet events
§Solana-Specific Traits
WalletSolanaSignMessage: For signing arbitrary messagesWalletSolanaSignTransaction: For signing transactionsWalletSolanaSignAndSendTransaction: For signing and sending transactionsWalletSolanaSignIn: For implementing Sign-In With Solana (SIWS)
§Experimental Features
WalletExperimentalEncrypt: For encrypting dataWalletExperimentalDecrypt: For decrypting data
§Usage Examples
§Implementing a Basic Wallet
use async_trait::async_trait;
use wallet_standard::prelude::*;
// Define your wallet structure
struct MyWallet {
name: String,
icon: String,
accounts: Vec<MyAccount>,
current_account: Option<MyAccount>,
}
// Define your account structure
#[derive(Clone)]
struct MyAccount {
address: String,
public_key: Vec<u8>,
}
// Implement WalletAccountInfo for your account
impl WalletAccountInfo for MyAccount {
fn address(&self) -> String {
self.address.clone()
}
fn public_key(&self) -> Vec<u8> {
self.public_key.clone()
}
fn chains(&self) -> Vec<String> {
vec!["solana:mainnet".to_string()]
}
fn features(&self) -> Vec<String> {
vec![
STANDARD_CONNECT.to_string(),
STANDARD_DISCONNECT.to_string(),
SOLANA_SIGN_MESSAGE.to_string(),
]
}
fn label(&self) -> Option<String> {
Some("My Account".to_string())
}
fn icon(&self) -> Option<String> {
None
}
}
// Implement WalletInfo for your wallet
impl WalletInfo for MyWallet {
type Account = MyAccount;
fn version(&self) -> String {
"1.0.0".to_string()
}
fn name(&self) -> String {
self.name.clone()
}
fn icon(&self) -> String {
self.icon.clone()
}
fn chains(&self) -> Vec<String> {
vec!["solana:mainnet".to_string()]
}
fn features(&self) -> Vec<String> {
vec![
STANDARD_CONNECT.to_string(),
STANDARD_DISCONNECT.to_string(),
SOLANA_SIGN_MESSAGE.to_string(),
]
}
fn accounts(&self) -> Vec<Self::Account> {
self.accounts.clone()
}
}
// Implement Wallet for your wallet
impl Wallet for MyWallet {
type Account = MyAccount;
type Wallet = Self;
fn wallet(&self) -> Self::Wallet {
self.clone()
}
fn wallet_account(&self) -> Option<Self::Account> {
self.current_account.clone()
}
}
// Implement WalletStandardConnect
#[async_trait(?Send)]
impl WalletStandardConnect for MyWallet {
async fn connect(&mut self) -> WalletResult<Vec<Self::Account>> {
// Implement connection logic
// For example, prompt the user to select an account
if let Some(account) = self.accounts.first().cloned() {
self.current_account = Some(account.clone());
Ok(vec![account])
} else {
Err(WalletError::WalletConnection)
}
}
async fn connect_with_options(
&mut self,
_options: StandardConnectInput,
) -> WalletResult<Vec<Self::Account>> {
self.connect().await
}
}
// Implement WalletStandardDisconnect
#[async_trait(?Send)]
impl WalletStandardDisconnect for MyWallet {
async fn disconnect(&mut self) -> WalletResult<()> {
self.current_account = None;
Ok(())
}
}§Implementing Solana-Specific Features
use solana_signature::Keypair;
use solana_signature::Signature;
use solana_signer::Signer;
use wallet_standard::prelude::*;
// Assuming MyWallet is defined as above
// Define a custom output type for sign message
struct MySignMessageOutput {
signature: Signature,
signed_message: Vec<u8>,
}
impl SolanaSignatureOutput for MySignMessageOutput {
fn try_signature(&self) -> WalletResult<Signature> {
Ok(self.signature)
}
fn signature(&self) -> Signature {
self.signature
}
}
impl SolanaSignMessageOutput for MySignMessageOutput {
fn signed_message(&self) -> Vec<u8> {
self.signed_message.clone()
}
fn signature_type(&self) -> Option<String> {
None
}
}
// Implement WalletSolanaSignMessage
#[async_trait(?Send)]
impl WalletSolanaSignMessage for MyWallet {
type Output = MySignMessageOutput;
async fn sign_message_async(&self, message: impl Into<Vec<u8>>) -> WalletResult<Self::Output> {
let message_bytes = message.into();
// In a real implementation, you would use the wallet's signing mechanism
// This is just a placeholder example using a keypair
let keypair = Keypair::new(); // In reality, this would be the user's keypair
let signature = keypair.sign_message(&message_bytes);
Ok(MySignMessageOutput {
signature,
signed_message: message_bytes,
})
}
async fn sign_messages<M: Into<Vec<u8>>>(
&self,
messages: Vec<M>,
) -> WalletResult<Vec<Self::Output>> {
let mut results = Vec::new();
for message in messages {
results.push(self.sign_message_async(message).await?);
}
Ok(results)
}
}§Error Handling
The library provides a comprehensive error handling system through the WalletError enum:
use wallet_standard::prelude::*;
fn handle_wallet_operation() -> WalletResult<()> {
// Attempt some wallet operation
let result = some_wallet_function();
match result {
Ok(_) => Ok(()),
Err(e) => {
match e {
WalletError::WalletNotConnected => {
// Handle not connected error
Err(WalletError::WalletNotConnected)
}
WalletError::InvalidSignature => {
// Handle invalid signature error
Err(WalletError::InvalidSignature)
}
// Handle other error types
_ => Err(e),
}
}
}
}§Advanced Usage
For more advanced usage, including implementing experimental features or custom wallet behaviors, please refer to the API documentation.
A full example of how to use this crate can be found in the wallet_standard_browser crate.
Modules§
Structs§
- Experimental
Decrypt Props - Experimental
Encrypt Props - Standard
Connect Input - Input options for connecting to a wallet.
Enums§
Constants§
- CIPHER_
X25519_ XSALS A20_ POLY1305 - Default encryption algorithm in
NaCl. Curve25519 scalar multiplication, Salsa20 secret-key encryption, and Poly1305 one-time authentication. - EXPERIMENTAL_
DECRYPT - EXPERIMENTAL_
ENCRYPT - STANDARD_
CONNECT - Feature identifier for the standard connect feature.
- STANDARD_
DISCONNECT - Feature identifier for the standard disconnect feature.
- STANDARD_
EVENTS
Traits§
- Connected
Wallet Standard Events - Experimental
Decrypt Output - Experimental
Encrypt Output - Into
Wallet Error - Standard
Connect Output - Output of a successful wallet connection.
- Standard
Event Properties - Wallet
- The core trait for wallet implementations.
- Wallet
Account Info - Interface of a
WalletAccount, also referred to as an Account. - Wallet
Experimental Decrypt - Wallet
Experimental Encrypt - Wallet
Info - Provides information about a wallet implementation.
- Wallet
Standard - A trait that combines the core wallet functionality with standard connect and disconnect features.
- Wallet
Standard Connect - Trait for wallets that support connecting to authorize accounts.
- Wallet
Standard Disconnect - Trait for wallets that support disconnecting.