diff --git a/Cargo.lock b/Cargo.lock
index 901e0e10f..9887bd521 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -966,7 +966,7 @@ dependencies = [
"serde_urlencoded",
"sync_wrapper 1.0.2",
"tokio",
- "tower 0.5.1",
+ "tower 0.5.2",
"tower-layer",
"tower-service",
"tracing",
@@ -2004,8 +2004,7 @@ checksum = "cd7e35aee659887cbfb97aaf227ac12cad1a9d7c71e55ff3376839ed4e282d08"
[[package]]
name = "contract-build"
version = "5.0.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "857769855bf40d230e41baf6575cc44bd5e6869f69f88f45f6791da793f49a0c"
+source = "git+https://github.com/use-ink/cargo-contract?branch=peter/chore-make-types-pub#7c8fc481912d7a6f416a0f72e37840123064f90d"
dependencies = [
"anyhow",
"blake2",
@@ -2045,8 +2044,7 @@ dependencies = [
[[package]]
name = "contract-extrinsics"
version = "5.0.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e77ad38bef6454f97ca33481e960e9b105cb97f3794656071cbf50229445a89d"
+source = "git+https://github.com/use-ink/cargo-contract?branch=peter/chore-make-types-pub#7c8fc481912d7a6f416a0f72e37840123064f90d"
dependencies = [
"anyhow",
"blake2",
@@ -2078,8 +2076,7 @@ dependencies = [
[[package]]
name = "contract-metadata"
version = "5.0.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "733a6624ea05dd71050641c3cd9baff7a1445032a0082f0e55c800c078716424"
+source = "git+https://github.com/use-ink/cargo-contract?branch=peter/chore-make-types-pub#7c8fc481912d7a6f416a0f72e37840123064f90d"
dependencies = [
"anyhow",
"impl-serde 0.5.0",
@@ -2092,8 +2089,7 @@ dependencies = [
[[package]]
name = "contract-transcode"
version = "5.0.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cd9131028be7b8eefdd9151a0a682ed428c1418d5d1ec142c35d2dbe6b9653c6"
+source = "git+https://github.com/use-ink/cargo-contract?branch=peter/chore-make-types-pub#7c8fc481912d7a6f416a0f72e37840123064f90d"
dependencies = [
"anyhow",
"base58",
@@ -9339,6 +9335,8 @@ dependencies = [
"sp-weights",
"strum 0.26.3",
"strum_macros 0.26.4",
+ "subxt",
+ "subxt-signer",
"tempfile",
"tokio",
"tower-http 0.6.2",
@@ -9398,6 +9396,8 @@ dependencies = [
"sp-weights",
"strum 0.26.3",
"strum_macros 0.26.4",
+ "subxt",
+ "subxt-signer",
"tar",
"tempfile",
"thiserror 1.0.69",
@@ -13815,14 +13815,14 @@ dependencies = [
[[package]]
name = "tower"
-version = "0.5.1"
+version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2873938d487c3cfb9aed7546dc9f2711d867c9f90c46b889989a2cb84eba6b4f"
+checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9"
dependencies = [
"futures-core",
"futures-util",
"pin-project-lite",
- "sync_wrapper 0.1.2",
+ "sync_wrapper 1.0.2",
"tokio",
"tower-layer",
"tower-service",
diff --git a/Cargo.toml b/Cargo.toml
index c84ce914d..3effb7c2e 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -49,11 +49,15 @@ subxt = "0.38.0"
ink_env = "5.0.0"
sp-core = "32.0.0"
sp-weights = "31.0.0"
-contract-build = "5.0.0"
-contract-extrinsics = "5.0.0"
-contract-transcode = "5.0.0"
scale-info = { version = "2.11.4", default-features = false, features = ["derive"] }
scale-value = { version = "0.17.0", default-features = false, features = ["from-string", "parser-ss58"] }
+# TODO: git deps
+#contract-build = "5.0.0-alpha"
+contract-build = { git = "https://github.com/use-ink/cargo-contract", branch = "peter/chore-make-types-pub" }
+#contract-extrinsics = "5.0.0-alpha"
+contract-extrinsics = { git = "https://github.com/use-ink/cargo-contract", branch = "peter/chore-make-types-pub" }
+#contract-transcode = "5.0.0"
+contract-transcode = { git = "https://github.com/use-ink/cargo-contract", branch = "peter/chore-make-types-pub" }
heck = "0.5.0"
hex = { version = "0.4.3", default-features = false }
diff --git a/crates/pop-cli/Cargo.toml b/crates/pop-cli/Cargo.toml
index 4f3ab59ab..6d2d6ef42 100644
--- a/crates/pop-cli/Cargo.toml
+++ b/crates/pop-cli/Cargo.toml
@@ -49,11 +49,13 @@ pop-common = { path = "../pop-common", version = "0.5.0" }
# wallet-integration
axum.workspace = true
-tower-http = { workspace = true, features = ["fs"] }
+tower-http = { workspace = true, features = ["fs", "cors"] }
[dev-dependencies]
assert_cmd.workspace = true
predicates.workspace = true
+subxt.workspace = true
+subxt-signer.workspace = true
[features]
default = ["contract", "parachain", "telemetry"]
diff --git a/crates/pop-cli/src/assets/index.html b/crates/pop-cli/src/assets/index.html
new file mode 100644
index 000000000..d5659fbd8
--- /dev/null
+++ b/crates/pop-cli/src/assets/index.html
@@ -0,0 +1,134 @@
+
+
+
+
+
+
+
+ Pop CLI Signing Portal
+
+
+
+
+
+
+
+
+
diff --git a/crates/pop-cli/src/commands/up/contract.rs b/crates/pop-cli/src/commands/up/contract.rs
index 964237642..dfb95380e 100644
--- a/crates/pop-cli/src/commands/up/contract.rs
+++ b/crates/pop-cli/src/commands/up/contract.rs
@@ -4,14 +4,17 @@ use crate::{
cli::{traits::Cli as _, Cli},
common::contracts::{check_contracts_node_and_prompt, has_contract_been_built},
style::style,
+ wallet_integration::{FrontendFromString, TransactionData, WalletIntegrationManager},
};
use clap::Args;
-use cliclack::{confirm, log, log::error, spinner};
+use cliclack::{confirm, log, log::error, spinner, ProgressBar};
use console::{Emoji, Style};
use pop_contracts::{
build_smart_contract, dry_run_gas_estimate_instantiate, dry_run_upload,
- instantiate_smart_contract, is_chain_alive, parse_hex_bytes, run_contracts_node,
- set_up_deployment, set_up_upload, upload_smart_contract, UpOpts, Verbosity,
+ get_code_hash_from_event, get_contract_code, get_instantiate_payload, get_upload_payload,
+ instantiate_contract_signed, instantiate_smart_contract, is_chain_alive, parse_hex_bytes,
+ run_contracts_node, set_up_deployment, set_up_upload, upload_contract_signed,
+ upload_smart_contract, UpOpts, Verbosity,
};
use sp_core::Bytes;
use sp_weights::Weight;
@@ -64,6 +67,15 @@ pub struct UpContractCommand {
/// - with a password "//Alice///SECRET_PASSWORD"
#[clap(name = "suri", long, short, default_value = "//Alice")]
suri: String,
+ /// Use your browser wallet to sign a transaction.
+ #[clap(
+ name = "use-wallet",
+ long,
+ default_value = "false",
+ short('w'),
+ conflicts_with = "suri"
+ )]
+ use_wallet: bool,
/// Perform a dry-run via RPC to estimate the gas usage. This does not submit a transaction.
#[clap(long)]
dry_run: bool,
@@ -164,6 +176,76 @@ impl UpContractCommand {
None
};
+ // Run steps for signing with wallet integration. Returns early.
+ if self.use_wallet {
+ let (call_data, hash) = match self.get_contract_data().await {
+ Ok(data) => data,
+ Err(e) => {
+ error(format!("An error occurred getting the call data: {e}"))?;
+ Self::terminate_node(process)?;
+ Cli.outro_cancel(FAILED)?;
+ return Ok(());
+ },
+ };
+
+ let maybe_payload = self.wait_for_signature(call_data).await?;
+ if let Some(payload) = maybe_payload {
+ log::success("Signed payload received.")?;
+ let spinner = spinner();
+ spinner.start("Uploading contract...");
+
+ if self.upload_only {
+ let result = upload_contract_signed(self.url.as_str(), payload).await;
+ // TODO: dry (see else below)
+ if let Err(e) = result {
+ spinner.error(format!("An error occurred uploading your contract: {e}"));
+ Self::terminate_node(process)?;
+ Cli.outro_cancel(FAILED)?;
+ return Ok(());
+ }
+ let upload_result = result.expect("Error check above.");
+
+ match get_code_hash_from_event(&upload_result, hash) {
+ Ok(r) => {
+ spinner.stop(format!("Contract uploaded: The code hash is {:?}", r));
+ },
+ Err(e) => {
+ spinner
+ .error(format!("An error occurred uploading your contract: {e}"));
+ },
+ };
+ } else {
+ let result = instantiate_contract_signed(self.url.as_str(), payload).await;
+ if let Err(e) = result {
+ spinner.error(format!("An error occurred uploading your contract: {e}"));
+ Self::terminate_node(process)?;
+ Cli.outro_cancel(FAILED)?;
+ return Ok(());
+ }
+
+ let contract_info = result.unwrap();
+ let hash = contract_info.code_hash.map(|code_hash| format!("{:?}", code_hash));
+ display_contract_info(
+ &spinner,
+ contract_info.contract_address.to_string(),
+ hash,
+ );
+ };
+
+ if self.upload_only {
+ log::warning("NOTE: The contract has not been instantiated.")?;
+ }
+ } else {
+ Cli.outro_cancel("Signed payload doesn't exist.")?;
+ Self::terminate_node(process)?;
+ return Ok(());
+ }
+
+ Self::terminate_node(process)?;
+ Cli.outro(COMPLETE)?;
+ return Ok(())
+ }
+
// Check for upload only.
if self.upload_only {
let result = self.upload_contract().await;
@@ -180,19 +262,7 @@ impl UpContractCommand {
}
// Otherwise instantiate.
- let instantiate_exec = match set_up_deployment(UpOpts {
- path: self.path.clone(),
- constructor: self.constructor.clone(),
- args: self.args.clone(),
- value: self.value.clone(),
- gas_limit: self.gas_limit,
- proof_size: self.proof_size,
- salt: self.salt.clone(),
- url: self.url.clone(),
- suri: self.suri.clone(),
- })
- .await
- {
+ let instantiate_exec = match set_up_deployment(self.clone().into()).await {
Ok(i) => i,
Err(e) => {
error(format!("An error occurred instantiating the contract: {e}"))?;
@@ -226,29 +296,12 @@ impl UpContractCommand {
let spinner = spinner();
spinner.start("Uploading and instantiating the contract...");
let contract_info = instantiate_smart_contract(instantiate_exec, weight_limit).await?;
- spinner.stop(format!(
- "Contract deployed and instantiated:\n{}",
- style(format!(
- "{}\n{}",
- style(format!(
- "{} The contract address is {:?}",
- console::Emoji("●", ">"),
- contract_info.address
- ))
- .dim(),
- contract_info
- .code_hash
- .map(|hash| style(format!(
- "{} The contract code hash is {:?}",
- console::Emoji("●", ">"),
- hash
- ))
- .dim()
- .to_string())
- .unwrap_or_default(),
- ))
- .dim()
- ));
+ display_contract_info(
+ &spinner,
+ contract_info.address.to_string(),
+ contract_info.code_hash,
+ );
+
Self::terminate_node(process)?;
Cli.outro(COMPLETE)?;
}
@@ -313,6 +366,54 @@ impl UpContractCommand {
Ok(())
}
+
+ // get the call data and contract code hash
+ async fn get_contract_data(&self) -> anyhow::Result<(Vec, [u8; 32])> {
+ let contract_code = get_contract_code(self.path.as_ref()).await?;
+ let hash = contract_code.code_hash();
+ if self.upload_only {
+ let call_data = get_upload_payload(contract_code, self.url.as_str()).await?;
+ Ok((call_data, hash))
+ } else {
+ let instantiate_exec = set_up_deployment(self.clone().into()).await?;
+
+ let weight_limit = if self.gas_limit.is_some() && self.proof_size.is_some() {
+ Weight::from_parts(self.gas_limit.unwrap(), self.proof_size.unwrap())
+ } else {
+ // Frontend will do dry run and update call data.
+ Weight::from_parts(0, 0)
+ };
+ let call_data = get_instantiate_payload(instantiate_exec, weight_limit).await?;
+ Ok((call_data, hash))
+ }
+ }
+
+ async fn wait_for_signature(&self, call_data: Vec) -> anyhow::Result