Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
31caae4
requirements not required, use address_map
brady-stacks Aug 27, 2026
b02f060
fix requirements->address_map and add auto detecting tests
brady-stacks Aug 27, 2026
bcb4a86
remove address_map to simplify feature
brady-stacks Aug 31, 2026
5b651ae
fix: auto-detect resolved deps and filter user contracts from auto-de…
brady-stacks Aug 31, 2026
bee55db
Add failing tests to showcase issues
jbencin-stacks Sep 9, 2026
beaf83a
clean up autodetection tests and fix
brady-stacks Sep 10, 2026
5d1c6a2
Add test for external trait argument
jbencin-stacks Sep 10, 2026
a5da596
Add test for broken JSON ser/deser roundtrip
jbencin-stacks Sep 10, 2026
a5688df
fix autodetection cycles and use indoc
brady-stacks Sep 11, 2026
20b0f85
simplify passes and test mnemonic
brady-stacks Sep 30, 2026
3280254
add regression tests for requirements backward compat
brady-stacks Sep 30, 2026
eead969
add test for requirement fetch failure
brady-stacks Oct 1, 2026
9da6dab
fix testnet sbtc detection
brady-stacks Oct 1, 2026
1e7649a
fix with-nft/ft traversal and other small bugs
brady-stacks Oct 1, 2026
7ed6c7c
fix contract-hash? and enhance logging
brady-stacks Oct 1, 2026
c040bfe
more tests and cleanup
brady-stacks Oct 1, 2026
29ee22a
remove requirements cli command
brady-stacks Oct 2, 2026
d6ff6bf
use indoc and avoid self ref
brady-stacks Oct 6, 2026
9f45053
fix trait args inside exprs
brady-stacks Oct 6, 2026
1ed154f
simplify collect_contract_principals
brady-stacks Oct 6, 2026
f552b07
contract-hash regression test and avoid ast rebuild
brady-stacks Oct 6, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ Clarinet is the fastest way to build, test, and deploy smart contracts on the St
gives you a local devnet, REPL, testing framework, and debugging tools to ship high-quality Clarity
code with confidence.

- 🧑‍💻 **Leverage a powerful CLI:** Create new projects, manage your smart contracts and their
dependencies using clarinet requirements, and interact with your code through the built-in REPL.
- 🧑‍💻 **Leverage a powerful CLI:** Create new projects, manage your smart contracts with automatic
dependency detection, and interact with your code through the built-in REPL.

- 🧪 **Write unit tests with the SDK:** Use the Clarinet SDK to write unit tests in a familiar JS
environment and validate contract behavior.
Expand Down
1 change: 1 addition & 0 deletions components/clarinet-cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ tikv-jemallocator = { workspace = true }

[dev-dependencies]
futures = { workspace = true }
mockito = { workspace = true }
tempfile = { workspace = true }
toml = { workspace = true }
tower = { version = "0.5", features = ["util"] }
Expand Down
262 changes: 16 additions & 246 deletions components/clarinet-cli/src/frontend/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,7 @@ use clarinet_deployments::{
use clarinet_files::clarinetrc::ClarinetRC;
use clarinet_files::devnet_diff::DevnetDiffConfig;
use clarinet_files::{
get_manifest_location, paths, NetworkManifest, ProjectManifest, RequirementConfig,
StacksNetwork,
get_manifest_location, paths, NetworkManifest, ProjectManifest, StacksNetwork,
};
use clarinet_format::formatter::{self, ClarityFormatter};
use clarity::types::StacksEpochId;
Expand Down Expand Up @@ -99,9 +98,6 @@ enum Command {
/// Subcommands for working with contracts
#[clap(subcommand, name = "contracts", aliases = &["contract"])]
Contracts(Contracts),
/// Interact with contracts deployed on Mainnet
#[clap(subcommand, name = "requirements", aliases = &["requirement"])]
Requirements(Requirements),
/// Manage contracts deployments on Simnet/Devnet/Testnet/Mainnet
#[clap(subcommand, name = "deployments", aliases = &["deployment"])]
Deployments(Deployments),
Expand Down Expand Up @@ -260,13 +256,6 @@ enum Contracts {
RemoveContract(RemoveContract),
}

#[derive(Subcommand, PartialEq, Clone, Debug)]
enum Requirements {
/// Interact with contracts deployed on Mainnet
#[clap(name = "add", bin_name = "add")]
AddRequirement(AddRequirement),
}

#[allow(clippy::enum_variant_names)]
#[derive(Subcommand, PartialEq, Clone, Debug)]
enum Deployments {
Expand Down Expand Up @@ -328,15 +317,6 @@ struct RemoveContract {
pub manifest_path: Option<String>,
}

#[derive(Parser, PartialEq, Clone, Debug)]
struct AddRequirement {
/// Contract id (ex. "SP2PABAF9FTAJYNFZH93XENAJ8FVY99RRM50D2JG9.nft-trait")
pub contract_id: String,
/// Path to Clarinet.toml
#[clap(long = "manifest-path", short = 'm')]
pub manifest_path: Option<String>,
}

#[derive(Parser, PartialEq, Clone, Debug)]
struct CheckDeployments {
/// Path to Clarinet.toml
Expand Down Expand Up @@ -1106,31 +1086,6 @@ pub fn main() {
}
}
},
Command::Requirements(subcommand) => match subcommand {
Requirements::AddRequirement(cmd) => {
let manifest = load_manifest_or_exit(cmd.manifest_path, true);

let change = TOMLEdition {
comment: format!(
"{} with requirement {}",
yellow!("Updated Clarinet.toml"),
green!("{}", cmd.contract_id)
),
manifest_location: manifest.location,
contracts_to_rm: vec![],
contracts_to_add: HashMap::new(),
requirements_to_add: vec![RequirementConfig {
contract_id: cmd.contract_id,
}],
};
if !execute_changes(vec![Changes::EditTOML(change)]) {
std::process::exit(1);
}
if clarinetrc.enable_hints.unwrap_or(true) {
display_post_check_hint();
}
}
},
Command::Console(cmd) => {
// Loop to handle `::reload` command
loop {
Expand Down Expand Up @@ -2074,10 +2029,6 @@ fn load_manifest(location: &Path) -> Option<DocumentMut> {

/// Edit a TOML document directly, preserving comments and structure.
fn edit_toml_document(mut doc: DocumentMut, options: &mut TOMLEdition) -> DocumentMut {
for req in options.requirements_to_add.drain(..) {
add_requirement_to_doc(&mut doc, &req.contract_id);
}

for (name, contract) in options.contracts_to_add.drain() {
add_contract_to_doc(&mut doc, &name, &contract);
}
Expand All @@ -2089,45 +2040,6 @@ fn edit_toml_document(mut doc: DocumentMut, options: &mut TOMLEdition) -> Docume
doc
}

/// Add a requirement to the [[project.requirements]] array in the document.
fn add_requirement_to_doc(doc: &mut DocumentMut, contract_id: &str) {
use toml_edit::{ArrayOfTables, Item, Table};

// Ensure [project] table exists
let project = doc
.entry("project")
.or_insert(Item::Table(Table::new()))
.as_table_mut()
.expect("[project] should be a table");

// Ensure [[project.requirements]] array exists.
// If requirements = [] (an empty inline array), replace it with an array of tables.
if project
.get("requirements")
.is_some_and(|v| v.as_array().is_some_and(|a| a.is_empty()))
{
project["requirements"] = Item::ArrayOfTables(ArrayOfTables::new());
}

let requirements = project
.entry("requirements")
.or_insert(Item::ArrayOfTables(ArrayOfTables::new()))
.as_array_of_tables_mut()
.expect("[[project.requirements]] should be an array of tables");

// Check for duplicates
let already_exists = requirements
.iter()
.filter_map(|req| req.get("contract_id")?.as_str())
.any(|id| id == contract_id);

if !already_exists {
let mut new_req = Table::new();
new_req["contract_id"] = toml_edit::value(contract_id);
requirements.push(new_req);
}
}

/// Add a contract to the [contracts.<name>] section in the document.
fn add_contract_to_doc(doc: &mut DocumentMut, name: &str, contract: &ClarityContract) {
use toml_edit::{Item, Table};
Expand Down Expand Up @@ -2550,16 +2462,21 @@ mod tests {
/// Helper to check if a requirement exists in the TOML
fn has_requirement(content: &str, contract_id: &str) -> bool {
let doc: DocumentMut = content.parse().expect("Failed to parse TOML");
if let Some(project) = doc.get("project").and_then(|p| p.as_table()) {
if let Some(requirements) = project.get("requirements") {
if let Some(arr) = requirements.as_array_of_tables() {
return arr.iter().any(|req| {
req.get("contract_id")
.and_then(|v| v.as_str())
.map(|s| s == contract_id)
.unwrap_or(false)
});
}
let Some(project) = doc.get("project").and_then(|p| p.as_table()) else {
return false;
};
if let Some(arr) = project
.get("requirements")
.and_then(|v| v.as_array_of_tables())
{
if arr.iter().any(|entry| {
entry
.get("contract_id")
.and_then(|v| v.as_str())
.map(|s| s == contract_id)
.unwrap_or(false)
}) {
return true;
}
}
false
Expand Down Expand Up @@ -2664,153 +2581,6 @@ mod tests {
);
}

#[test]
fn test_add_requirement_preserves_comments() {
let input = indoc! {r#"
[project]
name = "test-project"
description = "A test project"
authors = ["Test Author"]
telemetry = false

# This comment should survive

[contracts.my-contract]
path = "contracts/my-contract.clar"
clarity_version = 2
epoch = "latest"
"#};

let mut doc: DocumentMut = input.parse().expect("Failed to parse TOML");
add_requirement_to_doc(
&mut doc,
"SP2PABAF9FTAJYNFZH93XENAJ8FVY99RRM50D2JG9.nft-trait",
);

let output = doc.to_string();

// Verify the requirement was added
assert!(
has_requirement(
&output,
"SP2PABAF9FTAJYNFZH93XENAJ8FVY99RRM50D2JG9.nft-trait"
),
"Requirement should be added"
);

// Verify comment is preserved
assert!(
contains_comment(&output, "This comment should survive"),
"Comment should be preserved"
);

// Verify existing settings are preserved
assert!(
has_toml_value(&output, "project.name", "test-project"),
"Project name should be preserved"
);
assert!(
has_toml_value(&output, "project.telemetry", "false"),
"Telemetry setting should be preserved"
);

// Verify contract is still there
assert!(
has_contract(&output, "my-contract"),
"Contract should be preserved"
);
}

#[test]
fn test_add_requirement_with_empty_requirements_array() {
let input = indoc! {r#"
[project]
name = 'project-template'
requirements = []
"#};

let mut doc: DocumentMut = input.parse().expect("Failed to parse TOML");
add_requirement_to_doc(
&mut doc,
"SP2PABAF9FTAJYNFZH93XENAJ8FVY99RRM50D2JG9.nft-trait",
);

let output = doc.to_string();

assert!(
has_requirement(
&output,
"SP2PABAF9FTAJYNFZH93XENAJ8FVY99RRM50D2JG9.nft-trait"
),
"Requirement should be added when requirements was an empty array"
);

assert!(
has_toml_value(&output, "project.name", "project-template"),
"Project name should be preserved"
);
}

#[test]
fn test_add_requirement_does_not_duplicate() {
let input = indoc! {r#"
[project]
name = "test-project"

[[project.requirements]]
contract_id = "SP2PABAF9FTAJYNFZH93XENAJ8FVY99RRM50D2JG9.nft-trait"
"#};

let mut doc: DocumentMut = input.parse().expect("Failed to parse TOML");

// Try to add the same requirement again
add_requirement_to_doc(
&mut doc,
"SP2PABAF9FTAJYNFZH93XENAJ8FVY99RRM50D2JG9.nft-trait",
);

let output = doc.to_string();

// Count how many times the contract_id appears
let count = output.matches("nft-trait").count();
assert_eq!(count, 1, "Requirement should not be duplicated");
}

#[test]
fn test_add_multiple_requirements() {
let input = indoc! {r#"
[project]
name = "test-project"

[[project.requirements]]
contract_id = "SP2PABAF9FTAJYNFZH93XENAJ8FVY99RRM50D2JG9.nft-trait"
"#};

let mut doc: DocumentMut = input.parse().expect("Failed to parse TOML");
add_requirement_to_doc(
&mut doc,
"SP3K8BC0PPEVCV7NZ6QSRWPQ2JE9E5B6N3PA0KBR9.another-trait",
);

let output = doc.to_string();

// Both requirements should exist
assert!(
has_requirement(
&output,
"SP2PABAF9FTAJYNFZH93XENAJ8FVY99RRM50D2JG9.nft-trait"
),
"Original requirement should be preserved"
);
assert!(
has_requirement(
&output,
"SP3K8BC0PPEVCV7NZ6QSRWPQ2JE9E5B6N3PA0KBR9.another-trait"
),
"New requirement should be added"
);
}

#[test]
fn test_edit_simple_nft_example() {
// Test with the actual simple-nft example content
Expand Down
2 changes: 0 additions & 2 deletions components/clarinet-cli/src/generate/changes.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
use std::collections::HashMap;
use std::path::PathBuf;

use clarinet_files::RequirementConfig;
use clarity_repl::repl::ClarityContract;

#[derive(Clone, Debug)]
Expand Down Expand Up @@ -29,7 +28,6 @@ pub struct TOMLEdition {
pub manifest_location: PathBuf,
pub contracts_to_add: HashMap<String, ClarityContract>,
pub contracts_to_rm: Vec<String>,
pub requirements_to_add: Vec<RequirementConfig>,
}

#[derive(Clone, Debug)]
Expand Down
2 changes: 0 additions & 2 deletions components/clarinet-cli/src/generate/contract.rs
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,6 @@ impl GetChangesForRmContract {
manifest_location,
contracts_to_rm,
contracts_to_add: HashMap::new(),
requirements_to_add: vec![],
};
self.changes.push(Changes::EditTOML(change));
}
Expand Down Expand Up @@ -219,7 +218,6 @@ impl GetChangesForNewContract {
manifest_location,
contracts_to_rm: vec![],
contracts_to_add,
requirements_to_add: vec![],
};
self.changes.push(Changes::EditTOML(change));
}
Expand Down
Loading
Loading