Skip to main content

shadow_nft_standard/instructions/
create_collection.rs

1//! This module contains all code for the `create_group` instruction,
2//! which initializes a creator group account.
3use anchor_lang::prelude::*;
4
5use crate::{
6    common::{
7        collection::{Collection, Symbol, MAX_SYMBOL_BYTE_LENGTH},
8        creator_group::{CreatorGroup, MAX_GROUP_SIZE},
9    },
10    error::ErrorCode,
11    verbose_msg,
12};
13
14/// This handler must verify
15/// 1) multisig signatures,
16/// 2) symbol size
17/// 3) royalty entries
18///
19/// before initializing the collection value.
20pub fn handle_create_collection(
21    ctx: Context<CreateCollection>,
22    args: CreateCollectionArgs,
23) -> Result<()> {
24    if !ctx.accounts.creator_group.initialized() {
25        return Err(ErrorCode::UnintializedGroup.into());
26    }
27
28    // Initialize values only on first signature
29    verbose_msg!(
30        "Signatures present {}",
31        ctx.accounts.collection.sigs.count_ones()
32    );
33    if ctx.accounts.collection.sigs.count_ones() == 0 {
34        // Check symbol length before saving
35        let symbol_byte_len = args.symbol.as_bytes().len();
36        if symbol_byte_len > MAX_SYMBOL_BYTE_LENGTH {
37            return Err(ErrorCode::SymbolToolarge.into());
38        }
39        ctx.accounts.collection.symbol[..symbol_byte_len].copy_from_slice(args.symbol.as_bytes());
40
41        // Check bps length
42        let num_creators = ctx.accounts.creator_group.creators.len();
43        if num_creators != args.royalty_50bps.len() {
44            verbose_msg!("number of royalty bps entries != number of creators in group");
45            return Err(ErrorCode::IncorrectCreatorGroupSize.into());
46        }
47        // Check bps values
48        let total_50bps = args.royalty_50bps.iter().fold(0_u8, |a, b| {
49            a.checked_add(*b)
50                .expect("Invalid royalties. Must add up to a max of 100%")
51        });
52        if total_50bps > 200 {
53            return Err(ErrorCode::InvalidRoyalties.into());
54        }
55        ctx.accounts.collection.royalty_50bps[..num_creators].copy_from_slice(&args.royalty_50bps);
56
57        // Save name
58        ctx.accounts.collection.name = args.name;
59
60        // Save mint method
61        ctx.accounts.collection.for_minter = args.for_minter;
62
63        // Store group key
64        ctx.accounts.collection.creator_group_key = ctx.accounts.creator_group.key();
65
66        // Initialize collection size at zero
67        ctx.accounts.collection.size = 0;
68    }
69
70    // Update signatures (This is idempotent)
71    ctx.accounts.collection.sigs |= 1
72        << ctx
73            .accounts
74            .creator_group
75            .creators
76            .iter()
77            .position(|c| c == ctx.accounts.payer_creator.key)
78            .unwrap();
79
80    if ctx
81        .accounts
82        .collection
83        .initialized(&ctx.accounts.creator_group.creators)
84    {
85        // This should take 233 billion years to overflow assuming a collection is created
86        // by this group every 400ms
87        ctx.accounts.creator_group.num_collections += 1;
88
89        // Truncate name when printing in case it is stupidly large
90        verbose_msg!(
91            "Created collection {}: {}..",
92            &args.symbol,
93            &ctx.accounts.collection.name[..32.min(ctx.accounts.collection.name.len())]
94        );
95    } else {
96        verbose_msg!(
97            "Creating collection, {}/{} members have signed",
98            &ctx.accounts.collection.sigs.count_ones(),
99            &ctx.accounts.creator_group.creators.len(),
100        );
101    }
102
103    Ok(())
104}
105
106#[derive(Accounts)]
107#[instruction(
108    args: CreateCollectionArgs
109)]
110pub struct CreateCollection<'info> {
111    /// The collection account to be initialized
112    #[account(
113        init_if_needed,
114        payer = payer_creator,
115        space = {
116            const DISCRIMINATOR: usize = 8;
117            let group_key = ::core::mem::size_of::<Pubkey>();
118            let size = ::core::mem::size_of::<u32>();
119            let verified = ::core::mem::size_of::<bool>();
120            let symbol = ::core::mem::size_of::<Symbol>();
121            let bps = ::core::mem::size_of::<[u8; MAX_GROUP_SIZE]>();
122            let name = 4 + args.name.as_bytes().len();
123            let for_minter = ::core::mem::size_of::<bool>();
124
125            DISCRIMINATOR + group_key + size + verified + symbol + name + bps + for_minter
126
127        },
128        seeds = [
129            creator_group.key().as_ref(),
130            args.name.as_ref(),
131        ],
132        bump
133    )]
134    pub collection: Account<'info, Collection>,
135
136    /// The creator group
137    pub creator_group: Account<'info, CreatorGroup>,
138
139    /// A creator within the creator group which is paying to initalize the collection
140    #[account(
141        mut,
142        constraint = creator_group.creators.contains(payer_creator.key)
143    )]
144    pub payer_creator: Signer<'info>,
145
146    pub system_program: Program<'info, System>,
147}
148
149#[derive(AnchorSerialize, AnchorDeserialize, PartialEq, Debug, Clone)]
150pub struct CreateCollectionArgs {
151    /// The name of the collection
152    pub name: String,
153
154    /// The symbol for the collection
155    pub symbol: String,
156
157    /// Whether the collection will be used with a minter or 1/1s
158    pub for_minter: bool,
159
160    /// The royalties for the collection in half-percentages. This must be in
161    /// the same order as the creator keys are stored (they are sorted by `Pubkey`).
162    ///
163    /// Half-percentage are used to maximize the utility of a single `u8`.
164    pub royalty_50bps: Vec<u8>,
165}