1use continuation_router_syn::router_action;
9
10use anchor_lang::{prelude::*, solana_program::pubkey::PUBKEY_BYTES};
11use anchor_spl::token::{Token, TokenAccount};
12use num_enum::{IntoPrimitive, TryFromPrimitive};
13use vipers::prelude::*;
14
15pub mod action;
16pub mod processor;
17
18use crate::action::ProcessAction;
19use crate::processor::{ActionContext, Processor};
20
21declare_id!("Crt7UoUR6QgrFrN7j8rmSQpUTNWNSitSwWvsWGf1qZ5t");
22
23macro_rules! process_action {
24 ($ctx:expr) => {{
25 let ctx = $ctx;
26 let cont = &mut ctx.accounts.continuation.continuation;
27 let action = &ctx.accounts.action;
28 let action_ctx = &ActionContext {
29 program_id: ctx.program_id,
30 action,
31 remaining_accounts: ctx.remaining_accounts,
32 token_program: ctx.accounts.continuation.token_program.clone(),
33 swap_program: ctx.accounts.continuation.swap_program.to_account_info(),
34 owner: ctx.accounts.continuation.owner.to_account_info(),
35 };
36 Processor::process(action_ctx, cont)
37 }};
38}
39
40#[program]
41pub mod continuation_router {
42 use super::*;
43
44 pub fn create_ata_if_not_exists(ctx: Context<CreateATAIfNotExists>) -> Result<()> {
46 if !ctx.accounts.ata.try_borrow_data()?.is_empty() {
47 return Ok(());
49 }
50 anchor_spl::associated_token::create(CpiContext::new(
51 ctx.accounts.associated_token_program.to_account_info(),
52 anchor_spl::associated_token::Create {
53 payer: ctx.accounts.payer.to_account_info(),
54 associated_token: ctx.accounts.ata.to_account_info(),
55 authority: ctx.accounts.authority.to_account_info(),
56 mint: ctx.accounts.mint.to_account_info(),
57 rent: ctx.accounts.rent.to_account_info(),
58 system_program: ctx.accounts.system_program.to_account_info(),
59 token_program: ctx.accounts.token_program.to_account_info(),
60 },
61 ))?;
62 Ok(())
63 }
64
65 pub fn begin(
67 ctx: Context<Begin>,
68 amount_in: u64,
69 minimum_amount_out: u64,
70 num_steps: u16,
71 ) -> Result<()> {
72 let continuation = &mut ctx.accounts.continuation;
73 continuation.owner = *ctx.accounts.owner.key;
74 continuation.payer = *ctx.accounts.payer.key;
75
76 continuation.input = *ctx.accounts.input.to_account_info().key;
77 continuation.initial_amount_in = TokenAmount::new(ctx.accounts.input.mint, amount_in);
78 continuation.output = *ctx.accounts.output.to_account_info().key;
79 continuation.output_initial_balance = ctx.accounts.output.amount;
80
81 continuation.amount_in = TokenAmount::new(ctx.accounts.input.mint, amount_in);
82 continuation.minimum_amount_out =
83 TokenAmount::new(ctx.accounts.output.mint, minimum_amount_out);
84 continuation.steps_left = num_steps;
85 continuation.__nonce = *unwrap_int!(ctx.bumps.get("continuation"));
86
87 Ok(())
88 }
89
90 pub fn begin_v2(
93 ctx: Context<BeginV2>,
94 amount_in: u64,
95 minimum_amount_out: u64,
96 num_steps: u16,
97 ) -> Result<()> {
98 let continuation = &mut ctx.accounts.continuation;
99 continuation.owner = ctx.accounts.owner.key();
100 continuation.payer = ctx.accounts.owner.key();
101
102 continuation.input = ctx.accounts.input.key();
103 continuation.initial_amount_in = TokenAmount::new(ctx.accounts.input.mint, amount_in);
104 continuation.output = ctx.accounts.output.key();
105 continuation.output_initial_balance = ctx.accounts.output.amount;
106
107 continuation.amount_in = TokenAmount::new(ctx.accounts.input.mint, amount_in);
108 continuation.minimum_amount_out =
109 TokenAmount::new(ctx.accounts.output.mint, minimum_amount_out);
110 continuation.steps_left = num_steps;
111 Ok(())
112 }
113
114 pub fn end(ctx: Context<End>) -> Result<()> {
116 let continuation = &ctx.accounts.continuation;
117 require!(continuation.steps_left == 0, EndIncomplete);
118
119 let result_balance = ctx.accounts.output.amount;
120 require!(
121 result_balance >= continuation.output_initial_balance,
122 BalanceLower
123 );
124 require!(
125 ctx.accounts.output.mint == continuation.minimum_amount_out.mint,
126 OutputMintMismatch,
127 );
128
129 let mut amount_out = result_balance - continuation.output_initial_balance;
130 if continuation.initial_amount_in.mint == ctx.accounts.output.mint {
132 amount_out += continuation.initial_amount_in.amount;
133 }
134
135 require!(
136 amount_out >= continuation.minimum_amount_out.amount,
137 MinimumOutNotMet,
138 );
139
140 emit!(SwapCompleteEvent {
141 owner: continuation.owner,
142 amount_in: continuation.initial_amount_in,
143 amount_out: TokenAmount::new(continuation.minimum_amount_out.mint, amount_out),
144 });
145 Ok(())
146 }
147
148 pub fn ss_swap<'info>(ctx: Context<'_, '_, '_, 'info, SSSwapAccounts<'info>>) -> Result<()> {
149 process_action!(ctx)
150 }
151
152 pub fn ss_withdraw_one<'info>(
153 ctx: Context<'_, '_, '_, 'info, SSWithdrawOneAccounts<'info>>,
154 ) -> Result<()> {
155 process_action!(ctx)
156 }
157
158 pub fn ss_deposit_a<'info>(
159 ctx: Context<'_, '_, '_, 'info, SSDepositAAccounts<'info>>,
160 ) -> Result<()> {
161 process_action!(ctx)
162 }
163
164 pub fn ss_deposit_b<'info>(
165 ctx: Context<'_, '_, '_, 'info, SSDepositBAccounts<'info>>,
166 ) -> Result<()> {
167 process_action!(ctx)
168 }
169
170 pub fn ad_withdraw<'info>(
171 ctx: Context<'_, '_, '_, 'info, ADWithdrawAccounts<'info>>,
172 ) -> Result<()> {
173 process_action!(ctx)
174 }
175
176 pub fn ad_deposit<'info>(
177 ctx: Context<'_, '_, '_, 'info, ADDepositAccounts<'info>>,
178 ) -> Result<()> {
179 process_action!(ctx)
180 }
181}
182
183#[router_action]
188#[derive(Accounts)]
189pub struct SSSwap<'info> {
190 pub swap: StableSwap<'info>,
192 pub input: SwapToken<'info>,
194 pub output: SwapOutput<'info>,
196}
197
198#[router_action]
199#[derive(Accounts)]
200pub struct SSWithdrawOne<'info> {
201 pub swap: StableSwap<'info>,
203 #[account(mut)]
206 pub pool_mint: AccountInfo<'info>,
207 #[account(mut)]
209 pub input_lp: Account<'info, TokenAccount>,
210 #[account(mut)]
213 pub quote_reserves: AccountInfo<'info>,
214 pub output: SwapOutput<'info>,
216}
217
218#[router_action]
219#[derive(Accounts)]
220pub struct SSDepositA<'info> {
221 pub inner: SSDeposit<'info>,
222}
223
224#[router_action]
225#[derive(Accounts)]
226pub struct SSDepositB<'info> {
227 pub inner: SSDeposit<'info>,
228}
229
230#[router_action(pass_through)]
231#[derive(Accounts)]
232pub struct ADWithdraw<'info> {
233 pub input: Account<'info, TokenAccount>,
234 pub output: Account<'info, TokenAccount>,
235}
236
237#[router_action(pass_through)]
238#[derive(Accounts)]
239pub struct ADDeposit<'info> {
240 pub input: Account<'info, TokenAccount>,
241 pub output: Account<'info, TokenAccount>,
242}
243
244#[derive(Accounts)]
250pub struct CreateATAIfNotExists<'info> {
251 #[account(mut)]
253 pub payer: Signer<'info>,
254
255 #[account(mut)]
257 pub ata: SystemAccount<'info>,
258
259 pub authority: UncheckedAccount<'info>,
262
263 pub mint: UncheckedAccount<'info>,
266
267 pub rent: Sysvar<'info, Rent>,
269
270 pub system_program: Program<'info, System>,
272
273 pub token_program: Program<'info, Token>,
275
276 pub associated_token_program: Program<'info, anchor_spl::associated_token::AssociatedToken>,
278}
279
280#[derive(Accounts)]
282pub struct Begin<'info> {
283 #[account(
285 init,
286 seeds = [
287 b"anchor".as_ref(),
288 owner.key().as_ref(),
289 random.key().as_ref()
290 ],
291 bump,
292 space = 8 + Continuation::LEN,
293 payer = payer
294 )]
295 pub continuation: Box<Account<'info, Continuation>>,
296
297 pub random: UncheckedAccount<'info>,
300
301 #[account(has_one = owner)]
303 pub input: Box<Account<'info, TokenAccount>>,
304
305 #[account(has_one = owner)]
307 pub output: Box<Account<'info, TokenAccount>>,
308
309 pub owner: Signer<'info>,
311
312 #[account(mut)]
315 pub payer: Signer<'info>,
316
317 pub rent: Sysvar<'info, Rent>,
319
320 pub system_program: Program<'info, System>,
322}
323
324#[derive(Accounts)]
326pub struct BeginV2<'info> {
327 #[account(zero)]
329 pub continuation: Box<Account<'info, Continuation>>,
330
331 #[account(has_one = owner)]
333 pub input: Box<Account<'info, TokenAccount>>,
334
335 #[account(has_one = owner)]
337 pub output: Box<Account<'info, TokenAccount>>,
338
339 pub owner: Signer<'info>,
341}
342
343#[derive(Accounts)]
345pub struct End<'info> {
346 #[account(
348 mut,
349 close = payer,
350 has_one = owner,
351 has_one = payer,
352 has_one = output,
353 )]
354 pub continuation: Box<Account<'info, Continuation>>,
355
356 pub output: Box<Account<'info, TokenAccount>>,
358
359 pub owner: Signer<'info>,
361
362 #[account(mut)]
366 pub payer: UncheckedAccount<'info>,
367}
368
369#[derive(Accounts)]
370pub struct SSSwapAccounts<'info> {
371 pub continuation: ContinuationAccounts<'info>,
372 pub action: SSSwap<'info>,
373}
374
375#[derive(Accounts)]
376pub struct SSWithdrawOneAccounts<'info> {
377 pub continuation: ContinuationAccounts<'info>,
378 pub action: SSWithdrawOne<'info>,
379}
380
381#[derive(Accounts)]
382pub struct SSDepositAAccounts<'info> {
383 pub continuation: ContinuationAccounts<'info>,
384 pub action: SSDepositA<'info>,
385}
386
387#[derive(Accounts)]
388pub struct SSDepositBAccounts<'info> {
389 pub continuation: ContinuationAccounts<'info>,
390 pub action: SSDepositB<'info>,
391}
392
393#[derive(Accounts)]
394pub struct ADWithdrawAccounts<'info> {
395 pub continuation: ContinuationAccounts<'info>,
396 pub action: ADWithdraw<'info>,
397}
398
399#[derive(Accounts)]
400pub struct ADDepositAccounts<'info> {
401 pub continuation: ContinuationAccounts<'info>,
402 pub action: ADDeposit<'info>,
403}
404
405#[derive(Accounts)]
411pub struct ContinuationAccounts<'info> {
412 #[account(
414 mut,
415 has_one = owner,
416 )]
417 pub continuation: Box<Account<'info, Continuation>>,
418
419 pub token_program: Program<'info, Token>,
421
422 pub swap_program: UncheckedAccount<'info>,
425
426 pub owner: Signer<'info>,
428}
429
430#[derive(Accounts)]
432pub struct SSDeposit<'info> {
433 pub swap: StableSwap<'info>,
435 pub input_a: SwapToken<'info>,
437 pub input_b: SwapToken<'info>,
439 #[account(mut)]
442 pub pool_mint: AccountInfo<'info>,
443 #[account(mut)]
445 pub output_lp: Account<'info, TokenAccount>,
446}
447
448#[derive(Accounts)]
450pub struct StableSwap<'info> {
451 pub swap: AccountInfo<'info>,
454 pub swap_authority: AccountInfo<'info>,
457 pub clock: Sysvar<'info, Clock>,
459}
460
461#[derive(Accounts)]
463pub struct SwapToken<'info> {
464 #[account(mut)]
466 pub user: Box<Account<'info, TokenAccount>>,
467 #[account(mut)]
470 pub reserve: AccountInfo<'info>,
471}
472
473#[derive(Accounts)]
475pub struct SwapOutput<'info> {
476 pub user_token: SwapToken<'info>,
478 #[account(mut)]
481 pub fees: AccountInfo<'info>,
482}
483
484#[account]
486#[derive(Default)]
487pub struct Continuation {
488 pub owner: Pubkey,
490
491 pub payer: Pubkey,
493
494 pub initial_amount_in: TokenAmount,
496
497 pub input: Pubkey,
499
500 pub amount_in: TokenAmount,
502
503 pub steps_left: u16,
505
506 pub output: Pubkey,
508
509 pub output_initial_balance: u64,
511
512 pub minimum_amount_out: TokenAmount,
514
515 __nonce: u8,
518}
519
520impl Continuation {
521 pub const LEN: usize = PUBKEY_BYTES * 2
522 + TokenAmount::LEN
523 + PUBKEY_BYTES
524 + TokenAmount::LEN
525 + 2
526 + PUBKEY_BYTES
527 + 8
528 + TokenAmount::LEN
529 + 1;
530}
531
532#[error_code]
536pub enum ErrorCode {
537 #[msg("Path input does not match prior output.")]
538 PathInputOutputMismatch,
539 #[msg("Error in a transitive swap input/output calculation.")]
540 TransitiveSwapCalculationError,
541 #[msg("Swap result overflowed when checking balance difference.")]
542 OverflowSwapResult,
543 #[msg("Swap resulted in a balance lower than the original balance.")]
544 BalanceLower,
545 #[msg("Cannot perform a zero swap.")]
546 ZeroSwap,
547 #[msg("Input owner does not match continuation owner.")]
548 InputOwnerMismatch,
549 #[msg("Input mint does not match continuation input mint.")]
550 InputMintMismatch,
551 #[msg("Output owner does not match continuation owner.")]
552 OutputOwnerMismatch,
553 #[msg("No more steps to process.")]
554 NoMoreSteps,
555 #[msg("Insufficient input balance")]
556 InsufficientInputBalance,
557
558 #[msg("Not all steps were processed.")]
559 EndIncomplete,
560 #[msg("Minimum amount out not met.")]
561 MinimumOutNotMet,
562 #[msg("Output mint does not match continuation output mint.")]
563 OutputMintMismatch,
564}
565
566#[event]
571pub struct SwapActionEvent {
572 pub action_type: ActionType,
573 pub owner: Pubkey,
574 pub input_amount: TokenAmount,
575 pub output_account: Pubkey,
576 pub output_amount: TokenAmount,
577}
578
579#[event]
580pub struct SwapCompleteEvent {
581 pub owner: Pubkey,
582 pub amount_in: TokenAmount,
583 pub amount_out: TokenAmount,
584}
585
586#[derive(AnchorSerialize, AnchorDeserialize, Clone, Copy, Debug, Default, Eq, PartialEq)]
588pub struct TokenAmount {
589 pub mint: Pubkey,
591 pub amount: u64,
593}
594
595impl TokenAmount {
596 pub const LEN: usize = PUBKEY_BYTES + 8;
597
598 fn new(mint: Pubkey, amount: u64) -> TokenAmount {
599 TokenAmount { mint, amount }
600 }
601}
602
603pub trait Action {
605 const TYPE: ActionType;
606}
607
608#[interface]
610pub trait RouterActionProcessor<'info, T: Accounts<'info>> {
611 fn process_action(
612 ctx: Context<T>,
613 action: u16,
614 amount_in: u64,
615 minimum_amount_out: u64,
616 ) -> Result<()>;
617}
618
619#[derive(
621 AnchorSerialize, AnchorDeserialize, IntoPrimitive, TryFromPrimitive, Copy, Clone, Debug,
622)]
623#[repr(u16)]
624pub enum ActionType {
625 SSSwap = 0,
626 SSWithdrawOne = 1,
627 SSDepositA = 2,
628 SSDepositB = 3,
629
630 ADWithdraw = 10,
631 ADDeposit = 11,
632}