Today I am excited to launch the ADA Blobs smart contract marketplace on Cardano! This is the first auction NFT marketplace on the Cardano Blockchain and I will explain how it works in this Blob post.
Cardano uses the extended UTxO model which is similar the UTxO model used by Bitcoin, but with the additional ability to add arbitrary data to the transactions.
UTxO stands for “Unspent Transaction Output” and works similarly to how you would pay with cash at a store. Let’s say you wanted to buy an adorable stuffed animal Blob for $10 at the toy store, but you only have a $20 bill. You would give the entire $20 bill to the cashier and they would give you back a $10 bill in change. Now lets say you similarly wanted to send someone 10 ADA (10₳) but you only have 1 UTxO in your Cardano wallet with 20₳ on it. The input to the transaction would be the UTxO with 20₳ and you would get back a UTxO with 10₳ in change.
This model works well for sending and validating transaction on the blockchain but there is a fundamental limitation. We cannot create more complex transactions because we do not have access to additional additional blockchain data or programmable validators.
The eUTxO Model
To solved this problem, the eUTxO model was developed for Cardano. This gives developers access to the Plutus language which allows them to create custom on-chain validators. Along with Plutus, the eUTxO model allows developers to attach data called datum to a transaction as well as supply data called the redeemer when a UTxO sitting at a script address is trying to be spent.
The example below showcases Bob sending funds to a script address and Yolg withdrawing those funds after a Plutus Validator evaluates to “true” for the transaction.
In the above example, Bob sends 40₳ to a script address. He supplies a 100₳ UTxO as input and instructs the output to be a 60₳ UTxO back to Bob as change and a 40₳ UTxO to the script address. Bob also supplies a datum called State 0. The Plutus Validator evaluates to true and the script is send 40₳.
Now Bob’s friend Yolg wants to withdraw some of the funds that Bob sent to the script address. Yolg creates a new transaction with the UTxO at the script address being used as input and the outputs are a UTxO with 20₳ to Yolg and another UTxO with 20₳ back to the script address. Yolg supplies a “Withdraw” redeemer and the datum on the output UTxO at the script transitions from State 0 to State 1.
A Plutus Validator will take in 3 inputs and return true or false. Validator(Datum, Redeemer, ScriptContext) = True. If the validator evaluates the true, the transaction succeeds. If not, the transaction fails.
Creating the ADA Blobs Validator
To create the Plutus Auction Validator for ADA Blobs, we need to handle 3 possible user actions:
- Starting a auction
- Bidding on an auction
- Closing an auction
Starting an Auction
To start an auction for a Blob NFT, we need to send our script address a UTxO with the Blob NFT along with a datum that will be used by the Plutus Validator to determine how others can bid on the Blob.
For example, if our datum has a minimum bid value and a bidder tries to bid less than that value, the bidder’s transaction will return false and fail.
As you can see from above, our AuctionDatum will have 2 parts, an AuctionDetails and a Maybe BidDetails.
We want the auction details to always be the same throughout the auction so we will set the auction details when starting the auction. We won’t set the bid details yet as we will let the bidders set that value when they bid for our ADA Blobs.
Bidding on an Auction
Now that we have submitted a transaction to start our blob auction, we need to give others a way to bid on the auction.
The bidder will supply a new datum with their updated bid as well as a “Bid” redeemer (action) to spend the UTxO that exists at the script address. The validator script will than check to make sure the a bid is valid (larger than all other bids and the reserve amount) and that we set the next datum correctly (validating that the bidDetails data is the same as how much ₳ the bidder actually sent to the script address).
As an example lets say Glob bids 100₳ for the Blob NFT that Bob put up for auction. Now the script address has 101₳ and a Blob NFT.
Note: The extra 1₳ is because Cardano native tokens cannot be at a script address with no ₳. This is because then you would not be able to send the token because you would not be able to pay the Cardano Blockchain fees.
Now Yolg comes along and wants to bid 200₳ for the Blob NFT. Yolg will send 200₳ to the script address, and 100₳ sitting at the script address will be returned to Glob.
We have to ensure with the bid transaction that if a bidder gets outbid, that their funds are correctly returned to them. All of this validation is performed in the bid section of the validator. The full source code contains additional helper functions for this process. You can follow along with the full Plutus source code here.
Closing an auction
After an auction is complete, anyone can close the auction which will send the bid funds to the seller, and the Blob NFT to the bidder. We require the “Close” Redeemer to close the auction. The validation for closing the auction includes code that ensures the auction deadline has passed. You can view the code for the close portion of the validator below.
In our example, lets say the auction has closed. Yolg would get the Blob NFT and 1 ₳ at the script address and Bob would get the 200₳ at the script address.
The code for constructing all 3 types of transactions can be found in this source code.
Building the Start Auction Transaction
In order to start our auction we need to construct our initial AuctionDatum, construct the desired outputs of the transaction, and select our input UTxOs that must contain the Blob NFT that we are intending to auction.
We now need to take our typescript object and convert it into data that Plutus can understand. For that we need to use the Cardano Serialization Library. However, we need to add some custom functionality for it to work properly at the time of writing this. Originally SpaceBudz implemented a custom version of this library to build the transactions correctly. I copied their implementation here.
Cardano requires our data to be in a specific format in order to properly submit the transactions. I suggest reading this page on the Cardano Documentation in order to understand the JSON format that is required.
In my datum.ts file. I have commented the JSON structure that corresponds to the Haskell datatype that the function is trying to build. The easiest way to understand how to build the datum with the Cardano Serialization Library is the read the START_DATUM function I have created here.
Finally, we need to select our input UTxOs for the transaction. To do this we will use the random improve coin selection algorithm. I don’t want to get too technical into this algorithm here so I have provided a link to the algorithm from IOHK above. Essentially, the algorithm selects UTxOs in such a way that the outputs will allow future payments with a minimum number of inputs.
For ADA Blobs, I copied the algorithm implementation from SpaceBudz in the CoinSelection.ts file. I suggest copying this as well.
Finally with our datum, inputs, and outputs we can construct, sign, and submit the transaction.
Building the Bid Transaction
We will be building the bid transactions in a very similar way to the auction transactions. The differences occur in our bid transaction output and the requirement that we set a transaction time to live (ttl). For our output, if there is already an existing bid for our ADA Blob, we need send the previous bidder’s funds back to their address with the code below.
We also need to set a validity start interval and time to live since bid validation will depend on the time that the bid transaction occurs.
With this data set along with our updated datum, outputs, and inputs we can sign and submit our bid transaction!
Building the Close Transaction
We are almost done! We need to build our close transaction to give the highest bidder the ADA Blob NFT and the seller the highest bid. There is a case we also have to cover where nobody bids on the adorable pet Blob that is up for auction (but realistically that will never happen because they are so cute). For completeness however, we should include that in our “Close” code.
The code above will give the highest bidder the adorable Blob NFT and the seller the highest bid amount. The “splitAmount” function you see there splits the output between the seller and the ADA Blobs marketplace fee which is 1%. The On Chain validation code takes this fee into account which you can see in the source code.
Along with the outputs we will need to set a time to live (ttl) for the Close transaction as well because the close transaction also depends on time.
After a few weeks of testing on the Cardano testnet and mainnet the ADA Blobs project is working perfectly! There are 300 adorable pet Blob NFTs that will be released 1 a week for the next 6 years. I’m excited to release this project and open source it to the community for everyone to learn from.
Cardano projects are ramping up very quickly now and it is an exciting time to be a part of the community. Feel free to check out the ADA Blobs source code to use in your own projects!