Skip to main content
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 not abi.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 Order tuple

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.
#FieldSolidity typeStatic / dynamicABI role
1consumeraddressstatichead word, left-padded to 32 bytes
2adapteraddressstatichead word
3paramsbytesdynamichead word holds the offset; the tail holds a length word then the right-padded data
4payTokenaddressstatichead word
5amountuint256statichead word
6feeBpsuint16statichead word, right-aligned (a uint16 still occupies a full 32-byte word)
7payeeaddressstatichead word
8nonceuint256statichead word; the consumer’s uniqueness salt, not the EIP-3009 nonce

A worked example you can verify on-chain

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.
FieldValue
consumer0x26E47C6963Ee0BaB1A70B9079cF5135e00e18d46
adapter0x76C4F7d48Bba5f4fa36C47D9B34ba3b5Ea46FE43 (Robinhood AllocationAdapter)
params0x0000…0001 (abi.encode(uint256 vaultId), vaultId = 1)
payToken0x125959541Bb486058E7e3b55E49b3B04e49fBa5E (TestUSDC)
amount5000000 (5.000000 TUSDC)
feeBps100
payee0x000000000000000000000000000000000000bEEF
nonce (salt)0xcb7f0b80aee8ca4abb79e682c04f01cb975102134602afcd4f858b4a70edf390
Its abi.encode(order) is 11 words (352 bytes):
0x000  0000000000000000000000000000000000000000000000000000000000000020  offset to the Order tuple (= 0x20)
# the tuple head (8 words), starting at byte 0x020
0x020  00000000000000000000000026e47c6963ee0bab1a70b9079cf5135e00e18d46  consumer
0x040  00000000000000000000000076c4f7d48bba5f4fa36c47d9b34ba3b5ea46fe43  adapter
0x060  0000000000000000000000000000000000000000000000000000000000000100  params: offset 0x100 from the tuple start -> byte 0x120
0x080  000000000000000000000000125959541bb486058e7e3b55e49b3b04e49fba5e  payToken
0x0a0  00000000000000000000000000000000000000000000000000000000004c4b40  amount  = 5000000
0x0c0  0000000000000000000000000000000000000000000000000000000000000064  feeBps  = 100
0x0e0  000000000000000000000000000000000000000000000000000000000000beef  payee
0x100  cb7f0b80aee8ca4abb79e682c04f01cb975102134602afcd4f858b4a70edf390  nonce (the salt)
# the tail: the dynamic params bytes
0x120  0000000000000000000000000000000000000000000000000000000000000020  params length = 32 bytes
0x140  0000000000000000000000000000000000000000000000000000000000000001  params data   = uint256(1) = vaultId
orderHash = keccak256(abi.encode(order))
          = 0x4c7aac9359ac3cf807e312c24fed6501aa61dfcc9c03a73c66618f6d7db9b0f9

Reproduce the hash in three stacks

Each block below produces the identical 0x4c7aac…b0f9. This is the proof you can verify the encoding without trusting the SDK.
cast keccak "$(cast abi-encode \
  'f((address,address,bytes,address,uint256,uint16,address,uint256))' \
  '(0x26E47C6963Ee0BaB1A70B9079cF5135e00e18d46,0x76C4F7d48Bba5f4fa36C47D9B34ba3b5Ea46FE43,0x0000000000000000000000000000000000000000000000000000000000000001,0x125959541Bb486058E7e3b55E49b3B04e49fBa5E,5000000,100,0x000000000000000000000000000000000000bEEF,92043977228133022826366390371535562086103842654639431805352199450526986662800)')"
# 0x4c7aac9359ac3cf807e312c24fed6501aa61dfcc9c03a73c66618f6d7db9b0f9
The salt is shown as a 32-byte hex in the field table and as its decimal equivalent in the snippets; they are the same value.

The two nonces

Order.nonce (field 8) is a uint256 the consumer picks so two otherwise-identical orders hash differently. The EIP-3009 authorization nonce is the bytes32 orderHash. They are different values that share the word “nonce”; do not conflate them.

See also