use leptos::prelude::*;
use leptos::task::spawn_local;
use solana_transaction::versioned::VersionedTransaction;
use crate::discovery::{self, DiscoveryHandle, WalletList};
use crate::error::Result;
use crate::wallet::{Wallet, WalletAccount};
use crate::{features, storage, tx};
#[derive(Clone)]
pub struct WalletContext {
pub wallets: ReadSignal<WalletList>,
#[allow(dead_code)]
set_wallets: WriteSignal<WalletList>,
pub selected: ReadSignal<Option<Wallet>>,
set_selected: WriteSignal<Option<Wallet>>,
pub account: ReadSignal<Option<WalletAccount>>,
set_account: WriteSignal<Option<WalletAccount>>,
pub chain: ReadSignal<String>,
set_chain: WriteSignal<String>,
}
impl WalletContext {
pub fn select(&self, wallet: Wallet) {
self.set_selected.set(Some(wallet));
}
pub fn set_chain(&self, chain: impl Into<String>) {
self.set_chain.set(chain.into());
}
pub async fn connect(&self) -> Result<()> {
let wallet = self
.selected
.get_untracked()
.ok_or(crate::Error::NoAccount)?;
let accounts = features::connect(&wallet, false).await?;
let picked = pick_solana_account(accounts).ok_or(crate::Error::NoAccount)?;
self.set_account.set(Some(picked));
storage::remember_wallet(&wallet.name());
Ok(())
}
pub async fn disconnect(&self) -> Result<()> {
if let Some(wallet) = self.selected.get_untracked() {
features::disconnect(&wallet).await?;
}
self.set_account.set(None);
storage::forget_wallet();
Ok(())
}
pub async fn sign_message(&self, message: &[u8]) -> Result<Vec<u8>> {
let (wallet, account) = self.wallet_account()?;
features::sign_message(&wallet, &account, message).await
}
pub async fn sign_transaction(
&self,
transaction: &VersionedTransaction,
) -> Result<VersionedTransaction> {
let bytes = tx::serialize(transaction)?;
let signed = self.sign_transaction_raw(&bytes).await?;
tx::deserialize(&signed)
}
pub async fn sign_and_send(&self, transaction: &VersionedTransaction) -> Result<Vec<u8>> {
let bytes = tx::serialize(transaction)?;
self.sign_and_send_raw(&bytes).await
}
pub async fn sign_transaction_raw(&self, tx_bytes: &[u8]) -> Result<Vec<u8>> {
let (wallet, account) = self.wallet_account()?;
let chain = self.chain.get_untracked();
features::sign_transaction(&wallet, &account, &chain, tx_bytes).await
}
pub async fn sign_and_send_raw(&self, tx_bytes: &[u8]) -> Result<Vec<u8>> {
let (wallet, account) = self.wallet_account()?;
let chain = self.chain.get_untracked();
features::sign_and_send_transaction(&wallet, &account, &chain, tx_bytes).await
}
fn wallet_account(&self) -> Result<(Wallet, WalletAccount)> {
let wallet = self
.selected
.get_untracked()
.ok_or(crate::Error::NoAccount)?;
let account = self
.account
.get_untracked()
.ok_or(crate::Error::NoAccount)?;
Ok((wallet, account))
}
}
fn pick_solana_account(accounts: Vec<WalletAccount>) -> Option<WalletAccount> {
accounts.into_iter().find(|a| {
a.chains()
.iter()
.filter_map(|c| c.as_string())
.any(|c| c.starts_with("solana:"))
})
}
pub fn provide_wallet_context(default_chain: &str) -> WalletContext {
let (wallets, set_wallets) = signal(WalletList::default());
let (selected, set_selected) = signal(None::<Wallet>);
let (account, set_account) = signal(None::<WalletAccount>);
let (chain, set_chain) = signal(default_chain.to_string());
let last = storage::last_wallet();
let handle = discovery::start(move |wallet| {
let name = wallet.name();
let already_known = wallets.with_untracked(|list: &WalletList| {
list.0.iter().any(|w| w.name() == name)
});
if !already_known {
set_wallets.update(|list| list.0.push(wallet.clone()));
}
if Some(&name) == last.as_ref() && account.get_untracked().is_none() {
set_selected.set(Some(wallet.clone()));
let wallet = wallet.clone();
spawn_local(async move {
if let Ok(accounts) = features::connect(&wallet, true).await {
if let Some(a) = pick_solana_account(accounts) {
set_account.set(Some(a));
}
}
});
}
})
.ok();
if let Some(h) = handle {
let _ = StoredValue::new(HandleCell(Some(h)));
}
let ctx = WalletContext {
wallets,
set_wallets,
selected,
set_selected,
account,
set_account,
chain,
set_chain,
};
provide_context(ctx.clone());
ctx
}
struct HandleCell(#[allow(dead_code)] Option<DiscoveryHandle>);
unsafe impl Send for HandleCell {}
unsafe impl Sync for HandleCell {}
pub fn use_wallet() -> WalletContext {
use_context::<WalletContext>()
.expect("WalletContext missing — call provide_wallet_context at the app root")
}