Injective Options Docs
Injective EVM Testnet Launch app
Injective Options · testnet

Onchain options on every Injective market

European, cash-settled, fully collateralized call spreads and put spreads on all 286 Injective mainnet perp markets: crypto, stocks, FX, commodities and indices. Takers trade over RFQ against signed quotes from liquidity providers, and a relayer settles each trade on Injective EVM. Anyone with collateral can write.

Chain Injective EVM testnet · 1439 Collateral USDC Style European, cash-settled Expiry every hour Premium tick $0.01 Margin none Taker fee none

Instruments

Every option is a vertical spread defined by five terms: underlying, kind, strike, bound and expiry. Calls carry a cap above the strike, which makes them bull call spreads. Puts carry a floor below the strike, which makes them bear put spreads; a floor of 0 is a plain put. The width between strike and bound fixes the most a contract can ever pay, and the writer locks exactly that in USDC.

call payoff = min(max(S − K, 0), C − K) × size
put payoff  = min(max(K − S, 0), K − F) × size
collateral = width × size   (width = C − K for calls, K − F for puts)

S is the settlement price, K the strike, C the cap, F the floor and size the contract size in index units. Collateral rounds up to the USDC unit and payoffs round down, so the vault can always pay every claim. A series is identified on chain by keccak256(abi.encode(terms)) and is created by its first fill; nobody has to list strikes.

Structured payoffs

A series can hold up to four spreads, each a leg with a signed whole-number ratio (negative means sold), on one underlying and expiry. It trades, settles and redeems as one contract, priced by one RFQ quote and filled all or nothing. A plain spread is one leg with ratio 1.

payoff = Σ ratioi × spreadi(S) × size
collateral = the highest payoff over every price
  • Butterfly CS(K−w, K) − CS(K, K+w): pays most (w) at K, nothing beyond the wings.
  • Condor CS(L−w, L) − CS(H, H+w): pays w anywhere from L to H, tapering to 0 at L−w and H+w.
  • Bull shark fin CS(K, B) − n × CS(B, B+tick), n = (B − K) / tick: gains like a call up to the barrier B, then knocks out one tick above it.
  • Bear shark fin the mirror image with put spreads: gains as the index falls to B, knocks out one tick below.

The vault requires legs sorted by (kind, strike, bound) so each structure has one series id, a payoff that is never negative (the holder never owes) and positive somewhere, strikes and bounds below 2128, and an expiry at most a year out. It checks the payoff at 0 and at every strike and bound, since it is piecewise linear and flat beyond the outermost one. The writer locks the peak, so a structure stays fully collateralized: the buyer can lose only the premium.

Structures have no indicative book. The app builds the four presets on each market's strike grid and asks for a firm quote; the reference maker prices a structure as the sum of its legs' Black-Scholes values.

Maximum loss

Both sides know their worst case before they trade, and it never changes afterwards. There are no margin calls, no liquidations and no counterparty exposure, because the payout is already in the vault.

PositionMax loss per contractMax profit per contractCapital tied up
Long (bought)premium paidwidth × size − premiumpremium only
Short (written)width × size − premiumpremium receivedwidth × size, until expiry or close

Examples from testnet fills

SeriesContract sizeCollateralPremiumWriter max loss
XAU 4100/4200 call · 29 Oct0.25 oz$25.00$0.42$24.58
WTI 100/95 put · 20 Nov10 bbl$50.00$16.85$33.15
SICI 115/125 call · 29 Oct10 units$100.00$1.68$98.32

The gold calls above were settled on testnet at 4150, paying (4150 − 4100) × 0.25 = $12.50 a contract.

Markets and collateral

Every Injective mainnet perp is listed. Strikes and bounds are multiples of the strike tick, and premiums multiples of $0.01. A spread's collateral per contract is its width times the contract size, which is also the most a holder can be paid and the most a writer can lose. The app quotes spreads two strike steps wide; any width on the strike tick can be requested over RFQ.

Loading markets…
MarketCategoryContract sizeStrike tickStrike stepSpread widthCollateral / contract≈ Notional

Notional is contract size times the current mainnet mark. SICI settles on the LO:TECH SICI oracle; every other market on its mainnet perp mark. Terms of a series are fixed when it's created.

Expiry and settlement

  • Expiry is any whole hour UTC. The app lists the next 12 hourly expiries, then the next daily, the next Friday and the next two monthlies (third Fridays) at 08:00 UTC; any other whole hour can be requested over RFQ.
  • Trading cutoff: fills revert in the last 60 seconds before expiry.
  • Price feeds: every underlying has its own feed on Injective EVM. A keeper pushes every 5 minutes for each underlying with a live series: SICI from the LO:TECH SICI/USDC oracle on Injective mainnet, the rest from the mark price of their Injective mainnet perp. A print older than 15 minutes is rejected as stale.
  • Settlement is permissionless for one hour after expiry. settle(seriesId) uses the feed's first print published at or after the expiry hour (priceAt(expiry)), so every caller settles on the same price whenever they call. The keeper pushes that print and settles every expired series within a minute of expiry. If no print lands within the hour, the admin can forceSettle.
  • After settlement holders call redeem for the payoff and writers call claim for collateral minus payoff. Before expiry, anyone holding both legs can close pairs to free collateral.
  • Testnet only: god mode lets the admin settle any series at any time, so demos don't wait for a real expiry.

How a trade happens

Trading runs over an off-chain RFQ stream modelled on Injective's perp RFQ streams. Quotes and orders are EIP-712 signatures; only the final fill touches the chain.

1Taker requestsTerms, buy or sell, and a quantity go to the stream, which checks them the way the vault will.
2Makers quoteEvery logged-in maker gets the request and answers with a signed quote or a pass within 750 ms.
3Taker signsQuotes come back ranked best first. The taker signs an intent with a premium limit and a deadline.
4Relayer settlesThe relayer calls fillIntent on the vault with the ranked quotes and pays the gas.
  • The stream verifies every maker signature and field before forwarding, and ranks by lowest premium for taker buys, highest for sells, earliest on ties.
  • The vault fills quotes in order until the quantity is done. A quote that fails is skipped with a QuoteRejected event and a decoded reason, so later quotes back up earlier ones.
  • Fill-or-kill: an intent fills in full or not at all. If the quotes can't cover the whole quantity, fillIntent reverts with NotFullyFilled and nothing moves. A maker quote can be used for part of its size only when the taker asked for less.
  • Intents live up to 60 seconds and name the relayer allowed to submit them.
Gas: the relayer pays for every fill. Traders and makers only send a one-time USDC approval to the vault, plus redeem, claim or close when they take money out.

What a fill does on chain

Taker buys q contracts

  • The taker pays q × premium to the maker first, so the maker can use it towards collateral.
  • Any long inventory the maker holds in that series moves to the taker.
  • For the rest, the maker writes: the vault pulls width × size per contract and records a short for the maker and a long for the taker.

Taker sells q contracts

  • The maker pays q × premium to the taker.
  • The taker's long in that series moves to the maker.
  • For the rest, the taker writes and locks collateral.

Netting: whoever receives a long while short in the same series has the matching pairs closed in the same transaction and gets that collateral back. A maker who sold 30 and buys 30 back ends flat with all collateral returned. mint(seriesId, qty) locks collateral for both legs at once, for makers who want inventory ready before quoting.

Anyone can write

There is no maker whitelist. The vault has no maker list, and the RFQ stream accepts any address that proves its key. A writer's obligation is fully funded at the moment of the fill, so the protocol has nothing to underwrite about who the writer is. Traders can write too: selling an option you don't hold writes it against your own collateral.

Fees

Takers pay no trading fee: the premium on the quote is the whole cost. Makers pay a fee on each fill, which they price into their quotes.

Maker fee per fill

fee = min(5 bps × qty × highest strike × contract size, 5% × qty × premium)

  • Notional is measured at the structure's highest strike, which the vault knows at fill time, so it doesn't depend on a fresh price and extra legs can only raise it.
  • The 5% cap keeps cheap, out-of-the-money spreads from paying more than a small share of their premium.
  • A selling maker pays it from the premium it just received; a buying maker pays premium plus fee. mint, close, redeem and claim are free.
  • Each fee emits MakerFeePaid(rfqId, seriesId, maker, fee). The admin sets the rates and recipient with setMakerFee, capped at 1% of notional and 50% of premium; the stream's hello carries the current makerFee.

Example: a SICI 110/120 call with 10 units per contract has $1,100 of notional. At a $3.40 premium the cap applies and the fee is $0.17 per contract; at $50 the 5 bps applies and it's $0.55.

Integrating as a market maker

Connect and log in

  • Open wss://options.inj.so/rfq/ws?role=maker.
  • The server sends a challenge with a 32-byte nonce. Within 10 seconds, reply with auth, signing StreamAuthChallenge(uint64 evmChainId, address maker, bytes32 nonce, uint64 expiresAt) under the RFQ-Options domain.
  • You can sign with a hot key registered by setDelegate(key, expiry); positions and nonces stay on the maker address.
  • Send ping every 10 seconds. A stream that stays silent for 30 seconds is closed.
  • Approve the vault for USDC once. Makers need no INJ to trade.

Quote rules

  • Premium per contract is a multiple of $0.01 (10,000 USDC units).
  • The deadline must be 2 to 60 seconds away and before expiry minus 60 seconds.
  • Each nonce is single use per maker; invalidateNonce cancels one.
  • Up to 4 quotes per request. minFillQuantity sets a floor on partial fills, and taker = 0x0 makes a blind quote.
  • Quoting less than the requested size is fine; the taker can take the partial.

EIP-712 domain and types

EIP712Domain(string name, string version, uint256 chainId, address verifyingContract)
  name "RFQ-Options", version "2", chainId 1439, verifyingContract = OptionsVault

OptionQuote(uint64 chainId, uint64 rfqId, bytes32 underlying, uint64 expiry, Leg[] legs,
            uint8 takerSide, uint256 quantity, uint256 premiumPerContract, address taker,
            address maker, uint64 nonce, uint64 deadline, uint256 minFillQuantity)

OptionIntent(uint64 chainId, uint64 rfqId, bytes32 underlying, uint64 expiry, Leg[] legs,
             uint8 side, uint256 quantity, uint256 limitPremiumPerContract, address taker,
             uint64 nonce, uint64 deadline, address allowedRelayer)

Leg(uint8 kind, uint256 strike, uint256 bound, int32 ratio)

kind: 0 call, 1 put · ratio: contracts of that spread per contract, negative = sold
takerSide / side: 0 taker buys, 1 taker sells · a plain spread is one leg with ratio 1
underlying: ASCII symbol right-padded to bytes32 · strike, bound: 1e18 fixed point
premiums and collateral: USDC units (6 decimals) · stream login keeps version "1"

Messages

// server → maker
{ "type": "request", "rfqId": 1793220407574372, "underlying": "XAU", "expiry": 1793260800,
  "legs": [ { "kind": 0, "strike": "4100", "bound": "4200", "ratio": 1 } ],
  "kind": "call", "strike": "4100", "bound": "4200", // also sent for plain spreads
  "side": "buy", "quantity": "20", "taker": "0x5115…61f9", "takerVerified": false, "requestedAt": 1793220407580 }

// maker → server: a signed quote, or { "type": "pass", "rfqId": … }
{ "type": "quote", "rfqId": 1793220407574372, "quote": { …OptionQuote, amounts as integer strings,
  strike and bound as decimals }, "signature": "0x…" }

// taker → server: the underlyings to stream chains for (replaces the set, up to 64)
{ "type": "subscribe", "underlyings": [ "XAU", "BTC" ] }

// server → maker, after login and whenever it changes: what takers are watching
{ "type": "watch", "underlyings": [ "BTC", "XAU" ] }

// maker → server, any time: indicative prices for the chain and the order book (not binding)
{ "type": "indicative", "underlying": "SICI", "series": [ { "kind": "call", "strike": "110",
  "bound": "120", "expiry": 1795161600, "bid": "41.93", "ask": "42.37", "size": "10",
  "levels": [ { "size": "10", "bid": "41.93", "ask": "42.37" },
              { "size": "40", "bid": "41.51", "ask": "42.79" },
              { "size": "150", "bid": "40.88", "ask": "43.42" } ] } ] }

// server → maker after settlement
{ "type": "filled", "rfqId": …, "txHash": "0x…", "fills": [ { "seriesId": "…", "takerSide": 0,
  "qty": "20", "premiumPerContract": "420000" } ] }

The app draws each series' order book from the best maker's indicative levels and asks for a firm quote only when someone trades. Takers get chains only for the underlyings they subscribe to, so makers need only publish the watched ones; firm requests can come for any listed underlying. Indicative prices expire 30 seconds after a maker's last update. Rate limits: 5,000 quotes per second per maker and 10 requests per second per taker.

Reference liquidity provider

One process quotes every listed underlying so the testnet always has two-sided prices. It's a starting point, not a pricing recommendation. The first nine markets have hand-tuned terms:

UnderlyingVolStrike stepSpread widthCollateral / contract
SICI40%510$100
US50018%1020$20
USTECH22%1020$20
XAU18%50100$25
XAG30%12$40
XPT25%2550$50
XPD35%2040$40
WTI35%25$50
BRENT32%25$50
  • Other markets: terms come from the mainnet price at listing: a strike step near 1.5% of spot (1, 2, 2.5 or 5 times a power of ten), spreads two steps wide, a tick of a tenth of the step, and a contract size that makes each spread pay at most about $50. Vol is 50% for BTC, 65% for ETH, 90% for other crypto, 120% for memecoins, 45% for stocks, 8% for FX and 30% for other macro markets.
  • Model: Black-Scholes with zero rates, valued as the difference of two vanillas at the strike and the bound. Strikes cover spot ± 6% from a week out, narrowing with the square root of time for nearer expiries, and always at least two steps either side.
  • Book: three tiers around the model value: 10 contracts at ±0.5%, 40 at ±1.5% and 150 at ±3%. Asks round up and bids down to $0.01; no ask exceeds the collateral, and bids under $0.01 are left out.
  • Firm quotes price the requested size as the size-weighted average of the tiers it sweeps, with anything past the book at the last tier.
  • Limits: it quotes only what its live USDC balance can collateralize, valid for 30 seconds, with no inventory or delta limits on testnet.

Addresses and endpoints

Web apphttps://options.inj.so
RFQ streamwss://options.inj.so/rfq/ws?role=maker | taker
Stream config and indicative priceshttps://options.inj.so/rfq/config · /rfq/indicative
OptionsVault0x26FcFfB7E4b6A1813769E26a2Ed7A9C02D13a52d
USDC (testnet, 6 decimals)0x0C382e685bbeeFE5d3d9C29e29E341fEE8E84C5d
Relayer0x7059B83e66057d67968A1dfef2BF21dE2c4Df611
RPChttps://k8s.testnet.json-rpc.injective.network
Explorertestnet.blockscout.injective.network
Test fundsGet test funds in the app's account menu: 500 USDC and 2 INJ per address per day.

Price feeds

FeederPriceFeed.latest() returns (price1e18, publishTime) and reverts when the print is older than 15 minutes. Every market has its own feed: GET /api/config lists all of them, and the first nine are below.

UnderlyingFeed
SICI0xC7b44Cff8c6c45fc5B0d0f8fb4d7693753E473D1
US5000x4cF8AF80F922c90BD2F78d9aeCA0A6BE570fe565
USTECH0xA327613E3281e22838Ad04366d20D8E309f7AA45
XAU0xa190E8aAcd6a94B16e5224d7b70753f615591dF4
XAG0xbffe921Ef7209DE3f940F4D5319F2C27f93F8aF5
XPT0x51c1c42026aE6CF6aC9248787CA9945d40D124E1
XPD0x1884951c4f88891Ab3dc50b1082699bC11F6AC4e
WTI0x12bDBAED6919Da4BeA6E97aEE8b9aF73a105B9bF
BRENT0xecD61E728f08a21Af045E02771eb371b5c8F0b2B

Before mainnet

  • Price feeds move from the keeper to Pyth or the Injective exchange precompile, so settlement doesn't depend on one feeder key.
  • Collateral moves from testnet USDC to mainnet USDC, and god mode is switched off.
  • Contract sizes and default spread widths are confirmed with market makers.
  • The RFQ stream may move into the Injective indexer next to the perp RFQ streams; its message names already match theirs.
  • Single legs (sell calls, sell puts) next to spreads, and more structure presets (seagulls, ranges) built from the same legs.
  • Fee rates are confirmed with market makers.