Thanks for considering a contribution. This document covers the practicalities: how to set up a dev environment, how to send a PR, and what we expect for code quality.
New to the codebase? Read Architecture & module map first — the layering, the one-way dependency rule, and an "I want to X → touch Y" table that points you at the right module.
git clone https://github.com/Radiant-Core/pyrxd.git
cd pyrxd
python3 -m venv .venv
source .venv/bin/activate
poetry install --sync # installs all groups (dev + test) — matches CI exactly
poetry run task test # full pytest suite, ~45 secondsIf you don't have Poetry installed, pip install -e ".[dev]" works for
basic development but won't pull in the full test group (pytest-cov,
hypothesis, pytest-mock). Use Poetry to match the exact CI environment.
The full test suite runs in under a minute on a modern laptop. If something is slow, that's a regression — please flag it.
Run task ci (the full local-CI matrix) automatically before every push:
./scripts/install-git-hooks.shThis catches the same failures CI catches, locally, in ~1-2 minutes —
much faster than the push-fail-fix-push loop. Bypass for a specific push
with git push --no-verify.
We use the Developer Certificate of Origin instead of a Contributor License Agreement. Every commit must carry a sign-off line:
Signed-off-by: Your Name <your@email.example>
Add it automatically by committing with git commit -s. If you forget,
amend with git commit --amend -s.
By signing off you assert that:
- You wrote the patch (or have the right to submit it under the project's license).
- You agree the contribution is licensed under Apache License 2.0 matching the rest of the project.
A sign-off is a one-line statement in each commit, not a separate paperwork process. Most editors and CI systems handle DCO transparently.
- Small, focused changes. One logical change per PR. If you find a drive-by typo while you're in there, send it as a separate PR.
- Tests for new behavior. New code paths get test coverage. Bug fixes ideally include a regression test that fails before the fix.
- Type annotations. New functions and methods carry full type
signatures. We run
mypy --strictonsrc/. - Docstrings on public API.
def public_function():with no docstring is incomplete. Brief is fine; "no docstring" is not. - No new dependencies without discussion. Open an issue first if your change pulls in a new third-party package.
- Don't bypass tests or linters. If a check is failing, fix the
cause; don't add a
# noqaor skip the test.
We use:
ruff checkfor linting + import sorting (must pass oversrc/,tests/,examples/).ruff formatfor formatting — black-compatible, byte-identical output for ~99.9% of code.mypy --strictfor type checking onsrc/pyrxd/security/.
Ruff replaced the previous flake8 + black combo in 0.3 — config lives in
[tool.ruff] in pyproject.toml. Pre-commit (.pre-commit-config.yaml)
runs both ruff hooks plus bandit and detect-secrets. Install hooks with
pre-commit install after cloning.
Before opening a PR, run the full local CI matrix:
poetry run task ci # runs everything CI runs (~3-5 min)This is the canonical "is my PR likely to pass CI" check. Mirrors
.github/workflows/{lint,ci}.yml exactly. If task ci passes locally,
PR CI will almost always pass too.
For faster iteration during a work session, run the individual tasks:
poetry run task test # full pytest suite
poetry run task lint # ruff check + bandit security scan
poetry run task format-check # ruff format --check (no rewrites)
poetry run task format # ruff format src tests examples (does rewrite)
poetry run task typecheck # mypy on src/pyrxd/security/
poetry run task coverage-security # security module coverage (must be 100%)
poetry run task coverage-overall # overall coverage (must be ≥85%)To run task ci automatically before every git push, install the
versioned pre-push hook:
./scripts/install-git-hooks.shThis symlinks scripts/git-hooks/pre-push into .git/hooks/pre-push, so
every git push runs task ci first. Bypass for a specific push with
git push --no-verify (e.g. for WIP branches you're sharing for review).
The hook is strongly recommended if you push to PRs frequently — catching CI failures locally takes 3-5 minutes; finding out via a failed PR check takes 4-6 minutes plus another full CI run after fixing.
Test fixtures that exercise wallet, signing, or key-derivation paths
generate disposable per-run mnemonics via
HdWallet.from_mnemonic(secrets.SystemRandom().choice(...)) or
equivalent. The mnemonic exists only inside the test process and
never leaves it.
Two rules follow from this:
-
Never commit a snapshot file or test fixture containing a real BIP39 mnemonic — yours or anyone else's. Even a published "test vector" mnemonic (e.g. the
abandon abandon ... aboutcanonical BIP39 vector) should only be referenced by name in source, not embedded in a file the test compares against by string-equality under an assertion that could regress and serialize it into a traceback. If you find yourself reaching for snapshot testing (syrupy, etc.), open an issue first so we can decide on a pre-commit hook that scans snapshot files for BIP39-shaped strings. -
result.outputfromCliRunnercaptures contain the mnemonic in JSON-mode tests (per #9). pytest's assertion introspection may includeresult.outputin a traceback if a downstream assertion fails. This is low-risk in practice — mnemonics are random per-run, CI logs are private — but worth knowing. If you need to assert on output that doesn't need the mnemonic itself, extract the specific field you're checking (e.g._extract_json(result.output)["address"]) rather than asserting on the whole output blob.
Conventional Commits with a scope:
feat(glyph): add prepare_dmint_deploy() for v2 dMint contracts
Implements REP-3011 §4.2 state script + §4.3 covenant code script.
Includes round-trip CBOR test and structural deploy integration test.
Types we use: feat, fix, refactor, docs, test, chore,
perf, build, ci. Keep the subject under 72 characters; describe
the why in the body.
Open an issue at https://github.com/Radiant-Core/pyrxd/issues.
Please include:
- pyrxd version (
pip show pyrxd | grep Version) - Python version (
python --version) - A minimal reproduction (smallest code that triggers the bug)
- Expected behavior vs. actual behavior
For security bugs, see SECURITY.md — do not file a public issue.
This project follows the Contributor Covenant 2.1. Be kind. Disagree on substance, not on people.
For project direction, partnership inquiries, or anything that doesn't fit an issue: opensource@mudwoodlabs.com.
For security, see SECURITY.md.
By contributing, you agree your contributions are licensed under
Apache License 2.0 (see LICENSE). The DCO sign-off is your
attestation that you have the right to make this grant.