PUT Options Marketplace: Trade Flying Tulip PUT Options (ftPUT)

PUT Options Marketplace: Trade Flying Tulip PUT Options (ftPUT)

We launched Flying Tulip’s Capital Allocation as a mechanism, not a fundraiser.

The output wasn’t “a token buy”. It was a Perpetual PUT Option, represented onchain by an ERC‑721 NFT (“ftPUT”) that encodes a standing redemption right (collateral denomination + remaining backing + remaining wrapped FT + strike reference), and the ability to Hold / Divest / Withdraw over time.

Today that same instrument has a venue:


Overview

The PUT Options Marketplace is a secure, decentralized venue for trading Flying Tulip PUT Options (ftPUT). It allows holders to sell PUT Options with transparent pricing and built‑in checks that protect both sides of the trade.
Docs: https://docs.flyingtulip.com/product-suite/ftput-marketplace/

Important distinction:


What it supports

  • Listing, buying, editing, and removing ftPUT listings
  • Direct purchases at a pre-defined price
  • Signed buy offers that only settle if a seller accepts them
  • Built-in validation to prevent trades on outdated positions

What’s being traded (and why this is different from “selling FT”)

The marketplace trades the position NFT (ftPUT), not a synthetic derivative and not spot FT.

When you buy an ftPUT you become the owner of the ERC‑721, which means you control the position’s actions (via the Capital Allocation contracts):

  • Divest: redeem backing capital at par (burns the corresponding FT inside the position)
  • Withdraw FT: unlock FT into your wallet (invalidates the PUT on that portion permanently)
  • Partial: do either action on a fraction of the position

So a marketplace purchase is a transfer of:

  • the remaining FT still wrapped inside the position (put.ft)
  • the remaining collateral backing still reserved for par redemption (put.amountRemaining)
  • the embedded strike reference (put.strike) that defines the PUT characteristics

How trades stay accurate

ftPUT positions can change over time as collateral or FT is withdrawn. Before any purchase is finalized, the marketplace verifies that the underlying position still matches the buyer’s expectation (collateral type + remaining balances). If the position has changed, the trade fails.

In the contract this is implemented as a PUT state hash check.

The marketplace binds a trade to the current economic substance of the position using:

keccak256(abi.encode(tokenId, put.token, put.amountRemaining, put.ft))

If the seller modifies the position (divests or withdraws) after you quoted it, the hash changes and the trade reverts.

This validation prevents “stale position” fills. It does not prevent “bad price” fills.
Always sanity-check bid/ask vs collateral floor (and do it net of fees).

Payments and fees

  • Listings can be priced in tokens approved by the protocol
  • Bids are token-only and use approved tokens
  • Maker and taker fees are applied separately to the seller and buyer
  • Fees are capped and shown before confirmation
  • Fee rates and fee recipients are managed by the protocol
    Docs: https://docs.flyingtulip.com/product-suite/ftput-marketplace/

At time of writing:

  • Maker fee = 0.1%
  • Taker fee = 0.3%
Always treat fee rates as configuration, not gospel. They are protocol-managed and should be verified in the UI before you sign.

Listings (onchain asks)

Listings are onchain metadata attached to a tokenId:

  • seller address
  • payment token (approved ERC‑20)
  • price
  • expiration timestamp

Constraints (mirrored in code):

  • You must own the ftPUT and approve the marketplace for transfer
  • Each ftPUT can have only one active sell listing
  • Price must be > 0, token must be approved, and every listing has an expiry

Seller flow (contract-level):

  1. Approve marketplace to transfer your tokenId
  2. addListing(tokenId, token, price, expires)
  3. Optionally update it with editListing(...) or remove it with removeListing(tokenId)

Buyer flow:

  1. Read listing
  2. Compute the current PUT hash
  3. buy(tokenId, expectedToken, expectedPrice, expectedPutHash, permit2Data)
Pricing sanity check (again): expectedPutHash protects you from buying a modified position, not from paying a bad price.
Before you click Buy, always compare:Payment side: what you pay is price + takerFee (and payment token matters)Collateral floor: put.token + put.amountRemainingRemaining wrapped FT: put.ft
Convert both sides into the same unit (USD or denomination token). If your intent is “buy → immediately divest”, factor in redemption-rate limits (capital outflows can be throttled in extreme windows). See Capital Allocation docs for the guardrails.
Docs: https://docs.flyingtulip.com/capital-allocation/

Bids (Offers): offchain signed buy offers

Bids are created with a wallet signature and only settle if a seller accepts them.

  • include an expiration time
  • can specify minimum remaining collateral and FT (and strike) to ensure the position still meets requirements
  • are token-only (no ETH) and use tokens approved by the protocol

Buyer flow:

  • Sign the offer (EIP‑712)
  • Provide a Permit2 signature authorizing payment transfer (single-use)
  • Distribute off-chain (orderbook, DM, etc.)

Seller flow:

  • Confirm your tokenId meets the bid requirements
  • acceptBuyOffer(offer, tokenId, signature, permit2Signature, permit2Nonce, expectedPutHash)
bid vs collateral: when you accept a buy offer you’re selling a collateral‑backed claim, not “just an NFT”.
Always compare:the net proceeds (maker fee comes out of what you receive)versus the position’s collateral floor (put.amountRemaining in put.token) and remaining wrapped FT (put.ft)
The contract enforces structure (requirements + hash). It does not assert that the bid is “fair”.

Safety controls

  • Early position validation runs before the trade proceeds (prevents last-minute changes)
  • Each trade is atomic: payment and a PUT Option transfer together
  • The marketplace can be paused in case of an emergency

Owner and upgradability

  • The protocol manages accepted payment tokens, fee settings, and emergency pause authority
  • The marketplace is upgradeable to support future improvements
  • The current owner is set to the Flying Tulip treasury
    Docs: https://docs.flyingtulip.com/risks/multisigs/

(If you’re integrating, always verify current addresses here: https://docs.flyingtulip.com/contract-addresses/)


Transparency

Listings, edits, sales, fee updates, and payment token changes are recorded onchain for auditability.


There are rational, observable conditions that make each route dominant.

If you’re a seller

Route A: List (ask liquidity)
Use a listing when:

  • you want deterministic execution without negotiating bids
  • you’re happy to wait for a taker at your price
  • your position is “standard enough” that it should attract passive flow

Operational note: keep the position unchanged while listed, otherwise buyers using expectedPutHash will revert.

Route B: Take a bid (accept a buy offer)
Use bids when:

  • you want immediate execution at a known price
  • you’re targeting buyers who care about strike/denomination/remaining balances
  • you prefer “maker comes to you” mechanics instead of racing a listing

Operational note (again): don’t accept bids blind, sanity-check bid (net of fees) vs collateral floor.

Route C: Don’t sell the PUT, manage it
If your intent is capital recovery or FT liquidity, you can directly use the position controls:

If you’re a buyer

Route A: Buy a listing
Buy a listing when:

  • you want instant settlement
  • you’ve inspected the position state and it matches your intent
  • you’re comfortable paying taker fee

Best practice: always submit the current expectedPutHash so you get exactly what you quoted.

Route B: Place buy offers
Place offers when:

  • you care about constraints more than a specific tokenId
  • you want to enforce minimum strike and minimum remaining balances
  • you want makers to come to you at your bid

How the actions interact with code (builder appendix)

Core listing lifecycle:

  • addListing(tokenId, token, price, expires)
  • editListing(tokenId, token, price, expires)
  • removeListing(tokenId)
  • getListing(tokenId)
  • isListingValid(owner, tokenId) (useful for UIs/indexers)

Direct buy (atomic):

  • buy(tokenId, expectedToken, expectedPrice, expectedPutHash, permit2Data)
    • validates listing + expiry + token allowlist
    • validates expectedToken / expectedPrice (anti-front-run)
    • validates expectedPutHash (anti-stale-position)
    • transfers NFT, then distributes funds (native, ERC‑20, optional Permit2)

Off-chain offers:

  • acceptBuyOffer(offer, tokenId, signature, permit2Signature, permit2Nonce, expectedPutHash)
    • verifies EIP‑712 signature and offer deadline
    • enforces min requirements (token, remaining balances, strike)
    • forbids ETH as payment (token-only bids)
    • pulls payment via Permit2 and distributes proceeds/fees

Offer cancellation:

  • cancelBuyOffer(offer) marks the offer hash as cancelled

Not investment advice. This is a technical description of protocol mechanics and contract behavior. DeFi involves smart contract, market, and operational risks.