---
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`