A reusable GitHub Action that spins up a full ZecKit Zcash devnet with pre-built container images and runs the complete golden shielded-transaction flow end-to-end:
Generate UA → Fund → Autoshield → Shielded Send → Rescan/Sync → Verify
Available on the GitHub Marketplace.
- Quick Start
- Inputs Reference
- Outputs Reference
- Usage Patterns
- Running Locally
- Artifacts
- Common Failure Modes & Troubleshooting
# .github/workflows/my-zcash-tests.yml
name: My Zcash Project E2E
on: [push, pull_request]
jobs:
zeckit:
name: ZecKit E2E
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: zecdev/ZecKit@v1
with:
ghcr_token: ${{ secrets.GITHUB_TOKEN }}jobs:
zeckit:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: ZecKit E2E golden flow
id: e2e
uses: zecdev/ZecKit@v1
with:
# Backend
backend: zaino # 'zaino' or 'lwd'
# Timeouts
startup_timeout_minutes: '10' # wait for healthy services
block_wait_seconds: '75' # wait for block confirmation
# Transaction parameters
send_amount: '0.05' # ZEC to send
send_address: '' # empty = self-send (safe default)
send_memo: 'My project E2E test'
# Image selection
image_prefix: 'ghcr.io/zecdev/zeckit'
image_tag: '' # empty = auto-detect
# Artifacts
upload_artifacts: 'on-failure' # 'always' | 'on-failure' | 'never'
# Auth
ghcr_token: ${{ secrets.GITHUB_TOKEN }}
- name: Use action outputs
run: |
echo "UA : ${{ steps.e2e.outputs.unified_address }}"
echo "Shield txid : ${{ steps.e2e.outputs.shield_txid }}"
echo "Send txid : ${{ steps.e2e.outputs.send_txid }}"
echo "Orchard final : ${{ steps.e2e.outputs.final_orchard_balance }} ZEC"
echo "Block height : ${{ steps.e2e.outputs.block_height }}"
echo "Result : ${{ steps.e2e.outputs.test_result }}"| Input | Required | Default | Description |
|---|---|---|---|
ghcr_token |
yes | – | Token to pull pre-built images from GHCR. Pass ${{ secrets.GITHUB_TOKEN }}. |
backend |
no | zaino |
Light-client backend: zaino (Rust, ~30 % faster) or lwd (Lightwalletd, Go). |
startup_timeout_minutes |
no | 10 |
Minutes to wait for all services to become healthy. Zaino typically ready in 2-3 min; lwd in 3-4 min. |
block_wait_seconds |
no | 75 |
Seconds to wait for Zebra to mine a confirming block after broadcasting a transaction. Zebra regtest mines every 30-60 s. |
send_amount |
no | 0.05 |
Amount in ZEC sent during the shielded-send step. |
send_address |
no | '' |
Destination Unified Address for the shielded send. Empty string performs a self-send back to the faucet UA (safe, no external address needed). |
send_memo |
no | ZecKit E2E golden flow |
Memo text included in the shielded send transaction. |
image_prefix |
no | ghcr.io/zecdev/zeckit |
Registry prefix for pre-built images (resolves to …-zebra:TAG, …-zaino:TAG, …-faucet:TAG, etc.). |
image_tag |
no | '' |
Specific image tag to pull. Empty triggers auto-detection: sha-<short> → branch-name → main → latest. |
upload_artifacts |
no | on-failure |
When to upload log artifacts: always, on-failure, or never. |
zaino |
lwd |
|
|---|---|---|
| Language | Rust | Go |
| Startup time | 2-3 min | 3-4 min |
| Sync speed | Faster (~30 %) | Baseline |
| Recommendation | Development / CI | Compatibility testing |
All outputs are available under steps.<id>.outputs.* after the action completes.
| Output | Type | Description |
|---|---|---|
unified_address |
string | Unified Address (UA) generated by the faucet wallet for this run. |
transparent_address |
string | Transparent (t-addr) of the faucet wallet. |
shield_txid |
string | Transaction ID of the autoshield (transparent → Orchard). Empty if transparent balance was below fee threshold. |
send_txid |
string | Transaction ID of the shielded Orchard → Orchard send. |
final_orchard_balance |
number (str) | Orchard balance in ZEC after the complete flow. |
block_height |
number (str) | Zcash regtest blockchain height at end of the run. |
test_result |
pass | fail |
Overall golden-flow result. The action also exits non-zero on fail. |
A machine-readable run-summary.json containing all output fields is always written inside the log artifact.
Use uses: zecdev/ZecKit@v1 in any step. Returns all outputs on the same job.
Suitable when:
- You need the outputs (addresses, txids) in subsequent steps.
- You want to mix ZecKit E2E with other steps in the same job.
- uses: zecdev/ZecKit@v1
id: zcash
with:
ghcr_token: ${{ secrets.GITHUB_TOKEN }}
- run: |
echo "Shielded send confirmed: ${{ steps.zcash.outputs.send_txid }}"Call .github/workflows/golden-e2e.yml from another workflow's jobs entry.
Outputs are available under needs.<job>.outputs.*.
Suitable when:
- You want E2E to run as a dedicated named job with its own status badge.
- You need to block other jobs on the E2E result without coupling steps.
jobs:
# Pull in ZecKit E2E as a dedicated job block
e2e:
uses: zecdev/ZecKit/.github/workflows/golden-e2e.yml@v1
with:
backend: zaino
startup_timeout_minutes: 10
secrets:
ghcr_token: ${{ secrets.GITHUB_TOKEN }}
# Gate a downstream job on e2e passing
deploy:
needs: e2e
if: needs.e2e.outputs.test_result == 'pass'
runs-on: ubuntu-latest
steps:
- run: echo "Deploying – E2E passed with txid ${{ needs.e2e.outputs.send_txid }}"jobs:
e2e:
strategy:
matrix:
backend: [zaino, lwd]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: zecdev/ZecKit@v1
with:
backend: ${{ matrix.backend }}
ghcr_token: ${{ secrets.GITHUB_TOKEN }}The action itself requires a GitHub Actions runner environment (it writes to
$GITHUB_ENV, $GITHUB_OUTPUT, $GITHUB_STEP_SUMMARY). However, you can
exercise the exact same flow locally using the zeckit CLI and curl.
# Docker Engine 24+ and Compose v2
docker --version && docker compose version
# Rust toolchain (for CLI build)
rustup update stable# 1. Clone and build CLI
git clone https://github.com/zecdev/ZecKit.git && cd ZecKit
cd cli && cargo build --release && cd ..
# 2. Login to GHCR so pre-built images can be pulled
echo "$CR_PAT" | docker login ghcr.io -u YOUR_GITHUB_HANDLE --password-stdin
# 3. Start the devnet (zaino backend, fresh volumes)
./cli/target/release/zeckit up --backend zaino --fresh
# Wait for output: "Devnet is ready"
# 4. Generate Unified Address
curl http://localhost:8080/address | jq
# 5. Check wallet is funded (mining rewards appear after ~60 s)
curl http://localhost:8080/stats | jq '.transparent_balance, .orchard_balance'
# 6. Autoshield transparent → Orchard
curl -X POST http://localhost:8080/shield | jq
sleep 75 # one block confirmation window
# 7. Sync wallet
curl -X POST http://localhost:8080/sync | jq
# 8. Shielded send (Orchard → Orchard, self-send)
UA=$(curl -s http://localhost:8080/address | jq -r .unified_address)
curl -X POST http://localhost:8080/send \
-H "Content-Type: application/json" \
-d "{\"address\":\"$UA\",\"amount\":0.05,\"memo\":\"local test\"}" | jq
sleep 75
# 9. Verify final state
curl http://localhost:8080/stats | jq
# 10. Run the full built-in smoke test suite (covers same flow + more)
./cli/target/release/zeckit test
# 11. Tear down
./cli/target/release/zeckit downact can run the CI self-test workflow on your machine:
brew install act
# Provide a GITHUB_TOKEN with read:packages scope
act -j composite-action-test \
-s GITHUB_TOKEN="$(gh auth token)" \
--workflows .github/workflows/ci-action-test.ymlNote:
actusescatthehacker/ubuntu:act-latestby default which may not have all Docker-in-Docker capabilities. Use a full Docker socket mount if you encounterdocker: not foundinside the runner:act ... --bind
When upload_artifacts is always or on-failure (default), the action
uploads a ZIP named zeckit-e2e-logs-<run_number> as a workflow artifact.
Artifact retention: 14 days (configurable in action.yml).
| File | Description |
|---|---|
run-summary.json |
Machine-readable JSON: backend, UA, txids, final balance, block height, test_result. |
faucet-stats.json |
Raw /stats response at end of run. |
zebra.log |
Full stdout/stderr from the Zebra container. |
zaino.log |
Zaino indexer container logs (when backend=zaino). |
lightwalletd.log |
Lightwalletd container logs (when backend=lwd). |
faucet.log |
Faucet (Axum + Zingolib) container logs. |
containers.log |
docker ps -a snapshot. |
networks.log |
docker network ls snapshot. |
# List artifacts for a run
gh run view <run-id> --repo zecdev/ZecKit
# Download
gh run download <run-id> --repo zecdev/ZecKit -n zeckit-e2e-logs-<run-number>Symptom: ::error::Zebra did not become available within 10 minutes or similar for the faucet.
Cause: Pulling images took too long, or a container OOM-killed on runner.
Fix:
- Increase
startup_timeout_minutesto15or20. - Ensure the
ghcr_tokenhasread:packagesscope — without it, pulls silently fail and compose falls back to a slow local build. - Check that the runner has ≥ 4 GB RAM and ≥ 5 GB free disk.
Symptom: ::error::Insufficient Orchard balance (0 ZEC) to send 0.05 ZEC
Cause: The shield step was skipped (no transparent balance above fee threshold) but no pre-existing Orchard balance exists. Can happen on a very fresh chain where mining hasn't produced enough rewards yet.
Fix: Increase block_wait_seconds to allow more mining time, or increase startup_timeout_minutes so the fund step waits longer for rewards. For a self-hosted runner with slow networks, also try setting image_tag: main to skip auto-detection overhead.
Symptom: Shield step reports status: no_funds but subsequent send also fails.
Cause: Mining rewards are still pending (mempool not yet mined). Timing is probabilistic: Zebra mines every 30-60 s.
Fix: Increase block_wait_seconds to 120. This is safe — extra wait does not cause failures.
Symptom: ::error::Shielded send failed with status 'error'
Cause: Wallet's internal spendable notes haven't caught up after shielding. The wallet needs a sync after the shield block is confirmed.
Fix: The action already performs a sync between shield and send. If this still fails, increase block_wait_seconds to give more time before the sync.
Symptom: Action logs show No pre-built image found; docker compose will build locally (slow). and the job takes 20+ minutes.
Cause: The auto-detected tag doesn't match any published image. This happens on feature branches or forks where build-images.yml hasn't run yet.
Fix: Set image_tag: main explicitly to pull the latest stable images regardless of branch.
with:
image_tag: 'main'
ghcr_token: ${{ secrets.GITHUB_TOKEN }}Symptom: Lightwalletd startup exceeds the default 10-minute timeout.
Cause: Lightwalletd syncs slower than Zaino, especially on cold starts.
Fix: Use startup_timeout_minutes: '15' and block_wait_seconds: '90' when running with backend: lwd.
with:
backend: lwd
startup_timeout_minutes: '15'
block_wait_seconds: '90'
ghcr_token: ${{ secrets.GITHUB_TOKEN }}Symptom: bind: address already in use for ports 8080, 8232, or 9067.
Cause: A previous run left containers running on the same runner.
Fix: Add a cleanup step before the action in your workflow:
- name: Pre-clean ZecKit
run: |
docker compose -f /path/to/ZecKit/docker-compose.yml down --remove-orphans 2>/dev/null || true
docker stop zeckit-zebra zeckit-faucet 2>/dev/null || trueOr use docker run --network host alternatives. The action itself calls docker compose down at the end (if: always()), so subsequent runs on the same runner should not encounter this after the first cleanup.
Symptom: /bin/bash: jq: command not found
Cause: Minimal self-hosted runner image without standard utilities.
Fix: The action auto-installs jq and bc via apt-get if they are missing. If your runner doesn't have apt-get, pre-install them in your runner image.
Set the secret ACTIONS_STEP_DEBUG to true in your repo's Actions secrets to get verbose shell (set -x) output from every step.
This action is published at: https://github.com/marketplace/actions/zeckit-e2e
Required files for Marketplace listing:
action.yml(root of repo) — present ✓branding.icon/branding.color— set toshield/blue✓- Public repository — required for Marketplace visibility ✓
- This documentation linked from the repo README ✓
To publish a new version tag and update the Marketplace listing:
git tag v1.x.x
git push origin v1.x.x
# Then move the major-version floating tag:
git tag -fa v1 -m "Update v1 to v1.x.x"
git push origin v1 --force