diff --git a/FULL_HELP_DOCS.md b/FULL_HELP_DOCS.md index 9ba32c35a..43e8370f6 100644 --- a/FULL_HELP_DOCS.md +++ b/FULL_HELP_DOCS.md @@ -1292,6 +1292,7 @@ Sign, Simulate, and Send transactions * `simulate` — Simulate a transaction envelope from stdin * `hash` — Calculate the hash of a transaction envelope from stdin * `sign` — Sign a transaction envelope appending the signature to the envelope +* `send` — Send a transaction envelope to the network @@ -1345,6 +1346,22 @@ Sign a transaction envelope appending the signature to the envelope +## `stellar tx send` + +Send a transaction envelope to the network + +**Usage:** `stellar tx send [OPTIONS]` + +###### **Options:** + +* `--rpc-url ` — RPC server endpoint +* `--network-passphrase ` — Network passphrase to sign the transaction sent to the rpc server +* `--network ` — Name of network to use from config +* `--global` — Use global config +* `--config-dir ` — Location of config directory, default is "." + + + ## `stellar xdr` Decode and encode XDR diff --git a/cmd/crates/soroban-test/tests/it/integration/tx.rs b/cmd/crates/soroban-test/tests/it/integration/tx.rs index c3d2cebd6..34c6f086d 100644 --- a/cmd/crates/soroban-test/tests/it/integration/tx.rs +++ b/cmd/crates/soroban-test/tests/it/integration/tx.rs @@ -1,7 +1,8 @@ +use soroban_rpc::GetTransactionResponse; use soroban_sdk::xdr::{Limits, ReadXdr, TransactionEnvelope, WriteXdr}; use soroban_test::{AssertExt, TestEnv}; -use crate::integration::util::{deploy_contract, DeployKind, HELLO_WORLD}; +use crate::integration::util::{deploy_contract, deploy_hello, DeployKind, HELLO_WORLD}; #[tokio::test] async fn simulate() { @@ -73,9 +74,12 @@ async fn build_simulate_sign_send() { .stdout_as_str(); dbg!("{tx_signed}"); - // TODO: Replace with calling tx send when that command is added. - let tx_signed = TransactionEnvelope::from_xdr_base64(tx_signed, Limits::none()).unwrap(); - let client = soroban_rpc::Client::new(&sandbox.rpc_url).unwrap(); - let rpc_result = client.send_transaction_polling(&tx_signed).await.unwrap(); - assert_eq!(rpc_result.status, "SUCCESS"); + let output = sandbox + .new_assert_cmd("tx") + .arg("send") + .write_stdin(tx_signed.as_bytes()) + .assert() + .success() + .stdout_as_str(); + assert_eq!(output, "SUCCESS"); } diff --git a/cmd/soroban-cli/src/commands/tx/mod.rs b/cmd/soroban-cli/src/commands/tx/mod.rs index 5f0f90c4c..587a75fda 100644 --- a/cmd/soroban-cli/src/commands/tx/mod.rs +++ b/cmd/soroban-cli/src/commands/tx/mod.rs @@ -3,6 +3,7 @@ use clap::Parser; use super::global; pub mod hash; +pub mod send; pub mod sign; pub mod simulate; pub mod xdr; @@ -15,18 +16,20 @@ pub enum Cmd { Hash(hash::Cmd), /// Sign a transaction envelope appending the signature to the envelope Sign(sign::Cmd), + /// Send a transaction envelope to the network + Send(send::Cmd), } #[derive(thiserror::Error, Debug)] pub enum Error { - /// An error during the simulation #[error(transparent)] Simulate(#[from] simulate::Error), - /// An error during hash calculation #[error(transparent)] Hash(#[from] hash::Error), #[error(transparent)] Sign(#[from] sign::Error), + #[error(transparent)] + Send(#[from] send::Error), } impl Cmd { @@ -35,6 +38,7 @@ impl Cmd { Cmd::Simulate(cmd) => cmd.run(global_args).await?, Cmd::Hash(cmd) => cmd.run(global_args)?, Cmd::Sign(cmd) => cmd.run(global_args).await?, + Cmd::Send(cmd) => cmd.run(global_args).await?, }; Ok(()) } diff --git a/cmd/soroban-cli/src/commands/tx/send.rs b/cmd/soroban-cli/src/commands/tx/send.rs new file mode 100644 index 000000000..c3856114d --- /dev/null +++ b/cmd/soroban-cli/src/commands/tx/send.rs @@ -0,0 +1,59 @@ +use async_trait::async_trait; +use soroban_rpc::GetTransactionResponse; + +use crate::commands::{global, NetworkRunnable}; +use crate::config::{self, locator, network}; + +#[derive(thiserror::Error, Debug)] +pub enum Error { + #[error(transparent)] + XdrArgs(#[from] super::xdr::Error), + #[error(transparent)] + Network(#[from] network::Error), + #[error(transparent)] + Config(#[from] config::Error), + #[error(transparent)] + Rpc(#[from] crate::rpc::Error), + #[error(transparent)] + SerdeJson(#[from] serde_json::Error), +} + +#[derive(Debug, clap::Parser, Clone)] +#[group(skip)] +/// Command to send a transaction envelope to the network +/// e.g. `cat file.txt | soroban tx send` +pub struct Cmd { + #[clap(flatten)] + pub network: network::Args, + #[clap(flatten)] + pub locator: locator::Args, +} + +impl Cmd { + pub async fn run(&self, global_args: &global::Args) -> Result<(), Error> { + let response = self.run_against_rpc_server(Some(global_args), None).await?; + println!("{}", serde_json::to_string_pretty(&response)?); + Ok(()) + } +} + +#[async_trait] +impl NetworkRunnable for Cmd { + type Error = Error; + + type Result = GetTransactionResponse; + async fn run_against_rpc_server( + &self, + _: Option<&global::Args>, + config: Option<&config::Args>, + ) -> Result { + let network = if let Some(config) = config { + config.get_network()? + } else { + self.network.get(&self.locator)? + }; + let client = crate::rpc::Client::new(&network.rpc_url)?; + let tx_env = super::xdr::tx_envelope_from_stdin()?; + Ok(client.send_transaction_polling(&tx_env).await?) + } +}