Documentation Index
Fetch the complete documentation index at: https://anchoragedigital-shahankhatch-228-lint-diagnostics-refactor.mintlify.app/llms.txt
Use this file to discover all available pages before exploring further.
For full parser CLI documentation, see 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)
- 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:
parser_cli --chain <chain> -t <your_transaction_hex> --output human
For example, with an Ethereum transaction:
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:
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:
parser_cli --chain <chain> -t <your_transaction_hex> --output json
You can extract specific fields with jq:
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 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:
#[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:
cargo test -p visualsign-<chain> test_my_protocol
Property-based testing (Solana)
Solana IDL parsing includes 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
# 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 targets that feed arbitrary bytes into the full visualsign-solana stack. These require a nightly toolchain and cargo-fuzz:
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:
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:
./scripts/fuzz_all_idls.sh
You can also target a specific IDL:
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
- Write a strategy that generates the IDL shape you want to test (or use an existing one from
solana_parser_fuzz_core::proptest)
- Add a
proptest! test that exercises the parser with generated inputs
- Add a concrete roundtrip test for the same scenario to serve as specification-by-example
- Run the tests — if proptest finds a failure, it saves a regression seed to
.proptest-regressions
- 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.
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 |
| 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: