diff --git a/tui/explorer_model.go b/tui/explorer_model.go index 2c36789..efbdd4c 100644 --- a/tui/explorer_model.go +++ b/tui/explorer_model.go @@ -309,14 +309,7 @@ func (m *ExplorerModel) viewTxDetail() string { if len(tx.Vin) > 0 { b.WriteString(" Inputs:\n") for i, in := range tx.Vin { - switch v := in.(type) { - case types.TxInputGenesis: - b.WriteString(fmt.Sprintf(" [%d] coinbase height=%d\n", i, v.Height)) - case types.TxInputToKey: - b.WriteString(fmt.Sprintf(" [%d] to_key amount=%d key_image=%x\n", i, v.Amount, v.KeyImage[:4])) - default: - b.WriteString(fmt.Sprintf(" [%d] %T\n", i, v)) - } + b.WriteString(fmt.Sprintf(" [%d] %s\n", i, describeTxInput(in))) } } @@ -357,6 +350,25 @@ func describeTxOutTarget(target types.TxOutTarget) string { } } +// describeTxInput renders a human-readable summary for transaction inputs in +// the explorer tx detail view. +func describeTxInput(input types.TxInput) string { + switch v := input.(type) { + case types.TxInputGenesis: + return fmt.Sprintf("coinbase height=%d", v.Height) + case types.TxInputToKey: + return fmt.Sprintf("to_key amount=%d key_image=%x", v.Amount, v.KeyImage[:4]) + case types.TxInputHTLC: + return fmt.Sprintf("htlc origin=%q amount=%d key_image=%x", v.HTLCOrigin, v.Amount, v.KeyImage[:4]) + case types.TxInputMultisig: + return fmt.Sprintf("multisig amount=%d sigs=%d out=%x", v.Amount, v.SigsCount, v.MultisigOutID[:4]) + case types.TxInputZC: + return fmt.Sprintf("zc inputs=%d key_image=%x", len(v.KeyOffsets), v.KeyImage[:4]) + default: + return fmt.Sprintf("%T", v) + } +} + // loadBlocks refreshes the block list from the chain store. // Blocks are listed from newest (top) to oldest. func (m *ExplorerModel) loadBlocks() { diff --git a/tui/explorer_model_test.go b/tui/explorer_model_test.go index d2118c9..4127fb8 100644 --- a/tui/explorer_model_test.go +++ b/tui/explorer_model_test.go @@ -10,6 +10,8 @@ import ( "testing" tea "github.com/charmbracelet/bubbletea" + + "dappco.re/go/core/blockchain/types" ) func TestExplorerModel_View_Good_BlockList(t *testing.T) { @@ -174,3 +176,52 @@ func TestExplorerModel_ViewBlockDetail_Good_CoinbaseOnly(t *testing.T) { t.Errorf("block detail should contain 'coinbase only' for blocks with no TxHashes, got:\n%s", out) } } + +func TestDescribeTxInput_Good(t *testing.T) { + tests := []struct { + name string + input types.TxInput + want string + }{ + { + name: "genesis", + input: types.TxInputGenesis{Height: 12}, + want: "coinbase height=12", + }, + { + name: "to_key", + input: types.TxInputToKey{ + Amount: 42, + KeyImage: types.KeyImage{0xaa, 0xbb, 0xcc, 0xdd}, + }, + want: "to_key amount=42 key_image=aabbccdd", + }, + { + name: "htlc", + input: types.TxInputHTLC{ + HTLCOrigin: "origin-hash", + Amount: 7, + KeyImage: types.KeyImage{0x10, 0x20, 0x30, 0x40}, + }, + want: `htlc origin="origin-hash" amount=7 key_image=10203040`, + }, + { + name: "multisig", + input: types.TxInputMultisig{ + Amount: 99, + SigsCount: 3, + MultisigOutID: types.Hash{0x01, 0x02, 0x03, 0x04}, + }, + want: "multisig amount=99 sigs=3 out=01020304", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := describeTxInput(tt.input) + if got != tt.want { + t.Fatalf("describeTxInput() = %q, want %q", got, tt.want) + } + }) + } +}