Skip to content

fallen-icarus/cardano-options

Repository files navigation

Cardano-Options

A p2p-DeFi protocol for writing, buying, and trading American-style covered options contracts on the Cardano Settlement Layer.

As with all p2p-DeFi protocols, all users maintain full custody, delegation control, and voting control of their assets at all times. No batchers are required.

Knowledge of basic Haskell syntax and cardano-cli usage is recommended.

The Getting Started instructions can be found here, and the benchmarks can be found here.

Table of Contents

Abstract

Cardano-Options is a p2p-DeFi protocol for American-style covered options contracts that are naturally enforced by the Cardano Settlement Layer (CSL). Users maintain delegation control and voting control of assets at all times. Every aspect of the options contract is fully customizable (eg, which asset will be offered, which asset must be used to pay the premium, the strike price, etc). Beacon tokens enable easily filtering the entire options market for contracts using specific assets. Once purchased, the protocol will trustlessly enforce the specified terms. Buyers of options contracts can freely trade the active contracts on secondary markets. Finally, this protocol can be trustlessly composed with all other Cardano DApps.

Motivation

Options contracts play a vital role in the economy by providing a mechanism for risk management, improving market efficiency, and enhancing price discovery. This is especially important for an endogenous p2p economy that does not rely on off-chain price feeds. Currently, there are very few options markets on Cardano. Those that do exist (usually) have the following limitations:

  • Users are required to sacrifice custody, delegation control, and voting control of all assets used for the options contracts. Not only does this increase the risk of theft for users (pooled assets attracts more hackers than distributed assets), but this is also an existential risk for both Proof-of-Stake blockchains such as Cardano, and decentralized governance.
  • Very little support (if any) for direct composability with other DApps. Lack of composability across core DeFi protocols can create systemic risks to the DeFi economy. At best, these risks create an unhealthy economy while at worst, they can easily cascade into economic recessions. (For an elaboration of this argument, see the Cardano-Loans README.)
  • Restrictions on the feature set supported by the options trading market. For an economy to reach its full potential, users must be able to express all possible preferences exactly. Any deviation from their true preferences will create economic distortions. For example, would Alice rather have the premium paid in ada or a stablecoin? Requiring Alice to accept ada instead of the stablecoin for the premium payment forces her to accept more risk than she otherwise would. As a result, Alice may be less active in the DeFi economy just because she wants to minimize her risk. By providing more customizable features, users can manage risk using other methods than just abstaining from DeFi.
  • The protocols are centralized. The common Cardano design pattern today is to have a DApp that is ultimately controlled by a multisig; even the minting of the yield tokens is centralized. This centralization is an existential risk to both the Dapp itself and also Cardano DeFi as a whole.

For DeFi to reach its full potential, there is a need for an options trading protocol without any of the above limitations.

The Cardano-Options Protocol

By using the distributed-DApp design (ie, all users get their own personal DApp addresses), and allowing users to configure all possible parts of the options contract, Cardano-Options does not have any of the same limitations as contemporary DApps. And just like with the other p2p-DeFi protocols, it favors an endogenous price discovery mechanism.

Supported Features

  • Native Support for All Assets - all assets are directly supported as offered assets, asked assets, and premium assets. This includes assets that have yet to be invented. For example, Alice can offer an options contract that swaps ADA -> AGIX and have the premium paid in DJED.
  • Direct Payments to Writers - buyers make payments directly to addresses specified by the writers. This means writers do not need to wait until after the contract finishes to claim the proceeds from the premium payments. Additionally, when a contract is executed, the proceeds from the contract execution also go directly to the writer.
  • Fully Configurable Contracts - Alice can set all terms exactly how she likes! No algorithms/oracles can force her to sell contracts with terms she doesn't actually want.
  • Assortment of Possible Terms - when a writer creates a new contract for sale, they can offer a selection of terms for the buyer to choose from. For example, when Alice wants to create an options contract for swapping 100 ADA to AGIX with a premium paid in DJED, she can let the buyer pick either a) a premium of 0.80 DJED, an expiration in 1 month, and a strike price of 0.5 AGIX/ADA, or b) a premium of 1.10 DJED, an expiration in 3 months, and a strike price of 0.7 AGIX/ADA. This enables writers to use more advanced trading strategies and risk-management techniques.
  • Tradable Contracts - when a buyer purchases a new contract, they are given a Key NFT for that new contract. Whoever controls this Key NFT is able to execute the associated options contract. This Key NFT can be freely traded on any DApp, especially those meant to act as secondary markets for financial assets.
  • A Single DApp Address for Each Writer - every writer gets their own personal DApp address. All contracts created by this writer are held in their personal DApp address. This makes it very easy for front-ends to integrate Cardano-Options as well as for writers to manage the stake/voting power for assets currently locked in contracts.
  • Efficient Off-chain Sorting of Contracts - by using beacons tokens, users can easily query the protocol for any information they may want. These beacon queries can even be combined to create more complicated queries. For example, buyers can see all current contracts for sale that are offering DJED and asking for a premium paid in ADA.
  • Full Composability - this protocol is natively composable with all other DeFi DApps. It can even be composed with itself: an options writer can close an expired contract and immediately create a new contract for sale in a single transaction. Another possible composition is buying one options contract and creating one to sell in a single transaction. These compositions allow for very advanced trading strategies while keeping risk to an absolute minimum.
  • Democratic Upgradability - users decide if and when to upgrade to a new version of the DApp. No one can decide this for them, or force them to move to a new version (eg, by freezing all functionality for the current version). Thanks to the protocol's composability, it is even cross-version compatible: a user can buy contracts from v1 and execute a contract from v2 in the same transaction.

Specification

This section is the low-level specification for the protocol. If you are only interested in the high-level aspects, feel free to skip to the next section.

The Writer's Address

Writers each create a unique "options" address - this is where all contract purchases take place and where all locked assets are kept until the contract is either executed or expired. As is common in distributed DApps, all such options addresses use the same validator script for the payment credential, and a unique, user-defined staking key or script for the staking credential. Owner-related actions are delegated to the staking credential by the validator script, so the user maintains full control of all assets at the address.

Payments are not made to this address. Instead, the payment credential acts as an overseer to ensure that the payment goes to the address specified in the options contract.

Proxy Script

Since Cardano-Options has buyers make payments directly to addresses specified by the writers, care must be taken when the specified address uses a payment plutus script. This is because the protocol enforces a specific datum with contract payments in order to guarantee uniqueness of outputs - this is currently the cheapest option for preventing double satisfaction. If the payments with this enforced datum are sent to an address that requires a different datum, the payment could be locked forever.

To address this issue, Cardano-Options uses the same Proxy Script as Cardano-Loans. The proxy script can accept any datum, use any redeemer, and simply delegates spending authorization to the address' staking credential, which can be anything (eg, a native script, a plutus script, or even a pubkey).

Payment pubkey addresses are always allowed since they can accept any datum. The proxy script is only necessary if the writer wishes to use more than just a single pubkey to protect their proceeds. For example, if the writer wishes to use a multisig, they would use the proxy script as the payment credential and the native multisig script as the staking credential for their specified payment address. If the writer wanted more complicated logic to protect their assets, they would use the proxy script as the payment credential and a plutus script as the staking credential. The proxy script must always be paired with a staking credential. The protocol will disallow any proxy scripts used without a staking credential.

Telling Time

Ultimately, the protocol does not need to know the current time; it only needs to know whether a certain time has, or has not, passed.

This is where the validity intervals come in. A smart contract will only ever be run if the validity interval is true. Therefore, if the invalid-before is set to slot 10 and the smart contract is being executed, then the smart contract knows that slot 10 is guaranteed to have already passed. Likewise, if the invalid-hereafter is set to slot 20 and the smart contract is being executed, then the smart contract knows that slot 20 has definitely not passed yet.

Therefore, this protocol uses the following general rules:

  • If you need to prove to the smart contract that time y has passed, set the invalid-before bound to y.
  • If you need to prove to the smart contract that time x has not passed yet, set the invalid-hereafter bound to x.

Protocol Phases and Beacons

All actions are largely categorized into two broad phases:

  1. Proposal Phase - this is when a writer creates a new options contract for sale. It ends when the contract is purchased and the associated Key NFT is created.
  2. Active Phase - this phase starts when a buyer purchases a writer's proposal. During this phase, the protocol will enforce all terms set in the proposal phase. This phase ends when either the contract is executed using the Key NFT or the contract expires.

Each phase has a dedicated UTxO type: Proposal UTxO and Active UTxO.

Proposal UTxO Beacons

There is a dedicated beacon minting policy for proposal beacons. Proposal UTxOs have four dedicated beacons:

  • Offer Asset Beacon - a proposal phase beacon token representing which asset can be taken when the contract is executed. It has the token name: sha2_256("01" ++ asset_policy_id ++ asset_token_name).
  • Ask Asset Beacon - a proposal phase beacon token representing which asset must be paid to the writer when the contract is executed. It has the token name: sha2_256("02" ++ asset_policy_id ++ asset_token_name).
  • Premium Asset Beacon - a proposal phase beacon token representing which asset must be paid to the writer for the premium. It has the token name: sha2_256("03" ++ asset_policy_id ++ asset_token_name).
  • Trading Pair Beacon - a proposal phase beacon token representing which assets are being traded in the contract, and which direction the trade is going in. It has the token name: sha2_256( offer_id ++ offer_name ++ ask_id ++ ask_name ). However, in the case where one of the assets is ada (which is just the empty bytestring), the ada policy id is set to "00" instead. For example, an ADA -> DJED contract pair beacon would be: sha2_256(djed_id ++ djed_name ++ "00" ++ ""). Without this change, each direction would not get its own beacon (ie, DJED -> ADA would have the same trading pair beacon as ADA -> DJED).

Active UTxO Beacons

There is a dedicated beacon minting policy for active beacons. Active UTxOs have four dedicated beacons:

  • Offer Asset Beacon - an active phase beacon token representing which asset can be taken when the contract is executed. It has the exact same name as the associated Proposal UTxO's Offer Beacon.
  • Ask Asset Beacon - an active phase beacon token representing which asset must be paid to the writer when the contract is executed. It has the exact same name as the associated Proposal UTxO's Ask Beacon.
  • Trading Pair Beacon - an active phase beacon token representing which assets are being traded in the contract, and which direction the trade is going in. It has the exact same name as the associated Proposal UTxO's Trading Pair Beacon.
  • Contract ID Beacon - an active phase beacon token uniquely representing this options contract. Its token name is: sha2_256( proposal_utxo_tx_hash ++ proposal_utxo_output_index ). There are two of these for each contract.

The beacon names for each phase are deliberately the same to simplify the off-chain querying.

The 2 Contract ID Beacons act as Lock & Key NFTs. One copy is always stored with the contract while the other is the Key NFT that can be freely traded. When the contract is executed, both copies must be burned. Because of this, the Key NFT can act as a proxy for the contract owner's approval; the Key NFT can only be burned if the owner approved the transaction. Furthermore, it is trivial for prospective buyers of the Key NFT to look up the associated contract's terms since the Lock NFT will be have the same name as the Key NFT, and be stored with the contract at an options address.

Four Aiken Smart Contracts

Due to the amount of logic required for this protocol and the desire to minimize the impact from redundant executions, this protocol uses 4 separate smart contracts; not all of them are required with each transaction. Each smart contract is dedicated to a specific purpose. As a consequence of this, there are actually two separate minting policies for beacons tokens: one for proposal phase beacons and one for active phase beacons. Most user actions only required 2 of the 4 contracts in a single transaction. The most that is ever needed is 3 out of the 4 contracts. However, since these scripts can be used as reference scripts, there is still plenty of room for DApp composability.

  • Proposal Smart Contract - this smart contract is in charge of minting/burning beacons for the proposal phase of the protocol. In addition to being a minting policy, it can also be executed as a staking script to enable cheaply updating Proposal UTxOs in-place (ie, no beacons need to be minted/burned, but the new datums need to be checked).
  • Active Smart Contract - this smart contract is in charge of minting/burning beacons for the active phase of the protocol. It can only be executed as a minting policy.
  • Address Update Observer Smart Contract - this smart contract is in charge of observing all payment address updates. It can only be executed as a staking script.
  • Options Spending Smart Contract - this smart contract is the payment credential for all DApp addresses. It delegates checks to one of the other 3 smart contracts depending on the action being taken. It can only be executed as a spending script.

The options spending smart contract hash is hard-coded into all of the other smart contracts to enforce the use of the proper payment credential for all options addresses.

The address observer smart contract hash is hard-coded into the active smart contract so that the active smart contract can force the use of the proper observer logic. Then, the active smart contract hash is hard-coded into the proposal smart contract so that it can force the use of the proper active smart contract. The active smart contract hash embodies the observer hash hard-coded into it so there is no need to also hard-code the observer hash into the proposal smart contract. It looks like this:

flowchart LR
    1[Address Update Observer] --> 2{Active};
    2[Active] --> 3{Proposal};
Loading

The protocol's complementary proxy smart contract is hard-coded into both the address update observer smart contract and the proposal smart contract so that payment addresses can be checked for proper configurations.

The redeemers and datums are introduced here:

Options Spending Smart Contract Datums

A UTxO's datum can either be a ProposalDatum, or an ActiveDatum. POSIXTime is always in milliseconds.

-- | The terms that an options' writer can vary within the same Proposal UTxO.
data Terms = Terms
  { premium :: Integer
  , strikePrice :: Rational -- Ask/Offer
  , expiration :: POSIXTime
  }

-- | The datum for an options contract that is available for purchase.
data ProposalDatum = ProposalDatum
  -- | The policy id for the proposal beacon script.
  { proposalBeaconId :: CurrencySymbol
  -- | The policy id for the active beacon script.
  , activeBeaconId :: CurrencySymbol
  -- | The asset being offered.
  , offerAsset :: (CurrencySymbol,TokenName)
  -- | The amount of the offer asset being offered.
  , offerQuantity :: Integer
  -- | The asset being asked for.
  , askAsset :: (CurrencySymbol,TokenName)
  -- | The token name for the trading pair beacon.
  , tradingPairBeacon :: TokenName
  -- | The token name for the offer beacon.
  , offerBeacon :: TokenName
  -- | The token name for the ask beacon.
  , askBeacon :: TokenName
  -- | The asset the premium must be paid in.
  , premiumAsset :: (CurrencySymbol,TokenName)
  -- | The token name for the premium asset beacon.
  , premiumBeacon :: TokenName
  -- | The amount the writer paid for the minUTxOValue.
  , contractDeposit :: Integer
  -- | The address where the premium must go upon purchase of the contract.
  , paymentAddress :: Address
  -- | The possible terms the buyer can pick from.
  , possibleTerms :: [Terms]
  }

-- | The datum for an options contract that has been purchased, and can be executed any time up
-- until the expiration.
data ActiveDatum = ActiveDatum
  -- | The policy id for the proposal beacon script. This is needed to support using the same 
  -- active beacons redeemer for purchases, executions, and closing expired.
  { proposalBeaconId :: CurrencySymbol
  -- | The policy id for the active beacon script.
  , activeBeaconId :: CurrencySymbol
  -- | The hash of the address update observer script.
  , addressObserverHash :: ScriptHash
  -- | The asset being offered.
  , offerAsset :: (CurrencySymbol,TokenName)
  -- | The amount of the offer asset being offered.
  , offerQuantity :: Integer
  -- | The asset being asked for.
  , askAsset :: (CurrencySymbol,TokenName)
  -- | The token name for the trading pair beacon.
  , tradingPairBeacon :: TokenName
  -- | The token name for the offer beacon.
  , offerBeacon :: TokenName
  -- | The token name for the ask beacon.
  , askBeacon :: TokenName
  -- | The strike price for this contract. It is always Ask/Offer.
  , strikePrice :: Rational
  -- | This contract's expiration.
  , expiration :: POSIXTime
  -- | The amount the writer paid for the minUTxOValue.
  , contractDeposit :: Integer
  -- | The address where the ask asset must go upon execution of the contract.
  , paymentAddress :: Address
  -- | The unique identitier for this contract.
  , contractId :: TokenName
  }

Options Spending Smart Contract Redeemers

data OptionsRedeemer
  -- | Close or update a Proposal UTxO.
  = CloseOrUpdateProposal
  -- | Purchase an options contract by converting a Proposal UTxO into an Active UTxO. The
  -- `desiredTermsIndex` identifies which `Terms` the buyer is purchasing; it is a 0-based index
  -- into the `possibleTerms` list in the associated Proposal UTxO.
  | PurchaseContract { desiredTermsIndex :: Integer }
  -- | Execute an active options contract.
  | ExecuteContract
  -- | Close an active options contract that has expired.
  | CloseExpiredContract
  -- | Update the address where the ask asset must go. Optionally deposit additional ada if needed.
  | UpdatePaymentAddress { newAddress :: Address, depositIncrease :: Integer }

Address Update Observer Smart Contract Redeemers

data AddressObserverRedeemer
  -- | Observe a writer's address update transaction.
  = ObserveAddressUpdate
  -- | Register the script.
  | RegisterAddressObserverScript

Proposal Smart Contract Redeemers

data ProposalBeaconsRedeemer
  -- | Create, close, or update some Proposal UTxOs (1 or more). 
  = CreateCloseOrUpdateProposals
  -- | Burn any beacons. This is only used during contract purchases.
  | BurnProposalBeacons
  -- | Register the script.
  | RegisterProposalScript

Active Smart Contract Redeemers

data ActiveBeaconsRedeemer
  -- | Create some Active UTxOs by buying Proposal UTxOs, burn beacons from executed
  -- contracts, and/or burn beacons from expired active contracts. The CurrencySymbol is the 
  -- policy id for the proposal beacons. The options spending script enforces the use of the 
  -- proper CurrencySymbol. In addition to minting/burning beacons, this redeemer enforces all logic
  -- for purchases, executions, and closings.
  = PurchaseExecuteOrCloseExpiredContracts { proposalPolicyId :: CurrencySymbol }
  -- | Burn any beacons. This is only used to burn unused Key NFTs.
  | BurnActiveBeacons

By using a single redeemer for purchases, executions, and closing expired contracts, it is possible to securely compose all three actions in a single transaction. If the actions used separate redeemers, each transaction using the active beacon smart contract would be forced to be dedicated to that individual action. It is for this reason that burning unused Key NFTs cannot occur in the same transaction where contracts are purchased, executed, or closed.

Payment Datum

This datum is attached to all direct outputs to payment addresses. It is used to prevent double-satisfaction.

-- | The `CurrencySymbol` is always the active beacon policy id, and the `TokenName` is always
-- the Contract ID this payment output corresponds to.
newtype PaymentDatum = PaymentDatum (CurrencySymbol,TokenName)

The CurrencySymbol is included to prevent double-satisfaction during DApp composition; just using the TokenName may not be enough to guarantee uniqueness.

Proposal UTxO Actions

Writers can create/update/close multiple Proposal UTxOs in a single transaction; each UTxO can have different terms.

Creating Proposal UTxOs

At a high-level, creating Proposal UTxOs involves creating the new UTxOs at the writer's options address with the desired ProposalDatums, storing them with the required offer asset, and tagging them with the required beacons. The proposal beacon smart contract will check all outputs containing proposal beacons to ensure invalid UTxOs are never broadcast to other users.

At a low-level, all of the following must be true:

  • The proposal beacon smart contract must be executed as a minting policy using CreateCloseOrUpdateProposals.
  • All outputs with proposal beacons must be to an options address with a valid staking credential.
  • All outputs with proposal beacons must have exactly four kinds of beacons, with exactly one unit of each:
    • an Ask Asset Beacon with the token name corresponding to the askAsset in the ProposalDatum
    • an Offer Asset Beacon with the token name corresponding to the offerAsset in the ProposalDatum
    • a Premium Asset Beacon with the token name corresponding to the premiumAsset in the ProposalDatum
    • a Trading Pair Beacon with the token name corresponding to the offerAsset and askAsset in the ProposalDatum
  • All outputs with proposal beacons must have a valid inline ProposalDatum:
    • proposalBeaconId == the proposal smart contract policy id
    • activeBeaconId == the proposal smart contract's hard-coded active smart contract hash
    • offerAsset == asset that corresponds to the offer asset beacon in this output
    • offerQuantity > 0
    • askAsset == asset that corresponds to the ask asset beacon in this output
    • tradingPairBeacon == token name that corresponds to the trading pair beacon in this output
    • offerBeacon == token name that corresponds to the offer beacon in this output
    • askBeacon == token name that corresponds to the ask beacon in this output
    • premiumAsset == asset that corresponds to the premium asset beacon in this output
    • premiumBeacon == token name that corresponds to the premium asset beacon in this output
    • contractDeposit > 0
    • paymentAddress must use either a payment pubkey, or the proxy script as the payment credential and a valid staking credential
    • possibleTerms must not be empty
    • For all Terms in possibleTerms:
      • strikePrice numerator must be > 0 and denominator must be > 0
      • expiration >= invalid-hereafter of this transaction
      • premium > 0
    • offerAsset != askAsset
  • All outputs with proposal beacons must have exactly the contractDeposit amount of ada and the offerQuantity amount of the offerAsset. No other assets are allowed in the output.

In order to help prevent creating a bunch of expired contracts to clutter the beacon queries, all contract expirations must be set to a time that has not passed yet. The invalid-hereafter bound is used to prove that time x has not passed yet, and all expirations must be >= x. This is how the smart contract can prove that all of the newly created options contracts are not already expired.

In TradFi, it is possible for writers to create many different contracts backed by the same assets. Buyers pick which contract they are interested in and the assets get paired with the chosen contract. To enable this feature, writers can place their desired terms in the possibleTerms list. Only the expiration, strikePrice, and premium are allowed to vary across the assortment. If a writer does not want to provide an assortment, they can offer only a single Terms in possibleTerms.

The strikePrice is always Ask/Offer.

Closing Proposal UTxOs

At a high-level, closing Proposal UTxOs involves spending the target UTxOs at the writer's options address, and burning the proposal beacons attached to them. The writer must approve this transaction. The proposal beacon smart contract will check all beacons are properly burned to ensure invalid UTxOs are never broadcast to other users.

At a low-level, all of the following must be true:

  • The options spending smart contract is executed for the Proposal UTxO input using CloseOrUpdateProposal.
  • The Proposal UTxO must have a ProposalDatum.
  • The options address' staking credential must signal approval.
  • If the Proposal UTxO being spent contains proposal beacons:
    • The proposal beacon smart contract must be executed as a minting policy using CreateCloseOrUpdateProposals.

The proposal smart contract will actually do the exact same check as when creating Proposal UTxOs. However, since closing Proposal UTxOs implies no new Proposal UTxO outputs, there are no outputs to check.

If there is ever an invalid Proposal UTxO (ie, a UTxO with a ProposalDatum but no proposal beacons), it can be spent with this method; the proposal smart contract would not need to be executed.

Updating Proposal UTxOs

Updating Proposal UTxOs in-place can be done regardless of whether beacons must be changed. The steps are identical to closing Proposal UTxOs, except you now create Proposal UTxO outputs as well. Since there are now outputs, the outputs will be checked by the proposal beacon script and must comply with the same requirements as when creating Proposal UTxOs.

If no beacons need to be minted/burned, the proposal beacon script must be executed as a staking script using CreateCloseOrUpdateProposals. If beacons do need to be minted/burned, then the proposal beacon script must be executed as a minting policy using the same redeemer.

Purchasing Proposal UTxOs

At a high-level, purchasing a proposal involves sending the premium payment to the writer's payment address and creating a new Active UTxO for that contract at the writer's options address. The new Active UTxO must be tagged with the proper beacons so that it can be easily found off-chain. The buyer can keep the newly minted Key NFT for this contract. The beacons attached to the original Proposal UTxO must be burned (or recycled).

At a low-level, all of the following must be true:

  • The proposal UTxO being purchased must be spent using PurchaseContract where the desiredTermsIndex is the 0-based index of the desired Terms in the UTxO's possibleTerms.
  • The UTxO being spent with PurchaseContract must have a ProposalDatum.
  • The UTxO being spent with PurchaseContract must have the required proposal beacons.
  • The active beacon smart contract must be executed as a minting policy using PurchaseExecuteOrCloseExpiredContracts with the proposalBeaconId from the input's ProposalDatum.
  • For each proposal purchased, there must be a corresponding contract output with the following characteristics:
    • It must be locked at the same options address where the Proposal UTxO originates.
    • It must have exactly four Active beacons, one unit of each:
      • The Trading Pair beacon for the associated proposal
      • The Offer beacon for the associated proposal
      • The Ask beacon for the associated proposal
      • The Lock NFT (Contract ID beacon) for the associated proposal
    • It must have the proper inline ActiveDatum where:
      • proposalBeaconId == proposalBeaconId in the active beacon redeemer
      • activeBeaconId == active beacon smart contract policy id
      • addressObserverHash == the active beacon smart contract's hard-coded address observer hash
      • offerAsset == offerAsset from proposal
      • offerQuantity == offerQuantity from proposal
      • askAsset == askAsset from proposal
      • tradingPairBeacon == tradingPairBeacon from proposal
      • offerBeacon == offerBeacon from proposal
      • askBeacon == askBeacon from proposal
      • contractDeposit == contractDeposit from proposal
      • paymentAddress == paymentAddress from proposal
      • strikePrice == strikePrice from the Terms at the index (specified by the PurchaseContract spending redeemer) in the proposal's possibleTerms list
      • expiration == expiration from the Terms at the index (specified by the PurchaseContract spending redeemer) in the proposal's possibleTerms list
      • contractId == token name for the corresponding Contract ID beacons for this contract output
    • It must have the contractDeposit amount of ada and the offerQuantity amount of the offerAsset
  • For each proposal purchased, there must be a corresponding premium payment output with the following characteristics:
    • It must be locked at the payment address specified by the corresponding proposal input
    • It must contain the premium from the Terms at the index (specified by the PurchaseContract spending redeemer) in the proposal's possibleTerms list
    • It must contain an inline PaymentDatum with the active beacon policy id as the CurrencySymbol and the new contract's Contract ID beacon token name as the TokenName
  • Either all proposal beacons attached to the proposal inputs must be burned by executing the proposal beacon smart contract as a minting policy with BurnProposalBeacons or the proposal beacon smart contract must be executed with CreateCloseOrUpdateProposals (can be staking or minting execution)
  • The active beacon smart contract cannot mint/burn any extra active beacons.

Buyers can purchase contracts from multiple different sellers in the same transaction.

There is no need to check whether the purchased contracts are expired since buyers are incentivized NOT to buy expired contracts.

By allowing the proposal beacon script to be executed using CreateCloseOrUpdateProposals, it is possible to create Proposal UTxOs in the same transacion where one is purchased. At a high-level, this means options traders can create one contract for sale and buy another one in the same transaction.

Purchase outputs must be in the same order as the purchase inputs! The contract output and premium payment output do not need to be paired up. The requirements are:

  1. Contract1 output must appear before contract2 output.
  2. Premium1 output must appear before premium2 output.

Any ordering that satisfies the above contraints will succeed. You can even have unrelated outputs between the required outputs. This ordering restriction helps with performance.

Active UTxO Actions

Executing Active UTxOs

At a high-level, executing an Active UTxO entails proving to the protocol the contract is indeed still active, sending the required amount of the asked asset to the writer's payment address, and claiming the offered asset stored with the contract. This action requires both the Lock and the Key NFTs for the contract being executed. All beacons from the contract must either be burned or recycled.

At a low-level, all of the following must be true:

  • The active beacon smart contract must be executed using PurchaseExecuteOrCloseExpiredContracts.
  • For all Active UTxO inputs being executed:
    • The input must be spent using the ExecuteContract spending redeemer.
    • The input must have an ActiveDatum.
    • The input must have the required active beacons.
    • The contract's expiration must be >= invalid-hereafter of this transaction.
    • Both the Lock and Key NFTs for this contract must be burned.
    • All other active beacons attached to the contract must also be burned.
    • There must be a corresponding ask payment output with the following characteristics:
      • It must be locked at the payment address specified by the contract
      • It must contain the contractDeposit amount of ada + the required amount of the askAsset which is determined by the strikePrice and offerQuantity
      • It must contain an inline PaymentDatum with the active beacon policy id as the CurrencySymbol and the new contract's Contract ID beacon token name as the TokenName
  • The active beacon smart contract cannot mint/burn any extra active beacons.

The invalid-hereafter flag is used to prove that the expiration time has not actually passed. When executing multiple contracts, this flag should be set to the earliest expiration time.

A user is able to execute multiple contracts in a given transaction as long as they control all the required Key NFTs.

Execution payment outputs must be in the same order as the execution inputs! They do not need to be paired up. You can even have unrelated outputs between the required outputs. This ordering restriction helps with performance.

Closing Expired Active UTxOs

At a high-level, closing an expired Active UTxO entails proving to the script that the contract is indeed expired, burning all beacons, and getting approval from the writer. This action only requires the Lock NFT.

At a low-level, all of the following must be true for all contracts being closed:

  • The active beacon smart contract must be executed using PurchaseExecuteOrCloseExpiredContracts.
  • For all Active UTxOs being closed:
    • The options address' staking credential must approve the transaction.
    • The input must be spent using the CloseExpiredContract spending redeemer.
    • The input must have an ActiveDatum.
    • If the input has the required active beacons:
      • The contract's expiration must be <= invalid-before of this transaction.
      • All active beacons attached to the contract must be burned.
  • The active beacon smart contract cannot mint/burn any extra active beacons.

The invalid-before flag is used to prove that the expiration time has actually passed. This flag can always be set to the current time.

This method can also be used to close invalid Active UTxOs (ie, UTxOs with an ActiveDatum but no beacons). If you are only closing invalid Active UTxOs, you do not need to set invalid-before and you do not need to execute the active beacon script since there are no beacons to burn.

Updating Payment Addresses

The writer can change the paymentAddress of any Active UTxOs at any time. This just requires their approval, and the proper updated contract output.

At a low-level, all of the following must be true for all contracts updated:

  • The address observer smart contract must be executed as a staking script using ObserveAddressUpdate.
  • For all contract inputs:
    • It must have an ActiveDatum
    • The origin address' staking credential must signal approval
    • It must be spent using UpdatePaymentAddress where the newAddress is the new address to be used and the depositIncrease is the amount of ada added for a larger minUTxOValue
    • The newAddress must either use a payment pubkey, or the proxy script as the payment credential and a valid staking credential
    • The depositIncrease must be >= 0
    • There must be a corresponding output to the input's origin address with:
      • The same exact value as the input + the depositIncrease amount of ada
      • The ActiveDatum must be exactly the same as the input's except:
        • paymentAddress == newAddress
        • contractDeposit == starting contractDeposit + depositIncrease

There is no need to check if the contract is expired since the writer is already incentivized to close the expired contract instead of updating the address.

There is also no need to check for the beacons. UTxOs with an ActiveDatum but no beacons are invalid UTxOs and belong to the writer anyway. This observer script will still require the proper outputs at the writer's address. There is no incentive for writers to update addresses of invalid Active UTxOs.

Address update outputs must be in the same order as the address update inputs! They do not need to be paired up. You can even have unrelated outputs between the required outputs. This ordering restriction helps with performance.

Benchmarks and Fee Estimations (YMMV)

No CIPs or hard-forks are needed. This protocol works on the Cardano blockchain, as is.

Full benchmarking details can be found here. The following table provides a quick summary. Only the worst case benchmarks are shown. The Max Tx Fee is the transaction fee for the worst case scenario while the Min Tx Fee is the fee if only one action was taken in that scenario (eg, only 1 proposal was created, or 1 proposal was purchased).

Action Worst Case Max Tx Fee Min Tx Fee
Creating Proposals 16 proposals/tx 1.457198 ADA 0.274393 ADA
Updating Proposals 12 proposals/tx 1.504038 ADA 0.366034 ADA
Closing Proposals 30 proposals/tx 1.848303 ADA 0.276683 ADA
Purchasing Proposals 8 proposals/tx 1.716877 ADA 0.346103 ADA
Executing Contracts 13 contracts/tx 1.634363 ADA 0.364356 ADA
Closing Expired Contracts 13 contracts/tx 1.448727 ADA 0.237353 ADA
Updating Payment Addresses 18 contracts/tx 1.570385 ADA 0.261055 ADA

Features Discussion

Script-based Payment Addresses

Supporting staking script credentials allows using any kind of custom logic to protect assets. One major use case for this is enabling corporations to adopt DeFi. Most corporations will not be comfortable having their assets only protected by a single pubkey. At the very least, these corporations will prefer using multisig native scripts. Furthermore, there will likely be intense pressure from regulators for corporations to use at least a multisig for all DeFi activities.

Maximum Composability

Cardano-Options is maximally composable not only with other DApps, but also with itself. For example, it is possible to create a new proposal contract for sale, buy another proposal contract, execute an active contract, close an expired active contract, and update the payment address for an active contract, all in one transaction. The total transaction fee for this composition is only 0.7 ADA!

Direct Payments

Instead of having to wait until the contracts finish, premium payment and execution payments are made directly to writers. Writers do not need to return to the DApp address to claim their proceeds.

Maximum Expressiveness

Writers can configure every part of the contract:

  • Which asset will the premium be paid in?
  • Which asset will be offered upon execution?
  • Which asset must be given upon execution?
  • Where will payments go?
  • What will the strike price be?
  • What will the expiration be?
  • What will the premium be?

Writers can even create a contract with an assortment of possible answers for the last three questions. Because of this, writers will be more likely to find buyers for their contracts without having to lock up extra units of the underlying assets (which would be the case if a separate Proposal UTxO was needed for each set of terms).

This flexibility can result in a very complex options trading market that no other DApp can compare to. And Cardano-Options achieves this without sacrificing any of the core principles of p2p-DeFi protocols.

Advanced Beacon Querying

Despite Cardano-Options supporting an abundance of possible configurations, thanks to the beacons, it is still very easy for users to only see contracts for terms they are interested in. Perhaps Bob only wants to see contracts that convert ADA -> AGIX. He can query the TradingPair beacon to only see these contracts. The proposal TradingPair beacon will show contracts for sale while the active TradingPair beacon will show active contracts for that trading pair.

Meanwhile, Mike may want to see all contracts for sale that are offering ada and require a premium paid in DJED. He can see exactly these contracts by querying both the proposal Offer beacon for ada and the proposal Premium beacon for DJED.

Native Support For Secondary Options Markets

Because all contracts get a Key NFT that is freely tradable, it is trivial for secondary markets to form around Cardano-Options. These secondary markets can be anything from other DApps to centralized exchanges. These secondary markets will help Cardano-Options reach its full potential as a primary market for decentralized options trading.

Conclusion

Cardano-Options is (arguably) one of the most advanced decentralized options trading DeFi protocols in all of crypto. It enables the formation of a radically permissionless and highly composable options market on the CSL, and works synergistically with other p2p-DeFi protocols. And best of all, users never give up delegation control or voting control of their assets while using the protocol.

About

A p2p-DeFi protocol for options contracts on the Cardano Settlement Layer.

Resources

License

Stars

Watchers

Forks

Packages

No packages published