Maker onboarding
This guide takes you from a fresh wallet to a live, hedged quote. Everything here can also be done from the makers dashboard or with the SDK.
1. Use two keys
| Key | Holds | Can do |
|---|---|---|
| Owner | your inventory | register, deposit, withdraw, add or revoke operators, pause and unpause |
| Operator | nothing (gas only) | update quotes, pause your maker |
The operator key lives on your quoting server and is treated as compromisable: it can never move funds. This mirrors Hyperliquid's agent wallets. Keep the owner key offline or in a hardware wallet.
2. Register and add an operator
import { makerClient } from '@quotient/sdk';
const owner = makerClient({ wallet: ownerWallet, chainId: 99801 });
await owner.registerMaker();
await owner.setOperator(operatorAddress, true); // the newest operator becomes your active quoter
Registration is permissionless. A maker has exactly one active quoter at a time. Adding a new operator makes it the quoter; revoking the active quoter falls back to the owner's own quotes. That makes key rotation instant: add the new key, then revoke the old one.
3. Deposit inventory
Inventory is per pool and per currency. Native HYPE is deposited with msg.value; ERC-20s need an
approval for the hook first.
await owner.deposit(poolId, USDC, 5_000n * 10n ** 6n);
await owner.deposit(poolId, NATIVE, 50n * 10n ** 18n); // sends 50 HYPE as value
Deposits are converted into ERC-6909 claims inside the Uniswap v4 PoolManager and credited to you.
Withdraw any time with withdraw(poolId, currency, amount, to), even while the venue or your maker is
paused. On mainnet, deposits are capped per maker and per pool during the launch period.
4. Post quotes
A quote is one 32-byte slot per pool: mid price, bid and ask spreads, a per-block size cap, a linear size-impact slope, a per-block spread decay, a time-to-live in L2 blocks, a sequence number and flags. See the quote encoding spec for every bit.
import { quoteFromHuman, encodeQuote } from '@quotient/sdk';
const slot = encodeQuote(quoteFromHuman({
price: 77.21, decimals0: 18, decimals1: 6,
bidBps: 2, askBps: 2,
maxFill: 250, // HYPE per L2 block
impactAtMaxBps: 6, // +6 bp at a 250 HYPE fill, linear in size
decayBpsPerBlock: 0.5, // +0.5 bp per block of quote age
ttlBlocks: 20, // fillable for 20 blocks after inclusion
seq: 1,
}));
await operator.updateQuotes([{ poolIdx: 0, slot }]);
The contract stamps each slot with the L2 block it was included in, read from
ArbSys.arbBlockNumber() (on Elysium, Solidity's block.number is the parent chain's block). Age,
expiry and strict mode are measured from that stamp, so you never guess your inclusion block.
Cost
Through the packed entry (updateQuotesPacked344079(), the SDK's default) one pool costs about
29,300 gas including the 21,000 intrinsic; each extra pool in the same batch adds under 6,000. At
testnet gas prices (0.1 gwei) that is about 0.000003 HYPE per update. The status page shows
the measured numbers.
Choosing protections
| Protection | What it does | Suggested start |
|---|---|---|
ttlBlocks | quote unfillable after N blocks | 10–30 |
decayPerBlock | spread widens with every block of age | 0.3–1 bp |
maxFill | token0 you'll fill per L2 block | 0.5–2x your typical hedge size |
impactAtMax | extra spread at a full-size fill | 2–5x your base spread |
| strict mode | only fillable in the update block or the next | on, if you refresh every block |
The reference bot's defaults come from experiment E5. A sniper watched Hyperliquid and hit stale quotes while the maker's updates were stalled for 20 seconds every minute; each configuration ran interleaved on testnet. This is a partial run, 10–15 minutes per configuration, because the testnet halted mid-experiment.
| Protections | Sniper profit (= maker loss) per hour |
|---|---|
| none | $13.53 |
| expiry (40 blocks) | $3.51 |
| expiry + 0.5 bp/block decay | $0.00 |
| + per-block cap | $0.11 |
| strict mode | $0.00, at twice the update traffic |
Expiry alone is not enough: a quote stays snipeable for its whole lifetime. Age decay is what made stale quotes unprofitable. The bot ships with a 40-block TTL, 0.5 bp/block decay and a per-block cap sized to its hedge capacity, with strict mode off.
5. Priority fees and ordering
Elysium orders transactions by priority fee inside each block, with no lane for quote updates. A swap that pays more than your update can execute against your previous quote in the same block.
Measured on testnet (experiment E3, 200 simultaneous update/swap pairs, 99% in the same block):
- 1 wei decides it. With the update tipping 1 wei more, it executed first in 97% of same-block pairs; with the swap tipping 1 wei more, the update went first in only 5%; at 10× the tip, 100%.
- Equal tips are a coin flip. At equal tips the order was close to arbitrary (69% / 50% for the update depending on which request left first), not first-come-first-served.
- Tips are charged in full. The effective gas price is the base fee plus the whole tip.
The reference bot therefore tips 1 wei on quote updates (BOT_PRIORITY_FEE_WEI): it wins ties
against takers who tip nothing, at no meaningful cost. Any taker can outbid it, so expiry, age decay
and the per-block cap are what actually bound adverse selection. Testnet ordering may differ from
mainnet and will change when Elysium's keeper lane and Timeboost ship.
There is no public mempool: you cannot see pending swaps. Watch new blocks (newHeads) and receipts.
6. Run the reference bot
The open-source bot in packages/bot quotes from Hyperliquid's order book, pushes one batched update per
block when the mid moves past a threshold, detects fills from receipts, and hedges the net delta on
Hyperliquid through a trade-only agent wallet. It has a kill switch: pnpm --filter @quotient/bot kill
pauses your maker on-chain and cancels open Hyperliquid orders.
7. Pause and withdraw
Your operator can pause your maker instantly (for example on a stale price feed). Only the owner can unpause. Withdrawals are never blocked: not by your pause, not by a pool pause, not by the guardian.