Crowdfunding Smart Contracts on Sui Blockchain // Move
A Simple Guide to Supra’s Oracle Price Feed Integration with Move
Last updated
A Simple Guide to Supra’s Oracle Price Feed Integration with Move
Last updated
Welcome to our step-by-step tutorial on creating a crowdfunding smart contract using Supra’s price feed Oracle with the Move programming language on the Sui blockchain. This guide will demonstrate the simplicity of integrating Supra's Oracle into any smart contract, and by the end of this tutorial, you'll be fully equipped to incorporate Supra’s price feed Oracle into your own projects. So, let's jump right in!
Click for a video tutorial of this guide.
Before we dive into the intricacies of our crowdfunding smart contract, it's essential to lay the groundwork correctly. The first step in this process is to set up your files and directories.
In this structure:
crowd_fund/ is the root directory of our project.
The sources/ directory within crowd_fund/ houses your Move source files. In our case, it contains fund_contract.move - our crowdfunding smart contract.
The move.toml file is located in the root of our crowd_fund/ directory. This file is essential for managing your project's dependencies and addresses.
Now that you've correctly set up your project structure, we're ready to proceed to the next step - configuring your move.toml file. This is an integral step as it enables you to manage dependencies and addresses for your crowdfunding smart contract. Let's go ahead and get that set up now!
Before we start writing any code, it's essential to set up our environment correctly. One of the crucial steps in this process is configuring the move.toml file. The move.toml file is like a backstage manager for your project—it organizes and directs different parts of your code so that everything runs smoothly. Let's take a look at what goes into it:
The [package] section is where we declare the package name (in our case, "crowd_fund") and the version.
In the [dependencies] section, we define the external packages our project relies on. For our crowdfunding contract, we need to pull in two dependencies:
Sui: This is 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.
SupraOracle: This is the SupraOracles 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.
Finally, the [addresses] section is where we define addresses for modules in our package. This is essential for deploying your contract to the Sui Blockchain. Here we have two addresses:
crowd_fund: The address where our crowdfunding contract will reside. It's set to 0x0, the typical address for main modules in Move.
sui: This address corresponds to the Sui blockchain's standard library. It's typically set to 0x2.
Now that we have our move.toml file set up, we're ready to dive into the exciting world of crowdfunding smart contracts on the Sui blockchain. Up next, we'll delve into the code of our contract to understand how it all works.
Our code begins with the necessary imports that allow us to utilize various functions and structures within the Sui blockchain.
The modules we import include essential Sui modules for handling objects, transactions, balances, coins, and events, as well as the price feed Oracle for handling data feeds.
Having set up the project structure and dependencies, let's now turn our attention to the code itself. The first block of our code establishes a crucial error constant.
This constant ENotFundOwner
signifies an error state when a user who is not the fund owner tries to perform an operation that only the fund owner should be able to perform (like withdrawing funds). The error is represented by a u64
integer, which in this case, is set to 0.
Remember, it's good practice to define your error constants at the beginning of your smart contract. This makes it easier to maintain and understand your code.
In this section, we define the objects and events that our smart contract will use. Let's break it down:
This is the central data structure of our contract. A Fund struct has three fields:
id:
a UID (unique identifier) that uniquely identifies a crowdfunding campaign.
target:
the fundraising target in USD.
raised:
the amount of money that has been raised so far, expressed as a Balance of SUI tokens.
This struct represents a receipt that gets generated whenever a donation is made. It has two fields:
id:
a UID that uniquely identifies a receipt.
amount_donated:
the amount of SUI tokens that were donated.
This struct represents a special capability granted to the fund creator, which allows them to withdraw funds from the specific crowdfunding campaign that they created. It has two fields:
id:
a UID that uniquely identifies this capability.
fund_id:
an ID that identifies the specific crowdfunding campaign that this capability pertains to.
This event is emitted when a crowdfunding campaign has reached its target. It has a single field, raised_amount_sui
, which indicates the total amount of funds raised (in SUI tokens) at the time the target was reached.
By defining these objects and events, our smart contract has the building blocks it needs to operate. Now, let's move on to our functions and how they interact with these building blocks.
In this section, we define the functions that perform operations in our smart contract. These functions are responsible for creating crowdfunding campaigns, making donations, and withdrawing funds.
create_fund
functionThis function is the starting point of our crowdfunding contract. It's responsible for the creation of a new fund.
The create_fund
function takes two parameters: the target
amount for the crowdfunding campaign in USD (type u64) and a mutable reference to the transaction context ctx
(type TxContext).
The function starts by creating a unique identifier for the fund using the object::new
function, which is then converted to an ID type. The Fund struct is then instantiated with this ID, the target amount, and an initial raised balance of zero.
Next, the function creates a FundOwnerCap
object, giving the creator of the fund (who is identified by the tx_context::sender(ctx)
) the capability to withdraw funds once the target is met. The FundOwnerCap is then transferred to the fund creator.
Finally, the fund is made public by using the transfer::share_object(fund)
function, allowing anyone to donate to this fund. This is a significant aspect of any crowdfunding platform: it should allow anyone to contribute to a fund, hence the need for making the Fund object publicly accessible.
Now, anyone with a reference to this Fund can call the donate
function and contribute to the campaign. In the next section, we'll take a closer look at the donate function.
donate
FunctionHere's where we see the magic of Supra's price feed Oracle in action! This function allows someone to donate a certain amount of SUI tokens to the fund. This function accepts four parameters: a reference to the OracleHolder (which fetches the current price of SUI in USD), a mutable reference to the Fund object (which receives the donation), the amount to be donated, and a mutable reference to the transaction context ctx.
First, the function captures the amount_donated
in SUI by calling coin::value(&amount)
. This will be used to create a receipt for the donation.
Next, the donation amount is added to the total raised balance of the fund using balance::join
. The coin::into_balance
function is used to convert the Coin type into Balance.
Then, the function uses the get_price
function to fetch the current price of SUI in USD. The get_price
function returns four values (value, decimal, timestamp, round), but we're only interested in the value, so we disregard the rest with (_, _, _, _). The 90 passed into get_price
is the price feed ID for the SUI/USDT pair.
The function then calculates the total amount raised so far in SUI and the target amount for the fund in USD.
If the total amount raised in SUI (converted to USD using the price feed) meets or exceeds the target amount in USD, an event TargetReached
is emitted. This will be broadcast to all nodes in the network, signaling that the crowdfunding goal has been met.
Finally, a Receipt object is created to keep a record of the donation. This receipt, represented as an NFT, is sent to the donor for record-keeping (and perhaps for tax purposes as well).
withdraw_funds
functionThe withdraw_funds
function allows the owner of a fund to withdraw funds from it. This function accepts three parameters: a reference to a FundOwnerCap capability (which authorizes the withdrawal), a mutable reference to the Fund object from which the funds will be withdrawn, and a mutable reference to the transaction context ctx.
First, an assert statement checks that the fund ID associated with the FundOwnerCap capability matches the fund ID of the fund from which the funds are to be withdrawn. If not, it raises an error ENotFundOwner
, halting the execution of the function.
Next, the function calculates the total balance amount of the fund by calling balance::value(&fund.raised)
.
Then, the function calls coin::take
to withdraw the total balance from the fund and convert it back into Coin form. The coin::take
function mutates the fund's balance, subtracting the withdrawn amount from it.
Finally, the function calls transfer::public_transfer
to transfer the withdrawn funds to the fund owner, i.e., the sender of the transaction. This completes the withdrawal process. The fund's balance is reduced by the withdrawn amount, and the owner's balance is increased by the same amount.
This withdraw_funds
function showcases the power of the Sui blockchain's capability-based security model. Only an entity with the right capability can withdraw funds, ensuring secure fund management. Also, with the Move language's resource semantics, funds can't be duplicated or destroyed arbitrarily, ensuring the integrity of the financial transactions.
This tutorial aimed to provide a comprehensive yet easily digestible guide to building a crowdfunding smart contract using Supra’s price feed Oracle in Move on the Sui blockchain. We dissected each part of the contract, from setting up your development environment to defining structures and implementing crucial functionalities.
We also emphasized the simplicity and robustness of integrating Supra’s price feeds into your blockchain applications.
As a developer, understanding and utilizing these concepts not only provides you with the tools to build advanced, secure, and efficient applications but also helps you leverage the powerful features of blockchain technology to its fullest potential. We hope that this guide serves as a solid foundation for your journey into blockchain development and inspires you to explore further and create innovative solutions.
Here is the complete code for the crowdfunding smart contract written in Move for the Sui blockchain, utilizing Supra's price feed Oracle.
\