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.
Installation
# Latest from GitHub
# From source
&&
Quick start
Commands
| 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 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 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 clean --no-preserve: clean everything, including keypairs
Testing
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:
--build-features is used as given. Pass --no-build to skip the build when iterating on tests that don't load the .so.
Entrypoint check
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.
Project structure
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
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
Configuration
Pinoc.toml holds deployment defaults and is optional:
[]
= "localhost"
= "~/.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.
Key management
keys sync finds the program's own ID declaration anywhere under src/ (either declare_id! or a const ID) and updates it in place.
IDL and client generation
pinoc build regenerates the IDL at target/idl/ on every build, and pinoc client generate renders a standalone Rust client crate from it. 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
Linting
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.
- Lint codes, configuration, and suppression: src/check/README.md
Prerequisites
- Rust 1.70+
- Solana CLI
- Node.js/npm (only for the Codama client generator)
Contributing
Fork, branch, make your change with tests, and open a pull request.
&& &&
License
Apache 2.0. See LICENSE.
Support
- Issues: GitHub Issues
- Discussions: GitHub Discussions
- Pinocchio: anza-xyz/pinocchio
Acknowledgements
Pinoc began as a fork of solana-chio by Arjun.