Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: wallet integration #371

Merged
merged 26 commits into from
Dec 19, 2024
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
fd962e2
feat(wallet-integration): server and API (#362)
peterwht Dec 9, 2024
a0a8397
feat(wallet integration): pop up contract (#365)
peterwht Dec 16, 2024
074e2f9
feat: wallet integration pop call chain (#379)
AlexD10S Dec 16, 2024
6b13134
Test wallet integration (#383)
peterwht Dec 16, 2024
7227abc
test(wallet-integration): remove sign_call_data test (#384)
al3mart Dec 17, 2024
89df5f6
chore: remove hex crate and use alternate means with test
peterwht Dec 17, 2024
ddb8ba6
chore: minor changes to comments, spinners, and deps
peterwht Dec 17, 2024
6035fd8
refactor: shared prompt, determine_signing_method
peterwht Dec 17, 2024
58c7ca6
refactor: use extrinsic instead of transaction
peterwht Dec 17, 2024
30e5c82
chore: latest frontend
peterwht Dec 17, 2024
b884f9d
feat: send remark on contracts-node launch
peterwht Dec 17, 2024
a4e9da2
chore: use cargo-contract crate release
peterwht Dec 17, 2024
0e19b8a
feat(wallet): choose random port for server
peterwht Dec 17, 2024
b2a6dcb
feat(wallet): auto-open browser
peterwht Dec 17, 2024
8d14f8c
chore: merge main
peterwht Dec 17, 2024
72b404c
refactor: find_free_port has preferred port
peterwht Dec 17, 2024
ca1e622
fix: conflicting tests merged to one
peterwht Dec 18, 2024
ecb8eaa
fix(wallet): port selection, update frontend
peterwht Dec 18, 2024
5e12dea
feat: update frontend with warning
peterwht Dec 18, 2024
4663b5e
[CI test] test: include instantiate_call_data_works (#389)
al3mart Dec 18, 2024
b757ac1
refactor: review fixes and improvements (#390)
evilrobot-01 Dec 18, 2024
c479866
chore: use latest frontend
peterwht Dec 18, 2024
b368da9
chore: latest frontend
peterwht Dec 18, 2024
f5c963e
chore: update frontend to remove trailing slash
peterwht Dec 18, 2024
52fdf6b
chore: improve uploading / calling contract message to wait for final…
peterwht Dec 18, 2024
ae7b3a2
test: improve `crates/pop-cli/src/common/contracts.rs` coverage (#391)
al3mart Dec 18, 2024
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
1,506 changes: 465 additions & 1,041 deletions Cargo.lock

Large diffs are not rendered by default.

15 changes: 11 additions & 4 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -49,13 +49,16 @@ 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" }
peterwht marked this conversation as resolved.
Show resolved Hide resolved
#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 }

# parachains
askama = "0.12"
Expand All @@ -76,3 +79,7 @@ console = "0.15"
os_info = { version = "3", default-features = false }
strum = "0.26"
strum_macros = "0.26"

# wallet-integration
axum = "0.7.9"
tower-http = "0.6.2"
13 changes: 11 additions & 2 deletions crates/pop-cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ duct.workspace = true
env_logger.workspace = true
os_info.workspace = true
reqwest.workspace = true
serde = { workspace = true, version = "1.0", features = ["derive"] }
peterwht marked this conversation as resolved.
Show resolved Hide resolved
serde_json.workspace = true
tempfile.workspace = true
tokio.workspace = true
Expand All @@ -27,12 +28,12 @@ url.workspace = true
clap.workspace = true
cliclack.workspace = true
console.workspace = true
sp-core.workspace = true
strum.workspace = true
strum_macros.workspace = true

# contracts
pop-contracts = { path = "../pop-contracts", version = "0.5.0", optional = true }
sp-core = { workspace = true, optional = true }
sp-weights = { workspace = true, optional = true }

# parachains
Expand All @@ -46,12 +47,20 @@ pop-telemetry = { path = "../pop-telemetry", version = "0.5.0", optional = true
# common
pop-common = { path = "../pop-common", version = "0.5.0" }

# wallet-integration
axum.workspace = true
tower-http = { workspace = true, features = ["fs", "cors"] }

[dev-dependencies]
assert_cmd.workspace = true
contract-extrinsics.workspace = true
predicates.workspace = true
subxt.workspace = true
subxt-signer.workspace = true
sp-weights.workspace = true

[features]
default = ["contract", "parachain", "telemetry"]
contract = ["dep:pop-contracts", "dep:sp-core", "dep:sp-weights", "dep:dirs"]
contract = ["dep:pop-contracts", "dep:sp-weights", "dep:dirs"]
parachain = ["dep:pop-parachains", "dep:dirs"]
telemetry = ["dep:pop-telemetry"]
134 changes: 134 additions & 0 deletions crates/pop-cli/src/assets/index.html

Large diffs are not rendered by default.

1 change: 0 additions & 1 deletion crates/pop-cli/src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,6 @@ impl traits::Confirm for Confirm {

/// A input prompt using cliclack.
struct Input(cliclack::Input);

impl traits::Input for Input {
/// Sets the default value for the input.
fn default_input(mut self, value: &str) -> Self {
Expand Down
129 changes: 113 additions & 16 deletions crates/pop-cli/src/commands/call/chain.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,17 @@

use std::path::Path;

use crate::cli::{self, traits::*};
use crate::{
cli::{self, traits::*},
common::wallet::wait_for_signature,
};
use anyhow::{anyhow, Result};
use clap::Args;
use pop_parachains::{
construct_extrinsic, construct_sudo_extrinsic, decode_call_data, encode_call_data,
find_dispatchable_by_name, find_pallet_by_name, parse_chain_metadata, set_up_client,
sign_and_submit_extrinsic, supported_actions, Action, CallData, DynamicPayload, Function,
OnlineClient, Pallet, Param, SubstrateConfig,
sign_and_submit_extrinsic, submit_signed_extrinsic, supported_actions, Action, CallData,
DynamicPayload, Function, OnlineClient, Pallet, Param, Payload, SubstrateConfig,
};
use url::Url;

Expand Down Expand Up @@ -40,6 +43,9 @@
/// - with a password "//Alice///SECRET_PASSWORD"
#[arg(short, long)]
suri: Option<String>,
/// Use your browser wallet to sign the extrinsic.
peterwht marked this conversation as resolved.
Show resolved Hide resolved
#[arg(name = "use-wallet", short('w'), long, default_value = "false", conflicts_with = "suri")]
use_wallet: bool,
/// SCALE encoded bytes representing the call data of the extrinsic.
#[arg(name = "call", short, long, conflicts_with_all = ["pallet", "function", "args"])]
call_data: Option<String>,
Expand Down Expand Up @@ -95,7 +101,14 @@
};

// Sign and submit the extrinsic.
if let Err(e) = call.submit_extrinsic(&chain.client, &chain.url, xt, &mut cli).await {
let result = if self.use_wallet {
let call_data = xt.encode_call_data(&chain.client.metadata())?;
submit_extrinsic_with_wallet(&chain.client, &chain.url, call_data, &mut cli).await
} else {
call.submit_extrinsic(&chain.client, &chain.url, xt, &mut cli).await
};

if let Err(e) = result {
display_message(&e.to_string(), false, &mut cli)?;
break;
}
Expand Down Expand Up @@ -197,11 +210,25 @@
// sudo.
self.configure_sudo(chain, cli)?;

// Resolve who is signing the extrinsic.
// Resolve who is signing the extrinsic. If a `suri` was provided via the command line,
// skip the prompt.
let suri = match self.suri.as_ref() {
Some(suri) => suri.clone(),
None =>
cli.input("Signer of the extrinsic:").default_input(DEFAULT_URI).interact()?,
None => {
if !self.use_wallet {
if cli.confirm("Do you want to use your browser wallet to sign the transaction? (Selecting 'No' will prompt you to manually enter the secret key URI for signing, e.g., '//Alice')")
peterwht marked this conversation as resolved.
Show resolved Hide resolved
.initial_value(true)
.interact()? {
self.use_wallet = true;
DEFAULT_URI.to_string() // Default value because the user is using the browser wallet.
}
else {
cli.input("Signer of the extrinsic:").default_input(DEFAULT_URI).interact()?
}
} else {
DEFAULT_URI.to_string() // Default value because the user is using the browser wallet.
}
},
};

return Ok(Call {
Expand All @@ -210,6 +237,7 @@
suri,
skip_confirm: self.skip_confirm,
sudo: self.sudo,
use_wallet: self.use_wallet,
});
}
}
Expand All @@ -222,11 +250,36 @@
call_data: &str,
cli: &mut impl Cli,
) -> Result<()> {
// Resolve who is signing the extrinsic.
// Resolve who is signing the extrinsic. If a `suri` was provided via the command line,
// skip the prompt.
let mut use_wallet = self.use_wallet;
let suri = match self.suri.as_ref() {
Some(suri) => suri,
None => &cli.input("Signer of the extrinsic:").default_input(DEFAULT_URI).interact()?,
Some(suri) => suri.clone(),
None =>
if !self.use_wallet {
peterwht marked this conversation as resolved.
Show resolved Hide resolved
if cli.confirm("Do you want to use your browser wallet to sign the transaction? (Selecting 'No' will prompt you to manually enter the secret key URI for signing, e.g., '//Alice')")
.initial_value(true)
.interact()? {
use_wallet = true;
DEFAULT_URI.to_string()
}
else {
cli.input("Signer of the extrinsic:").default_input(DEFAULT_URI).interact()?
}
} else {
DEFAULT_URI.to_string()
},
};
// Perform signing steps with wallet integration and return early.
if use_wallet {
let call_data_bytes =
decode_call_data(call_data).map_err(|err| anyhow!("{}", format!("{err:?}")))?;
submit_extrinsic_with_wallet(client, &url, call_data_bytes, cli)

Check warning on line 277 in crates/pop-cli/src/commands/call/chain.rs

View workflow job for this annotation

GitHub Actions / clippy

this expression creates a reference which is immediately dereferenced by the compiler

warning: this expression creates a reference which is immediately dereferenced by the compiler --> crates/pop-cli/src/commands/call/chain.rs:277:41 | 277 | submit_extrinsic_with_wallet(client, &url, call_data_bytes, cli) | ^^^^ help: change this to: `url` | = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#needless_borrow = note: `#[warn(clippy::needless_borrow)]` on by default
peterwht marked this conversation as resolved.
Show resolved Hide resolved
.await
.map_err(|err| anyhow!("{}", format!("{err:?}")))?;
display_message("Call complete.", true, cli)?;
return Ok(());
}
cli.info(format!("Encoded call data: {}", call_data))?;
if !self.skip_confirm &&
!cli.confirm("Do you want to submit the extrinsic?")
Expand All @@ -244,7 +297,7 @@
spinner.start("Signing and submitting the extrinsic and then waiting for finalization, please be patient...");
let call_data_bytes =
decode_call_data(call_data).map_err(|err| anyhow!("{}", format!("{err:?}")))?;
let result = sign_and_submit_extrinsic(client, url, CallData::new(call_data_bytes), suri)
let result = sign_and_submit_extrinsic(client, url, CallData::new(call_data_bytes), &suri)
.await
.map_err(|err| anyhow!("{}", format!("{err:?}")))?;

Expand Down Expand Up @@ -283,6 +336,7 @@
self.function = None;
self.args.clear();
self.sudo = false;
self.use_wallet = false;
}

// Function to check if all required fields are specified.
Expand Down Expand Up @@ -334,6 +388,8 @@
/// - for a dev account "//Alice"
/// - with a password "//Alice///SECRET_PASSWORD"
suri: String,
/// Whether to use your browser wallet to sign the extrinsic.
use_wallet: bool,
/// Whether to automatically sign and submit the extrinsic without prompting for confirmation.
skip_confirm: bool,
/// Whether to dispatch the function call with `Root` origin.
Expand Down Expand Up @@ -411,14 +467,45 @@
.collect();
full_message.push_str(&format!(" --args {}", args.join(" ")));
}
full_message.push_str(&format!(" --url {} --suri {}", chain.url, self.suri));
full_message.push_str(&format!(" --url {}", chain.url));
if self.use_wallet {
full_message.push_str(" --use-wallet");
} else {
full_message.push_str(&format!(" --suri {}", self.suri));
}
if self.sudo {
full_message.push_str(" --sudo");
}
full_message
}
}

// Sign and submit an extrinsic using wallet integration.
async fn submit_extrinsic_with_wallet(
client: &OnlineClient<SubstrateConfig>,
url: &Url,
call_data: Vec<u8>,
cli: &mut impl Cli,
) -> Result<()> {
let maybe_payload = wait_for_signature(call_data, url.to_string()).await?;
if let Some(payload) = maybe_payload {
cli.success("Signed payload received.")?;
let spinner = cliclack::spinner();
spinner.start(
"Submitting the extrinsic and then waiting for finalization, please be patient...",
);

let result = submit_signed_extrinsic(client.clone(), payload)
.await
.map_err(|err| anyhow!("{}", format!("{err:?}")))?;

spinner.stop(format!("Extrinsic submitted with hash: {:?}", result));
} else {
display_message("No signed payload received.", false, cli)?;
}
Ok(())
}

// Displays a message to the user, with formatting based on the success status.
fn display_message(message: &str, success: bool, cli: &mut impl Cli) -> Result<()> {
if success {
Expand Down Expand Up @@ -642,7 +729,7 @@
)
.expect_input("The value for `remark` might be too large to enter. You may enter the path to a file instead.", "0x11".into())
.expect_confirm("Would you like to dispatch this function call with `Root` origin?", true)
.expect_input("Signer of the extrinsic:", "//Bob".into());
.expect_confirm("Do you want to use your browser wallet to sign the transaction? (Selecting 'No' will prompt you to manually enter the secret key URI for signing, e.g., '//Alice')", true);
peterwht marked this conversation as resolved.
Show resolved Hide resolved

let chain = call_config.configure_chain(&mut cli).await?;
assert_eq!(chain.url, Url::parse(POP_NETWORK_TESTNET_URL)?);
Expand All @@ -651,9 +738,10 @@
assert_eq!(call_chain.function.pallet, "System");
assert_eq!(call_chain.function.name, "remark");
assert_eq!(call_chain.args, ["0x11".to_string()].to_vec());
assert_eq!(call_chain.suri, "//Bob");
assert_eq!(call_chain.suri, "//Alice"); // Default value
assert!(call_chain.use_wallet);
assert!(call_chain.sudo);
assert_eq!(call_chain.display(&chain), "pop call chain --pallet System --function remark --args \"0x11\" --url wss://rpc1.paseo.popnetwork.xyz/ --suri //Bob --sudo");
assert_eq!(call_chain.display(&chain), "pop call chain --pallet System --function remark --args \"0x11\" --url wss://rpc1.paseo.popnetwork.xyz/ --use-wallet --sudo");
cli.verify()
}

Expand Down Expand Up @@ -714,6 +802,7 @@
},
args: vec!["0x11".to_string()].to_vec(),
suri: DEFAULT_URI.to_string(),
use_wallet: false,
skip_confirm: false,
sudo: false,
};
Expand Down Expand Up @@ -753,6 +842,7 @@
function: find_dispatchable_by_name(&pallets, "System", "remark")?.clone(),
args: vec!["0x11".to_string()].to_vec(),
suri: DEFAULT_URI.to_string(),
use_wallet: false,
skip_confirm: false,
sudo: false,
};
Expand All @@ -776,11 +866,13 @@
args: vec![].to_vec(),
url: Some(Url::parse(POP_NETWORK_TESTNET_URL)?),
suri: None,
use_wallet: false,
skip_confirm: false,
call_data: Some("0x00000411".to_string()),
sudo: false,
};
let mut cli = MockCli::new()
.expect_confirm("Do you want to use your browser wallet to sign the transaction? (Selecting 'No' will prompt you to manually enter the secret key URI for signing, e.g., '//Alice')", false)
peterwht marked this conversation as resolved.
Show resolved Hide resolved
.expect_input("Signer of the extrinsic:", "//Bob".into())
.expect_confirm("Do you want to submit the extrinsic?", false)
.expect_outro_cancel("Extrinsic with call data 0x00000411 was not submitted.");
Expand All @@ -803,8 +895,9 @@
pallet: None,
function: None,
args: vec![].to_vec(),
url: Some(Url::parse("wss://polkadot-rpc.publicnode.com")?),
url: Some(Url::parse(POLKADOT_NETWORK_URL)?),
suri: Some("//Alice".to_string()),
use_wallet: false,
skip_confirm: false,
call_data: Some("0x00000411".to_string()),
sudo: true,
Expand Down Expand Up @@ -836,6 +929,7 @@
function: Some("remark".to_string()),
args: vec!["0x11".to_string()].to_vec(),
url: Some(Url::parse(POP_NETWORK_TESTNET_URL)?),
use_wallet: true,
suri: Some(DEFAULT_URI.to_string()),
skip_confirm: false,
call_data: None,
Expand All @@ -846,6 +940,7 @@
assert_eq!(call_config.function, None);
assert_eq!(call_config.args.len(), 0);
assert!(!call_config.sudo);
assert!(!call_config.use_wallet);
Ok(())
}

Expand All @@ -857,6 +952,7 @@
args: vec!["0x11".to_string()].to_vec(),
url: Some(Url::parse(POP_NETWORK_TESTNET_URL)?),
suri: Some(DEFAULT_URI.to_string()),
use_wallet: false,
skip_confirm: false,
call_data: None,
sudo: false,
Expand All @@ -875,6 +971,7 @@
args: vec!["2000".to_string(), "0x1".to_string(), "0x12".to_string()].to_vec(),
url: Some(Url::parse(POP_NETWORK_TESTNET_URL)?),
suri: Some(DEFAULT_URI.to_string()),
use_wallet: false,
call_data: None,
skip_confirm: false,
sudo: false,
Expand Down
Loading
Loading