Jam on Bread contract

NFT marketplace, which allows you to buy and sell Cardano-based NFTs.

Design

Fees and royalty calculation

The calculation for price 100 ADA, and a typical set of treasuries and 5% royalty:

Major changes

Mint

Simple contract, that checks argument OutputReference is present on inputs. The purpose is only for minting disposable assets (for treasury validation method)

Staking

A simple contract that is validated same as the Withdrawal method, which is passed as an argument. The second argument is just salt for PolicyId It is a stake address that can be withdrawn by the wallet/token holder

Treasury

Common contract where user can collect ADA value during manipulation with a market. There are two purposes:

The contract holds information about the withdrawal method in datum

flowchart LR
   treasuryIn1[Treasury + Stake1]
   treasuryIn2[Treasury + Stake1]
   treasuryIn3[Treasury + Stake2]
   treasuryInMore[...]

   treasuryIn1 & treasuryIn2 & treasuryIn3 & treasuryInMore -->|Withdraw| tx

   tx["Transaction (Tx)"]

   tx --> |ReCreate| treasuryOut1 & treasuryOut2 & treasuryOutMore
   tx --> |Reward| user

   treasuryOut1[Treasury + Stake1]
   treasuryOut2[Treasury + Stake2]
   treasuryOutMore[...]
   user[Wallet]

Usage in transaction

Typical usage of the treasury is, that the original UTxO is spending during the transaction and a new one is created on output. The diff between output and input is provision.

Usage during withdrawal

There are two validation mechanisms, depending on the type of treasury:

Instant buy

Contract for buying NFTs

There are two purposes:

The validator has a params:

validator(
  treasury_script_hash: Hash<Blake2b_224, credential.Script>, // Reference to treasury validator
  stake_addresses: List<credential.StakeCredential>,          // List of accepted JoB stake address
  job: types.WithdrawalMethod,                                // JoB treasury, what is present in each transaction
)

Information about price, royalty etc., are stored in datum

Accept

flowchart LR
   treasuryIn1[Treasury1]
   treasuryIn2[Treasury2]
   treasuryIn3[Treasuries...]
   instantBuy[Contract]
   buyerIn[Buyer Wallet]

   treasuryIn1 & treasuryIn2 & treasuryIn3 -->|Spend| tx
   instantBuy --> |NFT| tx
   buyerIn --> |ADA| tx

   tx["Transaction (Tx)"]

   tx --> |Provision| treasuryOut1 & treasuryOut2 & treasuryOut3
   tx --> |ADA| sellerOut
   tx --> |NFT| buyerOut

   treasuryOut1[Treasury1]
   treasuryOut2[Treasury2]
   treasuryOut3[Treasuries...]
   buyerOut[Buyer Wallet]
   sellerOut[Seller Wallet]

Cancel

The transaction is signed by stored payment credentials There is no other check for the spending contract

Improvements

Offer

Is very similar to instant buying instead of a final check to pay assets/ADA to the offerer’s address.

Information about price, royalty etc., are stored in datum

flowchart LR
   treasuryIn1[Treasury1]
   treasuryIn2[Treasury2]
   treasuryIn3[Treasuries...]
   offer[Contract]
   sellerIn[Seller Wallet]

   treasuryIn1 & treasuryIn2 & treasuryIn3 -->|Spend| tx
   offer --> |ADA| tx
   sellerIn --> |NFT| tx

   tx["Transaction (Tx)"]

   tx --> |Provision| treasuryOut1 & treasuryOut2 & treasuryOut3
   tx --> |ADA| sellerOut
   tx --> |NFT| buyerOut

   treasuryOut1[Treasury1]
   treasuryOut2[Treasury2]
   treasuryOut3[Treasuries...]
   buyerOut[Buyer Wallet]
   sellerOut[Seller Wallet]

Possible vectors of attack

Treasury blocking - Low-impact

Attackers can block happy path usage of the contract by using treasury outside of the contract. It is a problem, because a user cannot use predefined treasuries, what is unaccessible

Treasury with datum by hash - Off-Chain

An attacker can create treasuries with datum, that is not Inline or with less ADA than is allowed, this treasury SHOULD NOT be listed

Building

A standard build is very simple by command:

aiken build

or if you want to keep traces:

aiken build -k

Off-Chain

There is a library for off-chain manipulation:

Testing

You can write tests in any module using the test keyword. For example:

test foo() {
  1 + 1 == 2
}

To run all tests, simply do:

aiken check

To run only tests matching the string foo, do:

aiken check -m foo

Resources

Find more on Aiken’s user manual.

Search Document