1use steel::*;
2
3use crate::{
4 consts::{
5 BASIS_POINTS_DENOMINATOR, DEFAULT_FEE_BPS, PAYMENT_STATE_FUNDED, PAYMENT_STATE_REFUNDED,
6 PAYMENT_STATE_RELEASED,
7 },
8 error::EscrowError,
9 instruction::*,
10 state::{bank_pda, escrow_pda, normalize_payment_uid, payment_pda, sol_storage_pda, Payment},
11};
12
13pub struct EscrowSdk;
15
16impl EscrowSdk {
17 pub fn program_id() -> Pubkey {
19 crate::ID
20 }
21
22 pub fn bank_pda() -> (Pubkey, u8) {
24 bank_pda()
25 }
26
27 pub fn authority_transfer_pda(bank: Pubkey) -> (Pubkey, u8) {
29 crate::state::authority_transfer_pda(bank)
30 }
31
32 pub fn config_pda() -> (Pubkey, u8) {
34 crate::state::config_pda()
35 }
36
37 pub fn escrow_pda(mint: Pubkey) -> (Pubkey, u8) {
39 let (bank_pda, _) = Self::bank_pda();
40 escrow_pda(mint, bank_pda)
41 }
42
43 pub fn payment_pda(payment_uid: &str, bank: Pubkey) -> (Pubkey, u8) {
45 payment_pda(payment_uid, bank)
46 }
47
48 pub fn sol_storage_pda(mint: Pubkey, bank: Pubkey, escrow: Pubkey) -> (Pubkey, u8) {
50 sol_storage_pda(mint, bank, escrow)
51 }
52
53 pub fn associated_token_account(wallet: Pubkey, mint: Pubkey) -> Pubkey {
55 Self::associated_token_account_with_program(wallet, mint, spl_token::ID)
56 }
57
58 pub fn associated_token_account_with_program(
60 wallet: Pubkey,
61 mint: Pubkey,
62 token_program: Pubkey,
63 ) -> Pubkey {
64 spl_associated_token_account::get_associated_token_address_with_program_id(
65 &wallet,
66 &mint,
67 &token_program,
68 )
69 }
70
71 pub fn escrow_token_account(mint: Pubkey) -> Pubkey {
73 Self::escrow_token_account_with_program(mint, spl_token::ID)
74 }
75
76 pub fn escrow_token_account_with_program(mint: Pubkey, token_program: Pubkey) -> Pubkey {
78 let (escrow_pda, _) = Self::escrow_pda(mint);
79 Self::associated_token_account_with_program(escrow_pda, mint, token_program)
80 }
81
82 pub fn calculate_gross_quote(
95 desired_net: u64,
96 fee_bps: u16,
97 min_fee_amount: u64,
98 oracle_fee_bps: u16,
99 ) -> u64 {
100 let numerator_a =
104 (desired_net as u128 + min_fee_amount as u128).saturating_mul(BASIS_POINTS_DENOMINATOR);
105 let denominator_a = (BASIS_POINTS_DENOMINATOR - oracle_fee_bps as u128).max(1);
106 let amount_a = (numerator_a.saturating_add(denominator_a - 1) / denominator_a) as u64;
107
108 let numerator_b = (desired_net as u128).saturating_mul(BASIS_POINTS_DENOMINATOR);
112 let denominator_b =
113 (BASIS_POINTS_DENOMINATOR - fee_bps as u128 - oracle_fee_bps as u128).max(1);
114 let amount_b = (numerator_b.saturating_add(denominator_b - 1) / denominator_b) as u64;
115
116 amount_a.max(amount_b)
118 }
119
120 pub fn initialize(signer: Pubkey, fee_bps: Option<u16>) -> Instruction {
126 let (bank_pda, _) = Self::bank_pda();
127 let (config_pda, _) = Self::config_pda();
128 Instruction {
129 program_id: Self::program_id(),
130 accounts: vec![
131 AccountMeta::new(signer, true),
132 AccountMeta::new(bank_pda, false),
133 AccountMeta::new(config_pda, false),
134 AccountMeta::new_readonly(solana_program::system_program::ID, false),
135 AccountMeta::new_readonly(solana_program::sysvar::rent::ID, false),
136 ],
137 data: Initialize {
138 fee_bps: (fee_bps.unwrap_or(DEFAULT_FEE_BPS)).to_le_bytes(),
139 _padding: [0; 6], }
141 .to_bytes(),
142 }
143 }
144
145 #[allow(clippy::too_many_arguments)]
154 pub fn open_escrow(
155 signer: Pubkey,
156 payer: Pubkey,
157 mint: Pubkey,
158 min_payment_amount: u64,
159 max_payment_amount: u64,
160 min_fee_amount: u64,
161 fee_bps: u16,
162 oracle_fee_bps: u16,
163 ) -> Instruction {
164 Self::open_escrow_with_token_program(
165 signer,
166 payer,
167 mint,
168 min_payment_amount,
169 max_payment_amount,
170 min_fee_amount,
171 fee_bps,
172 oracle_fee_bps,
173 spl_token::ID,
174 )
175 }
176
177 #[allow(clippy::too_many_arguments)]
179 pub fn open_escrow_with_token_program(
180 signer: Pubkey,
181 payer: Pubkey,
182 mint: Pubkey,
183 min_payment_amount: u64,
184 max_payment_amount: u64,
185 min_fee_amount: u64,
186 fee_bps: u16,
187 oracle_fee_bps: u16,
188 token_program: Pubkey,
189 ) -> Instruction {
190 let (bank_pda, _) = Self::bank_pda();
191 let (escrow_pda, _) = Self::escrow_pda(mint);
192 let escrow_tokens = if mint == Pubkey::default() {
193 escrow_pda
195 } else {
196 Self::escrow_token_account_with_program(mint, token_program)
198 };
199
200 let mut accounts = vec![
201 AccountMeta::new(signer, true),
202 AccountMeta::new(payer, true),
203 AccountMeta::new_readonly(bank_pda, false),
204 AccountMeta::new(escrow_pda, false),
205 AccountMeta::new(escrow_tokens, false),
206 AccountMeta::new_readonly(mint, false),
207 AccountMeta::new_readonly(solana_program::system_program::ID, false),
208 AccountMeta::new_readonly(token_program, false),
209 AccountMeta::new_readonly(spl_associated_token_account::ID, false),
210 AccountMeta::new_readonly(solana_program::sysvar::rent::ID, false),
211 ];
212
213 if mint == Pubkey::default() {
215 let (sol_storage_pda, _) = Self::sol_storage_pda(mint, bank_pda, escrow_pda);
216 accounts.push(AccountMeta::new(sol_storage_pda, false));
217 }
218
219 Instruction {
220 program_id: Self::program_id(),
221 accounts,
222 data: OpenEscrow {
223 min_payment_amount: min_payment_amount.to_le_bytes(),
224 max_payment_amount: max_payment_amount.to_le_bytes(),
225 min_fee_amount: min_fee_amount.to_le_bytes(),
226 fee_bps: fee_bps.to_le_bytes(),
227 oracle_fee_bps: oracle_fee_bps.to_le_bytes(),
228 _padding: [0; 4],
229 }
230 .to_bytes(),
231 }
232 }
233
234 #[allow(clippy::too_many_arguments)]
238 pub fn fund_payment(
239 buyer: Pubkey,
240 buyer_tokens: Option<Pubkey>, seller: Pubkey,
242 mint: Pubkey,
243 amount: u64,
244 ttl_seconds: i64,
245 payment_uid: &str,
246 sla_hash: [u8; 32],
247 oracle_authority: Pubkey,
248 ) -> Instruction {
249 Self::fund_payment_with_token_program(
250 buyer,
251 buyer_tokens,
252 seller,
253 mint,
254 amount,
255 ttl_seconds,
256 payment_uid,
257 sla_hash,
258 oracle_authority,
259 spl_token::ID,
260 )
261 }
262
263 #[allow(clippy::too_many_arguments)]
265 pub fn fund_payment_with_token_program(
266 buyer: Pubkey,
267 buyer_tokens: Option<Pubkey>,
268 seller: Pubkey,
269 mint: Pubkey,
270 amount: u64,
271 ttl_seconds: i64,
272 payment_uid: &str,
273 sla_hash: [u8; 32],
274 oracle_authority: Pubkey,
275 token_program: Pubkey,
276 ) -> Instruction {
277 let (bank_pda, _) = Self::bank_pda();
278 let (config_pda, _) = Self::config_pda();
279 let (escrow_pda, _) = Self::escrow_pda(mint);
280 let (payment_pda, _) = crate::state::payment_pda(payment_uid, bank_pda);
281
282 let is_sol = mint == Pubkey::default();
283
284 let mut accounts = vec![
285 AccountMeta::new(buyer, true),
286 AccountMeta::new_readonly(bank_pda, false),
287 AccountMeta::new_readonly(config_pda, false),
288 AccountMeta::new(escrow_pda, false),
289 AccountMeta::new(payment_pda, false),
290 AccountMeta::new_readonly(mint, false),
291 ];
292
293 if is_sol {
294 let (sol_storage_pda, _) = Self::sol_storage_pda(mint, bank_pda, escrow_pda);
296 accounts.push(AccountMeta::new(sol_storage_pda, false));
297 accounts.push(AccountMeta::new_readonly(
298 solana_program::system_program::ID,
299 false,
300 ));
301 } else {
302 let buyer_tokens = buyer_tokens.expect("buyer_tokens required for SPL tokens");
304 let escrow_tokens = Self::escrow_token_account_with_program(mint, token_program);
305 accounts.push(AccountMeta::new(escrow_tokens, false));
306 accounts.push(AccountMeta::new(buyer_tokens, false));
307 accounts.push(AccountMeta::new_readonly(token_program, false));
308 accounts.push(AccountMeta::new_readonly(
309 solana_program::system_program::ID,
310 false,
311 ));
312 }
313
314 Instruction {
315 program_id: Self::program_id(),
316 accounts,
317 data: FundPayment {
318 seller,
319 mint,
320 oracle_authority,
321 payment_uid: normalize_payment_uid(payment_uid),
322 sla_hash,
323 amount: amount.to_le_bytes(),
324 ttl_seconds: ttl_seconds.to_le_bytes(),
325 }
326 .to_bytes(),
327 }
328 }
329
330 pub fn release_payment(
334 caller: Pubkey,
335 seller_tokens: Option<Pubkey>, seller: Option<Pubkey>, mint: Pubkey,
338 payment_uid: &str,
339 oracle_tokens: Option<Pubkey>,
340 oracle_authority: Option<Pubkey>,
341 ) -> Instruction {
342 Self::release_payment_with_token_program(
343 caller,
344 seller_tokens,
345 seller,
346 mint,
347 payment_uid,
348 oracle_tokens,
349 oracle_authority,
350 spl_token::ID,
351 )
352 }
353
354 #[allow(clippy::too_many_arguments)]
356 pub fn release_payment_with_token_program(
357 caller: Pubkey,
358 seller_tokens: Option<Pubkey>,
359 seller: Option<Pubkey>,
360 mint: Pubkey,
361 payment_uid: &str,
362 oracle_tokens: Option<Pubkey>,
363 oracle_authority: Option<Pubkey>,
364 token_program: Pubkey,
365 ) -> Instruction {
366 let (bank_pda, _) = Self::bank_pda();
367 let (config_pda, _) = Self::config_pda();
368 let (escrow_pda, _) = Self::escrow_pda(mint);
369 let (payment_pda, _) = Self::payment_pda(payment_uid, bank_pda);
370
371 let is_sol = mint == Pubkey::default();
372
373 let mut accounts = vec![
374 AccountMeta::new(caller, true),
375 AccountMeta::new_readonly(bank_pda, false),
376 AccountMeta::new_readonly(config_pda, false),
377 AccountMeta::new(escrow_pda, false),
378 AccountMeta::new(payment_pda, false),
379 AccountMeta::new_readonly(mint, false),
380 ];
381
382 if is_sol {
383 let (sol_storage_pda, _) = Self::sol_storage_pda(mint, bank_pda, escrow_pda);
385 accounts.push(AccountMeta::new(sol_storage_pda, false));
386 accounts.push(AccountMeta::new(
387 seller.expect("seller required for SOL"),
388 false,
389 ));
390 accounts.push(AccountMeta::new_readonly(
391 solana_program::system_program::ID,
392 false,
393 ));
394 if let Some(oracle_authority) = oracle_authority {
395 accounts.push(AccountMeta::new(oracle_authority, false));
396 }
397 } else {
398 let seller_tokens = seller_tokens.expect("seller_tokens required for SPL tokens");
400 let seller = seller.expect("seller account required for SPL tokens");
401 let escrow_tokens = Self::escrow_token_account_with_program(mint, token_program);
402 accounts.push(AccountMeta::new(escrow_tokens, false));
403 accounts.push(AccountMeta::new(seller_tokens, false));
404 accounts.push(AccountMeta::new(seller, false));
405 accounts.push(AccountMeta::new_readonly(token_program, false));
406 accounts.push(AccountMeta::new_readonly(
407 spl_associated_token_account::ID,
408 false,
409 ));
410 accounts.push(AccountMeta::new_readonly(
411 solana_program::system_program::ID,
412 false,
413 ));
414 if let (Some(oracle_tokens), Some(oracle_authority)) = (oracle_tokens, oracle_authority)
415 {
416 accounts.push(AccountMeta::new(oracle_tokens, false));
417 accounts.push(AccountMeta::new(oracle_authority, false));
418 }
419 }
420
421 Instruction {
422 program_id: Self::program_id(),
423 accounts,
424 data: ReleasePayment {}.to_bytes(),
425 }
426 }
427
428 pub fn refund_payment(
432 caller: Pubkey,
433 buyer_tokens: Option<Pubkey>, mint: Pubkey,
435 payment_uid: &str,
436 oracle_tokens: Option<Pubkey>,
437 oracle_authority: Option<Pubkey>,
438 ) -> Instruction {
439 Self::refund_payment_with_token_program(
440 caller,
441 buyer_tokens,
442 mint,
443 payment_uid,
444 oracle_tokens,
445 oracle_authority,
446 spl_token::ID,
447 )
448 }
449
450 #[allow(clippy::too_many_arguments)]
452 pub fn refund_payment_with_token_program(
453 caller: Pubkey,
454 buyer_tokens: Option<Pubkey>,
455 mint: Pubkey,
456 payment_uid: &str,
457 oracle_tokens: Option<Pubkey>,
458 oracle_authority: Option<Pubkey>,
459 token_program: Pubkey,
460 ) -> Instruction {
461 let (bank_pda, _) = Self::bank_pda();
462 let (config_pda, _) = Self::config_pda();
463 let (escrow_pda, _) = Self::escrow_pda(mint);
464 let (payment_pda, _) = Self::payment_pda(payment_uid, bank_pda);
465
466 let is_sol = mint == Pubkey::default();
467
468 let mut accounts = vec![
469 AccountMeta::new(caller, true),
470 AccountMeta::new_readonly(bank_pda, false),
471 AccountMeta::new_readonly(config_pda, false),
472 AccountMeta::new(escrow_pda, false),
473 AccountMeta::new(payment_pda, false),
474 AccountMeta::new_readonly(mint, false),
475 ];
476
477 if is_sol {
478 let (sol_storage_pda, _) = Self::sol_storage_pda(mint, bank_pda, escrow_pda);
480 accounts.push(AccountMeta::new(sol_storage_pda, false));
481 accounts.push(AccountMeta::new(
482 buyer_tokens.expect("buyer required for SOL"),
483 false,
484 ));
485 accounts.push(AccountMeta::new_readonly(
486 solana_program::system_program::ID,
487 false,
488 ));
489 if let Some(oracle_authority) = oracle_authority {
490 accounts.push(AccountMeta::new(oracle_authority, false));
491 }
492 } else {
493 let buyer_tokens = buyer_tokens.expect("buyer_tokens required for SPL tokens");
495 let escrow_tokens = Self::escrow_token_account_with_program(mint, token_program);
496 accounts.push(AccountMeta::new(escrow_tokens, false));
497 accounts.push(AccountMeta::new(buyer_tokens, false));
498 accounts.push(AccountMeta::new_readonly(token_program, false));
499 if let (Some(oracle_tokens), Some(oracle_authority)) = (oracle_tokens, oracle_authority)
500 {
501 accounts.push(AccountMeta::new(oracle_tokens, false));
502 accounts.push(AccountMeta::new(oracle_authority, false));
503 accounts.push(AccountMeta::new_readonly(
504 spl_associated_token_account::ID,
505 false,
506 ));
507 accounts.push(AccountMeta::new_readonly(
508 solana_program::system_program::ID,
509 false,
510 ));
511 }
512 }
513
514 Instruction {
515 program_id: Self::program_id(),
516 accounts,
517 data: RefundPayment {}.to_bytes(),
518 }
519 }
520
521 pub fn submit_delivery(
523 caller: Pubkey,
524 mint: Pubkey,
525 payment_uid: &str,
526 delivery_hash: [u8; 32],
527 ) -> Instruction {
528 let (bank_pda, _) = Self::bank_pda();
529 let (config_pda, _) = Self::config_pda();
530 let (escrow_pda, _) = Self::escrow_pda(mint);
531 let (payment_pda, _) = Self::payment_pda(payment_uid, bank_pda);
532
533 Instruction {
534 program_id: Self::program_id(),
535 accounts: vec![
536 AccountMeta::new(caller, true), AccountMeta::new_readonly(bank_pda, false),
538 AccountMeta::new_readonly(config_pda, false),
539 AccountMeta::new_readonly(escrow_pda, false),
540 AccountMeta::new(payment_pda, false),
541 ],
542 data: SubmitDelivery { delivery_hash }.to_bytes(),
543 }
544 }
545
546 pub fn close_payment(
548 caller: Pubkey,
549 buyer: Pubkey,
550 mint: Pubkey,
551 payment_uid: &str,
552 ) -> Instruction {
553 let (bank_pda, _) = Self::bank_pda();
554 let (config_pda, _) = Self::config_pda();
555 let (escrow_pda, _) = Self::escrow_pda(mint);
556 let (payment_pda, _) = Self::payment_pda(payment_uid, bank_pda);
557
558 Instruction {
559 program_id: Self::program_id(),
560 accounts: vec![
561 AccountMeta::new(caller, true), AccountMeta::new(buyer, false), AccountMeta::new_readonly(bank_pda, false),
564 AccountMeta::new_readonly(config_pda, false),
565 AccountMeta::new(escrow_pda, false),
566 AccountMeta::new(payment_pda, false),
567 AccountMeta::new_readonly(solana_program::system_program::ID, false),
568 ],
569 data: ClosePayment {}.to_bytes(),
570 }
571 }
572
573 pub fn withdraw_fees(
577 authority: Pubkey,
578 beneficiary: Pubkey,
579 mint: Pubkey,
580 amount: u64,
581 ) -> Instruction {
582 Self::withdraw_fees_with_token_program(authority, beneficiary, mint, amount, spl_token::ID)
583 }
584
585 pub fn withdraw_fees_with_token_program(
587 authority: Pubkey,
588 beneficiary: Pubkey,
589 mint: Pubkey,
590 amount: u64,
591 token_program: Pubkey,
592 ) -> Instruction {
593 let (bank_pda, _) = Self::bank_pda();
594 let (config_pda, _) = Self::config_pda();
595 let (escrow_pda, _) = Self::escrow_pda(mint);
596
597 let is_sol = mint == Pubkey::default();
598
599 let mut accounts = vec![
600 AccountMeta::new(authority, true),
601 AccountMeta::new_readonly(bank_pda, false),
602 AccountMeta::new_readonly(config_pda, false),
603 AccountMeta::new(escrow_pda, false),
604 AccountMeta::new(beneficiary, false),
605 ];
606
607 if is_sol {
608 let (sol_storage_pda, _) = Self::sol_storage_pda(mint, bank_pda, escrow_pda);
610 accounts.push(AccountMeta::new(sol_storage_pda, false));
611 accounts.push(AccountMeta::new_readonly(
612 solana_program::system_program::ID,
613 false,
614 ));
615 } else {
616 let escrow_tokens = Self::escrow_token_account_with_program(mint, token_program);
618 let beneficiary_tokens =
619 Self::associated_token_account_with_program(beneficiary, mint, token_program);
620 accounts[4] = AccountMeta::new(escrow_tokens, false);
622 accounts.push(AccountMeta::new(beneficiary_tokens, false));
623 accounts.push(AccountMeta::new_readonly(mint, false));
624 accounts.push(AccountMeta::new_readonly(token_program, false));
625 }
626
627 Instruction {
628 program_id: Self::program_id(),
629 accounts,
630 data: WithdrawFees {
631 amount: amount.to_le_bytes(),
632 }
633 .to_bytes(),
634 }
635 }
636
637 pub fn extend_payment_ttl(
639 caller: Pubkey,
640 mint: Pubkey,
641 payment_uid: &str,
642 additional_seconds: i64,
643 ) -> Instruction {
644 let (bank_pda, _) = Self::bank_pda();
645 let (config_pda, _) = Self::config_pda();
646 let (escrow_pda, _) = Self::escrow_pda(mint);
647 let (payment_pda, _) = Self::payment_pda(payment_uid, bank_pda);
648
649 Instruction {
650 program_id: Self::program_id(),
651 accounts: vec![
652 AccountMeta::new(caller, true),
653 AccountMeta::new_readonly(bank_pda, false), AccountMeta::new_readonly(config_pda, false),
655 AccountMeta::new_readonly(escrow_pda, false),
656 AccountMeta::new(payment_pda, false), AccountMeta::new_readonly(solana_program::system_program::ID, false),
658 AccountMeta::new_readonly(solana_program::sysvar::rent::ID, false),
659 ],
660 data: ExtendPaymentTTL {
661 additional_seconds: additional_seconds.to_le_bytes(),
662 }
663 .to_bytes(),
664 }
665 }
666
667 pub fn close_escrow(authority: Pubkey, recipient: Pubkey, mint: Pubkey) -> Instruction {
669 Self::close_escrow_with_token_program(authority, recipient, mint, spl_token::ID)
670 }
671
672 pub fn close_escrow_with_token_program(
674 authority: Pubkey,
675 recipient: Pubkey,
676 mint: Pubkey,
677 token_program: Pubkey,
678 ) -> Instruction {
679 let (bank_pda, _) = Self::bank_pda();
680 let (escrow_pda, _) = Self::escrow_pda(mint);
681 let escrow_tokens = if mint == Pubkey::default() {
682 let (sol_storage_pda, _) = Self::sol_storage_pda(mint, bank_pda, escrow_pda);
683 sol_storage_pda
684 } else {
685 Self::escrow_token_account_with_program(mint, token_program)
686 };
687
688 Instruction {
689 program_id: Self::program_id(),
690 accounts: vec![
691 AccountMeta::new(authority, true),
692 AccountMeta::new_readonly(bank_pda, false),
693 AccountMeta::new(escrow_pda, false),
694 AccountMeta::new(escrow_tokens, false),
695 AccountMeta::new_readonly(mint, false),
696 AccountMeta::new_readonly(token_program, false),
697 AccountMeta::new(recipient, false),
698 ],
699 data: CloseEscrow {}.to_bytes(),
700 }
701 }
702
703 pub fn update_escrow_settings(
705 authority: Pubkey,
706 escrow: Pubkey,
707 fee_bps: u16,
708 min_payment_amount: u64,
709 max_payment_amount: u64,
710 min_fee_amount: u64,
711 oracle_fee_bps: u16,
712 ) -> Instruction {
713 let (bank_pda, _) = Self::bank_pda();
714 let (config_pda, _) = Self::config_pda();
715
716 Instruction {
717 program_id: Self::program_id(),
718 accounts: vec![
719 AccountMeta::new(authority, true),
720 AccountMeta::new_readonly(bank_pda, false), AccountMeta::new_readonly(config_pda, false),
722 AccountMeta::new(escrow, false), AccountMeta::new_readonly(solana_program::system_program::ID, false),
724 AccountMeta::new_readonly(solana_program::sysvar::rent::ID, false),
725 ],
726 data: UpdateEscrowSettings {
727 min_payment_amount: min_payment_amount.to_le_bytes(),
728 max_payment_amount: max_payment_amount.to_le_bytes(),
729 min_fee_amount: min_fee_amount.to_le_bytes(),
730 new_fee_bps: fee_bps.to_le_bytes(),
731 new_oracle_fee_bps: oracle_fee_bps.to_le_bytes(),
732 _padding: [0; 4],
733 }
734 .to_bytes(),
735 }
736 }
737
738 pub fn pause_escrow(authority: Pubkey, mint: Pubkey, pause: bool) -> Instruction {
740 let (bank_pda, _) = Self::bank_pda();
741 let (config_pda, _) = Self::config_pda();
742 let (escrow_pda, _) = Self::escrow_pda(mint);
743
744 Instruction {
745 program_id: Self::program_id(),
746 accounts: vec![
747 AccountMeta::new(authority, true),
748 AccountMeta::new_readonly(bank_pda, false), AccountMeta::new_readonly(config_pda, false),
750 AccountMeta::new(escrow_pda, false), AccountMeta::new_readonly(solana_program::system_program::ID, false),
752 AccountMeta::new_readonly(solana_program::sysvar::rent::ID, false),
753 ],
754 data: PauseEscrow {
755 pause: if pause { 1 } else { 0 },
756 }
757 .to_bytes(),
758 }
759 }
760
761 pub fn update_authority(current_authority: Pubkey, new_authority: Pubkey) -> Instruction {
763 let (bank_pda, _) = Self::bank_pda();
764 let (transfer_pda, _) = Self::authority_transfer_pda(bank_pda);
765
766 Instruction {
767 program_id: Self::program_id(),
768 accounts: vec![
769 AccountMeta::new(current_authority, true),
770 AccountMeta::new_readonly(bank_pda, false),
771 AccountMeta::new(transfer_pda, false),
772 AccountMeta::new_readonly(solana_program::system_program::ID, false),
773 ],
774 data: UpdateAuthority { new_authority }.to_bytes(),
775 }
776 }
777
778 pub fn accept_authority(new_authority: Pubkey) -> Instruction {
780 let (bank_pda, _) = Self::bank_pda();
781 let (transfer_pda, _) = Self::authority_transfer_pda(bank_pda);
782
783 Instruction {
784 program_id: Self::program_id(),
785 accounts: vec![
786 AccountMeta::new(new_authority, true), AccountMeta::new(bank_pda, false), AccountMeta::new(transfer_pda, false), ],
790 data: AcceptAuthority {}.to_bytes(),
791 }
792 }
793
794 pub fn cancel_authority_proposal(current_authority: Pubkey) -> Instruction {
796 let (bank_pda, _) = Self::bank_pda();
797 let (transfer_pda, _) = Self::authority_transfer_pda(bank_pda);
798
799 Instruction {
800 program_id: Self::program_id(),
801 accounts: vec![
802 AccountMeta::new(current_authority, true), AccountMeta::new_readonly(bank_pda, false),
804 AccountMeta::new(transfer_pda, false), ],
806 data: CancelAuthorityProposal {}.to_bytes(),
807 }
808 }
809
810 pub fn update_config(
816 admin: Pubkey,
817 closure_delay_seconds: i64,
818 refund_cooldown_seconds: i64,
819 delivery_cutoff_seconds: i64,
820 ) -> Instruction {
821 let (bank_pda, _) = Self::bank_pda();
822 let (config_pda, _) = Self::config_pda();
823
824 Instruction {
825 program_id: Self::program_id(),
826 accounts: vec![
827 AccountMeta::new(admin, true),
828 AccountMeta::new_readonly(bank_pda, false),
829 AccountMeta::new(config_pda, false),
830 ],
831 data: UpdateConfig {
832 closure_delay_seconds: closure_delay_seconds.to_le_bytes(),
833 refund_cooldown_seconds: refund_cooldown_seconds.to_le_bytes(),
834 delivery_cutoff_seconds: delivery_cutoff_seconds.to_le_bytes(),
835 }
836 .to_bytes(),
837 }
838 }
839
840 pub fn confirm_oracle(
849 oracle_authority: Pubkey,
850 mint: Pubkey,
851 payment_uid: &str,
852 delivery_hash: [u8; 32],
853 resolution_hash: [u8; 32],
854 resolution_state: u8,
855 resolution_reason: u16,
856 ) -> Instruction {
857 let (bank_pda, _) = Self::bank_pda();
858 let (config_pda, _) = Self::config_pda();
859 let (escrow_pda, _) = Self::escrow_pda(mint);
860 let (payment_pda, _) = Self::payment_pda(payment_uid, bank_pda);
861
862 Instruction {
863 program_id: Self::program_id(),
864 accounts: vec![
865 AccountMeta::new(oracle_authority, true), AccountMeta::new_readonly(bank_pda, false),
867 AccountMeta::new_readonly(config_pda, false),
868 AccountMeta::new_readonly(escrow_pda, false),
869 AccountMeta::new(payment_pda, false), ],
871 data: ConfirmOracle {
872 delivery_hash,
873 resolution_hash,
874 resolution_reason: resolution_reason.to_le_bytes(),
875 resolution_state,
876 _padding: [0; 5],
877 }
878 .to_bytes(),
879 }
880 }
881
882 pub fn validate_payment_for_funding(payment: &Payment) -> Result<(), EscrowError> {
888 if payment.state != PAYMENT_STATE_FUNDED {
889 return Err(EscrowError::InvalidPaymentState);
890 }
891 Ok(())
892 }
893
894 pub fn validate_payment_for_release(payment: &Payment) -> Result<(), EscrowError> {
896 if payment.state != PAYMENT_STATE_FUNDED {
897 return Err(EscrowError::InvalidPaymentState);
898 }
899 Ok(())
900 }
901
902 pub fn validate_payment_for_refund(payment: &Payment) -> Result<(), EscrowError> {
904 if payment.state != PAYMENT_STATE_FUNDED {
905 return Err(EscrowError::InvalidPaymentState);
906 }
907 Ok(())
908 }
909
910 pub fn calculate_fee(amount: u64, fee_bps: u16, min_fee_amount: u64) -> u64 {
916 let fee_bps = fee_bps as u128;
917 let amount_128 = amount as u128;
918 let calculated_fee = ((amount_128 * fee_bps) / BASIS_POINTS_DENOMINATOR) as u64;
919 calculated_fee.max(min_fee_amount).min(amount)
921 }
922
923 pub fn calculate_payout(amount: u64, fee_bps: u16, min_fee_amount: u64) -> u64 {
925 let fee = Self::calculate_fee(amount, fee_bps, min_fee_amount);
926 amount.saturating_sub(fee)
927 }
928
929 pub fn is_payment_expired(payment: &Payment) -> bool {
931 let clock = solana_program::clock::Clock::get().unwrap_or_default();
932 clock.unix_timestamp > payment.expires_at
933 }
934
935 pub fn get_payment_state_string(state: u8) -> &'static str {
937 match state {
938 PAYMENT_STATE_FUNDED => "Funded",
939 PAYMENT_STATE_RELEASED => "Released",
940 PAYMENT_STATE_REFUNDED => "Refunded",
941 _ => "Unknown",
942 }
943 }
944
945 pub fn get_escrow_state_string(paused: u8) -> &'static str {
947 match paused {
948 0 => "Active",
949 1 => "Paused",
950 _ => "Unknown",
951 }
952 }
953
954 pub fn get_payment_accounts(
960 buyer: Pubkey,
961 seller: Pubkey,
962 mint: Pubkey,
963 payment_uid: &str,
964 ) -> PaymentAccounts {
965 Self::get_payment_accounts_with_token_program(
966 buyer,
967 seller,
968 mint,
969 payment_uid,
970 spl_token::ID,
971 )
972 }
973
974 pub fn get_payment_accounts_with_token_program(
976 buyer: Pubkey,
977 seller: Pubkey,
978 mint: Pubkey,
979 payment_uid: &str,
980 token_program: Pubkey,
981 ) -> PaymentAccounts {
982 let (bank_pda, _) = Self::bank_pda();
983 let (escrow_pda, _) = Self::escrow_pda(mint);
984 let (payment_pda, _) = Self::payment_pda(payment_uid, bank_pda);
985 let escrow_tokens = Self::escrow_token_account_with_program(mint, token_program);
986
987 PaymentAccounts {
988 buyer,
989 seller,
990 mint,
991 bank: bank_pda,
992 escrow: escrow_pda,
993 payment: payment_pda,
994 escrow_tokens,
995 }
996 }
997
998 pub fn get_escrow_accounts(mint: Pubkey) -> EscrowAccounts {
1000 Self::get_escrow_accounts_with_token_program(mint, spl_token::ID)
1001 }
1002
1003 pub fn get_escrow_accounts_with_token_program(
1005 mint: Pubkey,
1006 token_program: Pubkey,
1007 ) -> EscrowAccounts {
1008 let (bank_pda, _) = Self::bank_pda();
1009 let (escrow_pda, _) = Self::escrow_pda(mint);
1010 let escrow_tokens = Self::escrow_token_account_with_program(mint, token_program);
1011
1012 EscrowAccounts {
1013 mint,
1014 bank: bank_pda,
1015 escrow: escrow_pda,
1016 escrow_tokens,
1017 }
1018 }
1019}
1020
1021#[derive(Debug, Clone)]
1027pub struct PaymentAccounts {
1028 pub buyer: Pubkey,
1029 pub seller: Pubkey,
1030 pub mint: Pubkey,
1031 pub bank: Pubkey,
1032 pub escrow: Pubkey,
1033 pub payment: Pubkey,
1034 pub escrow_tokens: Pubkey,
1035}
1036
1037#[derive(Debug, Clone)]
1039pub struct EscrowAccounts {
1040 pub mint: Pubkey,
1041 pub bank: Pubkey,
1042 pub escrow: Pubkey,
1043 pub escrow_tokens: Pubkey,
1044}
1045
1046pub struct PaymentBuilder {
1052 buyer: Option<Pubkey>,
1053 buyer_tokens: Option<Pubkey>,
1054 seller: Option<Pubkey>,
1055 mint: Option<Pubkey>,
1056 token_program: Option<Pubkey>,
1057 amount: Option<u64>,
1058 ttl_seconds: Option<i64>,
1059 payment_uid: Option<String>,
1060 sla_hash: Option<[u8; 32]>,
1061 oracle_authority: Option<Pubkey>,
1062}
1063
1064impl PaymentBuilder {
1065 pub fn new() -> Self {
1066 Self {
1067 buyer: None,
1068 buyer_tokens: None,
1069 seller: None,
1070 mint: None,
1071 token_program: None,
1072 amount: None,
1073 ttl_seconds: None,
1074 payment_uid: None,
1075 sla_hash: None,
1076 oracle_authority: None,
1077 }
1078 }
1079
1080 pub fn buyer(mut self, buyer: Pubkey) -> Self {
1081 self.buyer = Some(buyer);
1082 self
1083 }
1084
1085 pub fn buyer_tokens(mut self, buyer_tokens: Pubkey) -> Self {
1086 self.buyer_tokens = Some(buyer_tokens);
1087 self
1088 }
1089
1090 pub fn seller(mut self, seller: Pubkey) -> Self {
1091 self.seller = Some(seller);
1092 self
1093 }
1094
1095 pub fn mint(mut self, mint: Pubkey) -> Self {
1096 self.mint = Some(mint);
1097 self
1098 }
1099
1100 pub fn token_program(mut self, token_program: Pubkey) -> Self {
1101 self.token_program = Some(token_program);
1102 self
1103 }
1104
1105 pub fn amount(mut self, amount: u64) -> Self {
1106 self.amount = Some(amount);
1107 self
1108 }
1109
1110 pub fn ttl_seconds(mut self, ttl_seconds: i64) -> Self {
1111 self.ttl_seconds = Some(ttl_seconds);
1112 self
1113 }
1114
1115 pub fn payment_uid(mut self, payment_uid: &str) -> Self {
1116 self.payment_uid = Some(payment_uid.to_string());
1117 self
1118 }
1119
1120 pub fn sla_hash(mut self, sla_hash: [u8; 32]) -> Self {
1121 self.sla_hash = Some(sla_hash);
1122 self
1123 }
1124
1125 pub fn oracle_authority(mut self, oracle_authority: Pubkey) -> Self {
1126 self.oracle_authority = Some(oracle_authority);
1127 self
1128 }
1129
1130 pub fn build(self) -> Result<Instruction, EscrowError> {
1131 let buyer = self.buyer.ok_or(EscrowError::MissingRequiredField)?;
1132 let buyer_tokens = self.buyer_tokens; let seller = self.seller.ok_or(EscrowError::MissingRequiredField)?;
1134 let mint = self.mint.ok_or(EscrowError::MissingRequiredField)?;
1135 let token_program = self.token_program.unwrap_or(spl_token::ID);
1136 let amount = self.amount.ok_or(EscrowError::MissingRequiredField)?;
1137 let ttl_seconds = self.ttl_seconds.ok_or(EscrowError::MissingRequiredField)?;
1138 let payment_uid = self.payment_uid.ok_or(EscrowError::MissingRequiredField)?;
1139 let sla_hash = self.sla_hash.unwrap_or([0; 32]);
1140 let oracle_authority = self.oracle_authority.unwrap_or_default();
1141
1142 Ok(EscrowSdk::fund_payment_with_token_program(
1143 buyer,
1144 buyer_tokens,
1145 seller,
1146 mint,
1147 amount,
1148 ttl_seconds,
1149 &payment_uid,
1150 sla_hash,
1151 oracle_authority,
1152 token_program,
1153 ))
1154 }
1155}
1156
1157impl Default for PaymentBuilder {
1158 fn default() -> Self {
1159 Self::new()
1160 }
1161}
1162
1163pub struct EscrowBuilder {
1168 authority: Option<Pubkey>,
1169 funder: Option<Pubkey>,
1170 mint: Option<Pubkey>,
1171 token_program: Option<Pubkey>,
1172 min_payment_amount: Option<u64>,
1173 max_payment_amount: Option<u64>,
1174 min_fee_amount: Option<u64>,
1175 fee_bps: Option<u16>,
1176 oracle_fee_bps: Option<u16>,
1177}
1178
1179impl EscrowBuilder {
1180 pub fn new() -> Self {
1181 Self {
1182 authority: None,
1183 funder: None,
1184 mint: None,
1185 token_program: None,
1186 min_payment_amount: None,
1187 max_payment_amount: None,
1188 min_fee_amount: None,
1189 fee_bps: None,
1190 oracle_fee_bps: None,
1191 }
1192 }
1193
1194 pub fn authority(mut self, authority: Pubkey) -> Self {
1195 self.authority = Some(authority);
1196 self
1197 }
1198
1199 pub fn funder(mut self, funder: Pubkey) -> Self {
1200 self.funder = Some(funder);
1201 self
1202 }
1203
1204 pub fn mint(mut self, mint: Pubkey) -> Self {
1205 self.mint = Some(mint);
1206 self
1207 }
1208
1209 pub fn token_program(mut self, token_program: Pubkey) -> Self {
1210 self.token_program = Some(token_program);
1211 self
1212 }
1213
1214 pub fn min_payment_amount(mut self, min_payment_amount: u64) -> Self {
1215 self.min_payment_amount = Some(min_payment_amount);
1216 self
1217 }
1218
1219 pub fn max_payment_amount(mut self, max_payment_amount: u64) -> Self {
1220 self.max_payment_amount = Some(max_payment_amount);
1221 self
1222 }
1223
1224 pub fn min_fee_amount(mut self, min_fee_amount: u64) -> Self {
1225 self.min_fee_amount = Some(min_fee_amount);
1226 self
1227 }
1228
1229 pub fn fee_bps(mut self, fee_bps: u16) -> Self {
1230 self.fee_bps = Some(fee_bps);
1231 self
1232 }
1233
1234 pub fn oracle_fee_bps(mut self, oracle_fee_bps: u16) -> Self {
1235 self.oracle_fee_bps = Some(oracle_fee_bps);
1236 self
1237 }
1238
1239 pub fn create_escrow(self) -> Result<Instruction, EscrowError> {
1240 let authority = self.authority.ok_or(EscrowError::MissingRequiredField)?;
1241 let funder = self.funder.ok_or(EscrowError::MissingRequiredField)?;
1242 let mint = self.mint.ok_or(EscrowError::MissingRequiredField)?;
1243 let token_program = self.token_program.unwrap_or(spl_token::ID);
1244 let min_payment_amount = self
1245 .min_payment_amount
1246 .unwrap_or(crate::consts::DEFAULT_MIN_PAYMENT_RAW_USDC_DECIMALS_6);
1247 let max_payment_amount = self.max_payment_amount.unwrap_or(u64::MAX); let min_fee_amount = self
1249 .min_fee_amount
1250 .unwrap_or(crate::consts::DEFAULT_MIN_FEE_RAW_USDC_DECIMALS_6);
1251 let fee_bps = self.fee_bps.unwrap_or(u16::MAX);
1253 let oracle_fee_bps = self.oracle_fee_bps.unwrap_or(0); Ok(EscrowSdk::open_escrow_with_token_program(
1256 authority,
1257 funder,
1258 mint,
1259 min_payment_amount,
1260 max_payment_amount,
1261 min_fee_amount,
1262 fee_bps,
1263 oracle_fee_bps,
1264 token_program,
1265 ))
1266 }
1267
1268 pub fn pause_escrow(self, paused: bool) -> Result<Instruction, EscrowError> {
1269 let authority = self.authority.ok_or(EscrowError::MissingRequiredField)?;
1270 let mint = self.mint.ok_or(EscrowError::MissingRequiredField)?;
1271
1272 Ok(EscrowSdk::pause_escrow(authority, mint, paused))
1273 }
1274}
1275
1276impl Default for EscrowBuilder {
1277 fn default() -> Self {
1278 Self::new()
1279 }
1280}
1281
1282#[cfg(test)]
1283mod tests {
1284 use super::*;
1285
1286 #[test]
1287 fn token_program_aware_ata_derivation_changes_address() {
1288 let wallet = Pubkey::new_unique();
1289 let mint = Pubkey::new_unique();
1290
1291 let legacy = EscrowSdk::associated_token_account(wallet, mint);
1292 let token2022 = EscrowSdk::associated_token_account_with_program(
1293 wallet,
1294 mint,
1295 crate::consts::TOKEN_2022_PROGRAM_ID,
1296 );
1297
1298 assert_ne!(legacy, token2022);
1299 }
1300
1301 #[test]
1302 fn close_escrow_uses_supplied_token_program() {
1303 let authority = Pubkey::new_unique();
1304 let recipient = Pubkey::new_unique();
1305 let mint = Pubkey::new_unique();
1306
1307 let instruction = EscrowSdk::close_escrow_with_token_program(
1308 authority,
1309 recipient,
1310 mint,
1311 crate::consts::TOKEN_2022_PROGRAM_ID,
1312 );
1313
1314 assert_eq!(
1315 instruction.accounts[3].pubkey,
1316 EscrowSdk::escrow_token_account_with_program(
1317 mint,
1318 crate::consts::TOKEN_2022_PROGRAM_ID,
1319 )
1320 );
1321 assert_eq!(
1322 instruction.accounts[5].pubkey,
1323 crate::consts::TOKEN_2022_PROGRAM_ID
1324 );
1325 }
1326}
1327
1328pub mod tokens {
1334 use solana_program::pubkey::Pubkey;
1335
1336 pub const USDC: Pubkey =
1338 solana_program::pubkey!("EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v");
1339
1340 pub const USDT: Pubkey =
1342 solana_program::pubkey!("Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB");
1343
1344 pub const WSOL: Pubkey = solana_program::pubkey!("So11111111111111111111111111111111111111112");
1346
1347 pub const MARS: Pubkey =
1349 solana_program::pubkey!("7RAV5UPRTzxn46kLeA8MiJsdNy9VKc5fip8FWEgTpTHh");
1350
1351 pub const MIRACLE: Pubkey =
1353 solana_program::pubkey!("Mirab4SFVff6sCuK48PPnSUj7PNpDDrBWY6FkJmuifG");
1354
1355 pub const TESTCOIN: Pubkey =
1357 solana_program::pubkey!("2gNCDGj8Xi9Zs7LNQTPWf4pfZvAM7UHusY4xhKNYg6W6");
1358}
1359
1360pub mod payment_states {
1362 use crate::consts::{PAYMENT_STATE_FUNDED, PAYMENT_STATE_REFUNDED, PAYMENT_STATE_RELEASED};
1363
1364 pub const FUNDED: u8 = PAYMENT_STATE_FUNDED;
1365 pub const RELEASED: u8 = PAYMENT_STATE_RELEASED;
1366 pub const REFUNDED: u8 = PAYMENT_STATE_REFUNDED;
1367}
1368
1369pub mod escrow_states {
1371 pub const ACTIVE: u8 = 0;
1372 pub const PAUSED: u8 = 1;
1373}