Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
fbb9f7b
Add stellar tx fetch events cmd
elizabethengelman Aug 25, 2025
43e6d92
Merge branch 'main' into feat/tx-events
elizabethengelman Aug 27, 2025
2f00a23
Fix fmt and remove println
elizabethengelman Aug 27, 2025
69e2163
Merge branch 'main' into feat/tx-events
elizabethengelman Aug 28, 2025
f90e750
Merge branch 'main' into feat/tx-events
elizabethengelman Sep 5, 2025
976dce6
Add integration test
elizabethengelman Sep 8, 2025
ca9a2e2
Merge branch 'main' into feat/tx-events
elizabethengelman Sep 8, 2025
885bd2c
Merge branch 'main' into feat/tx-events
elizabethengelman Sep 8, 2025
f99001d
Merge branch 'main' into feat/tx-events
elizabethengelman Sep 8, 2025
5978d79
Generated docs & fmt
elizabethengelman Sep 8, 2025
49d8ce8
Merge branch 'main' into feat/tx-events
elizabethengelman Sep 10, 2025
31dc988
Merge branch 'main' into feat/tx-events
elizabethengelman Sep 11, 2025
5a100e9
Merge branch 'main' into feat/tx-events
elizabethengelman Sep 15, 2025
de48e84
Merge branch 'main' into feat/tx-events
elizabethengelman Sep 15, 2025
6c92f78
Merge branch 'main' into feat/tx-events
elizabethengelman Sep 15, 2025
f9877ae
Add text output options for tx events
elizabethengelman Sep 16, 2025
ed909ba
Clippy
elizabethengelman Sep 16, 2025
b790f4e
Cargo fmt
elizabethengelman Sep 16, 2025
4a82a74
Generated docs
elizabethengelman Sep 16, 2025
ef58612
Merge branch 'main' into feat/tx-events
elizabethengelman Sep 24, 2025
127fa37
Merge branch 'main' into feat/tx-events
fnando Sep 25, 2025
4b112f5
Merge branch 'main' into feat/tx-events
elizabethengelman Sep 29, 2025
d956189
Merge branch 'main' into feat/tx-events
elizabethengelman Sep 29, 2025
e198b3d
Merge branch 'main' into feat/tx-events
elizabethengelman Oct 14, 2025
9e686d5
Merge branch 'main' into feat/tx-events
elizabethengelman Oct 15, 2025
7b05bb8
Fix code issues in gen docs
elizabethengelman Oct 15, 2025
c371eef
Merge branch 'main' into feat/tx-events
janewang Oct 15, 2025
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
35 changes: 29 additions & 6 deletions FULL_HELP_DOCS.md
Original file line number Diff line number Diff line change
Expand Up @@ -3241,6 +3241,7 @@ Fetch a transaction from the network by hash If no subcommand is passed in, the
- `result` — Fetch the transaction result
- `meta` — Fetch the transaction meta
- `fee` — Fetch the transaction fee information
- `events` — Fetch the transaction events

###### **Options:**

Expand All @@ -3254,8 +3255,8 @@ Fetch a transaction from the network by hash If no subcommand is passed in, the
Default value: `json`

Possible values:
- `json`: JSON output of the ledger entry with parsed XDRs (one line, not formatted)
- `json-formatted`: Formatted (multiline) JSON output of the ledger entry with parsed XDRs
- `json`: JSON output with parsed XDRs (one line, not formatted)
- `json-formatted`: Formatted (multiline) JSON output with parsed XDRs
- `xdr`: Original RPC output (containing XDRs)

## `stellar tx fetch result`
Expand All @@ -3276,8 +3277,8 @@ Fetch the transaction result
Default value: `json`

Possible values:
- `json`: JSON output of the ledger entry with parsed XDRs (one line, not formatted)
- `json-formatted`: Formatted (multiline) JSON output of the ledger entry with parsed XDRs
- `json`: JSON output with parsed XDRs (one line, not formatted)
- `json-formatted`: Formatted (multiline) JSON output with parsed XDRs
- `xdr`: Original RPC output (containing XDRs)

## `stellar tx fetch meta`
Expand All @@ -3298,8 +3299,8 @@ Fetch the transaction meta
Default value: `json`

Possible values:
- `json`: JSON output of the ledger entry with parsed XDRs (one line, not formatted)
- `json-formatted`: Formatted (multiline) JSON output of the ledger entry with parsed XDRs
- `json`: JSON output with parsed XDRs (one line, not formatted)
- `json-formatted`: Formatted (multiline) JSON output with parsed XDRs
- `xdr`: Original RPC output (containing XDRs)

## `stellar tx fetch fee`
Expand All @@ -3324,6 +3325,28 @@ Fetch the transaction fee information
- `json-formatted`: Formatted (multiline) JSON output of the ledger entry with parsed XDRs
- `table`: Formatted in a table comparing fee types

## `stellar tx fetch events`

Fetch the transaction events

**Usage:** `stellar tx fetch events [OPTIONS] --hash <HASH>`

###### **Options:**

- `--hash <HASH>` — Transaction hash to fetch
- `--rpc-url <RPC_URL>` — RPC server endpoint
- `--rpc-header <RPC_HEADERS>` — RPC Header(s) to include in requests to the RPC provider
- `--network-passphrase <NETWORK_PASSPHRASE>` — Network passphrase to sign the transaction sent to the rpc server
- `-n`, `--network <NETWORK>` — Name of network to use from config
- `--output <OUTPUT>` — Format of the output

Default value: `json`

Possible values:
- `json`: JSON output of the events with parsed XDRs (one line, not formatted)
- `json-formatted`: Formatted (multiline) JSON output of events with parsed XDRs
- `text`: Human readable event output with parsed XDRs

## `stellar tx decode`

Decode a transaction envelope from XDR to JSON
Expand Down
87 changes: 87 additions & 0 deletions cmd/crates/soroban-test/tests/it/integration/tx/fetch.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use soroban_cli::{
commands::tx::fetch::fee::FeeTable,
commands::tx::fetch::GetTransactionEvents,
utils::transaction_hash,
xdr::{
Limits, ReadXdr, TransactionEnvelope, TransactionMeta, TransactionResult,
Expand Down Expand Up @@ -284,6 +285,92 @@ async fn tx_fetch_fee() {
);
}

#[tokio::test]
async fn tx_fetch_events() {
let sandbox = &TestEnv::new();
let test_account_alias = "test";
let contract_id = deploy_contract(
sandbox,
HELLO_WORLD,
DeployOptions {
deployer: Some(test_account_alias.to_string()),
..Default::default()
},
)
.await;

let tx_xdr = sandbox
.new_assert_cmd("contract")
.arg("invoke")
.arg("--build-only")
.arg("--id")
.arg(contract_id.clone())
.arg("--network")
.arg("local")
.arg("--")
.arg("log")
.arg("--str")
.arg("hi")
.assert()
.success()
.stdout_as_str();

let tx_simulated = sandbox
.new_assert_cmd("tx")
.arg("simulate")
.write_stdin(tx_xdr.as_bytes())
.assert()
.success()
.stdout_as_str();

let signed = sandbox
.new_assert_cmd("tx")
.arg("sign")
.arg("--sign-with-key")
.arg("test")
.write_stdin(tx_simulated.as_bytes())
.assert()
.success()
.stdout_as_str();

sandbox
.new_assert_cmd("tx")
.arg("send")
.write_stdin(signed.as_bytes())
.assert()
.success()
.stdout_as_str();

let tx_env = TransactionEnvelope::from_xdr_base64(signed.clone(), Limits::none()).unwrap();
let tx = if let TransactionEnvelope::Tx(env) = tx_env {
env.tx
} else {
panic!("Expected TransactionEnvelope::Tx, got something else");
};

let tx_hash = hex::encode(transaction_hash(&tx, &sandbox.network.network_passphrase).unwrap());

// fetch the tx events
let output = sandbox
.new_assert_cmd("tx")
.arg("fetch")
.arg("events")
.arg("--hash")
.arg(&tx_hash)
.arg("--network")
.arg("local")
.arg("--output")
.arg("json")
.assert()
.success()
.stdout_as_str();

let parsed: GetTransactionEvents = serde_json::from_str(&output).unwrap();
assert!(parsed.diagnostic_events.is_empty());
assert_eq!(parsed.contract_events.len(), 1);
assert_eq!(parsed.transaction_events.len(), 2);
}

async fn add_account_data(
sandbox: &TestEnv,
account_alias: &str,
Expand Down
5 changes: 3 additions & 2 deletions cmd/soroban-cli/src/commands/tx/fetch/args.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,10 @@ pub enum Error {

#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, clap::ValueEnum, Default)]
pub enum OutputFormat {
/// JSON output of the ledger entry with parsed XDRs (one line, not formatted)
/// JSON output with parsed XDRs (one line, not formatted)
#[default]
Json,
/// Formatted (multiline) JSON output of the ledger entry with parsed XDRs
/// Formatted (multiline) JSON output with parsed XDRs
JsonFormatted,
/// Original RPC output (containing XDRs)
Xdr,
Expand Down Expand Up @@ -70,5 +70,6 @@ impl Args {
if let Some(ledger) = tx.ledger {
println!("Transaction Ledger: {ledger}");
}
println!();
}
}
136 changes: 136 additions & 0 deletions cmd/soroban-cli/src/commands/tx/fetch/events.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
use crate::{commands::global, xdr};
use clap::{command, Parser};

use super::args;

#[derive(Parser, Debug, Clone)]
#[group(skip)]
pub struct Cmd {
#[command(flatten)]
args: args::Args,

/// Format of the output
#[arg(long, default_value = "json")]
output: EventsOutputFormat,
}

#[derive(thiserror::Error, Debug)]
pub enum Error {
#[error(transparent)]
Serde(#[from] serde_json::Error),
#[error(transparent)]
Xdr(#[from] xdr::Error),
#[error(transparent)]
Args(#[from] args::Error),
}

#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, clap::ValueEnum, Default)]
pub enum EventsOutputFormat {
Comment thread
fnando marked this conversation as resolved.
/// JSON output of the events with parsed XDRs (one line, not formatted)
Json,
/// Formatted (multiline) JSON output of events with parsed XDRs
JsonFormatted,
/// Human readable event output with parsed XDRs
#[default]
Text,
}

impl Cmd {
pub async fn run(&self, global_args: &global::Args) -> Result<(), Error> {
let resp = self.args.fetch_transaction(global_args).await?;
let events = &resp.events;
let contract_events: &Vec<Vec<xdr::ContractEvent>> = &events.contract_events;
let diagnostic_events = &events.diagnostic_events;
let transaction_events = &events.transaction_events;
match self.output {
EventsOutputFormat::Text => {
args::Args::print_tx_summary(&resp);
Self::print_contract_events(contract_events);
Self::print_transaction_events(transaction_events);
Self::print_diagnostic_events(diagnostic_events);
}
EventsOutputFormat::JsonFormatted => {
args::Args::print_tx_summary(&resp);
println!("{}", serde_json::to_string_pretty(&events)?);
}
EventsOutputFormat::Json => {
println!("{}", serde_json::to_string(&events)?);
}
}
Ok(())
}

fn get_sc_val_string(val: &xdr::ScVal) -> String {
match val {
xdr::ScVal::Symbol(sym) => {
format!("Symbol: {:?}", sym.to_string())
}
xdr::ScVal::Address(addr) => {
format!("Address: {:?}", addr.to_string())
}
xdr::ScVal::I128(val) => {
format!("I128: {:?}", val.to_string())
}
other => {
format!("Other: {other:?}")
}
}
}

fn print_contract_event(event: &xdr::ContractEvent) {
if let Some(id) = event.contract_id.as_ref() {
println!(" Contract Id: {id}");
}

match &event.body {
xdr::ContractEventBody::V0(body) => {
for (i, topic) in body.topics.iter().enumerate() {
println!(" Topic[{i}]: {}", Self::get_sc_val_string(topic));
}
println!(" Data: {}", Self::get_sc_val_string(&body.data));
}
}
}

fn print_contract_events(events: &[Vec<xdr::ContractEvent>]) {
if events.is_empty() {
println!("Contract Events: None");
return;
}
println!("Contract Events:");
for event in events.iter().flatten() {
Self::print_contract_event(event);
println!();
}
}

fn print_transaction_events(events: &Vec<xdr::TransactionEvent>) {
if events.is_empty() {
println!("Transaction Events: None");
return;
}
println!("Transaction Events:");
for event in events {
println!(" Transaction State: {:?}", event.stage);
Self::print_contract_event(&event.event);
println!();
}
}

fn print_diagnostic_events(events: &Vec<xdr::DiagnosticEvent>) {
if events.is_empty() {
println!("Diagnostic Events: None");
return;
}

println!("Diagnostic Events:");
for event in events {
println!(
" In Successful Contract Call: {:?}",
event.in_successful_contract_call
);
Self::print_contract_event(&event.event);
println!();
}
}
}
7 changes: 7 additions & 0 deletions cmd/soroban-cli/src/commands/tx/fetch/mod.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
use crate::{commands::global, config::network, xdr::Hash};
use clap::{command, Subcommand};
pub use soroban_rpc::GetTransactionEvents;
use std::fmt::Debug;

mod args;
mod envelope;
mod events;
pub mod fee;
mod meta;
mod result;
Expand All @@ -26,6 +28,8 @@ pub enum FetchCommands {
Meta(meta::Cmd),
/// Fetch the transaction fee information
Fee(fee::Cmd),
/// Fetch the transaction events
Events(events::Cmd),
/// Fetch the transaction envelope
#[command(hide = true)]
Envelope(envelope::Cmd),
Expand Down Expand Up @@ -56,6 +60,8 @@ pub enum Error {
#[error(transparent)]
Envelope(#[from] envelope::Error),
#[error(transparent)]
Events(#[from] events::Error),
#[error(transparent)]
NotSupported(#[from] fee::Error),
#[error("the following required argument was not provided: {0}")]
MissingArg(String),
Expand All @@ -68,6 +74,7 @@ impl Cmd {
Some(FetchCommands::Meta(cmd)) => cmd.run(global_args).await?,
Some(FetchCommands::Envelope(cmd)) => cmd.run(global_args).await?,
Some(FetchCommands::Fee(cmd)) => cmd.run(global_args).await?,
Some(FetchCommands::Events(cmd)) => cmd.run(global_args).await?,
None => {
envelope::Cmd {
args: args::Args {
Expand Down