Skip to content

feat(near): add CHAIN_NEAR identity, registry/CLI wiring, and e2e coverage - #430

Open
shahan-khatchadourian-anchorage wants to merge 4 commits into
shahankhatchadourian/near-a4-verifyfrom
shahankhatchadourian/near-b-wiring
Open

feat(near): add CHAIN_NEAR identity, registry/CLI wiring, and e2e coverage#430
shahan-khatchadourian-anchorage wants to merge 4 commits into
shahankhatchadourian/near-a4-verifyfrom
shahankhatchadourian/near-b-wiring

Conversation

@shahan-khatchadourian-anchorage

@shahan-khatchadourian-anchorage shahan-khatchadourian-anchorage commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

Fifth PR of the NEAR chain-support stack (stacked on #429). Gives NEAR a first-class wire identity and wires it into production -- the point where the crate built across A1-A4 becomes reachable from a real request.

Stack: A1 (#425) -> A2 (#426) -> A3 (#428) -> A4 (#429) -> B (this) -> C (docs site) -> D (wallet-signed token metadata).

What this PR does

  • proto/parser/parser.proto: CHAIN_NEAR = 6 added to the wire Chain enum, regenerated via make -C src generated.
  • visualsign::registry::Chain gains a Near variant (as_str/FromStr).
  • parser_app: chain_conversion.rs maps CHAIN_NEAR <-> Chain::Near; create_registry() registers NearVisualSignConverter; near joins default features -- this is the actual production-reachability change, the rest of the stack built the crate but never wired it to a request.
  • visualsign-near gains a cli-plugin feature (default-on, byte-for-byte matching visualsign-tron's existing convention) with a NearPlugin implementing parser_cli_core::ChainPlugin; near joins parser_cli's default features with the plugin wired into ChainArgs/build_plugins.
  • parser_cli_core::chains.rs gains a "near" entry in the CLI's chain-name listing. One existing test (parse_chain_maps_unknown_to_custom) used "near" as its example of an unmapped string -- now that NEAR has a real identity, updated to use a string that's actually still unmapped.
  • integration/tests/parser.rs: three e2e tests against the real parser_app binary over gRPC -- a native transfer, a pre-signature intents envelope (proving the crate's borsh-vs-JSON format discrimination works end-to-end under the one CHAIN_NEAR identity, not just in unit tests), and a rejection case for input that's neither.

Verification

  • Manually decoded both fixtures via parser_cli decode --chain near --output json before writing the e2e assertions, so the expected JSON in the tests is real output, not hand-typed.
  • cargo test -p visualsign-near (54/54), -p parser_app (7/7), -p parser_cli_core (31/31), -p integration (12/12, including all 3 new NEAR e2e tests against the real spawned binary) -- all pass.
  • cargo clippy/cargo fmt --check clean across every touched crate.
  • make -C src lint and the near-only narrow-build variant (parser_app --no-default-features --features near) both clean.
  • make -C src test (full workspace) completed clean.
  • Internal code review independently re-derived both e2e fixtures' exact expected JSON from the actual render code (not just trusting the copied output) and swept the whole workspace for any other exhaustive match on either Chain enum that would need a new arm -- found none missed.
  • Security review confirmed: no cross-chain routing confusion is possible (proto discriminants are unique, TryFrom rejects anything else), the near-joins-default change is strictly additive to existing chains' behavior, and the CLI-only wiring stays architecturally isolated from the gRPC production path despite sharing a binary (mirroring the existing Ethereum/Solana/Tron pattern).

@shahan-khatchadourian-anchorage shahan-khatchadourian-anchorage changed the title shahankhatchadourian/near b wiring feat(near): add CHAIN_NEAR identity, registry/CLI wiring, and e2e coverage Jul 31, 2026
@shahan-khatchadourian-anchorage
shahan-khatchadourian-anchorage force-pushed the shahankhatchadourian/near-b-wiring branch from 04803f4 to a9bf5ae Compare July 31, 2026 10:48
@shahan-khatchadourian-anchorage
shahan-khatchadourian-anchorage force-pushed the shahankhatchadourian/near-b-wiring branch from a9bf5ae to 953b741 Compare July 31, 2026 10:52
@shahan-khatchadourian-anchorage
shahan-khatchadourian-anchorage marked this pull request as ready for review July 31, 2026 10:58
Copilot AI review requested due to automatic review settings July 31, 2026 10:58

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR makes NEAR a first-class chain identity across the wire protocol, registry, CLI, and production parser wiring, and adds end-to-end gRPC integration tests to prove NEAR requests are now routed and rendered correctly.

Changes:

  • Adds CHAIN_NEAR = 6 to the protobuf Chain enum and updates generated Rust bindings accordingly.
  • Introduces Chain::Near in visualsign’s registry and wires NEAR into parser_app and parser_cli default feature sets + registration paths.
  • Adds NEAR gRPC e2e coverage (borsh transfer, intents JSON envelope, and a fail-closed rejection case).

Reviewed changes

Copilot reviewed 12 out of 15 changed files in this pull request and generated no comments.

Show a summary per file
File Description
src/visualsign/src/registry.rs Adds Chain::Near and updates as_str/FromStr mappings.
src/parser/cli/src/main.rs Adds NEAR CLI args + plugin wiring behind the near feature.
src/parser/cli/Cargo.toml Enables near by default and adds the optional visualsign-near dependency.
src/parser/cli-core/src/chains.rs Adds "near" to available chain names and updates the “unknown maps to custom” test.
src/parser/app/src/registry.rs Registers NearVisualSignConverter in the production registry behind feature = "near".
src/parser/app/src/chain_conversion.rs Maps ProtoChain::NearRegistryChain::Near and updates conversion tests.
src/parser/app/Cargo.toml Adds near to parser_app default features and defines the near feature.
src/integration/tests/parser.rs Adds three NEAR end-to-end gRPC tests for transfer, intents envelope, and invalid input rejection.
src/generated/src/generated/parser.rs Updates generated enum with Near = 6 and string conversions.
src/chain_parsers/visualsign-near/src/lib.rs Exposes a CLI plugin module and re-exports NearArgs/NearPlugin behind cli-plugin.
src/chain_parsers/visualsign-near/src/cli_plugin.rs Introduces NearPlugin implementing parser_cli_core::ChainPlugin.
src/chain_parsers/visualsign-near/Cargo.toml Adds optional deps + cli-plugin feature (default-on) to support CLI wiring.
src/Cargo.lock Updates lockfile to include new optional dependency edges (e.g., clap, generated, parser_cli_core) for NEAR’s CLI plugin feature.
proto/parser/parser.proto Adds CHAIN_NEAR = 6 and adjusts reserved range.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@prasanna-anchorage prasanna-anchorage left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Requesting changes on this one, but only because CI is red and the fix is one word per line -- everything else here is in good shape. The wire identity is done properly: CHAIN_NEAR = 6 is a fresh discriminant with no reuse, Chain::Near is added to both as_str and FromStr, chain_conversion maps both directions with a round-trip table entry, and re-pointing parse_chain_maps_unknown_to_custom at a genuinely unmapped string rather than deleting it was the right instinct.

The ubuntu job is failing and it's this PR's own tests. parser_near_native_transfer_e2e and parser_near_intent_envelope_e2e both fail; parser_near_rejects_input_that_is_neither_transaction_nor_intent passes. I reproduced it locally (make -C src build && cargo test -p integration near) -- deterministic, not a runner flake or a feature-unification artifact. Details inline; the fix is "VisualSign" -> "NearTx" in two places, which I verified takes it to 3/3.

One process note, raised because the method was sound and worth keeping. The description says the expected JSON came from parser_cli decode --chain near --output json rather than being hand-typed -- but "VisualSign" is not a payload type this codebase emits anywhere (I grepped; zero hits outside these two test literals), so it can't have come from real output. It also states -p integration passed 12/12. Something was edited after capture, or a different tree was measured. Copying real output is exactly the practice that should have caught this, so it's worth finding where the loop broke rather than just patching the literals.

Sequencing, for you to decide rather than a blocker: this is the PR where NEAR becomes production-reachable -- near joins parser_app's default features, so a real gRPC request now renders these payloads. Two findings from earlier in the stack are still open at that moment: Signature: valid overstating what was checked (#428) and mt_withdraw silently rendering 1 of 3 declared tokens (#428). Neither is caused by this PR, but B is the point where they stop being theoretical. Worth an explicit call on whether B waits for them.

Comment thread src/integration/tests/parser.rs Outdated
"Type": "amount_v2"
}
],
"PayloadType": "VisualSign",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the CI failure, here and again at line 891.

The server returns PayloadType: "NearTx"; both tests expect "VisualSign". Every other field matches exactly -- the only difference in either test is this one string:

left  (actual):   "PayloadType": String("NearTx")
right (expected): "PayloadType": String("VisualSign")

"NearTx" is the correct value. It's the crate's PAYLOAD_TYPE constant from A2, chosen to match the sibling convention, and the sibling e2e tests in this same file agree: "SolanaTx" (line 363), "EthereumTx" (474), "Sui" (742). "VisualSign" isn't a payload type any chain emits.

Changing both occurrences to "NearTx" takes the NEAR e2e tests to 3/3 locally -- I ran it:

test parser_near_rejects_input_that_is_neither_transaction_nor_intent ... ok
test parser_near_intent_envelope_e2e ... ok
test parser_near_native_transfer_e2e ... ok
test result: ok. 3 passed; 0 failed

Worth noting what these tests did catch once corrected: the intent-envelope case genuinely exercises borsh-vs-JSON discrimination end-to-end under one CHAIN_NEAR identity, which is the thing most worth having an e2e test for in this PR. The tests are good; only the expectation is wrong.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Already fixed on this branch — see the commit fix(near): update e2e fixtures to match NearTx payload_type. Both occurrences already read NearTx; no further change needed here.

);
}

fn create_metadata(&self, _network: Option<String>) -> Result<Option<ChainMetadata>, String> {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is where the network question from #426 lands, and the answer is that --network is accepted and discarded. create_metadata returns Ok(None) regardless of input, and both registration sites call NearVisualSignConverter::new(), which pins NearNetwork::Mainnet into the single registered instance for the process lifetime.

So parser_cli decode --chain near --network testnet ... renders Network: NEAR Mainnet, with nothing telling the user their flag was dropped. Silently ignoring an explicitly-set flag is worse than not accepting it -- the user made a choice, got no error, and received output contradicting it.

The docstring says this mirrors Solana/Tron, but the comparison doesn't quite hold. Tron is single-network, so create_text_field("Network", "Tron") can't be wrong. Solana returns network_id: None inside a SolanaMetadata variant that actually exists in the ChainMetadata oneof, so the plumbing is there and unused. NEAR is multi-network, renders a Network field as fact, and has no oneof variant at all -- it's the one case where the value can be confidently wrong.

Until the metadata path exists, I'd rather this rejected the flag than swallowed it:

fn create_metadata(&self, network: Option<String>) -> Result<Option<ChainMetadata>, String> {
    if let Some(n) = network {
        return Err(format!("--network is not yet supported for NEAR (got {n}); output is always mainnet"));
    }
    Ok(None)
}

That's honest about the gap and costs nothing. Adding the Near variant to the oneof is the real fix, and I'm fine with it landing in a follow-up as long as the flag stops lying in the meantime.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed — now that #426 added the Near oneof variant, create_metadata builds a real ChainMetadata::Near(NearMetadata { network_id }) instead of dropping the flag, validated via NearNetwork::from_network_id. An unrecognized value now errors before anything renders, rather than silently defaulting. Verified end-to-end via the CLI: no flag defaults to mainnet, --network NEAR_TESTNET renders NEAR Testnet, an invalid value errors cleanly (Invalid network 'bogus'. Supported: NEAR_MAINNET, NEAR_TESTNET). Added test coverage for all three cases.

"aptos" => Chain::Aptos,
"polkadot" => Chain::Polkadot,
"tron" => Chain::Tron,
"near" => Chain::Near,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Chain::Near is added to FromStr here and to as_str at line 38, but neither test_chain_from_str nor test_chain_as_str gained a Near line. Both tests enumerate every other variant -- Solana, Ethereum, Bitcoin, Sui, Aptos, Polkadot, Tron -- so NEAR is now the only chain in the enum with no round-trip coverage.

These are exactly the tests that would catch the class of typo this PR could plausibly introduce (a mismatched "near"/"Near" pairing, or a variant wired into one direction but not the other), and they're two lines:

assert_eq!(Chain::from_str("near"), Ok(Chain::Near));
assert_eq!(Chain::Near.as_str(), "Near");

Minor, and the chain_conversion round-trip table does cover the proto boundary -- but this is the string boundary, which is what the CLI's --chain near actually goes through.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed — added Chain::Near to both test_chain_from_str and test_chain_as_str, matching every other chain's round-trip coverage.

@shahan-khatchadourian-anchorage
shahan-khatchadourian-anchorage force-pushed the shahankhatchadourian/near-b-wiring branch 2 times, most recently from 90c28b7 to 1b54c41 Compare August 1, 2026 01:44
@shahan-khatchadourian-anchorage
shahan-khatchadourian-anchorage force-pushed the shahankhatchadourian/near-b-wiring branch from 1b54c41 to 1d1b8c0 Compare August 1, 2026 03:05
@shahan-khatchadourian-anchorage
shahan-khatchadourian-anchorage force-pushed the shahankhatchadourian/near-b-wiring branch 2 times, most recently from 026b995 to fb97bb0 Compare August 1, 2026 23:27
@shahan-khatchadourian-anchorage
shahan-khatchadourian-anchorage force-pushed the shahankhatchadourian/near-b-wiring branch from fb97bb0 to e59b080 Compare August 2, 2026 02:21

@pepe-anchor pepe-anchor left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Peer Review Summary — NEAR stack #430 (COMMENT)\n\n4 findings: 1 MEDIUM, 3 LOW. Detailed inline comments below.\n\nKey concern: Containerfile CHAIN_FEATURES omits 'near', so Docker enclave binary won't include NEAR support.


AI Review on behalf of @pepe-anchor. Please flag any inaccuracies.

chain_metadata: None,
};

let parse_response = test_args

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[LOW] NEAR e2e tests send chain_metadata: None, don't exercise NearMetadata protobuf path

All three NEAR integration tests (parser_near_native_transfer_e2e at line 765, parser_near_intent_envelope_e2e at line 832, parser_near_rejects_input at line 914) set chain_metadata: None. This means the gRPC parse handler -> NearMetadata deserialization -> extract_network_from_metadata -> converter path is never exercised end-to-end. The metadata extraction code IS covered at the unit level in src/chain_parsers/visualsign-near/src/networks.rs (extract_network_from_metadata tests), but a regression in the protobuf serialization/deserialization of chain_metadata::Metadata::Near(NearMetadata { ... }) through the real gRPC pipeline would not be caught. The network override behavior (metadata-supplied network taking precedence over the converter's constructed network) is tested only at the unit level in convert.rs.


Authored by Claude on behalf of @pepe-anchor. Please flag any inaccuracies, I'm not always right.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed, covered in 81e7eb7 with two tests rather than one, because a single positive test can't distinguish the two failure modes.

parser_near_metadata_selects_the_network_e2e sends a .testnet transaction with NEAR_TESTNET metadata through the real gRPC pipeline and asserts the payload renders NEAR Testnet. On its own that's weak: it would also pass if the metadata deserialized and were then ignored, since the assertion only shows a value arriving.

So parser_near_metadata_network_reaches_the_account_suffix_check_e2e sends a .near transaction declared as Testnet and asserts it's rejected naming alice.near. That can only happen if the metadata-supplied network reached the converter and overrode its default -- which is the half your finding is actually about.

The .testnet transaction is hand-built borsh; I self-checked the layout by rebuilding the existing mainnet vector byte-for-byte with the same code before trusting it (which caught a wrong nonce on the first attempt).

Comment thread src/parser/cli/Cargo.toml
@@ -17,6 +18,7 @@ clap = { version = "4.0", features = ["derive"] }
parser_cli_core = { path = "../cli-core" }
visualsign = { workspace = true }
visualsign-ethereum = { path = "../../chain_parsers/visualsign-ethereum", optional = true }

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[LOW] parser_cli diagnostics feature propagates to solana but not near

The diagnostics feature definition is diagnostics = ["visualsign/diagnostics", "visualsign-solana?/diagnostics"]. When near was added to parser_cli's features, the corresponding visualsign-near?/diagnostics propagation was not added. Functionally this is a no-op because visualsign/diagnostics is already in the feature set and visualsign-near/diagnostics just propagates it, but it is inconsistent with how solana is handled and may confuse future maintenance if visualsign-near ever adds diagnostics-specific behavior beyond core propagation. Also parser_app/Cargo.toml has diagnostics = ["visualsign/diagnostics", "visualsign-solana?/diagnostics"] with the same pattern -- near was omitted in both places.


Authored by Claude on behalf of @pepe-anchor. Please flag any inaccuracies, I'm not always right.


Authored by Claude on behalf of @pepe-anchor. Please flag any inaccuracies, I'm not always right.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correct on both manifests -- fixed in 81e7eb7, and confirming your no-op analysis.

visualsign-near and visualsign-solana are the only two chain crates that declare a diagnostics feature (ethereum, tron and unspecified don't), so near was the one omission. Verified the ? resolves in all three combinations that matter: parser_app --features diagnostics with near off (the ? correctly no-ops), parser_app --features "near diagnostics", and parser_cli's default set.

One note for when you read #439: that PR adds the same visualsign-near?/diagnostics entry, so with this landing first, #439's diff on those lines shrinks to just parser_cli_core/diagnostics.

…erage

Gives NEAR a first-class chain identity, replacing the placeholder
scaffolding from earlier PRs in this stack:

- proto/parser/parser.proto: CHAIN_NEAR = 6, regenerated via
  make -C src generated.
- visualsign::registry::Chain gains a Near variant (as_str/FromStr).
- parser_app: chain_conversion.rs maps CHAIN_NEAR <-> Chain::Near;
  create_registry() registers the NearVisualSignConverter; near joins
  parser_app's default features.
- visualsign-near gains a cli-plugin feature (default-on, mirroring
  every other chain crate) with a NearPlugin implementing
  parser_cli_core::ChainPlugin, and near joins parser_cli's default
  features with the plugin wired into ChainArgs/build_plugins.
- parser_cli_core::chains.rs's chain_string_mapping gains an entry for
  "near"; the test asserting an arbitrary string falls back to
  Chain::Custom now uses a string that is actually still unmapped.
- integration/tests/parser.rs: three e2e tests against the real
  parser_app binary over gRPC -- a native transfer, a pre-signature
  intents envelope (proving the format-discrimination in tx.rs works
  end-to-end under the one CHAIN_NEAR identity), and a rejection case
  for input that is neither.

Verified end-to-end via parser_cli decode --chain near for both input
formats before writing the e2e assertions. Full workspace make lint
and make test pass; the near-only narrow-build variant (parser_app
--no-default-features --features near) passes standalone.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…trip

- cli_plugin.rs: create_metadata no longer silently drops --network.
  Now that #426 added the Near oneof variant, this builds a real
  ChainMetadata::Near(NearMetadata { network_id }) instead of the
  interim "reject the flag" fallback proposed in review -- validated
  via NearNetwork::from_network_id, rejecting unrecognized values
  with a clear error rather than rendering a confident but wrong
  "NEAR Mainnet" on a signing screen. Verified end-to-end via the
  CLI: no flag defaults to mainnet, --network NEAR_TESTNET renders
  "NEAR Testnet", an invalid value errors before rendering anything.
- registry.rs: add Chain::Near to test_chain_from_str/test_chain_as_str,
  the only chain missing round-trip coverage after this PR wired it in.

The e2e PayloadType: "VisualSign" vs "NearTx" mismatch flagged in
review is already fixed on this branch (fix(near): update e2e
fixtures to match NearTx payload_type) -- no change needed here.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…ta e2e

parser_cli and parser_app both listed visualsign-solana in the diagnostics
feature but not visualsign-near, the only other chain crate that declares
one. A no-op today, since both also enable visualsign/diagnostics directly,
but it silently drops any diagnostics-gated behaviour visualsign-near adds.

The three NEAR integration tests all sent chain_metadata: None, so the
ChainMetadata -> NearMetadata -> extract_network_from_metadata path was
never exercised through the real gRPC pipeline. Two tests now cover it: a
.testnet transaction with NEAR_TESTNET metadata renders "NEAR Testnet", and
a .near transaction declared as Testnet is rejected naming the offending
account -- the second is what distinguishes a metadata value that reached
the converter from one that merely deserialized.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants