The Direct Offer project provides a Plutarch-based implementation of a smart contract enabling peer-to-peer trading, in a trustless manner, for the Cardano blockchain. Without the need for a trusted third party or a Decentralized Exchange (DEX), a user can put up any Cardano native asset(s) for sale in exchange for any user-specified native asset(s).
This project is funded by the Cardano Treasury in Catalyst Fund 10
P2P trading in the context of this project refers to the direct buying and selling of Cardano Native Tokens (both Fungible & Non-Fungible Tokens) among users, without a third party or an intermediary. This is unlike buying and selling digital assets using a Centralized Exchange (CEX), where you cannot transact directly with counterparties or a DEX where you trade against a fixed Liquidity Pool.
Trading on a CEX requires you to give custody of your tokens to them, so they can execute the trades you enter based on their charts and market order aggregators. A CEX provides access to their order book and facilitates trades and takes fees in exchange.
Depending on the type of order you use, effects such as slippage may mean you don’t get the exact price you want. P2P trading, on the other hand, gives you full control over pricing, settlement time, and whom you choose to sell to and buy from. What is even better you don't need to give custody of your assets to a centralized entity, they are locked in a contract from which you can reclaim them up until the point they are bought.
This project fulfills the cornerstone requirement of a trusted Escrow, over seeing the trade in the form of a smart contract. It locks the seller's assets in the contract until a buyer provides the required ask price or the seller wishes to cancel the offer and claim the funds back.
A seller sends the assets he wishes to exchange, to the contract, thus locking the funds therein. This UTxO contains a datum which specifies the "creator" of the offer and the assets wished in return, "toBuy" indicates the price.
data PDirectOfferDatum (s :: S)
= PDirectOfferDatum
( Term
s
( PDataRecord
'[ "creator" ':= PAddress
, "toBuy" ':= PValue 'Sorted 'Positive
]
)
)
A buyer interested in the offered assets initiates a transaction (with a "PExecuteOffer" redeemer) spending the locked UTxO, along with sending the "toBuy" assets to the "creator". This condition is validated by the contract first before allowing the transaction to proceed further. However, if the seller decides to cancel the offer, he can do so by initiating a transaction (with a "PReclaim" redeemer) and claim all the locked assets back.
data PDirectOfferRedeemer (s :: S)
= PExecuteOffer (Term s (PDataRecord '[]))
| PReclaim (Term s (PDataRecord '[]))
One UTxO at the smart contract address translates to one sell order.
A single transaction can fulfill multiple sell orders by matching it with valid buy orders. Whether all the buy orders in the tx are correct or not is validated only once at the tx level using the Zero ADA Withdrawal Trick from the Staking validator.
directOfferGlobalLogic :: Term s PStakeValidator
This Staking validator's credential is used as a parameter to a Spending Validator (the smart contract which locks the seller UTxOs). Spending validator ensures that the Staking validator is executed in the tx. A successful validation from both spending and staking validator is essentail for the spending of seller UTxOs.
directOfferValidator :: Term s (PStakingCredential :--> PValidator)
For carrying out this validation, the Staking Validator requires a redeemer containing one-to-one correlation between script input UTxOs (seller UTxOs) and buy output UTxOs (sent to seller from buyer). This is provided via ordered lists of input/output indices of inputs/ouputs present in the Script Context.
data PGlobalRedeemer (s :: S)
= PGlobalRedeemer
( Term
s
( PDataRecord
'[ "inputIdxs" ':= PBuiltinList (PAsData PInteger)
, "outputIdxs" ':= PBuiltinList (PAsData PInteger)
]
)
)
For e.g.
Inputs : [saleOfferA, saleOfferC, buyerInput3, saleOfferB, buyerInput1, buyerInput2]
Outputs : [buyOfferA, buyOfferB, buyOfferC, buyerOuput1, buyerOutput2, buyerOutput3]
InputIdxs : [0, 1, 3]
OutputIdxs : [0, 2, 1]
While its easy to understand and declare indices of outputs (the order in which outputs appear in the tx builder), we cannot control the order of inputs as seen by the script. As inputs are sorted lexicographically based on their output reference, first by Tx#Id and then by Tx#Idx.
Note: Staking validator is not needed to be invoked if the seller wishes to cancel the offer and reclaim his funds.
Before you begin, ensure you have Nix installed on your system. Nix is used for package management and to provide a consistent development environment. If you don't have Nix installed, you can do so by running the following command:
sh <(curl -L https://nixos.org/nix/install) --daemon
curl --proto '=https' --tlsv1.2 -sSf -L https://install.determinate.systems/nix | sh -s -- install
Make sure to enable Nix Flakes by editing either ~/.config/nix/nix.conf
or /etc/nix/nix.conf
on
your machine and add the following configuration entries:
experimental-features = nix-command flakes ca-derivations
allow-import-from-derivation = true
Optionally, to improve build speed, it is possible to set up binary caches by adding additional configuration entries:
substituters = https://cache.nixos.org https://cache.iog.io https://cache.zw3rk.com
trusted-public-keys = cache.nixos.org-1:6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcDShjY= hydra.iohk.io:f/Ea+s+dFdN+3Y/G+FDgSq+a5NEWhJGzdjvKNGv0/EQ= loony-tools:pr9m4BkM/5/eSTZlkQyRt57Jz7OMBxNSUiMC4FkcNfk=
Once Nix is installed, you should be able to seamlessly use the repository to develop, build and run packages.
Download the Git repository:
git clone https://github.com/Anastasia-Labs/direct-offer.git
Navigate to the repository directory:
cd direct-offer
Activate the development environment with Nix:
nix develop --accept-flake-config
Additionally, when you run nix run .#help
you'll get a list of scripts you can run, the Github CI (nix flake check) is setup in a way where it checks the project builds successfully, haskell format is done correctly, and commit message follows conventional commits. Before pushing you should run cabal run
, nix run .#haskellFormat
(automatically formats all haskell files, including cabal), if you want to commit a correct format message you can run cz commit
Build:
cabal build
Execute the test suite:
cabal test
© 2023 Anastasia Labs.
All code is licensed under MIT License. See LICENSE file for details.