willow-core 0.1.0

Rust + JS framework for Solana programs without IDL
Documentation

Willow

Willow is a Rust + JS framework for Solana programs that generates a clean JS SDK without an IDL.
You define instructions, accounts, and state in Rust — Willow extracts the layout and produces:

  • Rust auto‑parsing for instruction args (handlers receive typed args)
  • PDA / Token PDA helpers (auto‑creation on-chain + JS derivation)
  • JS instruction builders with strict args/accounts validation
  • JS account decoders + fetchers (dataSize‑filtered)

No manual packing/unpacking. No IDL.


Install

Rust workspace (this repo):

  • willow/ – Rust crate + CLI
  • willow-runtime/ – JS runtime helpers (published: @neuron1/willow-runtime)
  • willow-derive/ – proc-macro crate (required by Rust; re-exported by willow)

Note: Rust proc-macros must live in a separate crate, so willow-derive cannot be fully merged into willow.
We keep it internal and re-export everything from willow so devs only depend on willow.

CLI usage:

willow init [--name <program_name>] [--path <dir>]
willow build [--path <program_dir>] [--no-raydium] [--runtime <spec>] [--runtime-pkg <name>]
willow gen   [--path <program_dir>] [--runtime <spec>] [--runtime-pkg <name>]
willow deploy [--path <program_dir>] [--keypair <path>] [--program-id <path>]

Runtime dependency controls:

  • --runtime <spec>: version / file spec (e.g. ^0.1.0, file:../willow-runtime)
  • --runtime-pkg <name>: package name (default @neuron1/willow-runtime)

Project Structure

When you run willow gen, the SDK is generated as:

<program>_sdk/
  _gen/              # auto‑generated (flat files)
  instructions/      # dev‑editable wrappers (NOT overwritten if already exists)
  accounts/          # dev‑editable wrappers (NOT overwritten if already exists)

Examples:

vault_sdk/_gen/deposit.js
vault_sdk/instructions/deposit.js
vault_sdk/_gen/vault.js
vault_sdk/accounts/vault.js

Rust: Accounts & Instructions

Accounts (input context)

use pinocchio::account_info::AccountInfo;
use willow::prelude::*;

#[willow_accounts(
    vault_account(writable),
    authority_account(signer),
    system_program
)]
pub struct DepositAccounts<'a> {
    pub vault_account: &'a AccountInfo,
    pub authority_account: &'a AccountInfo,
    pub system_program: &'a AccountInfo,
}

State (serialized account data)

use pinocchio::pubkey::Pubkey;
use willow::prelude::*;

#[repr(C)]
#[willow_account(pda(const("vault"), pubkey(owner)))]
pub struct Vault {
    pub owner: Pubkey,
    pub total_assets: u64,
    #[willow_string]
    pub name: [u8; 32], // decoded to string in JS
}

Instructions

use willow::prelude::*;

#[willow_instruction(
    id = 1,
    accounts = DepositAccounts,
    args = [amount: u64]
)]
pub fn deposit_handler(
    program_id: &Pubkey,
    accounts: &DepositAccounts,
    args: &crate::__willow_args::DepositArgs,
) -> ProgramResult {
    // use args.amount, accounts.vault_account, etc
    Ok(())
}

✅ Args are auto‑parsed and injected by the macro
✅ No manual data decoding in handlers
✅ Accounts have parse() generated automatically


JS SDK (Generated)

Generated instruction functions:

export const deposit_args = { amount: "u64" };
export const deposit_accounts = {
  vault_account: { signer: false, writable: true },
  authority_account: { signer: true, writable: false },
};

export async function deposit(accounts, args) {
  assertAccounts(accounts, deposit_accounts, { allowRemaining: false });
  assertArgs(args, deposit_args);
  ...
}

Strict Validation

Willow throws if:

  • missing args fields
  • unknown args fields
  • wrong arg types
  • missing accounts
  • unknown accounts

Account Fetchers (dataSize‑filtered)

const vault = await fetchVault(connection, vaultPda);
const allVaults = await fetchVaults(connection); // auto dataSize filter

PDA Helpers

On-chain: PDAs are auto‑created by Willow’s generated auto‑pda module.

In JS:

const [vaultPda, bump] = agentic_vault_sdk.pdas.Vault.pda({ owner });
const [agentUsdcPda] = await agentic_vault_sdk.pdas.derive_agent_usdc_account({
  agent_account: agentPda,
  usdc_mint_account: USDC_MINT,
});

Token PDA helpers are exported as verbs (e.g. derive_agent_usdc_account).


JS Runtime (@neuron1/willow-runtime)

Runtime provides:

  • reader, writer
  • strict assertArgs, assertAccounts
  • big number helpers
  • PDA helpers (findPda, findAta)
  • tx helpers (externInstruction, createExternTransaction, buildExternTransactions, execute, serializeExternTransaction)

Example flow:

const ix = deposit({ vault_account, authority }, { amount: 5n });
const tx = buildExternTransactions([ix]);
const sig = await execute(connection, tx, wallet, []);

CPI Support (Rust)

Willow provides CPI builder + struct‑based arg packing:

use willow::prelude::*;

#[willow_cpi_args(discriminator = [43,4,237,11,26,201,30,98])]
pub struct SwapArgs {
    pub amount_in: u64,
    pub min_amount_out: u64,
    pub sqrt_price_limit_x64: u128,
    pub is_base_input: bool,
}

let data = SwapArgs { ... }.to_bytes()?;
let mut cpi = CpiBuilder::new(clmm_program.key(), data);
cpi
  .account(payer, true, false)
  .account(pool_state, false, true)
  .remaining_writable(&remaining_accounts);
cpi.invoke_signed(&signer_seeds)?;

Supported CPI arg types:

  • integers (u8/u16/u32/u64/i*)
  • u128/i128
  • bool
  • Pubkey
  • String
  • Vec<u8>
  • [u8; N]
  • FixedString<N>
  • FixedBytes<N>

Remaining Accounts

If an instruction supports remaining accounts, pass:

deposit({
  vault_account,
  authority,
  remaining: [
    { pubkey: extra1, isSigner: false, isWritable: true },
  ],
}, { amount: 5n });

Publish / Release Flow

JS runtime

OTP=123456 /Users/winjun/Documents/pino_kit_vault/scripts/publish_runtime.sh

Rust crates

/Users/winjun/Documents/pino_kit_vault/scripts/publish_willow_crate.sh

Sync SDKs to published runtime

/Users/winjun/Documents/pino_kit_vault/scripts/sync_runtime_to_sdk.sh \
  /Users/winjun/Documents/pino_kit_vault/agentic_vault ^0.1.0 @neuron1/willow-runtime

Notes

  • Program ID is embedded in generated JS; you never pass it.
  • SDK uses ESM.
  • _gen files are overwritten on willow gen; dev folders are not.
  • Strings in state use #[willow_string] (decoded to JS string; bytes remain raw).

Next

Tell me what you want to add:

  • more CPI helpers
  • more JS runtime utilities
  • template improvements
  • deeper build.rs split