Skip to content

Commit 039b2d6

Browse files
committed
Add simplex new and simplex example commands, improve generated test template, and expand README docs
New commands ------------ simplex new <name> Creates a complete Simplex project scaffold in a new <name>/ directory. Equivalent to running `simplex init --lib` in a freshly created directory, but with a better UX — errors immediately if the target directory already exists rather than clobbering existing files. simplex example <name> Scaffolds a full example project into a new directory. Currently supports `simplex example basic`, which writes all files from the examples/basic/ template (including the options, option_offer, array_tr_storage, bytes32_tr_storage, and dual_currency_deposit contracts) using include_str! so the content is embedded in the binary at compile time. The Cargo.toml is generated dynamically to pin to the latest published smplx-std version from crates.io, consistent with how `simplex new` works. Improved generated test template --------------------------------- The default test file produced by `simplex new` and `simplex init --lib` was previously a non-compiling todo!() stub. It is now a working end-to-end p2pk integration test that demonstrates: - Instantiating a program with typed arguments (P2pkArguments) - Deriving a script pubkey from the program - Funding the script via FinalTransaction + PartialOutput - Spending the script by supplying a P2pkWitness with DUMMY_SIGNATURE and RequiredSignature::Witness so the signer fills in the real signature The crate name (simplex_<project_name>) is substituted at generation time so the artifact imports resolve correctly for any project name. anyhow is now also added to the generated Cargo.toml to support the -> anyhow::Result<()> return type. README ------ - Replaced the `cargo add smplx-std` getting-started flow with `simplex new` as the primary entry point - Added `simplex new` and `simplex example` to the CLI command reference - Added a "Typical workflow" section showing the new -> build -> test flow - Added a "Using smplx-std as a library" section covering: dependency setup, how generated artifacts are structured, instantiating a program with arguments, funding and spending scripts with FinalTransaction, a key types reference table, and the #[simplex::test] macro - Updated the future work checklist to mark `simplex new` and `simplex example` as complete
1 parent 7f21210 commit 039b2d6

7 files changed

Lines changed: 329 additions & 19 deletions

File tree

README.md

Lines changed: 125 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -31,13 +31,23 @@ See the [simplexup manual](simplexup/README.md) for more details.
3131

3232
## Getting started
3333

34-
Add `smplx-std` dependency to cargo:
34+
Create a new Simplex project in a new directory:
3535

3636
```bash
37-
cargo add --dev smplx-std
37+
simplex new <name>
3838
```
3939

40-
Optionally, initialize a new project:
40+
This scaffolds a complete project with a `Simplex.toml`, `Cargo.toml`, a p2pk contract in `simf/p2pk.simf`, and a working integration test in `tests/p2pk_test.rs`.
41+
42+
To scaffold a full working example instead:
43+
44+
```bash
45+
simplex example basic
46+
```
47+
48+
This creates a `basic/` directory containing the complete basic example project, including several contract examples and an integration test you can run immediately after building.
49+
50+
Alternatively, initialize a Simplex project in the current directory:
4151

4252
```bash
4353
simplex init
@@ -97,7 +107,9 @@ Where:
97107

98108
Simplex CLI provides the following commands:
99109

100-
- `simplex init` - Initializes a new Simplex project.
110+
- `simplex new <name>` - Creates a new Simplex project in a new `<name>` directory.
111+
- `simplex example <name>` - Scaffolds a complete example project into a new directory (e.g. `simplex example basic`).
112+
- `simplex init` - Initializes a Simplex project in the current directory.
101113
- `simplex config` - Prints the current config.
102114
- `simplex build` - Generates simplicity artifacts.
103115
- `simplex regtest` - Spins up local Electrs + Elements nodes.
@@ -110,17 +122,123 @@ To view the available options, run the help command:
110122
simplex -h
111123
```
112124

113-
### Example
125+
### Typical workflow
126+
127+
```bash
128+
# Create a new project
129+
simplex new mycontract
130+
cd mycontract
131+
132+
# Build artifacts from .simf contracts
133+
simplex build
134+
135+
# Start a local regtest node and run integration tests
136+
simplex test integration
137+
```
138+
139+
### Using `smplx-std` as a library
140+
141+
`smplx-std` is the Rust library that backs your Simplex project. Add it to `Cargo.toml`:
142+
143+
```toml
144+
[dependencies]
145+
smplx-std = "x.y.z"
146+
```
147+
148+
Everything is re-exported from the `simplex` crate name:
149+
150+
```rust
151+
use simplex::transaction::{FinalTransaction, PartialInput, PartialOutput, ProgramInput, RequiredSignature};
152+
use simplex::utils::tr_unspendable_key;
153+
use simplex::constants::DUMMY_SIGNATURE;
154+
```
155+
156+
#### Building and spending a program
157+
158+
The generated artifacts for each `.simf` contract live in `src/artifacts/` after running `simplex build`. Each contract exposes a typed program struct, an arguments struct, and a witness struct:
159+
160+
```rust
161+
// Generated from simf/p2pk.simf
162+
use my_project::artifacts::p2pk::P2pkProgram;
163+
use my_project::artifacts::p2pk::derived_p2pk::{P2pkArguments, P2pkWitness};
164+
```
165+
166+
Instantiate the program by passing a Taproot internal key and the typed arguments:
167+
168+
```rust
169+
let arguments = P2pkArguments {
170+
public_key: signer.get_schnorr_public_key().unwrap().serialize(),
171+
};
172+
let program = P2pkProgram::new(tr_unspendable_key(), arguments);
173+
let script = program.get_program().get_script_pubkey(context.get_network()).unwrap();
174+
```
175+
176+
Fund the script by adding it as an output to a `FinalTransaction`:
177+
178+
```rust
179+
let mut ft = FinalTransaction::new(*context.get_network());
180+
ft.add_output(PartialOutput::new(script.clone(), 50, context.get_network().policy_asset()));
181+
let (tx, _) = signer.finalize(&ft).unwrap();
182+
provider.broadcast_transaction(&tx).unwrap();
183+
```
184+
185+
Spend from the script by constructing the witness and calling `add_program_input`. Use `DUMMY_SIGNATURE` as a placeholder — the signer replaces it with a real signature identified by the `RequiredSignature::Witness` name:
186+
187+
```rust
188+
let witness = P2pkWitness { signature: DUMMY_SIGNATURE };
189+
let mut ft = FinalTransaction::new(*context.get_network());
190+
ft.add_program_input(
191+
PartialInput::new(utxo_outpoint, utxo_txout),
192+
ProgramInput::new(Box::new(program.get_program().clone()), Box::new(witness)),
193+
RequiredSignature::Witness("SIGNATURE".to_string()),
194+
).unwrap();
195+
let (tx, _) = signer.finalize(&ft).unwrap();
196+
provider.broadcast_transaction(&tx).unwrap();
197+
```
198+
199+
#### Key types
200+
201+
| Type | Description |
202+
|---|---|
203+
| `FinalTransaction` | Transaction builder — holds inputs and outputs |
204+
| `PartialInput` | A UTXO to spend, identified by outpoint and `TxOut` |
205+
| `PartialOutput` | An output with script, amount, and asset |
206+
| `ProgramInput` | Pairs a compiled Simplicity program with its witness |
207+
| `RequiredSignature` | Tells the signer which witness field to fill (`Witness("NAME")`) |
208+
| `tr_unspendable_key()` | Returns the standard unspendable Taproot internal key used for Simplicity outputs |
209+
| `DUMMY_SIGNATURE` | 64-byte placeholder replaced by the signer at finalization time |
210+
211+
#### Test macro
212+
213+
Annotate integration test functions with `#[simplex::test]` to get an injected `TestContext` wired to the configured regtest or remote network:
214+
215+
```rust
216+
#[simplex::test]
217+
fn my_test(context: simplex::TestContext) -> anyhow::Result<()> {
218+
let signer = context.get_signer();
219+
let provider = context.get_provider();
220+
// ...
221+
Ok(())
222+
}
223+
```
224+
225+
Run tests with:
226+
227+
```bash
228+
simplex test integration
229+
```
230+
231+
### Examples
114232

115-
Check out the complete project examples in the `examples` directory to learn more.
233+
Check out the complete project examples in the `examples` directory, or scaffold one locally with `simplex example basic`.
116234

117235
## Contributing
118236

119237
We are open to any mind-blowing ideas! Please take a look at our [contributing guidelines](CONTRIBUTING.md) to get involved.
120238

121239
## Future work
122240

123-
- [x] Complete `simplex init` and `simplex clean` tasks.
241+
- [x] Complete `simplex init`, `simplex new`, `simplex example`, and `simplex clean` commands.
124242
- [ ] SDK support for confidential assets, taproot signer, and custom witness signatures.
125243
- [ ] Local regtest 10x speedup.
126244
- [ ] Regtest cheat codes.

crates/cli/src/cli.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@ use clap::Parser;
55
use crate::commands::Command;
66
use crate::commands::build::Build;
77
use crate::commands::clean::Clean;
8+
use crate::commands::example::Example;
89
use crate::commands::init::Init;
10+
use crate::commands::new::New;
911
use crate::commands::regtest::Regtest;
1012
use crate::commands::test::Test;
1113
use crate::config::Config;
@@ -24,6 +26,8 @@ pub struct Cli {
2426
impl Cli {
2527
pub async fn run(&self) -> Result<(), CliError> {
2628
match &self.command {
29+
Command::New { name } => Ok(New::run(name)?),
30+
Command::Example { example } => Ok(Example::run(example)?),
2731
Command::Init { additional_flags } => {
2832
let simplex_conf_path = Config::get_default_path()?;
2933

crates/cli/src/commands/core.rs

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,17 @@
1-
use clap::{Args, Subcommand};
1+
use clap::{Args, Subcommand, ValueEnum};
22

33
#[derive(Debug, Subcommand)]
44
pub enum Command {
5+
/// Creates a new Simplex project in a new directory
6+
New {
7+
/// Name of the new project
8+
name: String,
9+
},
10+
/// Scaffolds an example Simplex project into a new directory
11+
Example {
12+
/// Name of the example to scaffold
13+
example: ExampleName,
14+
},
515
/// Initializes Simplex project
616
Init {
717
#[command(flatten)]
@@ -22,6 +32,12 @@ pub enum Command {
2232
Clean,
2333
}
2434

35+
#[derive(Debug, Clone, ValueEnum)]
36+
pub enum ExampleName {
37+
/// Basic p2pk example with contract scaffolding
38+
Basic,
39+
}
40+
2541
#[derive(Debug, Subcommand)]
2642
pub enum TestCommand {
2743
/// Runs integration tests

crates/cli/src/commands/example.rs

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
use std::path::PathBuf;
2+
3+
use crate::commands::ExampleName;
4+
use crate::commands::error::{CommandError, InitError};
5+
use crate::commands::init::{Init, SIMPLEX_CRATE_NAME};
6+
7+
pub struct Example;
8+
9+
impl Example {
10+
pub fn run(example: &ExampleName) -> Result<(), CommandError> {
11+
match example {
12+
ExampleName::Basic => Self::create_basic()?,
13+
}
14+
Ok(())
15+
}
16+
17+
fn create_basic() -> Result<(), InitError> {
18+
let dir: PathBuf = std::env::current_dir().map_err(InitError::FmtError)?.join("basic");
19+
20+
if dir.exists() {
21+
return Err(InitError::CreateDirs(
22+
std::io::Error::new(std::io::ErrorKind::AlreadyExists, "destination 'basic' already exists"),
23+
dir,
24+
));
25+
}
26+
27+
let smplx_version = Init::get_smplx_max_version()?;
28+
29+
// Generate Cargo.toml dynamically with the latest smplx-std version
30+
let manifest = {
31+
let mut manifest = toml_edit::DocumentMut::new();
32+
manifest["package"] = toml_edit::Item::Table(toml_edit::Table::new());
33+
manifest["package"]["name"] = toml_edit::value("simplex_example");
34+
manifest["package"]["version"] = toml_edit::value("0.1.0");
35+
manifest["package"]["edition"] = toml_edit::value("2024");
36+
manifest["package"]["rust-version"] = toml_edit::value("1.91.0");
37+
38+
let mut dep_table = toml_edit::Table::default();
39+
dep_table.insert(
40+
SIMPLEX_CRATE_NAME,
41+
toml_edit::Item::Value(toml_edit::Value::String(toml_edit::Formatted::new(smplx_version))),
42+
);
43+
dep_table.insert(
44+
"anyhow",
45+
toml_edit::Item::Value(toml_edit::Value::String(toml_edit::Formatted::new("1".to_string()))),
46+
);
47+
manifest["dependencies"] = toml_edit::Item::Table(dep_table);
48+
manifest
49+
};
50+
51+
Init::write_to_file(dir.join("Cargo.toml"), manifest.to_string())?;
52+
Init::write_to_file(
53+
dir.join("Simplex.toml"),
54+
include_str!("../../../../examples/basic/Simplex.toml"),
55+
)?;
56+
Init::write_to_file(
57+
dir.join(".gitignore"),
58+
include_str!("../../../../examples/basic/.gitignore"),
59+
)?;
60+
Init::write_to_file(
61+
dir.join("src/lib.rs"),
62+
include_str!("../../../../examples/basic/src/lib.rs"),
63+
)?;
64+
Init::write_to_file(
65+
dir.join("tests/example_test.rs"),
66+
include_str!("../../../../examples/basic/tests/example_test.rs"),
67+
)?;
68+
Init::write_to_file(
69+
dir.join("simf/p2pk.simf"),
70+
include_str!("../../../../examples/basic/simf/p2pk.simf"),
71+
)?;
72+
Init::write_to_file(
73+
dir.join("simf/options.simf"),
74+
include_str!("../../../../examples/basic/simf/options.simf"),
75+
)?;
76+
Init::write_to_file(
77+
dir.join("simf/module/option_offer.simf"),
78+
include_str!("../../../../examples/basic/simf/module/option_offer.simf"),
79+
)?;
80+
Init::write_to_file(
81+
dir.join("simf/another_dir/array_tr_storage.simf"),
82+
include_str!("../../../../examples/basic/simf/another_dir/array_tr_storage.simf"),
83+
)?;
84+
Init::write_to_file(
85+
dir.join("simf/another_dir/another_module/bytes32_tr_storage.simf"),
86+
include_str!("../../../../examples/basic/simf/another_dir/another_module/bytes32_tr_storage.simf"),
87+
)?;
88+
Init::write_to_file(
89+
dir.join("simf/another_dir/another_module/dual_currency_deposit.simf"),
90+
include_str!("../../../../examples/basic/simf/another_dir/another_module/dual_currency_deposit.simf"),
91+
)?;
92+
93+
println!("Created example project 'basic'");
94+
println!(
95+
"Run `simplex build` inside 'basic/' to generate artifacts, then `simplex test integration` to run the tests."
96+
);
97+
98+
Ok(())
99+
}
100+
}

0 commit comments

Comments
 (0)