A Rust CLI for Pinocchio programs. It scaffolds a project, builds and deploys it, keeps program IDs in sync, generates an IDL and a standalone Rust client from it, and lints for Solana-specific safety issues. Sensible defaults, no required configuration.
cargo install pinocOther methods
# Latest from GitHub
cargo install --git https://github.com/A91y/pinoc --force
# From source
git clone https://github.com/A91y/pinoc.git
cd pinoc && cargo install --path .pinoc init my_app # scaffold a project
cd my_app
pinoc build # build + regenerate the IDL
pinoc test # run tests (mollusk-svm)
pinoc check # lint for Solana safety issues
pinoc deploy # deploy to the configured cluster| Command | Description |
|---|---|
pinoc init <name> |
Create a new project |
pinoc build |
Build the program and regenerate the IDL |
pinoc test |
Build the program, then run tests |
pinoc check |
Lint for Solana-specific safety issues (account, CPI, zero-copy) |
pinoc deploy |
Deploy to a cluster |
pinoc clean |
Clean build artifacts (keypairs preserved) |
pinoc add <package> |
Add a Pinocchio package |
pinoc search [query] |
Search packages |
pinoc keys list |
List program keypairs |
pinoc keys sync |
Sync the program ID in source with its keypair |
pinoc idl |
Regenerate the IDL JSON |
pinoc client generate |
Generate a Rust or TypeScript client from the IDL |
pinoc config init |
Create a Pinoc.toml for the project |
Common options:
pinoc init <name> --with-example: scaffold a worked PDA-account example instead of a no-op programpinoc init <name> --no-git: skip git initializationpinoc deploy --cluster <cluster> --wallet <path>: override deployment settingspinoc build --program-id <ADDRESS>: set the IDL program address for programs that don't calldeclare_id!pinoc build --features <FEATURES>/pinoc test --features <FEATURES>: activate cargo features, passed tocargo build-sbfandcargo test(repeatable or comma-separated, like cargo's own flag)pinoc build --arch <v0|v1|v2|v3|v4>/pinoc test --arch <..>: the SBPF version to build for, passed tocargo build-sbf(see SBPF version)pinoc test --build-features <FEATURES>: features for the pre-test SBF build when they differ from the test features (""for none)pinoc test --no-build: skip the SBF build and test against the existingtarget/deploy/*.sopinoc test -- <CARGO_ARGS>: everything after--goes tocargo test(pinoc test -- --test client,pinoc test -- my_test -- --nocapture)pinoc client generate --language ts: generate a TypeScript client (codama generator) intoclients/tspinoc clean --no-preserve: clean everything, including keypairs
pinoc test runs cargo build-sbf before cargo test, because SVM tests (mollusk-svm, litesvm, solana-program-test) load target/deploy/*.so, which cargo test does not build. Without the build step, tests silently run against the last-built binary.
--features applies to both steps, except for any feature that enables no-entrypoint (such as the scaffold's test-default), which is left out of the build: no-entrypoint compiles the program out of the SBF artifact. When the build and the tests need different features, set the build's separately:
pinoc test --features test-default # build: no features, tests: test-default
pinoc test --features test-default,devnet --build-features devnet
pinoc test --features test-default --build-features "" # build with no features at all--build-features is used as given. Pass --no-build to skip the build when iterating on tests that don't load the .so.
Arguments after -- are handed to cargo test unchanged, after the features, so one target or one test can be run without leaving pinoc test:
pinoc test --features test-default -- --test client_roundtrip
pinoc test --features test-default -- parses_the_fixture -- --exact --nocapturepinoc build and pinoc test write target/deploy/<name>.build.json beside the artifact they build: its features, its --arch, and its length and hash. pinoc test --no-build reads it and stops if the artifact was built with other features or another arch than this run would have used, because tests run against it fail in ways that read as bugs in the program:
Error: target/deploy/prog.so was built with --features devnet, but this run would build it with no features. Drop --no-build to rebuild it, or pass --build-features "devnet" if that is the artifact to test.
With --no-build, --build-features and --arch say which artifact is expected. If there is no record, or the artifact is not the one the record describes (built by cargo build-sbf directly, say), pinoc says it cannot tell what the artifact is and runs the tests.
pinoc build, pinoc test, and pinoc deploy refuse an SBF artifact with no entrypoint (an ELF entry address outside its executable code, the same rule the SBF loader enforces), which is what no-entrypoint produces: it builds fine but contains no program, so no test can load it and a deploy would pay rent for an unusable program. The error names the cause (a build feature, the default feature, or a program with no entrypoint). pinoc build and pinoc test delete the bad artifact; pinoc deploy checks whatever it is about to upload, however it was built. Unlike pinoc test, pinoc build never drops a feature on its own: its features describe the artifact you asked for.
cargo build-sbf builds for the SBPF version given by --arch, and for its own default when the flag is absent (v0 in cargo-build-sbf 4.3). pinoc build and pinoc test pass --arch only when one is set, with the flag or in Pinoc.toml:
[build]
arch = "v3"The flag overrides the file. Setting it in the file keeps pinoc build and pinoc test on the same version; with the flag alone, a pinoc test run without it rebuilds the artifact for the toolchain's default.
Which versions a cluster deploys is decided by two feature gates: one enables SBPFv3, and SIMD-0500 disables deployment of v0, v1 and v2. solana-test-validator 4.3 starts with both active, so it deploys only v3. Before uploading, pinoc deploy reads the artifact's version from its ELF header and asks the cluster for both gates (solana feature status), and stops if the cluster would reject the artifact, naming the --arch to rebuild with. If the cluster cannot be asked, the deploy goes ahead unchecked.
pinoc init produces a blank, buildable program with a single no-op instruction:
my_app/
├── Cargo.toml
├── Pinoc.toml # deployment configuration
├── src/lib.rs
└── target/deploy/my_app-keypair.json
--with-example layout
A full PDA-account creation example, annotated so pinoc idl works out of the box:
my_app/
├── src/
│ ├── lib.rs
│ ├── entrypoint.rs
│ ├── errors.rs
│ ├── instructions/{mod.rs, initialize.rs}
│ └── states/{mod.rs, state.rs, utils.rs}
├── tests/tests.rs
└── target/deploy/my_app-keypair.json
Pinoc.toml holds deployment defaults and is optional:
[provider]
cluster = "localhost"
wallet = "~/.config/solana/id.json"Without it, pinoc deploy falls back to solana config get; --cluster/--wallet always override. Create one on demand with pinoc config init.
pinoc keys list # list program keypairs and their addresses
pinoc keys sync # rewrite the program ID in source to match the keypairkeys sync finds the program's own ID declaration anywhere under src/ (either declare_id! or a const ID) and updates it in place.
pinoc build regenerates the IDL at target/idl/ on every build, and pinoc client generate renders a standalone Rust client crate from it, or a TypeScript client with --language ts. Both understand shank programs and programs using native Codama derive macros.
- IDL generation (files produced, generator selection, error handling, the zero-copy padding lint): src/idl/README.md
- Client generation (the shank and Codama generators, CPI variants,
fetch_*helpers, output paths): src/client_gen/README.md
pinoc check statically lints a program for Solana-specific safety issues that rustc, clippy, and rust-analyzer do not model: account ownership and signer checks, cross-program invocation safety, and zero-copy memory layout. Configurable severity, inline // pinoc:allow(CODE) suppression, and --json output for CI. A run that finds no handlers to analyse says so (NO-HANDLERS) instead of reporting a clean result.
pinoc check # report findings, exit nonzero on a deny
pinoc check --deny '*' # promote every check to a hard failure- Lint codes, configuration, and suppression: src/check/README.md
- Rust 1.70+
- Solana CLI
- Node.js/npm (only for the Codama client generator)
Fork, branch, make your change with tests, and open a pull request.
git clone https://github.com/A91y/pinoc.git
cd pinoc && cargo build --release && cargo install --path .Apache 2.0. See LICENSE.
- Issues: GitHub Issues
- Discussions: GitHub Discussions
- Pinocchio: anza-xyz/pinocchio
Pinoc began as a fork of solana-chio by Arjun.
