Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

solana-wasi

Solana primitives that actually compile to wasm32-wasip2.

crates.io docs.rs license

solana-sdk and solana-client do not build inside a WebAssembly component without a fight, and the parts of them a plugin needs are small. This crate is those parts, written against the wire formats directly:

  • Pubkeys and PDAs — base58, on-curve checks, find_program_address, associated token addresses.
  • A typed JSON-RPC client over a swappable transport. wasi:http in a component, a canned-response mock on the host.
  • SPL Token and Token-2022 — the 82-byte mint, the 165-byte token account, and the full TLV extension set: permanent delegates, transfer hooks, transfer fees, default account state, pausable, non-transferable, interest-bearing, scaled UI amount, confidential transfers, metadata.
  • Unsigned v0 transactions — instruction building, message compilation, short-vec encoding, base64, and a message digest a human can verify.
  • Durable nonces — because an approval queue outlives a blockhash.
  • Metaplex metadata — enough to answer "can this token still be renamed?".
  • Output shaping and hostile-string handling — turning chain data into the two hundred tokens a model needs, and neutralizing the ones an attacker wrote.

No solana-sdk. No bincode. No borsh. No C toolchain. No async runtime. Six pure-Rust dependencies: serde, serde_json, bs58, sha2, curve25519-dalek, base64 — plus waki, and only when targeting wasm.

Three properties, on purpose

It cannot sign. There is no keypair type, no signer trait, and no path from an instruction to a signature. A plugin built on this crate can hold at most an RPC URL. Signing belongs to a wallet, a human, or a multisig.

It is host-testable. Everything except WakiTransport is pure Rust with no wasm dependency. Your plugin's cargo test runs on the host against MockTransport, with no wasm toolchain and no live network — which is exactly what ZeroClaw's plugin CI requires.

It treats the chain as hostile input. Token names, symbols and metadata URIs are written by whoever deployed the mint, and they end up in a language model's context. sanitize exists because a tool that forwards them verbatim is an injection vector with an RPC bill.

Example

use solana_wasi::prelude::*;

let rpc = RpcClient::new(rpc_url, WakiTransport::new());
let mint = Pubkey::from_base58("2b1kV6DkPAnxd5ixfnxCpjxmKwqjjaYmCZfHsFu24GXo")?;
let state = MintState::parse(mint, &rpc.require_account(&mint)?)?;

if let Some(delegate) = state.permanent_delegate() {
    println!("{} can seize any balance", delegate.abbreviated());
}

Building something a human will approve:

let ix = instructions::transfer_checked(
    state.program, &source, &mint, &destination, &sender, 25_000_000, 6,
);
let message = Message::compile(&sender, &[ix], blockhash)?;
let digest = message.digest();                       // show this to the human
let base64 = UnsignedTransaction::new(message).to_base64()?;

Testing it, with no network and no wasm:

let transport = MockTransport::new().on("getAccountInfo", canned_response);
let rpc = RpcClient::new("https://rpc.example", &transport);

assess(&rpc, &mint)?;
assert_eq!(transport.call_count(), 2);               // no N+1 RPC regressions

Verified, not just tested

94 host tests, and the parts that would be embarrassing to get wrong are checked against the cluster itself rather than only against this crate's own opinion:

  • Transaction encoding — a v0 transfer built by this crate, replayed through simulateTransaction on mainnet-beta, executes with err: null.
  • PDA derivation — the ATA and Metaplex metadata addresses this crate derives are the accounts mainnet actually holds.
  • Token-2022 parsing — the fixtures are the real USDC and PYUSD mint accounts, byte for byte.
  • On-curve checks — cross-checked against an independent ed25519 implementation.

See docs/VERIFICATION.md for the commands, so you can re-run them rather than take this paragraph's word for it.

What fought us on wasm32-wasip2

Written down in docs/VERIFICATION.md, but the short version:

  • solana-program and solana-client are not viable inside a WIT component. The wire formats are small enough to write by hand, and doing so removes the whole problem.
  • You do not need borsh for the payments path. SPL Token accounts are C-layout with COption discriminants; instruction data is a handful of little-endian bytes. Metaplex metadata is borsh-shaped and is parsed by hand in eighty lines.
  • curve25519-dalek builds for wasm32-wasip2 with default-features = false, features = ["alloc"], which is what makes find_program_address — and therefore every ATA — possible in a component.
  • The one that costs an hour: do not glob-import a prelude that re-exports a Result<T> alias inside a wit_bindgen::generate! module. A WIT export returns Result<ToolResult, String>, and the alias shadows it with an error that points at the wrong line.

Used by

Status

0.1.x. The API will move before 1.0. wit/v0 in ZeroClaw is itself marked experimental, so pin your version and expect a rebuild.

License

MIT.

About

Solana primitives that actually compile to wasm32-wasip2: pubkeys, PDAs, JSON-RPC over a swappable transport, SPL Token / Token-2022 parsing, unsigned v0 transactions. No solana-sdk, no keypair type.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages