EIP-712 signing
Maker, taker (Dutch), and fill-authority offers must recover to the expected address (or pass EIP-1271). The API can verify options RFQ maker signatures against on-chain digests when verify_offer_signatures is true (default outside tests).
Always take digests from the mothership (RfqHashFacet / FillHashFacet) — do not hand-roll typed-data hashing.
Options RFQ — leg-by-leg FillMatched
Domain (Solady EIP-712 on the mothership):
- Name:
OpshunsRFQ - Version:
1 - verifyingContract: mothership address for that chain
- chainId: target chain
Typed struct: OpshunIntentRequestLegByLeg — intents + directionless premiums (+ optional clob_offers) + makerExpiry + takerAddressHash + makerNonce.
Practical approach
- Assemble the exact request object you will POST (normalized addresses, integers).
- Call
digestOpshunIntentRequestLegByLeg(request). - Sign the 32-byte digest with the maker key (recoverable secp256k1,
vas27/28). - POST
{ offer_type: "leg_by_leg", request, maker_signature }.
Do not ship placeholder signatures. On-chain executeIntent reverts with InvalidIntentSignature().
Field gotchas
| Field | Notes |
|---|---|
intents | Must match the RFQ byte-for-byte after API normalization |
taker_address_hash | Copy from the request event / quote |
maker_expiry | uint32 unix seconds; must be in the future at fill time |
maker_nonce | Unique per maker; consumed on-chain |
Premium Buy / Sell | Directionless; taker directions pick which side applies |
clob_offers | Optional resting-book slices; each must fill exactly on-chain |
Settlement: On-chain fill modes · CLOB.
FixedPackage (maker-signed package)
- Name:
OpshunsFixedPackage - Version:
1 - Typed:
FixedPackageRequest(dual A/B absolute package premium, legs,takerAddressHash,makerNonce/makerExpiry, optionalrequiredAuthority)
Digest via FillHashFacet helpers on the mothership. Fill with executeFixedPackageIntent (or *WithAuthority when gated). Buy/Sell polarity is not in the signed payload — revealed at fill via directions + salt matching takerAddressHash.
Dutch auction (taker-signed package)
- Name:
OpshunsDutchFill - Version:
1 - Typed:
DutchFillRequest(DutchPackagePremiumschedule withauctionStartonly, top-levelauctionEnd, legs,makerAddressHash,takerNonce,requiredAuthority,requiredDutchAuthority,referrer)
Taker signs; maker fills via executeDutchIntent / executeDutchIntentWithAuctioneer / executeDutchIntentWithAuthority as appropriate. Premium decays or ramps between startPremium and endPremium from auctionStart until auctionEnd (sole hard deadline — no separate takerExpiry).
Auctioneer competition path (API dutch_only): makerAddressHash = directionsHash(directions), requiredDutchAuthority = mothership.
Fill authority
- Name:
OpshunsFillAuthority - Version:
1 - Typed:
AuthorityApproval(fillDigest, exclusiveFill, authorityNonce, metadata)
Required when the Dutch / FixedPackage request sets requiredAuthority != address(0). Authority must be registered (setFillAuthority). Dutch + authority requires non-zero exclusiveFill (authority picks the maker); FixedPackage may use optional exclusive fill to pin the taker.
metadata is opaque bytes hashed into the approval (must match at fill). Use it for off-chain policy tags (e.g. KYC tier). Dutch fillDigest includes the taker-signed referrer; FixedPackage digests do not include referrer (taker passes it at fill).
For KYC-oriented product shapes (dedicated desk, exclusive maker pick, attestation tags), see On-chain fill modes — Future KYC-specific modes.
Details: On-chain fill modes (Authority section).
Borrow RFQ (coming soon)
Borrow is not live. When it ships, lenders will sign SignedLoan under the MarginRFQCore EIP-712 domain (see margin ABIs / MarginRFQTypes), including the RFQ's borrower_hash and the same positions array.
Borrowers would later sign a LoanRequest wrapping the selected loan for borrow. Do not treat this as a production signing path yet.
Local verification
Against Anvil after mix dev.local:
# Example: eth_call digest on mothership, then sign with cast / ethers
cast call $MOTHERSHIP \
"digestOpshunIntentRequestLegByLeg(((address,uint8,uint8,uint32,uint32,uint160,uint256)[],((uint16,(uint16,uint256)[]),(uint16,(uint16,uint256)[]))[],uint32,bytes32,uint256))" \
...
The API fake market maker uses the same digest → ExSecp256k1.sign path for local fills.