Your first plan

The shortest path from one program call to a two-step piped plan.

The smallest interesting plan is two steps: one that produces a value on chain, and one that consumes exactly that value. This is the Swap → Deposit demo that runs live on devnet.

1. Initialize the register file

Each owner has one register file, a PDA at seeds ["registers", owner]. Create it once with init_registers.

import { initRegistersIx } from "@/lib/ptbvm";

const tx = new Transaction().add(initRegistersIx(owner));
await sendTransaction(tx, connection);

2. Assemble the plan

The swap step calls a program, captures its u64 return value into register 0. The deposit step splices register 0 into its own instruction data, so it deposits exactly what the swap produced, the client never sees or types that number.

import { PlanBuilder, disc, u64le } from "@/lib/ptbvm";

const plan = new PlanBuilder(owner);

plan.step({
  program: SWAP,
  accounts: [{ pubkey: pool, isWritable: false }],
  data: Buffer.concat([disc("global", "swap_mock"), u64le(100)]),
  captureInto: 0,                 // capture return value into register 0
});

plan.step({
  program: VAULT,
  accounts: [{ pubkey: vault, isWritable: true }],
  data: Buffer.concat([disc("global", "deposit"), u64le(0)]),
  dataSplices: [{ offset: 8, register: 0 }], // overwrite the amount with reg 0
});

await sendTransaction(new Transaction().add(plan.compile()), connection);

What just happened

You enter 100. On chain, the swap produces 150 and writes it to register 0. The deposit step's amount field (8 bytes at offset 8, right after the discriminator) is overwritten with register 0 before it runs, so the vault receives 150. If any step had failed, the entire transaction, including the swap, would have reverted.

you type 100swap(100)→ produces 150captureregister file · 8 slots1500000000reg 0splicedeposit(150)amount ← reg 0The client nevertypes 150.Only reg 0 iswritten here.
Register piping. You type 100. The swap runs on chain and produces 150; the VM captures 150 into register 0, then splices register 0 into the deposit's amount before it runs, so the deposit receives 150 without the client ever typing it.