1use anchor_lang::{
3 prelude::*,
4 solana_program::{
5 instruction::{AccountMeta, Instruction},
6 program::{invoke, invoke_signed},
7 system_instruction, sysvar,
8 },
9};
10use anchor_spl::token::{self, Token, TokenAccount};
11use mpl_token_metadata;
12use std::io::Write;
13
14pub mod merkle_proof;
15
16declare_id!("gdrpGjVffourzkdDRrQmySw4aTHr8a3xmQzzxSwFD1a");
17pub const CANDY_MACHINE_V1_PROGRAM_ID: Pubkey = Pubkey::new_from_array([
18 0x09, 0x2a, 0xee, 0x40, 0xbb, 0xdd, 0x63, 0x1e, 0xef, 0xfb, 0x7c, 0x96, 0xf6, 0x15, 0x65, 0x76,
19 0x84, 0x65, 0xf3, 0xc1, 0x9c, 0xf9, 0x90, 0xcd, 0x7f, 0x74, 0x8c, 0x8d, 0x79, 0x95, 0x08, 0x20,
20]);
21
22pub const CANDY_MACHINE_V2_PROGRAM_ID: Pubkey = Pubkey::new_from_array([
23 0x09, 0x2a, 0xee, 0x3d, 0xfc, 0x2d, 0x0e, 0x55, 0x78, 0x23, 0x13, 0x83, 0x79, 0x69, 0xea, 0xf5,
24 0x21, 0x51, 0xc0, 0x96, 0xc0, 0x6b, 0x5c, 0x2a, 0x82, 0xf0, 0x86, 0xa5, 0x03, 0xe8, 0x2c, 0x34,
25]);
26
27fn verify_candy(candy_machine_program_account: &Pubkey) -> Result<()> {
28 if candy_machine_program_account != &CANDY_MACHINE_V1_PROGRAM_ID
29 && candy_machine_program_account != &CANDY_MACHINE_V2_PROGRAM_ID
30 {
31 return Err(GumdropError::MustUseOfficialCandyMachine.into());
32 }
33 Ok(())
34}
35
36const CLAIM_COUNT: &[u8] = b"ClaimCount";
37const CLAIM_STATUS: &[u8] = b"ClaimStatus";
38
39fn verify_temporal<'a>(
40 distributor: &Account<'a, MerkleDistributor>,
41 temporal: &Signer<'a>,
42 claimant_secret: Pubkey,
43) -> Result<()> {
44 require!(
45 temporal.key() == distributor.temporal
47 || temporal.key() == claimant_secret
50 || distributor.temporal == Pubkey::default(),
52 GumdropError::TemporalMismatch
53 );
54
55 Ok(())
56}
57
58fn verify_claim_bump<'a>(
59 claim_account: &AccountInfo<'a>,
60 claim_prefix: &[u8],
61 claim_bump: u8,
62 index: u64,
63 distributor: &Account<'a, MerkleDistributor>,
64) -> Result<()> {
65 require!(
66 claim_prefix == CLAIM_COUNT || claim_prefix == CLAIM_STATUS,
67 GumdropError::InvalidClaimBump,
68 );
69
70 let (claim_account_key, claim_account_bump) = Pubkey::find_program_address(
71 &[
72 claim_prefix,
73 &index.to_le_bytes(),
74 &distributor.key().to_bytes(),
75 ],
76 &ID,
77 );
78 require!(
79 claim_account_key == *claim_account.key && claim_account_bump == claim_bump,
80 GumdropError::InvalidClaimBump,
81 );
82
83 Ok(())
84}
85
86fn get_or_create_claim_count<'a>(
87 distributor: &Account<'a, MerkleDistributor>,
88 claim_count: &AccountInfo<'a>,
89 temporal: &Signer<'a>,
90 payer: &Signer<'a>,
91 system_program: &Program<'a, System>,
92 claim_bump: u8,
93 index: u64,
94 claimant_secret: Pubkey,
95) -> Result<Account<'a, ClaimCount>> {
96 let rent = &Rent::get()?;
97 let space = 8 + ClaimCount::default().try_to_vec().unwrap().len();
98
99 verify_claim_bump(claim_count, CLAIM_COUNT, claim_bump, index, distributor)?;
100
101 let create_claim_state = claim_count.lamports() == 0; if create_claim_state {
103 let lamports = rent.minimum_balance(space);
104 let claim_count_seeds = [
105 CLAIM_COUNT.as_ref(),
106 &index.to_le_bytes(),
107 &distributor.key().to_bytes(),
108 &[claim_bump],
109 ];
110
111 invoke_signed(
112 &system_instruction::create_account(
113 &payer.key(),
114 claim_count.key,
115 lamports,
116 space as u64,
117 &ID,
118 ),
119 &[
120 payer.to_account_info().clone(),
121 claim_count.clone(),
122 system_program.to_account_info().clone(),
123 ],
124 &[&claim_count_seeds],
125 )?;
126
127 let mut data = claim_count.try_borrow_mut_data()?;
128 let dst: &mut [u8] = &mut data;
129 let mut cursor = std::io::Cursor::new(dst);
130 cursor
131 .write_all(&<ClaimCount as anchor_lang::Discriminator>::discriminator())
132 .unwrap();
133 }
134
135 let mut pa: Account<ClaimCount> = Account::try_from(&claim_count)?;
137
138 if create_claim_state {
139 verify_temporal(distributor, temporal, claimant_secret)?;
140 pa.claimant = payer.key();
141 } else {
142 require!(pa.claimant == payer.key(), GumdropError::OwnerMismatch);
143 }
144
145 Ok(pa)
146}
147
148#[program]
150pub mod gumdrop {
151 use super::*;
152
153 pub fn new_distributor(
157 ctx: Context<NewDistributor>,
158 _bump: u8,
159 root: [u8; 32],
160 temporal: Pubkey,
161 ) -> Result<()> {
162 let distributor = &mut ctx.accounts.distributor;
163
164 distributor.base = ctx.accounts.base.key();
165 distributor.bump = *ctx
166 .bumps
167 .get("distributor")
168 .ok_or(GumdropError::BumpSeedNotInHashMap)?;
169
170 distributor.root = root;
171 distributor.temporal = temporal;
172
173 Ok(())
174 }
175
176 pub fn close_distributor_token_account(
179 ctx: Context<CloseDistributorTokenAccount>,
180 _bump: u8,
181 ) -> Result<()> {
182 let distributor = &ctx.accounts.distributor;
183
184 require!(
186 distributor.base == ctx.accounts.base.key(),
187 GumdropError::Unauthorized
188 );
189
190 let seeds = [
191 b"MerkleDistributor".as_ref(),
192 &distributor.base.to_bytes(),
193 &[ctx.accounts.distributor.bump],
194 ];
195
196 token::transfer(
197 CpiContext::new(
198 ctx.accounts.token_program.to_account_info(),
199 token::Transfer {
200 from: ctx.accounts.from.to_account_info(),
201 to: ctx.accounts.to.to_account_info(),
202 authority: ctx.accounts.distributor.to_account_info(),
203 },
204 )
205 .with_signer(&[&seeds[..]]),
206 ctx.accounts.from.amount,
207 )?;
208
209 token::close_account(
210 CpiContext::new(
211 ctx.accounts.token_program.to_account_info(),
212 token::CloseAccount {
213 account: ctx.accounts.from.to_account_info(),
214 destination: ctx.accounts.receiver.to_account_info(),
215 authority: ctx.accounts.distributor.to_account_info(),
216 },
217 )
218 .with_signer(&[&seeds[..]]),
219 )?;
220
221 Ok(())
222 }
223
224 pub fn close_distributor<'info>(
229 ctx: Context<'_, '_, '_, 'info, CloseDistributor<'info>>,
230 _bump: u8,
231 _wallet_bump: u8,
232 ) -> Result<()> {
233 let distributor = &ctx.accounts.distributor;
234
235 require!(
237 distributor.base == ctx.accounts.base.key(),
238 GumdropError::Unauthorized
239 );
240
241 let wallet_seeds = [
242 b"Wallet".as_ref(),
243 &distributor.key().to_bytes(),
244 &[_wallet_bump],
245 ];
246
247 if !ctx.remaining_accounts.is_empty() {
248 let candy_machine_info = &ctx.remaining_accounts[0];
250 let candy_machine_program_info = &ctx.remaining_accounts[1];
251 verify_candy(candy_machine_program_info.key)?;
252 let mut data = vec![0x20, 0x2e, 0x40, 0x1c, 0x95, 0x4b, 0xf3, 0x58];
254
255 data.push(0x01);
256 data.extend_from_slice(&ctx.accounts.receiver.key.to_bytes());
257
258 invoke_signed(
259 &Instruction {
260 program_id: *candy_machine_program_info.key,
261 accounts: vec![
262 AccountMeta::new(*candy_machine_info.key, false),
263 AccountMeta::new(*ctx.accounts.distributor_wallet.key, true),
264 ],
265 data: data,
266 },
267 &[
268 candy_machine_info.clone(),
269 ctx.accounts.distributor_wallet.clone(),
270 ],
271 &[&wallet_seeds],
272 )?;
273 }
274
275 invoke_signed(
276 &system_instruction::transfer(
277 ctx.accounts.distributor_wallet.key,
278 ctx.accounts.receiver.key,
279 ctx.accounts.distributor_wallet.lamports(),
280 ),
281 &[
282 ctx.accounts.distributor_wallet.clone(),
283 ctx.accounts.receiver.clone(),
284 ctx.accounts.system_program.to_account_info().clone(),
285 ],
286 &[&wallet_seeds],
287 )?;
288
289 Ok(())
290 }
291
292 pub fn prove_claim<'info>(
293 ctx: Context<ProveClaim>,
294 claim_prefix: Vec<u8>,
295 claim_bump: u8,
296 index: u64,
297 amount: u64,
298 claimant_secret: Pubkey,
299 resource: Pubkey,
300 resource_nonce: Vec<u8>,
301 proof: Vec<[u8; 32]>,
302 ) -> Result<()> {
303 require!(
310 claim_prefix.as_slice() == CLAIM_COUNT || claim_prefix.as_slice() == CLAIM_STATUS,
311 GumdropError::InvalidProof,
312 );
313
314 let claim_proof = &mut ctx.accounts.claim_proof;
315 let distributor = &ctx.accounts.distributor;
316
317 verify_claim_bump(
318 &claim_proof.to_account_info(),
319 claim_prefix.as_slice(),
320 claim_bump,
321 index,
322 distributor,
323 )?;
324
325 let node = if resource_nonce.is_empty() {
327 solana_program::keccak::hashv(&[
328 &[0x00],
329 &index.to_le_bytes(),
330 &claimant_secret.to_bytes(),
331 &resource.to_bytes(),
332 &amount.to_le_bytes(),
333 ])
334 } else {
335 solana_program::keccak::hashv(&[
336 &[0x00],
337 &index.to_le_bytes(),
338 &claimant_secret.to_bytes(),
339 &resource.to_bytes(),
340 &amount.to_le_bytes(),
341 resource_nonce.as_slice(),
342 ])
343 };
344 require!(
345 merkle_proof::verify(proof, distributor.root, node.0),
346 GumdropError::InvalidProof,
347 );
348
349 verify_temporal(distributor, &ctx.accounts.temporal, claimant_secret)?;
350
351 claim_proof.amount = amount;
352 claim_proof.count = 0;
353 claim_proof.claimant = ctx.accounts.payer.key();
354 claim_proof.resource = resource;
355 claim_proof.resource_nonce = resource_nonce;
356
357 Ok(())
358 }
359
360 pub fn claim(
362 ctx: Context<Claim>,
363 claim_bump: u8,
364 index: u64,
365 amount: u64,
366 claimant_secret: Pubkey,
367 proof: Vec<[u8; 32]>,
368 ) -> Result<()> {
369 let claim_status = &mut ctx.accounts.claim_status;
370 require!(
371 *claim_status.to_account_info().owner == ID,
372 GumdropError::OwnerMismatch
373 );
374 require!(
375 !claim_status.is_claimed && claim_status.claimed_at == 0,
377 GumdropError::DropAlreadyClaimed
378 );
379
380 let distributor = &ctx.accounts.distributor;
381 let mint = ctx.accounts.from.mint;
382
383 verify_claim_bump(
384 &claim_status.to_account_info(),
385 CLAIM_STATUS,
386 claim_bump,
387 index,
388 distributor,
389 )?;
390
391 let node = solana_program::keccak::hashv(&[
393 &[0x00],
394 &index.to_le_bytes(),
395 &claimant_secret.to_bytes(),
396 &mint.to_bytes(),
397 &amount.to_le_bytes(),
398 ]);
399 require!(
400 merkle_proof::verify(proof, distributor.root, node.0),
401 GumdropError::InvalidProof
402 );
403
404 claim_status.amount = amount;
406 claim_status.is_claimed = true;
407 let clock = Clock::get()?;
408 claim_status.claimed_at = clock.unix_timestamp;
409 claim_status.claimant = ctx.accounts.payer.key();
410
411 let seeds = [
412 b"MerkleDistributor".as_ref(),
413 &distributor.base.to_bytes(),
414 &[ctx.accounts.distributor.bump],
415 ];
416
417 verify_temporal(distributor, &ctx.accounts.temporal, claimant_secret)?;
418 token::transfer(
419 CpiContext::new(
420 ctx.accounts.token_program.to_account_info(),
421 token::Transfer {
422 from: ctx.accounts.from.to_account_info(),
423 to: ctx.accounts.to.to_account_info(),
424 authority: ctx.accounts.distributor.to_account_info(),
425 },
426 )
427 .with_signer(&[&seeds[..]]),
428 amount,
429 )?;
430
431 emit!(ClaimedEvent {
432 index,
433 claimant: ctx.accounts.payer.key(),
434 amount
435 });
436 Ok(())
437 }
438
439 pub fn claim_candy<'info>(
441 ctx: Context<'_, '_, '_, 'info, ClaimCandy<'info>>,
442 wallet_bump: u8,
443 claim_bump: u8,
444 index: u64,
445 amount: u64,
446 claimant_secret: Pubkey,
447 proof: Vec<[u8; 32]>,
448 ) -> Result<()> {
449 let distributor = &ctx.accounts.distributor;
450 let mut claim_count = get_or_create_claim_count(
451 &ctx.accounts.distributor,
452 &ctx.accounts.claim_count,
453 &ctx.accounts.temporal,
454 &ctx.accounts.payer,
455 &ctx.accounts.system_program,
456 claim_bump,
457 index,
458 claimant_secret,
459 )?;
460 require!(
461 *claim_count.to_account_info().owner == ID,
462 GumdropError::OwnerMismatch
463 );
464
465 let node = solana_program::keccak::hashv(&[
469 &[0x00],
470 &index.to_le_bytes(),
471 &claimant_secret.to_bytes(),
472 &ctx.accounts.candy_machine_config.key.to_bytes(),
473 &amount.to_le_bytes(),
474 ]);
475 require!(
476 merkle_proof::verify(proof, distributor.root, node.0),
477 GumdropError::InvalidProof
478 );
479
480 require!(claim_count.count < amount, GumdropError::DropAlreadyClaimed);
482
483 claim_count.count = claim_count
485 .count
486 .checked_add(1)
487 .ok_or(GumdropError::NumericalOverflow)?;
488
489 issue_mint_nft(
490 &distributor,
491 &ctx.accounts.distributor_wallet,
492 &ctx.accounts.payer,
493 &ctx.accounts.candy_machine_config,
494 &ctx.accounts.candy_machine,
495 &ctx.accounts.candy_machine_wallet,
496 &ctx.accounts.candy_machine_mint,
497 &ctx.accounts.candy_machine_metadata,
498 &ctx.accounts.candy_machine_master_edition,
499 &ctx.accounts.system_program,
500 &ctx.accounts.token_program,
501 &ctx.accounts.token_metadata_program,
502 &ctx.accounts.candy_machine_program,
503 &ctx.accounts.rent,
504 &ctx.accounts.clock,
505 &ctx.remaining_accounts,
506 wallet_bump,
507 )?;
508
509 {
511 let mut claim_count_data: &mut [u8] =
512 &mut ctx.accounts.claim_count.try_borrow_mut_data()?;
513 claim_count.try_serialize(&mut claim_count_data)?;
514 }
515
516 Ok(())
517 }
518
519 pub fn claim_edition(
521 ctx: Context<ClaimEdition>,
522 claim_bump: u8,
523 index: u64,
524 amount: u64,
525 edition: u64,
526 claimant_secret: Pubkey,
527 proof: Vec<[u8; 32]>,
528 ) -> Result<()> {
529 let distributor = &ctx.accounts.distributor;
530 let mut claim_count = get_or_create_claim_count(
531 &ctx.accounts.distributor,
532 &ctx.accounts.claim_count,
533 &ctx.accounts.temporal,
534 &ctx.accounts.payer,
535 &ctx.accounts.system_program,
536 claim_bump,
537 index,
538 claimant_secret,
539 )?;
540 require!(
541 *claim_count.to_account_info().owner == ID,
542 GumdropError::OwnerMismatch
543 );
544
545 let node = solana_program::keccak::hashv(&[
547 &[0x00],
548 &index.to_le_bytes(),
549 &claimant_secret.to_bytes(),
550 &ctx.accounts.metadata_master_mint.key.to_bytes(),
551 &amount.to_le_bytes(),
552 &edition.to_le_bytes(),
553 ]);
554 require!(
555 merkle_proof::verify(proof, distributor.root, node.0),
556 GumdropError::InvalidProof
557 );
558
559 require!(claim_count.count < amount, GumdropError::DropAlreadyClaimed);
561
562 claim_count.count = claim_count
564 .count
565 .checked_add(1)
566 .ok_or(GumdropError::NumericalOverflow)?;
567
568 let seeds = [
569 b"MerkleDistributor".as_ref(),
570 &distributor.base.to_bytes(),
571 &[ctx.accounts.distributor.bump],
572 ];
573
574 let metadata_infos = [
575 ctx.accounts.token_metadata_program.clone(),
576 ctx.accounts.metadata_new_metadata.clone(),
577 ctx.accounts.metadata_new_edition.clone(),
578 ctx.accounts.metadata_master_edition.clone(),
579 ctx.accounts.metadata_new_mint.clone(),
580 ctx.accounts.metadata_edition_mark_pda.clone(),
581 ctx.accounts
582 .metadata_new_mint_authority
583 .to_account_info()
584 .clone(),
585 ctx.accounts.payer.to_account_info().clone(),
586 ctx.accounts.distributor.to_account_info().clone(),
587 ctx.accounts.metadata_master_token_account.clone(),
588 ctx.accounts.metadata_new_update_authority.clone(),
589 ctx.accounts.metadata_master_metadata.clone(),
590 ctx.accounts.metadata_master_mint.clone(),
591 ctx.accounts.rent.to_account_info().clone(),
592 ];
593
594 invoke_signed(
595 &mpl_token_metadata::instruction::mint_new_edition_from_master_edition_via_token(
596 *ctx.accounts.token_metadata_program.key,
597 *ctx.accounts.metadata_new_metadata.key,
598 *ctx.accounts.metadata_new_edition.key,
599 *ctx.accounts.metadata_master_edition.key,
600 *ctx.accounts.metadata_new_mint.key,
601 *ctx.accounts.metadata_new_mint_authority.key,
602 *ctx.accounts.payer.key,
603 ctx.accounts.distributor.key(),
604 *ctx.accounts.metadata_master_token_account.key,
605 *ctx.accounts.metadata_new_update_authority.key,
606 *ctx.accounts.metadata_master_metadata.key,
607 *ctx.accounts.metadata_master_mint.key,
608 edition,
609 ),
610 &metadata_infos,
611 &[&seeds],
612 )?;
613
614 {
616 let mut claim_count_data: &mut [u8] =
617 &mut ctx.accounts.claim_count.try_borrow_mut_data()?;
618 claim_count.try_serialize(&mut claim_count_data)?;
619 }
620
621 Ok(())
622 }
623
624 pub fn claim_candy_proven<'info>(
625 ctx: Context<'_, '_, '_, 'info, ClaimCandyProven<'info>>,
626 wallet_bump: u8,
627 _claim_bump: u8, _index: u64,
629 ) -> Result<()> {
630 let claim_proof = &mut ctx.accounts.claim_proof;
631 let distributor = &ctx.accounts.distributor;
632
633 require!(
634 claim_proof.claimant == ctx.accounts.payer.key(),
635 GumdropError::InvalidProof,
636 );
637
638 require!(
639 claim_proof.resource == *ctx.accounts.candy_machine_config.key,
640 GumdropError::InvalidProof,
641 );
642
643 require!(
645 claim_proof.count < claim_proof.amount,
646 GumdropError::DropAlreadyClaimed,
647 );
648
649 claim_proof.count = claim_proof
651 .count
652 .checked_add(1)
653 .ok_or(GumdropError::NumericalOverflow)?;
654
655 issue_mint_nft(
656 &distributor,
657 &ctx.accounts.distributor_wallet,
658 &ctx.accounts.payer,
659 &ctx.accounts.candy_machine_config,
660 &ctx.accounts.candy_machine,
661 &ctx.accounts.candy_machine_wallet,
662 &ctx.accounts.candy_machine_mint,
663 &ctx.accounts.candy_machine_metadata,
664 &ctx.accounts.candy_machine_master_edition,
665 &ctx.accounts.system_program,
666 &ctx.accounts.token_program,
667 &ctx.accounts.token_metadata_program,
668 &ctx.accounts.candy_machine_program,
669 &ctx.accounts.rent,
670 &ctx.accounts.clock,
671 &ctx.remaining_accounts,
672 wallet_bump,
673 )?;
674
675 Ok(())
676 }
677
678 pub fn recover_update_authority(
679 ctx: Context<RecoverUpdateAuthority>,
680 _bump: u8,
681 wallet_bump: u8,
682 ) -> Result<()> {
683 let wallet_seeds = [
684 b"Wallet".as_ref(),
685 &ctx.accounts.distributor.key().to_bytes(),
686 &[wallet_bump],
687 ];
688
689 invoke_signed(
690 &mpl_token_metadata::instruction::update_metadata_accounts(
691 *ctx.accounts.token_metadata_program.key,
692 *ctx.accounts.metadata.key,
693 *ctx.accounts.distributor_wallet.key,
694 Some(*ctx.accounts.new_update_authority.key),
695 None,
696 None,
697 ),
698 &[
699 ctx.accounts.token_metadata_program.to_account_info(),
700 ctx.accounts.metadata.to_account_info(),
701 ctx.accounts.distributor_wallet.to_account_info(),
702 ],
703 &[&wallet_seeds],
704 )?;
705
706 Ok(())
707 }
708}
709
710fn issue_mint_nft<'info>(
711 distributor: &Account<'info, MerkleDistributor>,
712 distributor_wallet: &AccountInfo<'info>,
713 payer: &Signer<'info>,
714 candy_machine_config: &AccountInfo<'info>,
715 candy_machine: &AccountInfo<'info>,
716 candy_machine_wallet: &AccountInfo<'info>,
717 candy_machine_mint: &AccountInfo<'info>,
718 candy_machine_metadata: &AccountInfo<'info>,
719 candy_machine_master_edition: &AccountInfo<'info>,
720 system_program: &Program<'info, System>,
721 token_program: &Program<'info, Token>,
722 token_metadata_program: &AccountInfo<'info>,
723 candy_machine_program: &AccountInfo<'info>,
724 rent: &Sysvar<'info, Rent>,
725 clock: &Sysvar<'info, Clock>,
726 claim_remaining_accounts: &[AccountInfo<'info>],
727 wallet_bump: u8,
728) -> Result<()> {
729 let required_lamports;
731 let remaining_accounts;
732 {
733 let rent = &Rent::get()?;
734 let mut candy_machine_data: &[u8] = &candy_machine.try_borrow_data()?;
735 verify_candy(candy_machine_program.key)?;
736 let candy_machine = CandyMachine::try_deserialize(&mut candy_machine_data)?;
737 let required_rent = rent.minimum_balance(mpl_token_metadata::state::MAX_METADATA_LEN)
738 + rent.minimum_balance(mpl_token_metadata::state::MAX_MASTER_EDITION_LEN);
739
740 if candy_machine.token_mint.is_some() {
741 required_lamports = required_rent;
742
743 let token_account_info = &claim_remaining_accounts[0];
745 let transfer_authority_info = &claim_remaining_accounts[1];
746 remaining_accounts = vec![token_account_info.clone(), transfer_authority_info.clone()];
747 } else {
748 required_lamports = candy_machine.data.price + required_rent;
749 remaining_accounts = vec![];
750 }
751 }
752 msg!(
753 "Transferring {} lamports to distributor wallet for candy machine mint",
754 required_lamports,
755 );
756 invoke(
757 &system_instruction::transfer(payer.key, distributor_wallet.key, required_lamports),
758 &[
759 payer.to_account_info().clone(),
760 distributor_wallet.clone(),
761 system_program.to_account_info().clone(),
762 ],
763 )?;
764
765 let wallet_seeds = [
766 b"Wallet".as_ref(),
767 &distributor.key().to_bytes(),
768 &[wallet_bump],
769 ];
770 let mut account_metas = vec![
771 AccountMeta::new_readonly(candy_machine_config.key(), false),
772 AccountMeta::new(candy_machine.key(), false),
773 AccountMeta::new(distributor_wallet.key(), true),
774 AccountMeta::new(candy_machine_wallet.key(), false),
775 AccountMeta::new(candy_machine_metadata.key(), false),
776 AccountMeta::new(candy_machine_mint.key(), false),
777 AccountMeta::new_readonly(payer.key(), true),
778 AccountMeta::new_readonly(payer.key(), true),
779 AccountMeta::new(candy_machine_master_edition.key(), false),
780 AccountMeta::new_readonly(token_metadata_program.key(), false),
781 AccountMeta::new_readonly(token_program.key(), false),
782 AccountMeta::new_readonly(system_program.key(), false),
783 AccountMeta::new_readonly(sysvar::rent::id(), false),
784 AccountMeta::new_readonly(sysvar::clock::id(), false),
785 ];
786 for a in &remaining_accounts {
787 account_metas.push(AccountMeta::new(a.key(), false));
788 }
789 let mut candy_machine_infos = vec![
790 candy_machine_config.clone(),
791 candy_machine.to_account_info().clone(),
792 distributor_wallet.clone(),
793 candy_machine_wallet.clone(),
794 candy_machine_metadata.clone(),
795 candy_machine_mint.clone(),
796 payer.to_account_info().clone(),
797 candy_machine_master_edition.clone(),
798 token_metadata_program.clone(),
799 token_program.to_account_info().clone(),
800 system_program.to_account_info().clone(),
801 rent.to_account_info().clone(),
802 clock.to_account_info().clone(),
803 ];
804 candy_machine_infos.extend(remaining_accounts);
805
806 invoke_signed(
807 &Instruction {
808 program_id: candy_machine_program.key(),
809 accounts: account_metas,
810 data: vec![0xd3, 0x39, 0x06, 0xa7, 0x0f, 0xdb, 0x23, 0xfb],
812 },
813 &candy_machine_infos,
814 &[&wallet_seeds],
815 )?;
816
817 let mut cm_config_data: &[u8] = &candy_machine_config.try_borrow_data()?;
819 let cm_config = Config::try_deserialize(&mut cm_config_data)?;
820 if cm_config.data.retain_authority {
821 invoke_signed(
822 &mpl_token_metadata::instruction::update_metadata_accounts(
823 *token_metadata_program.key,
824 *candy_machine_metadata.key,
825 *distributor_wallet.key,
826 Some(distributor.base),
827 None,
828 None,
829 ),
830 &[
831 token_metadata_program.to_account_info(),
832 candy_machine_metadata.to_account_info(),
833 distributor_wallet.to_account_info(),
834 ],
835 &[&wallet_seeds],
836 )?;
837 }
838
839 Ok(())
840}
841
842#[derive(Accounts)]
844#[instruction(bump: u8)]
845pub struct NewDistributor<'info> {
846 pub base: Signer<'info>,
848
849 #[account(
851 init,
852 seeds = [
853 b"MerkleDistributor".as_ref(),
854 base.key().to_bytes().as_ref()
855 ],
856 space = 8+97,
857 bump,
858 payer = payer
859 )]
860 pub distributor: Account<'info, MerkleDistributor>,
861
862 #[account(mut)]
864 pub payer: Signer<'info>,
865
866 pub system_program: Program<'info, System>,
868}
869
870#[derive(Accounts)]
872#[instruction(_bump: u8)]
873pub struct CloseDistributorTokenAccount<'info> {
874 pub base: Signer<'info>,
876
877 #[account(
879 seeds = [
880 b"MerkleDistributor".as_ref(),
881 base.key().to_bytes().as_ref()
882 ],
883 bump = _bump,
884 )]
885 pub distributor: Account<'info, MerkleDistributor>,
886
887 #[account(mut)]
889 pub from: Account<'info, TokenAccount>,
890
891 #[account(mut)]
893 pub to: Account<'info, TokenAccount>,
894
895 #[account(mut)]
897 pub receiver: AccountInfo<'info>,
899
900 pub system_program: Program<'info, System>,
902
903 pub token_program: Program<'info, Token>,
905}
906
907#[derive(Accounts)]
909#[instruction(_bump: u8, _wallet_bump: u8)]
910pub struct CloseDistributor<'info> {
911 pub base: Signer<'info>,
913
914 #[account(
916 seeds = [
917 b"MerkleDistributor".as_ref(),
918 base.key().to_bytes().as_ref()
919 ],
920 bump = _bump,
921 mut,
922 close = receiver,
923 )]
924 pub distributor: Account<'info, MerkleDistributor>,
925
926 #[account(
927 seeds = [
928 b"Wallet".as_ref(),
929 distributor.key().to_bytes().as_ref()
930 ],
931 bump = _wallet_bump,
932 mut,
933 )]
934 pub distributor_wallet: AccountInfo<'info>,
936
937 pub receiver: AccountInfo<'info>,
940
941 pub system_program: Program<'info, System>,
943
944 pub token_program: Program<'info, Token>,
946}
947
948#[derive(Accounts)]
950#[instruction(
951claim_prefix: Vec < u8 >,
952claim_bump: u8,
953index: u64,
954_amount: u64,
955_claimant_secret: Pubkey,
956_resource: Pubkey,
957resource_nonce: Vec < u8 >,
958)]
959pub struct ProveClaim<'info> {
960 #[account(mut)]
962 pub distributor: Account<'info, MerkleDistributor>,
963
964 #[account(
966 init,
967 seeds = [
968 claim_prefix.as_slice(),
969 index.to_le_bytes().as_ref(),
970 distributor.key().to_bytes().as_ref()
971 ],
972 bump,
973 payer = payer,
974 space = 8 + 8 + 8 + 32 + 32 + 4 + resource_nonce.len() )]
981 pub claim_proof: Account<'info, ClaimProof>,
982
983 pub temporal: Signer<'info>,
985
986 #[account(mut)]
988 pub payer: Signer<'info>,
989
990 pub system_program: Program<'info, System>,
992}
993
994#[derive(Accounts)]
996#[instruction(claim_bump: u8, index: u64)]
997pub struct Claim<'info> {
998 #[account(mut)]
1000 pub distributor: Account<'info, MerkleDistributor>,
1001
1002 #[account(
1004 init,
1005 seeds = [
1006 CLAIM_STATUS.as_ref(),
1007 index.to_le_bytes().as_ref(),
1008 distributor.key().to_bytes().as_ref()
1009 ],
1010 space = 8+49,
1011 bump,
1012 payer = payer
1013 )]
1014 pub claim_status: Account<'info, ClaimStatus>,
1015
1016 #[account(mut)]
1018 pub from: Account<'info, TokenAccount>,
1019
1020 #[account(mut)]
1022 pub to: Account<'info, TokenAccount>,
1023
1024 pub temporal: Signer<'info>,
1026
1027 #[account(mut)]
1029 pub payer: Signer<'info>,
1030
1031 pub system_program: Program<'info, System>,
1033
1034 pub token_program: Program<'info, Token>,
1036}
1037
1038#[derive(Accounts)]
1040#[instruction(_wallet_bump: u8, claim_bump: u8, index: u64)]
1041pub struct ClaimCandy<'info> {
1042 #[account(mut)]
1044 pub distributor: Account<'info, MerkleDistributor>,
1045
1046 #[account(
1048 seeds = [
1049 b"Wallet".as_ref(),
1050 distributor.key().to_bytes().as_ref()
1051 ],
1052 bump = _wallet_bump,
1053 mut
1054 )]
1055 pub distributor_wallet: AccountInfo<'info>,
1057
1058 #[account(
1060 seeds = [
1061 CLAIM_COUNT.as_ref(),
1062 index.to_le_bytes().as_ref(),
1063 distributor.key().to_bytes().as_ref()
1064 ],
1065 bump = claim_bump,
1066 mut,
1067 )]
1068 pub claim_count: AccountInfo<'info>,
1070
1071 pub temporal: Signer<'info>,
1073
1074 pub payer: Signer<'info>,
1077
1078 pub candy_machine_config: AccountInfo<'info>,
1081
1082 #[account(mut)]
1084 pub candy_machine: AccountInfo<'info>,
1086
1087 #[account(mut)]
1089 pub candy_machine_wallet: AccountInfo<'info>,
1091
1092 #[account(mut)]
1094 pub candy_machine_mint: AccountInfo<'info>,
1096
1097 #[account(mut)]
1099 pub candy_machine_metadata: AccountInfo<'info>,
1101
1102 #[account(mut)]
1104 pub candy_machine_master_edition: AccountInfo<'info>,
1106
1107 pub system_program: Program<'info, System>,
1109
1110 pub token_program: Program<'info, Token>,
1112
1113 #[account(address = mpl_token_metadata::id())]
1115 pub token_metadata_program: AccountInfo<'info>,
1117
1118 pub candy_machine_program: AccountInfo<'info>,
1121
1122 rent: Sysvar<'info, Rent>,
1123 clock: Sysvar<'info, Clock>,
1124}
1125
1126#[derive(Accounts)]
1129#[instruction(claim_bump: u8, index: u64)]
1130pub struct ClaimEdition<'info> {
1131 #[account(mut)]
1139 pub distributor: Account<'info, MerkleDistributor>,
1140
1141 #[account(
1143 seeds = [
1144 CLAIM_COUNT.as_ref(),
1145 index.to_le_bytes().as_ref(),
1146 distributor.key().to_bytes().as_ref()
1147 ],
1148 bump = claim_bump,
1149 mut,
1150 )]
1151 pub claim_count: AccountInfo<'info>,
1153
1154 pub temporal: Signer<'info>,
1156
1157 pub payer: Signer<'info>,
1160
1161 #[account(mut)]
1163 pub metadata_new_metadata: AccountInfo<'info>,
1165
1166 #[account(mut)]
1168 pub metadata_new_edition: AccountInfo<'info>,
1170
1171 #[account(mut)]
1173 pub metadata_master_edition: AccountInfo<'info>,
1175
1176 #[account(mut)]
1178 pub metadata_new_mint: AccountInfo<'info>,
1180
1181 #[account(mut)]
1183 pub metadata_edition_mark_pda: AccountInfo<'info>,
1185
1186 pub metadata_new_mint_authority: Signer<'info>,
1188
1189 pub metadata_master_token_account: AccountInfo<'info>,
1195
1196 pub metadata_new_update_authority: AccountInfo<'info>,
1199
1200 pub metadata_master_metadata: AccountInfo<'info>,
1203
1204 pub metadata_master_mint: AccountInfo<'info>,
1207
1208 pub system_program: Program<'info, System>,
1210
1211 pub token_program: Program<'info, Token>,
1213
1214 #[account(address = mpl_token_metadata::id())]
1216 pub token_metadata_program: AccountInfo<'info>,
1218
1219 rent: Sysvar<'info, Rent>,
1220}
1221
1222#[derive(Accounts)]
1224#[instruction(wallet_bump: u8, claim_bump: u8, index: u64)]
1225pub struct ClaimCandyProven<'info> {
1226 #[account(mut)]
1228 pub distributor: Account<'info, MerkleDistributor>,
1229
1230 #[account(
1232 seeds = [
1233 b"Wallet".as_ref(),
1234 distributor.key().to_bytes().as_ref()
1235 ],
1236 bump = wallet_bump,
1237 mut
1238 )]
1239 pub distributor_wallet: AccountInfo<'info>,
1241
1242 #[account(
1244 seeds = [
1245 CLAIM_COUNT.as_ref(),
1246 index.to_le_bytes().as_ref(),
1247 distributor.key().to_bytes().as_ref()
1248 ],
1249 bump = claim_bump,
1250 mut,
1251 )]
1252 pub claim_proof: Account<'info, ClaimProof>,
1253
1254 pub payer: Signer<'info>,
1257
1258 pub candy_machine_config: AccountInfo<'info>,
1261
1262 #[account(mut)]
1264 pub candy_machine: AccountInfo<'info>,
1266
1267 #[account(mut)]
1269 pub candy_machine_wallet: AccountInfo<'info>,
1271
1272 #[account(mut)]
1274 pub candy_machine_mint: AccountInfo<'info>,
1276
1277 #[account(mut)]
1279 pub candy_machine_metadata: AccountInfo<'info>,
1281
1282 #[account(mut)]
1284 pub candy_machine_master_edition: AccountInfo<'info>,
1286
1287 pub system_program: Program<'info, System>,
1289
1290 pub token_program: Program<'info, Token>,
1292
1293 #[account(address = mpl_token_metadata::id())]
1295 pub token_metadata_program: AccountInfo<'info>,
1297
1298 pub candy_machine_program: AccountInfo<'info>,
1301
1302 rent: Sysvar<'info, Rent>,
1303 clock: Sysvar<'info, Clock>,
1304}
1305
1306#[derive(Accounts)]
1308#[instruction(_bump: u8, wallet_bump: u8)]
1309pub struct RecoverUpdateAuthority<'info> {
1310 pub base: Signer<'info>,
1312
1313 #[account(
1315 seeds = [
1316 b"MerkleDistributor".as_ref(),
1317 base.key().to_bytes().as_ref()
1318 ],
1319 bump = _bump,
1320 )]
1321 pub distributor: Account<'info, MerkleDistributor>,
1322
1323 #[account(
1325 seeds = [
1326 b"Wallet".as_ref(),
1327 distributor.key().to_bytes().as_ref()
1328 ],
1329 bump = wallet_bump,
1330 )]
1331 pub distributor_wallet: AccountInfo<'info>,
1333
1334 pub new_update_authority: AccountInfo<'info>,
1337
1338 #[account(mut)]
1340 pub metadata: AccountInfo<'info>,
1342
1343 pub system_program: Program<'info, System>,
1345
1346 #[account(address = mpl_token_metadata::id())]
1348 pub token_metadata_program: AccountInfo<'info>,
1350}
1351
1352#[account]
1354#[derive(Default)]
1355pub struct MerkleDistributor {
1356 pub base: Pubkey,
1358 pub bump: u8,
1360
1361 pub root: [u8; 32],
1363
1364 pub temporal: Pubkey,
1366}
1367
1368#[account]
1369#[derive(Default)]
1370pub struct ClaimStatus {
1371 pub is_claimed: bool,
1373 pub claimant: Pubkey,
1375 pub claimed_at: i64,
1377 pub amount: u64,
1379}
1380
1381#[account]
1382#[derive(Default)]
1383pub struct ClaimCount {
1384 pub count: u64,
1386 pub claimant: Pubkey,
1388}
1389
1390#[account]
1394#[derive(Default)]
1395pub struct ClaimProof {
1396 pub amount: u64,
1398 pub count: u64,
1400 pub claimant: Pubkey,
1402 pub resource: Pubkey,
1404 pub resource_nonce: Vec<u8>,
1405}
1406
1407#[event]
1409pub struct ClaimedEvent {
1410 pub index: u64,
1412 pub claimant: Pubkey,
1414 pub amount: u64,
1416}
1417
1418#[error_code]
1419pub enum GumdropError {
1420 #[msg("Invalid Merkle proof.")]
1421 InvalidProof,
1422 #[msg("Drop already claimed.")]
1423 DropAlreadyClaimed,
1424 #[msg("Account is not authorized to execute this instruction")]
1425 Unauthorized,
1426 #[msg("Token account owner did not match intended owner")]
1427 OwnerMismatch,
1428 #[msg("Temporal signer did not match distributor")]
1429 TemporalMismatch,
1430 #[msg("Numerical Overflow")]
1431 NumericalOverflow,
1432 #[msg("Invalid Claim Bump")]
1433 InvalidClaimBump,
1434 #[msg("Gumdrop only supports the official Metaplex Candy machine contracts")]
1435 MustUseOfficialCandyMachine,
1436 #[msg("Bump seed not in hash map")]
1437 BumpSeedNotInHashMap,
1438}
1439
1440#[account]
1441#[derive(Default)]
1442pub struct CandyMachine {
1443 pub authority: Pubkey,
1444 pub wallet: Pubkey,
1445 pub token_mint: Option<Pubkey>,
1446 pub config: Pubkey,
1447 pub data: CandyMachineData,
1448 pub items_redeemed: u64,
1449 pub bump: u8,
1450}
1451
1452#[account]
1453#[derive(Default)]
1454pub struct Config {
1455 pub authority: Pubkey,
1456 pub data: ConfigData,
1457}
1458
1459#[derive(AnchorSerialize, AnchorDeserialize, Clone, Default)]
1460pub struct ConfigData {
1461 pub uuid: String,
1462 pub symbol: String,
1464 pub seller_fee_basis_points: u16,
1466 pub creators: Vec<Creator>,
1467 pub max_supply: u64,
1468 pub is_mutable: bool,
1469 pub retain_authority: bool,
1470 pub max_number_of_lines: u32,
1471}
1472
1473#[derive(AnchorSerialize, AnchorDeserialize, Clone)]
1474pub struct Creator {
1475 pub address: Pubkey,
1476 pub verified: bool,
1477 pub share: u8,
1479}
1480
1481#[derive(AnchorSerialize, AnchorDeserialize, Clone, Default)]
1482pub struct CandyMachineData {
1483 pub uuid: String,
1484 pub price: u64,
1485 pub items_available: u64,
1486 pub go_live_date: Option<i64>,
1487}