The byte-exact abi.encode(order) layout. Get it wrong and every settlement reverts.
orderHash = keccak256(abi.encode(order)), and that orderHash is the EIP-3009 authorization nonce the consumer signs (see the binding for why). To compute the same orderHash the contract computes, the off-chain encoding must equal the on-chain abi.encode(order) byte for byte. This page is that layout, with a real worked example you can recompute in three stacks.abi.encode is standard ABI head/tail encoding of the Order tuple. It is notabi.encodePacked. The SDK uses viem’s encodeAbiParameters, which produces the exact bytes the contract’s abi.encode produces; a parity test (order.parity.test.ts against OrderParity.t.sol) pins the equality.
The fields, in struct order. The Order is a dynamic tuple, because params is dynamic bytes; abi.encode of it therefore begins with a one-word offset to the tuple.
#
Field
Solidity type
Static / dynamic
ABI role
1
consumer
address
static
head word, left-padded to 32 bytes
2
adapter
address
static
head word
3
params
bytes
dynamic
head word holds the offset; the tail holds a length word then the right-padded data
4
payToken
address
static
head word
5
amount
uint256
static
head word
6
feeBps
uint16
static
head word, right-aligned (a uint16 still occupies a full 32-byte word)
7
payee
address
static
head word
8
nonce
uint256
static
head word; the consumer’s uniqueness salt, not the EIP-3009 nonce
This is the real order settled in transaction 0xb89a56…abd845 on Robinhood testnet (the Quickstart settlement). Fetch that transaction’s calldata, decode the order, and you reproduce these exact bytes and this exact hash.
Order.nonce (field 8) is a uint256 the consumer picks so two otherwise-identical orders hash differently. The EIP-3009 authorization nonce is the bytes32orderHash. They are different values that share the word “nonce”; do not conflate them.