> ## Documentation Index
> Fetch the complete documentation index at: https://anchoragedigital-docs-swig-highlight.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Testing Visualizations

> Verify parser output with the CLI before submitting

For full parser CLI documentation, see [Parser CLI](../parser-cli). This guide covers the practical steps to test your visualization locally.

## Prerequisites

Before testing, ensure you have:

* The `parser_cli` binary built (see [installation instructions](../parser-cli#installation))
* A raw transaction hex from your DApp or protocol
* The expected output you want to verify

## Quick testing workflow

### 1. Parse your transaction

Run the parser CLI with your transaction:

```bash theme={null}
parser_cli --chain <chain> -t <your_transaction_hex> --output human
```

For example, with an Ethereum transaction:

```bash theme={null}
parser_cli --chain ethereum -t 0x02f9... --output human
```

### 2. Verify the output

Check that the visualization:

* Shows the correct action (swap, transfer, approval, etc.)
* Displays accurate amounts and addresses
* Uses appropriate labels that users will understand

### 3. Test the condensed view

Hardware wallets have limited screen space. Verify your visualization works in condensed mode:

```bash theme={null}
parser_cli --chain <chain> -t <your_transaction_hex> --output human --condensed-only
```

The condensed view should show only the most critical information.

### 4. Check JSON output

For programmatic validation, use JSON output:

```bash theme={null}
parser_cli --chain <chain> -t <your_transaction_hex> --output json
```

You can extract specific fields with `jq`:

```bash theme={null}
parser_cli --chain ethereum -t <tx_hex> --output json | jq '.Fields'
```

## Common issues

### Transaction fails to parse

**Cause**: Incorrect chain type or malformed hex encoding.

**Solution**: Verify the chain flag matches your transaction and that the hex is properly formatted (with or without `0x` prefix, depending on chain conventions).

### Missing protocol details

**Cause**: The parser does not recognize your contract or protocol.

**Solution**: You may need to add a protocol-specific preset. See [Creating Visualizations](../creating-visualizations) for patterns.

### Output too verbose for hardware wallets

**Cause**: The condensed view includes too many fields.

**Solution**: Review your visualization's `Condensed` section in the `PreviewLayout` and reduce it to only the essential fields.

### Amounts display incorrectly

**Cause**: Decimal handling or token metadata issues.

**Solution**: Verify that token decimals are correctly applied. Use the `amount_v2` field type with proper `Amount` and `Abbreviation` values.

## Adding test fixtures

When your visualization is working, add a test fixture to ensure it does not regress.

### 1. Save your transaction

Create a fixture file in the appropriate chain directory:

```
src/chain_parsers/visualsign-<chain>/tests/fixtures/
```

Name the file descriptively, for example `my_protocol_swap.input`.

### 2. Add expected output

Create a corresponding expected output file with the same name but `.expected` extension. This captures the correct JSON output for comparison.

### 3. Write a test

Add a test that compares the parser output against your expected fixture:

```rust theme={null}
#[test]
fn test_my_protocol_swap() {
    let input = include_str!("fixtures/my_protocol_swap.input");
    let expected = include_str!("fixtures/my_protocol_swap.expected");

    let result = parse_transaction(input);
    assert_eq!(result, expected);
}
```

### 4. Run tests

Verify your fixture passes:

```bash theme={null}
cargo test -p visualsign-<chain> test_my_protocol
```

## Property-based testing (Solana)

Solana IDL parsing includes [proptest](https://proptest-rs.github.io/proptest/)-based fuzz tests that verify crash safety and correctness across randomly generated IDLs and instruction data. These tests live in:

* `src/chain_parsers/visualsign-solana/tests/fuzz_idl_parsing.rs` — parser-level fuzz and roundtrip tests
* `src/chain_parsers/visualsign-solana/tests/pipeline_integration.rs` — full-pipeline integration tests
* `src/chain_parsers/visualsign-solana/tests/semantic_pipeline.rs` — deterministic tests with real embedded IDLs
* `src/chain_parsers/visualsign-solana/tests/common/mod.rs` — shared test helpers

### Running proptest tests

```bash theme={null}
# Run all tests (proptest + semantic + fuzz_idl_parsing)
cargo test -p visualsign-solana

# Default 256 cases per property
cargo test -p visualsign-solana --test fuzz_idl_parsing

# More iterations for deeper fuzzing
PROPTEST_CASES=5000 cargo test -p visualsign-solana --test fuzz_idl_parsing

# Semantic tests only (real embedded IDLs)
cargo test -p visualsign-solana --test semantic_pipeline
```

### Running cargo fuzz targets (libFuzzer)

The `fuzz/` directory contains [libFuzzer](https://llvm.org/docs/LibFuzzer.html) targets that feed arbitrary bytes into the full visualsign-solana stack. These require a nightly toolchain and `cargo-fuzz`:

```bash theme={null}
cargo install cargo-fuzz --locked

# Run a fuzz target for 30 seconds (same as CI)
cd src/chain_parsers/visualsign-solana/fuzz
cargo +nightly fuzz run fuzz_transaction_string -- -max_total_time=30
cargo +nightly fuzz run fuzz_versioned_transaction -- -max_total_time=30
```

Available targets:

| Target                       | Entry point                            | What it exercises                                                          |
| ---------------------------- | -------------------------------------- | -------------------------------------------------------------------------- |
| `fuzz_transaction_string`    | `transaction_string_to_visual_sign`    | base64/hex decoding, transaction deserialization, IDL dispatch             |
| `fuzz_versioned_transaction` | `versioned_transaction_to_visual_sign` | bincode deserialization, versioned transaction path, address table lookups |

When a crash is found, libFuzzer writes a reproducer to `artifacts/`. Reproduce it with:

```bash theme={null}
cargo +nightly fuzz run <target> artifacts/<target>/crash-<hash>
```

### Testing against real IDLs

The `scripts/fuzz_all_idls.sh` script runs fuzz tests against all embedded production IDLs in one pass:

```bash theme={null}
./scripts/fuzz_all_idls.sh
```

You can also target a specific IDL:

```bash theme={null}
IDL_FILE=/path/to/my_program.json cargo test -p visualsign-solana --test fuzz_idl_parsing real_idl
```

### Roundtrip tests

A roundtrip test constructs an IDL and matching borsh-encoded instruction bytes, feeds them through the parser, and verifies the output matches expectations. "Roundtrip" refers to the encode-then-decode cycle: you know exactly what went in, so you can assert exactly what comes out.

There are two kinds in use:

* **Concrete roundtrips** (e.g., `roundtrip_single_u64_arg`) — Hand-crafted IDL JSON and hand-crafted byte payloads. These assert that specific parsed values match exactly (e.g., `amount == 42`). They serve as specification-by-example: each test documents one type scenario (no args, mixed primitives, `Option<T>`, `Vec<T>`, defined structs, multi-instruction dispatch).

* **Property-based roundtrips** (e.g., `fuzz_valid_data_always_parses_ok`) — Randomly generated IDL shapes paired with machine-generated valid borsh bytes from `arb_valid_instruction_bytes`. These assert that parsing succeeds and the instruction name matches, without checking specific field values. They verify the parser's contract holds across all type combinations, not just the hand-picked examples.

Both kinds complement each other: concrete roundtrips pin down known-good behavior, while property-based roundtrips explore the space of inputs you did not think to write by hand.

### Adding a new test

1. Write a strategy that generates the IDL shape you want to test (or use an existing one from `solana_parser_fuzz_core::proptest`)
2. Add a `proptest!` test that exercises the parser with generated inputs
3. Add a concrete roundtrip test for the same scenario to serve as specification-by-example
4. Run the tests — if proptest finds a failure, it saves a regression seed to `.proptest-regressions`
5. Commit the `.proptest-regressions` file so the failing case is reproduced in CI

### CI workflows

Tests are triggered by adding labels to a PR:

| Label      | Workflow       | What runs                                  |
| ---------- | -------------- | ------------------------------------------ |
| `proptest` | `proptest.yml` | `cargo test -p visualsign-solana`          |
| `fuzz`     | `fuzz.yml`     | Both libFuzzer targets for 30 seconds each |
| `ci`       | `main.yml`     | Full build, lint, and test suite           |

If a fuzz target crashes, the `fuzz-failure` label is added to the PR. If a proptest fails, the `proptest-failure` label is added. These labels are removed automatically on a clean run.

## End-to-end format chain with test coverage

This traces every data format transformation from wallet input to visual output, annotated with which testing methodology covers each stage.

```
Wallet/Client
  │
  │ base64 or hex encoded string
  │
  ▼
from_string()                        FUZZ: fuzz_transaction_string (arbitrary UTF-8)
  │ detect encoding (base64 vs hex)
  │ decode to Vec<u8>
  │
  ▼
bincode::deserialize()               FUZZ: fuzz_versioned_transaction (arbitrary bytes)
  │ try VersionedTransaction first
  │ fallback to legacy SolanaTransaction
  │
  ▼
SolanaTransactionWrapper
  │ extract message.instructions
  │
  ╔══════════════════════════════════ PER INSTRUCTION ═══════════════════╗
  ║                                                                     ║
  ║  &[u8] raw instruction data                                         ║
  ║    │                                                                ║
  ║    │ first 8 bytes                                                  ║
  ║    ▼                                                                ║
  ║  find_instruction_by_discriminator()                                ║
  ║    │  PROP: fuzz_valid_discriminator_random_args                    ║
  ║    │  PROP: real_idl_wrong_discriminator_not_dispatched_as_original  ║
  ║    │  CONCRETE: roundtrip_multiple_instructions_distinct_dispatch   ║
  ║    │                                                                ║
  ║    │ remaining bytes after disc                                     ║
  ║    ▼                                                                ║
  ║  parse_data_into_args()                                             ║
  ║    │  PROP: fuzz_idl_parsing_never_panics (random IDL)              ║
  ║    │  PROP: fuzz_valid_data_always_parses_ok (correct Borsh)        ║
  ║    │  PROP: fuzz_defined_struct_types_never_panics                  ║
  ║    │  PROP: fuzz_defined_enum_types_never_panics                    ║
  ║    │  PROP: fuzz_defined_alias_types_never_panics                   ║
  ║    │  PROP: real_idl_never_panics (production IDLs)                 ║
  ║    │  PROP: real_idl_valid_data_always_parses_ok                    ║
  ║    │  PROP: real_idl_all_arg_names_in_parsed_output                 ║
  ║    │  PROP: real_idl_overlong_data_is_rejected                      ║
  ║    │  PROP: real_idl_truncated_data_is_rejected                     ║
  ║    │  CONCRETE: all roundtrip_* tests                               ║
  ║    │                                                                ║
  ║    ├─ primitives                                                    ║
  ║    │   read_u8/u16/u32/u64/u128, i8..i128, f32, f64                ║
  ║    │   bool (u8: 0/1), PublicKey (32 bytes -> bs58)                 ║
  ║    │   CONCRETE: roundtrip_single_u64_arg                           ║
  ║    │   CONCRETE: roundtrip_mixed_primitive_args                     ║
  ║    │                                                                ║
  ║    ├─ String / Bytes                                                ║
  ║    │   u32 LE length prefix + raw bytes                             ║
  ║    │   (SizeGuard: budget 29,568 bytes)                             ║
  ║    │   PROP: fuzz_size_guard_vec_length_prefix                      ║
  ║    │   CONCRETE: size_guard_huge_vec_*                              ║
  ║    │                                                                ║
  ║    ├─ Vec<T>                                                        ║
  ║    │   u32 LE length prefix + N elements (recursive)                ║
  ║    │   (SizeGuard checked)                                          ║
  ║    │   PROP: fuzz_size_guard_vec_length_prefix                      ║
  ║    │   CONCRETE: roundtrip_vec_u8, roundtrip_vec_u32                ║
  ║    │   CONCRETE: size_guard_vec_u64_over_budget                     ║
  ║    │                                                                ║
  ║    ├─ Array<T, N>                                                   ║
  ║    │   N elements (size from IDL, SizeGuard checked)                ║
  ║    │   CONCRETE: roundtrip_array_u32                                ║
  ║    │                                                                ║
  ║    ├─ Option<T>                                                     ║
  ║    │   u8 flag (0=None, 1=Some) + value if Some                     ║
  ║    │   CONCRETE: roundtrip_option_some/none                         ║
  ║    │   SEMANTIC: semantic_stabble_swap (Option<u64>)                 ║
  ║    │                                                                ║
  ║    └─ Defined(name)                                                 ║
  ║        TypeResolver lookup, recursive parse_type()                  ║
  ║        (max depth 10)                                               ║
  ║        PROP: fuzz_defined_struct_types_never_panics                 ║
  ║        PROP: fuzz_defined_enum_types_never_panics                   ║
  ║        PROP: fuzz_defined_alias_types_never_panics                  ║
  ║        CONCRETE: roundtrip_defined_struct_arg                       ║
  ║        CONCRETE: roundtrip_nested_defined_struct                    ║
  ║        CONCRETE: roundtrip_defined_enum_arg                         ║
  ║        CONCRETE: dangling_defined_reference_returns_err             ║
  ║                                                                     ║
  ║    ▼                                                                ║
  ║  serde_json::Map<String, Value>                                     ║
  ║    (arg name -> decoded JSON value)                                 ║
  ║                                                                     ║
  ╚═════════════════════════════════════════════════════════════════════╝
  │
  ▼
IDL JSON                             PROP: fuzz_decode_idl_data_arbitrary_input
  │ Anchor IDL format                STRUCTURAL: real_idl_all_instructions_have_discriminators
  │ decode_idl_data() -> Idl struct  STRUCTURAL: real_idl_discriminators_are_unique
  │                                  STRUCTURAL: real_idl_instruction_names_are_unique
  │                                  STRUCTURAL: real_idl_idl_hash_is_stable
  │
  ▼
IdlRegistry                          PIPELINE: pipeline_idl_path_correct_data
  │ program_id -> IdlRecord mapping  PIPELINE: pipeline_no_idl_registered
  │ built-in + wallet-provided IDLs  PIPELINE: pipeline_multi_instruction_mixed_programs
  │                                  PROP: fuzz_pipeline_idl_path_taken_on_valid_discriminator
  │
  ▼
Visualizer dispatch                  PIPELINE: pipeline_idl_discriminator_miss (fallback)
  │ UnknownProgramVisualizer         PROP: fuzz_pipeline_never_panics
  │ -> try_idl_parsing if IDL exists PROP: fuzz_pipeline_field_count_invariant
  │ -> raw hex fallback if no IDL
  │
  ▼
SignablePayload                      PIPELINE: pipeline_field_count_equals_instruction_count
  │ fields: Vec<SignablePayloadField>PIPELINE: pipeline_named_accounts
  │  └─ PreviewLayout per instr      SEMANTIC: semantic_drift_deposit (exact arg values)
  │      ├─ title: "deposit (IDL)"   SEMANTIC: semantic_lifinity_swap
  │      ├─ condensed_fields         SEMANTIC: semantic_raydium_swap
  │      │   └─ TextV2{label, text}  SEMANTIC: semantic_orca_swap (u128 decoding)
  │      └─ expanded_fields          SEMANTIC: semantic_meteora/kamino/stabble/openbook
  │          ├─ Address{label, addr}
  │          ├─ TextV2{label, text}
  │          └─ RawData{label, hex}
  │
  ▼
to_validated_json()                  (explicit validate_charset() + to_validated_json() assertions)
  │ ASCII-only JSON string
  │ no unicode escapes
  ▼
Wallet display
```

### Annotation legend

| Marker         | Testing type                 | What it proves                                            |
| -------------- | ---------------------------- | --------------------------------------------------------- |
| **FUZZ**       | cargo-fuzz / libfuzzer       | Never panics on arbitrary binary input                    |
| **PROP**       | proptest (structured random) | Never panics + correctness invariants on structured input |
| **CONCRETE**   | Hand-crafted exact bytes     | Exact decoded values match expected                       |
| **SEMANTIC**   | Real DeFi protocol data      | Real-world instruction args decode correctly              |
| **STRUCTURAL** | IDL invariant checks         | Production IDLs are well-formed                           |
| **PIPELINE**   | Full end-to-end pipeline     | IdlRegistry -> dispatch -> SignablePayload correct        |

### Format summary

| Stage                  | Format                                                                   | Covered by                  |
| ---------------------- | ------------------------------------------------------------------------ | --------------------------- |
| Wire input             | base64 or hex encoded string                                             | FUZZ                        |
| After decode           | `Vec<u8>` raw bytes                                                      | FUZZ                        |
| Transaction struct     | bincode-deserialized `SolanaTransaction` or `VersionedTransaction`       | FUZZ                        |
| Discriminator matching | First 8 bytes of instruction data (byte comparison)                      | PROP + CONCRETE             |
| Arg encoding           | Borsh (little-endian, length-prefixed strings/vecs)                      | PROP + CONCRETE + SizeGuard |
| IDL definition         | Anchor IDL JSON (instructions, discriminators, arg types, defined types) | PROP + STRUCTURAL           |
| Parsed args            | `serde_json::Value` (JSON in memory)                                     | CONCRETE + SEMANTIC         |
| Final output           | `SignablePayload` -> validated JSON (ASCII-only)                         | PIPELINE + SEMANTIC         |

## Validation checklist

Before submitting your visualization:

* [ ] Parses correctly with `--output human`
* [ ] Condensed view shows critical information only
* [ ] Amounts and addresses are accurate
* [ ] Labels are clear to non-technical users
* [ ] Test fixture added and passing

## Related documentation

* [Parser CLI Development](../parser-cli) - Full CLI reference and examples
* [Creating Visualizations](../creating-visualizations) - Design patterns for visualizations
* [Field Types Reference](../field-types) - Available field types and their usage
