Swap contract Demo
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, the Charli3 developer team faced the challenge of transitioning from the previous Haskell-based wallet architecture to a new architecture that fully supports Vasil's features. One important aspect of this transition was the creation of an oracle data feed as a reference input for various transactions. To achieve this, the team decided to rewrite a Haskell contract that utilizes reference inputs, leveraging the Pycardano, Python library. This approach enabled them to explore the capabilities of the library and develop a comprehensive tutorial on reading information from Charli3's oracles using contracts.
In this guide, we will demonstrate how to access the oracle feed through reference UTXOs and illustrate the process of creating transactions using Pycardano.
Python and Haskell
Before we proceed with the code, it's worth noting that while the off-chain portion of smart contracts can be rewritten in different programming languages, the on-chain code can only be written in Haskell (although there are rising alternative available). Keeping that in mind, this guide will focus on explaining the main functions of the smart contract, emphasizing the distinctions between the Haskell and Python implementations of the code.
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 includes a minted NFT. This NFT serves as an identifier for the UTXO that will hold two assets.
The Add liquidity transaction allows the user to add specific amounts of tokens to the swap's UTXO. These token quantities must be available in the user's wallet.
The Swap A transaction facilitates the exchange of asset A from the customers' wallet to the swap's UTXO in return for asset B.
The Swap B transaction enables the exchange of asset B from the customers' wallet to the swap's UTXO in return for asset A.
The on-chain component of the swap contract validates that when multiplied by the oracle feed, the input tokens from the customers' wallet result in the deterministic addition or removal of assets from the swap contract's UTXO. It also adds or removes tokens from the user's wallet accordingly.
Furthermore, the contract guarantees that the "add liquidity" transaction is always an incremental operation.
The initial transaction, Run swap, establishes the contract's address and generates a UTXO that includes the minted SWAP NFT, serving as the pool liquidity. The Pycardano library simplifies the setup of a Cardano development environment by integrating Blockfrost's services. To utilize these services, you need a Blockfrost account and a token ID to interact with the Cardano blockchain.
Start swap
The Start operation creates a unique SWAP NFT at a contract address. Once the SWAP UTXO is generated, it needs to be filled with assets to serve as a liquidity pool. The contract operates with a single UTXO for all transactions.
See below for the token name variable and 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 showcase how to mint assets using Pycardano. In the actual implementation, this function is executed automatically when a new swap address is created through 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 SWAP UTXO created earlier. This allows the contract to enable trades of assets from the UTXO with any user possessing any of the predetermined assets.
Note: To enhance the code:
Use separate wallets for adding assets and trading with the swap contract, improving security and separation of concerns.
Implement additional security measures like multi-signature or threshold signature schemes to ensure authorized participation in adding assets or trading.
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, you need to provide the following information:
Oracle contract address and its UTXO feed NFT identifier.
Swap contract address and its UTXO NFT identifier.
User's wallet address.
Details of the assets being traded.
Note that in this example, tADA information is not required as it doesn't contain a policy ID or asset name.
It is assumed that a valid oracle contract already exists on the blockchain, and you can use it for testing purposes. If using a private test-net, you have the option to deploy your own oracle contract.
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 follows a standard format created with a Haskell library. Using the library's generic data type, we generate the inline oracle data. Accessing the information is as simple as reading the UTXO that holds this data as a reference UTXO.
The following on-chain Haskell code snippet demonstrates how to read the datum and extract the integer value representing the exchange price. Additionally, in this example we ensure that the oracle feed UTXO is the only input reference UTXO.
The Pycardano library offers convenient built-in functions for searching and retrieving the oracle feed. By providing the oracle's address, we can search for available UTXOs and locate the one that contains the oracle fee NFT (OracleFeed
). From there, the get_price()
function can extract the integer value from the datum list structure.
Let's take a closer look to the code:
Here's a breakdown of what each part does:
oracle_feed_utxo
: This is an object that represents a UTXO of the transaction that contains the oracle feed.oracle_feed_utxo.output
: This is a property of theoracle_feed_utxo
object that represents the output of the UTXO. We can access diferente pieces of information like address, amount, datum_hash, datum, script, etc. More information in pycardano's documentation.oracle_feed_utxo.output.datum
: This is a property of the output that represents the CBOR-encoded datum associated with the output.oracle_feed_utxo.output.datum.cbor
: This is the binary CBOR-encoded datum that is being parsed.GenericData.from_cbor
: This is a static method of theGenericData
class that takes in a CBOR-encoded byte string and returns aGenericData
object. TheGenericData
object is a custom data type that is defined in the datum.py file, see the code below.oracle_inline_datum: GenericData
: This is a variable declaration that creates a new variable calledoracle_inline_datum
of typeGenericData.
This process is known as downcasting.= GenericData.from_cbor(oracle_feed_utxo.output.datum.cbor)
: This sets the value oforacle_inline_datum
to the result of calling thefrom_cbor
method of theGenericData
class with the CBOR-encoded datum as the argument.
In summary, this code extracts the CBOR-encoded datum from the oracle feed UTXO, parses it into a GenericData object using a static method, and assigns the result to the variable oracle_inline_datum
.
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, such as 1,000,000. This enables accurate evaluation of decimal precision when working with integers. For example, if we have 2,400,000 with a coin_precision of 1,000,000, it would be evaluated as 2.4.
SwapA transaction (tUSDT for tADA)
The SwapA transaction is exactly the opposite operation of SwapB
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.
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