Use the repository's installed toolchain (see the root README prerequisites).
The commands below need no network after the pinned Bend, Anza and Cargo
dependencies are available. The test runner builds with Cargo's offline mode.
On initial setup with networking, fetch its dependencies using
cargo fetch --locked --manifest-path scripts/test-runner/Cargo.toml.
npm run bend -- new projects/my-program
npm run bend -- check projects/my-program
npm run bend -- verify projects/my-program
npm run bend -- build projects/my-program
npm run bend -- test projects/my-programCreation refuses an existing path. Quote paths containing spaces. The starter contains an owner-controlled collection, three source laws, their proofs, a storage layout and a readable TypeScript test. Edit those files; generated C and wire layouts are build outputs. The template references the shared SDK in this checkout, so keep that dependency available when moving the source project.
| Command | What it establishes |
|---|---|
new |
Creates the collection project and tests without overwriting work |
check |
Checks declared source laws, target compatibility, storage layout and generated-client compatibility |
verify |
Checks the project's declared Bend source laws and their dependencies |
build |
Runs checked compilation, emits SBF and clients, checks stack limits, records source/artifact hashes |
test |
Rebuilds, then runs the project's test.ts against the actual SBF in LiteSVM |
verify does not invent specifications or run universal SMT proofs for a new
application. Those proofs need application-specific contracts and verifiers.
Execution tests and source laws are distinct evidence; neither proves the compiler
or a deployed binary. test uses no RPC connection and makes no live deployment.
The build writes dist/program.client.ts, a bundled runtime and its declarations,
and a self-contained dist/program.client.js. Copy the TypeScript client together
with its runtime/declaration files, or copy the JavaScript client alone. Neither
needs the compiler or SDK source at runtime.
import {createClient} from './dist/program.client.ts';
const client = createClient(programAddress);
const accounts = {state: stateAddress, actor: ownerAddress, collection: recordsAddress};
const instruction = client.instructions.Append({value: 17}, accounts);An instruction has program, accounts (address/signer/writable) and data.
The client derives fixed account order, flags, constructor tags and padding from
the checked interface. A transport submits and signs this instruction; the client
does not hold wallet keys. Addresses are base58 strings, U32 values are numbers,
SDK U64 values are bigint, single-constructor records are field objects, and
multi-constructor values use a kind field. Bad fields, values and account aliases
fail before submission. Target validators still enforce their own checks on-chain.
client.state(bytes) decodes single-account state. Multi-account clients expose
client.state.<field>(bytes). Collection clients expose
client.collection(bytes, stateAddress, start, count), returning metadata and the
selected typed entries. client.allocationBytes(capacity) supplies creation sizes;
omit capacity for programs without a collection.
For token or generic CPI effects, options.remaining supplies explicit account
metas. The ABI cannot infer application-specific recipient, mint or callee policy.
These account requirements remain part of the application's client API.
test.ts exports an async function accepting the test context. The generated
starter shows the complete setup; the core flow is:
const client = createClient(t.program);
const accounts = {
state: await t.key('state'),
actor: await t.key('owner'),
collection: await t.key('records'),
};
const sizes = client.allocationBytes(4096);
await t.createAccounts([
{address: accounts.state, bytes: sizes.state},
{address: accounts.collection, bytes: sizes.collection},
], client.instructions.Initialize({}, accounts, {mode: 'initialize'}));
await t.send(client.instructions.Append({value: 17}, accounts));
t.equal(client.collection(await t.data(accounts.collection), accounts.state, 0, 1).entries, [17]);
await t.reject(client.instructions.Replace({index: 1, value: 7}, accounts), 'invalid-selection');The context owns a fresh in-memory runtime, a fee payer and deterministic test
signers. t.key(name) returns the same address for a reused name within a run.
t.createAccounts uses signed system-program instructions and rent-exempt sizes;
its optional instruction runs atomically with creation. t.send submits one or
several generated instructions. t.account reads account metadata/data and
t.data reads required account bytes. t.equal asserts decoded results.
t.reject(instruction, {application: 1}) expects the application's Rejected{1};
numeric runtime codes and 'invalid-selection' are also accepted. Rejection
assertions compare all instruction accounts before and after, excluding the fee
payer. Errors include transaction logs. Tests must invoke the loaded program and
assert a result; an empty test cannot create a success report.
Only success writes the test report (dist/<target>/program.test-report.json), including transaction logs,
compute measurements, assertions and hashes of the build manifest, test entrypoint,
project configuration and runner inputs. A test attempt with a valid project
configuration removes the prior success report before build setup. These are fixture measurements,
not worst-case compute bounds. The runner does not require user-written Rust or C.
bend.json has version: 1, source (checked Bend entry module), root (exported
transition function), layout (storage JSON path, or null for the default single
state ABI), and output (artifact stem). Source/layout/output paths stay inside
the project. Existing single-account, multi-account and collection programs use
the same commands and generated client. Add a test.ts using their instructions.
bend.json can list several networks. npm run bend -- new writes both targets;
new <project> --target fogo (or solana) writes just one:
{ "version": 1, "source": "PROOF.bend", "root": "program.process",
"layout": "storage.json", "output": "dist/program", "targets": ["fogo", "solana"] }| Target | Program format | Toolchain | Offline test runtime |
|---|---|---|---|
fogo |
SBPFv0 (build:program --target fogo-v0) |
platform-tools v1.57 | LiteSVM 0.7.1 (scripts/test-runner) |
solana |
SBPFv3 (details) | platform-tools v1.57 | LiteSVM 0.13.0, all features (scripts/test-runner-modern) |
Both targets use one install, npm run install:platform-tools. Fogo programs
keep the ordinary SBPFv0 import checks and final-ELF call-depth gate.
build and test then run for every listed target with no flags. Each target
writes dist/<target>/program.* (ELF, manifest, interface, test report). The
generated client is target-independent: it is checked byte-identical across
targets and published once as dist/program.client.*, so test.ts keeps
importing ./dist/program.client.ts. --target fogo|solana builds or tests
one listed target; --local-rpc requires it when several are listed. If the
v1.57 toolchain is missing, build names the fix (npm run install:platform-tools
or set SBF_V157_TOOLS). npm run bootstrap:alpha installs it.
install:platform-tools checks the release archive and its clang, ld.lld and
llvm-readelf against SHA-256 pins for macOS arm64, Linux x86_64 or Linux aarch64, and never overwrites a
mismatched installation. npm run doctor reports the v1.57 tools as required checks. Without "targets" (projects created before targets existed, or
edited by hand), a project builds the single SBPFv0 output with v1.53 exactly as before.
CI (Ubuntu x86_64) and the acceptance gates install v1.57 and build and test the
default starter for both targets with no flags.
npm run test:project-targets exercises these behaviours and refusals.
The supported language and resource bounds still apply; see subset.md and
diagnostics.md. This workflow adds packaging and testing, not unrestricted Bend
coverage or automatic wallet integration. An explicit
local-validator test backend now deploys a fresh program and
runs the same test.ts with test <project> --local-rpc http://127.0.0.1:18899.
The default test <project> command remains offline.