From 4aafe3f100b22fbe7621647bf701808dac751530 Mon Sep 17 00:00:00 2001 From: Willem Wyndham Date: Thu, 2 May 2024 12:04:42 -0400 Subject: [PATCH] feat: add txn subcommand --- cmd/soroban-cli/src/commands/mod.rs | 10 +- cmd/soroban-cli/src/commands/txn/mod.rs | 48 ++++ cmd/soroban-cli/src/commands/txn/send.rs | 35 +++ cmd/soroban-cli/src/commands/txn/sign.rs | 97 +++++++ cmd/soroban-cli/src/commands/txn/simulate.rs | 38 +++ cmd/soroban-cli/src/commands/txn/xdr.rs | 67 +++++ cmd/soroban-cli/src/lib.rs | 1 + cmd/soroban-cli/src/signer.rs | 277 +++++++++++++++++++ docs/soroban-cli-full-docs.md | 246 ++++++++++++++++ 9 files changed, 817 insertions(+), 2 deletions(-) create mode 100644 cmd/soroban-cli/src/commands/txn/mod.rs create mode 100644 cmd/soroban-cli/src/commands/txn/send.rs create mode 100644 cmd/soroban-cli/src/commands/txn/sign.rs create mode 100644 cmd/soroban-cli/src/commands/txn/simulate.rs create mode 100644 cmd/soroban-cli/src/commands/txn/xdr.rs create mode 100644 cmd/soroban-cli/src/signer.rs diff --git a/cmd/soroban-cli/src/commands/mod.rs b/cmd/soroban-cli/src/commands/mod.rs index f904c465f6..4eeeb53e28 100644 --- a/cmd/soroban-cli/src/commands/mod.rs +++ b/cmd/soroban-cli/src/commands/mod.rs @@ -12,9 +12,9 @@ pub mod global; pub mod keys; pub mod network; pub mod plugin; -pub mod version; - +pub mod txn; pub mod txn_result; +pub mod version; pub const HEADING_RPC: &str = "Options (RPC)"; const ABOUT: &str = "Build, deploy, & interact with contracts; set identities to sign with; configure networks; generate keys; and more. @@ -101,6 +101,7 @@ impl Root { Cmd::Network(network) => network.run().await?, Cmd::Version(version) => version.run(), Cmd::Keys(id) => id.run().await?, + Cmd::Txn(tx) => tx.run().await?, Cmd::Cache(data) => data.run()?, }; Ok(()) @@ -135,6 +136,9 @@ pub enum Cmd { Network(network::Cmd), /// Print version information Version(version::Cmd), + /// Sign, Simulate, and Send transactions + #[command(subcommand)] + Txn(txn::Cmd), /// Cache for tranasctions and contract specs #[command(subcommand)] Cache(cache::Cmd), @@ -158,6 +162,8 @@ pub enum Error { #[error(transparent)] Network(#[from] network::Error), #[error(transparent)] + Txn(#[from] txn::Error), + #[error(transparent)] Cache(#[from] cache::Error), } diff --git a/cmd/soroban-cli/src/commands/txn/mod.rs b/cmd/soroban-cli/src/commands/txn/mod.rs new file mode 100644 index 0000000000..9de360fcc2 --- /dev/null +++ b/cmd/soroban-cli/src/commands/txn/mod.rs @@ -0,0 +1,48 @@ +use clap::Parser; + +pub mod send; +pub mod sign; +pub mod simulate; +pub mod xdr; + +use stellar_xdr::cli as xdr_cli; + +#[derive(Debug, Parser)] +pub enum Cmd { + /// Add a new identity (keypair, ledger, macOS keychain) + Inspect(xdr_cli::Root), + /// Given an identity return its address (public key) + Sign(sign::Cmd), + /// Submit a transaction to the network + Send(send::Cmd), + /// Simulate a transaction + Simulate(simulate::Cmd), +} + +#[derive(thiserror::Error, Debug)] +pub enum Error { + /// An error during the simulation + #[error(transparent)] + Simulate(#[from] simulate::Error), + /// An error during the inspect + #[error(transparent)] + Inspect(#[from] xdr_cli::Error), + /// An error during the sign + #[error(transparent)] + Sign(#[from] sign::Error), + /// An error during the send + #[error(transparent)] + Send(#[from] send::Error), +} + +impl Cmd { + pub async fn run(&self) -> Result<(), Error> { + match self { + Cmd::Inspect(cmd) => cmd.run()?, + Cmd::Sign(cmd) => cmd.run().await?, + Cmd::Send(cmd) => cmd.run().await?, + Cmd::Simulate(cmd) => cmd.run().await?, + }; + Ok(()) + } +} diff --git a/cmd/soroban-cli/src/commands/txn/send.rs b/cmd/soroban-cli/src/commands/txn/send.rs new file mode 100644 index 0000000000..1cf3f8478d --- /dev/null +++ b/cmd/soroban-cli/src/commands/txn/send.rs @@ -0,0 +1,35 @@ +use soroban_rpc::GetTransactionResponse; + +#[derive(thiserror::Error, Debug)] +pub enum Error { + #[error(transparent)] + XdrArgs(#[from] super::xdr::Error), + #[error(transparent)] + Config(#[from] super::super::config::Error), + #[error(transparent)] + Rpc(#[from] crate::rpc::Error), +} + +#[derive(Debug, clap::Parser, Clone)] +#[group(skip)] +pub struct Cmd { + #[clap(flatten)] + pub xdr_args: super::xdr::Args, + #[clap(flatten)] + pub config: super::super::config::Args, +} + +impl Cmd { + pub async fn run(&self) -> Result<(), Error> { + let response = self.send().await?; + println!("{response:#?}"); + Ok(()) + } + + pub async fn send(&self) -> Result { + let txn_env = self.xdr_args.txn_envelope()?; + let network = self.config.get_network()?; + let client = crate::rpc::Client::new(&network.rpc_url)?; + Ok(client.send_transaction(&txn_env).await?) + } +} diff --git a/cmd/soroban-cli/src/commands/txn/sign.rs b/cmd/soroban-cli/src/commands/txn/sign.rs new file mode 100644 index 0000000000..ec4bb1155b --- /dev/null +++ b/cmd/soroban-cli/src/commands/txn/sign.rs @@ -0,0 +1,97 @@ +use std::io; + +// use crossterm::{ +// event::{self, Event, KeyCode, KeyEvent, KeyModifiers}, +// execute, +// terminal::{self, EnterAlternateScreen, LeaveAlternateScreen}, +// }; +use soroban_sdk::xdr::{self, Limits, TransactionEnvelope, WriteXdr}; + +use crate::signer::{self, InMemory, Stellar}; + +#[derive(thiserror::Error, Debug)] +pub enum Error { + #[error(transparent)] + XdrArgs(#[from] super::xdr::Error), + #[error(transparent)] + Signer(#[from] signer::Error), + #[error(transparent)] + Config(#[from] super::super::config::Error), + #[error(transparent)] + StellarStrkey(#[from] stellar_strkey::DecodeError), + #[error(transparent)] + Xdr(#[from] xdr::Error), + #[error(transparent)] + Io(#[from] io::Error), + #[error("User cancelled signing, perhaps need to add -y")] + UserCancelledSigning, +} + +#[derive(Debug, clap::Parser, Clone)] +#[group(skip)] +pub struct Cmd { + /// Confirm that a signature can be signed by the given keypair automatically. + #[arg(long, short = 'y')] + yes: bool, + #[clap(flatten)] + pub xdr_args: super::xdr::Args, + #[clap(flatten)] + pub config: super::super::config::Args, +} + +impl Cmd { + #[allow(clippy::unused_async)] + pub async fn run(&self) -> Result<(), Error> { + let envelope = self.sign()?; + println!("{}", envelope.to_xdr_base64(Limits::none())?.trim()); + Ok(()) + } + + pub fn sign(&self) -> Result { + let source = &self.config.source_account; + tracing::debug!("signing transaction with source account {}", source); + let txn = self.xdr_args.txn()?; + let key = self.config.key_pair()?; + let address = + stellar_strkey::ed25519::PublicKey::from_payload(key.verifying_key().as_bytes())?; + let in_memory = InMemory { + network_passphrase: self.config.get_network()?.network_passphrase, + keypairs: vec![key], + }; + self.prompt_user()?; + Ok(in_memory.sign_txn(txn, &stellar_strkey::Strkey::PublicKeyEd25519(address))?) + } + + pub fn prompt_user(&self) -> Result<(), Error> { + if self.yes { + return Ok(()); + } + Err(Error::UserCancelledSigning) + // TODO use crossterm to prompt user for confirmation + // // Set up the terminal + // let mut stdout = io::stdout(); + // execute!(stdout, EnterAlternateScreen)?; + // terminal::enable_raw_mode()?; + + // println!("Press 'y' or 'Y' for yes, any other key for no:"); + + // loop { + // if let Event::Key(KeyEvent { + // code, + // modifiers: KeyModifiers::NONE, + // .. + // }) = event::read()? + // { + // match code { + // KeyCode::Char('y' | 'Y') => break, + // _ => return Err(Error::UserCancelledSigning), + // } + // } + // } + + // // Clean up the terminal + // terminal::disable_raw_mode()?; + // execute!(stdout, LeaveAlternateScreen)?; + // Ok(()) + } +} diff --git a/cmd/soroban-cli/src/commands/txn/simulate.rs b/cmd/soroban-cli/src/commands/txn/simulate.rs new file mode 100644 index 0000000000..5fbae28c01 --- /dev/null +++ b/cmd/soroban-cli/src/commands/txn/simulate.rs @@ -0,0 +1,38 @@ +use soroban_rpc::Assembled; +use soroban_sdk::xdr::{self, WriteXdr}; + +#[derive(thiserror::Error, Debug)] +pub enum Error { + #[error(transparent)] + XdrArgs(#[from] super::xdr::Error), + #[error(transparent)] + Config(#[from] super::super::config::Error), + #[error(transparent)] + Rpc(#[from] crate::rpc::Error), + #[error(transparent)] + Xdr(#[from] xdr::Error), +} + +#[derive(Debug, clap::Parser, Clone)] +#[group(skip)] +pub struct Cmd { + #[clap(flatten)] + pub xdr_args: super::xdr::Args, + #[clap(flatten)] + pub config: super::super::config::Args, +} + +impl Cmd { + pub async fn run(&self) -> Result<(), Error> { + let res = self.simulate().await?; + println!("{}", res.transaction().to_xdr_base64(xdr::Limits::none())?); + Ok(()) + } + + pub async fn simulate(&self) -> Result { + let tx = self.xdr_args.txn()?; + let network = self.config.get_network()?; + let client = crate::rpc::Client::new(&network.rpc_url)?; + Ok(client.create_assembled_transaction(&tx).await?) + } +} diff --git a/cmd/soroban-cli/src/commands/txn/xdr.rs b/cmd/soroban-cli/src/commands/txn/xdr.rs new file mode 100644 index 0000000000..180c12e6bc --- /dev/null +++ b/cmd/soroban-cli/src/commands/txn/xdr.rs @@ -0,0 +1,67 @@ +use std::{ + io::{stdin, Read}, + path::PathBuf, +}; + +use soroban_env_host::xdr::ReadXdr; +use soroban_sdk::xdr::{Limits, Transaction, TransactionEnvelope}; + +#[derive(Debug, thiserror::Error)] +pub enum Error { + #[error("failed to decode XDR from base64")] + Base64Decode, + #[error("failed to decode XDR from file: {0}")] + FileDecode(PathBuf), + #[error("failed to decode XDR from stdin")] + StdinDecode, + #[error(transparent)] + Io(#[from] std::io::Error), +} + +/// XDR input, either base64 encoded or file path and stdin if neither is provided +#[derive(Debug, clap::Args, Clone)] +#[group(skip)] +pub struct Args { + /// Base64 encoded XDR transaction + #[arg( + long = "xdr-base64", + env = "STELLAR_TXN_XDR_BASE64", + conflicts_with = "xdr_file" + )] + pub xdr_base64: Option, + //// File containing Binary encoded data + #[arg( + long = "xdr-file", + env = "STELLAR_TXN_XDR_FILE", + conflicts_with = "xdr_base64" + )] + pub xdr_file: Option, +} + +impl Args { + pub fn xdr(&self) -> Result { + match (self.xdr_base64.as_ref(), self.xdr_file.as_ref()) { + (Some(xdr_base64), None) => { + T::from_xdr_base64(xdr_base64, Limits::none()).map_err(|_| Error::Base64Decode) + } + (_, Some(xdr_file)) => T::from_xdr(std::fs::read(xdr_file)?, Limits::none()) + .map_err(|_| Error::FileDecode(xdr_file.clone())), + + _ => { + let mut buf = String::new(); + let _ = stdin() + .read_to_string(&mut buf) + .map_err(|_| Error::StdinDecode)?; + T::from_xdr_base64(buf.trim(), Limits::none()).map_err(|_| Error::StdinDecode) + } + } + } + + pub fn txn(&self) -> Result { + self.xdr::() + } + + pub fn txn_envelope(&self) -> Result { + self.xdr::() + } +} diff --git a/cmd/soroban-cli/src/lib.rs b/cmd/soroban-cli/src/lib.rs index 5cde45436c..5f44d6b8bb 100644 --- a/cmd/soroban-cli/src/lib.rs +++ b/cmd/soroban-cli/src/lib.rs @@ -12,6 +12,7 @@ pub mod commands; pub mod fee; pub mod key; pub mod log; +pub mod signer; pub mod toid; pub mod utils; pub mod wasm; diff --git a/cmd/soroban-cli/src/signer.rs b/cmd/soroban-cli/src/signer.rs new file mode 100644 index 0000000000..bd56055984 --- /dev/null +++ b/cmd/soroban-cli/src/signer.rs @@ -0,0 +1,277 @@ +use ed25519_dalek::Signer; +use sha2::{Digest, Sha256}; + +use soroban_env_host::xdr::{ + self, AccountId, DecoratedSignature, Hash, HashIdPreimage, HashIdPreimageSorobanAuthorization, + InvokeHostFunctionOp, Limits, Operation, OperationBody, PublicKey, ScAddress, ScMap, ScSymbol, + ScVal, Signature, SignatureHint, SorobanAddressCredentials, SorobanAuthorizationEntry, + SorobanAuthorizedFunction, SorobanCredentials, Transaction, TransactionEnvelope, + TransactionSignaturePayload, TransactionSignaturePayloadTaggedTransaction, + TransactionV1Envelope, Uint256, WriteXdr, +}; + +#[derive(thiserror::Error, Debug)] +pub enum Error { + #[error(transparent)] + Xdr(#[from] xdr::Error), + #[error("Error signing transaction {address}")] + MissingSignerForAddress { address: String }, +} + +fn requires_auth(txn: &Transaction) -> Option { + let [op @ Operation { + body: OperationBody::InvokeHostFunction(InvokeHostFunctionOp { auth, .. }), + .. + }] = txn.operations.as_slice() + else { + return None; + }; + matches!( + auth.first().map(|x| &x.root_invocation.function), + Some(&SorobanAuthorizedFunction::ContractFn(_)) + ) + .then(move || op.clone()) +} + +/// A trait for signing Stellar transactions and Soroban authorization entries +pub trait Stellar { + /// The type of the options that can be passed when creating a new signer + type Init; + /// Create a new signer with the given network passphrase and options + fn new(network_passphrase: &str, options: Option) -> Self; + + /// Get the network hash + fn network_hash(&self) -> xdr::Hash; + + /// Sign a transaction hash with the given source account + /// # Errors + /// Returns an error if the source account is not found + fn sign_txn_hash( + &self, + txn: [u8; 32], + source_account: &stellar_strkey::Strkey, + ) -> Result; + + /// Sign a Soroban authorization entry with the given address + /// # Errors + /// Returns an error if the address is not found + fn sign_soroban_authorization_entry( + &self, + unsigned_entry: &SorobanAuthorizationEntry, + signature_expiration_ledger: u32, + address: &[u8; 32], + ) -> Result; + + /// Sign a Stellar transaction with the given source account + /// This is a default implementation that signs the transaction hash and returns a decorated signature + /// # Errors + /// Returns an error if the source account is not found + fn sign_txn( + &self, + txn: Transaction, + source_account: &stellar_strkey::Strkey, + ) -> Result { + let signature_payload = TransactionSignaturePayload { + network_id: self.network_hash(), + tagged_transaction: TransactionSignaturePayloadTaggedTransaction::Tx(txn.clone()), + }; + let hash = Sha256::digest(signature_payload.to_xdr(Limits::none())?).into(); + let decorated_signature = self.sign_txn_hash(hash, source_account)?; + Ok(TransactionEnvelope::Tx(TransactionV1Envelope { + tx: txn, + signatures: vec![decorated_signature].try_into()?, + })) + } + + /// Sign a Soroban authorization entries for a given transaction and set the expiration ledger + /// # Errors + /// Returns an error if the address is not found + fn sign_soroban_authorizations( + &self, + raw: &Transaction, + signature_expiration_ledger: u32, + ) -> Result, Error> { + let mut tx = raw.clone(); + let Some(mut op) = requires_auth(&tx) else { + return Ok(None); + }; + + let xdr::Operation { + body: OperationBody::InvokeHostFunction(ref mut body), + .. + } = op + else { + return Ok(None); + }; + + let signed_auths = body + .auth + .as_slice() + .iter() + .map(|raw_auth| { + self.maybe_sign_soroban_authorization_entry(raw_auth, signature_expiration_ledger) + }) + .collect::, Error>>()?; + + body.auth = signed_auths.try_into()?; + tx.operations = vec![op].try_into()?; + Ok(Some(tx)) + } + + /// Sign a Soroban authorization entry if the address is public key + /// # Errors + /// Returns an error if the address in entry is a contract + fn maybe_sign_soroban_authorization_entry( + &self, + unsigned_entry: &SorobanAuthorizationEntry, + signature_expiration_ledger: u32, + ) -> Result { + if let SorobanAuthorizationEntry { + credentials: SorobanCredentials::Address(SorobanAddressCredentials { ref address, .. }), + .. + } = unsigned_entry + { + // See if we have a signer for this authorizationEntry + // If not, then we Error + let needle = match address { + ScAddress::Account(AccountId(PublicKey::PublicKeyTypeEd25519(Uint256(ref a)))) => a, + ScAddress::Contract(Hash(c)) => { + // This address is for a contract. This means we're using a custom + // smart-contract account. Currently the CLI doesn't support that yet. + return Err(Error::MissingSignerForAddress { + address: stellar_strkey::Strkey::Contract(stellar_strkey::Contract(*c)) + .to_string(), + }); + } + }; + self.sign_soroban_authorization_entry( + unsigned_entry, + signature_expiration_ledger, + needle, + ) + } else { + Ok(unsigned_entry.clone()) + } + } +} + +use std::fmt::Debug; +#[derive(Debug)] +pub struct InMemory { + pub network_passphrase: String, + pub keypairs: Vec, +} + +impl InMemory { + pub fn get_key( + &self, + key: &stellar_strkey::Strkey, + ) -> Result<&ed25519_dalek::SigningKey, Error> { + match key { + stellar_strkey::Strkey::PublicKeyEd25519(stellar_strkey::ed25519::PublicKey(bytes)) => { + self.keypairs + .iter() + .find(|k| k.verifying_key().to_bytes() == *bytes) + } + _ => None, + } + .ok_or_else(|| Error::MissingSignerForAddress { + address: key.to_string(), + }) + } +} + +impl Stellar for InMemory { + type Init = Vec; + fn new(network_passphrase: &str, options: Option>) -> Self { + InMemory { + network_passphrase: network_passphrase.to_string(), + keypairs: options.unwrap_or_default(), + } + } + + fn sign_txn_hash( + &self, + txn: [u8; 32], + source_account: &stellar_strkey::Strkey, + ) -> Result { + let source_account = self.get_key(source_account)?; + let tx_signature = source_account.sign(&txn); + Ok(DecoratedSignature { + // TODO: remove this unwrap. It's safe because we know the length of the array + hint: SignatureHint( + source_account.verifying_key().to_bytes()[28..] + .try_into() + .unwrap(), + ), + signature: Signature(tx_signature.to_bytes().try_into()?), + }) + } + + fn sign_soroban_authorization_entry( + &self, + unsigned_entry: &SorobanAuthorizationEntry, + signature_expiration_ledger: u32, + signer: &[u8; 32], + ) -> Result { + let mut auth = unsigned_entry.clone(); + let SorobanAuthorizationEntry { + credentials: SorobanCredentials::Address(ref mut credentials), + .. + } = auth + else { + // Doesn't need special signing + return Ok(auth); + }; + let SorobanAddressCredentials { nonce, .. } = credentials; + + let preimage = HashIdPreimage::SorobanAuthorization(HashIdPreimageSorobanAuthorization { + network_id: self.network_hash(), + invocation: auth.root_invocation.clone(), + nonce: *nonce, + signature_expiration_ledger, + }) + .to_xdr(Limits::none())?; + + let strkey = stellar_strkey::ed25519::PublicKey(*signer); + let payload = Sha256::digest(preimage); + let signer = self.get_key(&stellar_strkey::Strkey::PublicKeyEd25519(strkey))?; + let signature = signer.sign(&payload); + + let map = ScMap::sorted_from(vec![ + ( + ScVal::Symbol(ScSymbol("public_key".try_into()?)), + ScVal::Bytes( + signer + .verifying_key() + .to_bytes() + .to_vec() + .try_into() + .map_err(Error::Xdr)?, + ), + ), + ( + ScVal::Symbol(ScSymbol("signature".try_into()?)), + ScVal::Bytes( + signature + .to_bytes() + .to_vec() + .try_into() + .map_err(Error::Xdr)?, + ), + ), + ]) + .map_err(Error::Xdr)?; + credentials.signature = ScVal::Vec(Some( + vec![ScVal::Map(Some(map))].try_into().map_err(Error::Xdr)?, + )); + credentials.signature_expiration_ledger = signature_expiration_ledger; + auth.credentials = SorobanCredentials::Address(credentials.clone()); + + Ok(auth) + } + + fn network_hash(&self) -> xdr::Hash { + xdr::Hash(Sha256::digest(self.network_passphrase.as_bytes()).into()) + } +} diff --git a/docs/soroban-cli-full-docs.md b/docs/soroban-cli-full-docs.md index b005f1e7f7..70935103cc 100644 --- a/docs/soroban-cli-full-docs.md +++ b/docs/soroban-cli-full-docs.md @@ -51,6 +51,17 @@ This document contains the help content for the `soroban` command-line program. * [`soroban network start`↴](#soroban-network-start) * [`soroban network stop`↴](#soroban-network-stop) * [`soroban version`↴](#soroban-version) +* [`soroban txn`↴](#soroban-txn) +* [`soroban txn inspect`↴](#soroban-txn-inspect) +* [`soroban txn inspect types`↴](#soroban-txn-inspect-types) +* [`soroban txn inspect types list`↴](#soroban-txn-inspect-types-list) +* [`soroban txn inspect guess`↴](#soroban-txn-inspect-guess) +* [`soroban txn inspect decode`↴](#soroban-txn-inspect-decode) +* [`soroban txn inspect encode`↴](#soroban-txn-inspect-encode) +* [`soroban txn inspect version`↴](#soroban-txn-inspect-version) +* [`soroban txn sign`↴](#soroban-txn-sign) +* [`soroban txn send`↴](#soroban-txn-send) +* [`soroban txn simulate`↴](#soroban-txn-simulate) * [`soroban cache`↴](#soroban-cache) * [`soroban cache clean`↴](#soroban-cache-clean) * [`soroban cache path`↴](#soroban-cache-path) @@ -96,6 +107,7 @@ Full CLI reference: https://github.com/stellar/soroban-tools/tree/main/docs/soro * `xdr` — Decode and encode XDR * `network` — Start and configure networks * `version` — Print version information +* `txn` — Sign, Simulate, and Send transactions * `cache` — Cache for tranasctions and contract specs ###### **Options:** @@ -1314,6 +1326,240 @@ Print version information +## `soroban txn` + +Sign, Simulate, and Send transactions + +**Usage:** `soroban txn ` + +###### **Subcommands:** + +* `inspect` — Add a new identity (keypair, ledger, macOS keychain) +* `sign` — Given an identity return its address (public key) +* `send` — Submit a transaction to the network +* `simulate` — Simulate a transaction + + + +## `soroban txn inspect` + +Add a new identity (keypair, ledger, macOS keychain) + +**Usage:** `soroban txn inspect [CHANNEL] ` + +###### **Subcommands:** + +* `types` — View information about types +* `guess` — Guess the XDR type +* `decode` — Decode XDR +* `encode` — Encode XDR +* `version` — Print version information + +###### **Arguments:** + +* `` — Channel of XDR to operate on + + Default value: `+curr` + + Possible values: `+curr`, `+next` + + + + +## `soroban txn inspect types` + +View information about types + +**Usage:** `soroban txn inspect types ` + +###### **Subcommands:** + +* `list` — + + + +## `soroban txn inspect types list` + +**Usage:** `soroban txn inspect types list [OPTIONS]` + +###### **Options:** + +* `--output ` + + Default value: `plain` + + Possible values: `plain`, `json`, `json-formatted` + + + + +## `soroban txn inspect guess` + +Guess the XDR type + +**Usage:** `soroban txn inspect guess [OPTIONS] [FILE]` + +###### **Arguments:** + +* `` — File to decode, or stdin if omitted + +###### **Options:** + +* `--input ` + + Default value: `single-base64` + + Possible values: `single`, `single-base64`, `stream`, `stream-base64`, `stream-framed` + +* `--output ` + + Default value: `list` + + Possible values: `list` + +* `--certainty ` — Certainty as an arbitrary value + + Default value: `2` + + + +## `soroban txn inspect decode` + +Decode XDR + +**Usage:** `soroban txn inspect decode [OPTIONS] --type [FILES]...` + +###### **Arguments:** + +* `` — Files to decode, or stdin if omitted + +###### **Options:** + +* `--type ` — XDR type to decode +* `--input ` + + Default value: `stream-base64` + + Possible values: `single`, `single-base64`, `stream`, `stream-base64`, `stream-framed` + +* `--output ` + + Default value: `json` + + Possible values: `json`, `json-formatted`, `rust-debug`, `rust-debug-formatted` + + + + +## `soroban txn inspect encode` + +Encode XDR + +**Usage:** `soroban txn inspect encode [OPTIONS] --type [FILES]...` + +###### **Arguments:** + +* `` — Files to encode, or stdin if omitted + +###### **Options:** + +* `--type ` — XDR type to encode +* `--input ` + + Default value: `json` + + Possible values: `json` + +* `--output ` + + Default value: `single-base64` + + Possible values: `single`, `single-base64` + + + + +## `soroban txn inspect version` + +Print version information + +**Usage:** `soroban txn inspect version` + + + +## `soroban txn sign` + +Given an identity return its address (public key) + +**Usage:** `soroban txn sign [OPTIONS] --source-account ` + +###### **Options:** + +* `-y`, `--yes` — Confirm that a signature can be signed by the given keypair automatically + + Possible values: `true`, `false` + +* `--xdr-base64 ` — Base64 encoded XDR transaction +* `--xdr-file ` +* `--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 +* `--source-account ` — Account that signs the final transaction. Alias `source`. Can be an identity (--source alice), a secret key (--source SC36…), or a seed phrase (--source "kite urban…"). Default: `identity generate --default-seed` +* `--hd-path ` — If using a seed phrase, which hierarchical deterministic path to use, e.g. `m/44'/148'/{hd_path}`. Example: `--hd-path 1`. Default: `0` +* `--global` — Use global config + + Possible values: `true`, `false` + +* `--config-dir ` — Location of config directory, default is "." + + + +## `soroban txn send` + +Submit a transaction to the network + +**Usage:** `soroban txn send [OPTIONS] --source-account ` + +###### **Options:** + +* `--xdr-base64 ` — Base64 encoded XDR transaction +* `--xdr-file ` +* `--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 +* `--source-account ` — Account that signs the final transaction. Alias `source`. Can be an identity (--source alice), a secret key (--source SC36…), or a seed phrase (--source "kite urban…"). Default: `identity generate --default-seed` +* `--hd-path ` — If using a seed phrase, which hierarchical deterministic path to use, e.g. `m/44'/148'/{hd_path}`. Example: `--hd-path 1`. Default: `0` +* `--global` — Use global config + + Possible values: `true`, `false` + +* `--config-dir ` — Location of config directory, default is "." + + + +## `soroban txn simulate` + +Simulate a transaction + +**Usage:** `soroban txn simulate [OPTIONS] --source-account ` + +###### **Options:** + +* `--xdr-base64 ` — Base64 encoded XDR transaction +* `--xdr-file ` +* `--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 +* `--source-account ` — Account that signs the final transaction. Alias `source`. Can be an identity (--source alice), a secret key (--source SC36…), or a seed phrase (--source "kite urban…"). Default: `identity generate --default-seed` +* `--hd-path ` — If using a seed phrase, which hierarchical deterministic path to use, e.g. `m/44'/148'/{hd_path}`. Example: `--hd-path 1`. Default: `0` +* `--global` — Use global config + + Possible values: `true`, `false` + +* `--config-dir ` — Location of config directory, default is "." + + + ## `soroban cache` Cache for tranasctions and contract specs