Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: implement erc20 streaming #237

Open
wants to merge 10 commits into
base: main
Choose a base branch
from
4 changes: 4 additions & 0 deletions Scarb.lock
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,10 @@ dependencies = [
"openzeppelin",
]

[[package]]
name = "simple_storage"
version = "0.1.0"

[[package]]
name = "simple_vault"
version = "0.1.0"
Expand Down
118 changes: 118 additions & 0 deletions listings/applications/erc20/src/erc20_streaming.cairo
julio4 marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
#[starknet::contract]

pub mod erc20_streaming {

// Import necessary modules and traits
use core::num::traits::Zero;
use starknet::get_caller_address;
use starknet::ContractAddress;
use starknet::LegacyMap;

#[storage]
struct Storage {
streams: LegacyMap<(ContractAddress, ContractAddress), Stream>,
erc20_token: ContractAddress,
Mystic-Nayy marked this conversation as resolved.
Show resolved Hide resolved
}

#[derive(Copy, Drop, Debug, PartialEq)]
struct Stream {
start_time: u64,
end_time: u64,
total_amount: felt252,
released_amount: felt252,
}
Mystic-Nayy marked this conversation as resolved.
Show resolved Hide resolved

#[event]
#[derive(Copy, Drop, Debug, PartialEq, starknet::Event)]
pub enum Event {
StreamCreated: StreamCreated,
TokensReleased: TokensReleased,
}

#[derive(Copy, Drop, Debug, PartialEq, starknet::Event)]
pub struct StreamCreated {
pub from: ContractAddress,
pub to: ContractAddress,
pub total_amount: felt252,
pub start_time: u64,
pub end_time: u64,
}

#[derive(Copy, Drop, Debug, PartialEq, starknet::Event)]
pub struct TokensReleased {
pub to: ContractAddress,
pub amount: felt252,
}

mod Errors {
pub const STREAM_AMOUNT_ZERO: felt252 = 'Stream amount cannot be zero';
pub const STREAM_ALREADY_EXISTS: felt252 = 'Stream already exists';
}

#[constructor]
fn constructor(ref self: ContractState, erc20_token: ContractAddress) {
self.erc20_token.write(erc20_token);
}

#[abi(embed_v0)]
impl IStreamImpl of super::IStream<ContractState> {
fn create_stream(
ref self: ContractState,
to: ContractAddress,
total_amount: felt252,
start_time: u64,
Mystic-Nayy marked this conversation as resolved.
Show resolved Hide resolved
end_time: u64
) {
assert(total_amount != felt252::zero(), Errors::STREAM_AMOUNT_ZERO);
let caller = get_caller_address();
let stream_key = (caller, to);
assert(self.streams.read(stream_key).start_time == 0, Errors::STREAM_ALREADY_EXISTS);
julio4 marked this conversation as resolved.
Show resolved Hide resolved

// Call the ERC20 contract to transfer tokens
let erc20 = self.erc20_token.read();
erc20.call("transfer_from", (caller, self.contract_address(), total_amount));

let stream = Stream {
start_time,
end_time,
total_amount,
released_amount: felt252::zero(),
};
self.streams.write(stream_key, stream);

self.emit(StreamCreated { from: caller, to, total_amount, start_time, end_time });
}

fn release_tokens(ref self: ContractState, to: ContractAddress) {
let caller = get_caller_address();
Mystic-Nayy marked this conversation as resolved.
Show resolved Hide resolved
let stream_key = (caller, to);
Mystic-Nayy marked this conversation as resolved.
Show resolved Hide resolved
let stream = self.streams.read(stream_key);
let releasable_amount = self.releasable_amount(stream);
self.streams.write(
stream_key,
Stream {
released_amount: stream.released_amount + releasable_amount,
..stream
}
);
Mystic-Nayy marked this conversation as resolved.
Show resolved Hide resolved

// Call the ERC20 contract to transfer tokens
let erc20 = self.erc20_token.read();
erc20.call("transfer", (to, releasable_amount));

self.emit(TokensReleased { to, amount: releasable_amount });
}

fn releasable_amount(&self, stream: Stream) -> felt252 {
let current_time = starknet::get_block_timestamp();
if current_time >= stream.end_time {
return stream.total_amount - stream.released_amount;
} else {
let time_elapsed = current_time - stream.start_time;
let vesting_duration = stream.end_time - stream.start_time;
let vested_amount = stream.total_amount * time_elapsed / vesting_duration;
return vested_amount - stream.released_amount;
}
}
Mystic-Nayy marked this conversation as resolved.
Show resolved Hide resolved
}
}