1use borsh::{BorshDeserialize, BorshSerialize};
17use solana_program::{
18 entrypoint::ProgramResult,
19 msg,
20 program::{invoke, invoke_signed},
21 program_error::ProgramError,
22 program_pack::Pack,
23 pubkey::Pubkey,
24 system_instruction, system_program, sysvar,
25 sysvar::{clock::Clock, fees::Fees, rent::Rent, Sysvar},
26};
27use spl_associated_token_account::{create_associated_token_account, get_associated_token_address};
28
29use crate::state::{
30 CancelAccounts, InitializeAccounts, StreamInstruction, TokenStreamData, TransferAccounts,
31 WithdrawAccounts,
32};
33use crate::utils::{
34 duration_sanity, encode_base10, pretty_time, unpack_mint_account, unpack_token_account,
35};
36
37pub fn create(
44 program_id: &Pubkey,
45 acc: InitializeAccounts,
46 ix: StreamInstruction,
47) -> ProgramResult {
48 msg!("Initializing SPL token stream");
49
50 if !acc.escrow_tokens.data_is_empty() || !acc.metadata.data_is_empty() {
51 return Err(ProgramError::AccountAlreadyInitialized);
52 }
53
54 if !acc.sender.is_writable
55 || !acc.sender_tokens.is_writable
56 || !acc.recipient.is_writable
57 || !acc.recipient_tokens.is_writable
58 || !acc.metadata.is_writable
59 || !acc.escrow_tokens.is_writable
60 {
61 return Err(ProgramError::InvalidAccountData);
62 }
63
64 let (escrow_tokens_pubkey, nonce) =
65 Pubkey::find_program_address(&[acc.metadata.key.as_ref()], program_id);
66 let recipient_tokens_key = get_associated_token_address(acc.recipient.key, acc.mint.key);
67
68 if acc.system_program.key != &system_program::id()
69 || acc.token_program.key != &spl_token::id()
70 || acc.rent.key != &sysvar::rent::id()
71 || acc.escrow_tokens.key != &escrow_tokens_pubkey
72 || acc.recipient_tokens.key != &recipient_tokens_key
73 {
74 return Err(ProgramError::InvalidAccountData);
75 }
76
77 if !acc.sender.is_signer || !acc.metadata.is_signer {
78 return Err(ProgramError::MissingRequiredSignature);
79 }
80
81 let sender_token_info = unpack_token_account(&acc.sender_tokens)?;
82 let mint_info = unpack_mint_account(&acc.mint)?;
83
84 if &sender_token_info.mint != acc.mint.key {
85 return Err(ProgramError::Custom(3));
87 }
88
89 let now = Clock::get()?.unix_timestamp as u64;
90 if !duration_sanity(now, ix.start_time, ix.end_time, ix.cliff) {
91 msg!("Error: Given timestamps are invalid");
92 return Err(ProgramError::InvalidArgument);
93 }
94
95 let metadata_struct_size = std::mem::size_of::<TokenStreamData>();
97 let tokens_struct_size = spl_token::state::Account::LEN;
98 let cluster_rent = Rent::get()?;
99 let metadata_rent = cluster_rent.minimum_balance(metadata_struct_size);
100 let mut tokens_rent = cluster_rent.minimum_balance(tokens_struct_size);
101 if acc.recipient_tokens.data_is_empty() {
102 tokens_rent += cluster_rent.minimum_balance(tokens_struct_size);
103 }
104
105 let fees = Fees::get()?;
106 let lps = fees.fee_calculator.lamports_per_signature;
107
108 if acc.sender.lamports() < metadata_rent + tokens_rent + (2 * lps) {
109 msg!("Error: Insufficient funds in {}", acc.sender.key);
110 return Err(ProgramError::InsufficientFunds);
111 }
112
113 if sender_token_info.amount < ix.total_amount {
114 msg!("Error: Insufficient tokens in sender's wallet");
115 return Err(ProgramError::InsufficientFunds);
116 }
117
118 let metadata = TokenStreamData::new(
119 now,
120 *acc.sender.key,
121 *acc.sender_tokens.key,
122 *acc.recipient.key,
123 *acc.recipient_tokens.key,
124 *acc.mint.key,
125 *acc.escrow_tokens.key,
126 ix.start_time,
127 ix.end_time,
128 ix.total_amount,
129 ix.period,
130 ix.cliff,
131 ix.cliff_amount,
132 );
133 let bytes = metadata.try_to_vec()?;
134
135 if acc.recipient_tokens.data_is_empty() {
136 msg!("Initializing recipient's associated token account");
137 invoke(
138 &create_associated_token_account(acc.sender.key, acc.recipient.key, acc.mint.key),
139 &[
140 acc.sender.clone(),
141 acc.recipient_tokens.clone(),
142 acc.recipient.clone(),
143 acc.mint.clone(),
144 acc.system_program.clone(),
145 acc.token_program.clone(),
146 acc.rent.clone(),
147 ],
148 )?;
149 }
150
151 msg!("Creating account for holding metadata");
152 invoke(
153 &system_instruction::create_account(
154 acc.sender.key,
155 acc.metadata.key,
156 metadata_rent,
157 metadata_struct_size as u64,
158 program_id,
159 ),
160 &[
161 acc.sender.clone(),
162 acc.metadata.clone(),
163 acc.system_program.clone(),
164 ],
165 )?;
166
167 let mut data = acc.metadata.try_borrow_mut_data()?;
169 data[0..bytes.len()].clone_from_slice(&bytes);
170
171 let seeds = [acc.metadata.key.as_ref(), &[nonce]];
172 msg!("Creating account for holding tokens");
173 invoke_signed(
174 &system_instruction::create_account(
175 acc.sender.key,
176 acc.escrow_tokens.key,
177 cluster_rent.minimum_balance(tokens_struct_size),
178 tokens_struct_size as u64,
179 &spl_token::id(),
180 ),
181 &[
182 acc.sender.clone(),
183 acc.escrow_tokens.clone(),
184 acc.system_program.clone(),
185 ],
186 &[&seeds],
187 )?;
188
189 msg!("Initializing escrow account for {} token", acc.mint.key);
190 invoke(
191 &spl_token::instruction::initialize_account(
192 acc.token_program.key,
193 acc.escrow_tokens.key,
194 acc.mint.key,
195 acc.escrow_tokens.key,
196 )?,
197 &[
198 acc.token_program.clone(),
199 acc.escrow_tokens.clone(),
200 acc.mint.clone(),
201 acc.escrow_tokens.clone(),
202 acc.rent.clone(),
203 ],
204 )?;
205
206 msg!("Moving funds into escrow account");
207 invoke(
208 &spl_token::instruction::transfer(
209 acc.token_program.key,
210 acc.sender_tokens.key,
211 acc.escrow_tokens.key,
212 acc.sender.key,
213 &[],
214 metadata.ix.total_amount,
215 )?,
216 &[
217 acc.sender_tokens.clone(),
218 acc.escrow_tokens.clone(),
219 acc.sender.clone(),
220 acc.token_program.clone(),
221 ],
222 )?;
223
224 msg!(
225 "Successfully initialized {} {} token stream for {}",
226 encode_base10(metadata.ix.total_amount, mint_info.decimals.into()),
227 metadata.mint,
228 acc.recipient.key
229 );
230 msg!("Called by {}", acc.sender.key);
231 msg!("Metadata written in {}", acc.metadata.key);
232 msg!("Funds locked in {}", acc.escrow_tokens.key);
233 msg!(
234 "Stream duration is {}",
235 pretty_time(metadata.ix.end_time - metadata.ix.start_time)
236 );
237
238 if metadata.ix.cliff > 0 && metadata.ix.cliff_amount > 0 {
239 msg!("Cliff happens at {}", pretty_time(metadata.ix.cliff));
240 }
241
242 Ok(())
243}
244
245pub fn withdraw(program_id: &Pubkey, acc: WithdrawAccounts, amount: u64) -> ProgramResult {
252 msg!("Withdrawing from SPL token stream");
253
254 if acc.escrow_tokens.data_is_empty()
255 || acc.escrow_tokens.owner != &spl_token::id()
256 || acc.metadata.data_is_empty()
257 || acc.metadata.owner != program_id
258 {
259 return Err(ProgramError::UninitializedAccount);
260 }
261
262 if !acc.recipient.is_writable
263 || !acc.recipient_tokens.is_writable
264 || !acc.metadata.is_writable
265 || !acc.escrow_tokens.is_writable
266 {
267 return Err(ProgramError::InvalidAccountData);
268 }
269
270 let (escrow_tokens_pubkey, nonce) =
271 Pubkey::find_program_address(&[acc.metadata.key.as_ref()], program_id);
272 let recipient_tokens_key = get_associated_token_address(acc.recipient.key, acc.mint.key);
273
274 if acc.token_program.key != &spl_token::id()
275 || acc.escrow_tokens.key != &escrow_tokens_pubkey
276 || acc.recipient_tokens.key != &recipient_tokens_key
277 || acc.withdraw_authority.key != acc.recipient.key
278 {
279 return Err(ProgramError::InvalidAccountData);
280 }
281
282 if !acc.withdraw_authority.is_signer {
283 return Err(ProgramError::MissingRequiredSignature);
284 }
285
286 let mut data = acc.metadata.try_borrow_mut_data()?;
287 let mut metadata = match TokenStreamData::try_from_slice(&data) {
288 Ok(v) => v,
289 Err(_) => return Err(ProgramError::InvalidAccountData),
290 };
291
292 let mint_info = unpack_mint_account(&acc.mint)?;
293
294 if acc.recipient.key != &metadata.recipient
295 || acc.recipient_tokens.key != &metadata.recipient_tokens
296 || acc.mint.key != &metadata.mint
297 || acc.escrow_tokens.key != &metadata.escrow_tokens
298 {
299 msg!("Error: Metadata does not match given accounts");
300 return Err(ProgramError::InvalidAccountData);
301 }
302
303 let now = Clock::get()?.unix_timestamp as u64;
304 let available = metadata.available(now);
305 let requested: u64;
306
307 if amount > available {
308 msg!("Amount requested for withdraw is more than what is available");
309 return Err(ProgramError::InvalidArgument);
310 }
311
312 if amount == 0 {
314 requested = available;
315 } else {
316 requested = amount;
317 }
318
319 let seeds = [acc.metadata.key.as_ref(), &[nonce]];
320 invoke_signed(
321 &spl_token::instruction::transfer(
322 acc.token_program.key,
323 acc.escrow_tokens.key,
324 acc.recipient_tokens.key,
325 acc.escrow_tokens.key,
326 &[],
327 requested,
328 )?,
329 &[
330 acc.escrow_tokens.clone(), acc.recipient_tokens.clone(), acc.escrow_tokens.clone(), acc.token_program.clone(), ],
335 &[&seeds],
336 )?;
337
338 metadata.withdrawn_amount += requested;
339 metadata.last_withdrawn_at = now;
340 let bytes = metadata.try_to_vec()?;
341 data[0..bytes.len()].clone_from_slice(&bytes);
342
343 if metadata.withdrawn_amount == metadata.ix.total_amount {
345 if !acc.sender.is_writable || acc.sender.key != &metadata.sender {
346 return Err(ProgramError::InvalidAccountData);
347 }
348 let escrow_tokens_rent = acc.escrow_tokens.lamports();
354 msg!(
356 "Returning {} lamports (rent) to {}",
357 escrow_tokens_rent,
358 acc.sender.key
359 );
360 invoke_signed(
361 &spl_token::instruction::close_account(
362 acc.token_program.key,
363 acc.escrow_tokens.key,
364 acc.sender.key,
365 acc.escrow_tokens.key,
366 &[],
367 )?,
368 &[
369 acc.escrow_tokens.clone(),
370 acc.sender.clone(),
371 acc.escrow_tokens.clone(),
372 ],
373 &[&seeds],
374 )?;
375 }
376
377 msg!(
378 "Withdrawn: {} {} tokens",
379 encode_base10(requested, mint_info.decimals.into()),
380 metadata.mint
381 );
382 msg!(
383 "Remaining: {} {} tokens",
384 encode_base10(
385 metadata.ix.total_amount - metadata.withdrawn_amount,
386 mint_info.decimals.into()
387 ),
388 metadata.mint
389 );
390
391 Ok(())
392}
393
394pub fn cancel(program_id: &Pubkey, acc: CancelAccounts) -> ProgramResult {
400 msg!("Cancelling SPL token stream");
401
402 if acc.escrow_tokens.data_is_empty()
403 || acc.escrow_tokens.owner != &spl_token::id()
404 || acc.metadata.data_is_empty()
405 || acc.metadata.owner != program_id
406 {
407 return Err(ProgramError::UninitializedAccount);
408 }
409
410 if !acc.sender.is_writable
411 || !acc.sender_tokens.is_writable
412 || !acc.recipient.is_writable
413 || !acc.recipient_tokens.is_writable
414 || !acc.metadata.is_writable
415 || !acc.escrow_tokens.is_writable
416 {
417 return Err(ProgramError::InvalidAccountData);
418 }
419
420 let (escrow_tokens_pubkey, nonce) =
421 Pubkey::find_program_address(&[acc.metadata.key.as_ref()], program_id);
422 let recipient_tokens_key = get_associated_token_address(acc.recipient.key, acc.mint.key);
423
424 if acc.token_program.key != &spl_token::id()
425 || acc.escrow_tokens.key != &escrow_tokens_pubkey
426 || acc.recipient_tokens.key != &recipient_tokens_key
427 || acc.cancel_authority.key != acc.sender.key
428 {
429 return Err(ProgramError::InvalidAccountData);
430 }
431
432 if !acc.cancel_authority.is_signer {
433 return Err(ProgramError::MissingRequiredSignature);
434 }
435
436 let mut data = acc.metadata.try_borrow_mut_data()?;
437 let mut metadata = match TokenStreamData::try_from_slice(&data) {
438 Ok(v) => v,
439 Err(_) => return Err(ProgramError::InvalidAccountData),
440 };
441
442 let mint_info = unpack_mint_account(&acc.mint)?;
443
444 if acc.sender.key != &metadata.sender
445 || acc.sender_tokens.key != &metadata.sender_tokens
446 || acc.recipient.key != &metadata.recipient
447 || acc.recipient_tokens.key != &metadata.recipient_tokens
448 || acc.mint.key != &metadata.mint
449 || acc.escrow_tokens.key != &metadata.escrow_tokens
450 {
451 return Err(ProgramError::InvalidAccountData);
452 }
453
454 let now = Clock::get()?.unix_timestamp as u64;
455 let available = metadata.available(now);
456
457 let seeds = [acc.metadata.key.as_ref(), &[nonce]];
458 invoke_signed(
459 &spl_token::instruction::transfer(
460 acc.token_program.key,
461 acc.escrow_tokens.key,
462 acc.recipient_tokens.key,
463 acc.escrow_tokens.key,
464 &[],
465 available,
466 )?,
467 &[
468 acc.escrow_tokens.clone(), acc.recipient_tokens.clone(), acc.escrow_tokens.clone(), acc.token_program.clone(), ],
473 &[&seeds],
474 )?;
475
476 metadata.withdrawn_amount += available;
477 let remains = metadata.ix.total_amount - metadata.withdrawn_amount;
478
479 if remains > 0 {
481 invoke_signed(
482 &spl_token::instruction::transfer(
483 acc.token_program.key,
484 acc.escrow_tokens.key,
485 acc.sender_tokens.key,
486 acc.escrow_tokens.key,
487 &[],
488 remains,
489 )?,
490 &[
491 acc.escrow_tokens.clone(),
492 acc.sender_tokens.clone(),
493 acc.escrow_tokens.clone(),
494 acc.token_program.clone(),
495 ],
496 &[&seeds],
497 )?;
498 }
499
500 let rent_escrow_tokens = acc.escrow_tokens.lamports();
501 invoke_signed(
505 &spl_token::instruction::close_account(
506 acc.token_program.key,
507 acc.escrow_tokens.key,
508 acc.sender.key,
509 acc.escrow_tokens.key,
510 &[],
511 )?,
512 &[
513 acc.escrow_tokens.clone(),
514 acc.sender.clone(),
515 acc.escrow_tokens.clone(),
516 ],
517 &[&seeds],
518 )?;
519
520 metadata.last_withdrawn_at = now;
521 metadata.canceled_at = now;
522 let bytes = metadata.try_to_vec().unwrap();
524 data[0..bytes.len()].clone_from_slice(&bytes);
525
526 msg!(
527 "Transferred: {} {} tokens",
528 encode_base10(available, mint_info.decimals.into()),
529 metadata.mint
530 );
531 msg!(
532 "Returned: {} {} tokens",
533 encode_base10(remains, mint_info.decimals.into()),
534 metadata.mint
535 );
536 msg!(
537 "Returned rent: {} lamports",
538 rent_escrow_tokens );
540
541 Ok(())
542}
543
544pub fn transfer_recipient(program_id: &Pubkey, acc: TransferAccounts) -> ProgramResult {
545 msg!("Transferring stream recipient");
546 if acc.metadata.data_is_empty()
547 || acc.metadata.owner != program_id
548 || acc.escrow_tokens.data_is_empty()
549 || acc.escrow_tokens.owner != &spl_token::id()
550 {
551 return Err(ProgramError::UninitializedAccount);
552 }
553
554 if !acc.existing_recipient.is_signer {
555 return Err(ProgramError::MissingRequiredSignature);
556 }
557
558 if !acc.metadata.is_writable
559 || !acc.existing_recipient.is_writable
560 || !acc.new_recipient_tokens.is_writable
561 {
562 return Err(ProgramError::InvalidAccountData);
563 }
564
565 let mut data = acc.metadata.try_borrow_mut_data()?;
566 let mut metadata = match TokenStreamData::try_from_slice(&data) {
567 Ok(v) => v,
568 Err(_) => return Err(ProgramError::InvalidAccountData),
569 };
570
571 let (escrow_tokens_pubkey, _) =
572 Pubkey::find_program_address(&[acc.metadata.key.as_ref()], program_id);
573 let new_recipient_tokens_key =
574 get_associated_token_address(acc.new_recipient.key, acc.mint.key);
575
576 if acc.new_recipient_tokens.key != &new_recipient_tokens_key
577 || acc.mint.key != &metadata.mint
578 || acc.existing_recipient.key != &metadata.recipient
579 || acc.escrow_tokens.key != &metadata.escrow_tokens
580 || acc.escrow_tokens.key != &escrow_tokens_pubkey
581 || acc.token_program.key != &spl_token::id()
582 || acc.system_program.key != &system_program::id()
583 || acc.rent.key != &sysvar::rent::id()
584 {
585 return Err(ProgramError::InvalidAccountData);
586 }
587
588 if acc.new_recipient_tokens.data_is_empty() {
589 let tokens_struct_size = spl_token::state::Account::LEN;
591 let cluster_rent = Rent::get()?;
592 let tokens_rent = cluster_rent.minimum_balance(tokens_struct_size);
593 let fees = Fees::get()?;
594 let lps = fees.fee_calculator.lamports_per_signature;
595
596 if acc.existing_recipient.lamports() < tokens_rent + lps {
597 msg!(
598 "Error: Insufficient funds in {}",
599 acc.existing_recipient.key
600 );
601 return Err(ProgramError::InsufficientFunds);
602 }
603
604 msg!("Initializing new recipient's associated token account");
605 invoke(
606 &create_associated_token_account(
607 acc.existing_recipient.key,
608 acc.new_recipient.key,
609 acc.mint.key,
610 ),
611 &[
612 acc.existing_recipient.clone(), acc.new_recipient_tokens.clone(), acc.new_recipient.clone(), acc.mint.clone(),
616 acc.system_program.clone(),
617 acc.token_program.clone(),
618 acc.rent.clone(),
619 ],
620 )?;
621 }
622
623 metadata.recipient = *acc.new_recipient.key;
625 metadata.recipient_tokens = *acc.new_recipient_tokens.key;
626
627 let bytes = metadata.try_to_vec()?;
628 data[0..bytes.len()].clone_from_slice(&bytes);
629
630 Ok(())
631}