---
# System prepended metadata

title: Documentation for custom marketplaces built on Metaplex
tags: [Documentation]

---

---
title: 'Documentation for custom marketplaces built on Metaplex'
disqus: hackmd
---

Metaplex-powered Fixed Price and Highest Bid NFT Sales for Marketplaces
===



## Table of Contents

[TOC]

## Some Docs and Resources


* Solana Developer Docs: https://docs.solana.com/
* SPL Token: https://spl.solana.com/token
* Metaplex Docs: https://docs.metaplex.com/
* Metaplex Github: https://github.com/metaplex-foundation/metaplex
* Metaplex Developer Guide: https://www.notion.so/Metaplex-Developer-Guide-afefbc19841744c28587ab948a08cfac

## Overview: Sale Types and Basic Concepts

As of date (May 2022) two major sale types currently supported  are:

* **Prints Transfer** (roughly refers to `PrintingV2`)
* **Full Rights Transfer**

### Prints Ownership Transfer

since rights to create prints are tokenized itself, the owner of the master edition can distribute tokens that allow users to create prints from master editions. A notable difference of *master editions* from *editions* is that **the artwork remains visible in the original owner's wallet as a master edition**, still after **its prints have been moved to the buyer's wallets**. 

Note, that in this case **the buyer will not be able to modify the metadata** of limited edition NFTs to be different than their master edition's metadata, e.g. set a different image uri and name.

### Full Rights Ownership Transfer

Full rights ownership transfer is master edition NFT ownership transfer which additionally implies setting`updateAuthority` on the NFT Metadata to the effect of the **buyer acquiring all the rights and privileges associated with the original owner** (including the right **to modify metadata**, mint new prints, etc)

:::info
Metaplex's **non-fungible-token standard** is a part of the Solana Program Library (SPL). The basic definition of an NFT as a unique token with a fixed supply of 1 and 0 decimals was further extended on Solana to include **additional metadata** such as URI as defined in ERC-721 on Ethereum.
:::

:::info
A **master edition token**, when minted, represents both a non-fungible token on Solana and **metadata that allows creators to control the provenance of prints** created from the master edition.
:::

:::info
A **print** represents a **copy of an NFT**, and is created from a Master Edition. Each print has an **edition number** associated with it.
:::

>See more details in official metaplex documentation here:
https://docs.metaplex.com/architecture/deep_dive/metaplex#types-of-token-sales



Basic Sale Flows (Fixed Price Sale)
---
```sequence
Note left of Seller: lists NFT resource
Seller->Vault: Transfer NFT ownership on listing
Note right of Buyer: claims NFT resource
Vault->Buyer: Transfer token ownership on claiming
Buyer->Escrow: Transfer price lamports
Escrow-Seller: Transfer royalties
```



Stored On-Chain Data
---

### NFT Resource

A tokenized NFT resource (either a single token or a collection) built on Metaplex protocol.

:::warning
Note that from the sale types mentioned (print ownership transfer and full rights ownership transfer) follows that **only master edition tokens** are accepted as valid NFT resources for sales.
:::

>Size: 49 bytes

```rs=
pub struct NFTResource {
    /// Mint account for selling NFT Resource
    pub token_mint: Pubkey,
    /// Available supply
    pub supply: u64,
    /// Max available supply
    pub max_supply: Option<u64>,
}
```

### Market Data

Represents market-specific configuration data bound to an NFT resource for sales.


>Size: 177 bytes

```rs=
pub struct MarketData {
    /// Pubkey of the authority with permission to modify market
    pub authority: Pubkey,
    /// Name within the range of 32 bytes
    pub name: Option<MarketName>,
    /// A resource for sales the market is bound to
    pub resource: Pubkey,
    /// Whether or not market data is mutable
    pub mutable: bool,
    /// Price value
    pub price: u64,
    /// Optional end price for OpenSea-like declining-price listings
    pub end_price: Option<u64>,
    /// Optional start date
    pub start_date: Option<u64>,
    /// Optional end date (should be calculated from the client-provided 'Duration' field)
    pub end_date: Option<u64>,
    /// Aggregated funds to calculate royalties on withdrawal
    pub funds_collected: u64,
    /// Optional buyer's pubkey entitled to purchase NFT as soon as it is listed
    pub reserved_buyer: Option<Pubkey>,
}
```

Instructions
---


### CreateMarket

Creates a new market account to hold `MarketData` configuaration bound to an NFT resource for sales.


#### Instruction Accounts

```rs=
/// Create a new market account bound to an NFT selling resource.
/// 0. `[signer]` The account of the market creater/owner, authorised to make changes
/// 1. `[writable]` Uninitialized market account
/// 2. `[]` Mint account of the NFT resource being put on market
/// 3. `[]` Rent sysvar
/// 4. `[]` System account
CreateMarket(CreateMarketArgs),

```


#### Instruction Data 

```rs
pub struct CreateMarketArgs {
    /// Pubkey of the authority with permission to modify market
    pub authority: Pubkey,
    /// NFT resource bound to market account
    pub resource: Pubkey,
    /// Whether or not market data is mutable
    pub mutable: bool,
    /// Price value
    pub price: u64,
    /// Optional end price for OpenSea-like declining-price listings
    pub end_price: Option<u64>,
    /// Optional start date
    pub start_date: Option<u64>,
    /// Optional end date (should be calculated from the client-provided 'Duration' field)
    pub end_date: Option<u64>,
    /// Optional buyer's pubkey entitled to purchase NFT as soon as it is listed
    pub reserved_buyer: Option<Pubkey>,
    /// Name within the range of 32 bytes
    pub name: Option<MarketName>,
}
```

#### Errors and Limitations  

* `name` should be within the range of **32 bytes**
* `start_date` if provided **should not be in the past**
* If both `start_date` and `end_date` were provided `end_date` **should not precede** `start_date`
* **Market account address** should be derived with the following seeds:

```rs=
&[
   PREFIX.as_bytes(),
   &program_id.as_ref(),
   args.resource.as_ref(),
],
```

`PREFIX` - constant program prefix string
`resource` - account address holding NFT resource for sales

### UpdateMarket

#### Instruction Accounts

```rs=

```


#### Instruction Data 

```rs=

```

#### Errors and Limitations  



### ListNFT

#### Instruction Accounts

Lists NFT resource for sales: 

* Creates NFT resource account (to store `NFTResource` data)
* Transfers master edition ownership from source token account (seller's token account) to vault token account


```rs=
/// List NFT resource (implies NFT ME ownership transfer from the seller's token account
/// to vault token account).
/// 0. `[signer]` Authorised account
/// 1. `[writable]` Uninitialized market account
/// 2. `[]` NFT resource account put on market (holding market-relevant NFT data)
/// 3. `[]` Vault token account to transfer Master Edition ownership to
/// 4. `[]` The metadata account, storing information about NFT resource put on market
/// 5. `[]` Master Edition account of the NFT resource
/// 6. `[]` Source token account to transfer Master Edition ownership from
/// 7. `[]` SPL Token Program
/// 8. `[]` Rent sysvar
/// 9. `[]` System account
ListNFT(ListNFTArgs),
```

#### Instruction Data 


```rs=
pub struct ListNFTArgs {
    /// Mint account of the NFT resource
    pub mint: Pubkey,
    /// Supply provided on listing
    pub supply: u64,
    /// Optional max supply provided on listing, should not exceed available supply:
    /// i.e. ME max_supply - ME supply
    pub max_supply: Option<u64>,
}
```

#### Errors and Limitations

* if `primary_sale_happened` of the metadata account has not been set (which serves as an indicator of the post-primary sales) - **at least one creator should be listed** among `metadata.creators`
* `max_supply` provided on listing **should not exceed available supply** i.e. `max_supply` of master edition account without master edition `supply` of master edition: `master_edition.max_supply - master_edition.supply`

### ClaimNFT

#### Instruction Accounts

Enables the buyer to 'claim NFT resource': 

* Updates `primary_sale_happened` flag in metadata
* Transfers token ownership from vault token account to destination token account (buyer's token account)


```rs=
    /// Transfers token ownership from vault token account to destination token account
    /// (buyer's token account).
    /// 0. `[]` Vault token account to transfer Master Edition ownership from
    /// 1. `[]` Owner of the vault token account
    /// 2. `[]` Treasury holder/escrow account
    /// 3. `[writable]` The metadata account, storing information about NFT resource put on market
    /// 4. `[]` Destination (buyer's) NFT token account
    ClaimResource(ClaimResourceArgs),
```

:::info
`UpdatePrimarySaleHappenedViaToken` - allows updating the primary sale boolean on Metadata solely through owning an account with a token from the metadata's mint and signing the transaction.
:::

#### Instruction Data 


```rs=
pub struct ClaimResourceArgs {
    /// NFT resource for sales
    pub resource: Pubkey,
    /// Mint account of the NFT resource
    pub mint: Pubkey,
}

```

#### Errors and Limitations

* Treasury holder **balance should be zero** prior to claiming NFT resource for sale
* **Vault owner account address** should be derived with the following seeds:


```rs=
&[
  VAULT_OWNER_PREFIX.as_bytes(),
  program_id.as_ref(),
  args.resource.as_ref(),
  vault.as_ref(),
],
```
`VAULT_OWNER_PREFIX` - constant prefix string
`resource` - account address holding NFT resource for sales
`vault` - vault account to transfer ownership from

### BuyNFT

Transfers lamports from the buyer's wallet to escrow account:

* Transfers `market.price` lamports from the source token account (buyer's token account) to that of the treasury holder/escrow
* Updates `market.funds_collected`
* Mints new edition from master edition for NFT resource for sale
* Updates `primary_sale_happened` flag in the new metadata account
* Updates `resource.supply` accordingly

#### Instruction Accounts


```=rs
    /// Transfers `market.price` lamports from the source token account (buyer's token account)
    /// to that of treasury holder/escrow and mints new edition from NFT master edition.
    /// 0. `[writable]` Market account bound to NFT resource for sale
    /// 1. `[writable]` NFT resource for sale
    /// 2. `[]` Source (i.e. buyer's) token account to transfer `market.price` amount of lamports from
    /// 3. `[signer]` Authority (i.e. buyer) for spl token transfer from source to escrow account
    ///               and a new mint authority for new minted NFT print
    /// 4. `[]` Treasury holder/escrow account to transfer `market.price` amount of lamports to
    /// 5. `[writable]` New metadata account for minted NFT print
    /// 6. `[]` New edition account for minted NFT resource copy
    /// 7. `[writable]` Master edition account of the NFT resource for sale
    /// 8. `[writable]` New mint account to mint new edition from master edition
    /// 9. `[writable]` Edition marker account to mint new edition
    /// 10. `[writable]` Token account to mint new edition from
    /// 11. `[]` Token owner account to mint new edition from
    /// 12. `[]` New token account
    /// 13. `[]` Master edition metadata account
    /// 14. `[]` SPL Token Program
    /// 15. `[]` Rent sysvar
    /// 16. `[]` System account
    BuyNFT(),
```


#### Errors and Limitations


 * `resource.supply` should not exceed `resource.max_supply`
 * Same errors and limitations apply as on
    * `spl_token::instruction::transfer`
    * `MintNewEditionFromMasterEditionViaToken` :
 `mpl_token_metadata::instruction::mint_new_edition_from_master_edition_via_token`


### BuyNFTWithAuthUpdate


Transfers lamports from the buyer's wallet to escrow account and sets the corresponding new update authority in the NFT metadata (results in full rights master edition ownership transfer):

* Transfers `market.price` lamports from the source token account (buyer's token account) to that of the treasury holder/escrow
* Updates `market.funds_collected`
* Sets update authority for NFT metadata to `user_wallet`
* Updates `primary_sale_happened` flag in  the new metadata account
* Updates `resource.supply` accordingly


#### Instruction Accounts

```rs=
    /// Transfers `market.price` lamports from the source token account (buyer's token account)
    /// to that of treasury holder/escrow and sets new update authority in NFT metadata.
    /// 0. `[writable]` Market account bound to NFT resource for sale
    /// 1. `[writable]` NFT resource for sale
    /// 2. `[signer]` An update authority with permission to modify the NFT metadata
    /// 3. `[]` Source (i.e. buyer's) token account to transfer `market.price` amount of lamports from
    /// 4. `[]` Authority (i.e. buyer) for spl token transfer from source to escrow account
    /// 5. `[writable]` Metadata account for NFT resource
    /// 6. `[]` Treasury holder/escrow account to transfer `market.price` amount of lamports to
    /// 7. `[]` SPL Token Program
    BuyNFTWithAuthUpdate(),
```


#### Errors and Limitations  

 * `resource.supply` should not exceed `resource.max_supply`
 * Same errors and limitations apply as on
    * `spl_token::instruction::transfer`
    * `MintNewEditionFromMasterEditionViaToken` :
 `mpl_token_metadata::instruction::mint_new_edition_from_master_edition_via_token`

### DistributeRoyalties

Transfers royalties from escrow/treasury holder account to the destination account:

* Calculates shares and royalties according to the beneficiary party type (creator, market owner or both)
* Creates the destination associated token account if needed
* Transfers royalties to the destination associated token account
* Creates a marker account on successful royalties transfer so as to prevent multiple withdrawals

:::warning
Note: 
* Should be called for each beneficiary party (could be a creator of the NFT resource, a `market.owner`, or both)
* If the stated beneficiary is both a creator and a market owner he should receive both shares
:::


#### Instruction Accounts

```rs=
    /// Transfers royalties from escrow/treasury holder account to destination account.
    /// Note: Destination associated token account should match the provided beneficiary address
    /// (receiving wallet address) and spl token mint address (otherwise, error is thrown).
    /// 0. `[]` Market account bound to NFT resource for sale
    /// 1. `[]` Metadata account for NFT resource
    /// 2. `[]` Escrow/treasury holder account to transfer royalties from
    /// 3. `[]` Authority account for royalties transfer transaction
    /// 4. `[]` Credit associated token account of the shareholder to receive royalties: will be created if empty
    /// 5. `[]` Debit account for associated token account creation and payout_ticket account creation
    /// 6. `[writable]` Uninitialized marker account to prevent multiple withdrawal: created once on each withdrawal
    /// 7. `[]` SPL Token Program
    /// 8. `[]` Rent sysvar
    /// 9. `[]` System account
    DistributeRoyalties(DistributeRoyaltiesArgs),
}
```


#### Instruction Data 

```rs=
pub struct DistributeRoyaltiesArgs {
    // NFT resource for sale
    pub resource: Pubkey,
    // Receiving address (wallet address to be associated with the destination account)
    pub beneficiary: Pubkey,
    // Mint account for the new associated token account
    treasury_mint: Pubkey,
}
```

#### Errors and Limitations  

* `beneficiary` pubkey (i.e. the receiving wallet) provided for the royalties withdrawal **should belong to either market owner or any of the creators** in the creators list 
* A given beneficiary is **apt for a single (one-time) royalties withdrawal only** 
* If `primary_sale_happened` of the metadata account **has not been set** (which serves as an indicator of the post-primary sales) - **at least one creator should be listed** among `metadata.creators`
* **Credit associated token account**  of the shareholder should be a **valid associated token account address for the given receiving wallet** (`beneficary`) and **mint** (`treasury_mint`) accounts -- see `spl_associated_token_account::get_associated_token_address` for details  

### Shares and Royalties Calculation

#### Primary Sale

Revenue for each **creator** (corresponding creator's address listed as `beneficiary` wallet):

$$
Rev[cr_{i}] = Total\frac{s_{i}}{100},
\sum_{i=0}^n s_{i} = 100
$$

where `n` - creators number, `s` - creator's share, `Total` - total funds collected

#### Secondary Sale

Revenue for each **creator** (corresponding creator's address listed as `beneficiary` wallet):

$$
Rev[cr_{i}] = Total\frac{s_{i}}{100} \frac{P}{10000},
\sum_{i=0}^n s_{i} = 100, P \in \{1, 2, \dots,10000\}
$$

where `n` - creators number, `s` - creator's share, `Total` - total funds collected

Revenue for the **market owner** (market address listed as `beneficiary` wallet):

$$
Rev[cr_{i}] = Total \frac{P}{10000}, P \in \{1, 2, \dots,10000\}
$$

:::success
#### Example

Given:
Total funds = 1000, share for the first creator = 60,  second creator = 40 and `seller_fee_basis_points` = 250:

first creator withdraws 15,  second creator - 20, market - 25, i.e. 50 total fees added on top of the stated price.

:::


:::info
Market owner can withdraw royalties both as a creator and a market owner
:::


###### tags: `Documentation`