Skip to content

Token Donations

Soroban Token Donations
#![no_std]
use soroban_sdk::{contract, contractimpl, Env, Address, Val, TryFromVal, ConversionError, token, log};
 
#[derive(Clone, Copy)]
pub enum DataKey {
    AcceptedToken = 0, // address of the accepted token
    DonationsRecipient = 1 // address of the donations recipient
}
 
impl TryFromVal<Env, DataKey> for Val {
    type Error = ConversionError;
 
    fn try_from_val(_env: &Env, v: &DataKey) -> Result<Self, Self::Error> {
        Ok((*v as u32).into())
    }
}
 
// Helper functions
fn put_token_address(e: &Env, token: &Address) {
    e.storage().instance().set(&DataKey::AcceptedToken, token);
}
 
fn put_donations_recipient(e: &Env, recipient: &Address) {
    e.storage().instance().set(&DataKey::DonationsRecipient, recipient);
}
 
fn get_token_address(e: &Env) -> Address {
    e.storage()
        .instance()
        .get(&DataKey::AcceptedToken)
        .expect("not initialized")
}
 
fn get_donations_recipient(e: &Env) -> Address {
    e.storage()
        .instance()
        .get(&DataKey::DonationsRecipient)
        .expect("not initialized")
}
 
fn get_balance(e: &Env, token_address: &Address) -> i128 {
    let client = token::Client::new(e, token_address);
    client.balance(&e.current_contract_address())
}
 
// Transfer tokens from the contract to the recipient
fn transfer(e: &Env, to: &Address, amount: &i128) {
    let token_contract_address = &get_token_address(e);
    let client = token::Client::new(e, token_contract_address);
    client.transfer(&e.current_contract_address(), to, amount);
}
 
// Contract Trait
pub trait DonationsTrait {
    // Sets the recipient address and the token that will be accepted as a donation
    fn initialize(e: Env, recipient: Address, token: Address);
 
    // Donates specified amount units of the accepted token
    fn donate(e: Env, donor: Address, amount: i128);
 
    // Transfers all the donated amounts to the recipient. Can be called by anyone.
    fn withdraw(e: Env);
 
    // Get the token address that is accepted for donations.
    fn token(e: Env) -> Address;
 
    // Get the donations recipient address.
    fn recipient(e: Env) -> Address;
}
 
#[contract]
struct Donations;
 
#[contractimpl]
impl DonationsTrait for Donations {
    // Sets the recipient address and the token that will be accepted as a donation
    fn initialize(e: Env, recipient: Address, token: Address) {
        assert!(
            !e.storage().instance().has(&DataKey::AcceptedToken),
            "already initialized"
        );
 
        put_token_address(&e, &token);
        put_donations_recipient(&e, &recipient);
    }
 
    // Donates amount units of the accepted token
    fn donate(e: Env, donor: Address, amount: i128) {
        donor.require_auth();
 
        let token_address = get_token_address(&e);
        let client = token::Client::new(&e, &token_address);
        client.transfer(&donor, &e.current_contract_address(), &amount);
    }
 
    // Transfer all donation amounts to recipient. Can be called by anyone.
    fn withdraw(e: Env) {
        let token = get_token_address(&e);
        let recipient = get_donations_recipient(&e);
 
        transfer(&e, &recipient, &get_balance(&e, &token));
 
        /// @dev should remove logs before deploying smart contracts
        log!(&e, "Token: {}, Recipient: {}", token, recipient);
    }
 
    // Get the token address that is accepted as donations
    fn token(e: Env) -> Address {
        get_token_address(&e)
    }
 
    // Get the donations recipient address
    fn recipient(e: Env) -> Address {
        get_donations_recipient(&e)
    }
}

Explanation

#![no_std] This attribute prevents linking to the standard library, making the code lighter and more efficient for Soroban contracts. It's big so we save on size.

use soroban_sdk::{contract, contractimpl, Env, log} Imports stuffs from the Soroban SDK. Env is basic Soroban type, we need it because we can't use the Rust standard library.

pub Marks a function as external, meaning it can be invoked outside of the context of the contract code itself.

impl Defines trait implementations, which specify how a type conforms to a particular trait.

TryFromVal Converts values from one type to another, potentially returning an error if the conversion is not possible.

ConversionError Will likely be defined elsewhere in the code and represents the possible errors that can occur during the conversion. If no errors the Ok keyword will return the appropriate response.

into() This method is used to convert the u32 value to a Val value, assuming that the Val type has an implementation of the From<u32> trait.

fn This keyword is used to define functions in Rust.

trait This keyword defines interfaces in Rust, which specify a set of methods and associated functions that a type must implement to conform to the trait.

#[contract] Marks the struct as a Soroban smart contract. Soroban smart contracts are defined as Rust structs.

#[contractimpl] Marks the implementation block as containing contract methods and transforms it to code that Soroban can evaluate directly.

initialize Sets the recipient address and the token that will be accepted as a donation.

donate Donates specifiied amount units of the accepted token.

withdraw Transfers all donation amounts to recipient. Can be called by anyone; this can be noted by the fact that the transfer() method is not connected to an initialized client mechanism.

token and recipient each retrieve Address values as identified by their respective names.

Run in Playground

Loading playground...