solana-wasi 0.1.0

Solana primitives that actually compile to wasm32-wasip2: pubkeys, PDAs, JSON-RPC over a swappable transport, SPL Token / Token-2022 account parsing, and unsigned v0 transaction construction. No solana-sdk, no C toolchain, no async runtime.
Documentation
//! The handful of instructions a payment or attestation plugin actually emits.
//!
//! Each one is a few bytes of little-endian encoding. Hand-rolling them is what
//! removes the `solana-program` dependency, and every constructor here is
//! covered by a byte-for-byte test against the layout the on-chain programs
//! expect.

use crate::pubkey::{ids, Pubkey};
use crate::token::TokenProgram;
use crate::tx::{AccountMeta, Instruction};

/// System program instruction indices.
mod system_ix {
    pub const TRANSFER: u32 = 2;
    pub const ADVANCE_NONCE_ACCOUNT: u32 = 4;
}

/// SPL Token instruction indices.
mod token_ix {
    pub const TRANSFER_CHECKED: u8 = 12;
    pub const SYNC_NATIVE: u8 = 17;
}

/// Compute budget instruction indices.
mod compute_ix {
    pub const SET_COMPUTE_UNIT_LIMIT: u8 = 2;
    pub const SET_COMPUTE_UNIT_PRICE: u8 = 3;
}

/// Move native SOL.
pub fn transfer_sol(from: &Pubkey, to: &Pubkey, lamports: u64) -> Instruction {
    let mut data = Vec::with_capacity(12);
    data.extend_from_slice(&system_ix::TRANSFER.to_le_bytes());
    data.extend_from_slice(&lamports.to_le_bytes());
    Instruction {
        program_id: ids::SYSTEM_PROGRAM,
        accounts: vec![
            AccountMeta::writable_signer(*from),
            AccountMeta::writable(*to),
        ],
        data,
    }
}

/// Move SPL tokens, with the mint and decimals checked on-chain.
///
/// Always `TransferChecked`, never `Transfer`. The unchecked variant will
/// happily move 1000 base units when the caller meant 1000 whole tokens at 6
/// decimals, and an agent that computed the amount from a chat message is
/// exactly the caller that gets that wrong.
pub fn transfer_checked(
    program: TokenProgram,
    source: &Pubkey,
    mint: &Pubkey,
    destination: &Pubkey,
    authority: &Pubkey,
    amount: u64,
    decimals: u8,
) -> Instruction {
    let mut data = Vec::with_capacity(10);
    data.push(token_ix::TRANSFER_CHECKED);
    data.extend_from_slice(&amount.to_le_bytes());
    data.push(decimals);
    Instruction {
        program_id: program.id(),
        accounts: vec![
            AccountMeta::writable(*source),
            AccountMeta::readonly(*mint),
            AccountMeta::writable(*destination),
            AccountMeta::readonly_signer(*authority),
        ],
        data,
    }
}

/// Create an associated token account, succeeding if it already exists.
///
/// The idempotent variant matters for an agent: between building a transaction
/// and a human approving it on their phone, someone else may have created the
/// recipient's account. The non-idempotent instruction would fail the whole
/// transfer at that point.
pub fn create_associated_token_account_idempotent(
    payer: &Pubkey,
    ata: &Pubkey,
    owner: &Pubkey,
    mint: &Pubkey,
    program: TokenProgram,
) -> Instruction {
    Instruction {
        program_id: ids::ASSOCIATED_TOKEN_PROGRAM,
        accounts: vec![
            AccountMeta::writable_signer(*payer),
            AccountMeta::writable(*ata),
            AccountMeta::readonly(*owner),
            AccountMeta::readonly(*mint),
            AccountMeta::readonly(ids::SYSTEM_PROGRAM),
            AccountMeta::readonly(program.id()),
        ],
        data: vec![1],
    }
}

/// Attach a UTF-8 memo, which is how an invoice reference survives into the
/// ledger and back out through a transaction history.
pub fn memo(text: &str, signers: &[Pubkey]) -> Instruction {
    Instruction {
        program_id: ids::MEMO_PROGRAM,
        accounts: signers
            .iter()
            .map(|s| AccountMeta::readonly_signer(*s))
            .collect(),
        data: text.as_bytes().to_vec(),
    }
}

/// Advance a durable nonce.
///
/// This must be the first instruction in a transaction whose `recent_blockhash`
/// is a stored nonce. It is the answer to the structural problem of an
/// approval-gated agent: a nonce does not expire in five minutes, so the human
/// can come back from lunch and the transaction is still valid.
pub fn advance_nonce_account(
    nonce_account: &Pubkey,
    nonce_authority: &Pubkey,
) -> Instruction {
    Instruction {
        program_id: ids::SYSTEM_PROGRAM,
        accounts: vec![
            AccountMeta::writable(*nonce_account),
            AccountMeta::readonly(ids::SYSVAR_RECENT_BLOCKHASHES),
            AccountMeta::readonly_signer(*nonce_authority),
        ],
        data: system_ix::ADVANCE_NONCE_ACCOUNT.to_le_bytes().to_vec(),
    }
}

/// Reconcile a wrapped-SOL account's token balance with its lamports.
pub fn sync_native(program: TokenProgram, account: &Pubkey) -> Instruction {
    Instruction {
        program_id: program.id(),
        accounts: vec![AccountMeta::writable(*account)],
        data: vec![token_ix::SYNC_NATIVE],
    }
}

/// Cap the compute units this transaction may consume.
pub fn set_compute_unit_limit(units: u32) -> Instruction {
    let mut data = Vec::with_capacity(5);
    data.push(compute_ix::SET_COMPUTE_UNIT_LIMIT);
    data.extend_from_slice(&units.to_le_bytes());
    Instruction {
        program_id: ids::COMPUTE_BUDGET_PROGRAM,
        accounts: Vec::new(),
        data,
    }
}

/// Bid a priority fee, in micro-lamports per compute unit.
pub fn set_compute_unit_price(micro_lamports: u64) -> Instruction {
    let mut data = Vec::with_capacity(9);
    data.push(compute_ix::SET_COMPUTE_UNIT_PRICE);
    data.extend_from_slice(&micro_lamports.to_le_bytes());
    Instruction {
        program_id: ids::COMPUTE_BUDGET_PROGRAM,
        accounts: Vec::new(),
        data,
    }
}