Skip to content

Commit 3916464

Browse files
Display deploy hint command after sncast declare (#3771)
<!-- Reference any GitHub issues resolved by this PR --> Closes #2758 ## Introduced changes `sncast declare` command now outputs a ready-to-use deployment command after successful declaration. ## Checklist <!-- Make sure all of these are complete --> - [x] Linked relevant issue - [x] Updated relevant documentation - [x] Added relevant tests - [x] Performed self-review of the code - [x] Added changes to `CHANGELOG.md`
1 parent 1aeb828 commit 3916464

File tree

12 files changed

+253
-19
lines changed

12 files changed

+253
-19
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1818
#### Added
1919

2020
- Debug logging for `sncast` commands that can be enabled by setting `CAST_LOG` env variable.
21+
- `sncast declare` command now outputs a ready-to-use deployment command after successful declaration.
2122

2223
## [0.50.0] - 2025-09-29
2324

crates/sncast/src/helpers/rpc.rs

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ impl RpcArgs {
4545
}
4646

4747
#[must_use]
48-
fn get_url(&self, config_url: &String) -> Option<String> {
48+
pub fn get_url(&self, config_url: &String) -> Option<String> {
4949
if let Some(network) = self.network {
5050
let free_provider = FreeProvider::semi_random();
5151
Some(network.url(&free_provider))
@@ -98,6 +98,17 @@ impl Network {
9898
}
9999
}
100100

101+
#[must_use]
102+
pub fn generate_network_flag(rpc_url: Option<&str>, network: Option<&Network>) -> String {
103+
if let Some(network) = network {
104+
format!("--network {network}")
105+
} else if let Some(rpc_url) = rpc_url {
106+
format!("--url {rpc_url}")
107+
} else {
108+
unreachable!("Either `--rpc_url` or `--network` must be provided.")
109+
}
110+
}
111+
101112
#[cfg(test)]
102113
mod tests {
103114
use super::*;

crates/sncast/src/main.rs

Lines changed: 32 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,11 @@ use sncast::helpers::config::{combine_cast_configs, get_global_config_path};
2020
use sncast::helpers::configuration::CastConfig;
2121
use sncast::helpers::constants::DEFAULT_ACCOUNTS_FILE;
2222
use sncast::helpers::output_format::output_format_from_json_flag;
23+
use sncast::helpers::rpc::generate_network_flag;
2324
use sncast::helpers::scarb_utils::{
2425
BuildConfig, assert_manifest_path_exists, build_and_load_artifacts, get_package_metadata,
2526
};
26-
use sncast::response::declare::DeclareResponse;
27+
use sncast::response::declare::{DeclareResponse, DeployCommandMessage};
2728
use sncast::response::errors::handle_starknet_command_error;
2829
use sncast::response::explorer_link::block_explorer_link_if_allowed;
2930
use sncast::response::transformed_call::transform_response;
@@ -32,7 +33,7 @@ use sncast::{
3233
get_contract_class,
3334
};
3435
use starknet::core::types::ContractClass;
35-
use starknet::core::types::contract::AbiEntry;
36+
use starknet::core::types::contract::{AbiEntry, SierraClass};
3637
use starknet::core::utils::get_selector_from_name;
3738
use starknet::providers::Provider;
3839
use starknet_commands::verify::Verify;
@@ -289,7 +290,7 @@ async fn run_async_command(cli: Cli, config: CastConfig, ui: &UI) -> Result<()>
289290
.expect("Failed to build contract");
290291

291292
let result = starknet_commands::declare::declare(
292-
declare,
293+
&declare,
293294
&account,
294295
&artifacts,
295296
wait_config,
@@ -309,8 +310,36 @@ async fn run_async_command(cli: Cli, config: CastConfig, ui: &UI) -> Result<()>
309310

310311
let block_explorer_link =
311312
block_explorer_link_if_allowed(&result, provider.chain_id().await?, &rpc, &config);
313+
314+
let deploy_command_message = if let Ok(response) = &result {
315+
// TODO(#3785)
316+
let contract_artifacts = artifacts
317+
.get(&declare.contract.clone())
318+
.expect("Failed to get contract artifacts");
319+
let contract_definition: SierraClass =
320+
serde_json::from_str(&contract_artifacts.sierra)
321+
.context("Failed to parse sierra artifact")?;
322+
let network_flag = generate_network_flag(
323+
rpc.get_url(&config.url).as_deref(),
324+
rpc.network.as_ref(),
325+
);
326+
Some(DeployCommandMessage::new(
327+
&contract_definition.abi,
328+
response,
329+
&config.account,
330+
&config.accounts_file,
331+
network_flag,
332+
))
333+
} else {
334+
None
335+
};
336+
312337
process_command_result("declare", result, ui, block_explorer_link);
313338

339+
if let Some(deploy_command_message) = deploy_command_message {
340+
ui.println(&deploy_command_message?);
341+
}
342+
314343
Ok(())
315344
}
316345

crates/sncast/src/response/declare.rs

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
use super::{command::CommandResponse, explorer_link::OutputLink};
22
use crate::helpers::block_explorer::LinkProvider;
33
use crate::response::cast_message::SncastMessage;
4+
use anyhow::Error;
5+
use camino::Utf8PathBuf;
46
use conversions::string::IntoHexStr;
57
use conversions::{padded_felt::PaddedFelt, serde::serialize::CairoSerialize};
68
use foundry_ui::Message;
@@ -9,6 +11,8 @@ use indoc::formatdoc;
911
use serde::{Deserialize, Serialize};
1012
use serde_json::Value;
1113
use serde_json::json;
14+
use starknet::core::types::contract::{AbiConstructor, AbiEntry};
15+
use std::fmt::Write;
1216
#[derive(Clone, Serialize, Deserialize, CairoSerialize, Debug, PartialEq)]
1317
pub struct DeclareTransactionResponse {
1418
pub class_hash: PaddedFelt,
@@ -129,3 +133,118 @@ impl OutputLink for DeclareTransactionResponse {
129133
)
130134
}
131135
}
136+
137+
#[derive(Clone, Serialize, Deserialize, Debug, PartialEq)]
138+
pub struct DeployCommandMessage {
139+
accounts_file: Option<String>,
140+
account: String,
141+
class_hash: PaddedFelt,
142+
arguments_flag: Option<String>,
143+
network_flag: String,
144+
}
145+
146+
impl DeployCommandMessage {
147+
pub fn new(
148+
abi: &[AbiEntry],
149+
response: &DeclareTransactionResponse,
150+
account: &str,
151+
accounts_file: &Utf8PathBuf,
152+
network_flag: String,
153+
) -> Result<Self, Error> {
154+
let arguments_flag: Option<String> = generate_arguments_flag(abi);
155+
let accounts_file_str = accounts_file.to_string();
156+
let accounts_file = (!accounts_file_str
157+
.contains("starknet_accounts/starknet_open_zeppelin_accounts.json"))
158+
.then_some(accounts_file_str);
159+
160+
Ok(Self {
161+
account: account.to_string(),
162+
accounts_file,
163+
class_hash: response.class_hash,
164+
arguments_flag,
165+
network_flag,
166+
})
167+
}
168+
}
169+
170+
impl Message for DeployCommandMessage {
171+
fn text(&self) -> String {
172+
let mut command = String::from("sncast");
173+
174+
let accounts_file_flag = generate_accounts_file_flag(self.accounts_file.as_ref());
175+
if let Some(flag) = accounts_file_flag {
176+
write!(command, " {flag}").unwrap();
177+
}
178+
179+
let account_flag = format!("--account {}", self.account);
180+
write!(command, " {account_flag}").unwrap();
181+
182+
write!(command, " deploy").unwrap();
183+
184+
write!(
185+
command,
186+
" --class-hash {}",
187+
self.class_hash.into_hex_string()
188+
)
189+
.unwrap();
190+
191+
if let Some(arguments) = &self.arguments_flag {
192+
write!(command, " {arguments}").unwrap();
193+
}
194+
195+
write!(command, " {}", self.network_flag).unwrap();
196+
197+
let header = if self.arguments_flag.is_some() {
198+
"To deploy a contract of this class, replace the placeholders in `--arguments` with your actual values, then run:"
199+
} else {
200+
"To deploy a contract of this class, run:"
201+
};
202+
203+
formatdoc!(
204+
"
205+
{header}
206+
{command}
207+
"
208+
)
209+
}
210+
211+
fn json(&self) -> Value {
212+
// This message is only helpful in human mode, we don't need it in JSON mode.
213+
Value::Null
214+
}
215+
}
216+
217+
fn generate_constructor_placeholder_arguments(constructor: AbiConstructor) -> String {
218+
constructor
219+
.inputs
220+
.into_iter()
221+
.map(|input| {
222+
let input_type = input
223+
.r#type
224+
.split("::")
225+
.last()
226+
.expect("Failed to get last part of input type");
227+
format!("<{}: {})>", input.name, input_type)
228+
})
229+
.collect::<Vec<String>>()
230+
.join(", ")
231+
}
232+
233+
fn generate_arguments_flag(abi: &[AbiEntry]) -> Option<String> {
234+
let arguments = abi.iter().find_map(|entry| {
235+
if let AbiEntry::Constructor(constructor) = entry {
236+
let arguments = generate_constructor_placeholder_arguments(constructor.clone());
237+
(!arguments.is_empty()).then_some(arguments)
238+
} else {
239+
None
240+
}
241+
});
242+
243+
arguments.map(|arguments| format!("--arguments '{arguments}'"))
244+
}
245+
246+
fn generate_accounts_file_flag(accounts_file: Option<&String>) -> Option<String> {
247+
accounts_file
248+
.as_ref()
249+
.map(|file| format!("--accounts-file {file}"))
250+
}

crates/sncast/src/starknet_commands/account/create.rs

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ use sncast::helpers::constants::{
1515
BRAAVOS_BASE_ACCOUNT_CLASS_HASH, BRAAVOS_CLASS_HASH, CREATE_KEYSTORE_PASSWORD_ENV_VAR,
1616
OZ_CLASS_HASH, READY_CLASS_HASH,
1717
};
18-
use sncast::helpers::rpc::RpcArgs;
18+
use sncast::helpers::rpc::{RpcArgs, generate_network_flag};
1919
use sncast::response::account::create::AccountCreateResponse;
2020
use sncast::{
2121
AccountType, Network, check_class_hash_exists, check_if_legacy_contract,
@@ -336,16 +336,6 @@ fn write_account_to_file(
336336
Ok(())
337337
}
338338

339-
fn generate_network_flag(rpc_url: Option<&str>, network: Option<&Network>) -> String {
340-
if let Some(rpc_url) = rpc_url {
341-
format!("--url {rpc_url}")
342-
} else if let Some(network) = network {
343-
format!("--network {network}")
344-
} else {
345-
unreachable!("Either `--rpc_url` or `--network` must be provided.")
346-
}
347-
}
348-
349339
fn generate_deploy_command(
350340
accounts_file: &Utf8PathBuf,
351341
rpc_url: Option<&str>,

crates/sncast/src/starknet_commands/declare.rs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -47,8 +47,9 @@ pub struct Declare {
4747
pub rpc: RpcArgs,
4848
}
4949

50+
// TODO(#3785)
5051
pub async fn declare(
51-
declare: Declare,
52+
declare: &Declare,
5253
account: &SingleOwnerAccount<&JsonRpcClient<HttpTransport>, LocalWallet>,
5354
artifacts: &HashMap<String, StarknetContractArtifacts>,
5455
wait_config: WaitForTx,
@@ -70,7 +71,7 @@ pub async fn declare(
7071
declare_with_artifacts(
7172
contract_definition,
7273
casm_contract_definition,
73-
declare.fee_args,
74+
declare.fee_args.clone(),
7475
declare.nonce,
7576
account,
7677
wait_config,
@@ -81,7 +82,7 @@ pub async fn declare(
8182
}
8283

8384
#[allow(clippy::too_many_arguments)]
84-
pub(crate) async fn declare_with_artifacts(
85+
pub async fn declare_with_artifacts(
8586
sierra_class: SierraClass,
8687
compiled_casm: CompiledClass,
8788
fee_args: FeeArgs,

crates/sncast/src/starknet_commands/script/run.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -144,7 +144,7 @@ impl<'a> ExtensionLogic for CastScriptExtension<'a> {
144144
}
145145

146146
let declare_result = self.tokio_runtime.block_on(declare::declare(
147-
declare,
147+
&declare,
148148
self.account()?,
149149
self.artifacts,
150150
WaitForTx {
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
[package]
2+
name = "contract_with_constructor_params"
3+
version = "0.1.0"
4+
edition = "2024_07"
5+
6+
[dependencies]
7+
starknet = ">=2.10.1"
8+
9+
[[target.starknet-contract]]
10+
11+
[lib]
12+
sierra = false
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
#[starknet::contract]
2+
mod ContractWithConstructorParams {
3+
#[storage]
4+
struct Storage {}
5+
6+
#[constructor]
7+
fn constructor(ref self: ContractState, foo: felt252, bar: felt252) {
8+
let _x = foo + bar;
9+
}
10+
}

0 commit comments

Comments
 (0)