Erst is a specialized developer tool for the Stellar network, designed to solve the "black box" debugging experience on Soroban.
Status: Active Development (Pre-Alpha) Focus: Soroban Error Decoding & Transaction Replay
The primary goal of erst is to clarify why a Stellar smart contract transaction failed.
Currently, when a Soroban transaction fails on mainnet, developers receive a generic XDR error code. erst aims to bridge the gap between this opaque network error and the developer's source code.
Core Features (Planned):
- Transaction Replay: Fetch a failed transaction's envelope and ledger state from an RPC provider.
- Local Simulation: Re-execute the transaction logically in a local environment.
- Trace decoding: Map execution steps and failures back to readable instructions or Rust source lines.
- Source Mapping: Map WASM instruction failures to specific Rust source code lines using debug symbols.
- GitHub Source Links: Automatically generate clickable GitHub links to source code locations in traces (when in a Git repository).
- Error Suggestions: Heuristic-based engine that suggests potential fixes for common Soroban errors.
Fetches a transaction envelope from the Stellar Public network and prints its XDR size (Simulation pending).
./erst debug <transaction-hash> --network testnetLaunch an interactive terminal UI to explore transaction execution traces with search functionality.
./erst debug <transaction-hash> --interactive
# or
./erst debug <transaction-hash> -iFeatures:
- Search: Press
/to search through traces (contract IDs, functions, errors) - Help overlay: Press
?orhto see all keyboard shortcuts - Tree Navigation: Expand/collapse nodes, navigate with arrow keys
- Syntax Highlighting: Color-coded contract IDs, functions, and errors
- Fast Navigation: Jump between search matches with
n/N - Match Counter: See "Match 2 of 5" status while searching
See internal/trace/README.md for detailed documentation.
erst includes a small utility command to generate a deterministic, signed audit log from a JSON payload.
Provide a PKCS#8 PEM Ed25519 private key via env or CLI:
- Env:
ERST_AUDIT_PRIVATE_KEY_PEM - CLI:
--software-private-key <pem>
Example:
node dist/index.js audit:sign \
--payload '{"input":{},"state":{},"events":[],"timestamp":"2026-01-01T00:00:00.000Z"}' \
--software-private-key "$(cat ./ed25519-private-key.pem)"Select the PKCS#11 provider with --hsm-provider pkcs11 and configure the module/token/key via env vars.
Required env vars:
ERST_PKCS11_MODULE(path to the PKCS#11 module.so)ERST_PKCS11_PINERST_PKCS11_KEY_LABELorERST_PKCS11_KEY_ID(hex)ERST_PKCS11_PUBLIC_KEY_PEM(SPKI PEM public key for verification/audit metadata)
Optional:
ERST_PKCS11_SLOT(numeric index into the slot list)ERST_PKCS11_TOKEN_LABELERST_PKCS11_PIV_SLOT(YubiKey PIV slot such as 9a, 9c, 9d, 9e, 82-95, f9)
Example:
export ERST_PKCS11_MODULE=/usr/lib/softhsm/libsofthsm2.so
export ERST_PKCS11_PIN=1234
export ERST_PKCS11_KEY_LABEL=erst-audit-ed25519
export ERST_PKCS11_PUBLIC_KEY_PEM="$(cat ./ed25519-public-key-spki.pem)"
node dist/index.js audit:sign \
--hsm-provider pkcs11 \
--payload '{"input":{},"state":{},"events":[],"timestamp":"2026-01-01T00:00:00.000Z"}'The command prints the signed audit log JSON to stdout so it can be redirected to a file.
For YubiKey PIV (YKCS11) usage details, see docs/pkcs11-yubikey.md.
- Architecture Overview: Deep dive into how the Go CLI communicates with the Rust simulator, including data flow, IPC mechanisms, and design decisions.
- Project Proposal: Detailed project proposal and roadmap.
- Source Mapping: Implementation details for mapping WASM failures to Rust source code.
- Debug Symbols Guide: How to compile Soroban contracts with debug symbols.
- Error Suggestions: Heuristic-based error suggestion engine for common Soroban errors.
- Interactive Trace Showcase: Try out the interactive trace explorer online.
Stellar's soroban-env-host executes WASM. When it traps (crashes), the specific reason is often sanitized or lost in the XDR result to keep the ledger size small.
erst operates by:
- Fetching Data: Using the Stellar RPC to get the
TransactionEnvelopeandLedgerFootprint(read/write set) for the block where the tx failed. - Simulation Environment: A Rust binary (
erst-sim) that integrates withsoroban-env-hostto replay transactions. - Execution: Feeding the inputs into the VM and capturing
diagnostic_events.
For a detailed explanation of the architecture, see docs/architecture.md.
We are building this open-source to help the entire Stellar community. All contributions, from bug reports to new features, are welcome. Please follow our guidelines to ensure code quality and consistency.
- Go 1.24.0+
- Rust 1.70+ (for building the simulator binary)
- Stellar CLI (for comparing results)
make(for running standard development tasks)
-
Clone the repo:
git clone https://github.com/dotandev/hintents.git cd hintents -
Install dependencies:
go mod download cd simulator && cargo fetch && cd ..
-
Build the Rust simulator:
cd simulator cargo build --release cd ..
-
Run tests:
go test ./... cargo test --release -p erst-sim
This project enforces strict linting rules to maintain code quality. See docs/STRICT_LINTING.md for details.
Quick commands:
# Run all strict linting (Go + Rust)
make lint-all-strict
# Go linting only
make lint-strict
# Rust linting only
make rust-lint-strict
# Install pre-commit hooks
pip install pre-commit && pre-commit installThe CI pipeline fails immediately on:
- Unused variables, imports, or functions
- Dead code
- Any linting warnings
- Formatting: Run
go fmt ./...before committing - Linting: Must pass
golangci-lintwithout errors:golangci-lint run ./...
- Naming Conventions:
- Use
PascalCasefor exported identifiers (types, functions, constants) - Use
camelCasefor unexported identifiers - Use
UPPER_SNAKE_CASEfor constants - Interface names should end with
-er:Reader,Writer,Logger
- Use
- Error Handling:
- Always check and handle errors explicitly
- Wrap errors with context using
fmt.Errorf:fmt.Errorf("operation failed: %w", err) - Never use bare
panic()in production code
- Documentation:
- All exported functions and types must have documentation comments
- Comments should be complete sentences starting with the name
- Example:
// Logger provides structured logging for diagnostic events.
- Formatting: Run
cargo fmt --allbefore committing - Linting: Must pass
cargo clippy:cargo clippy --all-targets --release -- -D warnings
- Naming Conventions:
- Use
snake_casefor functions and variables - Use
PascalCasefor types and traits - Use
UPPER_SNAKE_CASEfor constants
- Use
- Error Handling:
- Prefer
Result<T, E>over panics - Use custom error types for domain-specific errors
- Avoid unwrapping in production code except for obvious invariants
- Prefer
- Documentation:
- Document all public functions with doc comments (
///) - Include examples for complex functions
- Use
cargo doc --opento review generated documentation
- Document all public functions with doc comments (
Follow the Conventional Commits specification:
<type>(<scope>): <subject>
<body>
<footer>
Types:
feat: A new featurefix: A bug fixtest: Adding or improving testsdocs: Documentation changesrefactor: Code refactoring without feature changesperf: Performance improvementschore: Build, CI, or dependency updatesci: CI/CD configuration changes
Scopes: Use specific areas like sim, cli, updater, trace, analyzer, etc.
Examples:
feat(sim): Add protocol version spoofing for harness
test(sim): Add 1000+ transaction regression suite
fix(updater): Handle network timeouts gracefully
docs: Add comprehensive contribution guidelines
Rules:
- Keep subject line under 50 characters
- Use imperative mood ("add", not "added" or "adds")
- No period at the end of the subject
- Provide detailed explanation in the body if the change is non-obvious
- Reference related issues:
Closes #350, refs #343
- Title: Follow commit message convention (this becomes the squashed commit)
- Description:
- Brief summary of changes
- Link to related issues:
Closes #XXX - Explain the "why" behind the changes
- Highlight any breaking changes
- PR Checks:
- All CI checks must pass
- Code coverage must not decrease
- All tests must pass locally before submitting
- Format:
## Description Brief explanation of the changes. ## Related Issues Closes #350, relates to #343 ## Testing How was this tested? Include specific test cases. ## Checklist - [ ] Code follows style guidelines - [ ] Tests added/updated - [ ] Documentation updated - [ ] No new warnings or errors
- Unit Tests: All new functions must have unit tests
- Coverage: Aim for 80%+ coverage. Critical paths should have 90%+ coverage
- Integration Tests: Include tests that verify feature interactions
- Running Tests:
# Go tests go test -v -race ./... go test -v -race -cover ./... # Rust tests cargo test --all cargo test --all --release
- Bench Tests: For performance-critical code, include benchmarks
go test -bench=. -benchmem ./...
-
Create a branch:
git checkout -b feat/my-feature # or for bug fixes: git checkout -b fix/issue-description -
Make changes and test locally:
go test ./... go fmt ./... golangci-lint run ./... cargo clippy --all-targets -- -D warnings cargo fmt --all -
Commit with conventional messages:
git add . git commit -m "feat(scope): description"
-
Push and create PR:
git push origin feat/my-feature # Then create PR on GitHub with detailed description -
Address feedback:
- Make requested changes
- Commit with descriptive messages
- Force-push if necessary:
git push -f origin feat/my-feature
Run the provided scripts before submitting:
# Format Go code
go fmt ./...
# Run linters
golangci-lint run ./...
# Format Rust code
cargo fmt --all
# Check Rust with clippy
cargo clippy --all-targets --release -- -D warnings
# Run all checks
make lint
make formatSee docs/proposal.md for the detailed proposal.
- Phase 1: Research RPC endpoints for fetching historical ledger keys.
- Phase 2: Build a basic "Replay Harness" that can execute a loaded WASM file.
- Phase 3: Connect the harness to live mainnet data.
- Phase 4: Advanced Diagnostics & Source Mapping (Current Focus).
go test -run TestName ./packagego test -cpuprofile=cpu.prof -memprofile=mem.prof ./...
go tool pprof cpu.profGOOS=linux GOARCH=amd64 go build -o erst-linux-amd64 ./cmd/erstgo clean
cargo clean
make cleanWhen reviewing PRs, ensure:
- Code follows naming and style conventions
- Error handling is appropriate
- Tests are adequate and pass
- Documentation is clear and complete
- No unnecessary dependencies added
- Performance implications considered
- Security implications reviewed
- Commit messages follow convention
- Questions? Open a GitHub Discussion
- Found a bug? Create an Issue with reproduction steps
- Have an idea? Start a Discussion before implementing
- Documentation issue? Create an Issue with details
- No Emojis: Commit messages and PR titles should not contain emojis
- No "Slops": Avoid vague language like "fixes stuff" or "updates things"
- Clear Messages: Every commit should have a clear, descriptive message
- Lint-Free: Only suppress linting errors if they are objectively false positives. Always explain suppression with
// nolint:rule-namecomments - Assume Bad Faith in Code: Write code defensively, validate inputs, handle edge cases
Thanks goes to these wonderful people:
dotdev. Code Documentation Ideas & Planning |
This project follows the all-contributors specification. Contributions of any kind welcome!
Erst is an open-source initiative. Contributions, PRs, and Issues are welcome.