# 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)
```rust
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)
```rust
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
```rust
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:
```js
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)
```js
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:**
```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:
```js
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**:
```rust
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:
```js
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