orderHash on one chain be
checked on the other. Both sides hash the exact same struct in the exact
same way.
One tuple, two chains
There is a single canonical 19-fieldOrder tuple. Each escrow builds it
from its own direction-specific OrderParams plus its local context
(chain id, contract address) before hashing. The digest is equal across
chains by construction.
v3 added the last four: the deadline,
the ad’s settlement signer
and the two fee rates. Because they’re in the hash,
both parties sign them and neither chain can apply different ones.
Typehash
adId is hashed with keccak256 as a string member, per EIP-712.
orderDecimals and adDecimals are described in
Decimal scaling.
Minimal domain, on purpose
chainId and no verifyingContract. Chain ids and contract addresses
live inside the struct (slots 5–6, 8–9), so the same digest is
accepted on either chain. They’re still signed: a replay to the wrong
chain or contract fails validation.
Values both chains can hold
Soroban types some fields narrower than EVM, so both chains refuse values that would hash differently:amount and both chain ids must fit in 128
bits, and deadline in 64 bits.
How the two sides build it
- OrderPortal (order chain) fills in
orderChainIdandorderPortalfrom its own context; the ad-chain fields come from the order. - AdManager (ad chain) does the mirror image: it fills in
adChainIdandadManager, and takes the order-chain fields from the order.
OrderHash library
(contracts/evm/src/libraries/OrderHash.sol); on Stellar it’s
proofbridge-core. Off-chain, @proofbridge/order-params reproduces the
digest, pinned by shared test vectors.
Why this matters for proofs
The events circuit’s public inputs include the order hash:[nullifier, orderHash, targetRoot, leafDomain]. If the two chains
computed different digests for “the same” order, no single proof or
co-signature could serve both sides.