@@ -10,6 +10,7 @@ use soroban_sdk::{
1010pub enum DataKey {
1111 Stream ( u64 ) ,
1212 StreamCounter ,
13+ ProtocolConfig ,
1314}
1415
1516#[ derive( Clone , Debug , Eq , PartialEq ) ]
@@ -26,13 +27,25 @@ pub struct Stream {
2627 pub is_active : bool ,
2728}
2829
30+ #[ contracttype]
31+ #[ derive( Clone , Debug , Eq , PartialEq ) ]
32+ pub struct ProtocolConfig {
33+ pub admin : Address ,
34+ pub treasury : Address ,
35+ pub fee_rate_bps : u32 ,
36+ }
37+
2938#[ contracterror]
3039#[ derive( Copy , Clone , Debug , Eq , PartialEq ) ]
3140pub enum StreamError {
3241 InvalidAmount = 1 ,
3342 StreamNotFound = 2 ,
3443 Unauthorized = 3 ,
3544 StreamInactive = 4 ,
45+ AlreadyInitialized = 5 ,
46+ NotAdmin = 6 ,
47+ InvalidFeeRate = 7 ,
48+ NotInitialized = 8 ,
3649}
3750
3851#[ contracttype]
@@ -73,11 +86,142 @@ pub struct StreamToppedUpEvent {
7386 pub new_deposited_amount : i128 ,
7487}
7588
89+ #[ contracttype]
90+ #[ derive( Clone , Debug , Eq , PartialEq ) ]
91+ pub struct FeeCollectedEvent {
92+ pub stream_id : u64 ,
93+ pub treasury : Address ,
94+ pub fee_amount : i128 ,
95+ pub token : Address ,
96+ }
97+
7698#[ contract]
7799pub struct StreamContract ;
78100
101+ /// Maximum fee rate: 1000 basis points = 10%
102+ const MAX_FEE_RATE_BPS : u32 = 1_000 ;
103+
79104#[ contractimpl]
80105impl StreamContract {
106+ // ─── Admin: Protocol Fee Configuration ───────────────────────────
107+
108+ /// One-time initialization of the protocol fee config.
109+ /// Sets the admin, treasury address, and fee rate (in basis points).
110+ pub fn initialize (
111+ env : Env ,
112+ admin : Address ,
113+ treasury : Address ,
114+ fee_rate_bps : u32 ,
115+ ) -> Result < ( ) , StreamError > {
116+ admin. require_auth ( ) ;
117+
118+ if env
119+ . storage ( )
120+ . instance ( )
121+ . has ( & DataKey :: ProtocolConfig )
122+ {
123+ return Err ( StreamError :: AlreadyInitialized ) ;
124+ }
125+
126+ if fee_rate_bps > MAX_FEE_RATE_BPS {
127+ return Err ( StreamError :: InvalidFeeRate ) ;
128+ }
129+
130+ let config = ProtocolConfig {
131+ admin,
132+ treasury,
133+ fee_rate_bps,
134+ } ;
135+ env. storage ( )
136+ . instance ( )
137+ . set ( & DataKey :: ProtocolConfig , & config) ;
138+
139+ Ok ( ( ) )
140+ }
141+
142+ /// Update the treasury address and/or fee rate. Admin-only.
143+ pub fn update_fee_config (
144+ env : Env ,
145+ admin : Address ,
146+ treasury : Address ,
147+ fee_rate_bps : u32 ,
148+ ) -> Result < ( ) , StreamError > {
149+ admin. require_auth ( ) ;
150+
151+ let config: ProtocolConfig = env
152+ . storage ( )
153+ . instance ( )
154+ . get ( & DataKey :: ProtocolConfig )
155+ . ok_or ( StreamError :: NotInitialized ) ?;
156+
157+ if config. admin != admin {
158+ return Err ( StreamError :: NotAdmin ) ;
159+ }
160+
161+ if fee_rate_bps > MAX_FEE_RATE_BPS {
162+ return Err ( StreamError :: InvalidFeeRate ) ;
163+ }
164+
165+ let new_config = ProtocolConfig {
166+ admin : config. admin ,
167+ treasury,
168+ fee_rate_bps,
169+ } ;
170+ env. storage ( )
171+ . instance ( )
172+ . set ( & DataKey :: ProtocolConfig , & new_config) ;
173+
174+ Ok ( ( ) )
175+ }
176+
177+ /// Read the current protocol fee configuration (returns None if not initialized).
178+ pub fn get_fee_config ( env : Env ) -> Option < ProtocolConfig > {
179+ env. storage ( )
180+ . instance ( )
181+ . get ( & DataKey :: ProtocolConfig )
182+ }
183+
184+ // ─── Fee Collection ──────────────────────────────────────────────
185+
186+ /// Deducts protocol fee from `amount` and transfers it to the treasury.
187+ /// Returns the net amount (amount - fee). If no config or fee is 0, returns `amount` unchanged.
188+ fn collect_fee (
189+ env : & Env ,
190+ token_address : & Address ,
191+ amount : i128 ,
192+ stream_id : u64 ,
193+ ) -> i128 {
194+ let config: Option < ProtocolConfig > = env
195+ . storage ( )
196+ . instance ( )
197+ . get ( & DataKey :: ProtocolConfig ) ;
198+
199+ match config {
200+ Some ( cfg) if cfg. fee_rate_bps > 0 => {
201+ let fee = amount * ( cfg. fee_rate_bps as i128 ) / 10_000 ;
202+ if fee > 0 {
203+ let token_client = token:: Client :: new ( env, token_address) ;
204+ let contract_address = env. current_contract_address ( ) ;
205+ token_client. transfer ( & contract_address, & cfg. treasury , & fee) ;
206+
207+ env. events ( ) . publish (
208+ ( Symbol :: new ( env, "fee_collected" ) , stream_id) ,
209+ FeeCollectedEvent {
210+ stream_id,
211+ treasury : cfg. treasury ,
212+ fee_amount : fee,
213+ token : token_address. clone ( ) ,
214+ } ,
215+ ) ;
216+ }
217+ amount - fee
218+ }
219+ _ => amount,
220+ }
221+ }
222+
223+ // ─── Stream Operations ───────────────────────────────────────────
224+
81225 pub fn create_stream (
82226 env : Env ,
83227 sender : Address ,
@@ -94,18 +238,22 @@ impl StreamContract {
94238
95239 let stream_id = Self :: get_next_stream_id ( & env) ;
96240 let start_time = env. ledger ( ) . timestamp ( ) ;
97- let rate_per_second = amount / ( duration as i128 ) ;
98241
242+ // Transfer full amount from sender to contract
99243 let token_client = token:: Client :: new ( & env, & token_address) ;
100244 let contract_address = env. current_contract_address ( ) ;
101245 token_client. transfer ( & sender, & contract_address, & amount) ;
102246
247+ // Deduct protocol fee (if configured) and get net amount for the stream
248+ let net_amount = Self :: collect_fee ( & env, & token_address, amount, stream_id) ;
249+ let rate_per_second = net_amount / ( duration as i128 ) ;
250+
103251 let stream = Stream {
104252 sender : sender. clone ( ) ,
105253 recipient : recipient. clone ( ) ,
106254 token_address : token_address. clone ( ) ,
107255 rate_per_second,
108- deposited_amount : amount ,
256+ deposited_amount : net_amount ,
109257 withdrawn_amount : 0 ,
110258 start_time,
111259 last_update_time : start_time,
@@ -258,11 +406,15 @@ impl StreamContract {
258406 return Err ( StreamError :: StreamInactive ) ;
259407 }
260408
409+ // Transfer full amount from sender to contract
261410 let token_client = token:: Client :: new ( & env, & stream. token_address ) ;
262411 let contract_address = env. current_contract_address ( ) ;
263412 token_client. transfer ( & sender, & contract_address, & amount) ;
264413
265- stream. deposited_amount += amount;
414+ // Deduct protocol fee (if configured) and add net amount to stream
415+ let net_amount = Self :: collect_fee ( & env, & stream. token_address , amount, stream_id) ;
416+
417+ stream. deposited_amount += net_amount;
266418 stream. last_update_time = env. ledger ( ) . timestamp ( ) ;
267419
268420 storage. set ( & stream_key, & stream) ;
@@ -272,7 +424,7 @@ impl StreamContract {
272424 StreamToppedUpEvent {
273425 stream_id,
274426 sender,
275- amount,
427+ amount : net_amount ,
276428 new_deposited_amount : stream. deposited_amount ,
277429 } ,
278430 ) ;
0 commit comments