QI ZHANG
    • Create new note
    • Create a note from template
      • Sharing URL Link copied
      • /edit
      • View mode
        • Edit mode
        • View mode
        • Book mode
        • Slide mode
        Edit mode View mode Book mode Slide mode
      • Customize slides
      • Note Permission
      • Read
        • Only me
        • Signed-in users
        • Everyone
        Only me Signed-in users Everyone
      • Write
        • Only me
        • Signed-in users
        • Everyone
        Only me Signed-in users Everyone
      • Engagement control Commenting, Suggest edit, Emoji Reply
    • Invite by email
      Invitee

      This note has no invitees

    • Publish Note

      Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note

      Your note will be visible on your profile and discoverable by anyone.
      Your note is now live.
      This note is visible on your profile and discoverable online.
      Everyone on the web can find and read all notes of this public team.
      See published notes
      Unpublish note
      Please check the box to agree to the Community Guidelines.
      View profile
    • Commenting
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
      • Everyone
    • Suggest edit
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
    • Emoji Reply
    • Enable
    • Versions and GitHub Sync
    • Note settings
    • Note Insights New
    • Engagement control
    • Make a copy
    • Transfer ownership
    • Delete this note
    • Save as template
    • Insert from template
    • Import from
      • Dropbox
      • Google Drive
      • Gist
      • Clipboard
    • Export to
      • Dropbox
      • Google Drive
      • Gist
    • Download
      • Markdown
      • HTML
      • Raw HTML
Menu Note settings Note Insights Versions and GitHub Sync Sharing URL Create Help
Create Create new note Create a note from template
Menu
Options
Engagement control Make a copy Transfer ownership Delete this note
Import from
Dropbox Google Drive Gist Clipboard
Export to
Dropbox Google Drive Gist
Download
Markdown HTML Raw HTML
Back
Sharing URL Link copied
/edit
View mode
  • Edit mode
  • View mode
  • Book mode
  • Slide mode
Edit mode View mode Book mode Slide mode
Customize slides
Note Permission
Read
Only me
  • Only me
  • Signed-in users
  • Everyone
Only me Signed-in users Everyone
Write
Only me
  • Only me
  • Signed-in users
  • Everyone
Only me Signed-in users Everyone
Engagement control Commenting, Suggest edit, Emoji Reply
  • Invite by email
    Invitee

    This note has no invitees

  • Publish Note

    Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note

    Your note will be visible on your profile and discoverable by anyone.
    Your note is now live.
    This note is visible on your profile and discoverable online.
    Everyone on the web can find and read all notes of this public team.
    See published notes
    Unpublish note
    Please check the box to agree to the Community Guidelines.
    View profile
    Engagement control
    Commenting
    Permission
    Disabled Forbidden Owners Signed-in users Everyone
    Enable
    Permission
    • Forbidden
    • Owners
    • Signed-in users
    • Everyone
    Suggest edit
    Permission
    Disabled Forbidden Owners Signed-in users Everyone
    Enable
    Permission
    • Forbidden
    • Owners
    • Signed-in users
    Emoji Reply
    Enable
    Import from Dropbox Google Drive Gist Clipboard
       Owned this note    Owned this note      
    Published Linked with GitHub
    • Any changes
      Be notified of any changes
    • Mention me
      Be notified of mention me
    • Unsubscribe
    # Burnt summary ## Metaplex - How it work(High-level) - Any importanat structure - How many instructions here? - How each instruction work? Does any check miss? ## Auction In big picture, this contract can create auction(auction, auction-extended) change authority of auction, then start auction, also end auction eariler. User can place bid(pot, pot-token, metadata), winner can claim bid, rest of users can cancel bid to get refund ### Programming Layout The instructions are inside `/processor` For each instruction, `parse_accounts` will do part of the privilege check, for example `owner`, `signer` ### Structure - auction - auction extended data - pot ### Instruction #### 1. `CreateAuction(CreateAuctionArgs)` Create a new auction account bound to a resource, initially in a pending state **Account Info:** 0. `[signer]` The account creating the auction, which is authorised to make changes. 1. `[writable]` Uninitialized auction account. 2. `[]` Rent sysvar 3. `[]` System account [Question]: /rust/auction/program/src/instruction.rs ```rust= /// Create a new auction account bound to a resource, initially in a pending state. /// 0. `[signer]` The account creating the auction, which is authorised to make changes. /// 1. `[writable]` Uninitialized auction account. /// 2. `[]` Rent sysvar /// 3. `[]` System account ``` Should be 5 accounts, missing `auction_extended` --- **Create Instruction(test purpose):** <details> ```rust= pub fn create_auction_instruction( program_id: Pubkey, creator_pubkey: Pubkey, args: CreateAuctionArgs, ) -> Instruction { let seeds = &[ PREFIX.as_bytes(), &program_id.as_ref(), args.resource.as_ref(), ]; let (auction_pubkey, _) = Pubkey::find_program_address(seeds, &program_id); let seeds = &[ PREFIX.as_bytes(), program_id.as_ref(), args.resource.as_ref(), EXTENDED.as_bytes(), ]; let (auction_extended_pubkey, _) = Pubkey::find_program_address(seeds, &program_id); Instruction { program_id, accounts: vec![ AccountMeta::new(creator_pubkey, true), AccountMeta::new(auction_pubkey, false), AccountMeta::new(auction_extended_pubkey, false), AccountMeta::new_readonly(sysvar::rent::id(), false), AccountMeta::new_readonly(solana_program::system_program::id(), false), ], data: AuctionInstruction::CreateAuction(args) .try_to_vec() .unwrap(), } } ``` </details> --- **Process:** <details> ```rust= pub fn create_auction( program_id: &Pubkey, accounts: &[AccountInfo], args: CreateAuctionArgs, ) -> ProgramResult { msg!("+ Processing CreateAuction"); let accounts = parse_accounts(program_id, accounts)?; let auction_path = [ PREFIX.as_bytes(), program_id.as_ref(), &args.resource.to_bytes(), ]; // Derive the address we'll store the auction in, and confirm it matches what we expected the // user to provide. let (auction_key, bump) = Pubkey::find_program_address(&auction_path, program_id); // seeds, program_id if auction_key != *accounts.auction.key { return Err(AuctionError::InvalidAuctionAccount.into()); } // The data must be large enough to hold at least the number of winners. let auction_size = match args.winners { WinnerLimit::Capped(n) => { mem::size_of::<Bid>() * BidState::max_array_size_for(n) + BASE_AUCTION_DATA_SIZE } WinnerLimit::Unlimited(_) => BASE_AUCTION_DATA_SIZE, }; let bid_state = match args.winners { WinnerLimit::Capped(n) => BidState::new_english(n), WinnerLimit::Unlimited(_) => BidState::new_open_edition(), WinnerLimit::Capped(n) => BidState::new_dutch(n), // [Questuion] will never triggered? }; if let Some(gap_tick) = args.gap_tick_size_percentage { if gap_tick > 100 { return Err(AuctionError::InvalidGapTickSizePercentage.into()); } } // Create auction account with enough space for a winner tracking. create_or_allocate_account_raw( *program_id, accounts.auction, accounts.rent, accounts.system, accounts.payer, auction_size, &[ PREFIX.as_bytes(), program_id.as_ref(), &args.resource.to_bytes(), &[bump], ], )?; let auction_ext_bump = assert_derivation( program_id, accounts.auction_extended, &[ PREFIX.as_bytes(), program_id.as_ref(), &args.resource.to_bytes(), EXTENDED.as_bytes(), ], )?; create_or_allocate_account_raw( *program_id, accounts.auction_extended, accounts.rent, accounts.system, accounts.payer, MAX_AUCTION_DATA_EXTENDED_SIZE, &[ PREFIX.as_bytes(), program_id.as_ref(), &args.resource.to_bytes(), EXTENDED.as_bytes(), &[auction_ext_bump], ], )?; // Configure extended AuctionDataExtended { total_uncancelled_bids: 0, tick_size: args.tick_size, gap_tick_size_percentage: args.gap_tick_size_percentage, price_ceiling : args.price_ceiling, decrease_rate : args.decrease_rate, decrease_interval : args.decrease_interval } .serialize(&mut *accounts.auction_extended.data.borrow_mut())?; // Configure Auction. AuctionData { authority: args.authority, bid_state: bid_state, end_auction_at: args.end_auction_at, end_auction_gap: args.end_auction_gap, ended_at: None, last_bid: None, price_floor: args.price_floor, state: AuctionState::create(), token_mint: args.token_mint, } .serialize(&mut *accounts.auction.data.borrow_mut())?; Ok(()) } ``` </details> [Question]: /rust/auction/program/src/processor/create_auction.rs ```rust=107 let bid_state = match args.winners { WinnerLimit::Capped(n) => BidState::new_english(n), WinnerLimit::Unlimited(_) => BidState::new_open_edition(), WinnerLimit::Capped(n) => BidState::new_dutch(n), // [Questuion] will never triggered? }; ``` `new_dutch` will never be called? --- #### 2. `SetAuthority` Update the authority for an auction account **Account Info:** 1. auction account 2. authority 3. new authority [Question] No Comment about account in this part. **Create Instruction(test purpose):** <details> ```rust= /// Creates an SetAuthority instruction. pub fn set_authority_instruction( program_id: Pubkey, resource: Pubkey, authority: Pubkey, new_authority: Pubkey, ) -> Instruction { let seeds = &[PREFIX.as_bytes(), &program_id.as_ref(), resource.as_ref()]; let (auction_pubkey, _) = Pubkey::find_program_address(seeds, &program_id); Instruction { program_id, accounts: vec![ AccountMeta::new(auction_pubkey, false), AccountMeta::new_readonly(authority, true), AccountMeta::new_readonly(new_authority, false), ], data: AuctionInstruction::SetAuthority.try_to_vec().unwrap(), } } ``` </details> **Process:** ```rust= pub fn set_authority(program_id: &Pubkey, accounts: &[AccountInfo]) -> ProgramResult { msg!("+ Processing SetAuthority"); let account_iter = &mut accounts.iter(); let auction_act = next_account_info(account_iter)?; let current_authority = next_account_info(account_iter)?; let new_authority = next_account_info(account_iter)?; let mut auction = AuctionData::from_account_info(auction_act)?; assert_owned_by(auction_act, program_id)?; if auction.authority != *current_authority.key { return Err(AuctionError::InvalidAuthority.into()); } if !current_authority.is_signer { return Err(AuctionError::InvalidAuthority.into()); } // Make sure new authority actually exists in some form. if new_authority.data_is_empty() || new_authority.lamports() == 0 { msg!("Disallowing new authority because it does not exist."); return Err(AuctionError::InvalidAuthority.into()); } auction.authority = *new_authority.key; auction.serialize(&mut *auction_act.data.borrow_mut())?; Ok(()) } ``` [Question] Should new authority also sign? #### 3.`StartAuction(StartAuctionArgs)` Start an inactive auction **Account Info:** 0. `[signer]` The creator/authorised account. 1. `[writable]` Initialized auction account. 2. `[]` Clock sysvar **Create Instruction(test purpose):** <details> ```rust= /// Creates an StartAuction instruction. pub fn start_auction_instruction( program_id: Pubkey, authority_pubkey: Pubkey, args: StartAuctionArgs, ) -> Instruction { // Derive Auction Key let seeds = &[ PREFIX.as_bytes(), &program_id.as_ref(), args.resource.as_ref(), ]; let (auction_pubkey, _) = Pubkey::find_program_address(seeds, &program_id); Instruction { program_id, accounts: vec![ AccountMeta::new(authority_pubkey, true), AccountMeta::new(auction_pubkey, false), AccountMeta::new_readonly(sysvar::clock::id(), false), ], data: AuctionInstruction::StartAuction(args).try_to_vec().unwrap(), } } ``` </details> **Process:** <details> ```rust= pub fn start_auction<'a, 'b: 'a>( program_id: &Pubkey, accounts: &'a [AccountInfo<'b>], args: StartAuctionArgs, ) -> ProgramResult { msg!("+ Processing StartAuction"); let accounts = parse_accounts(program_id, accounts)?; let clock = Clock::from_account_info(accounts.clock_sysvar)?; // Derive auction address so we can make the modifications necessary to start it. assert_derivation( program_id, accounts.auction, &[ PREFIX.as_bytes(), program_id.as_ref(), &args.resource.as_ref(), ], )?; // Initialise a new auction. The end time is calculated relative to now. let mut auction = AuctionData::from_account_info(accounts.auction)?; // Check authority is correct. if auction.authority != *accounts.authority.key { return Err(AuctionError::InvalidAuthority.into()); } // Calculate the relative end time. let ended_at = if let Some(end_auction_at) = auction.end_auction_at { match clock.unix_timestamp.checked_add(end_auction_at) { Some(val) => Some(val), None => return Err(AuctionError::NumericalOverflowError.into()), } } else { None }; AuctionData { ended_at, state: auction.state.start()?, ..auction } .serialize(&mut *accounts.auction.data.borrow_mut())?; Ok(()) } ``` </details> [Question] ~~rust/auction/program/src/processor/start_auction.rs~~ ~~No signer check for authority?~~ #### 4.`PlaceBid(PlaceBidArgs)` Place a bid on a running auction **Account Info:** 0. `[signer]` The bidders primary account, for PDA calculation/transit auth. 1. `[writable]` The bidders token account they'll pay with 2. `[writable]` The pot, containing a reference to the stored SPL token account. 3. `[writable]` The pot SPL account, where the tokens will be deposited. 4. `[writable]` The metadata account, storing information about the bidders actions. 5. `[writable]` Auction account, containing data about the auction and item being bid on. 6. `[writable]` Token mint, for transfer instructions and verification. 7. `[signer]` Transfer authority, for moving tokens into the bid pot. 8. `[signer]` Payer 9. `[]` Clock sysvar 10. `[]` Rent sysvar 11. `[]` System program 12. `[]` SPL Token Program **Create Instruction(test purpose):** <details> ```rust= pub fn place_bid_instruction( program_id: Pubkey, bidder_pubkey: Pubkey, bidder_token_pubkey: Pubkey, bidder_pot_token_pubkey: Pubkey, token_mint_pubkey: Pubkey, transfer_authority: Pubkey, payer: Pubkey, args: PlaceBidArgs, ) -> Instruction { // Derive Auction Key let seeds = &[ PREFIX.as_bytes(), program_id.as_ref(), args.resource.as_ref(), ]; let (auction_pubkey, _) = Pubkey::find_program_address(seeds, &program_id); let seeds = &[ PREFIX.as_bytes(), program_id.as_ref(), args.resource.as_ref(), EXTENDED.as_bytes(), ]; let (auction_extended_pubkey, _) = Pubkey::find_program_address(seeds, &program_id); // Derive Bidder Pot let seeds = &[ PREFIX.as_bytes(), &program_id.as_ref(), auction_pubkey.as_ref(), bidder_pubkey.as_ref(), ]; let (bidder_pot_pubkey, _) = Pubkey::find_program_address(seeds, &program_id); // Derive Bidder Meta let seeds = &[ PREFIX.as_bytes(), &program_id.as_ref(), auction_pubkey.as_ref(), bidder_pubkey.as_ref(), "metadata".as_bytes(), ]; let (bidder_meta_pubkey, _) = Pubkey::find_program_address(seeds, &program_id); Instruction { program_id, accounts: vec![ AccountMeta::new(bidder_pubkey, true), AccountMeta::new(bidder_token_pubkey, false), AccountMeta::new(bidder_pot_pubkey, false), AccountMeta::new(bidder_pot_token_pubkey, false), AccountMeta::new(bidder_meta_pubkey, false), AccountMeta::new(auction_pubkey, false), AccountMeta::new(auction_extended_pubkey, false), AccountMeta::new(token_mint_pubkey, false), AccountMeta::new_readonly(transfer_authority, true), AccountMeta::new_readonly(payer, true), AccountMeta::new_readonly(sysvar::clock::id(), false), AccountMeta::new_readonly(sysvar::rent::id(), false), AccountMeta::new_readonly(solana_program::system_program::id(), false), AccountMeta::new_readonly(spl_token::id(), false), ], data: AuctionInstruction::PlaceBid(args).try_to_vec().unwrap(), } } ``` </details> **Process:** <details> ```rust= #[allow(clippy::absurd_extreme_comparisons)] pub fn place_bid<'r, 'b: 'r>( program_id: &Pubkey, accounts: &'r [AccountInfo<'b>], args: PlaceBidArgs, ) -> ProgramResult { msg!("+ Processing PlaceBid"); let accounts = parse_accounts(program_id, accounts)?; // Load the auction and verify this bid is valid. let mut auction = AuctionData::from_account_info(accounts.auction)?; // Load the clock, used for various auction timing. let clock = Clock::from_account_info(accounts.clock_sysvar)?; // Verify auction has not ended. if auction.ended(clock.unix_timestamp)? { auction.state = auction.state.end()?; auction.serialize(&mut *accounts.auction.data.borrow_mut())?; msg!("Auction ended!"); return Ok(()); } // Derive Metadata key and load it. let metadata_bump = assert_derivation( program_id, accounts.bidder_meta, &[ PREFIX.as_bytes(), program_id.as_ref(), accounts.auction.key.as_ref(), accounts.bidder.key.as_ref(), "metadata".as_bytes(), ], )?; // If metadata doesn't exist, create it. if accounts.bidder_meta.owner != program_id { create_or_allocate_account_raw( *program_id, accounts.bidder_meta, accounts.rent, accounts.system, accounts.payer, // For whatever reason, using Mem function here returns 7, which is wholly wrong for this struct // seems to be issues with UnixTimestamp BIDDER_METADATA_LEN, &[ PREFIX.as_bytes(), program_id.as_ref(), accounts.auction.key.as_ref(), accounts.bidder.key.as_ref(), "metadata".as_bytes(), &[metadata_bump], ], )?; } else { // Verify the last bid was cancelled before continuing. let bidder_metadata: BidderMetadata = BidderMetadata::from_account_info(accounts.bidder_meta)?; if bidder_metadata.cancelled == false { return Err(AuctionError::BidAlreadyActive.into()); } }; // Derive Pot address, this account wraps/holds an SPL account to transfer tokens into and is // also used as the authoriser of the SPL pot. let pot_bump = assert_derivation( program_id, accounts.bidder_pot, &[ PREFIX.as_bytes(), program_id.as_ref(), accounts.auction.key.as_ref(), accounts.bidder.key.as_ref(), ], )?; // The account within the pot must be owned by us. let actual_account: Account = assert_initialized(accounts.bidder_pot_token)?; if actual_account.owner != *accounts.auction.key { return Err(AuctionError::BidderPotTokenAccountOwnerMismatch.into()); } if actual_account.delegate != COption::None { return Err(AuctionError::DelegateShouldBeNone.into()); } if actual_account.close_authority != COption::None { return Err(AuctionError::CloseAuthorityShouldBeNone.into()); } // Derive and load Auction. let auction_bump = assert_derivation( program_id, accounts.auction, &[ PREFIX.as_bytes(), program_id.as_ref(), args.resource.as_ref(), ], )?; // Can't bid on an auction that isn't running. if auction.state != AuctionState::Started { return Err(AuctionError::InvalidState.into()); } let bump_authority_seeds = &[ PREFIX.as_bytes(), program_id.as_ref(), accounts.auction.key.as_ref(), accounts.bidder.key.as_ref(), &[pot_bump], ]; // If the bidder pot account is empty, we need to generate one. if accounts.bidder_pot.data_is_empty() { create_or_allocate_account_raw( *program_id, accounts.bidder_pot, accounts.rent, accounts.system, accounts.payer, mem::size_of::<BidderPot>(), bump_authority_seeds, )?; // Attach SPL token address to pot account. let mut pot = BidderPot::from_account_info(accounts.bidder_pot)?; pot.bidder_pot = *accounts.bidder_pot_token.key; pot.bidder_act = *accounts.bidder.key; pot.auction_act = *accounts.auction.key; pot.serialize(&mut *accounts.bidder_pot.data.borrow_mut())?; } else { // Already exists, verify that the pot contains the specified SPL address. let bidder_pot = BidderPot::from_account_info(accounts.bidder_pot)?; if bidder_pot.bidder_pot != *accounts.bidder_pot_token.key { return Err(AuctionError::BidderPotTokenAccountOwnerMismatch.into()); } } // Update now we have new bid. assert_derivation( program_id, accounts.auction_extended, &[ PREFIX.as_bytes(), program_id.as_ref(), args.resource.as_ref(), EXTENDED.as_bytes(), ], )?; let mut auction_extended: AuctionDataExtended = AuctionDataExtended::from_account_info(accounts.auction_extended)?; auction_extended.total_uncancelled_bids = auction_extended .total_uncancelled_bids .checked_add(1) .ok_or(AuctionError::NumericalOverflowError)?; auction_extended.serialize(&mut *accounts.auction_extended.data.borrow_mut())?; // Confirm payers SPL token balance is enough to pay the bid. let account: Account = Account::unpack_from_slice(&accounts.bidder_token.data.borrow())?; if account.amount.saturating_sub(args.amount) < 0 { msg!( "Amount is too small: {:?}, compared to account amount of {:?}", args.amount, account.amount ); return Err(AuctionError::BalanceTooLow.into()); } // Transfer amount of SPL token to bid account. spl_token_transfer(TokenTransferParams { source: accounts.bidder_token.clone(), destination: accounts.bidder_pot_token.clone(), authority: accounts.transfer_authority.clone(), authority_signer_seeds: bump_authority_seeds, token_program: accounts.token_program.clone(), amount: args.amount, })?; // Serialize new Auction State auction.last_bid = Some(clock.unix_timestamp); auction.place_bid( Bid(*accounts.bidder.key, args.amount), auction_extended.tick_size, auction_extended.gap_tick_size_percentage, clock.unix_timestamp, auction_extended.price_ceiling, auction_extended.decrease_rate, auction_extended.decrease_interval )?; auction.serialize(&mut *accounts.auction.data.borrow_mut())?; // Update latest metadata with results from the bid. BidderMetadata { bidder_pubkey: *accounts.bidder.key, auction_pubkey: *accounts.auction.key, last_bid: args.amount, last_bid_timestamp: clock.unix_timestamp, cancelled: false, } .serialize(&mut *accounts.bidder_meta.data.borrow_mut())?; Ok(()) } ``` </details> [Question] 1. Account: `metadata`, check for non-exist account, can only check owner? ```rust= // If metadata doesn't exist, create it. if accounts.bidder_meta.owner != program_id { ``` 2. bid logic, need cancel bid first, then place a new bid ```rust=179 } else { // Verify the last bid was cancelled before continuing. let bidder_metadata: BidderMetadata = BidderMetadata::from_account_info(accounts.bidder_meta)?; if bidder_metadata.cancelled == false { return Err(AuctionError::BidAlreadyActive.into()); } }; ``` 3. How pot work, if `bidder_pot_token`'s owner is auction, how to get money back? ```rust=201 // The account within the pot must be owned by us. let actual_account: Account = assert_initialized(accounts.bidder_pot_token)?; if actual_account.owner != *accounts.auction.key { return Err(AuctionError::BidderPotTokenAccountOwnerMismatch.into()); } ``` `bidder_pot` to record the info(record), `bidder_pot_token` to hold token? metadata account: ```rust= pub struct BidderMetadata { // Relationship with the bidder who's metadata this covers. pub bidder_pubkey: Pubkey, // Relationship with the auction this bid was placed on. pub auction_pubkey: Pubkey, // Amount that the user bid. pub last_bid: u64, // Tracks the last time this user bid. pub last_bid_timestamp: UnixTimestamp, // Whether the last bid the user made was cancelled. This should also be enough to know if the // user is a winner, as if cancelled it implies previous bids were also cancelled. pub cancelled: bool, } ``` Bidder Pot ```rust= pub struct BidderPot { /// Points at actual pot that is a token account pub bidder_pot: Pubkey, /// Originating bidder account pub bidder_act: Pubkey, /// Auction account pub auction_act: Pubkey, /// emptied or not pub emptied: bool, } ``` - [ ] 4. not really the dutch auction? rust/auction/program/src/processor.rs ```rust= //Bid should be greater than the current ceiling price if Some(bid.1) > maximum { BidState::assert_dutch_parameters(maximum, decrease_rate, decrease_interval, minimum)? } ``` #### 5. `CancelBid(CancelBidArgs)` Cancel a bid on a running auction **Account Info:** 0. `[signer]` The bidders primary account, for PDA calculation/transit auth. 1. `[writable]` The bidders token account they'll receive refund with 2. `[writable]` The pot, containing a reference to the stored SPL token account. 3. `[writable]` The pot SPL account, where the tokens will be deposited. 4. `[writable]` The metadata account, storing information about the bidders actions. 5. `[writable]` Auction account, containing data about the auction and item being bid on. 6. `[writable]` Token mint, for transfer instructions and verification. 7. `[]` Clock sysvar 8. `[]` Rent sysvar 9. `[]` System program 10. `[]` SPL Token Program **Create Instruction(test purpose):** <details> ```rust= /// Creates an CancelBidinstruction. pub fn cancel_bid_instruction( program_id: Pubkey, bidder_pubkey: Pubkey, bidder_token_pubkey: Pubkey, bidder_pot_token_pubkey: Pubkey, token_mint_pubkey: Pubkey, args: CancelBidArgs, ) -> Instruction { // Derive Auction Key let seeds = &[ PREFIX.as_bytes(), program_id.as_ref(), args.resource.as_ref(), ]; let (auction_pubkey, _) = Pubkey::find_program_address(seeds, &program_id); let seeds = &[ PREFIX.as_bytes(), program_id.as_ref(), args.resource.as_ref(), EXTENDED.as_bytes(), ]; let (auction_extended_pubkey, _) = Pubkey::find_program_address(seeds, &program_id); // Derive Bidder Pot let seeds = &[ PREFIX.as_bytes(), &program_id.as_ref(), auction_pubkey.as_ref(), bidder_pubkey.as_ref(), ]; let (bidder_pot_pubkey, _) = Pubkey::find_program_address(seeds, &program_id); // Derive Bidder Meta let seeds = &[ PREFIX.as_bytes(), &program_id.as_ref(), auction_pubkey.as_ref(), bidder_pubkey.as_ref(), "metadata".as_bytes(), ]; let (bidder_meta_pubkey, _) = Pubkey::find_program_address(seeds, &program_id); Instruction { program_id, accounts: vec![ AccountMeta::new(bidder_pubkey, true), AccountMeta::new(bidder_token_pubkey, false), AccountMeta::new(bidder_pot_pubkey, false), AccountMeta::new(bidder_pot_token_pubkey, false), AccountMeta::new(bidder_meta_pubkey, false), AccountMeta::new(auction_pubkey, false), AccountMeta::new(auction_extended_pubkey, false), AccountMeta::new(token_mint_pubkey, false), AccountMeta::new_readonly(sysvar::clock::id(), false), AccountMeta::new_readonly(sysvar::rent::id(), false), AccountMeta::new_readonly(solana_program::system_program::id(), false), AccountMeta::new_readonly(spl_token::id(), false), ], data: AuctionInstruction::CancelBid(args).try_to_vec().unwrap(), } } ``` </details> **Process:** <details> ```rust= pub fn cancel_bid( program_id: &Pubkey, accounts: &[AccountInfo], args: CancelBidArgs, ) -> ProgramResult { msg!("+ Processing Cancelbid"); let accounts = parse_accounts(program_id, accounts)?; // The account within the pot must be owned by us. let actual_account: Account = assert_initialized(accounts.bidder_pot_token)?; if actual_account.owner != *accounts.auction.key { return Err(AuctionError::BidderPotTokenAccountOwnerMismatch.into()); } // Derive and load Auction. let auction_bump = assert_derivation( program_id, accounts.auction, &[ PREFIX.as_bytes(), program_id.as_ref(), args.resource.as_ref(), ], )?; let auction_seeds = &[ PREFIX.as_bytes(), program_id.as_ref(), args.resource.as_ref(), &[auction_bump], ]; // Load the auction and verify this bid is valid. let mut auction = AuctionData::from_account_info(accounts.auction)?; // The mint provided in this bid must match the one the auction was initialized with. if auction.token_mint != *accounts.mint.key { return Err(AuctionError::IncorrectMint.into()); } // Load the clock, used for various auction timing. let clock = Clock::from_account_info(accounts.clock_sysvar)?; // Derive Metadata key and load it. let metadata_bump = assert_derivation( program_id, accounts.bidder_meta, &[ PREFIX.as_bytes(), program_id.as_ref(), accounts.auction.key.as_ref(), accounts.bidder.key.as_ref(), "metadata".as_bytes(), ], )?; // If metadata doesn't exist, error, can't cancel a bid that doesn't exist and metadata must // exist if a bid was placed. if accounts.bidder_meta.owner != program_id { return Err(AuctionError::MetadataInvalid.into()); } // Derive Pot address, this account wraps/holds an SPL account to transfer tokens out of. let pot_seeds = [ PREFIX.as_bytes(), program_id.as_ref(), accounts.auction.key.as_ref(), accounts.bidder.key.as_ref(), ]; let pot_bump = assert_derivation(program_id, accounts.bidder_pot, &pot_seeds)?; let bump_authority_seeds = &[ PREFIX.as_bytes(), program_id.as_ref(), accounts.auction.key.as_ref(), accounts.bidder.key.as_ref(), &[pot_bump], ]; // If the bidder pot account is empty, this bid is invalid. if accounts.bidder_pot.data_is_empty() { return Err(AuctionError::BidderPotDoesNotExist.into()); } // Refuse to cancel if the auction ended and this person is a winning account. if auction.ended(clock.unix_timestamp)? && auction.is_winner(accounts.bidder.key).is_some() { return Err(AuctionError::InvalidState.into()); } // Confirm we're looking at the real SPL account for this bidder. let bidder_pot = BidderPot::from_account_info(accounts.bidder_pot)?; if bidder_pot.bidder_pot != *accounts.bidder_pot_token.key { return Err(AuctionError::BidderPotTokenAccountOwnerMismatch.into()); } // Transfer SPL bid balance back to the user. let account: Account = Account::unpack_from_slice(&accounts.bidder_pot_token.data.borrow())?; spl_token_transfer(TokenTransferParams { source: accounts.bidder_pot_token.clone(), destination: accounts.bidder_token.clone(), authority: accounts.auction.clone(), authority_signer_seeds: auction_seeds, token_program: accounts.token_program.clone(), amount: account.amount, })?; // Update Metadata let metadata = BidderMetadata::from_account_info(accounts.bidder_meta)?; let already_cancelled = metadata.cancelled; BidderMetadata { cancelled: true, ..metadata } .serialize(&mut *accounts.bidder_meta.data.borrow_mut())?; // Update Auction if auction.state != AuctionState::Ended { // Once ended we want uncancelled bids to retain it's pre-ending count assert_derivation( program_id, accounts.auction_extended, &[ PREFIX.as_bytes(), program_id.as_ref(), args.resource.as_ref(), EXTENDED.as_bytes(), ], )?; let mut auction_extended = AuctionDataExtended::from_account_info(accounts.auction_extended)?; msg!("Already cancelled is {:?}", already_cancelled); if !already_cancelled && auction_extended.total_uncancelled_bids > 0 { auction_extended.total_uncancelled_bids = auction_extended .total_uncancelled_bids .checked_sub(1) .ok_or(AuctionError::NumericalOverflowError)?; } auction_extended.serialize(&mut *accounts.auction_extended.data.borrow_mut())?; // Only cancel the bid if the auction has not ended yet auction.bid_state.cancel_bid(*accounts.bidder.key); auction.serialize(&mut *accounts.auction.data.borrow_mut())?; } Ok(()) } ``` </details> #### 6. `EndAuction(EndAuctionArgs)` Ends an auction, regardless of end timing conditions **Account Info:** (No info in comment) 1. Authority Key 2. Auction Account 3. Clock sysvar **Create Instruction(test purpose):** <details> ```rust= pub fn end_auction_instruction( program_id: Pubkey, authority_pubkey: Pubkey, args: EndAuctionArgs, ) -> Instruction { // Derive Auction Key let seeds = &[ PREFIX.as_bytes(), &program_id.as_ref(), args.resource.as_ref(), ]; let (auction_pubkey, _) = Pubkey::find_program_address(seeds, &program_id); Instruction { program_id, accounts: vec![ AccountMeta::new(authority_pubkey, true), AccountMeta::new(auction_pubkey, false), AccountMeta::new_readonly(sysvar::clock::id(), false), ], data: AuctionInstruction::EndAuction(args).try_to_vec().unwrap(), } } ``` </details> **Process:** <details> ```rust= use crate::{ errors::AuctionError, processor::{AuctionData, AuctionState, Bid, BidState, PriceFloor, WinnerLimit}, utils::{assert_derivation, assert_owned_by, assert_signer, create_or_allocate_account_raw}, PREFIX, }; use { borsh::{BorshDeserialize, BorshSerialize}, solana_program::{ account_info::{next_account_info, AccountInfo}, clock::Clock, entrypoint::ProgramResult, hash, msg, program_error::ProgramError, pubkey::Pubkey, sysvar::Sysvar, }, std::mem, }; type Price = u64; type Salt = u64; type Revealer = (Price, Salt); #[repr(C)] #[derive(Clone, BorshSerialize, BorshDeserialize, PartialEq)] pub struct EndAuctionArgs { /// The resource being auctioned. See AuctionData. pub resource: Pubkey, /// If the auction was blinded, a revealing price must be specified to release the auction /// winnings. pub reveal: Option<Revealer>, } struct Accounts<'a, 'b: 'a> { authority: &'a AccountInfo<'b>, auction: &'a AccountInfo<'b>, clock_sysvar: &'a AccountInfo<'b>, } fn parse_accounts<'a, 'b: 'a>( program_id: &Pubkey, accounts: &'a [AccountInfo<'b>], ) -> Result<Accounts<'a, 'b>, ProgramError> { let account_iter = &mut accounts.iter(); let accounts = Accounts { authority: next_account_info(account_iter)?, auction: next_account_info(account_iter)?, clock_sysvar: next_account_info(account_iter)?, }; assert_owned_by(accounts.auction, program_id)?; assert_signer(accounts.authority)?; Ok(accounts) } fn reveal(price_floor: PriceFloor, revealer: Option<Revealer>) -> Result<PriceFloor, ProgramError> { // If the price floor was blinded, we update it. if let PriceFloor::BlindedPrice(blinded) = price_floor { // If the hash matches, update the price to the actual minimum. if let Some(reveal) = revealer { let reveal_hash = hash::hashv(&[&reveal.0.to_be_bytes(), &reveal.1.to_be_bytes()]); if reveal_hash != blinded { return Err(AuctionError::InvalidReveal.into()); } Ok(PriceFloor::MinimumPrice([reveal.0, 0, 0, 0])) } else { return Err(AuctionError::MustReveal.into()); } } else { // No change needed in the else case. Ok(price_floor) } } pub fn end_auction<'a, 'b: 'a>( program_id: &Pubkey, accounts: &'a [AccountInfo<'b>], args: EndAuctionArgs, ) -> ProgramResult { msg!("+ Processing EndAuction"); let accounts = parse_accounts(program_id, accounts)?; let clock = Clock::from_account_info(accounts.clock_sysvar)?; assert_derivation( program_id, accounts.auction, &[ PREFIX.as_bytes(), program_id.as_ref(), &args.resource.as_ref(), ], )?; // End auction. let mut auction = AuctionData::from_account_info(accounts.auction)?; // Check authority is correct. if auction.authority != *accounts.authority.key { return Err(AuctionError::InvalidAuthority.into()); } // As long as it hasn't already ended. if auction.ended_at.is_some() { return Err(AuctionError::AuctionTransitionInvalid.into()); } AuctionData { ended_at: Some(clock.unix_timestamp), state: auction.state.end()?, price_floor: reveal(auction.price_floor, args.reveal)?, ..auction } .serialize(&mut *accounts.auction.data.borrow_mut())?; Ok(()) } ``` </details> [Question] 1. Function `reveal` - price floor, not complete understand 2. Alreay end check In Auction Data ```rust= /// Slot time the auction was officially ended by. pub ended_at: Option<UnixTimestamp>, /// End time is the cut-off point that the auction is forced to end by. pub end_auction_at: Option<UnixTimestamp>, ``` But in End auction: corebuild-rust/auction/program/src/processor/end_auction.rs ```rust=103 // As long as it hasn't already ended. if auction.ended_at.is_some() { return Err(AuctionError::AuctionTransitionInvalid.into()); } ``` `end_auction_at` is never checked? or it's a misleading naming here, it seems never used in the auction. #### 7. `ClaimBid(ClaimBidArgs)` Move SPL tokens from winning bid to the destination account **Account Info:** 0. `[writable]` The destination account 1. `[writable]` The bidder pot token account 2. `[]` The bidder pot pda account [seed of ['auction', program_id, auction key, bidder key]] 3. `[signer]` The authority on the auction 4. `[]` The auction 5. `[]` The bidder wallet 6. `[]` Token mint of the auction 7. `[]` Clock sysvar 8. `[]` Token program **Create Instruction(test purpose):** <details> ```rust= pub fn claim_bid_instruction( program_id: Pubkey, destination_pubkey: Pubkey, authority_pubkey: Pubkey, bidder_pubkey: Pubkey, bidder_pot_token_pubkey: Pubkey, token_mint_pubkey: Pubkey, args: ClaimBidArgs, ) -> Instruction { // Derive Auction Key let seeds = &[ PREFIX.as_bytes(), &program_id.as_ref(), args.resource.as_ref(), ]; let (auction_pubkey, _) = Pubkey::find_program_address(seeds, &program_id); // Derive Bidder Pot let seeds = &[ PREFIX.as_bytes(), &program_id.as_ref(), auction_pubkey.as_ref(), bidder_pubkey.as_ref(), ]; let (bidder_pot_pubkey, _) = Pubkey::find_program_address(seeds, &program_id); Instruction { program_id, accounts: vec![ AccountMeta::new(destination_pubkey, false), AccountMeta::new(bidder_pot_token_pubkey, false), AccountMeta::new(bidder_pot_pubkey, false), AccountMeta::new_readonly(authority_pubkey, true), AccountMeta::new_readonly(auction_pubkey, false), AccountMeta::new_readonly(bidder_pubkey, false), AccountMeta::new_readonly(token_mint_pubkey, false), AccountMeta::new_readonly(sysvar::clock::id(), false), AccountMeta::new_readonly(spl_token::id(), false), ], data: AuctionInstruction::ClaimBid(args).try_to_vec().unwrap(), } } ``` </details> **Process:** <details> ```rust= pub fn claim_bid( program_id: &Pubkey, accounts: &[AccountInfo], args: ClaimBidArgs, ) -> ProgramResult { msg!("+ Processing ClaimBid"); let accounts = parse_accounts(program_id, accounts)?; let clock = Clock::from_account_info(accounts.clock_sysvar)?; // The account within the pot must be owned by us. let actual_account: Account = assert_initialized(accounts.bidder_pot_token)?; if actual_account.owner != *accounts.auction.key { return Err(AuctionError::BidderPotTokenAccountOwnerMismatch.into()); } // Derive and load Auction. let auction_bump = assert_derivation( program_id, accounts.auction, &[ PREFIX.as_bytes(), program_id.as_ref(), args.resource.as_ref(), ], )?; let auction_seeds = &[ PREFIX.as_bytes(), program_id.as_ref(), args.resource.as_ref(), &[auction_bump], ]; // Load the auction and verify this bid is valid. let auction = AuctionData::from_account_info(accounts.auction)?; if auction.authority != *accounts.authority.key { return Err(AuctionError::InvalidAuthority.into()); } // User must have won the auction in order to claim their funds. Check early as the rest of the // checks will be for nothing otherwise. if auction.is_winner(accounts.bidder.key).is_none() { msg!("User {:?} is not winner", accounts.bidder.key); return Err(AuctionError::InvalidState.into()); } // Auction must have ended. if !auction.ended(clock.unix_timestamp)? { return Err(AuctionError::InvalidState.into()); } // The mint provided in this claim must match the one the auction was initialized with. if auction.token_mint != *accounts.mint.key { return Err(AuctionError::IncorrectMint.into()); } // Derive Pot address, this account wraps/holds an SPL account to transfer tokens into. let pot_seeds = [ PREFIX.as_bytes(), program_id.as_ref(), accounts.auction.key.as_ref(), accounts.bidder.key.as_ref(), ]; let pot_bump = assert_derivation(program_id, accounts.bidder_pot, &pot_seeds)?; let bump_authority_seeds = &[ PREFIX.as_bytes(), program_id.as_ref(), accounts.auction.key.as_ref(), accounts.bidder.key.as_ref(), &[pot_bump], ]; // If the bidder pot account is empty, this bid is invalid. if accounts.bidder_pot.data_is_empty() { return Err(AuctionError::BidderPotDoesNotExist.into()); } // Confirm we're looking at the real SPL account for this bidder. let mut bidder_pot = BidderPot::from_account_info(accounts.bidder_pot)?; if bidder_pot.bidder_pot != *accounts.bidder_pot_token.key { return Err(AuctionError::BidderPotTokenAccountOwnerMismatch.into()); } // Transfer SPL bid balance back to the user. spl_token_transfer(TokenTransferParams { source: accounts.bidder_pot_token.clone(), destination: accounts.destination.clone(), authority: accounts.auction.clone(), authority_signer_seeds: auction_seeds, token_program: accounts.token_program.clone(), amount: actual_account.amount, })?; bidder_pot.emptied = true; bidder_pot.serialize(&mut *accounts.bidder_pot.data.borrow_mut())?; Ok(()) } ``` </details> [Question] 1. `Winner` logic not complete understand 2. Claim bid(the winer) will get deposit back? Or it's a misleading comment(not back to user but to a destination). ```rust=167 // Transfer SPL bid balance back to the user. spl_token_transfer(TokenTransferParams { source: accounts.bidder_pot_token.clone(), destination: accounts.destination.clone(), authority: accounts.auction.clone(), authority_signer_seeds: auction_seeds, token_program: accounts.token_program.clone(), amount: actual_account.amount, })?; ``` ## Metaplex ### Structure #### 1. `InitAuctionManager(AuctionManagerSettings)` Initializes an Auction Manager **Account Info:** 0. `[writable]` Uninitialized, unallocated auction manager account with pda of ['metaplex', auction_key from auction referenced below] 1. `[]` Combined vault account with authority set to auction manager account (this will be checked) Note in addition that this vault account should have authority set to this program's pda of ['metaplex', auction_key] 2. `[]` Auction with auctioned item being set to the vault given and authority set to this program's pda of ['metaplex', auction_key] 3. `[]` Authority for the Auction Manager 4. `[signer]` Payer 5. `[]` Accept payment account of same token mint as the auction for taking payment for open editions, owner should be auction manager key 6. `[]` Store that this auction manager will belong to 7. `[]` System sysvar 8. `[]` Rent sysvar **Create Instruction(test purpose):** <details> ```rust /// Creates an InitAuctionManager instruction #[allow(clippy::too_many_arguments)] pub fn create_init_auction_manager_instruction( program_id: Pubkey, auction_manager: Pubkey, vault: Pubkey, auction: Pubkey, auction_manager_authority: Pubkey, payer: Pubkey, accept_payment_account_key: Pubkey, store: Pubkey, settings: AuctionManagerSettings, ) -> Instruction { Instruction { program_id, accounts: vec![ AccountMeta::new(auction_manager, false), AccountMeta::new_readonly(vault, false), AccountMeta::new_readonly(auction, false), AccountMeta::new_readonly(auction_manager_authority, false), AccountMeta::new_readonly(payer, true), AccountMeta::new_readonly(accept_payment_account_key, false), AccountMeta::new_readonly(store, false), AccountMeta::new_readonly(solana_program::system_program::id(), false), AccountMeta::new_readonly(sysvar::rent::id(), false), ], data: MetaplexInstruction::InitAuctionManager(settings) .try_to_vec() .unwrap(), } } ``` </details> **Process:** #### 2. `ValidateSafetyDepositBox` Validates that a given safety deposit box has in it contents that match the expected WinningConfig in the auction manager. **Account Info:** **Create Instruction(test purpose):** **Process:** #### 3. `DeprecatedRedeemBid` **Account Info:** **Create Instruction(test purpose):** **Process:** #### 4. `RedeemFullRightsTransferBid` **Account Info:** **Create Instruction(test purpose):** **Process:** #### 5. `DeprecatedRedeemParticipationBid` **Account Info:** **Create Instruction(test purpose):** **Process:** #### 6. `StartAuction` **Account Info:** 0. `[writable]` Auction manager 1. `[writable]` Auction 3. `[signer]` Auction manager authority 4. `[]` Store key 5. `[]` Auction program 6. `[]` Clock sysvar **Create Instruction(test purpose):** **Process:** #### 7. `ClaimBid` **Account Info:** **Create Instruction(test purpose):** **Process:** #### 8. `EmptyPaymentAccount(EmptyPaymentAccountArgs)` **Account Info:** **Create Instruction(test purpose):** **Process:** #### 9. `SetStore(SetStoreArgs)` **Account Info:** **Create Instruction(test purpose):** **Process:** #### 10. `SetWhitelistedCreator(SetWhitelistedCreatorArgs)` **Account Info:** **Create Instruction(test purpose):** **Process:** #### 11. `DeprecatedValidateParticipation` **Account Info:** **Create Instruction(test purpose):** **Process:** #### 12. `DeprecatedPopulateParticipationPrintingAccount` **Account Info:** **Create Instruction(test purpose):** **Process:** #### 13. `RedeemUnusedWinningConfigItemsAsAuctioneer(RedeemUnusedWinningConfigItemsAsAuctioneerArgs)` **Account Info:** **Create Instruction(test purpose):** **Process:** #### 14. `DecommissionAuctionManager` **Account Info:** **Create Instruction(test purpose):** **Process:** #### 15. `RedeemPrintingV2Bid(RedeemPrintingV2BidArgs)` **Account Info:** **Create Instruction(test purpose):** **Process:** #### 16. `WithdrawMasterEdition` **Account Info:** **Create Instruction(test purpose):** **Process:** #### 17. `RedeemParticipationBidV2` **Account Info:** **Create Instruction(test purpose):** **Process:** ## Token Metadata [Question] Deprecated intructions?? ### Structure ### Instructions #### 1. `CreateMetadataAccount(CreateMetadataAccountArgs)` Create Metadata object **Account Info:** 0. `[writable]` Metadata key (pda of ['metadata', program id, mint id]) 1. `[]` Mint of token asset 2. `[signer]` Mint authority 3. `[signer]` payer 4. `[]` update authority info 5. `[]` System program 6. `[]` Rent info **Create Instruction(test purpose):** **Process:** #### 2.`UpdateMetadataAccount(UpdateMetadataAccountArgs)` Update a Metadta **Account Info:** 0. `[writable]` Metadata account 1. `[signer]` Update authority key **Create Instruction(test purpose):** **Process:** #### 3.`DeprecatedCreateMasterEdition(CreateMasterEditionArgs)` **Account Info:** **Create Instruction(test purpose):** **Process:** #### 4.`DeprecatedMintNewEditionFromMasterEditionViaPrintingToken` **Account Info:** **Create Instruction(test purpose):** **Process:** #### 5.`UpdatePrimarySaleHappenedViaToken` **Account Info:** **Create Instruction(test purpose):** **Process:** #### 6.`DeprecatedSetReservationList(SetReservationListArgs)` **Account Info:** **Create Instruction(test purpose):** **Process:** #### 7.`DeprecatedCreateReservationList` **Account Info:** **Create Instruction(test purpose):** **Process:** #### 8.`SignMetadata` **Account Info:** **Create Instruction(test purpose):** **Process:** #### 9.`DeprecatedMintPrintingTokensViaToken(MintPrintingTokensViaTokenArgs)` **Account Info:** **Create Instruction(test purpose):** **Process:** #### 10. `DeprecatedMintPrintingTokens(MintPrintingTokensViaTokenArgs)` **Account Info:** **Create Instruction(test purpose):** **Process:** #### 11. `CreateMasterEdition(CreateMasterEditionArgs)` **Account Info:** **Create Instruction(test purpose):** **Process:** #### 12. `MintNewEditionFromMasterEditionViaToken(MintNewEditionFromMasterEditionViaTokenArgs)` **Account Info:** **Create Instruction(test purpose):** **Process:** #### 13. `ConvertMasterEditionV1ToV2` **Account Info:** **Create Instruction(test purpose):** **Process:** #### 14. `ConvertMasterEditionV1ToV2` **Account Info:** **Create Instruction(test purpose):** **Process:** [Question] 1. V1 to V2 authority check??? ```rust= pub struct MasterEditionV1 { pub key: Key, pub supply: u64, pub max_supply: Option<u64>, /// Can be used to mint tokens that give one-time permission to mint a single limited edition. pub printing_mint: Pubkey, /// If you don't know how many printing tokens you are going to need, but you do know /// you are going to need some amount in the future, you can use a token from this mint. /// Coming back to token metadata with one of these tokens allows you to mint (one time) /// any number of printing tokens you want. This is used for instance by Auction Manager /// with participation NFTs, where we dont know how many people will bid and need participation /// printing tokens to redeem, so we give it ONE of these tokens to use after the auction is over, /// because when the auction begins we just dont know how many printing tokens we will need, /// but at the end we will. At the end it then burns this token with token-metadata to /// get the printing tokens it needs to give to bidders. Each bidder then redeems a printing token /// to get their limited editions. pub one_time_printing_authorization_mint: Pubkey, } ``` ```rust= pub struct MasterEditionV2 { pub key: Key, pub supply: u64, pub max_supply: Option<u64>, } ``` 2. edition check: like this: /rust/token-metadata/test/src/main.rs ```rust=147 master_edition_account.data[0] == Key::MasterEditionV2 ``` ## Token Vault ### Structure #### `Vault` Structure ```rust= #[repr(C)] #[derive(Clone, BorshSerialize, BorshDeserialize)] pub struct Vault { pub key: Key, /// Store token program used pub token_program: Pubkey, /// Mint that produces the fractional shares pub fraction_mint: Pubkey, /// Authority who can make changes to the vault pub authority: Pubkey, /// treasury where fractional shares are held for redemption by authority pub fraction_treasury: Pubkey, /// treasury where monies are held for fractional share holders to redeem(burn) shares once buyout is made pub redeem_treasury: Pubkey, /// Can authority mint more shares from fraction_mint after activation pub allow_further_share_creation: bool, /// Must point at an ExternalPriceAccount, which gives permission and price for buyout. pub pricing_lookup_address: Pubkey, /// In inactive state, we use this to set the order key on Safety Deposit Boxes being added and /// then we increment it and save so the next safety deposit box gets the next number. /// In the Combined state during token redemption by authority, we use it as a decrementing counter each time /// The authority of the vault withdrawals a Safety Deposit contents to count down how many /// are left to be opened and closed down. Once this hits zero, and the fraction mint has zero shares, /// then we can deactivate the vault. pub token_type_count: u8, pub state: VaultState, /// Once combination happens, we copy price per share to vault so that if something nefarious happens /// to external price account, like price change, we still have the math 'saved' for use in our calcs pub locked_price_per_share: u64, } ``` #### `SafetyDepositBox` Structure ```rust= #[repr(C)] #[derive(Clone, BorshSerialize, BorshDeserialize)] pub struct SafetyDepositBox { // Please note if you change this struct, be careful as we read directly off it // in Metaplex to avoid serialization costs... /// Each token type in a vault has it's own box that contains it's mint and a look-back pub key: Key, /// Key pointing to the parent vault pub vault: Pubkey, /// This particular token's mint pub token_mint: Pubkey, /// Account that stores the tokens under management pub store: Pubkey, /// the order in the array of registries pub order: u8, } ``` #### `ExternalPriceAccount` Structure ```rust= #[repr(C)] #[derive(Clone, BorshSerialize, BorshDeserialize)] pub struct ExternalPriceAccount { pub key: Key, pub price_per_share: u64, /// Mint of the currency we are pricing the shares against, should be same as redeem_treasury. /// Most likely will be USDC mint most of the time. pub price_mint: Pubkey, /// Whether or not combination has been allowed for this vault. pub allowed_to_combine: bool, } ``` ### Instructions #### 1. **Account Info:** **Create Instruction(test purpose):** **Process:**

    Import from clipboard

    Paste your markdown or webpage here...

    Advanced permission required

    Your current role can only read. Ask the system administrator to acquire write and comment permission.

    This team is disabled

    Sorry, this team is disabled. You can't edit this note.

    This note is locked

    Sorry, only owner can edit this note.

    Reach the limit

    Sorry, you've reached the max length this note can be.
    Please reduce the content or divide it to more notes, thank you!

    Import from Gist

    Import from Snippet

    or

    Export to Snippet

    Are you sure?

    Do you really want to delete this note?
    All users will lose their connection.

    Create a note from template

    Create a note from template

    Oops...
    This template has been removed or transferred.
    Upgrade
    All
    • All
    • Team
    No template.

    Create a template

    Upgrade

    Delete template

    Do you really want to delete this template?
    Turn this template into a regular note and keep its content, versions, and comments.

    This page need refresh

    You have an incompatible client version.
    Refresh to update.
    New version available!
    See releases notes here
    Refresh to enjoy new features.
    Your user state has changed.
    Refresh to load new user state.

    Sign in

    Forgot password

    or

    By clicking below, you agree to our terms of service.

    Sign in via Facebook Sign in via Twitter Sign in via GitHub Sign in via Dropbox Sign in with Wallet
    Wallet ( )
    Connect another wallet

    New to HackMD? Sign up

    Help

    • English
    • 中文
    • Français
    • Deutsch
    • 日本語
    • Español
    • Català
    • Ελληνικά
    • Português
    • italiano
    • Türkçe
    • Русский
    • Nederlands
    • hrvatski jezik
    • język polski
    • Українська
    • हिन्दी
    • svenska
    • Esperanto
    • dansk

    Documents

    Help & Tutorial

    How to use Book mode

    Slide Example

    API Docs

    Edit in VSCode

    Install browser extension

    Contacts

    Feedback

    Discord

    Send us email

    Resources

    Releases

    Pricing

    Blog

    Policy

    Terms

    Privacy

    Cheatsheet

    Syntax Example Reference
    # Header Header 基本排版
    - Unordered List
    • Unordered List
    1. Ordered List
    1. Ordered List
    - [ ] Todo List
    • Todo List
    > Blockquote
    Blockquote
    **Bold font** Bold font
    *Italics font* Italics font
    ~~Strikethrough~~ Strikethrough
    19^th^ 19th
    H~2~O H2O
    ++Inserted text++ Inserted text
    ==Marked text== Marked text
    [link text](https:// "title") Link
    ![image alt](https:// "title") Image
    `Code` Code 在筆記中貼入程式碼
    ```javascript
    var i = 0;
    ```
    var i = 0;
    :smile: :smile: Emoji list
    {%youtube youtube_id %} Externals
    $L^aT_eX$ LaTeX
    :::info
    This is a alert area.
    :::

    This is a alert area.

    Versions and GitHub Sync
    Get Full History Access

    • Edit version name
    • Delete

    revision author avatar     named on  

    More Less

    Note content is identical to the latest version.
    Compare
      Choose a version
      No search result
      Version not found
    Sign in to link this note to GitHub
    Learn more
    This note is not linked with GitHub
     

    Feedback

    Submission failed, please try again

    Thanks for your support.

    On a scale of 0-10, how likely is it that you would recommend HackMD to your friends, family or business associates?

    Please give us some advice and help us improve HackMD.

     

    Thanks for your feedback

    Remove version name

    Do you want to remove this version name and description?

    Transfer ownership

    Transfer to
      Warning: is a public team. If you transfer note to this team, everyone on the web can find and read this note.

        Link with GitHub

        Please authorize HackMD on GitHub
        • Please sign in to GitHub and install the HackMD app on your GitHub repo.
        • HackMD links with GitHub through a GitHub App. You can choose which repo to install our App.
        Learn more  Sign in to GitHub

        Push the note to GitHub Push to GitHub Pull a file from GitHub

          Authorize again
         

        Choose which file to push to

        Select repo
        Refresh Authorize more repos
        Select branch
        Select file
        Select branch
        Choose version(s) to push
        • Save a new version and push
        • Choose from existing versions
        Include title and tags
        Available push count

        Pull from GitHub

         
        File from GitHub
        File from HackMD

        GitHub Link Settings

        File linked

        Linked by
        File path
        Last synced branch
        Available push count

        Danger Zone

        Unlink
        You will no longer receive notification when GitHub file changes after unlink.

        Syncing

        Push failed

        Push successfully