streamflow_sdk/
state.rs

1use anchor_lang::prelude::*;
2
3/// Streamflow Treasury address, by default receives 0.25% of tokens deposited
4pub const STRM_TREASURY: &str = "5SEpbdjFK5FxwTvfsGMXVQTD2v4M2c5tyRTxhdsPkgDw";
5/// Streamflow Withdrawor address, this account will process withdrawals
6pub const WITHDRAWOR_ADDRESS: &str = "wdrwhnCv4pzW8beKsbPa4S2UDZrXenjg16KJdKSpb5u";
7/// Address of Fee Oracle that stores information about fees for speficic partners
8pub const FEE_ORACLE_ADDRESS: &str = "B743wFVk2pCYhV91cn287e1xY7f1vt4gdY48hhNiuQmT";
9
10/// Prefix used to derive Escrow account address
11pub const ESCROW_SEED_PREFIX: &[u8] = b"strm";
12/// Size of Stream metadata
13pub const METADATA_LEN: usize = 1104;
14
15/// You can also use id that sdk exposes like so streamflow_sdk::id()
16pub const STREAMFLOW_PROGRAM_ID: &str = "strmRqUCoQUgGUan5YhzUZa6KqdzwX5L6FpUxfmKg5m";
17pub const STREAMFLOW_DEVNET_PROGRAM_ID: &str = "HqDGZjaVRXJ9MGRQEw7qDc2rAr6iH1n1kAQdCZaCMfMZ";
18
19pub fn find_escrow_account(seed: &[u8], pid: &Pubkey) -> (Pubkey, u8) {
20    Pubkey::find_program_address(&[ESCROW_SEED_PREFIX, seed], pid)
21}
22
23/// Calculate fee amount from a provided amount
24pub fn calculate_fee_from_amount(amount: u64, percentage: f32) -> u64 {
25    if percentage <= 0.0 {
26        return 0;
27    }
28    let precision_factor: f32 = 1000000.0;
29    // largest it can get is MAX_FEE * 10^4
30    let factor = (percentage / 100.0 * precision_factor) as u128;
31
32    // this does not fit if amount  itself cannot fit into u64
33    (amount as u128 * factor / precision_factor as u128) as u64
34}
35
36/// The struct containing parameters for initializing a stream
37#[derive(AnchorSerialize, AnchorDeserialize, Clone, Debug)]
38#[repr(C)]
39pub struct CreateParams {
40    /// Timestamp when the tokens start vesting
41    pub start_time: u64,
42    /// Deposited amount of tokens
43    pub net_amount_deposited: u64,
44    /// Time step (period) in seconds per which the vesting/release occurs
45    pub period: u64,
46    /// Amount released per period. Combined with `period`, we get a release rate.
47    pub amount_per_period: u64,
48    /// Vesting contract "cliff" timestamp
49    pub cliff: u64,
50    /// Amount unlocked at the "cliff" timestamp
51    pub cliff_amount: u64,
52    /// Whether a stream can be canceled by a sender
53    pub cancelable_by_sender: bool,
54    /// Whether a stream can be canceled by a recipient
55    pub cancelable_by_recipient: bool,
56    /// Whether a 3rd party can initiate withdraw in the name of recipient
57    pub automatic_withdrawal: bool,
58    /// Whether the sender can transfer the stream
59    pub transferable_by_sender: bool,
60    /// Whether the recipient can transfer the stream
61    pub transferable_by_recipient: bool,
62    /// Whether topup is enabled
63    pub can_topup: bool,
64    /// The name of this stream
65    pub stream_name: [u8; 64],
66    /// Withdraw frequency
67    pub withdraw_frequency: u64,
68    /// used as padding len in serialization in old streams, added for backwards compatibility
69    pub ghost: u32,
70    /// Whether the contract can be paused
71    pub pausable: bool,
72    /// Whether the contract can update release amount
73    pub can_update_rate: bool,
74}
75
76/// Struct that represents Stream Contract stored on chain, this account **DOES NOT** have a discriminator.
77///
78/// May be read like so
79///
80/// ```rust
81/// let stream_metadata: Contract = match try_from_slice_unchecked(&stream_data) {
82///     Ok(v) => v,
83///     Err(_) => return err!(ErrorCode::InvalidStreamMetadata),
84/// };
85/// ```
86#[derive(AnchorSerialize, AnchorDeserialize, Clone, Debug)]
87#[repr(C)]
88pub struct Contract {
89    /// Magic bytes
90    pub magic: u64,
91    /// Version of the program
92    pub version: u8,
93    /// Timestamp when stream was created
94    pub created_at: u64,
95    /// Amount of funds withdrawn
96    pub amount_withdrawn: u64,
97    /// Timestamp when stream was canceled (if canceled)
98    pub canceled_at: u64,
99    /// Timestamp at which stream can be safely canceled by a 3rd party
100    /// (Stream is either fully vested or there isn't enough capital to
101    /// keep it active)
102    pub end_time: u64,
103    /// Timestamp of the last withdrawal
104    pub last_withdrawn_at: u64,
105    /// Pubkey of the stream initializer
106    pub sender: Pubkey,
107    /// Pubkey of the stream initializer's token account
108    pub sender_tokens: Pubkey,
109    /// Pubkey of the stream recipient
110    pub recipient: Pubkey,
111    /// Pubkey of the stream recipient's token account
112    pub recipient_tokens: Pubkey,
113    /// Pubkey of the token mint
114    pub mint: Pubkey,
115    /// Escrow account holding the locked tokens for recipient
116    pub escrow_tokens: Pubkey,
117    /// Streamflow treasury authority
118    pub streamflow_treasury: Pubkey,
119    /// Escrow account holding the locked tokens for Streamflow (fee account)
120    pub streamflow_treasury_tokens: Pubkey,
121    /// The total fee amount for streamflow
122    pub streamflow_fee_total: u64,
123    /// The withdrawn fee amount for streamflow
124    pub streamflow_fee_withdrawn: u64,
125    /// Fee percentage for Streamflow
126    pub streamflow_fee_percent: f32,
127    /// Streamflow partner authority
128    pub partner: Pubkey,
129    /// Escrow account holding the locked tokens for Streamflow partner (fee account)
130    pub partner_tokens: Pubkey,
131    /// The total fee amount for the partner
132    pub partner_fee_total: u64,
133    /// The withdrawn fee amount for the partner
134    pub partner_fee_withdrawn: u64,
135    /// Fee percentage for partner
136    pub partner_fee_percent: f32,
137    /// The stream instruction
138    pub ix: CreateParams,
139    /// Padding for `ix: CreateParams` to allow for future upgrades.
140    pub ix_padding: Vec<u8>,
141    /// Stream is closed
142    pub closed: bool,
143    /// time of the current pause. 0 signifies unpaused state
144    pub current_pause_start: u64,
145    /// total time the contract was paused for
146    pub pause_cumulative: u64,
147    /// timestamp of last rate change for this stream.
148    /// Rate can be changed with `update` instruction
149    pub last_rate_change_time: u64,
150    /// Accumulated unlocked tokens before last rate change (excluding cliff_amount)
151    pub funds_unlocked_at_last_rate_change: u64,
152}
153
154impl Contract {
155    pub fn start_time(&self) -> u64 {
156        if self.ix.cliff > 0 {
157            self.ix.cliff
158        } else {
159            self.ix.start_time
160        }
161    }
162
163    pub fn effective_start_time(&self) -> u64 {
164        std::cmp::max(self.last_rate_change_time, self.start_time())
165    }
166
167    pub fn pause_time(&self, now: u64) -> u64 {
168        if self.current_pause_start > 0 {
169            return self.pause_cumulative + now - self.current_pause_start;
170        }
171        self.pause_cumulative
172    }
173
174    /// amount available that is vested (excluding cliff)
175    pub fn vested_available(&self, now: u64) -> u64 {
176        let start = self.start_time();
177        // if pause started before start/cliff and is still active, no unlocks
178        if self.current_pause_start < start && self.current_pause_start != 0 {
179            return 0;
180        }
181        // available from streaming based on current rate
182        let effective_stream_duration = now - self.effective_start_time() - self.pause_time(now);
183        let effective_periods_passed = effective_stream_duration / self.ix.period;
184        let effective_amount_available = effective_periods_passed * self.ix.amount_per_period;
185
186        effective_amount_available + self.funds_unlocked_at_last_rate_change
187    }
188
189    pub fn available_to_claim(&self, now: u64, fee_percentage: f32) -> u64 {
190        if self.start_time() > now
191            || self.ix.net_amount_deposited == 0
192            || self.ix.net_amount_deposited == self.amount_withdrawn
193        {
194            return 0;
195        }
196        if now >= self.end_time && self.current_pause_start == 0 {
197            return self.ix.net_amount_deposited - self.amount_withdrawn;
198        }
199
200        let vested_available =
201            calculate_fee_from_amount(self.vested_available(now), fee_percentage);
202        let cliff_available = calculate_fee_from_amount(self.cliff_available(now), fee_percentage);
203        let sum_available = vested_available + cliff_available;
204        sum_available - self.amount_withdrawn
205    }
206
207    pub fn cliff_available(&self, now: u64) -> u64 {
208        if self.current_pause_start < self.ix.cliff && self.current_pause_start != 0 {
209            return 0;
210        }
211        if now < self.ix.cliff {
212            return 0;
213        }
214        self.ix.cliff_amount
215    }
216}