Swap contract
The swap contract is an interactive example, written in Python, that integrates data feed from a Charli3's oracle. The contract support trade operations, liquidity and minting operations.
Introduction
After the Vasil upgrade, Charli3's developer team faced the challenge of transitioning from the previous wallet architecture, written in Haskell, to a new architecture that fully supports Vasil's features. 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 them 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 UTXO 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 functions, highlighting the differences between the code written in Haskell and Python.
Swap-contract code
The swap contract supports four primary operations:
The "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.
"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.
"Swap A" transaction allows the exchange of asset A from the user's wallet to the swap's UTXO in exchange for asset B.
"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.
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. To use these services, a Blockfrost account and token ID is required for interacting with the Cardano blockchain.
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 the token name variable with the quantity of tokens to mint.
We proceed to invoke the mint function to automatically generate the UTXO containing the custom 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.
Swap Use
Now, we will delve into the implementation of the "swap A" and "swap B" transactions within the Python code. It is assumed that the creation and filling of liquidity for the swap UTXO have already been accomplished.
Contract's arguments
To begin, we need to specify the address of the oracle contract and its UTXO feed NFT identifier, the address of the swap contract and its UTXO NFT identifier, the address of the user's wallet, and the details of the assets being traded. As in this example, we don't need to provide tADA information as it does not contain a policy ID or asset name. It is assumed that a valid oracle contract already exists on the blockchain and you are able to use that one, or that you can deploy your own for testing purposes if using a private testnet.
The Pycardano library supports mnemonic wallets. In this example, we utilize a 24-word Shelley-compatible wallet to sign transactions by restoring an existing wallet via the wallet recovery phase.
Reading oracle feed
The oracle feed data is specified in a standard format (CIP), created using a dedicated Haskell library. We use the library's generic data type to create the inline oracle data. Anyone can access the information by reading the UTXO containing this data as a reference UTXO.
The Haskell code snippet reads the datum and retrieves the integer value which serves as the exchange price. It is worth noting that the on-chain code verifies that the oracle feed UTXO is the sole input reference UTXO.
The python library has built-in functions that simplify the search and retrieval of the data feed. We only need to provide the oracle's address to search for available UTXOs, then search for the UTXO that holds the oracle fee NFT. Finally, the get_price()
function will retrieve the integer value from the datum list structure.
SwapB transaction (tADA for tUSDT)
The swapB transaction exchanges a specified amount of tADA for tUSD at the exchange rate provided by an oracle. To handle decimal precision, a variable called coin precision
is used, which is set as a multiple of 1, for example 1,000,000. This allows for evaluating the exact decimal precision when working with integers, for example 2400000 with a coin precision of 1000000 is evaluated as 2.4.
SwapA transaction (tUSDT for tADA)
The SwapB transaction is exactly the opposite operation of SwapA
Transaction submission of a swap operation
We will provide detailed instructions on how to submit a swapB transaction, which is similar to the process for submitting a swapA transaction. Before continuing, we recommend reviewing the Pycardano documentation on transactions.
First, we need to define a class SwapContract
that receives the contract's arguments
The swap class stores information about assets. We do not need to add information for the tADA asset as its fields contain empty values.
We then query and process the necessary information to construct the transaction.
We create two UTXOs using the processed information. The first UTXO goes to the swap address, it contains the amount of tADA
specified by the user, and a decreased quantity of tUSDT
Fourth, the second UTXO goes to the user's wallet address. This UTXO pays the user the tUSDT
earned from the swap operation.
Finally, we gather the information from the previous steps in the Pycardano builder, in this way we construct the swapB transaction. Finally, we sign and send it to the blockchain.
Command line interface
The python swap contract has a command line interface to easily submit transactions. To access it, first navigate to the src
directory. Then, run the command python main.py -h
to display helpful information on how to use the command line options. This command line interface is built using the Argparse python library.
The command line interface supports four different commands, each with its own options.
For example, you can change tADA
asset for tUSDT
using the command: python main.py trade tADA --amount N
.
Another useful command is python main.py swap-contract --liquidity
which allows to verify the swap contract liquidity. And for query the contract's address python main.py swap-contract --address
.
Additionally, you can also retrieve information from the oracle feed by using the command python main.py oracle-contract --feed.
When executing the start swap function, it's important to remember to replace the token variable 'asset_name
' in the 'mint.py
' file. Keep in mind that each NFT must be unique for each new UTXO, and the policy id cannot be changed via python code. After the execution, update the values at the 'swap_nft
' variable in the 'main.py
' file.
Last updated