Final Code
Complete crowdfunding smart contract implementation with all features.
This page provides the complete, final version of the crowdfunding smart contract developed throughout the tutorial. This implementation includes all the features covered in Part 1, Part 2, and Part 3.
Overview
The final crowdfunding smart contract includes:
- Initialization: Sets up the token identifier, target amount, and deadline
- Fund endpoint: Accepts token payments from donors during the funding period
- Claim endpoint: Allows the owner to claim funds if successful, or donors to get refunds if failed
- Status view: Returns the current campaign status (FundingPeriod, Successful, or Failed)
- Storage: Tracks target amount, deadline, deposits per donor, and token identifier
Contract Features
Status Enum
The contract uses a custom Status enum to represent the three possible states of a crowdfunding campaign:
- FundingPeriod: The campaign is still accepting donations (before the deadline)
- Successful: The deadline has passed and the target amount was reached
- Failed: The deadline has passed but the target amount was not reached
Key Methods
-
init: Initializes the contract with a token identifier, target amount, and deadline. Includes validation to ensure the token is valid, the target is greater than zero, and the deadline is in the future. -
fund: Allows users to contribute tokens to the campaign. Validates that the correct token is being sent, that only fungible tokens are accepted, and that the funding period is still active. -
claim: Handles the claiming logic based on the campaign status:- During the funding period: Returns an error
- If successful: Allows only the owner to claim all collected funds
- If failed: Allows donors to claim their individual refunds
-
status: A view function that returns the current status of the campaign based on the deadline and funds raised. -
get_current_funds: Returns the total amount of tokens currently held by the contract.
Complete Contract Code
#![no_std]
use multiversx_sc::{derive_imports::*, imports::*};
pub mod crowdfunding_proxy;
#[type_abi]
#[derive(TopEncode, TopDecode, PartialEq, Eq, Clone, Copy, Debug)]
pub enum Status {
FundingPeriod,
Successful,
Failed,
}
#[multiversx_sc::contract]
pub trait Crowdfunding {
#[init]
fn init(&self, token_identifier: TokenId, target: BigUint, deadline: TimestampMillis) {
require!(token_identifier.is_valid(), "Invalid token provided");
self.cf_token_id().set(token_identifier);
require!(target > 0, "Target must be more than 0");
self.target().set(target);
require!(
deadline > self.get_current_time_millis(),
"Deadline can't be in the past"
);
self.deadline().set(deadline);
}
#[endpoint]
#[payable]
fn fund(&self) {
let payment = self.call_value().single();
require!(
payment.token_identifier == self.cf_token_id().get(),
"wrong token"
);
require!(payment.is_fungible(), "only fungible tokens accepted");
require!(
self.status() == Status::FundingPeriod,
"cannot fund after deadline"
);
let caller = self.blockchain().get_caller();
self.deposit(&caller)
.update(|deposit| *deposit += payment.amount.as_big_uint());
}
#[view]
fn status(&self) -> Status {
if self.get_current_time_millis() < self.deadline().get() {
Status::FundingPeriod
} else if self.get_current_funds() >= self.target().get() {
Status::Successful
} else {
Status::Failed
}
}
#[view(getCurrentFunds)]
#[title("currentFunds")]
fn get_current_funds(&self) -> BigUint {
let token = self.cf_token_id().get();
self.blockchain().get_sc_balance(&token, 0)
}
#[endpoint]
fn claim(&self) {
match self.status() {
Status::FundingPeriod => sc_panic!("cannot claim before deadline"),
Status::Successful => {
let caller = self.blockchain().get_caller();
require!(
caller == self.blockchain().get_owner_address(),
"only owner can claim successful funding"
);
let token_identifier = self.cf_token_id().get();
let sc_balance = self.get_current_funds();
if let Some(sc_balance_non_zero) = sc_balance.into_non_zero() {
self.tx()
.to(&caller)
.payment(Payment::new(token_identifier, 0, sc_balance_non_zero))
.transfer();
}
}
Status::Failed => {
let caller = self.blockchain().get_caller();
let deposit = self.deposit(&caller).get();
if deposit > 0u32 {
let token_identifier = self.cf_token_id().get();
self.deposit(&caller).clear();
if let Some(deposit_non_zero) = deposit.into_non_zero() {
self.tx()
.to(&caller)
.payment(Payment::new(token_identifier, 0, deposit_non_zero))
.transfer();
}
}
}
}
}
// private
fn get_current_time_millis(&self) -> TimestampMillis {
self.blockchain().get_block_timestamp_millis()
}
// storage
#[view(getTarget)]
#[title("target")]
#[storage_mapper("target")]
fn target(&self) -> SingleValueMapper<BigUint>;
#[view(getDeadline)]
#[title("deadline")]
#[storage_mapper("deadline")]
fn deadline(&self) -> SingleValueMapper<TimestampMillis>;
#[view(getDeposit)]
#[title("deposit")]
#[storage_mapper("deposit")]
fn deposit(&self, donor: &ManagedAddress) -> SingleValueMapper<BigUint>;
#[view(getCrowdfundingTokenId)]
#[title("tokenIdentifier")]
#[storage_mapper("tokenIdentifier")]
fn cf_token_id(&self) -> SingleValueMapper<TokenId>;
}