world/
lib.rs

1#![allow(clippy::manual_unwrap_or_default)]
2use anchor_lang::prelude::*;
3use bolt_component::CpiContextBuilder;
4use error::WorldError;
5use std::collections::BTreeSet;
6
7#[cfg(not(feature = "no-entrypoint"))]
8use solana_security_txt::security_txt;
9
10declare_id!("WorLD15A7CrDwLcLy4fRqtaTb9fbd8o8iqiEMUDse2n");
11
12#[cfg(not(feature = "no-entrypoint"))]
13security_txt! {
14    name: "Bolt",
15    project_url: "https://magicblock.gg",
16    contacts: "email:dev@magicblock.gg,twitter:@magicblock",
17    policy: "",
18    preferred_languages: "en",
19    source_code: "https://github.com/magicblock-labs/bolt"
20}
21
22mod error;
23
24#[program]
25pub mod world {
26    use super::*;
27    use crate::error::WorldError;
28
29    pub fn initialize_registry(_ctx: Context<InitializeRegistry>) -> Result<()> {
30        Ok(())
31    }
32
33    pub fn initialize_new_world(ctx: Context<InitializeNewWorld>) -> Result<()> {
34        ctx.accounts.world.set_inner(World::default());
35        ctx.accounts.world.id = ctx.accounts.registry.worlds;
36        ctx.accounts.registry.worlds += 1;
37        Ok(())
38    }
39
40    #[allow(unused_variables)]
41    pub fn add_authority(ctx: Context<AddAuthority>, world_id: u64) -> Result<()> {
42        if ctx.accounts.world.authorities.is_empty()
43            || (ctx
44                .accounts
45                .world
46                .authorities
47                .contains(ctx.accounts.authority.key)
48                && !ctx
49                    .accounts
50                    .world
51                    .authorities
52                    .contains(ctx.accounts.new_authority.key))
53        {
54            ctx.accounts
55                .world
56                .authorities
57                .push(*ctx.accounts.new_authority.key);
58
59            let new_space = World::space_for_authorities(
60                ctx.accounts.world.authorities.len(),
61                ctx.accounts.world.systems.len(),
62            );
63
64            // Transfer to make it rent exempt
65            let rent = Rent::get()?;
66            let new_minimum_balance = rent.minimum_balance(new_space);
67            let lamports_diff =
68                new_minimum_balance.saturating_sub(ctx.accounts.world.to_account_info().lamports());
69            if lamports_diff > 0 {
70                anchor_lang::solana_program::program::invoke(
71                    &anchor_lang::solana_program::system_instruction::transfer(
72                        ctx.accounts.authority.key,
73                        ctx.accounts.world.to_account_info().key,
74                        lamports_diff,
75                    ),
76                    &[
77                        ctx.accounts.authority.to_account_info(),
78                        ctx.accounts.world.to_account_info(),
79                        ctx.accounts.system_program.to_account_info(),
80                    ],
81                )?;
82            }
83            ctx.accounts
84                .world
85                .to_account_info()
86                .realloc(new_space, false)?;
87        }
88        Ok(())
89    }
90
91    #[allow(unused_variables)]
92    pub fn remove_authority(ctx: Context<RemoveAuthority>, world_id: u64) -> Result<()> {
93        if !ctx
94            .accounts
95            .world
96            .authorities
97            .contains(ctx.accounts.authority.key)
98        {
99            return Err(WorldError::InvalidAuthority.into());
100        }
101        if let Some(index) = ctx
102            .accounts
103            .world
104            .authorities
105            .iter()
106            .position(|&x| x == *ctx.accounts.authority_to_delete.key)
107        {
108            ctx.accounts.world.authorities.remove(index);
109
110            let new_space = World::space_for_authorities(
111                ctx.accounts.world.authorities.len(),
112                ctx.accounts.world.systems.len(),
113            );
114
115            // Remove the extra rent
116            let rent = Rent::get()?;
117            let new_minimum_balance = rent.minimum_balance(new_space);
118            let lamports_diff =
119                new_minimum_balance.saturating_sub(ctx.accounts.world.to_account_info().lamports());
120            **ctx
121                .accounts
122                .world
123                .to_account_info()
124                .try_borrow_mut_lamports()? += lamports_diff;
125            **ctx
126                .accounts
127                .authority
128                .to_account_info()
129                .try_borrow_mut_lamports()? -= lamports_diff;
130            ctx.accounts
131                .world
132                .to_account_info()
133                .realloc(new_space, false)?;
134            Ok(())
135        } else {
136            Err(WorldError::AuthorityNotFound.into())
137        }
138    }
139
140    pub fn approve_system(ctx: Context<ApproveSystem>) -> Result<()> {
141        if !ctx.accounts.authority.is_signer {
142            return Err(WorldError::InvalidAuthority.into());
143        }
144        if !ctx
145            .accounts
146            .world
147            .authorities
148            .contains(ctx.accounts.authority.key)
149        {
150            return Err(WorldError::InvalidAuthority.into());
151        }
152        if ctx.accounts.world.permissionless {
153            ctx.accounts.world.permissionless = false;
154        }
155
156        let mut world_systems = ctx.accounts.world.systems();
157        world_systems
158            .approved_systems
159            .insert(ctx.accounts.system.key());
160
161        let encoded_world_systems = world_systems.try_to_vec()?;
162        ctx.accounts.world.systems = encoded_world_systems.clone();
163
164        let new_space = World::space_for_authorities(
165            ctx.accounts.world.authorities.len(),
166            encoded_world_systems.len(),
167        );
168
169        // Transfer to make it rent exempt
170        let rent = Rent::get()?;
171        let new_minimum_balance = rent.minimum_balance(new_space);
172        let lamports_diff =
173            new_minimum_balance.saturating_sub(ctx.accounts.world.to_account_info().lamports());
174        if lamports_diff > 0 {
175            anchor_lang::solana_program::program::invoke(
176                &anchor_lang::solana_program::system_instruction::transfer(
177                    ctx.accounts.authority.key,
178                    ctx.accounts.world.to_account_info().key,
179                    lamports_diff,
180                ),
181                &[
182                    ctx.accounts.authority.to_account_info(),
183                    ctx.accounts.world.to_account_info(),
184                    ctx.accounts.system_program.to_account_info(),
185                ],
186            )?;
187        }
188        ctx.accounts
189            .world
190            .to_account_info()
191            .realloc(new_space, false)?;
192        msg!("Approved system: {:?}", world_systems);
193        Ok(())
194    }
195
196    pub fn remove_system(ctx: Context<RemoveSystem>) -> Result<()> {
197        if !ctx.accounts.authority.is_signer {
198            return Err(WorldError::InvalidAuthority.into());
199        }
200        if !ctx
201            .accounts
202            .world
203            .authorities
204            .contains(ctx.accounts.authority.key)
205        {
206            return Err(WorldError::InvalidAuthority.into());
207        }
208
209        let mut world_systems = ctx.accounts.world.systems();
210        world_systems
211            .approved_systems
212            .remove(&ctx.accounts.system.key());
213
214        let encoded_world_systems = world_systems.try_to_vec()?;
215        ctx.accounts.world.systems = encoded_world_systems.clone();
216
217        let new_space = World::space_for_authorities(
218            ctx.accounts.world.authorities.len(),
219            encoded_world_systems.len(),
220        );
221
222        if world_systems.approved_systems.is_empty() {
223            ctx.accounts.world.permissionless = true;
224        }
225
226        // Remove the extra rent
227        let rent = Rent::get()?;
228        let new_minimum_balance = rent.minimum_balance(new_space);
229        let lamports_diff =
230            new_minimum_balance.saturating_sub(ctx.accounts.world.to_account_info().lamports());
231        **ctx
232            .accounts
233            .world
234            .to_account_info()
235            .try_borrow_mut_lamports()? += lamports_diff;
236        **ctx
237            .accounts
238            .authority
239            .to_account_info()
240            .try_borrow_mut_lamports()? -= lamports_diff;
241        ctx.accounts
242            .world
243            .to_account_info()
244            .realloc(new_space, false)?;
245        msg!("Approved system: {:?}", world_systems);
246        Ok(())
247    }
248
249    #[allow(unused_variables)]
250    pub fn add_entity(ctx: Context<AddEntity>, extra_seed: Option<Vec<u8>>) -> Result<()> {
251        require!(
252            ctx.accounts.world.key() == ctx.accounts.world.pda().0,
253            WorldError::WorldAccountMismatch
254        );
255        ctx.accounts.entity.id = ctx.accounts.world.entities;
256        ctx.accounts.world.entities += 1;
257        Ok(())
258    }
259
260    pub fn initialize_component(ctx: Context<InitializeComponent>) -> Result<()> {
261        if !ctx.accounts.authority.is_signer && ctx.accounts.authority.key != &ID {
262            return Err(WorldError::InvalidAuthority.into());
263        }
264        bolt_component::cpi::initialize(ctx.accounts.build(&[World::cpi_auth_seeds().as_slice()]))?;
265        Ok(())
266    }
267
268    pub fn destroy_component(ctx: Context<DestroyComponent>) -> Result<()> {
269        bolt_component::cpi::destroy(ctx.accounts.build(&[World::cpi_auth_seeds().as_slice()]))?;
270        Ok(())
271    }
272
273    pub fn apply<'info>(
274        ctx: Context<'_, '_, '_, 'info, Apply<'info>>,
275        args: Vec<u8>,
276    ) -> Result<()> {
277        let (pairs, results) = apply_impl(
278            &ctx.accounts.authority,
279            &ctx.accounts.world,
280            &ctx.accounts.bolt_system,
281            ctx.accounts.build(),
282            args,
283            ctx.remaining_accounts.to_vec(),
284        )?;
285        for ((program, component), result) in pairs.into_iter().zip(results.into_iter()) {
286            bolt_component::cpi::update(
287                build_update_context(
288                    program,
289                    component,
290                    ctx.accounts.authority.clone(),
291                    ctx.accounts.cpi_auth.clone(),
292                    &[World::cpi_auth_seeds().as_slice()],
293                ),
294                result,
295            )?;
296        }
297        Ok(())
298    }
299
300    #[derive(Accounts)]
301    pub struct Apply<'info> {
302        /// CHECK: bolt system program check
303        #[account()]
304        pub bolt_system: UncheckedAccount<'info>,
305        /// CHECK: authority check
306        #[account()]
307        pub authority: Signer<'info>,
308        #[account()]
309        /// CHECK: cpi auth check
310        pub cpi_auth: UncheckedAccount<'info>,
311        #[account()]
312        pub world: Account<'info, World>,
313    }
314
315    impl<'info> Apply<'info> {
316        pub fn build(
317            &self,
318        ) -> CpiContext<'_, '_, '_, 'info, bolt_system::cpi::accounts::BoltExecute<'info>> {
319            let cpi_program = self.bolt_system.to_account_info();
320            let cpi_accounts = bolt_system::cpi::accounts::BoltExecute {
321                authority: self.authority.to_account_info(),
322            };
323            CpiContext::new(cpi_program, cpi_accounts)
324        }
325    }
326
327    pub fn apply_with_session<'info>(
328        ctx: Context<'_, '_, '_, 'info, ApplyWithSession<'info>>,
329        args: Vec<u8>,
330    ) -> Result<()> {
331        let (pairs, results) = apply_impl(
332            &ctx.accounts.authority,
333            &ctx.accounts.world,
334            &ctx.accounts.bolt_system,
335            ctx.accounts.build(),
336            args,
337            ctx.remaining_accounts.to_vec(),
338        )?;
339        for ((program, component), result) in pairs.into_iter().zip(results.into_iter()) {
340            bolt_component::cpi::update_with_session(
341                build_update_context_with_session(
342                    program,
343                    component,
344                    ctx.accounts.authority.clone(),
345                    ctx.accounts.cpi_auth.clone(),
346                    ctx.accounts.session_token.clone(),
347                    &[World::cpi_auth_seeds().as_slice()],
348                ),
349                result,
350            )?;
351        }
352        Ok(())
353    }
354
355    #[derive(Accounts)]
356    pub struct ApplyWithSession<'info> {
357        /// CHECK: bolt system program check
358        #[account()]
359        pub bolt_system: UncheckedAccount<'info>,
360        /// CHECK: authority check
361        #[account()]
362        pub authority: Signer<'info>,
363        #[account()]
364        /// CHECK: cpi auth check
365        pub cpi_auth: UncheckedAccount<'info>,
366        #[account()]
367        pub world: Account<'info, World>,
368        #[account()]
369        /// CHECK: The session token
370        pub session_token: UncheckedAccount<'info>,
371    }
372
373    impl<'info> ApplyWithSession<'info> {
374        pub fn build(
375            &self,
376        ) -> CpiContext<'_, '_, '_, 'info, bolt_system::cpi::accounts::BoltExecute<'info>> {
377            let cpi_program = self.bolt_system.to_account_info();
378            let cpi_accounts = bolt_system::cpi::accounts::BoltExecute {
379                authority: self.authority.to_account_info(),
380            };
381            CpiContext::new(cpi_program, cpi_accounts)
382        }
383    }
384}
385
386#[allow(clippy::type_complexity)]
387fn apply_impl<'info>(
388    authority: &Signer<'info>,
389    world: &Account<'info, World>,
390    bolt_system: &UncheckedAccount<'info>,
391    cpi_context: CpiContext<'_, '_, '_, 'info, bolt_system::cpi::accounts::BoltExecute<'info>>,
392    args: Vec<u8>,
393    mut remaining_accounts: Vec<AccountInfo<'info>>,
394) -> Result<(Vec<(AccountInfo<'info>, AccountInfo<'info>)>, Vec<Vec<u8>>)> {
395    if !authority.is_signer && authority.key != &ID {
396        return Err(WorldError::InvalidAuthority.into());
397    }
398    if !world.permissionless
399        && !world
400            .systems()
401            .approved_systems
402            .contains(&bolt_system.key())
403    {
404        return Err(WorldError::SystemNotApproved.into());
405    }
406
407    let mut pairs = Vec::new();
408    while remaining_accounts.len() >= 2 {
409        let program = remaining_accounts.remove(0);
410        if program.key() == ID {
411            break;
412        }
413        let component = remaining_accounts.remove(0);
414        pairs.push((program, component));
415    }
416
417    let mut components_accounts = pairs
418        .iter()
419        .map(|(_, component)| component)
420        .cloned()
421        .collect::<Vec<_>>();
422    components_accounts.append(&mut remaining_accounts);
423    let remaining_accounts = components_accounts;
424
425    let results = bolt_system::cpi::bolt_execute(
426        cpi_context.with_remaining_accounts(remaining_accounts),
427        args,
428    )?
429    .get();
430
431    if results.len() != pairs.len() {
432        return Err(WorldError::InvalidSystemOutput.into());
433    }
434    Ok((pairs, results))
435}
436
437#[derive(Accounts)]
438pub struct InitializeRegistry<'info> {
439    #[account(init, payer = payer, space = Registry::size(), seeds = [Registry::seed()], bump)]
440    pub registry: Account<'info, Registry>,
441    #[account(mut)]
442    pub payer: Signer<'info>,
443    pub system_program: Program<'info, System>,
444}
445
446#[derive(Accounts)]
447pub struct InitializeNewWorld<'info> {
448    #[account(mut)]
449    pub payer: Signer<'info>,
450    #[account(init, payer = payer, space = World::size(), seeds = [World::seed(), &registry.worlds.to_be_bytes()], bump)]
451    pub world: Account<'info, World>,
452    #[account(mut, address = Registry::pda().0)]
453    pub registry: Account<'info, Registry>,
454    pub system_program: Program<'info, System>,
455}
456
457#[derive(Accounts)]
458#[instruction(world_id: u64)]
459pub struct AddAuthority<'info> {
460    #[account(mut)]
461    pub authority: Signer<'info>,
462    #[account()]
463    /// CHECK: new authority check
464    pub new_authority: AccountInfo<'info>,
465    #[account(mut, seeds = [World::seed(), &world_id.to_be_bytes()], bump)]
466    pub world: Account<'info, World>,
467    pub system_program: Program<'info, System>,
468}
469
470#[derive(Accounts)]
471#[instruction(world_id: u64)]
472pub struct RemoveAuthority<'info> {
473    #[account(mut)]
474    pub authority: Signer<'info>,
475    #[account()]
476    /// CHECK: new authority check
477    pub authority_to_delete: AccountInfo<'info>,
478    #[account(mut, seeds = [World::seed(), &world_id.to_be_bytes()], bump)]
479    pub world: Account<'info, World>,
480    pub system_program: Program<'info, System>,
481}
482
483#[derive(Accounts)]
484pub struct ApproveSystem<'info> {
485    #[account(mut)]
486    pub authority: Signer<'info>,
487    #[account(mut)]
488    pub world: Account<'info, World>,
489    /// CHECK: Used for the pda derivation
490    pub system: AccountInfo<'info>,
491    pub system_program: Program<'info, System>,
492}
493
494#[derive(Accounts)]
495pub struct RemoveSystem<'info> {
496    #[account(mut)]
497    pub authority: Signer<'info>,
498    #[account(mut)]
499    pub world: Account<'info, World>,
500    /// CHECK: Used for the pda derivation
501    pub system: AccountInfo<'info>,
502    pub system_program: Program<'info, System>,
503}
504
505#[derive(Accounts)]
506#[instruction(extra_seed: Option<Vec<u8>>)]
507pub struct AddEntity<'info> {
508    #[account(mut)]
509    pub payer: Signer<'info>,
510    #[account(init, payer = payer, space = World::size(), seeds = [Entity::seed(), &world.id.to_be_bytes(),
511    &match extra_seed {
512        Some(ref _seed) => [0; 8],
513        None => world.entities.to_be_bytes()
514    },
515    match extra_seed {
516        Some(ref seed) => seed,
517        None => &[],
518    }], bump)]
519    pub entity: Account<'info, Entity>,
520    #[account(mut)]
521    pub world: Account<'info, World>,
522    pub system_program: Program<'info, System>,
523}
524
525#[derive(Accounts)]
526pub struct InitializeComponent<'info> {
527    #[account(mut)]
528    pub payer: Signer<'info>,
529    #[account(mut)]
530    /// CHECK: component data check
531    pub data: AccountInfo<'info>,
532    #[account()]
533    pub entity: Account<'info, Entity>,
534    /// CHECK: component program check
535    pub component_program: AccountInfo<'info>,
536    /// CHECK: authority check
537    pub authority: AccountInfo<'info>,
538    #[account()]
539    /// CHECK: cpi auth check
540    pub cpi_auth: UncheckedAccount<'info>,
541    pub system_program: Program<'info, System>,
542}
543
544impl<'info> InitializeComponent<'info> {
545    pub fn build<'a, 'b, 'c>(
546        &self,
547        signer_seeds: &'a [&'b [&'c [u8]]],
548    ) -> CpiContext<'a, 'b, 'c, 'info, bolt_component::cpi::accounts::Initialize<'info>> {
549        let cpi_program = self.component_program.to_account_info();
550
551        let cpi_accounts = bolt_component::cpi::accounts::Initialize {
552            payer: self.payer.to_account_info(),
553            data: self.data.to_account_info(),
554            entity: self.entity.to_account_info(),
555            authority: self.authority.to_account_info(),
556            cpi_auth: self.cpi_auth.to_account_info(),
557            system_program: self.system_program.to_account_info(),
558        };
559        CpiContext::new_with_signer(cpi_program, cpi_accounts, signer_seeds)
560    }
561}
562
563#[derive(Accounts)]
564pub struct DestroyComponent<'info> {
565    #[account(mut)]
566    pub authority: Signer<'info>,
567    #[account(mut)]
568    /// CHECK: receiver check
569    pub receiver: AccountInfo<'info>,
570    /// CHECK: component program check
571    pub component_program: AccountInfo<'info>,
572    /// CHECK: component program data check
573    pub component_program_data: AccountInfo<'info>,
574    #[account()]
575    pub entity: Account<'info, Entity>,
576    #[account(mut)]
577    /// CHECK: component data check
578    pub component: UncheckedAccount<'info>,
579    #[account()]
580    /// CHECK: cpi auth check
581    pub cpi_auth: UncheckedAccount<'info>,
582    pub system_program: Program<'info, System>,
583}
584
585impl<'info> DestroyComponent<'info> {
586    pub fn build<'a, 'b, 'c>(
587        &self,
588        signer_seeds: &'a [&'b [&'c [u8]]],
589    ) -> CpiContext<'a, 'b, 'c, 'info, bolt_component::cpi::accounts::Destroy<'info>> {
590        let cpi_program = self.component_program.to_account_info();
591
592        let cpi_accounts = bolt_component::cpi::accounts::Destroy {
593            authority: self.authority.to_account_info(),
594            receiver: self.receiver.to_account_info(),
595            entity: self.entity.to_account_info(),
596            component: self.component.to_account_info(),
597            component_program_data: self.component_program_data.to_account_info(),
598            cpi_auth: self.cpi_auth.to_account_info(),
599            system_program: self.system_program.to_account_info(),
600        };
601        CpiContext::new_with_signer(cpi_program, cpi_accounts, signer_seeds)
602    }
603}
604
605#[account]
606#[derive(InitSpace, Default, Copy)]
607pub struct Registry {
608    pub worlds: u64,
609}
610
611impl Registry {
612    pub fn seed() -> &'static [u8] {
613        b"registry"
614    }
615
616    pub fn size() -> usize {
617        8 + Registry::INIT_SPACE
618    }
619
620    pub fn pda() -> (Pubkey, u8) {
621        Pubkey::find_program_address(&[Registry::seed()], &crate::ID)
622    }
623}
624
625#[account]
626#[derive(Debug)]
627pub struct World {
628    pub id: u64,
629    pub entities: u64,
630    pub authorities: Vec<Pubkey>,
631    pub permissionless: bool,
632    pub systems: Vec<u8>,
633}
634
635impl Default for World {
636    fn default() -> Self {
637        Self {
638            id: 0,
639            entities: 0,
640            authorities: Vec::new(),
641            permissionless: true,
642            systems: Vec::new(),
643        }
644    }
645}
646
647impl World {
648    fn space_for_authorities(auths: usize, systems_space: usize) -> usize {
649        16 + 8 + 32 * auths + 1 + 8 + systems_space
650    }
651
652    pub fn systems(&self) -> WorldSystems {
653        if self.permissionless {
654            return WorldSystems::default();
655        }
656        WorldSystems::try_from_slice(self.systems.as_ref()).unwrap_or_default()
657    }
658}
659
660#[derive(
661    anchor_lang::prelude::borsh::BorshSerialize,
662    anchor_lang::prelude::borsh::BorshDeserialize,
663    Default,
664    Debug,
665)]
666pub struct WorldSystems {
667    pub approved_systems: BTreeSet<Pubkey>,
668}
669
670impl World {
671    pub fn seed() -> &'static [u8] {
672        b"world"
673    }
674
675    pub fn size() -> usize {
676        16 + 8 + 1 + 8
677    }
678
679    pub fn pda(&self) -> (Pubkey, u8) {
680        Pubkey::find_program_address(&[World::seed(), &self.id.to_be_bytes()], &crate::ID)
681    }
682
683    pub fn cpi_auth_seeds() -> [&'static [u8]; 2] {
684        [b"cpi_auth", &[251]] // 251 is the pre-computed bump for cpi_auth.
685    }
686
687    pub const fn cpi_auth_address() -> Pubkey {
688        Pubkey::from_str_const("B2f2y3QTBv346wE6nWKor72AUhUvFF6mPk7TWCF2QVhi") // This is the pre-computed address for cpi_auth.
689    }
690}
691
692#[account]
693#[derive(InitSpace, Default, Copy)]
694pub struct Entity {
695    pub id: u64,
696}
697
698impl Entity {
699    pub fn seed() -> &'static [u8] {
700        b"entity"
701    }
702}
703
704#[account]
705#[derive(InitSpace, Default)]
706pub struct SystemWhitelist {}
707
708impl SystemWhitelist {
709    pub fn seed() -> &'static [u8] {
710        b"whitelist"
711    }
712
713    pub fn size() -> usize {
714        8 + Registry::INIT_SPACE
715    }
716}
717
718/// Builds the context for updating a component.
719pub fn build_update_context<'a, 'b, 'c, 'info>(
720    component_program: AccountInfo<'info>,
721    bolt_component: AccountInfo<'info>,
722    authority: Signer<'info>,
723    cpi_auth: UncheckedAccount<'info>,
724    signer_seeds: &'a [&'b [&'c [u8]]],
725) -> CpiContext<'a, 'b, 'c, 'info, bolt_component::cpi::accounts::Update<'info>> {
726    let authority = authority.to_account_info();
727    let cpi_auth = cpi_auth.to_account_info();
728    let cpi_program = component_program;
729    bolt_component::cpi::accounts::Update {
730        bolt_component,
731        authority,
732        cpi_auth,
733    }
734    .build_cpi_context(cpi_program, signer_seeds)
735}
736
737/// Builds the context for updating a component.
738pub fn build_update_context_with_session<'a, 'b, 'c, 'info>(
739    component_program: AccountInfo<'info>,
740    bolt_component: AccountInfo<'info>,
741    authority: Signer<'info>,
742    cpi_auth: UncheckedAccount<'info>,
743    session_token: UncheckedAccount<'info>,
744    signer_seeds: &'a [&'b [&'c [u8]]],
745) -> CpiContext<'a, 'b, 'c, 'info, bolt_component::cpi::accounts::UpdateWithSession<'info>> {
746    let authority = authority.to_account_info();
747    let cpi_auth = cpi_auth.to_account_info();
748    let cpi_program = component_program;
749    let session_token = session_token.to_account_info();
750    bolt_component::cpi::accounts::UpdateWithSession {
751        bolt_component,
752        authority,
753        cpi_auth,
754        session_token,
755    }
756    .build_cpi_context(cpi_program, signer_seeds)
757}