Creating A decentralized Crowdfund Contract on SUI


Hey, diving right in as the title explains it all, but first things first you need to have an understanding of Sui move a and also having your sui move client installed in your laptop
to go right into the code, you can visit the github repo : SUI CROWDFUND
You are already leaving!, A simple thank you will do?
The Smart Contract
This section we will majorly look at the move code.
starting with the move code
sui move new crowdfund
this will create a crowdfund directory within the file path you ran the command on your terminal.
you should have a directory named crowdfund with the file structure as seen below
If this is what you have, then you’re on the right track, now open the directory on vs code, navigate to your sources directory and open crowdfund.move file.
starting off with the needed imports and error(s):
module crowdfund::crowdfund;
use std::string::String;
use sui::clock::{Clock};
use sui::coin::{Self, Coin};
use sui::sui::SUI;
use sui::balance::{Self, Balance};
// Error constants
const EINVALID: u64 = 0;
The first import is a data type for accommodating String like data.
The second import is an is a built in object provided by sui that can be used to get the current timestamp and Epoch of a published package
The third import is also another built in object that is use to accommodate tokens whether existing or created Tokens
the forth import is another built object that specifies the sui token of the Sui ecosystem
the fifth import is used to get the balance of token in a object or an account
Moving into the structs
module crowdfund::crowdfund;
use std::string::String;
use sui::clock::{Clock};
use sui::coin::{Self, Coin};
use sui::sui::SUI;
use sui::balance::{Self, Balance};
// Error constants
const INVALID: u64 = 0;
// Struct to track contributors
public struct Contributor has store, drop, copy {
wallet_address: address,
amount: u64,
timestamp: u64,
}
// Struct to manage all crowdfunds
public struct CrowdfundManager has key {
id: UID,
crowdfunds: vector<address>, // Store all crowdfund addresses
}
// Main crowdfund struct (You can switch it up to what you want)
public struct Crowdfund has key {
id: UID,
name: String,
description: String,
creator: address,
crowdfund_type: u8, // 0 for grant, 1 for ICO
target_amount: u64,
current_amount: u64,
deadline: u64,
contributors: vector<Contributor>,
funds: Balance<SUI>,
is_active: bool,
is_successful: bool
}
The Contributor struct, just as the name implies contains details of accounts that contributes to a running crowdfund
The CrowdfundManager struct is like a table that stores all created Crowdfund
The Crowdfund struct contains the details of a Crowdfund
See how simple it is? So mindful, So demure :)
Next, we add the initialization function
// Initialize the crowdfunding platform
fun init(ctx: &mut TxContext) {
let manager = CrowdfundManager {
id: object::new(ctx),
crowdfunds: vector::empty<address>(),
};
transfer::share_object(manager)
}
Basically, we are initialing the CrowdfundManger’s crowdfunds vector to empty to enable us add created crowdfund to the CrowdfundManager. after which The CrowdfundManager object is publicly shared (So anyone can have access to the CrowdfundManager
Moving into the different function
Firstly, we will be looking into Create Crowdfund function
public fun create_crowdfund(
manager: &mut CrowdfundManager,
name: String,
description: String,
crowdfund_type: u8,
target_amount: u64,
deadline: u64,
ctx: &mut TxContext,
) {
let sender = tx_context::sender(ctx);
let crowdfund_property = Crowdfund {
id: object::new(ctx),
name,
description,
creator: sender,
crowdfund_type,
target_amount,
current_amount: 0,
deadline,
contributors: vector::empty<Contributor>(),
funds: balance::zero(),
is_active: true,
is_successful: false,
};
let property_id = object::id_address(&crowdfund_property);
vector::push_back(&mut manager.crowdfunds, property_id);
transfer::share_object(crowdfund_property);
}
Breakdown of what’s going on… This function basically create a crowdfund for contributors to contribute to… notice most of the argument being passed in… I will explain key things here:
manager: This is the previously shared CrowfundManger Object that anyone on the sui blockchain can have access too. One key thing to notice is that it is a mutable reference because we are going to update the vector structure in the CrowfundManger object when we append the newly created Crowdfund.
sender: this is gotten from the transaction context… so when the function is called. the transaction context has certain properties of that particular function call/transaction and that includes the sender or caller and the sender becomes the creator/owner of the soon to be created Crowdfund
contributors: this property found within the object instance is a vector that is initialized to an empty state(i.e when you create an empty array) this give room for us keep records of the people who contributed to the crowdfund
current_amount: this shows the current amount being contributed and on the first instance of creating the crowdfund… this is set to 0 and it can increase as contributor continue to contribute to the crowdfund
deadline: This is basically time in timestamps which shows how long the crowdfund campaign can last.. there are numerous ways of doing this but let’s keep it this way
funds: the stores actual contributions made with the coinType in this case sui
now after the crowdfund(crowdfund_property) is created, I had to get the crowdfund id via an immutable reference to append or register the created crowdfund in the CrowdfundManager vector property
let property_id = object::id_address(&crowdfund_property);
vector::push_back(&mut manager.properties, property_id);
transfer::share_object(crowdfund_property);
after which the newly created crowdfund is being shared, so anyone can have access to the crowdfund
Now, we will look into the Contribute Function
public fun contribute(crowdfund: &mut Crowdfund, payment: Coin<SUI>, clock: &Clock, ctx: &mut TxContext ){
let sender = tx_context::sender(ctx);
let amount = coin::value(&payment);
assert!(crowdfund.is_active, EINVALID);
let contributor = Contributor {
wallet_address: sender,
amount,
timestamp: clock.timestamp_ms()
};
vector::push_back(&mut crowdfund.contributors, contributor);
let payment_balance = coin::into_balance(payment);
crowdfund.current_amount = crowdfund.current_amount + amount;
balance::join(&mut crowdfund.funds, payment_balance);
}
looking at the arguments, we have the crowdfund, payment, clock, and transaction context…
The crowdfund is the particular crowdfund a contributor wants to contribute to hence the use of a mutable reference(&mut) as they will changes made in the crowdfund object.
The payment, the actual sui token being contributed to the crowdfund.
The clock, is a sui framework for anything that deals with date and time.
Breakdown of what is going on in the function:
Firstly, we get the sender from the transaction context,
then, we get amount of sui being contributed from the sui framework, coin via the value function(coin::value(…))
then, we check if the crowdfund campaign is still active via assert!(…);… only when it is true that a contributor can be able to make contributions
if the crowdfund is still active, we create a contributor object that contains the wallet_address(the contributor address), amount(amount of sui being contributed), and timestamp(the time at which the contribution was made)
then, we append or register the contributor into the crowdfund.contributors vectors.
once that is done, we fetch the actual payment_balance from payment argument via the sui framework, coin (coin::into_balance());
then we update the current_amount property of the crowdfund as well the funds balance property in the crowdfund
Thirdly, we will look into the Withdraw Function (Performed by the crowdfund creator)
public fun withdraw (crowdfund: &mut Crowdfund, clock: &Clock, ctx: &mut TxContext) {
assert!(clock.timestamp_ms() > crowdfund.deadline, EINVALID);
assert!(crowdfund.is_active, EINVALID);
let available_funds = balance::value(&crowdfund.funds);
if (available_funds > 0 ) {
let withdraw_coin = coin::from_balance(balance::split(&mut crowdfund.funds, available_funds), ctx);
crowdfund.is_active = false;
transfer::public_transfer(withdraw_coin, crowdfund.creator)
}
}
firstly we check if the current timestamp has passed the crowdfund deadline, then we check if the crowdfund is still active.
if those two checks successfully, we check the available_funds in crowdfund via balance::value(&crowdfund.funds);
now if available_funds is greater than 0… we first split the balance (essentially subtracting available_funds from crowdfund.funds) which results in the withdraw_coin
Keep in mind that literally everything on sui is an object… the coin you transact with all object, hence the withdraw_coin
Then we set the active state of the crowdfund to false
then transfer the withdraw_coin to crowdfund creator
Finally, A getter function to fetch details about a crowdfund
public fun get_crowdfund_info(crowdfund: &Crowdfund): (
String, // name
String, // description
address, // creator
u8, //Crowdfund Type
u64, // target amount
u64, // current amount
u64, // deadline
bool, // is active
bool // is successful
) {
(
crowdfund.name,
crowdfund.description,
crowdfund.creator,
crowdfund.crowdfund_type,
crowdfund.target_amount,
crowdfund.current_amount,
crowdfund.deadline,
crowdfund.is_active,
crowdfund.is_successful,
)
}
This function fetches the main properties of a particular crowdfund…
HERE IS A FULL CODE SNIPPET
module crowdfund::crowdfund;
use std::string::String;
use sui::balance::{Self, Balance};
use sui::clock::Clock;
use sui::coin::{Self, Coin};
use sui::sui::SUI;
// Error constants
const EINVALID: u64 = 0;
// Struct to track contributors
public struct Contributor has copy, drop, store {
wallet_address: address,
amount: u64,
timestamp: u64,
}
// Struct to manage all crowdfunds
public struct CrowdfundManager has key {
id: UID,
crowdfunds: vector<address>, // Store all crowdfund addresses
}
// Main crowdfund struct (You can switch it up to what you want)
public struct Crowdfund has key {
id: UID,
name: String,
description: String,
creator: address,
crowdfund_type: u8, // 0 for grant, 1 for ICO
target_amount: u64,
current_amount: u64,
deadline: u64,
contributors: vector<Contributor>,
funds: Balance<SUI>,
is_active: bool,
is_successful: bool,
}
// Initialize the crowdfunding platform
fun init(ctx: &mut TxContext) {
let manager = CrowdfundManager {
id: object::new(ctx),
crowdfunds: vector::empty<address>(),
};
transfer::share_object(manager)
}
public fun create_crowdfund(
manager: &mut CrowdfundManager,
name: String,
description: String,
crowdfund_type: u8,
target_amount: u64,
deadline: u64,
ctx: &mut TxContext,
) {
let sender = tx_context::sender(ctx);
let crowdfund_property = Crowdfund {
id: object::new(ctx),
name,
description,
creator: sender,
crowdfund_type,
target_amount,
current_amount: 0,
deadline,
contributors: vector::empty<Contributor>(),
funds: balance::zero(),
is_active: true,
is_successful: false,
};
let property_id = object::id_address(&crowdfund_property);
vector::push_back(&mut manager.crowdfunds, property_id);
transfer::share_object(crowdfund_property);
}
public fun contribute(
crowdfund: &mut Crowdfund,
payment: Coin<SUI>,
clock: &Clock,
ctx: &mut TxContext,
) {
let sender = tx_context::sender(ctx);
let amount = coin::value(&payment);
assert!(crowdfund.is_active, EINVALID);
let contributor = Contributor {
wallet_address: sender,
amount,
timestamp: clock.timestamp_ms(),
};
vector::push_back(&mut crowdfund.contributors, contributor);
let payment_balance = coin::into_balance(payment);
crowdfund.current_amount = crowdfund.current_amount + amount;
balance::join(&mut crowdfund.funds, payment_balance);
}
public fun withdraw(crowdfund: &mut Crowdfund, clock: &Clock, ctx: &mut TxContext) {
assert!(clock.timestamp_ms() > crowdfund.deadline, EINVALID);
assert!(crowdfund.is_active, EINVALID);
let available_funds = balance::value(&crowdfund.funds);
if (available_funds > 0) {
let withdraw_coin = coin::from_balance(
balance::split(&mut crowdfund.funds, available_funds),
ctx,
);
crowdfund.is_active = false;
transfer::public_transfer(withdraw_coin, crowdfund.creator)
}
}
public fun get_crowdfund_info(
crowdfund: &Crowdfund,
): (
String, // name
String, // description
address, // creator
u8, //Crowdfund Type
u64, // target amount
u64, // current amount
u64, // deadline
bool, // is active
bool, // is successful
) {
(
crowdfund.name,
crowdfund.description,
crowdfund.creator,
crowdfund.crowdfund_type,
crowdfund.target_amount,
crowdfund.current_amount,
crowdfund.deadline,
crowdfund.is_active,
crowdfund.is_successful,
)
}
If you got to this, I want to say that you rock 🤟🔥
Now with this, you can run tests and publish the smart contract… on the next drop… I will show you how to write tests on sui move
NOW SAY THANK YOU!
You’re welcome SUI😏 you later
Subscribe to my newsletter
Read articles from Daniel Nwachukwu Chigozirim directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by

Daniel Nwachukwu Chigozirim
Daniel Nwachukwu Chigozirim
Web3 Front-end developer || Smart contract developer || Tech enthusiast || Let's talk web 🕸️ || GitHub - http://github.com/Verifieddanny