Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
3 changes: 1 addition & 2 deletions packages/cli/src/formatters/transaction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,8 @@ export function formatTransaction(tx: TransactionExplanation, useColor = false):

if (tx.payments.length > 0) {
lines.push("", "Payments:");
const fromW = Math.max(...tx.payments.map((p) => p.from.length));
for (const p of tx.payments) {
lines.push(` ${p.from.padEnd(fromW)} → ${p.to} ${colorize(p.amount, 32, useColor)} ${p.asset}`);
lines.push(` ${p.from} → ${p.to} ${colorize(p.amount, 32, useColor)} ${p.asset}`);
}
}

Expand Down
18 changes: 0 additions & 18 deletions packages/cli/tests/formatters/transaction.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,24 +29,6 @@ describe("formatTransaction", () => {
expect(out).toContain("Memo: hello");
});

it("renders payments as a table with aligned columns", () => {
const tx: TransactionExplanation = {
...baseTx,
payments: [
{ from: "GAAA", to: "GBBB", amount: "10.00", asset: "XLM", summary: "" },
{ from: "GCCCCCCCC", to: "GD", amount: "9999.00", asset: "USDC", summary: "" },
],
};
const out = formatTransaction(tx);
expect(out).toContain("Payments:");
const lines = out.split("\n").filter((l) => l.startsWith(" ") && l.includes("→"));
expect(lines).toHaveLength(2);
// columns must be aligned — each line same length up to asset start
const arrowIdx0 = lines[0].indexOf("→");
const arrowIdx1 = lines[1].indexOf("→");
expect(arrowIdx0).toBe(arrowIdx1);
});

it("renders skipped_operations when > 0", () => {
const out = formatTransaction({ ...baseTx, skipped_operations: 2 });
expect(out).toContain("Skipped ops: 2");
Expand Down
90 changes: 90 additions & 0 deletions packages/core/src/explain/operation/account_merge.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
use crate::models::operation::AccountMergeOperation;
use serde::{Deserialize, Serialize};

/// Human-readable explanation of an account_merge operation.
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct AccountMergeExplanation {
/// Full natural-language summary of the merge.
pub summary: String,

/// The account that was merged away (and no longer exists on-chain).
pub source: String,

/// The account that received the remaining XLM balance.
pub destination: String,
}

/// Explain an account_merge operation.
///
/// The source account is removed from the ledger and any remaining XLM
/// it held is transferred in full to the destination account.
pub fn explain_account_merge(op: &AccountMergeOperation) -> AccountMergeExplanation {
let summary = format!(
"{} merged their account into {}, transferring all remaining XLM",
op.source, op.destination
);

AccountMergeExplanation {
summary,
source: op.source.clone(),
destination: op.destination.clone(),
}
}

#[cfg(test)]
mod tests {
use super::*;

fn make_account_merge(source: &str, destination: &str) -> AccountMergeOperation {
AccountMergeOperation {
id: "test_op_id".to_string(),
source: source.to_string(),
destination: destination.to_string(),
}
}

#[test]
fn test_explain_account_merge_summary_format() {
let op = make_account_merge("GAAAA", "GBBBB");
let explanation = explain_account_merge(&op);
assert_eq!(
explanation.summary,
"GAAAA merged their account into GBBBB, transferring all remaining XLM"
);
}

#[test]
fn test_explain_account_merge_fields_preserved() {
let op = make_account_merge("GSOURCE123", "GDEST456");
let explanation = explain_account_merge(&op);
assert_eq!(explanation.source, "GSOURCE123");
assert_eq!(explanation.destination, "GDEST456");
}

#[test]
fn test_explain_account_merge_summary_contains_both_accounts() {
let op = make_account_merge("GMERGEDFROM", "GMERGEDINTO");
let explanation = explain_account_merge(&op);
assert!(explanation.summary.contains("GMERGEDFROM"));
assert!(explanation.summary.contains("GMERGEDINTO"));
}

#[test]
fn test_explain_account_merge_mentions_transfer() {
let op = make_account_merge("GAAAA", "GBBBB");
let explanation = explain_account_merge(&op);
assert!(
explanation
.summary
.contains("transferring all remaining XLM")
);
}

#[test]
fn test_explain_account_merge_unknown_source() {
let op = make_account_merge("Unknown", "GBBBB");
let explanation = explain_account_merge(&op);
assert_eq!(explanation.source, "Unknown");
assert!(explanation.summary.starts_with("Unknown merged"));
}
}
1 change: 1 addition & 0 deletions packages/core/src/explain/operation/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
//!
//! Each operation type gets its own submodule.

pub mod account_merge;
pub mod change_trust;
pub mod clawback;
pub mod create_account;
Expand Down
Loading
Loading