1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
use clap::{Parser, Subcommand};
use colored::*;
use std::process;
mod commands;
mod docker;
mod config;
mod error;
mod utils;
pub mod assets;
#[derive(Parser)]
#[command(name = "zeckit")]
#[command(about = "ZecKit - Developer toolkit for Zcash on Zebra", long_about = None)]
#[command(version)]
struct Cli {
/// Path to the ZecKit project root (overrides auto-detection)
#[arg(long, global = true)]
project_dir: Option<String>,
#[command(subcommand)]
command: Commands,
}
#[derive(Subcommand)]
enum Commands {
/// Start the ZecKit devnet
Up {
/// Light-client backend: lwd (lightwalletd) or zaino
#[arg(short, long, default_value = "lwd")]
backend: String,
/// Force fresh start (remove volumes)
#[arg(short, long)]
fresh: bool,
/// Startup timeout in minutes
#[arg(long, default_value = "10")]
timeout: u64,
/// Run in action mode (generate artifacts)
#[arg(long)]
action_mode: bool,
/// Custom transparent address for Zebra mining rewards (overrides embedded default)
#[arg(long)]
miner_address: Option<String>,
/// Address to automatically fund after the devnet is healthy
#[arg(long)]
fund_address: Option<String>,
/// Amount in ZEC to send to --fund-address (default: 10.0)
#[arg(long, default_value = "10.0")]
fund_amount: f64,
/// Custom Docker image prefix (e.g. ghcr.io/user/repo)
#[arg(long, default_value = "ghcr.io/intellidean/zeckit")]
image_prefix: Option<String>,
/// Custom block mining interval in seconds
#[arg(long, default_value = "15")]
block_interval: u64,
/// Custom activation heights in key=value format (e.g. nu5=1,nu6=10)
#[arg(long)]
activation_heights: Option<String>,
},
/// Stop the ZecKit devnet
Down {
/// Remove volumes (clean slate)
#[arg(short, long)]
purge: bool,
},
/// Show devnet status
Status,
/// Run smoke tests
Test {
/// Amount to send in E2E test
#[arg(long, default_value = "0.05")]
amount: f64,
/// Memo to use for E2E test
#[arg(long, default_value = "ZecKit E2E Transaction")]
memo: String,
/// Run in action mode (generate artifacts)
#[arg(long)]
action_mode: bool,
/// Only run granular health checks (Docker network, Faucet API, Zebra RPC) and exit before transactions
#[arg(long)]
check: bool,
},
/// Initialize a GitHub Actions workflow for this project
#[command(long_about = "Generates a standardized GitHub Actions workflow (.github/workflows/zeckit-e2e.yml) that automatically spins up a 2-node Zebra cluster, configured with your choice of privacy backend and an embedded shielded faucet.")]
Init {
/// Light-client backend to use in CI: lwd (lightwalletd) or zaino
#[arg(short, long, default_value = "lwd", value_parser = ["zaino", "lwd"])]
backend: String,
/// Force overwrite of an existing workflow file
#[arg(short, long)]
force: bool,
/// Custom file path for the generated workflow (e.g. .github/workflows/custom.yml)
#[arg(short, long)]
output: Option<String>,
},
/// Manage blockchain state snapshots
Snapshot {
#[command(subcommand)]
action: SnapshotAction,
},
}
#[derive(Subcommand)]
pub enum SnapshotAction {
/// Create a new snapshot of the devnet state
Create {
/// Name of the snapshot
name: String,
},
/// Restore a snapshot of the devnet state
Restore {
/// Name of the snapshot to restore
name: String,
},
/// List available snapshots
List,
/// Delete a snapshot
Delete {
/// Name of the snapshot to delete
name: String,
},
}
#[tokio::main]
async fn main() {
let cli = Cli::parse();
let result = match cli.command {
Commands::Up { backend, fresh, timeout, action_mode, miner_address, fund_address, fund_amount, image_prefix, block_interval, activation_heights } => {
commands::up::execute(backend, fresh, timeout, action_mode, miner_address, fund_address, fund_amount, cli.project_dir, image_prefix, block_interval, activation_heights).await
}
Commands::Down { purge } => {
commands::down::execute(purge, cli.project_dir).await
}
Commands::Status => {
commands::status::execute(cli.project_dir).await
}
Commands::Test { amount, memo, action_mode, check } => {
commands::test::execute(amount, memo, action_mode, check, cli.project_dir).await
}
Commands::Init { backend, force, output } => {
commands::init::execute(backend, force, output, cli.project_dir).await
}
Commands::Snapshot { action } => {
commands::snapshot::execute(action, cli.project_dir).await
}
};
if let Err(e) = result {
eprintln!("{} {}", "Error:".red().bold(), e);
process::exit(1);
}
}