Charli3 Technical Documentation- Swap Contract: part 1

Charli3
5 min readFeb 27, 2023

Throughout our long development, technical documentation has always been front of mind for public review. Charli3 is in a unique position of being a business-to-business technical back-end service provider. This requires consistent updates and alterations in order to serve the front-facing projects of the Cardano ecosystem.

With Cardano infrastructure maturing, and our solution solidifying, we are proud to be able to begin sharing our technical documentation with the community and customers.

We thank PyCardano and Blockfrost for the beneficial tooling provided by their teams.

“It’s great to hear that PyCardano is proving to be a valuable tool for the Cardano ecosystem, and more specifically, the Charli3 team, in creating their oracles. We look forward to future project implementations using PyCardano and future collaboration” — JerryC (PyCardano)

Swap contract

The swap contract is an interactive example, written in Python, that integrates data feed from a Charli3’s oracle. The contract supports trade operations, liquidity, and minting operations.

Introduction

After the Vasil upgrade, Charli3’s developer team faced the challenges of transitioning from the previous wallet architecture, written in Haskell, to a new architecture that fully supports the Vasil Hardfork features, CIPs 31, 32, 33. A key aspect of this process was creating an oracle data feed as a reference input for various transactions. To accomplish this, the team chose to rewrite a Haskell contract that uses reference inputs with the help of Pycardano, a Python library. This allowed us to test the library’s capabilities and create a comprehensive tutorial on how contracts can read information from Charli3’s oracles. Through this guide, we will read the oracle feed by utilizing reference UTXOs and demonstrate how to create transactions using Pycardano.

Python and Haskell

Before delving into the code, it’s important to note that while it is possible to rewrite the off-chain portion of smart contracts in various programming languages, it is only possible to write the on-chain code in Haskell. With that in mind, in this guide, we will describe the smart contract’s primary functionality, highlighting the differences between the code written in Haskell and Python.

Swap-contract code

The swap contract supports four primary operations:

  1. “Run swap” transaction initiates the creation of a UTXO at the contract address, which contains a minted NFT. This serves as an identifier for the UTXO that will hold two assets.
  2. “Add liquidity” transaction enables the addition of specific amounts of tokens to the swap’s UTXO. These quantities must be present in the wallet of the swap’s creator.
  3. “Swap A” transaction allows the exchange of asset A from the user’s wallet to the swap’s UTXO in exchange for asset B.
  4. “Swap B” transaction enables the exchange of asset B from the user’s wallet to the swap’s UTXO in exchange for asset A.

The on-chain component of the swap contract verifies that the input tokens from the user’s wallet, when multiplied by the oracle feed, result in the deterministic addition or removal of assets from the swap contract’s UTXO and give or remove tokens from the user’s wallet. Additionally, the contract ensures that the adding liquidity transaction is always an incremental operation.

Swap’s Validator

  1. {- The validator argument holds details regarding the assets to be traded, the datum is the unit type, the redeemer denotes the number of assets to be exchanged by the user, and the context provides information about the transaction.
  2. -}
  3. {-# INLINABLE mkSwapValidator #-}
  4. mkSwapValidator
  5. :: Swap
  6. -> ()
  7. -> SwapRedeemer
  8. -> ScriptContext
  9. -> Bool
  10. mkSwapValidator Swap{..} _ (SwapA amountA) ctx =
  11. mkSwapXValidator checkExchangeA coinB oracle amountA ctx
  12. mkSwapValidator Swap{..} _ (SwapB amountB) ctx =
  13. mkSwapXValidator checkExchangeB coinA oracle amountB ctx
  14. mkSwapValidator swap _ AddLiquidity ctx =
  15. mkAddLiqValidator swap ctx

The swap contract validator generates a unique address by using an oracle’s information and the time of the initial transaction. The initial transaction, Run-swap, determines the contract’s address and creates a UTXO with a minted SWAP NFT, used as pool liquidity.

The Pycardano library simplifies the setup of a Cardano development environment by incorporating Blockfrost’s services. a Blockfrost account and token ID are required for interacting with the Cardano blockchain to use these services

Enivronment’s settings

  1. BLOCKFROST_PROJECT_ID = “BLOCKFROST_API_PROJECT_ID”
  2. BLOCKFROST_BASE_URL = “https://cardano-preprod.blockfrost.io/api"

Start swap

The “start operation” created a unique digital asset, called SWAP (NFT), at a specific contract address on the blockchain. We have established a pre-determined set of rules for the minting policy and use a custom variable as the token name for each new NFT we create. After the UTXO that holds the NFT is generated, it must be filled with assets to serve as a liquidity pool. It’s important to note that the contract mechanism only utilizes one UTXO for all transactions.

Below are the token name variables with the quantity of tokens to mint.

  1. asset_name = “SWAP” #Token Name
  2. nft_swap = MultiAsset.from_primitive(
  3. {
  4. policy_id.payload: {
  5. bytes(asset_name, “utf-8”): 1, #Amount to mint
  6. }
  7. }
  8. )

We proceed to invoke the mint function to automatically generate the UTXO containing the custom NFT.

Mint class and function

  1. swap_utxo_nft = Mint(…) #Mint class (Hidden arguments)

2. swap_utxo_nft.mint_nft_with_script() #Creation of swap UTXO with NFT

Note: The example provided does not require the execution of the start swap transaction. Its purpose is to demonstrate to the reader how to mint assets using Pycardano. This function is executed automatically when a new swap address is created via on-chain code.

Add liquidity

The “add liquidity” operation transfers a specified amount of the defined assets, USDT and TADA, from the user’s wallets to the UTXO SWAP created earlier. This allows the contract to enable trades of assets from the pool UTXO with any user possessing any of the predetermined assets.

Note: To improve the code, you can create a separate wallet for the purpose of adding assets to the UTXO swap, and another separate wallet for trading with the swap contract. This way, the wallet that holds the assets being added to the swap is separate from the wallet that is executing the trade, providing a better separation of concerns and increasing security. Additionally, you can consider implementing other security measures such as multi-sig or threshold signature schemes to ensure that assets can only be added to the swap or traded by multiple parties with the proper authorization.

Add liquidity class and function

  1. swapInstance = SwapContract(…) #Swap contract class (Hidden args)

2. swapInstance.add_liquidity(amountA, amountB, …) #The user’s wallet associated must cover the asset amount expected by the liquidity function.

— Swap contract part 2 coming soon —

--

--