soroban_cli/commands/tx/fetch/
fee.rs

1use super::args;
2use crate::{
3    commands::global,
4    config::network,
5    rpc,
6    xdr::{self, Hash, SorobanTransactionMetaExt, TransactionEnvelope, TransactionMeta},
7};
8use clap::{command, Parser};
9use prettytable::{
10    format::{FormatBuilder, LinePosition, LineSeparator, TableFormat},
11    Cell, Row, Table,
12};
13use serde::{Deserialize, Serialize};
14use soroban_rpc::GetTransactionResponse;
15
16#[derive(Parser, Debug, Clone)]
17#[group(skip)]
18pub struct Cmd {
19    #[command(flatten)]
20    args: args::Args,
21
22    /// Output format for fee command
23    #[arg(long, default_value = "table")]
24    pub output: FeeOutputFormat,
25}
26
27#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, clap::ValueEnum, Default)]
28pub enum FeeOutputFormat {
29    /// JSON output of the ledger entry with parsed XDRs (one line, not formatted)
30    Json,
31    /// Formatted (multiline) JSON output of the ledger entry with parsed XDRs
32    JsonFormatted,
33    /// Formatted in a table comparing fee types
34    #[default]
35    Table,
36}
37
38#[derive(thiserror::Error, Debug)]
39pub enum Error {
40    #[error(transparent)]
41    Network(#[from] network::Error),
42    #[error(transparent)]
43    Serde(#[from] serde_json::Error),
44    #[error(transparent)]
45    Xdr(#[from] xdr::Error),
46    #[error(transparent)]
47    Args(#[from] args::Error),
48    #[error("{message}")]
49    NotSupported { message: String },
50    #[error("transaction {tx_hash} not found on {network} network")]
51    NotFound { tx_hash: Hash, network: String },
52    #[error(transparent)]
53    Rpc(#[from] rpc::Error),
54    #[error("{field} is None, expected it to be Some")]
55    None { field: String },
56}
57
58const DEFAULT_FEE_VALUE: i64 = 0;
59const FEE_CHARGED_TITLE: &str = "Fee Charged";
60const RESOURCE_FEE_TITLE: &str = "Resource Fee";
61const INCLUSION_FEE_TITLE: &str = "Inclusion Fee";
62const NON_REFUNDABLE_TITLE: &str = "Non-Refundable";
63const REFUNDABLE_TITLE: &str = "Refundable";
64const FEE_PROPOSED_TITLE: &str = "Fee Proposed";
65const REFUNDED_TITLE: &str = "Refunded";
66const NON_REFUNDABLE_COMPONENTS: &str = "\n\ncpu instructions\nstorage read/write\ntx size";
67const REFUNDABLE_COMPONENTS: &str = "\n\nreturn value\nstorage rent\nevents";
68
69impl Cmd {
70    pub async fn run(&self, global_args: &global::Args) -> Result<(), Error> {
71        let resp = self.args.fetch_transaction(global_args).await?;
72        let fee_table = FeeTable::new_from_transaction_response(&resp)?;
73        match self.output {
74            FeeOutputFormat::Json => {
75                println!("{}", serde_json::to_string(&fee_table)?);
76            }
77            FeeOutputFormat::JsonFormatted => {
78                println!("{}", serde_json::to_string_pretty(&fee_table)?);
79            }
80            FeeOutputFormat::Table => {
81                fee_table.print();
82            }
83        }
84
85        Ok(())
86    }
87}
88
89#[derive(Serialize, Deserialize, Debug)]
90pub struct FeeTable {
91    pub fee_charged: i64,
92    pub resource_fee_charged: i64,
93    pub inclusion_fee_charged: i64,
94    pub non_refundable_resource_fee_charged: i64,
95    pub refundable_resource_fee_charged: i64,
96    pub max_fee: i64,
97    pub max_resource_fee: i64,
98}
99
100impl FeeTable {
101    fn new_from_transaction_response(resp: &GetTransactionResponse) -> Result<Self, Error> {
102        let tx_result = resp.result.clone().ok_or(Error::None {
103            field: "tx_result".to_string(),
104        })?; // fee charged
105        let tx_meta = resp.result_meta.clone().ok_or(Error::None {
106            field: "tx_meta".to_string(),
107        })?; // resource fees
108        let tx_envelope = resp.envelope.clone().ok_or(Error::None {
109            field: "tx_envelope".to_string(),
110        })?; // max fees
111
112        let fee_charged = tx_result.fee_charged;
113        let (non_refundable_resource_fee_charged, refundable_resource_fee_charged) =
114            Self::resource_fees_charged(&tx_meta);
115
116        let (max_fee, max_resource_fee) = Self::max_fees(&tx_envelope);
117
118        let resource_fee_charged =
119            non_refundable_resource_fee_charged + refundable_resource_fee_charged;
120        let inclusion_fee_charged = fee_charged - resource_fee_charged;
121        Ok(FeeTable {
122            fee_charged,
123            resource_fee_charged,
124            inclusion_fee_charged,
125            non_refundable_resource_fee_charged,
126            refundable_resource_fee_charged,
127            max_fee,
128            max_resource_fee,
129        })
130    }
131
132    fn max_fees(tx_envelope: &TransactionEnvelope) -> (i64, i64) {
133        match tx_envelope {
134            TransactionEnvelope::TxV0(transaction_v0_envelope) => {
135                let fee = transaction_v0_envelope.tx.fee;
136                (fee.into(), DEFAULT_FEE_VALUE)
137            }
138            TransactionEnvelope::Tx(transaction_v1_envelope) => {
139                let tx = transaction_v1_envelope.tx.clone();
140                let fee = tx.fee;
141                let resource_fee = match tx.ext {
142                    xdr::TransactionExt::V0 => DEFAULT_FEE_VALUE,
143                    xdr::TransactionExt::V1(soroban_transaction_data) => {
144                        soroban_transaction_data.resource_fee
145                    }
146                };
147
148                (fee.into(), resource_fee)
149            }
150            TransactionEnvelope::TxFeeBump(fee_bump_transaction_envelope) => {
151                let fee = fee_bump_transaction_envelope.tx.fee;
152                (fee, DEFAULT_FEE_VALUE)
153            }
154        }
155    }
156
157    fn resource_fees_charged(tx_meta: &TransactionMeta) -> (i64, i64) {
158        let (non_refundable_resource_fee_charged, refundable_resource_fee_charged) =
159            match tx_meta.clone() {
160                TransactionMeta::V0(_) | TransactionMeta::V1(_) | TransactionMeta::V2(_) => {
161                    (DEFAULT_FEE_VALUE, DEFAULT_FEE_VALUE)
162                }
163                TransactionMeta::V3(meta) => {
164                    if let Some(soroban_meta) = meta.soroban_meta {
165                        match soroban_meta.ext {
166                            SorobanTransactionMetaExt::V0 => (DEFAULT_FEE_VALUE, DEFAULT_FEE_VALUE),
167                            SorobanTransactionMetaExt::V1(v1) => (
168                                v1.total_non_refundable_resource_fee_charged,
169                                v1.total_refundable_resource_fee_charged,
170                            ),
171                        }
172                    } else {
173                        (DEFAULT_FEE_VALUE, DEFAULT_FEE_VALUE)
174                    }
175                }
176                TransactionMeta::V4(meta) => {
177                    if let Some(soroban_meta) = meta.soroban_meta {
178                        match soroban_meta.ext {
179                            SorobanTransactionMetaExt::V0 => (DEFAULT_FEE_VALUE, DEFAULT_FEE_VALUE),
180                            SorobanTransactionMetaExt::V1(v1) => (
181                                v1.total_non_refundable_resource_fee_charged,
182                                v1.total_refundable_resource_fee_charged,
183                            ),
184                        }
185                    } else {
186                        (DEFAULT_FEE_VALUE, DEFAULT_FEE_VALUE)
187                    }
188                }
189            };
190
191        (
192            non_refundable_resource_fee_charged,
193            refundable_resource_fee_charged,
194        )
195    }
196
197    fn should_include_resource_fees(&self) -> bool {
198        self.resource_fee_charged != 0 || self.max_resource_fee != 0
199    }
200
201    fn proposed_inclusion_fee(&self) -> i64 {
202        self.max_fee - self.max_resource_fee
203    }
204
205    fn refunded(&self) -> i64 {
206        self.max_fee - self.fee_charged
207    }
208
209    fn refundable_fee_proposed(&self) -> i64 {
210        self.max_resource_fee - self.non_refundable_resource_fee_charged
211    }
212
213    fn table(&self) -> Table {
214        let mut table = Table::new();
215        table.set_format(Self::table_format());
216
217        // Proposed
218        table.add_row(Row::new(vec![Cell::new(&format!(
219            "{FEE_PROPOSED_TITLE}: {}",
220            self.max_fee
221        ))
222        .with_hspan(4)]));
223
224        table.add_row(Row::new(vec![
225            Cell::new(&format!(
226                "{}: {}",
227                INCLUSION_FEE_TITLE,
228                self.proposed_inclusion_fee()
229            )),
230            Cell::new(&format!("{RESOURCE_FEE_TITLE}: {}", self.max_resource_fee)).with_hspan(3),
231        ]));
232
233        table.add_row(Row::new(vec![
234            Cell::new(&format!(
235                "{}: {}",
236                INCLUSION_FEE_TITLE,
237                self.proposed_inclusion_fee()
238            )),
239            Cell::new(&format!(
240                "{NON_REFUNDABLE_TITLE}: {}{}",
241                self.non_refundable_resource_fee_charged, NON_REFUNDABLE_COMPONENTS
242            )),
243            Cell::new(&format!(
244                "{REFUNDABLE_TITLE}: {}{}",
245                self.refundable_fee_proposed(),
246                REFUNDABLE_COMPONENTS
247            ))
248            .with_hspan(2),
249        ]));
250
251        // Info
252        table.add_row(Row::new(vec![Cell::new("👆 Proposed Fee  👇 Final Fee")
253            .style_spec("c")
254            .with_hspan(4)]));
255
256        // Fees Charged
257        if self.should_include_resource_fees() {
258            table.add_row(Row::new(vec![
259                Cell::new(&format!(
260                    "{INCLUSION_FEE_TITLE}: {}",
261                    self.inclusion_fee_charged
262                )),
263                Cell::new(&format!(
264                    "{NON_REFUNDABLE_TITLE}: {}",
265                    self.non_refundable_resource_fee_charged
266                )),
267                Cell::new(&format!(
268                    "{REFUNDABLE_TITLE}: {}",
269                    self.refundable_resource_fee_charged
270                )),
271                Cell::new(&format!("{REFUNDED_TITLE}: {}", self.refunded())),
272            ]));
273
274            table.add_row(Row::new(vec![
275                Cell::new(&format!(
276                    "{INCLUSION_FEE_TITLE}: {}",
277                    self.inclusion_fee_charged
278                )),
279                Cell::new(&format!(
280                    "{}: {}",
281                    RESOURCE_FEE_TITLE, self.resource_fee_charged
282                ))
283                .with_hspan(2),
284                Cell::new(&format!("{REFUNDED_TITLE}: {}", self.refunded())),
285            ]));
286        }
287
288        table.add_row(Row::new(vec![
289            Cell::new(&format!("{FEE_CHARGED_TITLE}: {}", self.fee_charged)).with_hspan(3),
290            Cell::new(&format!("{REFUNDED_TITLE}: {}", self.refunded())),
291        ]));
292
293        table
294    }
295
296    fn print(&self) {
297        self.table().printstd();
298    }
299
300    fn table_format() -> TableFormat {
301        FormatBuilder::new()
302            .column_separator('│')
303            .borders('│')
304            .separators(&[LinePosition::Top], LineSeparator::new('─', '─', '┌', '┐'))
305            .separators(
306                &[LinePosition::Intern],
307                LineSeparator::new('─', '─', '├', '┤'),
308            )
309            .separators(
310                &[LinePosition::Bottom],
311                LineSeparator::new('─', '─', '└', '┘'),
312            )
313            .padding(1, 1)
314            .build()
315    }
316}
317
318#[cfg(test)]
319mod test {
320    use soroban_rpc::GetTransactionResponse;
321
322    use super::*;
323
324    #[test]
325    #[ignore]
326    fn soroban_tx_fee_table() {
327        let resp = soroban_tx_response().unwrap();
328        let fee_table = FeeTable::new_from_transaction_response(&resp).unwrap();
329
330        let expected_fee_charged = 185_119;
331        let expected_non_refundable_charged = 59_343;
332        let expected_refundable_charged = 125_676;
333        let expected_resource_fee_charged =
334            expected_non_refundable_charged + expected_refundable_charged;
335        let expected_inclusion_fee_charged = expected_fee_charged - expected_resource_fee_charged;
336        let expected_max_fee = 248_869;
337        let expected_max_resource_fee = 248_769;
338
339        assert_eq!(fee_table.fee_charged, expected_fee_charged);
340        assert_eq!(
341            fee_table.resource_fee_charged,
342            expected_resource_fee_charged
343        );
344        assert_eq!(
345            fee_table.non_refundable_resource_fee_charged,
346            expected_non_refundable_charged
347        );
348        assert_eq!(
349            fee_table.refundable_resource_fee_charged,
350            expected_refundable_charged
351        );
352        assert_eq!(
353            fee_table.inclusion_fee_charged,
354            expected_inclusion_fee_charged
355        );
356        assert_eq!(fee_table.max_fee, expected_max_fee);
357        assert_eq!(fee_table.max_resource_fee, expected_max_resource_fee);
358    }
359
360    #[test]
361    #[ignore]
362    fn classic_tx_fee_table() {
363        let resp = classic_tx_response().unwrap();
364        let fee_table = FeeTable::new_from_transaction_response(&resp).unwrap();
365
366        let expected_fee_charged = 100;
367        let expected_non_refundable_charged = DEFAULT_FEE_VALUE;
368        let expected_refundable_charged = DEFAULT_FEE_VALUE;
369        let expected_resource_fee_charged =
370            expected_non_refundable_charged + expected_refundable_charged;
371        let expected_inclusion_fee_charged = expected_fee_charged - expected_resource_fee_charged;
372        let expected_max_fee = 100;
373        let expected_max_resource_fee = DEFAULT_FEE_VALUE;
374
375        assert_eq!(fee_table.fee_charged, expected_fee_charged);
376        assert_eq!(
377            fee_table.resource_fee_charged,
378            expected_resource_fee_charged
379        );
380        assert_eq!(
381            fee_table.non_refundable_resource_fee_charged,
382            expected_non_refundable_charged
383        );
384        assert_eq!(
385            fee_table.refundable_resource_fee_charged,
386            expected_refundable_charged
387        );
388        assert_eq!(
389            fee_table.inclusion_fee_charged,
390            expected_inclusion_fee_charged
391        );
392        assert_eq!(fee_table.max_fee, expected_max_fee);
393        assert_eq!(fee_table.max_resource_fee, expected_max_resource_fee);
394    }
395
396    #[test]
397    #[ignore]
398    fn fee_bump_tx_fee_table() {
399        let resp = fee_bump_tx_response().unwrap();
400        let fee_table = FeeTable::new_from_transaction_response(&resp).unwrap();
401
402        let expected_fee_charged = 200;
403        let expected_non_refundable_charged = DEFAULT_FEE_VALUE;
404        let expected_refundable_charged = DEFAULT_FEE_VALUE;
405        let expected_resource_fee_charged =
406            expected_non_refundable_charged + expected_refundable_charged;
407        let expected_inclusion_fee_charged = expected_fee_charged - expected_resource_fee_charged;
408        let expected_max_fee = 400;
409        let expected_max_resource_fee = DEFAULT_FEE_VALUE;
410
411        assert_eq!(fee_table.fee_charged, expected_fee_charged);
412        assert_eq!(
413            fee_table.resource_fee_charged,
414            expected_resource_fee_charged
415        );
416        assert_eq!(
417            fee_table.non_refundable_resource_fee_charged,
418            expected_non_refundable_charged
419        );
420        assert_eq!(
421            fee_table.refundable_resource_fee_charged,
422            expected_refundable_charged
423        );
424        assert_eq!(
425            fee_table.inclusion_fee_charged,
426            expected_inclusion_fee_charged
427        );
428        assert_eq!(fee_table.max_fee, expected_max_fee);
429        assert_eq!(fee_table.max_resource_fee, expected_max_resource_fee);
430    }
431
432    fn soroban_tx_response() -> Result<GetTransactionResponse, serde_json::Error> {
433        serde_json::from_str(SOROBAN_TX_RESPONSE)
434    }
435
436    fn classic_tx_response() -> Result<GetTransactionResponse, serde_json::Error> {
437        serde_json::from_str(CLASSIC_TX_RESPONSE)
438    }
439
440    fn fee_bump_tx_response() -> Result<GetTransactionResponse, serde_json::Error> {
441        serde_json::from_str(FEE_BUMP_TX_RESPONSE)
442    }
443
444    const SOROBAN_TX_RESPONSE: &str = r#"{"status":"SUCCESS","envelope":{"tx":{"tx":{"source_account":"GCBVAIKUZELFVCV6S7KBS47SF2DQQXTN63TJM7D3CNZ7PD6RCDTBYULI","fee":248869,"seq_num":197568495619,"cond":"none","memo":"none","operations":[{"source_account":null,"body":{"invoke_host_function":{"host_function":{"invoke_contract":{"contract_address":"CDJJ2YDDNWVVY6AFSN2UFLMG33Z2IE2ZVYCLU4FFEAVCICLF62IXO44D","function_name":"inc","args":[]}},"auth":[]}}}],"ext":{"v1":{"ext":"v0","resources":{"footprint":{"read_only":[{"contract_data":{"contract":"CDJJ2YDDNWVVY6AFSN2UFLMG33Z2IE2ZVYCLU4FFEAVCICLF62IXO44D","key":"ledger_key_contract_instance","durability":"persistent"}},{"contract_code":{"hash":"e54e59e63d77364714a001d2c968e811d8eafe96b725781458fb8b21acf6d50e"}}],"read_write":[{"contract_data":{"contract":"CDJJ2YDDNWVVY6AFSN2UFLMG33Z2IE2ZVYCLU4FFEAVCICLF62IXO44D","key":{"symbol":"COUNTER"},"durability":"persistent"}}]},"instructions":2092625,"read_bytes":7928,"write_bytes":80},"resource_fee":248769}}},"signatures":[{"hint":"d110e61c","signature":"6b2d9fba82e01a84129582815c554f7b158ea678aeb0eceaa596f21640e1ee926441c2ffebb4eeba81ac48b3c021e8435b6b6c61071a61e498aadcb4e7a04b08"}]}},"result":{"fee_charged":185119,"result":{"tx_success":[{"op_inner":{"invoke_host_function":{"success":"df071a249d03fc2f22313f75c734a254bbea03124cea77001704db0670b2fc02"}}}]},"ext":"v0"},"result_meta":{"v3":{"ext":"v0","tx_changes_before":[{"state":{"last_modified_ledger_seq":54,"data":{"account":{"account_id":"GCBVAIKUZELFVCV6S7KBS47SF2DQQXTN63TJM7D3CNZ7PD6RCDTBYULI","balance":99988036662,"seq_num":197568495618,"num_sub_entries":0,"inflation_dest":null,"flags":0,"home_domain":"","thresholds":"01000000","signers":[],"ext":{"v1":{"liabilities":{"buying":0,"selling":0},"ext":{"v2":{"num_sponsored":0,"num_sponsoring":0,"signer_sponsoring_i_ds":[],"ext":{"v3":{"ext":"v0","seq_ledger":51,"seq_time":1750166268}}}}}}}},"ext":"v0"}},{"updated":{"last_modified_ledger_seq":54,"data":{"account":{"account_id":"GCBVAIKUZELFVCV6S7KBS47SF2DQQXTN63TJM7D3CNZ7PD6RCDTBYULI","balance":99988036662,"seq_num":197568495619,"num_sub_entries":0,"inflation_dest":null,"flags":0,"home_domain":"","thresholds":"01000000","signers":[],"ext":{"v1":{"liabilities":{"buying":0,"selling":0},"ext":{"v2":{"num_sponsored":0,"num_sponsoring":0,"signer_sponsoring_i_ds":[],"ext":{"v3":{"ext":"v0","seq_ledger":54,"seq_time":1750166271}}}}}}}},"ext":"v0"}}],"operations":[{"changes":[{"created":{"last_modified_ledger_seq":54,"data":{"ttl":{"key_hash":"5ac6e64993239b0eba43a13feecffb99ffdbd359624949f275cc0aa417fc75bd","live_until_ledger_seq":2073653}},"ext":"v0"}},{"created":{"last_modified_ledger_seq":54,"data":{"contract_data":{"ext":"v0","contract":"CDJJ2YDDNWVVY6AFSN2UFLMG33Z2IE2ZVYCLU4FFEAVCICLF62IXO44D","key":{"symbol":"COUNTER"},"durability":"persistent","val":{"u32":1}}},"ext":"v0"}}]}],"tx_changes_after":[{"state":{"last_modified_ledger_seq":54,"data":{"account":{"account_id":"GCBVAIKUZELFVCV6S7KBS47SF2DQQXTN63TJM7D3CNZ7PD6RCDTBYULI","balance":99988036662,"seq_num":197568495619,"num_sub_entries":0,"inflation_dest":null,"flags":0,"home_domain":"","thresholds":"01000000","signers":[],"ext":{"v1":{"liabilities":{"buying":0,"selling":0},"ext":{"v2":{"num_sponsored":0,"num_sponsoring":0,"signer_sponsoring_i_ds":[],"ext":{"v3":{"ext":"v0","seq_ledger":54,"seq_time":1750166271}}}}}}}},"ext":"v0"}},{"updated":{"last_modified_ledger_seq":54,"data":{"account":{"account_id":"GCBVAIKUZELFVCV6S7KBS47SF2DQQXTN63TJM7D3CNZ7PD6RCDTBYULI","balance":99988100412,"seq_num":197568495619,"num_sub_entries":0,"inflation_dest":null,"flags":0,"home_domain":"","thresholds":"01000000","signers":[],"ext":{"v1":{"liabilities":{"buying":0,"selling":0},"ext":{"v2":{"num_sponsored":0,"num_sponsoring":0,"signer_sponsoring_i_ds":[],"ext":{"v3":{"ext":"v0","seq_ledger":54,"seq_time":1750166271}}}}}}}},"ext":"v0"}}],"soroban_meta":{"ext":{"v1":{"ext":"v0","total_non_refundable_resource_fee_charged":59343,"total_refundable_resource_fee_charged":125676,"rent_fee_charged":125597}},"events":[],"return_value":{"u32":1},"diagnostic_events":[{"in_successful_contract_call":true,"event":{"ext":"v0","contract_id":null,"type_":"diagnostic","body":{"v0":{"topics":[{"symbol":"fn_call"},{"bytes":"d29d60636dab5c7805937542ad86def3a41359ae04ba70a5202a240965f69177"},{"symbol":"inc"}],"data":"void"}}}},{"in_successful_contract_call":true,"event":{"ext":"v0","contract_id":"d29d60636dab5c7805937542ad86def3a41359ae04ba70a5202a240965f69177","type_":"diagnostic","body":{"v0":{"topics":[{"symbol":"log"}],"data":{"vec":[{"string":"count: {}"},{"u32":0}]}}}}},{"in_successful_contract_call":true,"event":{"ext":"v0","contract_id":"d29d60636dab5c7805937542ad86def3a41359ae04ba70a5202a240965f69177","type_":"diagnostic","body":{"v0":{"topics":[{"symbol":"fn_return"},{"symbol":"inc"}],"data":{"u32":1}}}}},{"in_successful_contract_call":false,"event":{"ext":"v0","contract_id":null,"type_":"diagnostic","body":{"v0":{"topics":[{"symbol":"core_metrics"},{"symbol":"read_entry"}],"data":{"u64":3}}}}},{"in_successful_contract_call":false,"event":{"ext":"v0","contract_id":null,"type_":"diagnostic","body":{"v0":{"topics":[{"symbol":"core_metrics"},{"symbol":"write_entry"}],"data":{"u64":1}}}}},{"in_successful_contract_call":false,"event":{"ext":"v0","contract_id":null,"type_":"diagnostic","body":{"v0":{"topics":[{"symbol":"core_metrics"},{"symbol":"ledger_read_byte"}],"data":{"u64":7928}}}}},{"in_successful_contract_call":false,"event":{"ext":"v0","contract_id":null,"type_":"diagnostic","body":{"v0":{"topics":[{"symbol":"core_metrics"},{"symbol":"ledger_write_byte"}],"data":{"u64":80}}}}},{"in_successful_contract_call":false,"event":{"ext":"v0","contract_id":null,"type_":"diagnostic","body":{"v0":{"topics":[{"symbol":"core_metrics"},{"symbol":"read_key_byte"}],"data":{"u64":144}}}}},{"in_successful_contract_call":false,"event":{"ext":"v0","contract_id":null,"type_":"diagnostic","body":{"v0":{"topics":[{"symbol":"core_metrics"},{"symbol":"write_key_byte"}],"data":{"u64":0}}}}},{"in_successful_contract_call":false,"event":{"ext":"v0","contract_id":null,"type_":"diagnostic","body":{"v0":{"topics":[{"symbol":"core_metrics"},{"symbol":"read_data_byte"}],"data":{"u64":104}}}}},{"in_successful_contract_call":false,"event":{"ext":"v0","contract_id":null,"type_":"diagnostic","body":{"v0":{"topics":[{"symbol":"core_metrics"},{"symbol":"write_data_byte"}],"data":{"u64":80}}}}},{"in_successful_contract_call":false,"event":{"ext":"v0","contract_id":null,"type_":"diagnostic","body":{"v0":{"topics":[{"symbol":"core_metrics"},{"symbol":"read_code_byte"}],"data":{"u64":7824}}}}},{"in_successful_contract_call":false,"event":{"ext":"v0","contract_id":null,"type_":"diagnostic","body":{"v0":{"topics":[{"symbol":"core_metrics"},{"symbol":"write_code_byte"}],"data":{"u64":0}}}}},{"in_successful_contract_call":false,"event":{"ext":"v0","contract_id":null,"type_":"diagnostic","body":{"v0":{"topics":[{"symbol":"core_metrics"},{"symbol":"emit_event"}],"data":{"u64":0}}}}},{"in_successful_contract_call":false,"event":{"ext":"v0","contract_id":null,"type_":"diagnostic","body":{"v0":{"topics":[{"symbol":"core_metrics"},{"symbol":"emit_event_byte"}],"data":{"u64":0}}}}},{"in_successful_contract_call":false,"event":{"ext":"v0","contract_id":null,"type_":"diagnostic","body":{"v0":{"topics":[{"symbol":"core_metrics"},{"symbol":"cpu_insn"}],"data":{"u64":2000326}}}}},{"in_successful_contract_call":false,"event":{"ext":"v0","contract_id":null,"type_":"diagnostic","body":{"v0":{"topics":[{"symbol":"core_metrics"},{"symbol":"mem_byte"}],"data":{"u64":1487598}}}}},{"in_successful_contract_call":false,"event":{"ext":"v0","contract_id":null,"type_":"diagnostic","body":{"v0":{"topics":[{"symbol":"core_metrics"},{"symbol":"invoke_time_nsecs"}],"data":{"u64":158917}}}}},{"in_successful_contract_call":false,"event":{"ext":"v0","contract_id":null,"type_":"diagnostic","body":{"v0":{"topics":[{"symbol":"core_metrics"},{"symbol":"max_rw_key_byte"}],"data":{"u64":60}}}}},{"in_successful_contract_call":false,"event":{"ext":"v0","contract_id":null,"type_":"diagnostic","body":{"v0":{"topics":[{"symbol":"core_metrics"},{"symbol":"max_rw_data_byte"}],"data":{"u64":104}}}}},{"in_successful_contract_call":false,"event":{"ext":"v0","contract_id":null,"type_":"diagnostic","body":{"v0":{"topics":[{"symbol":"core_metrics"},{"symbol":"max_rw_code_byte"}],"data":{"u64":7824}}}}},{"in_successful_contract_call":false,"event":{"ext":"v0","contract_id":null,"type_":"diagnostic","body":{"v0":{"topics":[{"symbol":"core_metrics"},{"symbol":"max_emit_event_byte"}],"data":{"u64":0}}}}}]}}}}"#;
445
446    const CLASSIC_TX_RESPONSE: &str = r#"{"status":"SUCCESS","envelope":{"tx":{"tx":{"source_account":"GCBVAIKUZELFVCV6S7KBS47SF2DQQXTN63TJM7D3CNZ7PD6RCDTBYULI","fee":100,"seq_num":197568495620,"cond":"none","memo":"none","operations":[{"source_account":null,"body":{"manage_data":{"data_name":"test","data_value":"abcdef"}}}],"ext":"v0"},"signatures":[{"hint":"d110e61c","signature":"89be4c9f86a3aa19de242f54b69e95894c451f0fa6e8c6f7ad7bb353e08c6aefafb45e73746340e54f87aa9112aee2e7424b81289e0a7756c3e024406d0cdf0a"}]}},"result":{"fee_charged":100,"result":{"tx_success":[{"op_inner":{"manage_data":"success"}}]},"ext":"v0"},"result_meta":{"v3":{"ext":"v0","tx_changes_before":[{"state":{"last_modified_ledger_seq":15678,"data":{"account":{"account_id":"GCBVAIKUZELFVCV6S7KBS47SF2DQQXTN63TJM7D3CNZ7PD6RCDTBYULI","balance":99988100312,"seq_num":197568495619,"num_sub_entries":0,"inflation_dest":null,"flags":0,"home_domain":"","thresholds":"01000000","signers":[],"ext":{"v1":{"liabilities":{"buying":0,"selling":0},"ext":{"v2":{"num_sponsored":0,"num_sponsoring":0,"signer_sponsoring_i_ds":[],"ext":{"v3":{"ext":"v0","seq_ledger":54,"seq_time":1750166271}}}}}}}},"ext":"v0"}},{"updated":{"last_modified_ledger_seq":15678,"data":{"account":{"account_id":"GCBVAIKUZELFVCV6S7KBS47SF2DQQXTN63TJM7D3CNZ7PD6RCDTBYULI","balance":99988100312,"seq_num":197568495620,"num_sub_entries":0,"inflation_dest":null,"flags":0,"home_domain":"","thresholds":"01000000","signers":[],"ext":{"v1":{"liabilities":{"buying":0,"selling":0},"ext":{"v2":{"num_sponsored":0,"num_sponsoring":0,"signer_sponsoring_i_ds":[],"ext":{"v3":{"ext":"v0","seq_ledger":15678,"seq_time":1750185606}}}}}}}},"ext":"v0"}}],"operations":[{"changes":[{"created":{"last_modified_ledger_seq":15678,"data":{"data":{"account_id":"GCBVAIKUZELFVCV6S7KBS47SF2DQQXTN63TJM7D3CNZ7PD6RCDTBYULI","data_name":"test","data_value":"abcdef","ext":"v0"}},"ext":"v0"}},{"state":{"last_modified_ledger_seq":15678,"data":{"account":{"account_id":"GCBVAIKUZELFVCV6S7KBS47SF2DQQXTN63TJM7D3CNZ7PD6RCDTBYULI","balance":99988100312,"seq_num":197568495620,"num_sub_entries":0,"inflation_dest":null,"flags":0,"home_domain":"","thresholds":"01000000","signers":[],"ext":{"v1":{"liabilities":{"buying":0,"selling":0},"ext":{"v2":{"num_sponsored":0,"num_sponsoring":0,"signer_sponsoring_i_ds":[],"ext":{"v3":{"ext":"v0","seq_ledger":15678,"seq_time":1750185606}}}}}}}},"ext":"v0"}},{"updated":{"last_modified_ledger_seq":15678,"data":{"account":{"account_id":"GCBVAIKUZELFVCV6S7KBS47SF2DQQXTN63TJM7D3CNZ7PD6RCDTBYULI","balance":99988100312,"seq_num":197568495620,"num_sub_entries":1,"inflation_dest":null,"flags":0,"home_domain":"","thresholds":"01000000","signers":[],"ext":{"v1":{"liabilities":{"buying":0,"selling":0},"ext":{"v2":{"num_sponsored":0,"num_sponsoring":0,"signer_sponsoring_i_ds":[],"ext":{"v3":{"ext":"v0","seq_ledger":15678,"seq_time":1750185606}}}}}}}},"ext":"v0"}}]}],"tx_changes_after":[],"soroban_meta":null}}}"#;
447
448    const FEE_BUMP_TX_RESPONSE: &str = r#"{"status":"SUCCESS","envelope":{"tx_fee_bump":{"tx":{"fee_source":"GDBFMEGF2EVTNISNTYVOOYGXAEP5A353YJCPDRGUH3L6GMIDATR4BWY6","fee":400,"inner_tx":{"tx":{"tx":{"source_account":"GCBVAIKUZELFVCV6S7KBS47SF2DQQXTN63TJM7D3CNZ7PD6RCDTBYULI","fee":100,"seq_num":197568495621,"cond":{"time":{"min_time":0,"max_time":1750189754}},"memo":"none","operations":[{"source_account":null,"body":{"payment":{"destination":"GDN2KA5HJ55DDXBKBBAPGWPXXAWEMLSPZIH3B2G4N7ERRUL7SN5BIRAX","asset":"native","amount":100000000}}}],"ext":"v0"},"signatures":[{"hint":"d110e61c","signature":"cfc3a142055e944995dcb1246bdbe84cd3f834ce8ae7a26ceeaeaa4edaee168628ed50d285d111d632d983a71fc305056e87d50ca50c80c193ed580398f43a0c"}]}},"ext":"v0"},"signatures":[{"hint":"0304e3c0","signature":"cc4d0d4a92ac2dbaeb7724c01713b4428ce48f61d263a70493e04cb9fadccd25342775200b1e1c27719971ce0f98ce44d0e2655491d432938aa22c92bf64df07"}]}},"result":{"fee_charged":200,"result":{"tx_fee_bump_inner_success":{"transaction_hash":"95a59fd736d69126705692a877c78881a33c28fc50684e61189b6b8bfa02e646","result":{"fee_charged":100,"result":{"tx_success":[{"op_inner":{"payment":"success"}}]},"ext":"v0"}}},"ext":"v0"},"result_meta":{"v3":{"ext":"v0","tx_changes_before":[{"state":{"last_modified_ledger_seq":18731,"data":{"account":{"account_id":"GDBFMEGF2EVTNISNTYVOOYGXAEP5A353YJCPDRGUH3L6GMIDATR4BWY6","balance":99999999800,"seq_num":80427557584896,"num_sub_entries":0,"inflation_dest":null,"flags":0,"home_domain":"","thresholds":"01000000","signers":[],"ext":"v0"}},"ext":"v0"}},{"updated":{"last_modified_ledger_seq":18731,"data":{"account":{"account_id":"GDBFMEGF2EVTNISNTYVOOYGXAEP5A353YJCPDRGUH3L6GMIDATR4BWY6","balance":99999999800,"seq_num":80427557584896,"num_sub_entries":0,"inflation_dest":null,"flags":0,"home_domain":"","thresholds":"01000000","signers":[],"ext":"v0"}},"ext":"v0"}},{"state":{"last_modified_ledger_seq":15678,"data":{"account":{"account_id":"GCBVAIKUZELFVCV6S7KBS47SF2DQQXTN63TJM7D3CNZ7PD6RCDTBYULI","balance":99988100312,"seq_num":197568495620,"num_sub_entries":1,"inflation_dest":null,"flags":0,"home_domain":"","thresholds":"01000000","signers":[],"ext":{"v1":{"liabilities":{"buying":0,"selling":0},"ext":{"v2":{"num_sponsored":0,"num_sponsoring":0,"signer_sponsoring_i_ds":[],"ext":{"v3":{"ext":"v0","seq_ledger":15678,"seq_time":1750185606}}}}}}}},"ext":"v0"}},{"updated":{"last_modified_ledger_seq":18731,"data":{"account":{"account_id":"GCBVAIKUZELFVCV6S7KBS47SF2DQQXTN63TJM7D3CNZ7PD6RCDTBYULI","balance":99988100312,"seq_num":197568495621,"num_sub_entries":1,"inflation_dest":null,"flags":0,"home_domain":"","thresholds":"01000000","signers":[],"ext":{"v1":{"liabilities":{"buying":0,"selling":0},"ext":{"v2":{"num_sponsored":0,"num_sponsoring":0,"signer_sponsoring_i_ds":[],"ext":{"v3":{"ext":"v0","seq_ledger":18731,"seq_time":1750189724}}}}}}}},"ext":"v0"}}],"operations":[{"changes":[{"state":{"last_modified_ledger_seq":18731,"data":{"account":{"account_id":"GCBVAIKUZELFVCV6S7KBS47SF2DQQXTN63TJM7D3CNZ7PD6RCDTBYULI","balance":99988100312,"seq_num":197568495621,"num_sub_entries":1,"inflation_dest":null,"flags":0,"home_domain":"","thresholds":"01000000","signers":[],"ext":{"v1":{"liabilities":{"buying":0,"selling":0},"ext":{"v2":{"num_sponsored":0,"num_sponsoring":0,"signer_sponsoring_i_ds":[],"ext":{"v3":{"ext":"v0","seq_ledger":18731,"seq_time":1750189724}}}}}}}},"ext":"v0"}},{"updated":{"last_modified_ledger_seq":18731,"data":{"account":{"account_id":"GCBVAIKUZELFVCV6S7KBS47SF2DQQXTN63TJM7D3CNZ7PD6RCDTBYULI","balance":99888100312,"seq_num":197568495621,"num_sub_entries":1,"inflation_dest":null,"flags":0,"home_domain":"","thresholds":"01000000","signers":[],"ext":{"v1":{"liabilities":{"buying":0,"selling":0},"ext":{"v2":{"num_sponsored":0,"num_sponsoring":0,"signer_sponsoring_i_ds":[],"ext":{"v3":{"ext":"v0","seq_ledger":18731,"seq_time":1750189724}}}}}}}},"ext":"v0"}},{"state":{"last_modified_ledger_seq":18687,"data":{"account":{"account_id":"GDN2KA5HJ55DDXBKBBAPGWPXXAWEMLSPZIH3B2G4N7ERRUL7SN5BIRAX","balance":100000000000,"seq_num":80260053860352,"num_sub_entries":0,"inflation_dest":null,"flags":0,"home_domain":"","thresholds":"01000000","signers":[],"ext":"v0"}},"ext":"v0"}},{"updated":{"last_modified_ledger_seq":18731,"data":{"account":{"account_id":"GDN2KA5HJ55DDXBKBBAPGWPXXAWEMLSPZIH3B2G4N7ERRUL7SN5BIRAX","balance":100100000000,"seq_num":80260053860352,"num_sub_entries":0,"inflation_dest":null,"flags":0,"home_domain":"","thresholds":"01000000","signers":[],"ext":"v0"}},"ext":"v0"}}]}],"tx_changes_after":[],"soroban_meta":null}}}"#;
449}