pinoc 0.3.5

A CLI tool for setting up pinocchio program project
pinoc-0.3.5 is not a library.

CI Crates.io License: Apache 2.0 Rust Downloads


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

cargo install pinoc
# 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 .

Quick start

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

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 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 program
  • pinoc init <name> --no-git: skip git initialization
  • pinoc deploy --cluster <cluster> --wallet <path>: override deployment settings
  • pinoc build --program-id <ADDRESS>: set the IDL program address for programs that don't call declare_id!
  • pinoc build --features <FEATURES> / pinoc test --features <FEATURES>: activate cargo features, passed to cargo build-sbf and cargo 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 to cargo 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 existing target/deploy/*.so
  • pinoc client generate --language ts: generate a TypeScript client (codama generator) into clients/ts
  • pinoc 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:

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.

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.

SBPF version

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.

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:

[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.

Key management

pinoc keys list         # list program keypairs and their addresses
pinoc keys sync         # rewrite the program ID in source to match the keypair

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, 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

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. 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

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.

git clone https://github.com/A91y/pinoc.git
cd pinoc && cargo build --release && cargo install --path .

License

Apache 2.0. See LICENSE.

Support

Acknowledgements

Pinoc began as a fork of solana-chio by Arjun.