You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Adds the NEAR-side prover path for Sui, so InitTransfer / FinTransfer / DeployToken / LogMetadata events emitted by the Sui locker can be proven
back to NEAR. This was the last code blocker listed in sui/README.md;
everything remaining after this is DAO calls and deployment.
Mirrors the Aptos rollout.
What's included
near/omni-types/src/sui/events.rs — parsers for the four bridge
events, plus parse_sui_event / parse_sui_proof dispatch.
near/omni-types/src/sui/bcs.rs — minimal BCS reader (no new deps).
MpcFinality::Sui(SuiFinality::Checkpointed) and the ChainKind::Sui
arm in mpc-omni-prover (request_to_chain_kind, request_matches_finality, verify_callback, seeded in init).
near-mpc-sdk rev bumpdcc9e042 -> 70c40b0f (merge commit of feat(foreign-tx): sui support in contract interface near/mpc#3754, which adds the Sui contract-interface DTOs). The old pin has
no Sui types at all. Verified no breaking changes for EVM / Starknet /
Aptos.
Two things worth reviewing closely
1. BCS, not JSON. Aptos delivers AptosEvent.data as a JSON rendering;
Sui delivers SuiEvent.bcs as canonical BCS bytes. Parsing is therefore
positional and must match the Move struct declaration field-for-field — note
the Sui events carry an extra coin_type: String between token_address and
the numeric fields, which Aptos does not have.
Two guards make a layout mistake loud instead of silent:
Reader::finish() rejects trailing bytes, so a wrong field order fails
rather than producing a plausible-looking wrong value;
ULEB128 lengths are canonical-only (BCS uses ULEB128 where Borsh uses a
fixed u32 — easy to get wrong).
2. Emitter comes from type_tag, not event.package_id. A Move type
keeps its defining package id across package upgrades, while package_id
becomes the upgraded package's id. Only the former stays equal to the factory
address registered on NEAR, so this survives a bridge package upgrade. Same
rule as the Aptos type-tag-address convention.
One extra check Aptos can't do
Sui coins are types, so every event carries both the 32-byte wire id and the coin_type string it derives from. The parsers assert keccak256(coin_type) == token_address, binding the two representations.
Tests
113 passing (+25): 77 in omni-types, 36 in mpc-omni-prover.
BCS fixtures are generated by an independent Python reference encoder, so the
tests don't just assert the Rust against itself. Coverage: all four events,
tag dispatch, proof-kind/tag mismatch, the keccak binding (flip one byte of
the token id), trailing and truncated payloads, short-form addresses,
malformed tags, non-canonical ULEB128, non-UTF-8 strings, and the
prover-level dispatch/finality/roundtrip cases mirroring Aptos.
Verified: cargo test, make clippy-near (pedantic, -D warnings), make fmt-near, and cargo check --target wasm32-unknown-unknown.
Adds the NEAR-side MPC-prover path for Sui, so InitTransfer / FinTransfer / DeployToken / LogMetadata events emitted by the Sui locker can be proven back to NEAR. This is the Sui analogue of the Aptos rollout (#626 / #629) and is stacked on feat/sui (which already carries ChainKind::Sui / OmniAddress::Sui). The one structural difference from Aptos is that the MPC layer delivers Sui events as canonical BCS bytes (SuiEvent.bcs) rather than a JSON rendering, so parsing is positional and must mirror the Move #[event] structs field-for-field.
Changes:
New near/omni-types/src/sui/{bcs.rs,events.rs,mod.rs}: a minimal positional BCS reader and four event parsers + parse_sui_event / parse_sui_proof dispatch.
mpc-omni-prover: MpcFinality::Sui(SuiFinality::Checkpointed) seeded in init, plus ChainKind::Sui arms in request_to_chain_kind, request_matches_finality, and the result-parse dispatch (parse_sui_result).
MpcFinality::Sui appended to the enum in mpc_types.rs.
near-mpc-sdk git rev bump dcc9e042 → 70c40b0f (adds the Sui contract-interface DTOs).
Doc updates (near/CLAUDE.md, sui/README.md).
Reviewed changes
Per-file summary
File
Description
near/omni-types/src/sui/bcs.rs
New positional BCS reader (u8/u64/u128, address, vector, String, Option, ULEB128) with trailing-byte / canonical-ULEB / UTF-8 guards
near/omni-types/src/sui/events.rs
Four BCS event parsers, emitter-from-type_tag derivation, keccak256(coin_type)==token_address binding, ProofKind/type_tag dispatch
near/omni-types/src/sui/mod.rs
Module wiring
near/omni-types/src/lib.rs
pub mod sui;
near/omni-types/src/mpc_types.rs
MpcFinality::Sui(SuiFinality) variant appended
near/omni-prover/mpc-omni-prover/src/lib.rs
Sui finality seed + ChainKind::Sui dispatch to parse_sui_result
near/omni-prover/mpc-omni-prover/src/tests.rs
Sui prover-level tests (dispatch, finality, roundtrip, rejects)
near/Cargo.toml / Cargo.lock
near-mpc-sdk rev bump
near/CLAUDE.md, sui/README.md
Docs
Findings
Blocking: none identified.
I traced all four BCS test fixtures byte-for-byte and each decodes exactly as the parser reads it and as the doc-comment layout claims (e.g. InitTransfer = sender(32) | token(32) | uleb(0x4e=78) coin_type | nonce=7 LE | amount=1000 | fee=10 | native_fee=5 | uleb(0x13) "near:frolik.testnet" | uleb(0x00) message). The guards are sound: finish() rejects trailing bytes, uleb128 rejects non-canonical/over-u32 lengths, strings enforce UTF-8, Option rejects tags >1, origin_chain goes through TryFrom<u8>, and every parser both checks the type_tag suffix and binds keccak256(coin_type) == token_address. Prover wiring mirrors Aptos completely (init map, request_to_chain_kind, request_matches_finality, parse dispatch), and appending MpcFinality::Sui is borsh-safe (no existing discriminant renumbered).
Non-blocking (verify before merge — no automated gate covers this):
near/omni-types/src/sui/events.rs:653,694,726,753 — The positional BCS layouts cannot be verified against the source of truth in this review. The actual Sui Move #[event] structs live in sui/sources/omni_bridge.move on the base branch feat/sui, which isn't in this diff, and per §8 there is no Sui CI workflow and no cross-chain payload gate — the round-trip tests use fixtures from an independent Python encoder, so they prove the parser agrees with that encoder, not with the Move contract. A single mis-ordered/mis-typed field would compile, pass all tests here, and silently brick the Sui lane. Please diff each parser's field order/types against the Move #[event] declarations one field at a time before merging.
near/omni-types/src/sui/events.rs:704 — Two specific spots to confirm against the Move source, since they differ from the Aptos mirror: (1) coin_type: String is read immediately after token_address in all four events (the new field), and (2) FinTransfer.message is read as a plain vector<u8> here, whereas Aptos declares it Option<vector<u8>> (aptos/events.rs:191). If the Sui struct actually uses Option<vector<u8>>, a Some(..) message would misalign and fail finish() (a None/empty message would still pass), so this only surfaces on a message-bearing FinTransfer — worth an explicit confirmation.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds the NEAR-side prover path for Sui, so
InitTransfer/FinTransfer/DeployToken/LogMetadataevents emitted by the Sui locker can be provenback to NEAR. This was the last code blocker listed in
sui/README.md;everything remaining after this is DAO calls and deployment.
Mirrors the Aptos rollout.
What's included
near/omni-types/src/sui/events.rs— parsers for the four bridgeevents, plus
parse_sui_event/parse_sui_proofdispatch.near/omni-types/src/sui/bcs.rs— minimal BCS reader (no new deps).MpcFinality::Sui(SuiFinality::Checkpointed)and theChainKind::Suiarm in
mpc-omni-prover(request_to_chain_kind,request_matches_finality,verify_callback, seeded ininit).near-mpc-sdkrev bumpdcc9e042->70c40b0f(merge commit offeat(foreign-tx): sui support in contract interface near/mpc#3754, which adds the Sui contract-interface DTOs). The old pin has
no Sui types at all. Verified no breaking changes for EVM / Starknet /
Aptos.
Two things worth reviewing closely
1. BCS, not JSON. Aptos delivers
AptosEvent.dataas a JSON rendering;Sui delivers
SuiEvent.bcsas canonical BCS bytes. Parsing is thereforepositional and must match the Move struct declaration field-for-field — note
the Sui events carry an extra
coin_type: Stringbetweentoken_addressandthe numeric fields, which Aptos does not have.
Two guards make a layout mistake loud instead of silent:
Reader::finish()rejects trailing bytes, so a wrong field order failsrather than producing a plausible-looking wrong value;
fixed u32 — easy to get wrong).
2. Emitter comes from
type_tag, notevent.package_id. A Move typekeeps its defining package id across package upgrades, while
package_idbecomes the upgraded package's id. Only the former stays equal to the factory
address registered on NEAR, so this survives a bridge package upgrade. Same
rule as the Aptos type-tag-address convention.
One extra check Aptos can't do
Sui coins are types, so every event carries both the 32-byte wire id and the
coin_typestring it derives from. The parsers assertkeccak256(coin_type) == token_address, binding the two representations.Tests
113 passing (+25): 77 in
omni-types, 36 inmpc-omni-prover.BCS fixtures are generated by an independent Python reference encoder, so the
tests don't just assert the Rust against itself. Coverage: all four events,
tag dispatch, proof-kind/tag mismatch, the keccak binding (flip one byte of
the token id), trailing and truncated payloads, short-form addresses,
malformed tags, non-canonical ULEB128, non-UTF-8 strings, and the
prover-level dispatch/finality/roundtrip cases mirroring Aptos.
Verified:
cargo test,make clippy-near(pedantic,-D warnings),make fmt-near, andcargo check --target wasm32-unknown-unknown.