A p2p-DeFi aftermarket protocol for buying/selling NFTs on the Cardano Settlement Layer.
NOTE This protocol was originally called Cardano-Secondary-Market. To minimize the risk of repojacking, a new repo was created instead of simply renaming the old one.
- Abstract
- Motivation
- The Cardano-Aftermarket DeFi Protocol
- Specification
- Benchmarks and Fee Estimations (YMMV)
- Features Discussion
- Future Directions
- Conclusion
Cardano-Aftermarket is a p2p-DeFi protocol for trading NFTs on the Cardano Settlement Layer (CSL). Users deploy and interact with each others' script addresses, thereby creating permissionless and high-availability NFT aftermarkets. This protocol is fully general and can support all possible aftermarkets; each aftermarket is tagged with dedicated beacons to enable filtering for specific markets. Both spot markets and auction markets are supported for all aftermarkets. Finally, as with all p2p-DeFi protocols, users maintain custody, delegation control, and voting control of their assets at all times.
Aftermarkets play a crucial role in enhancing the efficiency and dynamism of an economy, particularly in the DeFi space. For example, imagine if Bob owns a bond, but needs to sell it before maturity in order to raise some cash. Where would he go? What if he has no where to go? If Bob can't be sure that he will be able to liquidate the bond in an emergency, he may view lending money in the first place as being too risky. As a consequence, the primary lending/borrowing marketplace would be smaller just because there is no viable aftermarket for the associated bonds. The same logic applies to other financial markets like options and futures.
Current NFT aftermarket solutions on Cardano have three main drawbacks. The first is that they tend to rely on centralized entities for listing and finding assets. This introduces an availability bottleneck; centralized entities cannot guarantee the same level of uptime and censorship resistance as the underlying blockchain. In order for DeFi to be immune from influence by those in power, every critical piece of the economy must be decentralized and censorship resistance. Aftermarkets are critical.
The second drawback is that they lack composability with other DeFi protocols. This lack of composability can dramatically increase financial risk for users. For a full explanation of this, check out the Cardano-Loans README.
The final drawback is that users must give up custody of their assets in order to use the marketplaces. Contemporary DApps usually require users to pool their assets into a select set of addresses. This has three critical issues:
- Pooled assets are a much more enticing target for hackers and governments.
- Users are forced to give up delegation control of all assets participating in DeFi. This has serious negative implications for the long-term security of Cardano.
- Users are forced to give up voting control of all assets participating in DeFi. This puts the future or Cardano's decentralized government in jeopardy.
There are currently no true p2p aftermarkets. Using the blockchain does not automatically make the DApp p2p! It also doesn't automatically make it decentralized. Cardano needs a radically different kind of aftermarket DApp - one that is actually p2p, decentralized, and censorship resistant.
Cardano-Aftermarket is (arguably) the first truly p2p, decentralized, and censorship resistance DeFi aftermarket. By using the distributed DApp design (ie, all users get their own personal DApp address), it doesn't have any of the above limitations. Furthermore, this protocol is a general purpose marketplace that can serve as the aftermarket for all possible financial markets. It can even serve as an aftermarket for art NFTs.
- Spot Markets & Auction Markets - sellers can choose to either put up their NFTs for sale at a specified price or they can initiate an auction for the NFTs and accept bids from potential buyers.
- Batch Sales - sellers can group NFTs from the same policy id together and sell/auction them as a batch. For example, instead of selling 10 options contracts individually, Bob can offer them as a package deal.
- Multi-Asset Prices - sellers and buyers can trade any number of assets in exchange for the NFTs. For example, Alice can sell her options contract for 10 ADA + 10 DJED. Meanwhile, Bob can use 5 ADA, 10 AGIX, and 30 DJED to bid on a loan bond. All Cardano native assets are supported. It is also possible to use only a single asset for the price, if desired.
- Direct Payments - when a seller accepts a bid for an Auction, they send the NFTs directly to the winning bidder's specified payment address. Likewise, when a buyer purchases a spot NFT, they send the proceeds directly to the seller's specified payment address. There is no need for the users to return to the DApp address to claim their funds. There is even support for multi-sig and plutus addresses as the payment addresses (using the proxy script).
- Staking Script Support - buyers and sellers can use staking scripts for their DApp addresses. This enables using more complicated logic to protect their assets in the DApp.
- A Single DApp Address for each Seller - sellers only need to worry about a single address for the entire protocol; it does not matter which NFTs are being sold. For example, Bob would use the same DApp address for selling loan bonds and options contracts. This makes it very easy for front-ends to integrate the protocol, and for users to manage their delegation and voting preferences.
- 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). This protocol is cross-version compatible; Bob can create a new spot sale using v1 and create a bid on an auction that is using v2.
- Full Composability - this protocol is natively composable with all other DeFi DApps. It can even be composed with itself: a seller can create a new auction in the same transaction where they accept a bid for another auction.
- Censorship Resistant - any user that has the full UTxO set (ie, a full-node) automatically has the entire market for all aftermarkets. No one can prevent them from seeing market activity, and/or participating in the markets.
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.
Sellers each create a unique DApp address - this is where all of their sales/auctions take place. As is common in distributed dApps, all such DApp 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.
Since auctions occur in the seller's address, the seller maintains staking and voting rights over all assets throughout the auction. This includes any bids made by potential buyers which incentivizes bidders to be proactive and not leave bids open for too long.
Payments are not made to this address. Instead, the payment credential acts as an overseer to ensure that the payment goes to the proper address.
⚠️ If, at any point, a misconfigured UTxO is sent to a seller's DApp address, the seller will get custody. Such UTxOs can only come about by misusing the protocol.
Since Cardano-Aftermarket has users make payments directly to addresses specified in the datums, care must be taken when the specified address uses a payment plutus script. This is because the protocol enforces a specific datum with 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-Aftermarket 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 user wishes to use more than just a single pubkey to protect their proceeds. For example, if the seller 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 seller 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.
This protocol supports three types of UTxOs:
- Spot UTxOs - a UTxO dedicated to selling NFTs at a predefined price.
- Auction UTxOs - a UTxO dedicated to initiating an auction for the given NFTs.
- Bid UTxOs - a UTxO dedicated to bids for an associated Auction UTxO.
Spot UTxOs and Auction UTxOs are both created and managed by sellers while Bid UTxOs are created and managed by buyers.
While Auction and Bid UTxOs are meant to be paired up, they do not need to be. For example, if a buyer sees a Spot UTxO that hasn't sold at the desired price, they can create a Bid UTxO for those NFTs at a different price; this is effectively a counter-offer even though it was not meant to be an auction.
Each UTxO type has dedicated beacons:
- Spot beacon - a beacon with the token name "Spot" that represents the fact this UTxO is a Spot UTxO.
- Policy beacon - a beacon representing the policy id for the NFTs being sold. It has the token
name:
sha2_256("00" ++ policy_id)
.
- Auction beacon - a beacon with the token name "Auction" that represents the fact this UTxO is an Auction UTxO.
- Policy beacon - a beacon representing the policy id for the NFTs being auctioned. It has the token
name:
sha2_256("00" ++ policy_id)
.
- Bid beacon - a beacon with the token name "Bid" that represents the fact this UTxO is a Bid UTxO.
- Policy beacon - a beacon representing the policy id for the NFTs being auctioned. It has the token
name:
sha2_256("00" ++ policy_id)
. - BidderId beacon - a beacon representing the bidder's staking credential. It has the token name:
"01" ++ bidder_credential_hash
. This beacon enables bidders to find all of their bids located across the sellers' DApp addresses.
Due to the amount of logic required for this protocol and the desire to minimize the impact from redundant executions, this protocol uses 3 separate smart contracts; not all of them are required with each transaction. Each smart contract is dedicated to a specific purpose. Most user actions only required 2 of the 3 contracts in a single transaction. Some actions do require all three. However, since these scripts can be used as reference scripts, there is still plenty of room for DApp composability.
- Beacon Smart Contract - this smart contract is in charge of minting/burning beacons for the protocol. In addition to being a minting policy, it can also be executed as a staking script to enable cheaply updating protocol UTxOs in-place (ie, no beacons need to be minted/burned, but the new datums need to be checked).
- Payment Observer Smart Contract - this smart contract is in charge of observing all payments. It can only be executed as a staking script.
- Aftermarket Spending Smart Contract - this smart contract is the payment credential for all DApp addresses. It delegates checks to one of the other 2 smart contracts depending on the action being taken. It can only be executed as a spending script.
The aftermarket 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 DApp addresses.
The payment observer smart contract hash is hard-coded into the beacon smart contract so that the beacon smart contract can force the use of the proper observer logic. The protocol's complementary proxy smart contract is also hard-coded into the beacon smart contract so that payment addresses can be checked for proper configurations.
Spot UTxOs have a SpotDatum
.
Auction UTxOs have an AuctionDatum
.
Bid UTxOs have an BidDatum
.
-- | A type synonym for an asset's full name.
type Asset = (CurrencySymbol,TokenName)
-- | The datum for a Spot UTxO sale for NFTs.
data SpotDatum = SpotDatum
-- | The policy id for the beacon script.
{ beaconId :: CurrencySymbol
-- | The hash of the payment observer script.
, paymentObserverHash :: ScriptHash
-- | The policy id for the NFTs being offered.
, nftPolicyId :: CurrencySymbol
-- | The token names of the NFTs offered in this batch.
, nftNames :: [TokenName]
-- | The address where the proceeds must go upon purchase of the Spot UTxO.
, paymentAddress :: Address
-- | The amount of ada used for the minUTxOValue.
, saleDeposit :: Integer
-- | The price for this batch, denominated in the specified assets. This is the price for the
-- entire batch of NFTs. The payment output must contain *all* of the required assets, and their
-- specified amounts.
, salePrice :: [(Asset,Integer)]
}
-- | The datum for an Auction UTxO for NFTs.
data AuctionDatum = AuctionDatum
-- | The policy id for the beacon script.
{ beaconId :: CurrencySymbol
-- | The hash of the payment observer script.
, paymentObserverHash :: ScriptHash
-- | The policy id for the NFTs being auctioned.
, nftPolicyId :: CurrencySymbol
-- | The token names of the NFTs offered in this batch.
, nftNames :: [TokenName]
-- | The desired starting price. This is only used to broadcast the auctioner's desired value.
-- Bidders are able to create "counter-bids" with different assets and/or different nft names.
-- For example, the bidder can make a bid for just one of the NFTs in the batch.
, startingPrice :: [(Asset,Integer)]
}
-- | The datum for a Bid UTxO for NFTs.
data BidDatum = BidDatum
-- | The policy id for the beacon script.
{ beaconId :: CurrencySymbol
-- | The hash of the payment observer script.
, paymentObserverHash :: ScriptHash
-- | The policy id for the NFTs being bid on.
, nftPolicyId :: CurrencySymbol
-- | The credential used for the BidderId token name. This is used so bidders can find all of
-- their bids despite them being located in other addresses.
, bidderCredential :: Credential
-- | The token names of the NFTs being bid on.
, nftNames :: [TokenName]
-- | The address where the NFTs must go upon accepting the bid.
, paymentAddress :: Address
-- | The amount of ada used for the minUTxOValue.
, bidDeposit :: Integer
-- | The actual bid.
, bid :: [(Asset,Integer)]
}
data MarketRedeemer
-- | Close or update either a Spot UTxO or an Auction UTxO.
= CloseOrUpdateSellerUTxO
-- | Close or update a Bid UTxO.
| CloseOrUpdateBidderUTxO
-- | Purchase a Spot UTxO.
| PurchaseSpot
-- | Accept a Bid UTxO, and close the associated Auction UTxO.
| AcceptBid
data PaymentObserverRedeemer
-- | Observe a market payment transaction. This observes the payment for both spot purchases and
-- bid acceptances.
= ObservePayment { beaconId :: CurrencySymbol }
-- | Register the script.
| RegisterPaymentObserverScript
data BeaconsRedeemer
-- | Create, close, or update some market UTxOs (1 or more).
= CreateCloseOrUpdateMarketUTxOs
-- | Burn any beacons.
| BurnBeacons
-- | Register the script.
| RegisterBeaconsScript
This datum is attached to all direct outputs to payment addresses. It is used to prevent double-satisfaction.
-- | The `CurrencySymbol` is always the beacon policy id, and the `TxOutRef` is always
-- the output reference for either the Spot UTxO being purchased or the Bid UTxO being accepted.
newtype PaymentDatum = PaymentDatum (CurrencySymbol,TxOutRef)
The CurrencySymbol
is included to prevent double-satisfaction during DApp composition; just using
the TxOutRef
may not be enough to guarantee uniqueness.
At a high-level, creating Spot UTxOs involves creating the new UTxOs at the seller's DApp address
with the desired SpotDatum
s, storing them with the NFTs in question, and tagging them with the
required beacons. The beacon smart contract will check all outputs containing beacons to ensure
invalid UTxOs are never broadcast to other users.
At a low-level, all of the following must be true:
- The beacon smart contract must be executed as a minting policy using
CreateCloseOrUpdateMarketUTxOs
. - All Spot UTxO outputs must be to a DApp address with a valid staking credential.
- All Spot UTxO outputs must have exactly two kinds of beacons, with exactly one unit of each:
- a Spot beacon with the token name "Spot"
- a Policy beacon with the token name corresponding to the
nftPolicyId
in theSpotDatum
.
- All Spot UTxO outputs must have a valid inline
SpotDatum
:beaconId
== this policy idpaymentObserverHash
== hard-coded payment observer hashnftPolicyId
== policy id for nfts being sold in the UTxO and cannot be the beacon policy idnftNames
== token names for nfts being sold in the UTxO and cannot be emptypaymentAddress
must either use a payment pubkey, or the proxy script as the payment credential and a valid staking credentialsaleDeposit
> 0salePrice
must not be empty, must not use any protocol beacons as the assets, all prices must be > 0, and the list must be sorted.
- All Spot UTxO outputs must have the
saleDeposit
amount of ada + the NFTs being sold.
The nftNames
is the list of all NFTs being sold in this batch.
The paymentAddress
is where the proceeds will go when the NFTs are purchased.
The salePrice
is the price of the NFT batch. It can be a basket of assets. This amount will be
sent to the paymentAddress
when the batch is purchased.
The saleDeposit
is the amount of ada used for the minUTxOValue. This will be sent to the
paymentAddress
in addition to the required salePrice
assets.
At a high-level, closing Spot UTxOs involves spending the target UTxOs at the seller's DApp address, and burning the beacons attached to them. The seller must approve this transaction. The 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 aftermarket spending smart contract is executed for the Spot UTxO input using
CloseOrUpdateSellerUTxO
. - The Spot UTxO must have a
SpotDatum
. - The DApp address' staking credential must signal approval.
- If the Spot UTxO being spent contains beacons:
- The beacon smart contract must be executed as a minting policy using
CreateCloseOrUpdateMarketUTxOs
.
- The beacon smart contract must be executed as a minting policy using
The beacon smart contract will actually do the exact same check as when creating Spot UTxOs. However, since closing Spot UTxOs implies no new Spot UTxO outputs, there are no outputs to check.
If there is ever an invalid Spot UTxO (ie, a UTxO with a SpotDatum
but no beacons), it can be
spent with this method; the beacon smart contract would not need to be executed.
Updating Spot UTxOs in-place can be done regardless of whether beacons must be changed. The steps are identical to closing Spot UTxOs, except you now create Spot UTxO outputs as well. Since there are now outputs, the outputs will be checked by the beacon script and must comply with the same requirements as when creating Spot UTxOs.
If no beacons need to be minted/burned, the beacon script must be executed as a staking script using
CreateCloseOrUpdateMarketUTxOs
. If beacons do need to be minted/burned, then the beacon script
must be executed as a minting policy using the same redeemer.
At a high-level, purchasing a spot batch involves sending the payment to the seller's payment address, and burning all protocol beacons.
At a low-level, all of the following must be true:
- The payment observer script must be executed as a staking script using
ObservePayment
. - The following must be true for all Spot UTxO inputs being purchased:
- The input must be spent using
PurchaseSpot
. - The input must have a
SpotDatum
. - The input must have the required beacons.
- There must be a corresponding spot payment output with the following characteristics:
- It must be locked at the
paymentAddress
in theSpotDatum
. - It must contain the
salePrice
asset values + thesaleDeposit
amount of ada. - It must contain the inline
PaymentDatum
with the beacon policy id as theCurrencySymbol
and the input's output reference as theTxOutRef
.
- It must be locked at the
- Either all beacons attached to the input must be burned or the beacon script must be executed
using
CreateCloseOrUpdateMarketUTxOs
.
- The input must be spent using
If all beacons need to be burned, it is cheaper to use BurnBeacons
. However, if beacons are being
recycled into new outputs or new beacons need to be minted, then CreateCloseOrUpdateMarketUTxOs
should be used. If all beacons are being recycled and no minting is required, the beacon script
must be executed as a staking script.
Spot payment outputs must be in the same order as the Spot 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.
At a high-level, creating Auction UTxOs involves creating the new UTxOs at the seller's DApp address
with the desired AuctionDatum
s, storing them with the NFTs in question, and tagging them with the
required beacons. The beacon smart contract will check all outputs containing beacons to ensure
invalid UTxOs are never broadcast to other users.
At a low-level, all of the following must be true:
- The beacon smart contract must be executed as a minting policy using
CreateCloseOrUpdateMarketUTxOs
. - All Auction UTxO outputs must be to a DApp address with a valid staking credential.
- All Auction UTxO outputs must have exactly two kinds of beacons, with exactly one unit
of each:
- an Auction beacon with the token name "Auction"
- a Policy beacon with the token name corresponding to the
nftPolicyId
in theAuctionDatum
.
- All Auction UTxO outputs must have a valid inline
AuctionDatum
:beaconId
== this policy idpaymentObserverHash
== hard-coded payment observer hashnftPolicyId
== policy id for nfts being auctioned in the UTxO and cannot be the beacon policy idnftNames
== token names for nfts being auctioned in the UTxO and cannot be emptystartingPrice
must not be empty, must not use any protocol beacons as the assets, all prices must be > 0, and the list must be sorted.
- All Auction UTxO outputs must have the NFTs being auctioned.
The nftNames
is the list of all NFTs being auctioned in this batch.
The startingPrice
is just meant to broadcast the seller's desired starting price for the auction.
The seller is free to accept bids lower than this price, if desired. They can even accept bids for
only a subset of the NFTs in the batch.
At a high-level, closing Auction UTxOs involves spending the target UTxOs at the seller's DApp address, and burning the beacons attached to them. The seller must approve this transaction. The 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 aftermarket spending smart contract is executed for the Auction UTxO input using
CloseOrUpdateSellerUTxO
. - The Auction UTxO must have an
AuctionDatum
. - The DApp address' staking credential must signal approval.
- If the Auction UTxO being spent contains beacons:
- The beacon smart contract must be executed as a minting policy using
CreateCloseOrUpdateMarketUTxOs
.
- The beacon smart contract must be executed as a minting policy using
The beacon smart contract will actually do the exact same check as when creating Auction UTxOs. However, since closing Auction UTxOs implies no new Auction UTxO outputs, there are no outputs to check.
If there is ever an invalid Auction UTxO (ie, an UTxO with an AuctionDatum
but no beacons), it can be
spent with this method; the beacon smart contract would not need to be executed.
Updating Auction UTxOs in-place can be done regardless of whether beacons must be changed. The steps are identical to closing Auction UTxOs, except you now create Auction UTxO outputs as well. Since there are now outputs, the outputs will be checked by the beacon script and must comply with the same requirements as when creating Auction UTxOs.
If no beacons need to be minted/burned, the beacon script must be executed as a staking script using
CreateCloseOrUpdateMarketUTxOs
. If beacons do need to be minted/burned, then the beacon script
must be executed as a minting policy using the same redeemer.
At a high-level, creating Bid UTxOs involves creating the new UTxOs at the target seller's DApp
address with the desired BidDatum
s, storing them with the bid amount offered, and tagging them
with the required beacons. The beacon smart contract will check all outputs containing beacons to
ensure invalid UTxOs are never broadcast to other users.
At a low-level, all of the following must be true:
- The beacon smart contract must be executed as a minting policy using
CreateCloseOrUpdateMarketUTxOs
. - All Bid UTxO outputs must be to a DApp address with a valid staking credential.
- All Bid UTxO outputs must have exactly two kinds of beacons, with exactly one unit of each:
- a Bid beacon with the token name "Bid"
- a BidderId beacon with the token name corresponding to the
bidderCredential
in theBidDatum
. - a Policy beacon with the token name corresponding to the
nftPolicyId
in theBidDatum
.
- All Bid UTxO outputs must have a valid inline
BidDatum
:beaconId
== this policy idpaymentObserverHash
== hard-coded payment observer hashbidderCredential
== credential for the bidder.nftPolicyId
== policy id for nfts being sold in the UTxO and cannot be the beacon policy idnftNames
== token names for nfts being sold in the UTxO and cannot be emptypaymentAddress
must either use a payment pubkey, or the proxy script as the payment credential and a valid staking credentialbidDeposit
> 0bid
must not be empty, must not use any protocol beacons as the assets, all prices must be > 0, and the list must be sorted.
- All Bid UTxO outputs must have the
bidDeposit
amount of ada andbid
asset value. - All
bidderCredential
s used in the newBidDatum
must approve the transaction.
The nftNames
is the list of all NFTs being ask for in exchange for the bid assets.
The paymentAddress
is where the NFTs will go when the bid is accepted.
The bid
is the actual value of the bid for the target NFTs. It can be a basket of assets.
The bidDeposit
is the amount of ada used for the minUTxOValue. This will be sent to the
paymentAddress
in addition to the required NFTs.
The bidderCredential
s must approve the transaction so that only the user who controls that
credential can use the associated BidderId beacon.
The bid can have different terms than the associated Auction UTxO. For example, the bid could ask
for different NFTs and/or offer different assets than the startingPrice
from the AuctionDatum
.
At a high-level, closing Bid UTxOs involves spending the target UTxOs at the target seller's DApp address, and burning the beacons attached to them. The bidder must approve this transaction. The 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 aftermarket spending smart contract is executed for the Bid UTxO input using
CloseOrUpdateBidderUTxO
. - The Bid UTxO must have a
BidDatum
. - If the Bid UTxO being spent contains beacons:
- The
bidderCredential
must signal approval. - The beacon smart contract must be executed as a minting policy using
CreateCloseOrUpdateMarketUTxOs
.
- The
- If no beacons are present, the address' staking credential must approve.
The beacon smart contract will actually do the exact same check as when creating Bid UTxOs. However, since closing Bid UTxOs implies no new Bid UTxO outputs, there are no outputs to check.
If there is ever an invalid Bid UTxO (ie, an UTxO with an BidDatum
but no beacons), it can be
spent by the address owner with this method; the beacon smart contract would not need to be
executed.
Updating Bid UTxOs in-place can be done regardless of whether beacons must be changed. The steps are identical to closing Bid UTxOs, except you now create Bid UTxO outputs as well. Since there are now outputs, the outputs will be checked by the beacon script and must comply with the same requirements as when creating Auction UTxOs.
If no beacons need to be minted/burned, the beacon script must be executed as a staking script using
CreateCloseOrUpdateMarketUTxOs
. If beacons do need to be minted/burned, then the beacon script
must be executed as a minting policy using the same redeemer.
At a high-level, accepting a bid involves sending the NFTs to the bidder's payment address, and burning all protocol beacons.
At a low-level, all of the following must be true:
- The payment observer script must be executed as a staking script using
ObservePayment
. - The following must be true for all Bid UTxO inputs being accepted:
- The input must be spent using
AcceptBid
. - The input must have a
BidDatum
. - The input must have the required beacons.
- There must be a corresponding bid payment output with the following characteristics:
- It must be locked at the
paymentAddress
in theBidDatum
. - It must contain the NFTs + the
bidDeposit
amount of ada. - It must contain the inline
PaymentDatum
with the beacon policy id as theCurrencySymbol
and the input's output reference as theTxOutRef
.
- It must be locked at the
- Either all beacons attached to the input must be burned or the beacon script must be executed
using
CreateCloseOrUpdateMarketUTxOs
.
- The input must be spent using
If all beacons need to be burned, it is cheaper to use BurnBeacons
. However, if beacons are being
recycled into new outputs or new beacons need to be minted, then CreateCloseOrUpdateMarketUTxOs
should be used. If all beacons are being recycled and no minting is required, the beacon script
must be executed as a staking script.
Bid payment outputs must be in the same order as the Bid 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.
It is possible to accept bids that have different terms than the associated Auction UTxO. For example, you can accept a bid asking for only some of the NFTs being auctioned. You can then rollover the leftover NFTs into a new Auction UTxO in the same transaction.
If the NFTs required for the bid payment are currently in an Auction UTxO, that UTxO can be closed
in the same transaction using AcceptBid
as the spending redeemer. If the NFTs are in a Spot UTxO,
that UTxO can be closed using CloseOrUpdateSellerUTxO
as the spending redeemer, however, the
beacons script must then be executed using CreateCloseOrUpdateMarketUTxOs
with either a minting
execution or staking execution (depending on whether beacons must be minted/burned).
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 auction was created, or 1 spot batch was purchased).
Action | Worst Case | Max Tx Fee | Min Tx Fee |
---|---|---|---|
Creating Spot Batches | 32 spots/tx | 1.722147 ADA | 0.236636 ADA |
Updating Spot Batches | 21 spots/tx | 1.930661 ADA | 0.253392 ADA |
Closing Spot Batches | 31 spots/tx | 1.625099 ADA | 0.204058 ADA |
Purchasing Spot Batches | 22 spots/tx | 1.682365 ADA | 0.258371 ADA |
Creating Auction Batches | 35 auctions/tx | 1.528574 ADA | 0.227490 ADA |
Updating Auction Batches | 22 auctions/tx | 2.129716 ADA | 0.393530 ADA |
Closing Auction Batches | 32 auctions/tx | 1.661041 ADA | 0.202913 ADA |
Creating Bids | 21 bids/tx | 1.970657 ADA | 0.280369 ADA |
Updating Bids | 14 bids/tx | 1.764683 ADA | 0.296901 ADA |
Closing Bids | 31 bids/tx | 1.663468 ADA | 0.206458 ADA |
Accepting Bids | 10 bids/tx | 1.485366 ADA | 0.293522 ADA |
This marketplace is fully general and can work with all possible DeFi markets that offer tradable NFTs. Since all primary markets will likely have predefined minting policies for their financial assets, the associated aftermarkets can easily be found using only those minting policies. This is entirely thanks to the beacon tokens; they are the reason all possible aftermarkets are supported despite using only a single DApp address per user. The beacon tokens are also why this protocol supports financial markets that have not even been invented yet; no permission or governance action are required to create new aftermarkets.
Buyers and sellers can exchange any assets for the NFTs. This includes assets that do not even exist yet. Even other NFTs can be offered! No permission or governance action is needed to add support for new assets.
While only auctions are meant to use bids, the bids can actually be used to create counter-offers of both spot sales and auctions. Imagine if Alice is trying to sell two bonds using a spot sale for 100 ADA (50 ADA each). What if Bob only wants one of the bonds? He could use a bid to tell Alice he'll take only the first bond and pay 70 ADA for it. In other words, Bob is willing to pay up a little just to get the single bond; he would still save money since he doesn't even want the second bond. Alice can see this and choose to accept Bob's bid even though she didn't initiate an auction. She can close the Spot UTxO, accept Bob's Bid UTxO, and create a new Spot UTxO with the remaining bond, all in one transaction.
Because of the fully composable nature of spots, auctions, and bids, very complex market interactions can trustlessly occur. All without any middlemen/batchers.
Art assets have aftermarkets just like financial assets, and this protocol can also be used to trade art NFTs in a fully p2p fashion. However, this is not the intended purpose for this protocol. There is no support for royalties, and there likely never will be since financial assets do not include royalties.
Currently, the protocol does not support using beacons to further filter aftermarkets. For example, while it is possible to filter for all spot sales for option contracts, it is not currently possible to further filter for options contracts where ada is the offered asset. The only way to securely enable support for this is if the associated options contract was referenced when the corresponding aftermarket sale/auction was created. This would prevent creating the wrong beacons for each sale.
While this is technically possible to do, it would add some substantial complexity to the protocol and would dramatically impact overall performance when creating sales. Given the already high throughput when creating sales, this may be acceptable but it may not be necessary.
Off-chain infrastructure could potentially fill this niche. For example, when a new aftermarket sale is created, the off-chain infrastructure could automatically look up the associated financial asset's information and store them together in its database. Then, this off-chain infrastructure can offer filtering capabilities when users query those sales; it can even return the associated information for the financial asset in the same query. A decentralized Cardano database like Koios could fill this niche without sacrificing decentralization.
If the off-chain infrastructure is unable to satisfy this niche without sacrificing decentralization, then support for this filtering can be added at the protocol level.
Cardano-Aftermarket is a vital addition to the p2p-DeFi protocol family. It enables the formation of a radically permissionless and highly composable NFT marketplace on the CSL, and works synergistically with other p2p-DeFi protocols.