Skip to main content

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