Navigating the Crowd Funding Smart Contract Workflow in an Open Financial Network - STELLAR
The Stellar Ecosystem
For those new to the Stellar universe; welcome aboard! This train is headed toward deeply understanding one of the most innovative blockchain ecosystems. And for our seasoned travelers who have mastered the tracks, feel free to teleport straight to our last stop, where we’ll embark on the adventure of the workflow of a crowd funding smart contract.
Have you ever heard of an open network designed for securely storing and transferring money while onboarding boundless innovation? A world where cross-border payments are as effortless as sending a message?
Our First Stop: What Is Stellar?
Stellar is a layer 1 decentralized, open-source blockchain ecosystem designed to improve traditional financial systems, making money better. It bridges the cryptocurrency world and conventional finance, enabling fast, efficient, and cost-effective cross-border payments. It also allows the creation of digital representations of value, such as fiat currencies and custom-made currencies.
WHO ARE THE VISIONARIES BEHIND STELLAR?
THE FOUNDING STORY
Unlike other ecosystems, these visionaries had an unwavering singular focus; the seamless transfer of value. In 2014, two cryptocurrency OG’s laid the foundation for Stellar, Jed McCaleb and Joyce Kim. Jed McCaleb is an experienced entrepreneur known for founding Mt. Gox and co-founding Ripple. Joyce Kim, on the other hand, is a former lawyer and venture capitalist who established the Stellar Development Foundation.
The journey began with a pivotal conversation between McCaleb and Patrick Collison, co-founder of Stripe. They discovered a shared alignment in their goals and values, particularly the potential to create a more inclusive financial system. Recognizing the transformative potential of Stellar, Stripe invested $3 million into the project, providing critical early support alongside Silicon Valley legends Keith Radios (ex-PayPal exec), Sam Altman (OpenAI and Worldcoin founder), and Naval Ravikant (AngelList). This investment wasn’t just a financial boost, it was a validation of Stellar’s potential to reshape the financial landscape.
PROBLEMS WITH PAYMENTS
One of the biggest challenges in traditional banking is fragmentation, which refers to the lack of cooperation among various financial institutions and services. This fragmentation makes it increasingly difficult to send money to individuals in different parts of the world.
Several factors contribute to this issue:
Diverse Technologies and Platforms: The financial industry is riddled with different payment systems like SWIFT, ACH, and others. Each operates independently, creating barriers to seamless transactions.
Data Isolation: Data is often siloed within specific institutions or regions, further complicating the transfer of information and funds across borders.
Regulatory Frameworks: Each country or region has its own set of regulations governing financial transactions. This lack of standardization increases the complexity and cost of cross-border payments, leading to delays and inefficiencies.
This shouldn’t be a problem anymore, as cryptocurrency is providing solutions. With a crypto wallet, you can easily send and receive money across the globe without the need for intermediaries. However, not everyone has a crypto wallet. As of 2024, the global cryptocurrency adoption rate is approximately 6.8%, a surprisingly small number, right? While some have eagerly jumped on the crypto train, others remain comfortable using their banks for transactions.
Stellar's Solution
An exciting phase lies ahead for our journey.
With Stellar, you don’t need to have a crypto wallet to make cross-border transactions, not like the general crypto solutions, Amazed? yeah.
Introducing Anchors: Anchors are trusted entities that act as a bridge between the Stellar network and traditional financial systems. They facilitate the seamless movement of money between the crypto world and the real world, making the process accessible to everyone. With Stellar, users can send money effortlessly around the globe using their bank accounts, without even realizing they're interacting with a cryptocurrency system. Here's how it works:
When a user sends money through their bank using Stellar, the anchor silently converts the money into a digital form on the Stellar blockchain. This digital currency is then transferred to the recipient’s region, where another anchor converts it back into the local fiat currency, depositing it directly into the receiver’s bank account. All of this happens behind the scenes, ensuring a smooth and transparent experience for the user.
With Stellar, sending money across the world becomes effortless, simple, cheap, fast, and secure.
“The only thing that is constant is change.” This principle of continuous evolution is at the heart of Stellar's journey, as the ecosystem strives to become better and bigger. Stellar isn't just about facilitating payments and transferring value anymore. There is much more to finance than just payments.
Our Next Stop: Soroban
The future of Stellar is about expanding beyond pure payments, and that journey continues with Soroban—a groundbreaking development that brings smart contracts to the Stellar blockchain. Soroban enables developers to build endless innovations on the Stellar network, unlocking new possibilities for decentralized finance (DeFi), tokenization, and beyond.
Imagine the ability to tokenize real-world assets like real estate or commodities on the Stellar blockchain. With Soroban, developers can create smart contracts that automatically handle complex transactions, manage assets, or even facilitate automated compliance. This not only broadens the scope of what Stellar can do but also opens up new avenues for financial innovation, making the ecosystem more versatile and powerful than ever before.
KEY FEATURES OF STELLAR
Some key features of Stellar include:
Stellar Consensus Protocol (SCP): Unlike traditional Proof of Work or Proof of Stake mechanisms, Stellar uses SCP, which is faster and more energy efficient, allowing for quick transaction confirmations.
Lumens (XLM): Lumens are the native digital currency of the Stellar network. They are used to pay transaction fees and maintain account balances.
Decentralized Exchange: Stellar has a built-in decentralized exchange (DEX) that allows users to trade various assets directly on the network.
Great job making it this far! You've built a solid understanding of the Stellar ecosystem and how its blockchain operates.
Our Last Stop: Work Flow of a Crowdfunding Smart Contract on Stellar
In this final section, we’ll walk through understanding the Workflow of a Crowdfunding Smart Contract on Stellar.
Crowdfunding DApp on Soroban with Rust: Navigation for you to Build Better
This challenge will guide you through building a crowdfunding DApp on Stellar using Soroban, whether you're a new developer, an Ethereum developer, or someone not yet familiar with Rust. Stellar, with its open financial network technology, simplifies the process. Unlike traditional crowdfunding platforms, decentralized applications (DApps) enable users to pledge funds directly to campaigns from their digital wallets, eliminating the need for intermediaries.
Bus-stop0: 📦 Install 📚
Stellar smart contracts are small programs written in the Rust programming language.
To build and develop contracts you need only a couple prerequisites:
A Rust toolchain
Node
v18: Download NodeFreighter Wallet
: Freighter WalletLinux, macOS, or other Unix-like OS
If you use macOS, Linux, or another Unix-like OS, the simplest method to install a Rust toolchain is to install
rustup
. Installrustup
with the following command.curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
Then, install soroban-cli alias by running the following command
cargo install_soroban
Soroban CLI is the command line interface to Soroban. It allows you to build, deploy, and interact with smart contracts, configure identities, generate key pairs, manage networks, and more.
Set Up the Soroban Contract
cargo new --lib soroban_crowdfund cd soroban_crowdfund
Crowd Fund workflow
User creates a campaign.
Users can pledge, transferring their token to a campaign.
After the campaign ends, the campaign creator can claim the funds if the total amount pledged is more than the campaign goal.
Otherwise, campaign did not reach it's goal, users can withdraw their pledge
Create a Campaign:
The process begins when a user, referred to as the "campaign creator," initiates a new crowdfunding campaign.
The campaign creator specifies important details such as the fundraising goal (target amount), the deadline for the campaign, and the type of token accepted.
#[contractimpl] impl Crowdfund { pub fn initialize( e: Env, recipient: Address, deadline: u64, target_amount: i128, token: Address, ) { assert!( !e.storage().instance().has(&DataKey::Recipient), "already initialized" ); e.storage().instance().set(&DataKey::Recipient, &recipient); e.storage() .instance() .set(&DataKey::RecipientClaimed, &false); e.storage() .instance() .set(&DataKey::Started, &get_ledger_timestamp(&e)); e.storage().instance().set(&DataKey::Deadline, &deadline); e.storage().instance().set(&DataKey::Target, &target_amount); e.storage().instance().set(&DataKey::Token, &token); } }
Pledge Tokens:
Users interested in supporting the campaign can pledge their tokens by transferring them to the campaign's smart contract.
Each pledge is securely stored in the contract, and users can track their contributions.
#[contractimpl] impl Crowdfund { pub fn deposit(e: Env, user: Address, amount: i128) { user.require_auth(); assert!(amount > 0, "amount must be positive"); assert!(get_state(&e) == State::Running, "sale is not running"); let token_id = get_token(&e); let current_target_met = target_reached(&e, &token_id); let recipient = get_recipient(&e); assert!(user != recipient, "recipient may not deposit"); let balance = get_user_deposited(&e, &user); set_user_deposited(&e, &user, &(balance + amount)); let client = token::Client::new(&e, &token_id); client.transfer(&user, &e.current_contract_address(), &amount); let contract_balance = get_balance(&e, &token_id); // emit events events::pledged_amount_changed(&e, contract_balance); if !current_target_met && target_reached(&e, &token_id) { // only emit the target reached event once on the pledge that triggers target to be met events::target_reached(&e, contract_balance, get_target_amount(&e)); } } }
Campaign Success:
After the campaign ends, if the total amount pledged by all users exceeds the campaign's goal, the campaign creator can claim the funds.
The funds are transferred from the smart contract to the campaign creator’s account.
#[contractimpl] impl Crowdfund { pub fn withdraw(e: Env, to: Address) { let state = get_state(&e); let recipient = get_recipient(&e); match state { State::Running => { panic!("sale is still running") } State::Success => { assert!( to == recipient, "sale was successful, only the recipient may withdraw" ); assert!( !get_recipient_claimed(&e), "sale was successful, recipient has withdrawn funds already" ); let token = get_token(&e); transfer(&e, &recipient, &get_balance(&e, &token)); set_recipient_claimed(&e); } _ => {} }; } }
Campaign Failure:
- If the campaign does not reach its goal by the deadline, the campaign is considered unsuccessful.
In this case, users who pledged tokens can withdraw their contributions from the smart contract.
#[contractimpl]
impl Crowdfund {
pub fn withdraw(e: Env, to: Address) {
let state = get_state(&e);
let recipient = get_recipient(&e);
match state {
State::Expired => {
assert!(
to != recipient,
"sale expired, the recipient may not withdraw"
);
// Withdraw full amount
let balance = get_user_deposited(&e, &to);
set_user_deposited(&e, &to, &0);
transfer(&e, &to, &balance);
// emit events
let token_id = get_token(&e);
let contract_balance = get_balance(&e, &token_id);
events::pledged_amount_changed(&e, contract_balance);
}
_ => {}
};
}
}
Implementing the Crowdfunding Smart Contract
Let's explain how this process can be implemented in Soroban using Rust.
Data Structures
- We’ll define the data keys for storing campaign details like the recipient, deadline, target amount, token type, and pledges.
#[derive(Clone)]
#[contracttype]
pub enum DataKey {
Deadline,
Recipient,
Started,
Target,
Token,
User(Address),
RecipientClaimed,
}
Initializing a Campaign
The
initialize
the function sets up the campaign. It stores the recipient, deadline, target amount, and token type in the contract's storage.This function is called only once when the campaign is created.
#[contractimpl] impl Crowdfund { pub fn deposit(e: Env, user: Address, amount: i128) { user.require_auth(); assert!(amount > 0, "amount must be positive"); assert!(get_state(&e) == State::Running, "sale is not running"); let token_id = get_token(&e); let current_target_met = target_reached(&e, &token_id); let recipient = get_recipient(&e); assert!(user != recipient, "recipient may not deposit"); let balance = get_user_deposited(&e, &user); set_user_deposited(&e, &user, &(balance + amount)); let client = token::Client::new(&e, &token_id); client.transfer(&user, &e.current_contract_address(), &amount); let contract_balance = get_balance(&e, &token_id); // emit events events::pledged_amount_changed(&e, contract_balance); if !current_target_met && target_reached(&e, &token_id) { // only emit the target reached event once on the pledge that triggers target to be met events::target_reached(&e, contract_balance, get_target_amount(&e)); } } }
Pledging Tokens
The
deposit
function allows users to pledge tokens.Users specify the amount they want to pledge, and the smart contract securely transfers these tokens from the user to the contract.
#[contractimpl] impl Crowdfund { pub fn deposit(e: Env, user: Address, amount: i128) { user.require_auth(); assert!(amount > 0, "amount must be positive"); assert!(get_state(&e) == State::Running, "sale is not running"); let token_id = get_token(&e); let current_target_met = target_reached(&e, &token_id); let recipient = get_recipient(&e); assert!(user != recipient, "recipient may not deposit"); let balance = get_user_deposited(&e, &user); set_user_deposited(&e, &user, &(balance + amount)); let client = token::Client::new(&e, &token_id); client.transfer(&user, &e.current_contract_address(), &amount); let contract_balance = get_balance(&e, &token_id); // emit events events::pledged_amount_changed(&e, contract_balance); if !current_target_met && target_reached(&e, &token_id) { // only emit the target reached event once on the pledge that triggers target to be met events::target_reached(&e, contract_balance, get_target_amount(&e)); } } }
Checking Campaign State
The contract regularly checks the state of the campaign.
If the current time is before the deadline and the target amount has not been reached, the campaign continues to run.
If the target is reached, the campaign is marked as successful. If the deadline passes without reaching the target, the campaign is marked as expired.
fn get_state(e: &Env) -> State { let deadline = get_deadline(e); let token_id = get_token(e); let current_timestamp = get_ledger_timestamp(e); if current_timestamp < deadline { return State::Running; } if get_recipient_claimed(e) || target_reached(e, &token_id) { return State::Success; } State::Expired }
Now that we've explored how crowdfunding smart contracts work on Stellar, let's dive into this beautifully crowdfunding app built on the Stellar network . With the basics learnt, it's time to clone the code example built by stellar and fork it and build something even better on Stellar.
git clone https://github.com/stellar/soroban-dapps-challenge.git
cd soroban-dapps-challenge
git checkout crowdfund
Conclusion
In this tutorial, we’ve successfully built and deployed a crowdfunding DApp on Soroban using Rust. We also set up a frontend client to interact with our smart contract. This project demonstrates how Ethereum developers can leverage their skills to build on Stellar with Soroban, using Rust for secure and efficient smart contracts.
9. References
Soroban Documentation
Ethereum Smart Contract Tutorial