From a8f1f4ee0d7618ee027c6c694bf27f960c605e06 Mon Sep 17 00:00:00 2001 From: ifropc Date: Sun, 22 Mar 2026 21:15:03 -0700 Subject: [PATCH 1/5] feat: invoke from XDR args --- .../src/commands/contract/arg_parsing.rs | 56 ++++++++++++++++++- .../src/commands/contract/invoke.rs | 36 ++++++++++-- cmd/soroban-cli/src/commands/tx/xdr.rs | 30 +--------- 3 files changed, 84 insertions(+), 38 deletions(-) diff --git a/cmd/soroban-cli/src/commands/contract/arg_parsing.rs b/cmd/soroban-cli/src/commands/contract/arg_parsing.rs index 86297b4569..9c93a03c7d 100644 --- a/cmd/soroban-cli/src/commands/contract/arg_parsing.rs +++ b/cmd/soroban-cli/src/commands/contract/arg_parsing.rs @@ -1,4 +1,4 @@ -use crate::commands::contract::arg_parsing::Error::HelpMessage; +use crate::commands::contract::arg_parsing::Error::{CannotParseXDR, HelpMessage}; use crate::commands::contract::deploy::wasm::CONSTRUCTOR_FUNCTION_NAME; use crate::commands::txn_result::TxnResult; use crate::config::{self, sc_address, UnresolvedScAddress}; @@ -16,8 +16,10 @@ use std::convert::TryInto; use std::env; use std::ffi::OsString; use std::fmt::Debug; -use std::path::PathBuf; -use stellar_xdr::curr::ContractId; +use std::fs::File; +use std::io::{Cursor, Read}; +use std::path::{Path, PathBuf}; +use stellar_xdr::curr::{ContractId, Limited, Limits, ReadXdr, SkipWhitespace}; #[derive(thiserror::Error, Debug)] pub enum Error { @@ -77,6 +79,10 @@ pub enum Error { HelpMessage(String), #[error(transparent)] Signer(#[from] signer::Error), + #[error(transparent)] + Io(#[from] std::io::Error), + #[error("cannot parse XDR: {error}")] + CannotParseXDR { error: xdr::Error }, } pub type HostFunctionParameters = (String, Spec, InvokeContractArgs, Vec); @@ -126,6 +132,50 @@ async fn build_host_function_parameters_with_filter( Ok((function, spec, invoke_args, signers)) } +pub async fn build_host_function_parameters_from_string_xdr( + string_xdr: &OsString, + spec_entries: &[ScSpecEntry], + config: &config::Args, +) -> Result { + let spec = Spec(Some(spec_entries.to_vec())); + let invoke_args = invoke_contract_args_from_input(string_xdr)?; + let mut signers = Vec::::new(); + let args = invoke_args.args.to_vec(); + for x in args { + let signer = match x { + ScVal::Address(addr) => { + let resolved = resolve_address(addr.to_string().as_str(), config)?; + resolve_signer(resolved.as_str(), config).await + } + _ => None, + }; + if let Some(signer) = signer { + signers.push(signer); + } + } + + Ok(( + invoke_args.function_name.to_string(), + spec, + invoke_args, + signers, + )) +} + +fn invoke_contract_args_from_input(input: &OsString) -> Result { + let read: &mut dyn Read = { + let exist = Path::new(input).try_exists(); + if let Ok(true) = exist { + &mut File::open(input)? + } else { + &mut Cursor::new(input.clone().into_encoded_bytes()) + } + }; + + let mut lim = Limited::new(SkipWhitespace::new(read), Limits::none()); + InvokeContractArgs::read_xdr_base64_to_end(&mut lim).map_err(|e| CannotParseXDR { error: e }) +} + fn build_clap_command(spec: &Spec, filter_constructor: bool) -> Result { let mut cmd = clap::Command::new(running_cmd()) .no_binary_name(true) diff --git a/cmd/soroban-cli/src/commands/contract/invoke.rs b/cmd/soroban-cli/src/commands/contract/invoke.rs index bebba454a0..c6dcb4ddd5 100644 --- a/cmd/soroban-cli/src/commands/contract/invoke.rs +++ b/cmd/soroban-cli/src/commands/contract/invoke.rs @@ -12,6 +12,7 @@ use soroban_spec::read::FromWasmError; use super::super::events; use super::arg_parsing; use crate::assembled::Assembled; +use crate::commands::contract::arg_parsing::build_host_function_parameters_from_string_xdr; use crate::commands::tx::fetch; use crate::log::extract_events; use crate::print::Print; @@ -55,9 +56,16 @@ pub struct Cmd { #[arg(long, env = "STELLAR_INVOKE_VIEW")] pub is_view: bool, + #[arg(long, conflicts_with = "CONTRACT_FN_AND_ARGS")] + pub invoke_contract_args: Option, + /// Function name as subcommand, then arguments for that function as `--arg-name value` - #[arg(last = true, id = "CONTRACT_FN_AND_ARGS")] - pub slop: Vec, + #[arg( + last = true, + id = "CONTRACT_FN_AND_ARGS", + conflicts_with = "invoke_contract_args" + )] + pub slop: Option>, #[command(flatten)] pub config: config::Args, @@ -268,8 +276,13 @@ impl Cmd { let spec_entries = self.spec_entries()?; if let Some(spec_entries) = &spec_entries { - // For testing wasm arg parsing - build_host_function_parameters(&contract_id, &self.slop, spec_entries, config).await?; + if let Some(slop) = &self.slop { + // For testing wasm arg parsing + build_host_function_parameters(&contract_id, slop, spec_entries, config).await?; + } else if self.invoke_contract_args.is_none() { + // For giving a nice error message if --invoke-contract-args was not provided and slop not used + build_host_function_parameters(&contract_id, &[], spec_entries, config).await?; + } } let client = network.rpc_client()?; @@ -294,8 +307,19 @@ impl Cmd { .await .map_err(Error::from)?; - let params = - build_host_function_parameters(&contract_id, &self.slop, &spec_entries, config).await?; + let params = if let Some(slop) = &self.slop { + build_host_function_parameters(&contract_id, slop, &spec_entries, config).await? + } else if let Some(invoke_contract_args) = &self.invoke_contract_args { + build_host_function_parameters_from_string_xdr( + invoke_contract_args, + &spec_entries, + config, + ) + .await? + } else { + // For giving a nice error message if --invoke-contract-args was not provided and slop not used + build_host_function_parameters(&contract_id, &[], &spec_entries, config).await? + }; let (function, spec, host_function_params, signers) = params; diff --git a/cmd/soroban-cli/src/commands/tx/xdr.rs b/cmd/soroban-cli/src/commands/tx/xdr.rs index f9d0ef0c63..509aa4c319 100644 --- a/cmd/soroban-cli/src/commands/tx/xdr.rs +++ b/cmd/soroban-cli/src/commands/tx/xdr.rs @@ -6,7 +6,7 @@ use std::fs::File; use std::io::{stdin, Read}; use std::io::{Cursor, IsTerminal}; use std::path::Path; -use stellar_xdr::curr::Limited; +use stellar_xdr::curr::{Limited, SkipWhitespace}; #[derive(Debug, thiserror::Error)] pub enum Error { @@ -41,34 +41,6 @@ pub fn tx_envelope_from_input(input: &Option) -> Result { - pub inner: R, -} - -impl SkipWhitespace { - pub fn new(inner: R) -> Self { - SkipWhitespace { inner } - } -} - -impl Read for SkipWhitespace { - fn read(&mut self, buf: &mut [u8]) -> std::io::Result { - let n = self.inner.read(buf)?; - - let mut written = 0; - for read in 0..n { - if !buf[read].is_ascii_whitespace() { - buf[written] = buf[read]; - written += 1; - } - } - - Ok(written) - } -} -// - pub fn unwrap_envelope_v1(tx_env: TransactionEnvelope) -> Result { let TransactionEnvelope::Tx(TransactionV1Envelope { tx, .. }) = tx_env else { return Err(Error::OnlyTransactionV1Supported); From 10c1c9050a4c5e268a94e8c9e9956c8a737e9432 Mon Sep 17 00:00:00 2001 From: ifropc Date: Sun, 22 Mar 2026 21:45:17 -0700 Subject: [PATCH 2/5] docs --- FULL_HELP_DOCS.md | 1 + cmd/soroban-cli/src/commands/contract/invoke.rs | 2 ++ 2 files changed, 3 insertions(+) diff --git a/FULL_HELP_DOCS.md b/FULL_HELP_DOCS.md index e197e0238a..9a964eb72e 100644 --- a/FULL_HELP_DOCS.md +++ b/FULL_HELP_DOCS.md @@ -895,6 +895,7 @@ stellar contract invoke ... -- --help - `--id ` — Contract ID to invoke - `--is-view` — ⚠️ Deprecated, use `--send=no`. View the result simulating and do not sign and submit transaction +- `--invoke-contract-args ` — (Optional) Base-64 InvokeContractArgs envelope XDR or file containing XDR to decode. If used, function name and arguments must be omitted, as this value will be used instead - `-s`, `--source-account ` [alias: `source`] — Account that where transaction originates from. Alias `source`. Can be an identity (--source alice), a public key (--source GDKW...), a muxed account (--source MDA…), a secret key (--source SC36…), or a seed phrase (--source "kite urban…"). If `--build-only` was NOT provided, this key will also be used to sign the final transaction. In that case, trying to sign with public key will fail - `--sign-with-key ` — Sign with a local key or key saved in OS secure storage. Can be an identity (--sign-with-key alice), a secret key (--sign-with-key SC36…), or a seed phrase (--sign-with-key "kite urban…"). If using seed phrase, `--hd-path` defaults to the `0` path - `--hd-path ` — If using a seed phrase to sign, sets which hierarchical deterministic path to use, e.g. `m/44'/148'/{hd_path}`. Example: `--hd-path 1`. Default: `0` diff --git a/cmd/soroban-cli/src/commands/contract/invoke.rs b/cmd/soroban-cli/src/commands/contract/invoke.rs index c6dcb4ddd5..20af1e7db0 100644 --- a/cmd/soroban-cli/src/commands/contract/invoke.rs +++ b/cmd/soroban-cli/src/commands/contract/invoke.rs @@ -56,6 +56,8 @@ pub struct Cmd { #[arg(long, env = "STELLAR_INVOKE_VIEW")] pub is_view: bool, + /// (Optional) Base-64 InvokeContractArgs envelope XDR or file containing XDR to decode. If used, + /// function name and arguments must be omitted, as this value will be used instead. #[arg(long, conflicts_with = "CONTRACT_FN_AND_ARGS")] pub invoke_contract_args: Option, From d3f4b187a7a0914f74cbbe64bfca7a996a1ff73c Mon Sep 17 00:00:00 2001 From: ifropc Date: Wed, 1 Apr 2026 21:35:44 -0700 Subject: [PATCH 3/5] feat: add tx new command --- FULL_HELP_DOCS.md | 32 +++++++++++++++- .../src/commands/contract/arg_parsing.rs | 38 +++---------------- .../src/commands/contract/invoke.rs | 38 +++---------------- cmd/soroban-cli/src/commands/tx/args.rs | 3 ++ cmd/soroban-cli/src/commands/tx/help.rs | 1 + cmd/soroban-cli/src/commands/tx/new/mod.rs | 5 +++ 6 files changed, 51 insertions(+), 66 deletions(-) diff --git a/FULL_HELP_DOCS.md b/FULL_HELP_DOCS.md index 9a964eb72e..763e8d7ad1 100644 --- a/FULL_HELP_DOCS.md +++ b/FULL_HELP_DOCS.md @@ -895,7 +895,6 @@ stellar contract invoke ... -- --help - `--id ` — Contract ID to invoke - `--is-view` — ⚠️ Deprecated, use `--send=no`. View the result simulating and do not sign and submit transaction -- `--invoke-contract-args ` — (Optional) Base-64 InvokeContractArgs envelope XDR or file containing XDR to decode. If used, function name and arguments must be omitted, as this value will be used instead - `-s`, `--source-account ` [alias: `source`] — Account that where transaction originates from. Alias `source`. Can be an identity (--source alice), a public key (--source GDKW...), a muxed account (--source MDA…), a secret key (--source SC36…), or a seed phrase (--source "kite urban…"). If `--build-only` was NOT provided, this key will also be used to sign the final transaction. In that case, trying to sign with public key will fail - `--sign-with-key ` — Sign with a local key or key saved in OS secure storage. Can be an identity (--sign-with-key alice), a secret key (--sign-with-key SC36…), or a seed phrase (--sign-with-key "kite urban…"). If using seed phrase, `--hd-path` defaults to the `0` path - `--hd-path ` — If using a seed phrase to sign, sets which hierarchical deterministic path to use, e.g. `m/44'/148'/{hd_path}`. Example: `--hd-path 1`. Default: `0` @@ -1878,6 +1877,7 @@ Create a new transaction - `revoke-sponsorship` — Revoke sponsorship of a ledger entry or signer - `set-options` — Set account options like flags, signers, and home domain - `set-trustline-flags` — Configure authorization and trustline flags for an asset +- `invoke` — Invoke a smart contract ## `stellar tx new account-merge` @@ -2625,6 +2625,36 @@ Configure authorization and trustline flags for an asset - `--network-passphrase ` — Network passphrase to sign the transaction sent to the rpc server - `-n`, `--network ` — Name of network to use from config +## `stellar tx new invoke` + +Invoke a smart contract + +**Usage:** `stellar tx new invoke [OPTIONS] --source-account --xdr ` + +###### **Options:** + +- `-s`, `--source-account ` [alias: `source`] — Account that where transaction originates from. Alias `source`. Can be an identity (--source alice), a public key (--source GDKW...), a muxed account (--source MDA…), a secret key (--source SC36…), or a seed phrase (--source "kite urban…"). If `--build-only` was NOT provided, this key will also be used to sign the final transaction. In that case, trying to sign with public key will fail +- `--sign-with-key ` — Sign with a local key or key saved in OS secure storage. Can be an identity (--sign-with-key alice), a secret key (--sign-with-key SC36…), or a seed phrase (--sign-with-key "kite urban…"). If using seed phrase, `--hd-path` defaults to the `0` path +- `--hd-path ` — If using a seed phrase to sign, sets which hierarchical deterministic path to use, e.g. `m/44'/148'/{hd_path}`. Example: `--hd-path 1`. Default: `0` +- `--sign-with-lab` — Sign with https://lab.stellar.org +- `--sign-with-ledger` — Sign with a ledger wallet +- `--fee ` — ⚠️ Deprecated, use `--inclusion-fee`. Fee amount for transaction, in stroops. 1 stroop = 0.0000001 xlm +- `--inclusion-fee ` — Maximum fee amount for transaction inclusion, in stroops. 1 stroop = 0.0000001 xlm. Defaults to 100 if no arg, env, or config value is provided +- `--build-only` — Build the transaction and only write the base64 xdr to stdout +- `--xdr ` — Base-64 InvokeContractArgs envelope XDR or file containing XDR to decode + +###### **Options (Global):** + +- `--global` — ⚠️ Deprecated: global config is always on +- `--config-dir ` — Location of config directory. By default, it uses `$XDG_CONFIG_HOME/stellar` if set, falling back to `~/.config/stellar` otherwise. Contains configuration files, aliases, and other persistent settings + +###### **Options (RPC):** + +- `--rpc-url ` — RPC server endpoint +- `--rpc-header ` — RPC Header(s) to include in requests to the RPC provider, example: "X-API-Key: abc123". Multiple headers can be added by passing the option multiple times +- `--network-passphrase ` — Network passphrase to sign the transaction sent to the rpc server +- `-n`, `--network ` — Name of network to use from config + ## `stellar tx operation` Manipulate the operations in a transaction, including adding new operations diff --git a/cmd/soroban-cli/src/commands/contract/arg_parsing.rs b/cmd/soroban-cli/src/commands/contract/arg_parsing.rs index 9c93a03c7d..9b7464bdca 100644 --- a/cmd/soroban-cli/src/commands/contract/arg_parsing.rs +++ b/cmd/soroban-cli/src/commands/contract/arg_parsing.rs @@ -19,7 +19,9 @@ use std::fmt::Debug; use std::fs::File; use std::io::{Cursor, Read}; use std::path::{Path, PathBuf}; -use stellar_xdr::curr::{ContractId, Limited, Limits, ReadXdr, SkipWhitespace}; +use stellar_xdr::curr::{ + ContractId, InvokeHostFunctionOp, Limited, Limits, ReadXdr, SkipWhitespace, +}; #[derive(thiserror::Error, Debug)] pub enum Error { @@ -132,37 +134,7 @@ async fn build_host_function_parameters_with_filter( Ok((function, spec, invoke_args, signers)) } -pub async fn build_host_function_parameters_from_string_xdr( - string_xdr: &OsString, - spec_entries: &[ScSpecEntry], - config: &config::Args, -) -> Result { - let spec = Spec(Some(spec_entries.to_vec())); - let invoke_args = invoke_contract_args_from_input(string_xdr)?; - let mut signers = Vec::::new(); - let args = invoke_args.args.to_vec(); - for x in args { - let signer = match x { - ScVal::Address(addr) => { - let resolved = resolve_address(addr.to_string().as_str(), config)?; - resolve_signer(resolved.as_str(), config).await - } - _ => None, - }; - if let Some(signer) = signer { - signers.push(signer); - } - } - - Ok(( - invoke_args.function_name.to_string(), - spec, - invoke_args, - signers, - )) -} - -fn invoke_contract_args_from_input(input: &OsString) -> Result { +pub fn invoke_host_function_op_from_input(input: &OsString) -> Result { let read: &mut dyn Read = { let exist = Path::new(input).try_exists(); if let Ok(true) = exist { @@ -173,7 +145,7 @@ fn invoke_contract_args_from_input(input: &OsString) -> Result Result { diff --git a/cmd/soroban-cli/src/commands/contract/invoke.rs b/cmd/soroban-cli/src/commands/contract/invoke.rs index 20af1e7db0..bebba454a0 100644 --- a/cmd/soroban-cli/src/commands/contract/invoke.rs +++ b/cmd/soroban-cli/src/commands/contract/invoke.rs @@ -12,7 +12,6 @@ use soroban_spec::read::FromWasmError; use super::super::events; use super::arg_parsing; use crate::assembled::Assembled; -use crate::commands::contract::arg_parsing::build_host_function_parameters_from_string_xdr; use crate::commands::tx::fetch; use crate::log::extract_events; use crate::print::Print; @@ -56,18 +55,9 @@ pub struct Cmd { #[arg(long, env = "STELLAR_INVOKE_VIEW")] pub is_view: bool, - /// (Optional) Base-64 InvokeContractArgs envelope XDR or file containing XDR to decode. If used, - /// function name and arguments must be omitted, as this value will be used instead. - #[arg(long, conflicts_with = "CONTRACT_FN_AND_ARGS")] - pub invoke_contract_args: Option, - /// Function name as subcommand, then arguments for that function as `--arg-name value` - #[arg( - last = true, - id = "CONTRACT_FN_AND_ARGS", - conflicts_with = "invoke_contract_args" - )] - pub slop: Option>, + #[arg(last = true, id = "CONTRACT_FN_AND_ARGS")] + pub slop: Vec, #[command(flatten)] pub config: config::Args, @@ -278,13 +268,8 @@ impl Cmd { let spec_entries = self.spec_entries()?; if let Some(spec_entries) = &spec_entries { - if let Some(slop) = &self.slop { - // For testing wasm arg parsing - build_host_function_parameters(&contract_id, slop, spec_entries, config).await?; - } else if self.invoke_contract_args.is_none() { - // For giving a nice error message if --invoke-contract-args was not provided and slop not used - build_host_function_parameters(&contract_id, &[], spec_entries, config).await?; - } + // For testing wasm arg parsing + build_host_function_parameters(&contract_id, &self.slop, spec_entries, config).await?; } let client = network.rpc_client()?; @@ -309,19 +294,8 @@ impl Cmd { .await .map_err(Error::from)?; - let params = if let Some(slop) = &self.slop { - build_host_function_parameters(&contract_id, slop, &spec_entries, config).await? - } else if let Some(invoke_contract_args) = &self.invoke_contract_args { - build_host_function_parameters_from_string_xdr( - invoke_contract_args, - &spec_entries, - config, - ) - .await? - } else { - // For giving a nice error message if --invoke-contract-args was not provided and slop not used - build_host_function_parameters(&contract_id, &[], &spec_entries, config).await? - }; + let params = + build_host_function_parameters(&contract_id, &self.slop, &spec_entries, config).await?; let (function, spec, host_function_params, signers) = params; diff --git a/cmd/soroban-cli/src/commands/tx/args.rs b/cmd/soroban-cli/src/commands/tx/args.rs index 2b4c766c3e..62648ea16b 100644 --- a/cmd/soroban-cli/src/commands/tx/args.rs +++ b/cmd/soroban-cli/src/commands/tx/args.rs @@ -1,3 +1,4 @@ +use crate::commands::contract::arg_parsing; use crate::{ commands::{global, txn_result::TxnEnvelopeResult}, config::{ @@ -50,6 +51,8 @@ pub enum Error { InvalidPoolId(String), #[error("invalid hex for {name}: {hex}")] InvalidHex { name: String, hex: String }, + #[error(transparent)] + ArgParsing(#[from] arg_parsing::Error), } impl Args { diff --git a/cmd/soroban-cli/src/commands/tx/help.rs b/cmd/soroban-cli/src/commands/tx/help.rs index 21f7609180..bda630e0aa 100644 --- a/cmd/soroban-cli/src/commands/tx/help.rs +++ b/cmd/soroban-cli/src/commands/tx/help.rs @@ -24,3 +24,4 @@ pub const BEGIN_SPONSORING_FUTURE_RESERVES: &str = "Begin sponsoring future reserves for another account"; pub const END_SPONSORING_FUTURE_RESERVES: &str = "End sponsoring future reserves"; pub const REVOKE_SPONSORSHIP: &str = "Revoke sponsorship of a ledger entry or signer"; +pub const INVOKE: &str = "Invoke a smart contract"; diff --git a/cmd/soroban-cli/src/commands/tx/new/mod.rs b/cmd/soroban-cli/src/commands/tx/new/mod.rs index 7126bd2762..418179b7f8 100644 --- a/cmd/soroban-cli/src/commands/tx/new/mod.rs +++ b/cmd/soroban-cli/src/commands/tx/new/mod.rs @@ -14,6 +14,7 @@ pub mod create_account; pub mod create_claimable_balance; pub mod create_passive_sell_offer; pub mod end_sponsoring_future_reserves; +pub mod invoke; pub mod liquidity_pool_deposit; pub mod liquidity_pool_withdraw; pub mod manage_buy_offer; @@ -73,6 +74,8 @@ pub enum Cmd { SetOptions(set_options::Cmd), #[command(about = super::help::SET_TRUSTLINE_FLAGS)] SetTrustlineFlags(set_trustline_flags::Cmd), + #[command(about = super::help::INVOKE)] + Invoke(invoke::Cmd), } #[derive(thiserror::Error, Debug)] @@ -107,6 +110,7 @@ impl TryFrom<&Cmd> for OperationBody { Cmd::RevokeSponsorship(cmd) => cmd.try_into()?, Cmd::SetOptions(cmd) => cmd.try_into()?, Cmd::SetTrustlineFlags(cmd) => cmd.try_into()?, + Cmd::Invoke(cmd) => cmd.try_into()?, }) } } @@ -139,6 +143,7 @@ impl Cmd { Cmd::RevokeSponsorship(cmd) => cmd.tx.handle_and_print(op, global_args).await, Cmd::SetOptions(cmd) => cmd.tx.handle_and_print(op, global_args).await, Cmd::SetTrustlineFlags(cmd) => cmd.tx.handle_and_print(op, global_args).await, + Cmd::Invoke(cmd) => cmd.tx.handle_and_print(op, global_args).await, }?; Ok(()) } From 56d3dbe6ee823a865e32b2179f291f49ec34ed75 Mon Sep 17 00:00:00 2001 From: ifropc Date: Wed, 1 Apr 2026 21:39:41 -0700 Subject: [PATCH 4/5] docs --- FULL_HELP_DOCS.md | 4 ++-- cmd/soroban-cli/src/commands/tx/help.rs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/FULL_HELP_DOCS.md b/FULL_HELP_DOCS.md index 763e8d7ad1..30e4ff62ad 100644 --- a/FULL_HELP_DOCS.md +++ b/FULL_HELP_DOCS.md @@ -1877,7 +1877,7 @@ Create a new transaction - `revoke-sponsorship` — Revoke sponsorship of a ledger entry or signer - `set-options` — Set account options like flags, signers, and home domain - `set-trustline-flags` — Configure authorization and trustline flags for an asset -- `invoke` — Invoke a smart contract +- `invoke` — Invoke a smart contract using a given InvokeHostFunctionOp XDR ## `stellar tx new account-merge` @@ -2627,7 +2627,7 @@ Configure authorization and trustline flags for an asset ## `stellar tx new invoke` -Invoke a smart contract +Invoke a smart contract using a given InvokeHostFunctionOp XDR **Usage:** `stellar tx new invoke [OPTIONS] --source-account --xdr ` diff --git a/cmd/soroban-cli/src/commands/tx/help.rs b/cmd/soroban-cli/src/commands/tx/help.rs index bda630e0aa..e0301aead1 100644 --- a/cmd/soroban-cli/src/commands/tx/help.rs +++ b/cmd/soroban-cli/src/commands/tx/help.rs @@ -24,4 +24,4 @@ pub const BEGIN_SPONSORING_FUTURE_RESERVES: &str = "Begin sponsoring future reserves for another account"; pub const END_SPONSORING_FUTURE_RESERVES: &str = "End sponsoring future reserves"; pub const REVOKE_SPONSORSHIP: &str = "Revoke sponsorship of a ledger entry or signer"; -pub const INVOKE: &str = "Invoke a smart contract"; +pub const INVOKE: &str = "Invoke a smart contract using a given InvokeHostFunctionOp XDR"; From 80516025c449b324cd56c5e72cdcfdb1a25211d5 Mon Sep 17 00:00:00 2001 From: ifropc Date: Thu, 2 Apr 2026 00:02:36 -0700 Subject: [PATCH 5/5] add missing invoke --- cmd/soroban-cli/src/commands/tx/new/invoke.rs | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 cmd/soroban-cli/src/commands/tx/new/invoke.rs diff --git a/cmd/soroban-cli/src/commands/tx/new/invoke.rs b/cmd/soroban-cli/src/commands/tx/new/invoke.rs new file mode 100644 index 0000000000..89dc903e84 --- /dev/null +++ b/cmd/soroban-cli/src/commands/tx/new/invoke.rs @@ -0,0 +1,31 @@ +use crate::commands::contract::arg_parsing::invoke_host_function_op_from_input; +use crate::{commands::tx, xdr}; +use clap::Parser; +use std::ffi::OsString; +use stellar_xdr::curr::OperationBody; + +#[derive(Parser, Debug, Clone)] +#[group(skip)] +pub struct Cmd { + #[command(flatten)] + pub tx: tx::Args, + #[clap(flatten)] + pub op: Args, +} + +#[derive(Debug, clap::Args, Clone)] +#[allow(clippy::struct_excessive_bools, clippy::doc_markdown)] +pub struct Args { + /// Base-64 InvokeContractArgs envelope XDR or file containing XDR to decode. + #[arg(long)] + pub xdr: OsString, +} + +impl TryFrom<&Cmd> for xdr::OperationBody { + type Error = tx::args::Error; + fn try_from(cmd: &Cmd) -> Result { + let parameters = invoke_host_function_op_from_input(&cmd.op.xdr)?; + + Ok(OperationBody::InvokeHostFunction(parameters)) + } +}