Instructions
Anchor → Pinocchio
| Anchor | Pinocchio |
|---|---|
| 8-byte discriminator (hash of name) | 1-byte discriminator (manual) |
#[instruction(amount: u64)] |
Manual parsing from instruction_data |
| Automatic deserialization | Manual TryFrom<&[u8]> |
| IDL-generated client | Manual TypeScript client |
Discriminator Pattern
Anchor uses 8-byte discriminators derived from instruction names. Pinocchio uses a simple 1-byte index:
pub fn process_instruction(
program_id: &Address,
accounts: &[AccountView],
instruction_data: &[u8],
) -> ProgramResult {
match instruction_data.split_first() {
Some((&0, rest)) => initialize(accounts, rest),
Some((&1, rest)) => deposit(accounts, rest),
Some((&2, rest)) => withdraw(accounts, rest),
_ => Err(ProgramError::InvalidInstructionData),
}
}
Anchor equivalent:
#[program]
pub mod my_program {
pub fn initialize(ctx: Context<Initialize>) -> Result<()> { ... }
pub fn deposit(ctx: Context<Deposit>, amount: u64) -> Result<()> { ... }
pub fn withdraw(ctx: Context<Withdraw>, amount: u64) -> Result<()> { ... }
}
Parsing Instruction Data
Anchor deserializes instruction arguments automatically. In Pinocchio, parse manually:
fn deposit(accounts: &[AccountView], data: &[u8]) -> ProgramResult {
// Parse u64 amount
if data.len() < 8 {
return Err(ProgramError::InvalidInstructionData);
}
let amount = u64::from_le_bytes(data[0..8].try_into().unwrap());
// Use it
Ok(())
}
Instruction Data Struct
For complex data, use a struct with TryFrom:
pub struct DepositData {
pub amount: u64,
pub memo: u8,
}
impl TryFrom<&[u8]> for DepositData {
type Error = ProgramError;
fn try_from(data: &[u8]) -> Result<Self, Self::Error> {
if data.len() < 9 {
return Err(ProgramError::InvalidInstructionData);
}
Ok(Self {
amount: u64::from_le_bytes(data[0..8].try_into().unwrap()),
memo: data[8],
})
}
}
// Usage
let data = DepositData::try_from(data)?;
Combined Accounts + Data
Pattern similar to Anchor's Context<T> with instruction args:
pub struct Deposit<'a> {
pub accounts: DepositAccounts<'a>,
pub data: DepositData,
}
impl<'a> TryFrom<(&'a [u8], &'a [AccountView])> for Deposit<'a> {
type Error = ProgramError;
fn try_from((data, accounts): (&'a [u8], &'a [AccountView])) -> Result<Self, Self::Error> {
Ok(Self {
accounts: DepositAccounts::try_from(accounts)?,
data: DepositData::try_from(data)?,
})
}
}
impl Deposit<'_> {
pub fn process(&self) -> ProgramResult {
// Business logic here
Ok(())
}
}
// In entrypoint
match instruction_data.split_first() {
Some((&1, rest)) => Deposit::try_from((rest, accounts))?.process(),
// ...
}
Client Side (TypeScript)
Anchor generates clients from IDL. In Pinocchio, build instructions manually:
function createDepositIx(
program: PublicKey,
accounts: { vault: PublicKey; user: PublicKey },
amount: bigint
): TransactionInstruction {
const data = Buffer.alloc(9);
data.writeUInt8(1, 0); // discriminator
data.writeBigUInt64LE(amount, 1);
return new TransactionInstruction({
programId: program,
keys: [
{ pubkey: accounts.vault, isSigner: false, isWritable: true },
{ pubkey: accounts.user, isSigner: true, isWritable: true },
],
data,
});
}
Anchor equivalent:
await program.methods.deposit(new BN(amount)).accounts({ vault, user }).rpc();