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}