1use {
2 crate::{constants::vault_seeds, errors::HplHiveControlError, state::*},
3 anchor_lang::prelude::*,
4 hpl_toolkit::prelude::*,
5 spl_account_compression::{program::SplAccountCompression, Noop},
6 std::collections::HashSet,
7};
8
9fn create_user<'info>(
10 info: UserInfo,
11 social_info: Option<SocialInfo>,
12 wallets: Wallets,
13 clock_slot: u64,
14 vault_bump: u8,
15 global: &mut Account<'info, Global>,
16 merkle_tree: &AccountInfo<'info>,
17 vault: &AccountInfo<'info>,
18 compression_program: &Program<'info, SplAccountCompression>,
19 log_wrapper: &Program<'info, Noop>,
20) -> Result<u64> {
21 let (leaf_idx, _) = global
22 .user_trees
23 .assert_append(merkle_tree.to_account_info())?;
24
25 global.total_users += 1;
26
27 let user = User {
28 id: global.total_users,
29 info,
30 wallets,
31 social_info: social_info.unwrap_or_default(),
32 placeholder_1: PlaceHolder::None,
33 placeholder_2: PlaceHolder::None,
34 placeholder_3: PlaceHolder::None,
35 placeholder_4: PlaceHolder::None,
36 };
37
38 append_leaf(
39 user.to_compressed().to_node(),
40 &vault,
41 merkle_tree,
42 compression_program,
43 log_wrapper,
44 Some(&[&vault_seeds(&[vault_bump])[..]]),
45 )?;
46
47 user.emit(clock_slot, leaf_idx, merkle_tree, log_wrapper, 0)?;
48
49 Ok(user.id)
50}
51
52#[derive(Accounts)]
53pub struct NewUser<'info> {
54 #[account(mut)]
55 pub global: Account<'info, Global>,
56
57 #[account(mut)]
59 pub merkle_tree: AccountInfo<'info>,
60
61 pub shadow_signer: AccountInfo<'info>,
65
66 pub wallet: AccountInfo<'info>,
69
70 pub authority: Signer<'info>,
72
73 #[account(mut, seeds = [b"vault".as_ref(), crate::id().as_ref()], bump)]
75 pub vault: AccountInfo<'info>,
76
77 pub system_program: Program<'info, System>,
79
80 pub compression_program: Program<'info, SplAccountCompression>,
82
83 pub log_wrapper: Program<'info, Noop>,
85
86 pub clock: Sysvar<'info, Clock>,
88
89 pub rent_sysvar: Sysvar<'info, Rent>,
91
92 #[account(address = anchor_lang::solana_program::sysvar::instructions::ID)]
95 pub instructions_sysvar: AccountInfo<'info>,
96}
97
98#[derive(AnchorSerialize, AnchorDeserialize, Clone)]
107pub struct NewUserArgs {
108 pub username: ShortString,
109 pub name: ShortString,
110 pub bio: ShortString,
111 pub pfp: ShortString,
112 pub social_info: Option<SocialInfoInput>,
113}
114
115pub fn new_user(ctx: Context<NewUser>, args: NewUserArgs) -> Result<u64> {
116 let wallets = ShortVec::new_from_vec_unchecked(vec![ctx.accounts.wallet.key()]);
117
118 let social_info = args
119 .social_info
120 .map(|input| input.into_data(ctx.remaining_accounts, &wallets));
121
122 create_user(
123 UserInfo {
124 username: args.username,
125 name: args.name,
126 bio: args.bio,
127 pfp: args.pfp,
128 },
129 social_info,
130 Wallets {
131 shadow: ctx.accounts.shadow_signer.key(),
132 wallets,
133 },
134 ctx.accounts.clock.slot,
135 ctx.bumps.vault,
136 &mut ctx.accounts.global,
137 &ctx.accounts.merkle_tree,
138 &ctx.accounts.vault,
139 &ctx.accounts.compression_program,
140 &ctx.accounts.log_wrapper,
141 )
142}
143
144pub fn populate_user(ctx: Context<NewUser>, user: User) -> Result<u64> {
145 create_user(
146 user.info,
147 Some(user.social_info),
148 user.wallets,
149 ctx.accounts.clock.slot,
150 ctx.bumps.vault,
151 &mut ctx.accounts.global,
152 &ctx.accounts.merkle_tree,
153 &ctx.accounts.vault,
154 &ctx.accounts.compression_program,
155 &ctx.accounts.log_wrapper,
156 )
157}
158
159#[derive(Accounts)]
160pub struct NewUserWithProfile<'info> {
161 #[account(mut)]
162 pub global: Account<'info, Global>,
163
164 #[account(mut)]
165 pub project: Account<'info, Project>,
166
167 #[account(mut)]
169 pub users_merkle_tree: AccountInfo<'info>,
170
171 #[account(mut)]
173 pub profiles_merkle_tree: AccountInfo<'info>,
174
175 pub shadow_signer: AccountInfo<'info>,
179
180 pub wallet: AccountInfo<'info>,
183
184 pub authority: Signer<'info>,
186
187 #[account(mut)]
189 pub payer: Signer<'info>,
190
191 #[account(mut, seeds = [b"vault".as_ref(), crate::id().as_ref()], bump)]
193 pub vault: AccountInfo<'info>,
194
195 pub system_program: Program<'info, System>,
197
198 pub compression_program: Program<'info, SplAccountCompression>,
200
201 pub log_wrapper: Program<'info, Noop>,
203
204 pub clock: Sysvar<'info, Clock>,
206
207 pub rent_sysvar: Sysvar<'info, Rent>,
209
210 #[account(address = anchor_lang::solana_program::sysvar::instructions::ID)]
213 pub instructions_sysvar: AccountInfo<'info>,
214}
215
216#[derive(AnchorSerialize, AnchorDeserialize, Clone)]
225pub struct NewUserWithProfileArgs {
226 pub user_info: UserInfo,
227 pub user_social_info: Option<SocialInfoInput>,
228 pub profile_identity: Option<ShortString>,
229}
230
231pub fn new_user_with_profile(
232 ctx: &mut Context<NewUserWithProfile>,
233 args: NewUserWithProfileArgs,
234) -> Result<()> {
235 let wallets = ShortVec::new_from_vec_unchecked(vec![ctx.accounts.wallet.key()]);
236 let social_info = args
237 .user_social_info
238 .map(|input| input.into_data(ctx.remaining_accounts, &wallets));
239
240 let user_id = create_user(
241 UserInfo {
242 username: args.user_info.username,
243 name: args.user_info.name.clone(),
244 bio: args.user_info.bio.clone(),
245 pfp: args.user_info.pfp.clone(),
246 },
247 social_info,
248 Wallets {
249 shadow: ctx.accounts.shadow_signer.key(),
250 wallets,
251 },
252 ctx.accounts.clock.slot,
253 ctx.bumps.vault,
254 &mut ctx.accounts.global,
255 &ctx.accounts.users_merkle_tree,
256 &ctx.accounts.vault,
257 &ctx.accounts.compression_program,
258 &ctx.accounts.log_wrapper,
259 )?;
260
261 super::create_profile(
262 user_id,
263 args.profile_identity,
264 Some(ProfileInfo {
265 name: Some(args.user_info.name),
266 bio: Some(args.user_info.bio),
267 pfp: Some(args.user_info.pfp),
268 }),
269 &mut ctx.accounts.project,
270 ctx.accounts.clock.slot,
271 ctx.bumps.vault,
272 &ctx.accounts.profiles_merkle_tree,
273 &ctx.accounts.vault,
274 &ctx.accounts.compression_program,
275 &ctx.accounts.log_wrapper,
276 )
277}
278
279#[derive(AnchorSerialize, AnchorDeserialize, Clone)]
288pub struct PopulateUserWithProfileArgs {
289 pub user: User,
290 pub profile: Profile,
291}
292
293pub fn populate_user_with_profile(
294 ctx: Context<NewUserWithProfile>,
295 args: PopulateUserWithProfileArgs,
296) -> Result<()> {
297 let mut profile = args.profile;
298 profile.user_id = create_user(
299 args.user.info,
300 Some(args.user.social_info),
301 args.user.wallets,
302 ctx.accounts.clock.slot,
303 ctx.bumps.vault,
304 &mut ctx.accounts.global,
305 &ctx.accounts.users_merkle_tree,
306 &ctx.accounts.vault,
307 &ctx.accounts.compression_program,
308 &ctx.accounts.log_wrapper,
309 )?;
310 let (leaf_idx, _) = ctx
311 .accounts
312 .project
313 .profile_trees
314 .assert_append(ctx.accounts.profiles_merkle_tree.to_account_info())?;
315
316 append_leaf(
317 profile.to_compressed().to_node(),
318 &ctx.accounts.vault,
319 &ctx.accounts.profiles_merkle_tree,
320 &ctx.accounts.compression_program,
321 &ctx.accounts.log_wrapper,
322 Some(&[&vault_seeds(&[ctx.bumps.vault])[..]]),
323 )?;
324
325 profile.emit(
326 ctx.accounts.clock.slot,
327 leaf_idx,
328 &ctx.accounts.profiles_merkle_tree,
329 &ctx.accounts.log_wrapper,
330 0,
331 )
332}
333
334#[derive(Accounts)]
335pub struct UpdateUser<'info> {
336 pub global: Account<'info, Global>,
337
338 #[account(mut)]
340 pub merkle_tree: AccountInfo<'info>,
341
342 pub authority: Signer<'info>,
344
345 #[account(mut, seeds = [b"vault".as_ref(), crate::id().as_ref()], bump)]
347 pub vault: AccountInfo<'info>,
348
349 pub system_program: Program<'info, System>,
351
352 pub compression_program: Program<'info, SplAccountCompression>,
354
355 pub log_wrapper: Program<'info, Noop>,
357
358 pub clock: Sysvar<'info, Clock>,
360
361 pub rent_sysvar: Sysvar<'info, Rent>,
363
364 #[account(address = anchor_lang::solana_program::sysvar::instructions::ID)]
367 pub instructions_sysvar: AccountInfo<'info>,
368}
369
370#[derive(AnchorSerialize, AnchorDeserialize)]
371pub struct UpdateUserArgs {
372 pub root: [u8; 32],
373 pub leaf_idx: u32,
374 pub user_id: u64,
375 pub info: DataOrHash<UserInfoUpdates>,
376 pub wallets: DataOrHash<UserWalletsUpdates>,
377 pub social_info: DataOrHash<SocialInfoUpdates>,
378}
379
380pub fn update_user<'info>(
381 ctx: Context<'_, '_, '_, 'info, UpdateUser<'info>>,
382 args: UpdateUserArgs,
383) -> Result<()> {
384 let mut current_user_data = UserCompressed {
385 id: args.user_id,
386 info: [0; 32],
387 wallets: [0; 32],
388 social_info: [0; 32],
389 placeholder_1: PlaceHolder::None.to_node(),
390 placeholder_2: PlaceHolder::None.to_node(),
391 placeholder_3: PlaceHolder::None.to_node(),
392 placeholder_4: PlaceHolder::None.to_node(),
393 };
394
395 let mut new_user_data = UserCompressed {
396 id: args.user_id,
397 info: [0; 32],
398 wallets: [0; 32],
399 social_info: [0; 32],
400 placeholder_1: PlaceHolder::None.to_node(),
401 placeholder_2: PlaceHolder::None.to_node(),
402 placeholder_3: PlaceHolder::None.to_node(),
403 placeholder_4: PlaceHolder::None.to_node(),
404 };
405
406 let mut info_to_emit = None::<UserInfo>;
407 match args.info {
408 DataOrHash::Data(updates) => {
409 let mut info = UserInfo {
410 username: updates.current_username,
411 name: updates.current_name,
412 bio: updates.current_bio,
413 pfp: updates.current_pfp,
414 };
415 current_user_data.info = info.to_node();
416
417 if let Some(username) = &updates.new_username {
418 info.username = username.to_owned();
419 }
420
421 if let Some(name) = &updates.new_name {
422 info.name = name.to_owned();
423 }
424
425 if let Some(bio) = &updates.new_bio {
426 info.bio = bio.to_owned();
427 }
428
429 if let Some(pfp) = &updates.new_pfp {
430 info.pfp = pfp.to_owned();
431 }
432
433 new_user_data.info = info.to_node();
434 info_to_emit = Some(info);
435 }
436 DataOrHash::Hash(hash) => {
437 current_user_data.info = hash;
438 new_user_data.info = hash;
439 }
440 }
441
442 let mut wallet_changes = None::<UserWalletOutput>;
443 let mut wallets_to_emit = None::<Wallets>;
444 match args.wallets {
445 DataOrHash::Data(updates) => {
446 let mut wallets = updates.wallets.clone();
447 current_user_data.wallets = wallets.to_node();
448
449 wallet_changes = Some(wallets.apply_changes_and_return_effects(updates.changes));
450
451 new_user_data.wallets = wallets.to_node();
452 wallets_to_emit = Some(wallets);
453 }
454 DataOrHash::Hash(hash) => {
455 current_user_data.wallets = hash;
456 new_user_data.wallets = hash;
457 }
458 }
459
460 let mut social_info_to_emit = None::<SocialInfo>;
461 let mut remaining_account_offset: usize = 0;
462 match args.social_info {
463 DataOrHash::Data(updates) => {
464 let mut social_info = SocialInfo {
465 twitter: updates.current_twitter,
466 discord: updates.current_discord,
467 steam: updates.current_steam,
468 civic: updates.current_civic,
469 };
470 current_user_data.social_info = social_info.to_node();
471
472 if let Some(user_wallet_output) = wallet_changes {
473 social_info.civic = user_wallet_output.commit_to_civic_info(social_info.civic)
474 }
475 if let Some(twitter) = &updates.new_twitter {
477 social_info.twitter = Some(twitter.clone());
478 }
479
480 if let Some(discord) = &updates.new_discord {
481 social_info.discord = Some(discord.clone());
482 }
483
484 if let Some(steam) = &updates.new_steam {
485 social_info.steam = Some(steam.clone());
486 }
487 let civic_found_set: HashSet<(CivicGateway, u8)> = if updates.new_civic.len() > 0 {
488 let mut set = HashSet::new();
489 for civic in &social_info.civic.as_vec() {
490 set.insert((civic.gatekeeper_network.to_owned(), civic.wallet_index));
491 }
492 set
493 } else {
494 HashSet::new()
495 };
496 for civic_input in updates.new_civic {
497 let gateway_token = ctx.remaining_accounts.get(remaining_account_offset);
498 remaining_account_offset += 1;
499 if gateway_token.is_none() {
500 return Err(HplHiveControlError::GatewayTokenNotProvided.into());
501 }
502 if !civic_found_set.contains(&(
503 civic_input.gatekeeper_network.to_owned(),
504 civic_input.wallet_index.to_owned(),
505 )) {
506 let gateway_token = gateway_token.unwrap();
507 social_info.civic.push(civic_input.to_civic_info_validated(
508 gateway_token,
509 &wallets_to_emit.as_ref().unwrap().wallets,
510 ));
511 }
512 }
513
514 new_user_data.social_info = social_info.to_node();
515 social_info_to_emit = Some(social_info);
516 }
517
518 DataOrHash::Hash(hash) => {
519 if UserWalletOutput::require_to_update_civic(&wallet_changes) {
520 return Err(HplHiveControlError::SocialInfoRequiredWithWalletRemoval.into());
521 }
522 current_user_data.social_info = hash;
523 new_user_data.social_info = hash;
524 }
525 }
526
527 replace_leaf(
528 args.root,
529 current_user_data.to_node(),
530 new_user_data.to_node(),
531 args.leaf_idx,
532 &ctx.accounts.vault,
533 &ctx.accounts.merkle_tree,
534 &ctx.accounts.compression_program,
535 &ctx.accounts.log_wrapper,
536 ctx.remaining_accounts[remaining_account_offset..].to_vec(),
537 Some(&[&vault_seeds(&[ctx.bumps.vault])[..]]),
538 )?;
539
540 let mut seq_offset = 0;
541 if let Some(info) = info_to_emit {
542 info.emit(
543 ctx.accounts.clock.slot,
544 args.leaf_idx,
545 &ctx.accounts.merkle_tree,
546 &ctx.accounts.log_wrapper,
547 seq_offset,
548 )?;
549 seq_offset += 1;
550 }
551
552 if let Some(wallets) = wallets_to_emit {
553 wallets.emit(
554 ctx.accounts.clock.slot,
555 args.leaf_idx,
556 &ctx.accounts.merkle_tree,
557 &ctx.accounts.log_wrapper,
558 seq_offset,
559 )?;
560 seq_offset += 1;
561 }
562
563 if let Some(social_info) = social_info_to_emit {
564 social_info.emit(
565 ctx.accounts.clock.slot,
566 args.leaf_idx,
567 &ctx.accounts.merkle_tree,
568 &ctx.accounts.log_wrapper,
569 seq_offset,
570 )?;
571 }
572
573 Ok(())
574}
575#[derive(AnchorSerialize, AnchorDeserialize)]
576pub struct UpdateUserBrutallyArgs {
577 pub root: [u8; 32],
578 pub leaf_idx: u32,
579 pub previous_leaf: [u8; 32],
580 pub id: u64,
581 pub info: UserInfo,
582 pub wallets: Wallets,
583 pub social_info: SocialInfo,
584}
585
586pub fn update_user_brutally<'info>(
587 ctx: Context<'_, '_, '_, 'info, UpdateUser<'info>>,
588 args: UpdateUserBrutallyArgs,
589) -> Result<()> {
590 replace_leaf(
591 args.root,
592 args.previous_leaf.to_node(),
593 UserCompressed {
594 id: args.id,
595 info: [0; 32],
596 wallets: [0; 32],
597 social_info: [0; 32],
598 placeholder_1: PlaceHolder::None.to_node(),
599 placeholder_2: PlaceHolder::None.to_node(),
600 placeholder_3: PlaceHolder::None.to_node(),
601 placeholder_4: PlaceHolder::None.to_node(),
602 }
603 .to_node(),
604 args.leaf_idx,
605 &ctx.accounts.vault,
606 &ctx.accounts.merkle_tree,
607 &ctx.accounts.compression_program,
608 &ctx.accounts.log_wrapper,
609 ctx.remaining_accounts.to_vec(),
610 Some(&[&vault_seeds(&[ctx.bumps.vault])[..]]),
611 )?;
612
613 args.info.emit(
614 ctx.accounts.clock.slot,
615 args.leaf_idx,
616 &ctx.accounts.merkle_tree,
617 &ctx.accounts.log_wrapper,
618 0,
619 )?;
620
621 args.wallets.emit(
622 ctx.accounts.clock.slot,
623 args.leaf_idx,
624 &ctx.accounts.merkle_tree,
625 &ctx.accounts.log_wrapper,
626 1,
627 )?;
628
629 args.social_info.emit(
630 ctx.accounts.clock.slot,
631 args.leaf_idx,
632 &ctx.accounts.merkle_tree,
633 &ctx.accounts.log_wrapper,
634 2,
635 )?;
636
637 Ok(())
638}