Building a Crypto Index Fund Smart Contract on Sui Blockchain // Move
A Comprehensive Guide to Utilizing Supra's Oracle Price Feed Integration with Move
Last updated
A Comprehensive Guide to Utilizing Supra's Oracle Price Feed Integration with Move
Last updated
Welcome to our comprehensive guide on building a crypto index fund smart contract using Supra's price feed Oracle with the Move programming language on the Sui Blockchain. In this guide, we will walk you through the process of creating an index fund that tracks the value of a basket of crypto assets with equal weighting.
We’ll start by demonstrating how to integrate Supra's Oracle with your dApp to retrieve real-time prices for the crypto assets and calculate investment proportions. By the end of this tutorial, you will have a solid understanding of how to incorporate Supra's Oracle into your own projects.
Let's dive in!
Click for a video tutorial of this guide.
Before we delve into the details, it's essential to establish the proper groundwork. The first step in this process is to organize and structure your project files and directories.
In the crypto_index_fund/ directory:
The sources/ directory contains the index_fund.move file, which houses our index fund smart contract implementation.
The move.toml file is located in the root of the crypto_index_fund/ directory. This file plays a vital role in managing project dependencies and addresses.
With the project structure properly set up, we can now proceed to the next step: configuring the move.toml file. This step is crucial as it allows you to manage dependencies and addresses for your index fund smart contract. Let's move forward and set up the move.toml file accordingly.
Now that we have our project structure in place, let's move on to the next step: configuring the move.toml file. This file plays a crucial role in managing dependencies and addresses for our index fund smart contract.
Here is the content of the move.toml file:
Let's break down the different sections of the move.toml file:
[package]: This section defines the package information, including the name and version of our project.
[dependencies]: Here, we specify the dependencies required for our project. In this case, we have two dependencies: Sui and SupraOracle. The Sui dependency is sourced from the Sui Blockchain's standard library. We are linking to the MystenLabs repository on GitHub, specifically the sui-framework package from the devnet-v1.2.0 revision. The SupraOracle dependency is a local package that houses the SValueFeed framework. Notice that we're referencing it locally, which means you'll need to have it on your machine in the relative path specified ("../supra-svalue-feed-framework"). You can refer to our documentation for guidance on implementing this.
[addresses]: This section allows us to define addresses associated with our smart contract. In our case, we have set the address for crypto_index_fund to 0x0, the typical address for main modules in Move, and the address for sui, which is typically set to 0x2.
By properly configuring the move.toml file, we ensure that our project has the necessary dependencies and addresses set up correctly.
In the next step, we will start diving into the implementation details of our smart contract by exploring the index_fund.move file located in the sources/ directory. Let's proceed with the exciting part of writing our smart contract code!
Our code begins with the necessary imports that allow us to utilize various functions and structures within the Sui blockchain.
These imports provide access to features such as object manipulation, asset transfers, transaction context, coin handling, balance management, data structures, and Supra's Oracle functions for price data retrieval. With these imports, we have a solid foundation to begin implementing our index fund smart contract on the Sui Blockchain.
In this step, we define the indices of the crypto assets that we will query the oracle for. The indices provided are for popular assets like Bitcoin (BTC), Ethereum (ETH), XRP, Cardano (ADA), and Polygon (MATIC). However, you are free to customize these indices to include your preferred assets, such as Layer-1 protocols, gaming tokens, DeFi tokens, and more. You can check out all of our index pairs in our documentation. By adjusting these indices, you can tailor the index fund to match your investment preferences.
In this step, we define two essential structs for our index fund: IndexFundToken and IndexFund.
The IndexFundToken struct represents a token that tracks the value of a basket of crypto assets with equal weighting. It consists of two fields: id of type UID, which uniquely identifies the token, and crypto_assets of type Table<u32, u128>, which stores the amounts of respective assets held by the token. The u32 values in the table represent the asset indices, while the u128 values represent the corresponding asset amounts.
The IndexFund struct manages the balance and asset pairs for the index fund. It has three fields: id of type UID, which uniquely identifies the fund, balance of type Balance<SUI>, which stores the total amount of SUI deposited into the fund, and pairs of type vector<u32>, which represents the crypto assets that the fund will invest in. You can customize the asset pairs based on your preferences.
These structs lay the foundation for managing the index fund and tracking the values of different assets within it. Let's move on to the next step, where we'll initialize the fund and set up its initial state.
In this step, we define the init function, which runs once when the package is published. Its purpose is to set up the initial state of the index fund.
Inside the init function, we create a vector called pairs to store the indices of the crypto pairs that we want to query the oracle for.
Next, we initialize the fund balance to 0 by creating an IndexFund instance named index_fund. This instance has three fields: id, which is assigned the value of object::new(ctx) to generate a unique identifier, balance, which is set to balance::zero() to initialize it as zero, and pairs, which is assigned the pairs vector we defined earlier.
Finally, we use the transfer::share_object function to share the index_fund object, making it accessible for other functions to interact with.
By running the init function, we set up the initial state of the index fund, including the crypto pairs to query the oracle for and initializing the fund balance to 0. In the next step, we'll explore the function for depositing an investment into the index fund.
In this step, we'll explore the deposit_investment function, which allows users to deposit SUI and mint a new IndexFundToken.
The function takes several parameters: oracle_holder, index_fund, deposit_amount, and ctx.
First, we convert the deposit_amount from a coin into a balance by using coin::into_balance, assigning it to deposit_balance_sui. Then, we convert the deposit_amount_sui from the balance into a u128 value for mathematical operations.
Next, we add the deposit_balance_sui to the fund balance by using balance::join and passing in the mutable reference to index_fund.balance.
We query the oracle for the SUI/USD price by calling get_price with oracle_holder and 90 as parameters. We store the result in sui_usd_price and adjust it to 9 decimal places by calling our helper function convert_to_9_decimal_places and assigning the result to adjusted_sui_usd_price.
To calculate the deposit amount in USD, we multiply adjusted_sui_usd_price by deposit_amount_sui, storing the result in deposit_amount_usd.
Next, we divide deposit_amount_usd by 5 to determine the investment amount per crypto asset, as they are equally weighted. The result is stored in investment_amount_per_crypto.
We query the oracle for all five crypto assets in the fund by calling get_crypto_prices with oracle_holder and index_fund.pairs as parameters. The result is stored in price_holder, a VecMap that maps crypto asset indices to their respective prices.
We calculate the investment proportions for each crypto asset by dividing investment_amount_per_crypto by the price of each crypto asset in USD, retrieved from price_holder. The results are stored in btc, eth, xrp, ada, and matic.
To represent the crypto assets and their respective amounts, we create a crypto_assets table using table::new<u32, u128>(ctx). We then add the crypto assets and their amounts to the table using table::add.
Finally, we mint a new IndexFundToken by creating an instance of the IndexFundToken struct. We assign a new object identifier to id using object::new(ctx), and set crypto_assets to the crypto_assets table we created. The minted token is transferred to the sender using transfer::public_transfer.
By calling the deposit_investment function, users can deposit SUI and mint a new IndexFundToken that represents their investment in the index fund. In the next step, we'll explore the function for withdrawing an investment and burning the corresponding IndexFundToken.
In this step, we'll explore the withdraw_investment function, which allows users to withdraw their investment and burn the corresponding IndexFundToken.
The function takes several parameters: oracle_holder, index_fund, index_token, and ctx.
First, we query the oracle for all five crypto assets in the fund by calling get_crypto_prices with oracle_holder and index_fund.pairs as parameters. The result is stored in price_holder, a VecMap that maps crypto asset indices to their respective prices.
We calculate the total USD value of the IndexFundToken by multiplying the amount of each crypto asset in the index_token.crypto_assets table with their respective prices retrieved from price_holder. The results are stored in btc_usd_value, eth_usd_value, xrp_usd_value, ada_usd_value, and matic_usd_value. We then sum these values to obtain the total_usd_value.
Next, we query the oracle for the SUI/USD price by calling get_price with oracle_holder and 90 as parameters. We store the result in sui_usd_price and adjust it to 9 decimal places by calling convert_to_9_decimal_places and assigning the result to adjusted_sui_usd_price.
To convert the total USD value to SUI, we divide total_usd_value by adjusted_sui_usd_price and cast the result as a u64 to obtain the total_sui.
We subtract the total_sui from the fund balance by using balance::split and passing in the mutable reference to index_fund.balance. The resulting balance is assigned to index_token_balance, which represents the withdrawn amount.
To transfer the withdrawn value back to the sender, we convert index_token_balance into a Coin<SUI> using coin::from_balance and ctx, and assign it to total_coin_sui. Then, we use transfer::public_transfer to transfer total_coin_sui to the sender.
Lastly, we burn the IndexFundToken by destructuring index_token into id and crypto_assets. We drop the crypto_assets table using table::drop and delete the id object using object::delete.
By calling the withdraw_investment function, users can withdraw their investment, receive the corresponding SUI value, and burn the IndexFundToken. In the next step, we'll discuss a helper function that retrieves the prices of multiple crypto assets from the oracle.
In this step, we introduce a helper function called get_crypto_prices that retrieves the prices of multiple crypto assets from the oracle.
The function takes two parameters: oracle_holder, which is the oracle instance, and pairs, a vector of u32 values representing the indices of the crypto assets to query.
Inside the function, we call get_prices with oracle_holder and pairs as arguments, which returns a vector of Price objects representing the prices of the specified crypto assets. We assign this vector to crypto_prices.
We initialize an empty VecMap<u32, u128> called price_holder, which will store the crypto asset indices mapped to their adjusted prices.
Next, we determine the length of crypto_prices using vector::length and assign it to length. We also initialize an index variable, idx, to keep track of the current iteration.
We enter a while loop that iterates as long as idx is less than length. Within each iteration, we retrieve the Price object at the current index using vector::borrow and assign it to price.
Using destructuring, we extract the pair (crypto asset index), value (price value), and other unused variables from price.
To ensure consistency, we call the convert_to_9_decimal_places helper function to adjust the value to 9 decimal places and assign the result to adjusted_value.
Finally, we use vec_map::insert to insert the pair and adjusted_value into the price_holder map, mapping the crypto asset index to its adjusted price.
The get_crypto_prices function returns the price_holder map, which contains the adjusted prices of the requested crypto assets.
By utilizing the get_crypto_prices helper function, you can easily retrieve the prices of multiple crypto assets from the Oracle and incorporate them into your index fund calculations.
Congratulations! You've reached the end of our step-by-step tutorial on creating a crypto index fund smart contract using Supra's price feed Oracle with the Move programming language on the Sui blockchain. Throughout this guide, we've covered essential concepts such as setting up the project structure, importing required modules, initializing the index fund, depositing investments, withdrawing investments, and retrieving crypto prices from the oracle.
By following these steps, you now have a solid foundation for building your own index fund smart contracts and integrating Supra's Oracle for accurate price data. The Move programming language provides a secure and efficient environment for developing blockchain applications, and Supra's Oracle offers reliable and up-to-date price feed services.
Remember, this tutorial serves as a starting point, and you can customize and expand upon the provided code to suit your specific needs. Explore additional functionalities, add error handling, implement security measures, and consider integrating other modules to enhance your smart contract.
Happy coding and building on the Sui Blockchain with Supra's Oracle!
Here is the full code:
\
\
\
\