├── .cargo └── config.toml ├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── README.md ├── mpl_token_metadata ├── .gitignore ├── Cargo.toml ├── LICENSE ├── Makefile ├── README.md ├── buf.gen.yaml ├── proto │ └── mpl_token_metadata.proto ├── sol.png ├── src │ ├── lib.rs │ ├── mpl_token_metadata │ │ ├── constants.rs │ │ ├── error.rs │ │ ├── instruction │ │ │ ├── burn.rs │ │ │ ├── collection.rs │ │ │ ├── delegate.rs │ │ │ ├── edition.rs │ │ │ ├── escrow.rs │ │ │ ├── metadata.rs │ │ │ ├── mod.rs │ │ │ ├── scratchpad.rs │ │ │ ├── state.rs │ │ │ ├── uses.rs │ │ │ └── verification.rs │ │ ├── mod.rs │ │ ├── processor │ │ │ ├── burn │ │ │ │ ├── burn.rs │ │ │ │ ├── burn_edition_nft.rs │ │ │ │ ├── burn_nft.rs │ │ │ │ ├── fungible.rs │ │ │ │ ├── mod.rs │ │ │ │ ├── nonfungible.rs │ │ │ │ └── nonfungible_edition.rs │ │ │ ├── collection │ │ │ │ ├── approve_collection_authority.rs │ │ │ │ ├── mod.rs │ │ │ │ ├── revoke_collection_authority.rs │ │ │ │ ├── set_and_verify_collection.rs │ │ │ │ ├── set_and_verify_sized_collection_item.rs │ │ │ │ ├── set_collection_size.rs │ │ │ │ ├── unverify_collection.rs │ │ │ │ ├── unverify_sized_collection_item.rs │ │ │ │ ├── verify_collection.rs │ │ │ │ └── verify_sized_collection_item.rs │ │ │ ├── delegate │ │ │ │ ├── delegate.rs │ │ │ │ ├── mod.rs │ │ │ │ └── revoke.rs │ │ │ ├── edition │ │ │ │ ├── convert_master_edition_v1_to_v2.rs │ │ │ │ ├── create_master_edition_v3.rs │ │ │ │ ├── mint_new_edition_from_master_edition_via_token.rs │ │ │ │ └── mod.rs │ │ │ ├── escrow │ │ │ │ ├── README.md │ │ │ │ ├── close_escrow_account.rs │ │ │ │ ├── create_escrow_account.rs │ │ │ │ ├── mod.rs │ │ │ │ ├── pda.rs │ │ │ │ └── transfer_out.rs │ │ │ ├── fee │ │ │ │ └── mod.rs │ │ │ ├── freeze │ │ │ │ ├── freeze_delegated_account.rs │ │ │ │ ├── mod.rs │ │ │ │ └── thaw_delegated_account.rs │ │ │ ├── metadata │ │ │ │ ├── create.rs │ │ │ │ ├── create_medatata_accounts_v3.rs │ │ │ │ ├── mint.rs │ │ │ │ ├── mod.rs │ │ │ │ ├── print.rs │ │ │ │ ├── puff_metadata.rs │ │ │ │ ├── remove_creator_verification.rs │ │ │ │ ├── set_token_standard.rs │ │ │ │ ├── sign_metadata.rs │ │ │ │ ├── transfer.rs │ │ │ │ ├── update.rs │ │ │ │ ├── update_metadata_account_v2.rs │ │ │ │ └── update_primary_sale_happened_via_token.rs │ │ │ ├── mod.rs │ │ │ ├── state │ │ │ │ ├── lock.rs │ │ │ │ ├── mod.rs │ │ │ │ └── unlock.rs │ │ │ ├── uses │ │ │ │ ├── approve_use_authority.rs │ │ │ │ ├── mod.rs │ │ │ │ ├── revoke_use_authority.rs │ │ │ │ └── utilize.rs │ │ │ └── verification │ │ │ │ ├── collection.rs │ │ │ │ ├── creator.rs │ │ │ │ ├── mod.rs │ │ │ │ └── verify.rs │ │ ├── state │ │ │ ├── asset_data.rs │ │ │ ├── collection.rs │ │ │ ├── creator.rs │ │ │ ├── data.rs │ │ │ ├── delegate.rs │ │ │ ├── edition.rs │ │ │ ├── edition_marker.rs │ │ │ ├── edition_marker_v2.rs │ │ │ ├── escrow.rs │ │ │ ├── fee.rs │ │ │ ├── master_edition.rs │ │ │ ├── metadata.rs │ │ │ ├── migrate.rs │ │ │ ├── mod.rs │ │ │ ├── programmable.rs │ │ │ ├── token_auth_payload.rs │ │ │ └── uses.rs │ │ └── utils.rs │ └── pb │ │ ├── mod.rs │ │ └── mpl_token_metadata.rs └── substreams.yaml ├── pumpfun ├── .gitignore ├── Cargo.toml ├── LICENSE ├── Makefile ├── README.md ├── buf.gen.yaml ├── proto │ └── pumpfun.proto ├── pumpfun.png ├── src │ ├── lib.rs │ ├── pb │ │ ├── event.rs │ │ ├── mod.rs │ │ ├── pumpfun.rs │ │ └── raydium.rs │ └── pumpfun │ │ ├── constants.rs │ │ ├── instruction.rs │ │ ├── log.rs │ │ └── mod.rs └── substreams.yaml ├── raydium_amm ├── .gitignore ├── Cargo.toml ├── LICENSE ├── Makefile ├── README.md ├── buf.gen.yaml ├── proto │ └── raydium_amm.proto ├── raydium.png ├── src │ ├── lib.rs │ ├── pb │ │ ├── event.rs │ │ ├── mod.rs │ │ ├── raydium.rs │ │ └── raydium_amm.rs │ └── raydium_amm │ │ ├── constants.rs │ │ ├── instruction.rs │ │ ├── log.rs │ │ ├── mod.rs │ │ └── state.rs └── substreams.yaml ├── rust-toolchain.toml ├── spl_token ├── .gitignore ├── Cargo.toml ├── LICENSE ├── Makefile ├── README.md ├── buf.gen.yaml ├── proto │ └── spl_token.proto ├── sol.png ├── src │ ├── lib.rs │ └── pb │ │ ├── event.rs │ │ ├── mod.rs │ │ └── spl_token.rs └── substreams.yaml ├── system_program ├── .gitignore ├── Cargo.toml ├── LICENSE ├── Makefile ├── README.md ├── buf.gen.yaml ├── proto │ └── system_program.proto ├── sol.png ├── src │ ├── lib.rs │ └── pb │ │ ├── mod.rs │ │ ├── sf.solana.type.v1.rs │ │ ├── sol.instructions.v1.rs │ │ ├── sol.transactions.v1.rs │ │ └── system_program.rs └── substreams.yaml └── token.sh /.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [build] 2 | target = "wasm32-unknown-unknown" -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .envrc 2 | target 3 | .idea 4 | *.spkg 5 | *.log 6 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = [ 3 | "raydium_amm", 4 | "spl_token", 5 | "pumpfun", 6 | "system_program", 7 | "mpl_token_metadata", 8 | ] 9 | resolver = "2" 10 | 11 | [workspace.package] 12 | version = "0.1.5" 13 | edition = "2021" 14 | 15 | [workspace.dependencies] 16 | substreams = "^0.5.0" 17 | substreams-solana = { git = "https://github.com/streamingfast/substreams-solana", branch = "master" } 18 | substreams-solana-utils = { git = "https://github.com/0xpapercut/substreams-solana-utils", tag = "v0.1.5" } 19 | prost = "0.11" 20 | bs58 = "0.5.0" 21 | borsh = { version = "1.5.1", features = ["derive"] } 22 | lazy_static = "1.5.0" 23 | anyhow = "1.0.86" 24 | thiserror = "1.0.63" 25 | bincode = "1.3.3" 26 | base64 = "0.22.1" 27 | serde = { version = "1.0.197", features = ["derive"] } 28 | arrayref = "0.3.8" 29 | safe-transmute = "0.11.3" 30 | bytemuck = "1.17.0" 31 | regex = "1.10.6" 32 | num-derive = "0.4.2" 33 | num-traits = "0.2.19" 34 | 35 | [profile.release] 36 | lto = true 37 | opt-level = 's' 38 | strip = "debuginfo" 39 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # solana-substreams 2 | Solana substreams monorepo. 3 | 4 | ## Getting started 5 | 6 | Try out a module directly from the command line with `substreams`: 7 | 8 | ```bash 9 | # System Program 10 | substreams gui system-program-events 11 | # SPL Token 12 | substream gui spl-token-events 13 | # Raydium AMM 14 | substreams gui raydium-amm-events 15 | # Pumpfun 16 | substreams gui pumpfun-events 17 | # MPL Token Metadata 18 | substreams gui mpl-token-metadata-events 19 | ``` 20 | 21 | You can access the substreams in this repo either by specifying them as a dependency through `substreams.yaml`, or by using them as libraries (see setup). 22 | 23 | ## Setup 24 | ### Library usage 25 | ```toml 26 | [dependencies] 27 | substreams-solana-utils = { git = "https://github.com/0xpapercut/substreams-solana-utils", tag = "v0.1.5" } # Mandatory 28 | system-program-substream = { git = "https://github.com/0xpapercut/solana-substreams", tag = "v0.1.5" } 29 | spl-token-substream = { git = "https://github.com/0xpapercut/solana-substreams", tag = "v0.1.5" } 30 | raydium-amm-substream = { git = "https://github.com/0xpapercut/solana-substreams", tag = "v0.1.5" } 31 | pumpfun-substream = { git = "https://github.com/0xpapercut/solana-substreams", tag = "v0.1.5" } 32 | mpl-token-metadata-substream = { git = "https://github.com/0xpapercut/solana-substreams", tag = "v0.1.5" } 33 | ``` 34 | 35 | For a realistic example, checkout [solana-indexer](https://github.com/0xpapercut/solana-indexer). 36 | -------------------------------------------------------------------------------- /mpl_token_metadata/.gitignore: -------------------------------------------------------------------------------- 1 | *.spkg 2 | /replay.log 3 | target/ 4 | .idea 5 | .envrc 6 | -------------------------------------------------------------------------------- /mpl_token_metadata/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "mpl-token-metadata-substream" 3 | version.workspace = true 4 | edition.workspace = true 5 | 6 | [lib] 7 | name = "mpl_token_metadata_substream" 8 | crate-type = ["lib", "cdylib"] 9 | 10 | [dependencies] 11 | substreams = { workspace = true } 12 | substreams-solana = { workspace = true } 13 | substreams-solana-utils = { workspace = true } 14 | prost = { workspace = true } 15 | bs58 = { workspace = true } 16 | borsh = { workspace = true } 17 | lazy_static = { workspace = true } 18 | num-derive = { workspace = true } 19 | num-traits = { workspace = true } 20 | thiserror = { workspace = true } 21 | -------------------------------------------------------------------------------- /mpl_token_metadata/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 0xpapercut 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /mpl_token_metadata/Makefile: -------------------------------------------------------------------------------- 1 | ENDPOINT ?= mainnet.sol.streamingfast.io:443 2 | 3 | .PHONY: build 4 | build: 5 | CARGO_TARGET_DIR=./target cargo build --target wasm32-unknown-unknown --release 6 | 7 | .PHONY: stream 8 | stream: build 9 | if [ -n "$(STOP)" ]; then \ 10 | substreams run -e $(ENDPOINT) substreams.yaml mpl_token_metadata_events -s $(START) -t $(STOP); \ 11 | else \ 12 | substreams run -e $(ENDPOINT) substreams.yaml mpl_token_metadata_events -s $(START); \ 13 | fi 14 | 15 | .PHONY: protogen 16 | protogen: 17 | substreams protogen ./substreams.yaml --exclude-paths="sf/substreams,google" 18 | 19 | .PHONY: package 20 | package: 21 | substreams pack ./substreams.yaml 22 | -------------------------------------------------------------------------------- /mpl_token_metadata/README.md: -------------------------------------------------------------------------------- 1 | # metaplex-substream 2 | Stream Metaplex Token Metadata events with [substreams](https://substreams.streamingfast.io). 3 | 4 | ## Usage 5 | ```bash 6 | substreams gui mpl-token-metadaata-events 7 | ``` 8 | If you see no output, please check that you have set a starting block, e.g. `substreams gui mpl-token-metadaata-events -s 300000000`. 9 | 10 | ## Disclaimer 11 | Because of the complexity of the metadata program, I will not expand this particular substream. It's generally better to just account data directly from an RPC (if you need just state data). 12 | -------------------------------------------------------------------------------- /mpl_token_metadata/buf.gen.yaml: -------------------------------------------------------------------------------- 1 | 2 | version: v1 3 | plugins: 4 | - plugin: buf.build/community/neoeinstein-prost:v0.2.2 5 | out: src/pb 6 | opt: 7 | - file_descriptor_set=false 8 | 9 | - plugin: buf.build/community/neoeinstein-prost-crate:v0.3.1 10 | out: src/pb 11 | opt: 12 | - no_features 13 | -------------------------------------------------------------------------------- /mpl_token_metadata/sol.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0xbolt/solana-substreams/903c4ea30e3764f7dc290b5a461666b9f2060ce6/mpl_token_metadata/sol.png -------------------------------------------------------------------------------- /mpl_token_metadata/src/mpl_token_metadata/constants.rs: -------------------------------------------------------------------------------- 1 | use substreams_solana_utils::pubkey::Pubkey; 2 | use substreams_solana::b58; 3 | 4 | pub const MPL_TOKEN_METADATA_PROGRAM_ID: Pubkey = Pubkey(b58!("metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s")); 5 | -------------------------------------------------------------------------------- /mpl_token_metadata/src/mpl_token_metadata/instruction/burn.rs: -------------------------------------------------------------------------------- 1 | use borsh::{BorshDeserialize, BorshSerialize}; 2 | 3 | #[derive(BorshSerialize, BorshDeserialize, PartialEq, Eq, Debug, Clone)] 4 | pub enum BurnArgs { 5 | V1 { 6 | /// The amount of the token to burn 7 | amount: u64, 8 | }, 9 | } 10 | -------------------------------------------------------------------------------- /mpl_token_metadata/src/mpl_token_metadata/instruction/collection.rs: -------------------------------------------------------------------------------- 1 | use borsh::{BorshSerialize, BorshDeserialize}; 2 | 3 | #[derive(BorshSerialize, BorshDeserialize, PartialEq, Eq, Debug, Clone)] 4 | pub struct SetCollectionSizeArgs { 5 | pub size: u64, 6 | } 7 | -------------------------------------------------------------------------------- /mpl_token_metadata/src/mpl_token_metadata/instruction/delegate.rs: -------------------------------------------------------------------------------- 1 | use std::fmt; 2 | use borsh::BorshDeserialize; 3 | use substreams_solana_utils::pubkey::Pubkey; 4 | use super::super::processor::AuthorizationData; 5 | 6 | 7 | /// Delegate args can specify Metadata delegates and Token delegates. 8 | #[derive(BorshDeserialize, PartialEq, Eq, Debug, Clone)] 9 | pub enum DelegateArgs { 10 | CollectionV1 { 11 | /// Required authorization data to validate the request. 12 | authorization_data: Option, 13 | }, 14 | SaleV1 { 15 | amount: u64, 16 | /// Required authorization data to validate the request. 17 | authorization_data: Option, 18 | }, 19 | TransferV1 { 20 | amount: u64, 21 | /// Required authorization data to validate the request. 22 | authorization_data: Option, 23 | }, 24 | DataV1 { 25 | /// Required authorization data to validate the request. 26 | authorization_data: Option, 27 | }, 28 | UtilityV1 { 29 | amount: u64, 30 | /// Required authorization data to validate the request. 31 | authorization_data: Option, 32 | }, 33 | StakingV1 { 34 | amount: u64, 35 | /// Required authorization data to validate the request. 36 | authorization_data: Option, 37 | }, 38 | StandardV1 { 39 | amount: u64, 40 | }, 41 | LockedTransferV1 { 42 | amount: u64, 43 | #[deprecated( 44 | since = "1.13.2", 45 | note = "The locked address is deprecated and will soon be removed." 46 | )] 47 | /// locked destination pubkey 48 | locked_address: Pubkey, 49 | /// Required authorization data to validate the request. 50 | authorization_data: Option, 51 | }, 52 | ProgrammableConfigV1 { 53 | /// Required authorization data to validate the request. 54 | authorization_data: Option, 55 | }, 56 | AuthorityItemV1 { 57 | /// Required authorization data to validate the request. 58 | authorization_data: Option, 59 | }, 60 | DataItemV1 { 61 | /// Required authorization data to validate the request. 62 | authorization_data: Option, 63 | }, 64 | CollectionItemV1 { 65 | /// Required authorization data to validate the request. 66 | authorization_data: Option, 67 | }, 68 | ProgrammableConfigItemV1 { 69 | /// Required authorization data to validate the request. 70 | authorization_data: Option, 71 | }, 72 | PrintDelegateV1 { 73 | /// Required authorization data to validate the request. 74 | authorization_data: Option, 75 | }, 76 | } 77 | 78 | #[derive(BorshDeserialize, PartialEq, Eq, Debug, Clone)] 79 | pub enum RevokeArgs { 80 | CollectionV1, 81 | SaleV1, 82 | TransferV1, 83 | DataV1, 84 | UtilityV1, 85 | StakingV1, 86 | StandardV1, 87 | LockedTransferV1, 88 | ProgrammableConfigV1, 89 | MigrationV1, 90 | AuthorityItemV1, 91 | DataItemV1, 92 | CollectionItemV1, 93 | ProgrammableConfigItemV1, 94 | PrintDelegateV1, 95 | } 96 | 97 | #[derive(BorshDeserialize, PartialEq, Eq, Debug, Clone, Copy)] 98 | pub enum MetadataDelegateRole { 99 | AuthorityItem, 100 | Collection, 101 | Use, 102 | Data, 103 | ProgrammableConfig, 104 | DataItem, 105 | CollectionItem, 106 | ProgrammableConfigItem, 107 | } 108 | 109 | impl fmt::Display for MetadataDelegateRole { 110 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 111 | let message = match self { 112 | Self::AuthorityItem => "authority_item_delegate".to_string(), 113 | Self::Collection => "collection_delegate".to_string(), 114 | Self::Use => "use_delegate".to_string(), 115 | Self::Data => "data_delegate".to_string(), 116 | Self::ProgrammableConfig => "programmable_config_delegate".to_string(), 117 | Self::DataItem => "data_item_delegate".to_string(), 118 | Self::CollectionItem => "collection_item_delegate".to_string(), 119 | Self::ProgrammableConfigItem => "prog_config_item_delegate".to_string(), 120 | }; 121 | 122 | write!(f, "{message}") 123 | } 124 | } 125 | 126 | #[derive(BorshDeserialize, PartialEq, Eq, Debug, Clone, Copy)] 127 | pub enum HolderDelegateRole { 128 | PrintDelegate, 129 | } 130 | 131 | impl fmt::Display for HolderDelegateRole { 132 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 133 | let message = match self { 134 | Self::PrintDelegate => "print_delegate".to_string(), 135 | }; 136 | 137 | write!(f, "{message}") 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /mpl_token_metadata/src/mpl_token_metadata/instruction/edition.rs: -------------------------------------------------------------------------------- 1 | use borsh::{BorshDeserialize, BorshSerialize}; 2 | 3 | #[derive(BorshSerialize, BorshDeserialize, PartialEq, Eq, Debug, Clone)] 4 | pub struct MintNewEditionFromMasterEditionViaTokenArgs { 5 | pub edition: u64, 6 | } 7 | 8 | #[derive(BorshSerialize, BorshDeserialize, PartialEq, Eq, Debug, Clone)] 9 | pub struct CreateMasterEditionArgs { 10 | /// If set, means that no more than this number of editions can ever be minted. This is immutable. 11 | pub max_supply: Option, 12 | } 13 | -------------------------------------------------------------------------------- /mpl_token_metadata/src/mpl_token_metadata/instruction/escrow.rs: -------------------------------------------------------------------------------- 1 | use borsh::{BorshDeserialize, BorshSerialize}; 2 | 3 | #[derive(BorshSerialize, BorshDeserialize, PartialEq, Eq, Debug, Clone)] 4 | pub struct TransferOutOfEscrowArgs { 5 | pub amount: u64, 6 | } 7 | -------------------------------------------------------------------------------- /mpl_token_metadata/src/mpl_token_metadata/instruction/state.rs: -------------------------------------------------------------------------------- 1 | use borsh::BorshDeserialize; 2 | use super::super::processor::AuthorizationData; 3 | 4 | #[derive(BorshDeserialize, PartialEq, Eq, Debug, Clone)] 5 | pub enum LockArgs { 6 | V1 { 7 | /// Required authorization data to validate the request. 8 | authorization_data: Option, 9 | }, 10 | } 11 | 12 | #[derive(BorshDeserialize, PartialEq, Eq, Debug, Clone)] 13 | pub enum UnlockArgs { 14 | V1 { 15 | /// Required authorization data to validate the request. 16 | authorization_data: Option, 17 | }, 18 | } 19 | -------------------------------------------------------------------------------- /mpl_token_metadata/src/mpl_token_metadata/instruction/uses.rs: -------------------------------------------------------------------------------- 1 | use borsh::BorshDeserialize; 2 | use super::super::processor::AuthorizationData; 3 | 4 | #[derive(BorshDeserialize, PartialEq, Eq, Debug, Clone)] 5 | pub struct ApproveUseAuthorityArgs { 6 | pub number_of_uses: u64, 7 | } 8 | 9 | #[derive(BorshDeserialize, PartialEq, Eq, Debug, Clone)] 10 | pub struct UtilizeArgs { 11 | pub number_of_uses: u64, 12 | } 13 | 14 | #[derive(BorshDeserialize, PartialEq, Eq, Debug, Clone)] 15 | pub enum UseArgs { 16 | V1 { 17 | /// Required authorization data to validate the request. 18 | authorization_data: Option, 19 | }, 20 | } 21 | -------------------------------------------------------------------------------- /mpl_token_metadata/src/mpl_token_metadata/instruction/verification.rs: -------------------------------------------------------------------------------- 1 | use borsh::{BorshDeserialize, BorshSerialize}; 2 | #[cfg(feature = "serde-feature")] 3 | use serde::{Deserialize, Serialize}; 4 | // use solana_program::instruction::{AccountMeta, Instruction}; 5 | 6 | // use super::InstructionBuilder; 7 | // use crate::instruction::MetadataInstruction; 8 | 9 | #[repr(C)] 10 | #[cfg_attr(feature = "serde-feature", derive(Serialize, Deserialize))] 11 | #[derive(BorshSerialize, BorshDeserialize, PartialEq, Eq, Debug, Clone)] 12 | pub enum VerificationArgs { 13 | CreatorV1, 14 | CollectionV1, 15 | } 16 | 17 | // /// Verifies a creator or collection for an asset. 18 | // /// 19 | // /// # Accounts: 20 | // /// 21 | // /// 0. `[signer]` Creator to verify, collection update authority or delegate 22 | // /// 1. `[optional]` Delegate record PDA 23 | // /// 2. `[writable]` Metadata account 24 | // /// 3. `[optional]` Mint of the Collection 25 | // /// 4. `[optional, writable]` Metadata Account of the Collection 26 | // /// 5. `[optional]` Master Edition Account of the Collection Token 27 | // /// 6. `[]` System program 28 | // /// 7. `[]` Instructions sysvar account 29 | // impl InstructionBuilder for super::builders::Verify { 30 | // fn instruction(&self) -> solana_program::instruction::Instruction { 31 | // let accounts = vec![ 32 | // AccountMeta::new_readonly(self.authority, true), 33 | // AccountMeta::new_readonly(self.delegate_record.unwrap_or(crate::ID), false), 34 | // AccountMeta::new(self.metadata, false), 35 | // AccountMeta::new_readonly(self.collection_mint.unwrap_or(crate::ID), false), 36 | // if let Some(collection_metadata) = self.collection_metadata { 37 | // AccountMeta::new(collection_metadata, false) 38 | // } else { 39 | // AccountMeta::new_readonly(crate::ID, false) 40 | // }, 41 | // AccountMeta::new_readonly(self.collection_master_edition.unwrap_or(crate::ID), false), 42 | // AccountMeta::new_readonly(self.system_program, false), 43 | // AccountMeta::new_readonly(self.sysvar_instructions, false), 44 | // ]; 45 | 46 | // Instruction { 47 | // program_id: crate::ID, 48 | // accounts, 49 | // data: MetadataInstruction::Verify(self.args.clone()) 50 | // .try_to_vec() 51 | // .unwrap(), 52 | // } 53 | // } 54 | // } 55 | 56 | // /// Unverifies a creator or collection for an asset. 57 | // /// 58 | // /// # Accounts: 59 | // /// 60 | // /// 0. `[signer]` Creator to verify, collection (or metadata if parent burned) update authority or delegate 61 | // /// 1. `[optional]` Delegate record PDA 62 | // /// 2. `[writable]` Metadata account 63 | // /// 3. `[optional]` Mint of the Collection 64 | // /// 4. `[optional, writable]` Metadata Account of the Collection 65 | // /// 5. `[]` System program 66 | // /// 6. `[]` Instructions sysvar account 67 | // impl InstructionBuilder for super::builders::Unverify { 68 | // fn instruction(&self) -> solana_program::instruction::Instruction { 69 | // let accounts = vec![ 70 | // AccountMeta::new_readonly(self.authority, true), 71 | // AccountMeta::new_readonly(self.delegate_record.unwrap_or(crate::ID), false), 72 | // AccountMeta::new(self.metadata, false), 73 | // AccountMeta::new_readonly(self.collection_mint.unwrap_or(crate::ID), false), 74 | // if let Some(collection_metadata) = self.collection_metadata { 75 | // AccountMeta::new(collection_metadata, false) 76 | // } else { 77 | // AccountMeta::new_readonly(crate::ID, false) 78 | // }, 79 | // AccountMeta::new_readonly(self.system_program, false), 80 | // AccountMeta::new_readonly(self.sysvar_instructions, false), 81 | // ]; 82 | 83 | // Instruction { 84 | // program_id: crate::ID, 85 | // accounts, 86 | // data: MetadataInstruction::Unverify(self.args.clone()) 87 | // .try_to_vec() 88 | // .unwrap(), 89 | // } 90 | // } 91 | // } 92 | -------------------------------------------------------------------------------- /mpl_token_metadata/src/mpl_token_metadata/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod constants; 2 | pub mod instruction; 3 | pub mod processor; 4 | pub mod error; 5 | pub mod state; 6 | pub mod utils; 7 | -------------------------------------------------------------------------------- /mpl_token_metadata/src/mpl_token_metadata/processor/burn/burn_edition_nft.rs: -------------------------------------------------------------------------------- 1 | use mpl_utils::assert_signer; 2 | use solana_program::{account_info::AccountInfo, entrypoint::ProgramResult, pubkey::Pubkey}; 3 | use spl_token_2022::state::Account as TokenAccount; 4 | 5 | use super::nonfungible_edition::burn_nonfungible_edition; 6 | use crate::{ 7 | assertions::assert_owned_by, 8 | error::MetadataError, 9 | instruction::{Burn, Context}, 10 | processor::all_account_infos, 11 | state::{Metadata, TokenMetadataAccount, TokenStandard}, 12 | utils::{assert_initialized, SPL_TOKEN_ID}, 13 | }; 14 | 15 | pub fn process_burn_edition_nft<'a>( 16 | program_id: &Pubkey, 17 | accounts: &'a [AccountInfo<'a>], 18 | ) -> ProgramResult { 19 | all_account_infos!( 20 | accounts, 21 | metadata_info, 22 | owner_info, 23 | print_edition_mint_info, 24 | master_edition_mint_info, 25 | print_edition_token_info, 26 | master_edition_token_info, 27 | master_edition_info, 28 | print_edition_info, 29 | edition_marker_info, 30 | spl_token_program_info 31 | ); 32 | 33 | // Validate accounts 34 | // Owner is a signer. 35 | assert_signer(owner_info)?; 36 | 37 | // Owned by token-metadata program. 38 | assert_owned_by(metadata_info, program_id)?; 39 | assert_owned_by(master_edition_info, program_id)?; 40 | assert_owned_by(print_edition_info, program_id)?; 41 | assert_owned_by(edition_marker_info, program_id)?; 42 | 43 | // Owned by spl-token program. 44 | assert_owned_by(master_edition_mint_info, &SPL_TOKEN_ID)?; 45 | assert_owned_by(master_edition_token_info, &SPL_TOKEN_ID)?; 46 | assert_owned_by(print_edition_mint_info, &SPL_TOKEN_ID)?; 47 | assert_owned_by(print_edition_token_info, &SPL_TOKEN_ID)?; 48 | 49 | let metadata = Metadata::from_account_info(metadata_info)?; 50 | let token: TokenAccount = assert_initialized(print_edition_token_info)?; 51 | 52 | // Validate relationships between accounts. 53 | 54 | // Owner passed in matches the owner of the token account. 55 | if token.owner != *owner_info.key { 56 | return Err(MetadataError::InvalidOwner.into()); 57 | } 58 | 59 | // Mint account passed in matches the mint of the token account. 60 | if &token.mint != print_edition_mint_info.key { 61 | return Err(MetadataError::MintMismatch.into()); 62 | } 63 | 64 | // Token account must have sufficient balance for burn. 65 | if token.amount != 1 { 66 | return Err(MetadataError::InsufficientTokenBalance.into()); 67 | } 68 | 69 | // Metadata account must match the mint. 70 | if token.mint != metadata.mint { 71 | return Err(MetadataError::MintMismatch.into()); 72 | } 73 | // Contruct our new Burn handler context so we can re-use the same code for both. 74 | let accounts = Burn { 75 | authority_info: owner_info, 76 | collection_metadata_info: None, 77 | metadata_info, 78 | edition_info: Some(print_edition_info), 79 | mint_info: print_edition_mint_info, 80 | token_info: print_edition_token_info, 81 | master_edition_info: Some(master_edition_info), 82 | master_edition_mint_info: Some(master_edition_mint_info), 83 | master_edition_token_info: Some(master_edition_token_info), 84 | edition_marker_info: Some(edition_marker_info), 85 | token_record_info: None, 86 | // This handler doesn't get system program and sysvars instructions 87 | // but we need them to create the Burn struct. They are not used in the burn_nonfungible_edition handler. 88 | system_program_info: spl_token_program_info, 89 | sysvar_instructions_info: spl_token_program_info, 90 | spl_token_program_info, 91 | }; 92 | let context = Context { accounts }; 93 | 94 | burn_nonfungible_edition(&context, false, &TokenStandard::NonFungibleEdition) 95 | } 96 | -------------------------------------------------------------------------------- /mpl_token_metadata/src/mpl_token_metadata/processor/burn/burn_nft.rs: -------------------------------------------------------------------------------- 1 | use mpl_utils::assert_signer; 2 | use solana_program::{ 3 | account_info::{next_account_info, AccountInfo}, 4 | entrypoint::ProgramResult, 5 | program_error::ProgramError, 6 | pubkey::Pubkey, 7 | }; 8 | use spl_token_2022::state::Account; 9 | 10 | use super::{ 11 | nonfungible::{burn_nonfungible, BurnNonFungibleArgs}, 12 | *, 13 | }; 14 | use crate::{ 15 | assertions::assert_owned_by, 16 | instruction::{Burn, Context}, 17 | state::{Metadata, TokenMetadataAccount}, 18 | utils::{unpack_initialized, SPL_TOKEN_ID}, 19 | }; 20 | 21 | pub fn process_burn_nft<'a>(program_id: &Pubkey, accounts: &'a [AccountInfo<'a>]) -> ProgramResult { 22 | let account_info_iter = &mut accounts.iter(); 23 | 24 | let metadata_info = next_account_info(account_info_iter)?; 25 | let owner_info = next_account_info(account_info_iter)?; 26 | let mint_info = next_account_info(account_info_iter)?; 27 | let token_info = next_account_info(account_info_iter)?; 28 | let edition_info = next_account_info(account_info_iter)?; 29 | let spl_token_program_info = next_account_info(account_info_iter)?; 30 | 31 | let collection_metadata_info = account_info_iter.next(); 32 | 33 | // Validate accounts 34 | 35 | // Assert signer 36 | assert_signer(owner_info)?; 37 | 38 | // Assert program ownership. 39 | assert_owned_by(metadata_info, program_id)?; 40 | assert_owned_by(edition_info, program_id)?; 41 | assert_owned_by(mint_info, &SPL_TOKEN_ID)?; 42 | assert_owned_by(token_info, &SPL_TOKEN_ID)?; 43 | 44 | // Check program IDs. 45 | if spl_token_program_info.key != &SPL_TOKEN_ID { 46 | return Err(ProgramError::IncorrectProgramId); 47 | } 48 | 49 | // Deserialize accounts. 50 | let metadata = Metadata::from_account_info(metadata_info)?; 51 | let token = unpack_initialized::(&token_info.data.borrow())?; 52 | 53 | // Validate relationships between accounts. 54 | 55 | // Owner passed in matches the owner of the token account. 56 | if token.owner != *owner_info.key { 57 | return Err(MetadataError::InvalidOwner.into()); 58 | } 59 | 60 | // Mint account passed in matches the mint of the token account. 61 | if &token.mint != mint_info.key { 62 | return Err(MetadataError::MintMismatch.into()); 63 | } 64 | 65 | // Token account must have sufficient balance for burn. 66 | if token.amount != 1 { 67 | return Err(MetadataError::InsufficientTokenBalance.into()); 68 | } 69 | 70 | // Metadata account must match the mint. 71 | if token.mint != metadata.mint { 72 | return Err(MetadataError::MintMismatch.into()); 73 | } 74 | 75 | // Contruct our new Burn handler context so we can re-use the same code for both. 76 | let accounts = Burn { 77 | authority_info: owner_info, 78 | collection_metadata_info, 79 | metadata_info, 80 | edition_info: Some(edition_info), 81 | mint_info, 82 | token_info, 83 | master_edition_info: None, 84 | master_edition_mint_info: None, 85 | master_edition_token_info: None, 86 | edition_marker_info: None, 87 | token_record_info: None, 88 | // This handler doesn't get system program and sysvars instructions 89 | // but we need them to create the Burn struct. They are not used in the burn_nonfungible handler. 90 | system_program_info: spl_token_program_info, 91 | sysvar_instructions_info: spl_token_program_info, 92 | spl_token_program_info, 93 | }; 94 | let context = Context { accounts }; 95 | 96 | let args = BurnNonFungibleArgs { 97 | metadata, 98 | me_close_authority: false, 99 | }; 100 | burn_nonfungible(&context, args) 101 | } 102 | -------------------------------------------------------------------------------- /mpl_token_metadata/src/mpl_token_metadata/processor/burn/fungible.rs: -------------------------------------------------------------------------------- 1 | use mpl_utils::token::{spl_token_burn, spl_token_close, TokenBurnParams, TokenCloseParams}; 2 | use solana_program::entrypoint::ProgramResult; 3 | use spl_token_2022::state::Account; 4 | 5 | use crate::{ 6 | error::MetadataError, 7 | instruction::{Burn, Context}, 8 | utils::unpack, 9 | }; 10 | 11 | pub(crate) fn burn_fungible(ctx: &Context, amount: u64) -> ProgramResult { 12 | let token = unpack::(&ctx.accounts.token_info.data.borrow())?; 13 | 14 | if amount > token.amount { 15 | return Err(MetadataError::InsufficientTokenBalance.into()); 16 | } 17 | 18 | // Burn the SPL tokens 19 | let params = TokenBurnParams { 20 | mint: ctx.accounts.mint_info.clone(), 21 | source: ctx.accounts.token_info.clone(), 22 | authority: ctx.accounts.authority_info.clone(), 23 | token_program: ctx.accounts.spl_token_program_info.clone(), 24 | amount, 25 | authority_signer_seeds: None, 26 | }; 27 | spl_token_burn(params)?; 28 | 29 | if amount == token.amount { 30 | // Close token account. 31 | let params = TokenCloseParams { 32 | token_program: ctx.accounts.spl_token_program_info.clone(), 33 | account: ctx.accounts.token_info.clone(), 34 | destination: ctx.accounts.authority_info.clone(), 35 | owner: ctx.accounts.authority_info.clone(), 36 | authority_signer_seeds: None, 37 | }; 38 | spl_token_close(params)?; 39 | } 40 | 41 | Ok(()) 42 | } 43 | -------------------------------------------------------------------------------- /mpl_token_metadata/src/mpl_token_metadata/processor/burn/mod.rs: -------------------------------------------------------------------------------- 1 | #![allow(clippy::module_inception)] 2 | 3 | use arrayref::array_ref; 4 | use mpl_utils::{ 5 | assert_signer, 6 | token::{ 7 | get_mint_decimals, get_mint_supply, spl_token_burn, spl_token_close, TokenBurnParams, 8 | TokenCloseParams, 9 | }, 10 | }; 11 | use solana_program::{ 12 | account_info::AccountInfo, entrypoint::ProgramResult, program_error::ProgramError, 13 | pubkey::Pubkey, system_program, sysvar, 14 | }; 15 | 16 | use crate::{ 17 | error::MetadataError, 18 | instruction::{Burn, BurnArgs, Context}, 19 | pda::{find_metadata_account, EDITION, PREFIX}, 20 | processor::burn::nonfungible::{burn_nonfungible, BurnNonFungibleArgs}, 21 | state::{Edition, EditionMarker, Key, Metadata, TokenMetadataAccount, TokenStandard}, 22 | utils::{ 23 | assert_derivation, assert_owned_by, close_program_account, decrement_collection_size, 24 | is_master_edition, is_print_edition, 25 | }, 26 | }; 27 | 28 | mod burn; 29 | mod burn_edition_nft; 30 | mod burn_nft; 31 | mod fungible; 32 | mod nonfungible; 33 | mod nonfungible_edition; 34 | 35 | pub use burn::*; 36 | pub use burn_edition_nft::*; 37 | pub use burn_nft::*; 38 | -------------------------------------------------------------------------------- /mpl_token_metadata/src/mpl_token_metadata/processor/collection/approve_collection_authority.rs: -------------------------------------------------------------------------------- 1 | use mpl_utils::{assert_signer, create_or_allocate_account_raw}; 2 | use solana_program::{account_info::AccountInfo, entrypoint::ProgramResult, pubkey::Pubkey}; 3 | 4 | use crate::{ 5 | assertions::{assert_derivation, assert_owned_by}, 6 | error::MetadataError, 7 | processor::all_account_infos, 8 | state::{ 9 | CollectionAuthorityRecord, Key, Metadata, TokenMetadataAccount, COLLECTION_AUTHORITY, 10 | COLLECTION_AUTHORITY_RECORD_SIZE, PREFIX, 11 | }, 12 | utils::SPL_TOKEN_ID, 13 | }; 14 | 15 | pub fn process_approve_collection_authority( 16 | program_id: &Pubkey, 17 | accounts: &[AccountInfo], 18 | ) -> ProgramResult { 19 | all_account_infos!( 20 | accounts, 21 | collection_authority_record, 22 | new_collection_authority, 23 | update_authority, 24 | payer, 25 | metadata_info, 26 | mint_info, 27 | system_account_info 28 | ); 29 | 30 | let metadata = Metadata::from_account_info(metadata_info)?; 31 | assert_owned_by(metadata_info, program_id)?; 32 | assert_owned_by(mint_info, &SPL_TOKEN_ID)?; 33 | assert_signer(update_authority)?; 34 | assert_signer(payer)?; 35 | if metadata.update_authority != *update_authority.key { 36 | return Err(MetadataError::UpdateAuthorityIncorrect.into()); 37 | } 38 | if metadata.mint != *mint_info.key { 39 | return Err(MetadataError::MintMismatch.into()); 40 | } 41 | let collection_authority_info_empty = collection_authority_record.try_data_is_empty()?; 42 | if !collection_authority_info_empty { 43 | return Err(MetadataError::CollectionAuthorityRecordAlreadyExists.into()); 44 | } 45 | let collection_authority_path = Vec::from([ 46 | PREFIX.as_bytes(), 47 | program_id.as_ref(), 48 | mint_info.key.as_ref(), 49 | COLLECTION_AUTHORITY.as_bytes(), 50 | new_collection_authority.key.as_ref(), 51 | ]); 52 | let collection_authority_bump_seed = &[assert_derivation( 53 | program_id, 54 | collection_authority_record, 55 | &collection_authority_path, 56 | )?]; 57 | let mut collection_authority_seeds = collection_authority_path.clone(); 58 | collection_authority_seeds.push(collection_authority_bump_seed); 59 | create_or_allocate_account_raw( 60 | *program_id, 61 | collection_authority_record, 62 | system_account_info, 63 | payer, 64 | COLLECTION_AUTHORITY_RECORD_SIZE, 65 | &collection_authority_seeds, 66 | )?; 67 | 68 | let mut record = CollectionAuthorityRecord::from_account_info(collection_authority_record)?; 69 | record.key = Key::CollectionAuthorityRecord; 70 | record.bump = collection_authority_bump_seed[0]; 71 | record.update_authority = Some(*update_authority.key); 72 | borsh::to_writer( 73 | &mut collection_authority_record.try_borrow_mut_data()?[..], 74 | &record, 75 | )?; 76 | Ok(()) 77 | } 78 | -------------------------------------------------------------------------------- /mpl_token_metadata/src/mpl_token_metadata/processor/collection/mod.rs: -------------------------------------------------------------------------------- 1 | mod approve_collection_authority; 2 | mod revoke_collection_authority; 3 | mod set_and_verify_collection; 4 | mod set_and_verify_sized_collection_item; 5 | mod set_collection_size; 6 | mod unverify_collection; 7 | mod unverify_sized_collection_item; 8 | mod verify_collection; 9 | mod verify_sized_collection_item; 10 | 11 | pub use approve_collection_authority::*; 12 | pub use revoke_collection_authority::*; 13 | pub use set_and_verify_collection::*; 14 | pub use set_and_verify_sized_collection_item::*; 15 | pub use set_collection_size::*; 16 | pub use unverify_collection::*; 17 | pub use unverify_sized_collection_item::*; 18 | pub use verify_collection::*; 19 | pub use verify_sized_collection_item::*; 20 | -------------------------------------------------------------------------------- /mpl_token_metadata/src/mpl_token_metadata/processor/collection/revoke_collection_authority.rs: -------------------------------------------------------------------------------- 1 | use mpl_utils::assert_signer; 2 | use solana_program::{account_info::AccountInfo, entrypoint::ProgramResult, pubkey::Pubkey}; 3 | 4 | use crate::{ 5 | assertions::{assert_owned_by, collection::assert_is_collection_delegated_authority}, 6 | error::MetadataError, 7 | processor::all_account_infos, 8 | state::{Key, Metadata, TokenMetadataAccount}, 9 | utils::{close_program_account, SPL_TOKEN_ID}, 10 | }; 11 | 12 | pub fn process_revoke_collection_authority( 13 | program_id: &Pubkey, 14 | accounts: &[AccountInfo], 15 | ) -> ProgramResult { 16 | all_account_infos!( 17 | accounts, 18 | collection_authority_record, 19 | delegate_authority, 20 | revoke_authority, 21 | metadata_info, 22 | mint_info 23 | ); 24 | let metadata = Metadata::from_account_info(metadata_info)?; 25 | 26 | assert_owned_by(metadata_info, program_id)?; 27 | assert_owned_by(mint_info, &SPL_TOKEN_ID)?; 28 | assert_signer(revoke_authority)?; 29 | 30 | if metadata.update_authority != *revoke_authority.key 31 | && *delegate_authority.key != *revoke_authority.key 32 | { 33 | return Err(MetadataError::RevokeCollectionAuthoritySignerIncorrect.into()); 34 | } 35 | 36 | if metadata.mint != *mint_info.key { 37 | return Err(MetadataError::MintMismatch.into()); 38 | } 39 | 40 | if collection_authority_record.try_data_is_empty()? { 41 | return Err(MetadataError::CollectionAuthorityDoesNotExist.into()); 42 | } 43 | 44 | assert_is_collection_delegated_authority( 45 | collection_authority_record, 46 | delegate_authority.key, 47 | mint_info.key, 48 | )?; 49 | 50 | close_program_account( 51 | collection_authority_record, 52 | revoke_authority, 53 | Key::CollectionAuthorityRecord, 54 | ) 55 | } 56 | -------------------------------------------------------------------------------- /mpl_token_metadata/src/mpl_token_metadata/processor/collection/set_and_verify_collection.rs: -------------------------------------------------------------------------------- 1 | use mpl_utils::assert_signer; 2 | use solana_program::{ 3 | account_info::{next_account_info, AccountInfo}, 4 | entrypoint::ProgramResult, 5 | pubkey::Pubkey, 6 | }; 7 | 8 | use crate::{ 9 | assertions::{ 10 | assert_owned_by, 11 | collection::{assert_collection_verify_is_valid, assert_has_collection_authority}, 12 | }, 13 | error::MetadataError, 14 | state::{Collection, Metadata, TokenMetadataAccount}, 15 | utils::{clean_write_metadata, SPL_TOKEN_ID}, 16 | }; 17 | 18 | pub fn set_and_verify_collection(program_id: &Pubkey, accounts: &[AccountInfo]) -> ProgramResult { 19 | let account_info_iter = &mut accounts.iter(); 20 | 21 | let metadata_info = next_account_info(account_info_iter)?; 22 | let collection_authority_info = next_account_info(account_info_iter)?; 23 | let payer_info = next_account_info(account_info_iter)?; 24 | let update_authority = next_account_info(account_info_iter)?; 25 | let collection_mint = next_account_info(account_info_iter)?; 26 | let collection_info = next_account_info(account_info_iter)?; 27 | let edition_account_info = next_account_info(account_info_iter)?; 28 | 29 | assert_signer(collection_authority_info)?; 30 | assert_signer(payer_info)?; 31 | 32 | assert_owned_by(metadata_info, program_id)?; 33 | assert_owned_by(collection_info, program_id)?; 34 | assert_owned_by(collection_mint, &SPL_TOKEN_ID)?; 35 | assert_owned_by(edition_account_info, program_id)?; 36 | 37 | let mut metadata = Metadata::from_account_info(metadata_info)?; 38 | let collection_data = Metadata::from_account_info(collection_info)?; 39 | if metadata.update_authority != *update_authority.key 40 | || metadata.update_authority != collection_data.update_authority 41 | { 42 | return Err(MetadataError::UpdateAuthorityIncorrect.into()); 43 | } 44 | 45 | // If it's a verified item and the user is trying to move it to a new collection, 46 | // they must unverify first, in case it belongs to a sized collection. 47 | if let Some(collection) = metadata.collection { 48 | if collection.key != *collection_mint.key && collection.verified { 49 | return Err(MetadataError::MustUnverify.into()); 50 | } 51 | } 52 | 53 | let delegated_collection_authority_opt = account_info_iter.next(); 54 | 55 | assert_has_collection_authority( 56 | collection_authority_info, 57 | &collection_data, 58 | collection_mint.key, 59 | delegated_collection_authority_opt, 60 | )?; 61 | 62 | metadata.collection = Some(Collection { 63 | key: *collection_mint.key, 64 | verified: true, 65 | }); 66 | assert_collection_verify_is_valid( 67 | &metadata.collection, 68 | &collection_data, 69 | collection_mint, 70 | edition_account_info, 71 | )?; 72 | 73 | // This handler can only verify non-sized NFTs 74 | if collection_data.collection_details.is_some() { 75 | return Err(MetadataError::SizedCollection.into()); 76 | } 77 | 78 | clean_write_metadata(&mut metadata, metadata_info) 79 | } 80 | -------------------------------------------------------------------------------- /mpl_token_metadata/src/mpl_token_metadata/processor/collection/set_and_verify_sized_collection_item.rs: -------------------------------------------------------------------------------- 1 | use mpl_utils::assert_signer; 2 | use solana_program::{ 3 | account_info::{next_account_info, AccountInfo}, 4 | entrypoint::ProgramResult, 5 | pubkey::Pubkey, 6 | }; 7 | 8 | use crate::{ 9 | assertions::{ 10 | assert_owned_by, 11 | collection::{assert_collection_verify_is_valid, assert_has_collection_authority}, 12 | }, 13 | error::MetadataError, 14 | state::{Collection, Metadata, TokenMetadataAccount}, 15 | utils::{clean_write_metadata, increment_collection_size, SPL_TOKEN_ID}, 16 | }; 17 | 18 | pub fn set_and_verify_sized_collection_item( 19 | program_id: &Pubkey, 20 | accounts: &[AccountInfo], 21 | ) -> ProgramResult { 22 | let account_info_iter = &mut accounts.iter(); 23 | 24 | let metadata_info = next_account_info(account_info_iter)?; 25 | let collection_authority_info = next_account_info(account_info_iter)?; 26 | let payer_info = next_account_info(account_info_iter)?; 27 | let update_authority = next_account_info(account_info_iter)?; 28 | let collection_mint = next_account_info(account_info_iter)?; 29 | let collection_info = next_account_info(account_info_iter)?; 30 | let edition_account_info = next_account_info(account_info_iter)?; 31 | 32 | assert_signer(collection_authority_info)?; 33 | assert_signer(payer_info)?; 34 | 35 | assert_owned_by(metadata_info, program_id)?; 36 | assert_owned_by(collection_info, program_id)?; 37 | assert_owned_by(collection_mint, &SPL_TOKEN_ID)?; 38 | assert_owned_by(edition_account_info, program_id)?; 39 | 40 | let mut metadata = Metadata::from_account_info(metadata_info)?; 41 | let mut collection_metadata = Metadata::from_account_info(collection_info)?; 42 | 43 | // Don't verify already verified items, otherwise we end up with invalid size data. 44 | if let Some(collection) = metadata.collection { 45 | if collection.verified { 46 | return Err(MetadataError::MustUnverify.into()); 47 | } 48 | } 49 | 50 | if metadata.update_authority != *update_authority.key 51 | || metadata.update_authority != collection_metadata.update_authority 52 | { 53 | return Err(MetadataError::UpdateAuthorityIncorrect.into()); 54 | } 55 | 56 | let delegated_collection_authority_opt = account_info_iter.next(); 57 | 58 | assert_has_collection_authority( 59 | collection_authority_info, 60 | &collection_metadata, 61 | collection_mint.key, 62 | delegated_collection_authority_opt, 63 | )?; 64 | 65 | metadata.collection = Some(Collection { 66 | key: *collection_mint.key, 67 | verified: true, 68 | }); 69 | assert_collection_verify_is_valid( 70 | &metadata.collection, 71 | &collection_metadata, 72 | collection_mint, 73 | edition_account_info, 74 | )?; 75 | 76 | // Update the collection size if this is a valid parent collection NFT. 77 | increment_collection_size(&mut collection_metadata, collection_info)?; 78 | 79 | clean_write_metadata(&mut metadata, metadata_info) 80 | } 81 | -------------------------------------------------------------------------------- /mpl_token_metadata/src/mpl_token_metadata/processor/collection/set_collection_size.rs: -------------------------------------------------------------------------------- 1 | use solana_program::{ 2 | account_info::{next_account_info, AccountInfo}, 3 | entrypoint::ProgramResult, 4 | pubkey::Pubkey, 5 | }; 6 | 7 | use crate::{ 8 | assertions::{assert_owned_by, collection::assert_has_collection_authority}, 9 | error::MetadataError, 10 | instruction::SetCollectionSizeArgs, 11 | state::{CollectionDetails, Metadata, TokenMetadataAccount}, 12 | utils::{clean_write_metadata, SPL_TOKEN_ID}, 13 | }; 14 | 15 | pub fn set_collection_size( 16 | program_id: &Pubkey, 17 | accounts: &[AccountInfo], 18 | args: SetCollectionSizeArgs, 19 | ) -> ProgramResult { 20 | let size = args.size; 21 | 22 | let account_info_iter = &mut accounts.iter(); 23 | 24 | let parent_nft_metadata_account_info = next_account_info(account_info_iter)?; 25 | let collection_update_authority_account_info = next_account_info(account_info_iter)?; 26 | let collection_mint_account_info = next_account_info(account_info_iter)?; 27 | 28 | // Owned by token-metadata program. 29 | assert_owned_by(parent_nft_metadata_account_info, program_id)?; 30 | 31 | // Mint owned by spl token program. 32 | assert_owned_by(collection_mint_account_info, &SPL_TOKEN_ID)?; 33 | 34 | let mut metadata = Metadata::from_account_info(parent_nft_metadata_account_info)?; 35 | 36 | // Check that the update authority or delegate is a signer. 37 | if !collection_update_authority_account_info.is_signer { 38 | return Err(MetadataError::UpdateAuthorityIsNotSigner.into()); 39 | } 40 | 41 | let delegated_collection_authority_opt = account_info_iter.next(); 42 | 43 | assert_has_collection_authority( 44 | collection_update_authority_account_info, 45 | &metadata, 46 | collection_mint_account_info.key, 47 | delegated_collection_authority_opt, 48 | )?; 49 | 50 | // Only unsized collections can have the size set, and only once. 51 | if metadata.collection_details.is_some() { 52 | return Err(MetadataError::SizedCollection.into()); 53 | } else { 54 | metadata.collection_details = { 55 | #[allow(deprecated)] 56 | Some(CollectionDetails::V1 { size }) 57 | }; 58 | } 59 | 60 | clean_write_metadata(&mut metadata, parent_nft_metadata_account_info) 61 | } 62 | -------------------------------------------------------------------------------- /mpl_token_metadata/src/mpl_token_metadata/processor/collection/unverify_collection.rs: -------------------------------------------------------------------------------- 1 | use mpl_utils::assert_signer; 2 | use solana_program::{ 3 | account_info::{next_account_info, AccountInfo}, 4 | entrypoint::ProgramResult, 5 | pubkey::Pubkey, 6 | }; 7 | 8 | use crate::{ 9 | assertions::{ 10 | assert_owned_by, collection::assert_has_collection_authority, 11 | metadata::assert_metadata_derivation, 12 | }, 13 | error::MetadataError, 14 | state::{Metadata, TokenMetadataAccount}, 15 | utils::{clean_write_metadata, SPL_TOKEN_ID}, 16 | }; 17 | 18 | pub fn unverify_collection(program_id: &Pubkey, accounts: &[AccountInfo]) -> ProgramResult { 19 | let account_info_iter = &mut accounts.iter(); 20 | 21 | let metadata_info = next_account_info(account_info_iter)?; 22 | let collection_authority_info = next_account_info(account_info_iter)?; 23 | let collection_mint_info = next_account_info(account_info_iter)?; 24 | let collection_metadata_info = next_account_info(account_info_iter)?; 25 | let _edition_account_info = next_account_info(account_info_iter)?; 26 | 27 | // Account validation. 28 | assert_owned_by(metadata_info, program_id)?; 29 | assert_signer(collection_authority_info)?; 30 | assert_owned_by(collection_mint_info, &SPL_TOKEN_ID)?; 31 | 32 | // Deserialize the collection item metadata. 33 | let mut metadata = Metadata::from_account_info(metadata_info)?; 34 | 35 | // First, if there's no collection set, we can just short-circuit 36 | // since there's nothing to unverify. 37 | let collection = match metadata.collection.as_mut() { 38 | Some(collection) => collection, 39 | None => return Ok(()), 40 | }; 41 | 42 | // Short-circuit if it's already unverified. 43 | if !collection.verified { 44 | return Ok(()); 45 | } 46 | 47 | // The collection parent must be the actual parent of the 48 | // collection item. 49 | if collection.key != *collection_mint_info.key { 50 | return Err(MetadataError::NotAMemberOfCollection.into()); 51 | } 52 | 53 | // We need to ensure the metadata is derived from the mint so 54 | // someone cannot pass in a burned metadata account associated with 55 | // a different mint. 56 | assert_metadata_derivation(program_id, collection_metadata_info, collection_mint_info)?; 57 | 58 | // Check if the collection metadata account is burned. If it is, 59 | // there's no sized data to update and the user can simply unverify 60 | // the NFT. 61 | // 62 | // This check needs to happen before the program owned check. 63 | let parent_burned = 64 | collection_metadata_info.data_is_empty() || collection_metadata_info.data.borrow()[0] == 0; 65 | 66 | if parent_burned { 67 | // If the parent is burned, we need to check that the authority 68 | // is the update authority on the item metadata. 69 | // 70 | // Collection Delegates for burned collection parents should not be 71 | // respected as there's currently no way to revoke them. 72 | 73 | if metadata.update_authority != *collection_authority_info.key { 74 | return Err(MetadataError::UpdateAuthorityIncorrect.into()); 75 | } 76 | } else { 77 | // If the parent is not burned, we need to ensure the collection 78 | // metadata and edition accounts are owned by the token metadata program. 79 | assert_owned_by(collection_metadata_info, program_id)?; 80 | 81 | // Now we can deserialize the collection metadata account. 82 | let collection_metadata = Metadata::from_account_info(collection_metadata_info)?; 83 | 84 | // This handler can only unverify non-sized NFTs 85 | if collection_metadata.collection_details.is_some() { 86 | return Err(MetadataError::SizedCollection.into()); 87 | } 88 | 89 | let delegated_collection_authority_opt = account_info_iter.next(); 90 | 91 | assert_has_collection_authority( 92 | collection_authority_info, 93 | &collection_metadata, 94 | collection_mint_info.key, 95 | delegated_collection_authority_opt, 96 | )?; 97 | } 98 | 99 | // Unverify and update the metadata 100 | collection.verified = false; 101 | clean_write_metadata(&mut metadata, metadata_info) 102 | } 103 | -------------------------------------------------------------------------------- /mpl_token_metadata/src/mpl_token_metadata/processor/collection/unverify_sized_collection_item.rs: -------------------------------------------------------------------------------- 1 | use mpl_utils::assert_signer; 2 | use solana_program::{ 3 | account_info::{next_account_info, AccountInfo}, 4 | entrypoint::ProgramResult, 5 | pubkey::Pubkey, 6 | }; 7 | 8 | use crate::{ 9 | assertions::{ 10 | assert_owned_by, collection::assert_has_collection_authority, 11 | metadata::assert_metadata_derivation, 12 | }, 13 | error::MetadataError, 14 | state::{Metadata, TokenMetadataAccount}, 15 | utils::{clean_write_metadata, decrement_collection_size, SPL_TOKEN_ID}, 16 | }; 17 | 18 | pub fn unverify_sized_collection_item( 19 | program_id: &Pubkey, 20 | accounts: &[AccountInfo], 21 | ) -> ProgramResult { 22 | let account_info_iter = &mut accounts.iter(); 23 | 24 | let metadata_info = next_account_info(account_info_iter)?; 25 | let collection_authority_info = next_account_info(account_info_iter)?; 26 | let payer_info = next_account_info(account_info_iter)?; 27 | let collection_mint_info = next_account_info(account_info_iter)?; 28 | let collection_metadata_info = next_account_info(account_info_iter)?; 29 | let _edition_account_info = next_account_info(account_info_iter)?; 30 | 31 | assert_signer(collection_authority_info)?; 32 | assert_signer(payer_info)?; 33 | 34 | assert_owned_by(metadata_info, program_id)?; 35 | assert_owned_by(collection_mint_info, &SPL_TOKEN_ID)?; 36 | 37 | let mut metadata = Metadata::from_account_info(metadata_info)?; 38 | 39 | // First, if there's no collection set, we can just short-circuit 40 | // since there's nothing to unverify. 41 | let collection = match metadata.collection.as_mut() { 42 | Some(collection) => collection, 43 | None => return Ok(()), 44 | }; 45 | 46 | // Short-circuit if it's already unverified. 47 | if !collection.verified { 48 | return Ok(()); 49 | } 50 | 51 | // The collection parent must be the actual parent of the 52 | // collection item. 53 | if collection.key != *collection_mint_info.key { 54 | return Err(MetadataError::NotAMemberOfCollection.into()); 55 | } 56 | 57 | assert_metadata_derivation(program_id, collection_metadata_info, collection_mint_info)?; 58 | 59 | // Check if the collection metadata account is burned. If it is, 60 | // there's no sized data to update and the user can simply unverify 61 | // the NFT. 62 | // 63 | // This check needs to happen before the program owned check. 64 | let parent_burned = 65 | collection_metadata_info.data_is_empty() || collection_metadata_info.data.borrow()[0] == 0; 66 | 67 | if parent_burned { 68 | // If the parent is burned, we need to check that the authority 69 | // is the update authority on the item metadata. 70 | // 71 | // Collection Delegates for burned collection parents should not be 72 | // respected as there's currently no way to revoke them. 73 | 74 | if metadata.update_authority != *collection_authority_info.key { 75 | return Err(MetadataError::UpdateAuthorityIncorrect.into()); 76 | } 77 | } else { 78 | // If the parent is not burned, we need to ensure the collection 79 | // metadata and edition accounts are owned by the token metadata program. 80 | assert_owned_by(collection_metadata_info, program_id)?; 81 | 82 | // Now we can deserialize the collection metadata account. 83 | let mut collection_metadata = Metadata::from_account_info(collection_metadata_info)?; 84 | 85 | let delegated_collection_authority_opt = account_info_iter.next(); 86 | 87 | assert_has_collection_authority( 88 | collection_authority_info, 89 | &collection_metadata, 90 | collection_mint_info.key, 91 | delegated_collection_authority_opt, 92 | )?; 93 | decrement_collection_size(&mut collection_metadata, collection_metadata_info)?; 94 | } 95 | 96 | collection.verified = false; 97 | clean_write_metadata(&mut metadata, metadata_info) 98 | } 99 | -------------------------------------------------------------------------------- /mpl_token_metadata/src/mpl_token_metadata/processor/collection/verify_collection.rs: -------------------------------------------------------------------------------- 1 | use mpl_utils::assert_signer; 2 | use solana_program::{ 3 | account_info::{next_account_info, AccountInfo}, 4 | entrypoint::ProgramResult, 5 | pubkey::Pubkey, 6 | }; 7 | 8 | use crate::{ 9 | assertions::{ 10 | assert_owned_by, 11 | collection::{assert_collection_verify_is_valid, assert_has_collection_authority}, 12 | }, 13 | error::MetadataError, 14 | state::{Metadata, TokenMetadataAccount}, 15 | utils::{clean_write_metadata, SPL_TOKEN_ID}, 16 | }; 17 | 18 | pub fn verify_collection(program_id: &Pubkey, accounts: &[AccountInfo]) -> ProgramResult { 19 | let account_info_iter = &mut accounts.iter(); 20 | 21 | let metadata_info = next_account_info(account_info_iter)?; 22 | let collection_authority_info = next_account_info(account_info_iter)?; 23 | let payer_info = next_account_info(account_info_iter)?; 24 | let collection_mint = next_account_info(account_info_iter)?; 25 | let collection_info = next_account_info(account_info_iter)?; 26 | let edition_account_info = next_account_info(account_info_iter)?; 27 | 28 | assert_signer(collection_authority_info)?; 29 | assert_signer(payer_info)?; 30 | 31 | assert_owned_by(metadata_info, program_id)?; 32 | assert_owned_by(collection_info, program_id)?; 33 | assert_owned_by(collection_mint, &SPL_TOKEN_ID)?; 34 | assert_owned_by(edition_account_info, program_id)?; 35 | 36 | let mut metadata = Metadata::from_account_info(metadata_info)?; 37 | let collection_metadata = Metadata::from_account_info(collection_info)?; 38 | 39 | assert_collection_verify_is_valid( 40 | &metadata.collection, 41 | &collection_metadata, 42 | collection_mint, 43 | edition_account_info, 44 | )?; 45 | 46 | let delegated_collection_authority_opt = account_info_iter.next(); 47 | 48 | assert_has_collection_authority( 49 | collection_authority_info, 50 | &collection_metadata, 51 | collection_mint.key, 52 | delegated_collection_authority_opt, 53 | )?; 54 | 55 | // This handler can only verify non-sized NFTs 56 | if collection_metadata.collection_details.is_some() { 57 | return Err(MetadataError::SizedCollection.into()); 58 | } 59 | 60 | // If the NFT has collection data, we set it to be verified 61 | if let Some(collection) = &mut metadata.collection { 62 | collection.verified = true; 63 | clean_write_metadata(&mut metadata, metadata_info)?; 64 | } 65 | 66 | Ok(()) 67 | } 68 | -------------------------------------------------------------------------------- /mpl_token_metadata/src/mpl_token_metadata/processor/collection/verify_sized_collection_item.rs: -------------------------------------------------------------------------------- 1 | use mpl_utils::assert_signer; 2 | use solana_program::{ 3 | account_info::{next_account_info, AccountInfo}, 4 | entrypoint::ProgramResult, 5 | pubkey::Pubkey, 6 | }; 7 | 8 | use crate::{ 9 | assertions::{ 10 | assert_owned_by, 11 | collection::{assert_collection_verify_is_valid, assert_has_collection_authority}, 12 | }, 13 | error::MetadataError, 14 | state::{Metadata, TokenMetadataAccount}, 15 | utils::{clean_write_metadata, increment_collection_size, SPL_TOKEN_ID}, 16 | }; 17 | 18 | pub fn verify_sized_collection_item( 19 | program_id: &Pubkey, 20 | accounts: &[AccountInfo], 21 | ) -> ProgramResult { 22 | let account_info_iter = &mut accounts.iter(); 23 | 24 | let metadata_info = next_account_info(account_info_iter)?; 25 | let collection_authority_info = next_account_info(account_info_iter)?; 26 | let payer_info = next_account_info(account_info_iter)?; 27 | let collection_mint = next_account_info(account_info_iter)?; 28 | let collection_info = next_account_info(account_info_iter)?; 29 | let edition_account_info = next_account_info(account_info_iter)?; 30 | 31 | assert_signer(collection_authority_info)?; 32 | assert_signer(payer_info)?; 33 | 34 | assert_owned_by(metadata_info, program_id)?; 35 | assert_owned_by(collection_info, program_id)?; 36 | assert_owned_by(collection_mint, &SPL_TOKEN_ID)?; 37 | assert_owned_by(edition_account_info, program_id)?; 38 | 39 | let mut metadata = Metadata::from_account_info(metadata_info)?; 40 | let mut collection_metadata = Metadata::from_account_info(collection_info)?; 41 | 42 | // Don't verify already verified items, otherwise we end up with invalid size data. 43 | if let Some(collection) = &metadata.collection { 44 | if collection.verified { 45 | return Err(MetadataError::AlreadyVerified.into()); 46 | } 47 | } 48 | 49 | assert_collection_verify_is_valid( 50 | &metadata.collection, 51 | &collection_metadata, 52 | collection_mint, 53 | edition_account_info, 54 | )?; 55 | 56 | let delegated_collection_authority_opt = account_info_iter.next(); 57 | 58 | assert_has_collection_authority( 59 | collection_authority_info, 60 | &collection_metadata, 61 | collection_mint.key, 62 | delegated_collection_authority_opt, 63 | )?; 64 | 65 | // If the NFT has unverified collection data, we set it to be verified and then update the collection 66 | // size on the Collection Parent. 67 | if let Some(collection) = &mut metadata.collection { 68 | increment_collection_size(&mut collection_metadata, collection_info)?; 69 | 70 | collection.verified = true; 71 | clean_write_metadata(&mut metadata, metadata_info)?; 72 | } else { 73 | return Err(MetadataError::CollectionNotFound.into()); 74 | } 75 | 76 | Ok(()) 77 | } 78 | -------------------------------------------------------------------------------- /mpl_token_metadata/src/mpl_token_metadata/processor/delegate/mod.rs: -------------------------------------------------------------------------------- 1 | #![allow(clippy::module_inception)] 2 | mod delegate; 3 | mod revoke; 4 | 5 | pub use delegate::*; 6 | pub use revoke::*; 7 | -------------------------------------------------------------------------------- /mpl_token_metadata/src/mpl_token_metadata/processor/edition/convert_master_edition_v1_to_v2.rs: -------------------------------------------------------------------------------- 1 | use solana_program::{account_info::AccountInfo, entrypoint::ProgramResult, pubkey::Pubkey}; 2 | use spl_token_2022::state::Mint; 3 | 4 | use crate::{ 5 | assertions::{assert_initialized, assert_owned_by}, 6 | error::MetadataError, 7 | processor::all_account_infos, 8 | state::{Key, MasterEditionV1, MasterEditionV2, TokenMetadataAccount}, 9 | utils::SPL_TOKEN_ID, 10 | }; 11 | 12 | pub fn process_convert_master_edition_v1_to_v2( 13 | program_id: &Pubkey, 14 | accounts: &[AccountInfo], 15 | ) -> ProgramResult { 16 | all_account_infos!( 17 | accounts, 18 | master_edition_info, 19 | one_time_printing_auth_mint_info, 20 | printing_mint_info 21 | ); 22 | 23 | assert_owned_by(master_edition_info, program_id)?; 24 | assert_owned_by(one_time_printing_auth_mint_info, &SPL_TOKEN_ID)?; 25 | assert_owned_by(printing_mint_info, &SPL_TOKEN_ID)?; 26 | let master_edition = MasterEditionV1::from_account_info(master_edition_info)?; 27 | let printing_mint: Mint = assert_initialized(printing_mint_info)?; 28 | let auth_mint: Mint = assert_initialized(one_time_printing_auth_mint_info)?; 29 | if master_edition.one_time_printing_authorization_mint != *one_time_printing_auth_mint_info.key 30 | { 31 | return Err(MetadataError::OneTimePrintingAuthMintMismatch.into()); 32 | } 33 | 34 | if master_edition.printing_mint != *printing_mint_info.key { 35 | return Err(MetadataError::PrintingMintMismatch.into()); 36 | } 37 | 38 | if printing_mint.supply != 0 { 39 | return Err(MetadataError::PrintingMintSupplyMustBeZeroForConversion.into()); 40 | } 41 | 42 | if auth_mint.supply != 0 { 43 | return Err(MetadataError::OneTimeAuthMintSupplyMustBeZeroForConversion.into()); 44 | } 45 | 46 | borsh::to_writer( 47 | &mut master_edition_info.try_borrow_mut_data()?[..], 48 | &MasterEditionV2 { 49 | key: Key::MasterEditionV2, 50 | supply: master_edition.supply, 51 | max_supply: master_edition.max_supply, 52 | }, 53 | )?; 54 | 55 | Ok(()) 56 | } 57 | -------------------------------------------------------------------------------- /mpl_token_metadata/src/mpl_token_metadata/processor/edition/create_master_edition_v3.rs: -------------------------------------------------------------------------------- 1 | use mpl_utils::create_or_allocate_account_raw; 2 | use solana_program::{account_info::AccountInfo, entrypoint::ProgramResult, pubkey::Pubkey}; 3 | use spl_token_2022::state::Mint; 4 | 5 | use crate::{ 6 | assertions::{ 7 | assert_derivation, assert_mint_authority_matches_mint, assert_owned_by, 8 | assert_token_program_matches_package, metadata::assert_update_authority_is_correct, 9 | }, 10 | error::MetadataError, 11 | processor::all_account_infos, 12 | state::{ 13 | Key, MasterEditionV2, Metadata, TokenMetadataAccount, TokenStandard, EDITION, 14 | MAX_MASTER_EDITION_LEN, PREFIX, 15 | }, 16 | utils::{transfer_mint_authority, unpack_initialized}, 17 | }; 18 | 19 | /// Create master edition 20 | pub fn process_create_master_edition( 21 | program_id: &Pubkey, 22 | accounts: &[AccountInfo], 23 | max_supply: Option, 24 | ) -> ProgramResult { 25 | all_account_infos!( 26 | accounts, 27 | edition_account_info, 28 | mint_info, 29 | update_authority_info, 30 | mint_authority_info, 31 | payer_account_info, 32 | metadata_account_info, 33 | token_program_info, 34 | system_account_info 35 | ); 36 | 37 | let metadata = Metadata::from_account_info(metadata_account_info)?; 38 | let mint = unpack_initialized::(&mint_info.data.borrow())?; 39 | 40 | let bump_seed = assert_derivation( 41 | program_id, 42 | edition_account_info, 43 | &[ 44 | PREFIX.as_bytes(), 45 | program_id.as_ref(), 46 | mint_info.key.as_ref(), 47 | EDITION.as_bytes(), 48 | ], 49 | )?; 50 | 51 | assert_token_program_matches_package(token_program_info)?; 52 | assert_mint_authority_matches_mint(&mint.mint_authority, mint_authority_info)?; 53 | assert_owned_by(metadata_account_info, program_id)?; 54 | assert_owned_by(mint_info, token_program_info.key)?; 55 | 56 | if metadata.mint != *mint_info.key { 57 | return Err(MetadataError::MintMismatch.into()); 58 | } 59 | 60 | if mint.decimals != 0 { 61 | return Err(MetadataError::EditionMintDecimalsShouldBeZero.into()); 62 | } 63 | 64 | assert_update_authority_is_correct(&metadata, update_authority_info)?; 65 | 66 | if mint.supply != 1 { 67 | return Err(MetadataError::EditionsMustHaveExactlyOneToken.into()); 68 | } 69 | 70 | let edition_authority_seeds = &[ 71 | PREFIX.as_bytes(), 72 | program_id.as_ref(), 73 | mint_info.key.as_ref(), 74 | EDITION.as_bytes(), 75 | &[bump_seed], 76 | ]; 77 | 78 | create_or_allocate_account_raw( 79 | *program_id, 80 | edition_account_info, 81 | system_account_info, 82 | payer_account_info, 83 | MAX_MASTER_EDITION_LEN, 84 | edition_authority_seeds, 85 | )?; 86 | 87 | let mut edition = MasterEditionV2::from_account_info(edition_account_info)?; 88 | 89 | edition.key = Key::MasterEditionV2; 90 | edition.supply = 0; 91 | edition.max_supply = max_supply; 92 | borsh::to_writer( 93 | &mut edition_account_info.try_borrow_mut_data()?[..], 94 | &edition, 95 | )?; 96 | if metadata_account_info.is_writable { 97 | let mut metadata_mut = Metadata::from_account_info(metadata_account_info)?; 98 | metadata_mut.token_standard = Some(TokenStandard::NonFungible); 99 | borsh::to_writer( 100 | &mut metadata_account_info.try_borrow_mut_data()?[..], 101 | &metadata_mut, 102 | )?; 103 | } 104 | 105 | // While you can't mint any more of your master record, you can 106 | // mint as many limited editions as you like within your max supply. 107 | transfer_mint_authority( 108 | edition_account_info.key, 109 | edition_account_info, 110 | mint_info, 111 | mint_authority_info, 112 | token_program_info, 113 | )?; 114 | 115 | Ok(()) 116 | } 117 | -------------------------------------------------------------------------------- /mpl_token_metadata/src/mpl_token_metadata/processor/edition/mint_new_edition_from_master_edition_via_token.rs: -------------------------------------------------------------------------------- 1 | use solana_program::{account_info::AccountInfo, entrypoint::ProgramResult, pubkey::Pubkey}; 2 | 3 | use crate::{ 4 | error::MetadataError, 5 | processor::all_account_infos, 6 | utils::{ 7 | fee::{levy, set_fee_flag, LevyArgs}, 8 | process_mint_new_edition_from_master_edition_via_token_logic, 9 | MintNewEditionFromMasterEditionViaTokenLogicArgs, SPL_TOKEN_ID, 10 | }, 11 | }; 12 | 13 | pub fn process_mint_new_edition_from_master_edition_via_token<'a>( 14 | program_id: &'a Pubkey, 15 | accounts: &'a [AccountInfo<'a>], 16 | edition: u64, 17 | ) -> ProgramResult { 18 | all_account_infos!( 19 | accounts, 20 | new_metadata_account_info, 21 | new_edition_account_info, 22 | master_edition_account_info, 23 | mint_info, 24 | edition_marker_info, 25 | mint_authority_info, 26 | payer_account_info, 27 | owner_account_info, 28 | token_account_info, 29 | update_authority_info, 30 | master_metadata_account_info, 31 | token_program_account_info, 32 | system_account_info 33 | ); 34 | 35 | // only support SPL tokens 36 | if *token_program_account_info.key != SPL_TOKEN_ID { 37 | return Err(MetadataError::InvalidTokenProgram.into()); 38 | } 39 | 40 | // Levy fees first, to fund the metadata account with rent + fee amount. 41 | levy(LevyArgs { 42 | payer_account_info, 43 | token_metadata_pda_info: new_metadata_account_info, 44 | })?; 45 | 46 | process_mint_new_edition_from_master_edition_via_token_logic( 47 | program_id, 48 | MintNewEditionFromMasterEditionViaTokenLogicArgs { 49 | new_metadata_account_info, 50 | new_edition_account_info, 51 | master_edition_account_info, 52 | mint_info, 53 | edition_marker_info, 54 | mint_authority_info, 55 | payer_account_info, 56 | owner_account_info, 57 | token_account_info, 58 | update_authority_info, 59 | master_metadata_account_info, 60 | token_program_account_info, 61 | system_account_info, 62 | holder_delegate_record_info: None, 63 | delegate_info: None, 64 | }, 65 | edition, 66 | )?; 67 | 68 | // Set fee flag after metadata account is created. 69 | set_fee_flag(new_metadata_account_info) 70 | } 71 | -------------------------------------------------------------------------------- /mpl_token_metadata/src/mpl_token_metadata/processor/edition/mod.rs: -------------------------------------------------------------------------------- 1 | mod convert_master_edition_v1_to_v2; 2 | mod create_master_edition_v3; 3 | mod mint_new_edition_from_master_edition_via_token; 4 | 5 | pub use convert_master_edition_v1_to_v2::*; 6 | pub use create_master_edition_v3::*; 7 | pub use mint_new_edition_from_master_edition_via_token::*; 8 | -------------------------------------------------------------------------------- /mpl_token_metadata/src/mpl_token_metadata/processor/escrow/README.md: -------------------------------------------------------------------------------- 1 | # Token Metadata Escrow 2 | ## Overview 3 | This extension of the Token Metadata contract was created as a new feature primitive that could optionally be added to all NFTs. At its core it is simply an escrow account attached to an NFT, enabling NFTs to become owners of other tokens. 4 | 5 | Aside from the requisite security and ownership checks necessary, the functionality this feature affords has been left generic enough to allow users to implement whatever they desire on top of the composability of the token and its escrow account. 6 | ## Accounts 7 | ### Escrow 8 | The main account for this feature is the escrow account attached to the NFT. This can be considered the "wallet" that the NFT owns and uses to hold its tokens. This wallet has ownership over the various ATAs that are created to hold tokens transferred into it. 9 | ## Instructions 10 | ### Create Escrow Account 11 | Create the Token Owned Escrow account. This can only be performed on NFTs. 12 | ### Close Escrow Account 13 | Close the Token Owned Escrow account. 14 | ### Transfer Out 15 | Transfers a token out of the escrow account. 16 | 17 | ## Types of Escrow Accounts 18 | ### Token Owned Escrow 19 | A Token Owned Escrow account, or TOE, is an escrow account attached the NFT that is managed by the holder of the NFT. Transferring a token out of this escrow account is only allowable by the tokens holder and the permissions follow the NFT as it is transferred between wallets. This means Alice can add a token to a TOE on her NFT, then sell her NFT to Bob. Bob would then be the only one allowed to transfer that token out of the TOE. 20 | ### Creator Owned Escrow 21 | A Creator Owned Escrow, or COE, is an escrow account attached to the NFT that is managed by a specified creator. This escrow account allows creators to make associations between tokens that they themselves can manage, regardless of sales, transfers, and holders of the base NFT. An example use case for this is Metaverse avatars. Rather than storing avatars on a Web2 server, the Metaverse team could mint the avatar for an NFT as its own NFT, then put it in a COE (that the Metaverse team manages) attached to the corresponding NFT. Because usage of the COE is locked to the creator of the COE, a holder would be unable to transfer the avatar out of the escrow account and break the association. 22 | -------------------------------------------------------------------------------- /mpl_token_metadata/src/mpl_token_metadata/processor/escrow/close_escrow_account.rs: -------------------------------------------------------------------------------- 1 | use mpl_utils::{assert_signer, close_account_raw}; 2 | use solana_program::{ 3 | account_info::AccountInfo, entrypoint::ProgramResult, pubkey::Pubkey, system_program, 4 | }; 5 | 6 | use super::find_escrow_seeds; 7 | use crate::{ 8 | assertions::{assert_derivation, assert_initialized, assert_keys_equal, assert_owned_by}, 9 | error::MetadataError, 10 | pda::{EDITION, PREFIX}, 11 | processor::all_account_infos, 12 | state::{EscrowAuthority, Metadata, TokenMetadataAccount, TokenOwnedEscrow}, 13 | utils::SPL_TOKEN_ID, 14 | }; 15 | 16 | pub fn process_close_escrow_account( 17 | _program_id: &Pubkey, 18 | accounts: &[AccountInfo], 19 | ) -> ProgramResult { 20 | all_account_infos!( 21 | accounts, 22 | escrow_account_info, 23 | metadata_account_info, 24 | mint_account_info, 25 | token_account_info, 26 | edition_account_info, 27 | payer_account_info, 28 | system_account_info 29 | ); 30 | 31 | assert_owned_by(escrow_account_info, &crate::ID)?; 32 | assert_owned_by(metadata_account_info, &crate::ID)?; 33 | assert_owned_by(mint_account_info, &SPL_TOKEN_ID)?; 34 | assert_owned_by(token_account_info, &SPL_TOKEN_ID)?; 35 | assert_owned_by(edition_account_info, &crate::ID)?; 36 | assert_signer(payer_account_info)?; 37 | 38 | if *system_account_info.key != system_program::ID { 39 | return Err(MetadataError::InvalidSystemProgram.into()); 40 | } 41 | 42 | let metadata: Metadata = Metadata::from_account_info(metadata_account_info)?; 43 | 44 | // Mint account passed in must be the mint of the metadata account passed in. 45 | if &metadata.mint != mint_account_info.key { 46 | return Err(MetadataError::MintMismatch.into()); 47 | } 48 | 49 | // Check that the edition account is for this mint. 50 | let _edition_bump = assert_derivation( 51 | &crate::ID, 52 | edition_account_info, 53 | &[ 54 | PREFIX.as_bytes(), 55 | crate::ID.as_ref(), 56 | mint_account_info.key.as_ref(), 57 | EDITION.as_bytes(), 58 | ], 59 | )?; 60 | 61 | let token_account: spl_token_2022::state::Account = assert_initialized(token_account_info)?; 62 | 63 | if token_account.mint != *mint_account_info.key { 64 | return Err(MetadataError::MintMismatch.into()); 65 | } 66 | 67 | if token_account.amount != 1 { 68 | return Err(MetadataError::NotEnoughTokens.into()); 69 | } 70 | 71 | if token_account.mint != metadata.mint { 72 | return Err(MetadataError::MintMismatch.into()); 73 | } 74 | 75 | let creator_type = if token_account.owner == *payer_account_info.key { 76 | EscrowAuthority::TokenOwner 77 | } else { 78 | EscrowAuthority::Creator(*payer_account_info.key) 79 | }; 80 | 81 | // Derive the seeds for PDA signing. 82 | let escrow_seeds = find_escrow_seeds(mint_account_info.key, &creator_type); 83 | 84 | let bump_seed = assert_derivation(&crate::ID, escrow_account_info, &escrow_seeds)?; 85 | 86 | let token_account: spl_token_2022::state::Account = assert_initialized(token_account_info)?; 87 | let toe = TokenOwnedEscrow::from_account_info(escrow_account_info)?; 88 | assert_keys_equal(&toe.base_token, mint_account_info.key)?; 89 | 90 | if bump_seed != toe.bump { 91 | return Err(MetadataError::InvalidEscrowBumpSeed.into()); 92 | } 93 | 94 | match toe.authority { 95 | EscrowAuthority::TokenOwner => { 96 | if *payer_account_info.key != token_account.owner { 97 | return Err(MetadataError::MustBeEscrowAuthority.into()); 98 | } 99 | } 100 | EscrowAuthority::Creator(authority) => { 101 | if *payer_account_info.key != authority { 102 | return Err(MetadataError::MustBeEscrowAuthority.into()); 103 | } 104 | } 105 | } 106 | 107 | // Close the account. 108 | close_account_raw(payer_account_info, escrow_account_info)?; 109 | 110 | Ok(()) 111 | } 112 | -------------------------------------------------------------------------------- /mpl_token_metadata/src/mpl_token_metadata/processor/escrow/create_escrow_account.rs: -------------------------------------------------------------------------------- 1 | use borsh::BorshSerialize; 2 | use mpl_utils::{assert_signer, create_or_allocate_account_raw}; 3 | use solana_program::{ 4 | account_info::{next_account_info, AccountInfo}, 5 | entrypoint::ProgramResult, 6 | program_memory::sol_memcpy, 7 | pubkey::Pubkey, 8 | system_program, 9 | }; 10 | 11 | use super::find_escrow_seeds; 12 | use crate::{ 13 | assertions::{assert_derivation, assert_initialized, assert_owned_by}, 14 | error::MetadataError, 15 | pda::{EDITION, PREFIX}, 16 | state::{ 17 | EscrowAuthority, Key, Metadata, TokenMetadataAccount, TokenOwnedEscrow, TokenStandard, 18 | }, 19 | utils::{check_token_standard, SPL_TOKEN_ID}, 20 | }; 21 | 22 | pub fn process_create_escrow_account( 23 | _program_id: &Pubkey, 24 | accounts: &[AccountInfo], 25 | ) -> ProgramResult { 26 | let account_info_iter = &mut accounts.iter(); 27 | 28 | let escrow_account_info = next_account_info(account_info_iter)?; 29 | if escrow_account_info.owner != &system_program::ID || !escrow_account_info.data_is_empty() { 30 | return Err(MetadataError::AlreadyInitialized.into()); 31 | } 32 | 33 | let metadata_account_info = next_account_info(account_info_iter)?; 34 | assert_owned_by(metadata_account_info, &crate::ID)?; 35 | 36 | let mint_account_info = next_account_info(account_info_iter)?; 37 | assert_owned_by(mint_account_info, &SPL_TOKEN_ID)?; 38 | 39 | let token_account_info = next_account_info(account_info_iter)?; 40 | assert_owned_by(token_account_info, &SPL_TOKEN_ID)?; 41 | 42 | let edition_account_info = next_account_info(account_info_iter)?; 43 | assert_owned_by(edition_account_info, &crate::ID)?; 44 | 45 | let payer_account_info = next_account_info(account_info_iter)?; 46 | assert_signer(payer_account_info)?; 47 | 48 | let system_account_info = next_account_info(account_info_iter)?; 49 | if *system_account_info.key != system_program::ID { 50 | return Err(MetadataError::InvalidSystemProgram.into()); 51 | } 52 | 53 | let sysvar_ix_account_info = next_account_info(account_info_iter)?; 54 | if sysvar_ix_account_info.key != &solana_program::sysvar::instructions::ID { 55 | return Err(MetadataError::InvalidInstructionsSysvar.into()); 56 | } 57 | 58 | let is_using_authority = account_info_iter.len() == 1; 59 | 60 | let maybe_authority_info: Option<&AccountInfo> = if is_using_authority { 61 | Some(next_account_info(account_info_iter)?) 62 | } else { 63 | None 64 | }; 65 | 66 | let metadata: Metadata = Metadata::from_account_info(metadata_account_info)?; 67 | 68 | // Mint account passed in must be the mint of the metadata account passed in. 69 | if &metadata.mint != mint_account_info.key { 70 | return Err(MetadataError::MintMismatch.into()); 71 | } 72 | 73 | // Only standard or programmable non-fungible tokens (i.e. unique) can have escrow accounts. 74 | let token_standard = check_token_standard(mint_account_info, Some(edition_account_info))?; 75 | if !matches!( 76 | token_standard, 77 | TokenStandard::NonFungible | TokenStandard::ProgrammableNonFungible 78 | ) { 79 | return Err(MetadataError::MustBeNonFungible.into()); 80 | } 81 | 82 | // Check that the edition account is for this mint. 83 | let _edition_bump = assert_derivation( 84 | &crate::ID, 85 | edition_account_info, 86 | &[ 87 | PREFIX.as_bytes(), 88 | crate::ID.as_ref(), 89 | mint_account_info.key.as_ref(), 90 | EDITION.as_bytes(), 91 | ], 92 | )?; 93 | 94 | let creator = maybe_authority_info.unwrap_or(payer_account_info); 95 | assert_signer(creator)?; 96 | 97 | let token_account: spl_token_2022::state::Account = assert_initialized(token_account_info)?; 98 | 99 | if token_account.mint != *mint_account_info.key { 100 | return Err(MetadataError::MintMismatch.into()); 101 | } 102 | 103 | if token_account.amount < 1 { 104 | return Err(MetadataError::NotEnoughTokens.into()); 105 | } 106 | 107 | if token_account.mint != metadata.mint { 108 | return Err(MetadataError::MintMismatch.into()); 109 | } 110 | 111 | let creator_type = if token_account.owner == *creator.key { 112 | EscrowAuthority::TokenOwner 113 | } else { 114 | EscrowAuthority::Creator(*creator.key) 115 | }; 116 | 117 | // Derive the seeds for PDA signing. 118 | let escrow_seeds = find_escrow_seeds(mint_account_info.key, &creator_type); 119 | 120 | let bump_seed = &[assert_derivation( 121 | &crate::ID, 122 | escrow_account_info, 123 | &escrow_seeds, 124 | )?]; 125 | 126 | let escrow_authority_seeds = [escrow_seeds, vec![bump_seed]].concat(); 127 | 128 | // Initialize a default (empty) escrow structure. 129 | let toe = TokenOwnedEscrow { 130 | key: Key::TokenOwnedEscrow, 131 | base_token: *mint_account_info.key, 132 | authority: creator_type, 133 | bump: bump_seed[0], 134 | }; 135 | 136 | let serialized_data = toe 137 | .try_to_vec() 138 | .map_err(|_| MetadataError::BorshSerializationError)?; 139 | 140 | // Create the account. 141 | create_or_allocate_account_raw( 142 | crate::ID, 143 | escrow_account_info, 144 | system_account_info, 145 | payer_account_info, 146 | serialized_data.len(), 147 | &escrow_authority_seeds, 148 | )?; 149 | 150 | sol_memcpy( 151 | &mut escrow_account_info.try_borrow_mut_data()?, 152 | &serialized_data, 153 | serialized_data.len(), 154 | ); 155 | 156 | Ok(()) 157 | } 158 | -------------------------------------------------------------------------------- /mpl_token_metadata/src/mpl_token_metadata/processor/escrow/mod.rs: -------------------------------------------------------------------------------- 1 | mod close_escrow_account; 2 | mod create_escrow_account; 3 | mod pda; 4 | mod transfer_out; 5 | 6 | pub use close_escrow_account::*; 7 | pub use create_escrow_account::*; 8 | pub use pda::*; 9 | pub use transfer_out::*; 10 | -------------------------------------------------------------------------------- /mpl_token_metadata/src/mpl_token_metadata/processor/escrow/pda.rs: -------------------------------------------------------------------------------- 1 | use solana_program::pubkey::Pubkey; 2 | 3 | use crate::state::{EscrowAuthority, ESCROW_POSTFIX, PREFIX}; 4 | 5 | pub fn find_escrow_seeds<'a>(mint: &'a Pubkey, authority: &'a EscrowAuthority) -> Vec<&'a [u8]> { 6 | let mut seeds = vec![PREFIX.as_bytes(), crate::ID.as_ref(), mint.as_ref()]; 7 | 8 | for seed in authority.to_seeds() { 9 | seeds.push(seed); 10 | } 11 | 12 | seeds.push(ESCROW_POSTFIX.as_bytes()); 13 | 14 | seeds 15 | } 16 | 17 | pub fn find_escrow_account(mint: &Pubkey, authority: &EscrowAuthority) -> (Pubkey, u8) { 18 | let seeds = find_escrow_seeds(mint, authority); 19 | Pubkey::find_program_address(&seeds, &crate::ID) 20 | } 21 | -------------------------------------------------------------------------------- /mpl_token_metadata/src/mpl_token_metadata/processor/fee/mod.rs: -------------------------------------------------------------------------------- 1 | use mpl_utils::assert_signer; 2 | use num_traits::FromPrimitive; 3 | use solana_program::{account_info::next_account_info, rent::Rent, system_program, sysvar::Sysvar}; 4 | 5 | use super::*; 6 | use crate::{ 7 | state::{fee::FEE_AUTHORITY, MAX_METADATA_LEN}, 8 | utils::fee::clear_fee_flag, 9 | }; 10 | 11 | pub(crate) fn process_collect_fees(program_id: &Pubkey, accounts: &[AccountInfo]) -> ProgramResult { 12 | let account_info_iter = &mut accounts.iter(); 13 | 14 | let authority_info = next_account_info(account_info_iter)?; 15 | 16 | assert_signer(authority_info)?; 17 | 18 | if *authority_info.key != FEE_AUTHORITY { 19 | return Err(MetadataError::UpdateAuthorityIncorrect.into()); 20 | } 21 | 22 | let recipient_info = next_account_info(account_info_iter)?; 23 | 24 | for account_info in account_info_iter { 25 | if account_info.owner != program_id { 26 | return Err(MetadataError::InvalidFeeAccount.into()); 27 | } 28 | 29 | collect_fee_from_account(account_info, recipient_info)?; 30 | } 31 | 32 | Ok(()) 33 | } 34 | 35 | fn collect_fee_from_account(account_info: &AccountInfo, dest_info: &AccountInfo) -> ProgramResult { 36 | // Scope refcell borrow 37 | let account_key = { 38 | let data = account_info.data.borrow(); 39 | 40 | // Burned accounts with fees will have no data, so should be assigned the `Uninitialized` key. 41 | let key_byte = data.first().unwrap_or(&0); 42 | 43 | FromPrimitive::from_u8(*key_byte).ok_or(MetadataError::InvalidFeeAccount)? 44 | }; 45 | 46 | let rent = Rent::get()?; 47 | let metadata_rent = rent.minimum_balance(MAX_METADATA_LEN); 48 | 49 | let (fee_amount, rent_amount) = match account_key { 50 | Key::Uninitialized => { 51 | account_info.assign(&system_program::ID); 52 | 53 | (account_info.lamports(), 0) 54 | } 55 | Key::MetadataV1 => { 56 | let fee_amount = account_info 57 | .lamports() 58 | .checked_sub(metadata_rent) 59 | .ok_or(MetadataError::NumericalOverflowError)?; 60 | 61 | (fee_amount, metadata_rent) 62 | } 63 | _ => return Err(MetadataError::InvalidFeeAccount.into()), 64 | }; 65 | 66 | let dest_starting_lamports = dest_info.lamports(); 67 | **dest_info.lamports.borrow_mut() = dest_starting_lamports 68 | .checked_add(fee_amount) 69 | .ok_or(MetadataError::NumericalOverflowError)?; 70 | **account_info.lamports.borrow_mut() = rent_amount; 71 | 72 | // Clear fee flag. 73 | clear_fee_flag(account_info)?; 74 | 75 | Ok(()) 76 | } 77 | -------------------------------------------------------------------------------- /mpl_token_metadata/src/mpl_token_metadata/processor/freeze/freeze_delegated_account.rs: -------------------------------------------------------------------------------- 1 | use mpl_utils::assert_signer; 2 | use solana_program::{ 3 | account_info::AccountInfo, entrypoint::ProgramResult, program::invoke_signed, pubkey::Pubkey, 4 | }; 5 | use spl_token_2022::{instruction::freeze_account, state::Mint}; 6 | 7 | use crate::{ 8 | assertions::{ 9 | assert_delegated_tokens, assert_derivation, assert_freeze_authority_matches_mint, 10 | assert_initialized, assert_owned_by, edition::assert_edition_is_not_programmable, 11 | }, 12 | error::MetadataError, 13 | processor::all_account_infos, 14 | state::{EDITION, PREFIX}, 15 | utils::SPL_TOKEN_ID, 16 | }; 17 | 18 | pub fn process_freeze_delegated_account( 19 | program_id: &Pubkey, 20 | accounts: &[AccountInfo], 21 | ) -> ProgramResult { 22 | all_account_infos!( 23 | accounts, 24 | delegate_info, 25 | token_account_info, 26 | edition_info, 27 | mint_info, 28 | token_program_account_info 29 | ); 30 | 31 | if *token_program_account_info.key != SPL_TOKEN_ID { 32 | return Err(MetadataError::InvalidTokenProgram.into()); 33 | } 34 | 35 | // assert that edition pda is the freeze authority of this mint 36 | let mint: Mint = assert_initialized(mint_info)?; 37 | assert_owned_by(edition_info, program_id)?; 38 | assert_freeze_authority_matches_mint(&mint.freeze_authority, edition_info)?; 39 | 40 | // assert delegate is signer and delegated tokens 41 | assert_signer(delegate_info)?; 42 | assert_delegated_tokens( 43 | delegate_info, 44 | mint_info, 45 | token_account_info, 46 | token_program_account_info.key, 47 | )?; 48 | 49 | let edition_info_path = Vec::from([ 50 | PREFIX.as_bytes(), 51 | program_id.as_ref(), 52 | mint_info.key.as_ref(), 53 | EDITION.as_bytes(), 54 | ]); 55 | let edition_info_path_bump_seed = &[assert_derivation( 56 | program_id, 57 | edition_info, 58 | &edition_info_path, 59 | )?]; 60 | 61 | // check that we are not dealing with a pNFT master edition 62 | assert_edition_is_not_programmable(edition_info)?; 63 | 64 | let mut edition_info_seeds = edition_info_path.clone(); 65 | edition_info_seeds.push(edition_info_path_bump_seed); 66 | invoke_signed( 67 | &freeze_account( 68 | token_program_account_info.key, 69 | token_account_info.key, 70 | mint_info.key, 71 | edition_info.key, 72 | &[], 73 | ) 74 | .unwrap(), 75 | &[ 76 | token_account_info.clone(), 77 | mint_info.clone(), 78 | edition_info.clone(), 79 | ], 80 | &[&edition_info_seeds], 81 | )?; 82 | Ok(()) 83 | } 84 | -------------------------------------------------------------------------------- /mpl_token_metadata/src/mpl_token_metadata/processor/freeze/mod.rs: -------------------------------------------------------------------------------- 1 | mod freeze_delegated_account; 2 | mod thaw_delegated_account; 3 | 4 | pub use freeze_delegated_account::*; 5 | pub use thaw_delegated_account::*; 6 | -------------------------------------------------------------------------------- /mpl_token_metadata/src/mpl_token_metadata/processor/freeze/thaw_delegated_account.rs: -------------------------------------------------------------------------------- 1 | use mpl_utils::assert_signer; 2 | use solana_program::{ 3 | account_info::AccountInfo, entrypoint::ProgramResult, program::invoke_signed, pubkey::Pubkey, 4 | }; 5 | use spl_token_2022::{instruction::thaw_account, state::Mint}; 6 | 7 | use crate::{ 8 | assertions::{ 9 | assert_delegated_tokens, assert_derivation, assert_freeze_authority_matches_mint, 10 | assert_initialized, assert_owned_by, edition::assert_edition_is_not_programmable, 11 | }, 12 | error::MetadataError, 13 | processor::all_account_infos, 14 | state::{EDITION, PREFIX}, 15 | utils::SPL_TOKEN_ID, 16 | }; 17 | 18 | pub fn process_thaw_delegated_account( 19 | program_id: &Pubkey, 20 | accounts: &[AccountInfo], 21 | ) -> ProgramResult { 22 | all_account_infos!( 23 | accounts, 24 | delegate_info, 25 | token_account_info, 26 | edition_info, 27 | mint_info, 28 | token_program_account_info 29 | ); 30 | 31 | if *token_program_account_info.key != SPL_TOKEN_ID { 32 | return Err(MetadataError::InvalidTokenProgram.into()); 33 | } 34 | 35 | // assert that edition pda is the freeze authority of this mint 36 | let mint: Mint = assert_initialized(mint_info)?; 37 | assert_owned_by(edition_info, program_id)?; 38 | assert_freeze_authority_matches_mint(&mint.freeze_authority, edition_info)?; 39 | 40 | // assert delegate is signer and delegated tokens 41 | assert_signer(delegate_info)?; 42 | assert_delegated_tokens( 43 | delegate_info, 44 | mint_info, 45 | token_account_info, 46 | token_program_account_info.key, 47 | )?; 48 | 49 | let edition_info_path = Vec::from([ 50 | PREFIX.as_bytes(), 51 | program_id.as_ref(), 52 | mint_info.key.as_ref(), 53 | EDITION.as_bytes(), 54 | ]); 55 | let edition_info_path_bump_seed = &[assert_derivation( 56 | program_id, 57 | edition_info, 58 | &edition_info_path, 59 | )?]; 60 | 61 | // check that we are not dealing with a pNFT master edition 62 | assert_edition_is_not_programmable(edition_info)?; 63 | 64 | let mut edition_info_seeds = edition_info_path.clone(); 65 | edition_info_seeds.push(edition_info_path_bump_seed); 66 | invoke_signed( 67 | &thaw_account( 68 | token_program_account_info.key, 69 | token_account_info.key, 70 | mint_info.key, 71 | edition_info.key, 72 | &[], 73 | ) 74 | .unwrap(), 75 | &[ 76 | token_account_info.clone(), 77 | mint_info.clone(), 78 | edition_info.clone(), 79 | ], 80 | &[&edition_info_seeds], 81 | )?; 82 | Ok(()) 83 | } 84 | -------------------------------------------------------------------------------- /mpl_token_metadata/src/mpl_token_metadata/processor/metadata/create_medatata_accounts_v3.rs: -------------------------------------------------------------------------------- 1 | use solana_program::{account_info::AccountInfo, entrypoint::ProgramResult, pubkey::Pubkey}; 2 | 3 | use crate::{ 4 | processor::all_account_infos, 5 | state::{CollectionDetails, DataV2}, 6 | utils::{ 7 | fee::{levy, set_fee_flag, LevyArgs}, 8 | process_create_metadata_accounts_logic, CreateMetadataAccountsLogicArgs, 9 | }, 10 | }; 11 | 12 | pub fn process_create_metadata_accounts_v3<'a>( 13 | program_id: &'a Pubkey, 14 | accounts: &'a [AccountInfo<'a>], 15 | data: DataV2, 16 | is_mutable: bool, 17 | collection_details: Option, 18 | ) -> ProgramResult { 19 | all_account_infos!( 20 | accounts, 21 | metadata_account_info, 22 | mint_info, 23 | mint_authority_info, 24 | payer_account_info, 25 | update_authority_info, 26 | system_account_info 27 | ); 28 | 29 | // Levy fees first, to fund the metadata account with rent + fee amount. 30 | levy(LevyArgs { 31 | payer_account_info, 32 | token_metadata_pda_info: metadata_account_info, 33 | })?; 34 | 35 | process_create_metadata_accounts_logic( 36 | program_id, 37 | CreateMetadataAccountsLogicArgs { 38 | metadata_account_info, 39 | mint_info, 40 | mint_authority_info, 41 | payer_account_info, 42 | update_authority_info, 43 | system_account_info, 44 | }, 45 | data, 46 | false, 47 | is_mutable, 48 | false, 49 | true, 50 | collection_details, 51 | None, 52 | None, 53 | )?; 54 | 55 | // Set fee flag after metadata account is created. 56 | set_fee_flag(metadata_account_info) 57 | } 58 | -------------------------------------------------------------------------------- /mpl_token_metadata/src/mpl_token_metadata/processor/metadata/mod.rs: -------------------------------------------------------------------------------- 1 | mod create; 2 | mod create_medatata_accounts_v3; 3 | mod mint; 4 | mod print; 5 | mod puff_metadata; 6 | mod remove_creator_verification; 7 | mod set_token_standard; 8 | mod sign_metadata; 9 | mod transfer; 10 | mod update; 11 | mod update_metadata_account_v2; 12 | mod update_primary_sale_happened_via_token; 13 | 14 | pub use create::*; 15 | pub use create_medatata_accounts_v3::*; 16 | pub use mint::*; 17 | pub use print::*; 18 | pub use puff_metadata::*; 19 | pub use remove_creator_verification::*; 20 | pub use set_token_standard::*; 21 | pub use sign_metadata::*; 22 | pub use transfer::*; 23 | pub use update::*; 24 | pub use update_metadata_account_v2::*; 25 | pub use update_primary_sale_happened_via_token::*; 26 | -------------------------------------------------------------------------------- /mpl_token_metadata/src/mpl_token_metadata/processor/metadata/puff_metadata.rs: -------------------------------------------------------------------------------- 1 | use solana_program::{account_info::AccountInfo, entrypoint::ProgramResult, pubkey::Pubkey}; 2 | 3 | use crate::{ 4 | assertions::assert_owned_by, 5 | processor::all_account_infos, 6 | state::{Metadata, TokenMetadataAccount, EDITION, PREFIX}, 7 | utils::puff_out_data_fields, 8 | }; 9 | 10 | /// Puff out the variable length fields to a fixed length on a metadata 11 | /// account in a permissionless way. 12 | pub fn process_puff_metadata_account( 13 | program_id: &Pubkey, 14 | accounts: &[AccountInfo], 15 | ) -> ProgramResult { 16 | all_account_infos!(accounts, metadata_account_info); 17 | 18 | let mut metadata = Metadata::from_account_info(metadata_account_info)?; 19 | 20 | assert_owned_by(metadata_account_info, program_id)?; 21 | 22 | puff_out_data_fields(&mut metadata); 23 | 24 | let edition_seeds = &[ 25 | PREFIX.as_bytes(), 26 | program_id.as_ref(), 27 | metadata.mint.as_ref(), 28 | EDITION.as_bytes(), 29 | ]; 30 | let (_, edition_bump_seed) = Pubkey::find_program_address(edition_seeds, program_id); 31 | metadata.edition_nonce = Some(edition_bump_seed); 32 | 33 | metadata.save(&mut metadata_account_info.try_borrow_mut_data()?)?; 34 | Ok(()) 35 | } 36 | -------------------------------------------------------------------------------- /mpl_token_metadata/src/mpl_token_metadata/processor/metadata/remove_creator_verification.rs: -------------------------------------------------------------------------------- 1 | use mpl_utils::assert_signer; 2 | use solana_program::{account_info::AccountInfo, entrypoint::ProgramResult, pubkey::Pubkey}; 3 | 4 | use crate::{ 5 | assertions::assert_owned_by, 6 | error::MetadataError, 7 | processor::all_account_infos, 8 | state::{Metadata, TokenMetadataAccount}, 9 | }; 10 | 11 | pub fn process_remove_creator_verification( 12 | program_id: &Pubkey, 13 | accounts: &[AccountInfo], 14 | ) -> ProgramResult { 15 | all_account_infos!(accounts, metadata_info, creator_info); 16 | 17 | assert_signer(creator_info)?; 18 | assert_owned_by(metadata_info, program_id)?; 19 | 20 | let mut metadata = Metadata::from_account_info(metadata_info)?; 21 | 22 | if let Some(creators) = &mut metadata.data.creators { 23 | let mut found = false; 24 | for creator in creators { 25 | if creator.address == *creator_info.key { 26 | creator.verified = false; 27 | found = true; 28 | break; 29 | } 30 | } 31 | if !found { 32 | return Err(MetadataError::CreatorNotFound.into()); 33 | } 34 | } else { 35 | return Err(MetadataError::NoCreatorsPresentOnMetadata.into()); 36 | } 37 | metadata.save(&mut metadata_info.try_borrow_mut_data()?)?; 38 | 39 | Ok(()) 40 | } 41 | -------------------------------------------------------------------------------- /mpl_token_metadata/src/mpl_token_metadata/processor/metadata/set_token_standard.rs: -------------------------------------------------------------------------------- 1 | use solana_program::{ 2 | account_info::{next_account_info, AccountInfo}, 3 | entrypoint::ProgramResult, 4 | pubkey::Pubkey, 5 | }; 6 | 7 | use crate::{ 8 | assertions::{ 9 | assert_derivation, assert_owned_by, metadata::assert_update_authority_is_correct, 10 | }, 11 | error::MetadataError, 12 | state::{Metadata, TokenMetadataAccount, EDITION, PREFIX}, 13 | utils::{check_token_standard, clean_write_metadata}, 14 | }; 15 | 16 | pub fn process_set_token_standard(program_id: &Pubkey, accounts: &[AccountInfo]) -> ProgramResult { 17 | let account_info_iter = &mut accounts.iter(); 18 | 19 | let metadata_account_info = next_account_info(account_info_iter)?; 20 | let update_authority_account_info = next_account_info(account_info_iter)?; 21 | let mint_account_info = next_account_info(account_info_iter)?; 22 | 23 | // Owned by token-metadata program. 24 | assert_owned_by(metadata_account_info, program_id)?; 25 | let mut metadata = Metadata::from_account_info(metadata_account_info)?; 26 | 27 | // Mint account passed in must be the mint of the metadata account passed in. 28 | if &metadata.mint != mint_account_info.key { 29 | return Err(MetadataError::MintMismatch.into()); 30 | } 31 | 32 | // Update authority is a signer and matches update authority on metadata. 33 | assert_update_authority_is_correct(&metadata, update_authority_account_info)?; 34 | 35 | let edition_info_opt = account_info_iter.next(); 36 | 37 | // Edition account provided. 38 | let token_standard = if let Some(edition_info) = edition_info_opt { 39 | let edition_path = Vec::from([ 40 | PREFIX.as_bytes(), 41 | program_id.as_ref(), 42 | mint_account_info.key.as_ref(), 43 | EDITION.as_bytes(), 44 | ]); 45 | 46 | assert_owned_by(edition_info, program_id)?; 47 | assert_derivation(program_id, edition_info, &edition_path)?; 48 | 49 | check_token_standard(mint_account_info, Some(edition_info))? 50 | } else { 51 | check_token_standard(mint_account_info, None)? 52 | }; 53 | 54 | metadata.token_standard = Some(token_standard); 55 | clean_write_metadata(&mut metadata, metadata_account_info) 56 | } 57 | -------------------------------------------------------------------------------- /mpl_token_metadata/src/mpl_token_metadata/processor/metadata/sign_metadata.rs: -------------------------------------------------------------------------------- 1 | use mpl_utils::assert_signer; 2 | use solana_program::{account_info::AccountInfo, entrypoint::ProgramResult, pubkey::Pubkey}; 3 | 4 | use crate::{ 5 | assertions::assert_owned_by, 6 | error::MetadataError, 7 | processor::all_account_infos, 8 | state::{Metadata, TokenMetadataAccount}, 9 | }; 10 | 11 | pub fn process_sign_metadata(program_id: &Pubkey, accounts: &[AccountInfo]) -> ProgramResult { 12 | all_account_infos!(accounts, metadata_info, creator_info); 13 | 14 | assert_signer(creator_info)?; 15 | assert_owned_by(metadata_info, program_id)?; 16 | 17 | let mut metadata = Metadata::from_account_info(metadata_info)?; 18 | 19 | if let Some(creators) = &mut metadata.data.creators { 20 | let mut found = false; 21 | for creator in creators { 22 | if creator.address == *creator_info.key { 23 | creator.verified = true; 24 | found = true; 25 | break; 26 | } 27 | } 28 | if !found { 29 | return Err(MetadataError::CreatorNotFound.into()); 30 | } 31 | } else { 32 | return Err(MetadataError::NoCreatorsPresentOnMetadata.into()); 33 | } 34 | metadata.save(&mut metadata_info.try_borrow_mut_data()?)?; 35 | 36 | Ok(()) 37 | } 38 | -------------------------------------------------------------------------------- /mpl_token_metadata/src/mpl_token_metadata/processor/metadata/update_metadata_account_v2.rs: -------------------------------------------------------------------------------- 1 | use solana_program::{account_info::AccountInfo, entrypoint::ProgramResult, pubkey::Pubkey}; 2 | 3 | use crate::{ 4 | assertions::{ 5 | assert_owned_by, 6 | collection::assert_collection_update_is_valid, 7 | metadata::{assert_data_valid, assert_update_authority_is_correct}, 8 | uses::assert_valid_use, 9 | }, 10 | error::MetadataError, 11 | processor::all_account_infos, 12 | state::{DataV2, Metadata, TokenMetadataAccount}, 13 | utils::{clean_write_metadata, puff_out_data_fields}, 14 | }; 15 | 16 | // Update existing account instruction 17 | pub fn process_update_metadata_accounts_v2( 18 | program_id: &Pubkey, 19 | accounts: &[AccountInfo], 20 | optional_data: Option, 21 | update_authority: Option, 22 | primary_sale_happened: Option, 23 | is_mutable: Option, 24 | ) -> ProgramResult { 25 | all_account_infos!(accounts, metadata_account_info, update_authority_info); 26 | 27 | let mut metadata = Metadata::from_account_info(metadata_account_info)?; 28 | 29 | assert_owned_by(metadata_account_info, program_id)?; 30 | assert_update_authority_is_correct(&metadata, update_authority_info)?; 31 | 32 | if let Some(data) = optional_data { 33 | if metadata.is_mutable { 34 | let compatible_data = data.to_v1(); 35 | assert_data_valid( 36 | &compatible_data, 37 | update_authority_info.key, 38 | &metadata, 39 | false, 40 | update_authority_info.is_signer, 41 | )?; 42 | metadata.data = compatible_data; 43 | // If the user passes in Collection data, only allow updating if it's unverified 44 | // or if it exactly matches the existing collection info. 45 | // If the user passes in None for the Collection data then only set it if it's unverified. 46 | if data.collection.is_some() { 47 | assert_collection_update_is_valid(false, &metadata.collection, &data.collection)?; 48 | metadata.collection = data.collection; 49 | } else if let Some(collection) = metadata.collection.as_ref() { 50 | // Can't change a verified collection in this command. 51 | if collection.verified { 52 | return Err(MetadataError::CannotUpdateVerifiedCollection.into()); 53 | } 54 | // If it's unverified, it's ok to set to None. 55 | metadata.collection = data.collection; 56 | } 57 | // If already None leave it as None. 58 | assert_valid_use(&data.uses, &metadata.uses)?; 59 | metadata.uses = data.uses; 60 | } else { 61 | return Err(MetadataError::DataIsImmutable.into()); 62 | } 63 | } 64 | 65 | if let Some(val) = update_authority { 66 | metadata.update_authority = val; 67 | } 68 | 69 | if let Some(val) = primary_sale_happened { 70 | // If received val is true, flip to true. 71 | if val || !metadata.primary_sale_happened { 72 | metadata.primary_sale_happened = val 73 | } else { 74 | return Err(MetadataError::PrimarySaleCanOnlyBeFlippedToTrue.into()); 75 | } 76 | } 77 | 78 | if let Some(val) = is_mutable { 79 | // If received value is false, flip to false. 80 | if !val || metadata.is_mutable { 81 | metadata.is_mutable = val 82 | } else { 83 | return Err(MetadataError::IsMutableCanOnlyBeFlippedToFalse.into()); 84 | } 85 | } 86 | 87 | puff_out_data_fields(&mut metadata); 88 | clean_write_metadata(&mut metadata, metadata_account_info) 89 | } 90 | -------------------------------------------------------------------------------- /mpl_token_metadata/src/mpl_token_metadata/processor/metadata/update_primary_sale_happened_via_token.rs: -------------------------------------------------------------------------------- 1 | use solana_program::{ 2 | account_info::AccountInfo, entrypoint::ProgramResult, program_error::ProgramError, 3 | pubkey::Pubkey, 4 | }; 5 | use spl_token_2022::state::Account; 6 | 7 | use crate::{ 8 | assertions::{assert_initialized, assert_owned_by}, 9 | error::MetadataError, 10 | processor::all_account_infos, 11 | state::{Metadata, TokenMetadataAccount}, 12 | utils::SPL_TOKEN_ID, 13 | }; 14 | 15 | pub fn process_update_primary_sale_happened_via_token( 16 | program_id: &Pubkey, 17 | accounts: &[AccountInfo], 18 | ) -> ProgramResult { 19 | all_account_infos!( 20 | accounts, 21 | metadata_account_info, 22 | owner_info, 23 | token_account_info 24 | ); 25 | 26 | let token_account: Account = assert_initialized(token_account_info)?; 27 | let mut metadata = Metadata::from_account_info(metadata_account_info)?; 28 | 29 | assert_owned_by(metadata_account_info, program_id)?; 30 | assert_owned_by(token_account_info, &SPL_TOKEN_ID)?; 31 | 32 | if !owner_info.is_signer { 33 | return Err(ProgramError::MissingRequiredSignature); 34 | } 35 | 36 | if token_account.owner != *owner_info.key { 37 | return Err(MetadataError::OwnerMismatch.into()); 38 | } 39 | 40 | if token_account.amount == 0 { 41 | return Err(MetadataError::NoBalanceInAccountForAuthorization.into()); 42 | } 43 | 44 | if token_account.mint != metadata.mint { 45 | return Err(MetadataError::MintMismatch.into()); 46 | } 47 | 48 | metadata.primary_sale_happened = true; 49 | metadata.save(&mut metadata_account_info.try_borrow_mut_data()?)?; 50 | 51 | Ok(()) 52 | } 53 | -------------------------------------------------------------------------------- /mpl_token_metadata/src/mpl_token_metadata/processor/mod.rs: -------------------------------------------------------------------------------- 1 | // mod burn; 2 | // mod collection; 3 | // mod delegate; 4 | // mod edition; 5 | // pub(crate) mod escrow; 6 | // mod fee; 7 | // mod freeze; 8 | // mod metadata; 9 | // mod state; 10 | // mod uses; 11 | // mod verification; 12 | 13 | use borsh::BorshDeserialize; 14 | // pub use burn::*; 15 | // pub use collection::*; 16 | // pub use delegate::*; 17 | // pub use edition::*; 18 | // pub use escrow::*; 19 | // pub use freeze::*; 20 | // pub use metadata::*; 21 | // use mpl_token_auth_rules::payload::Payload; 22 | // pub use state::*; 23 | // pub use uses::*; 24 | // pub use verification::*; 25 | 26 | use substreams_solana_utils::pubkey::Pubkey; 27 | use std::collections::HashMap; 28 | 29 | #[repr(C)] 30 | #[derive(BorshDeserialize, PartialEq, Eq, Debug, Clone)] 31 | /// A seed path type used by the `DerivedKeyMatch` rule. 32 | pub struct SeedsVec { 33 | /// The vector of derivation seeds. 34 | pub seeds: Vec>, 35 | } 36 | 37 | impl SeedsVec { 38 | /// Create a new `SeedsVec`. 39 | pub fn new(seeds: Vec>) -> Self { 40 | Self { seeds } 41 | } 42 | } 43 | 44 | #[repr(C)] 45 | #[derive(BorshDeserialize, PartialEq, Eq, Debug, Clone)] 46 | /// A proof type used by the `PubkeyTreeMatch` rule. 47 | pub struct ProofInfo { 48 | /// The merkle proof. 49 | pub proof: Vec<[u8; 32]>, 50 | } 51 | 52 | impl ProofInfo { 53 | /// Create a new `ProofInfo`. 54 | pub fn new(proof: Vec<[u8; 32]>) -> Self { 55 | Self { proof } 56 | } 57 | } 58 | 59 | #[repr(C)] 60 | #[derive(BorshDeserialize, PartialEq, Eq, Debug, Clone)] 61 | /// Variants representing the different types represented in a payload. 62 | pub enum PayloadType { 63 | /// A plain `Pubkey`. 64 | Pubkey(Pubkey), 65 | /// PDA derivation seeds. 66 | Seeds(SeedsVec), 67 | /// A merkle proof. 68 | MerkleProof(ProofInfo), 69 | /// A plain `u64` used for `Amount`. 70 | Number(u64), 71 | } 72 | 73 | #[derive( 74 | BorshDeserialize, PartialEq, Eq, Debug, Clone, Default, 75 | )] 76 | /// A wrapper type for the payload hashmap. 77 | pub struct Payload { 78 | map: HashMap, 79 | } 80 | 81 | 82 | 83 | #[derive(BorshDeserialize, PartialEq, Eq, Debug, Clone)] 84 | pub struct AuthorizationData { 85 | pub payload: Payload, 86 | } 87 | 88 | impl AuthorizationData { 89 | pub fn new(payload: Payload) -> Self { 90 | Self { payload } 91 | } 92 | pub fn new_empty() -> Self { 93 | Self { 94 | payload: Payload::new(), 95 | } 96 | } 97 | } 98 | 99 | impl Payload { 100 | /// Create a new empty `Payload`. 101 | pub fn new() -> Self { 102 | Self { 103 | map: HashMap::new(), 104 | } 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /mpl_token_metadata/src/mpl_token_metadata/processor/state/lock.rs: -------------------------------------------------------------------------------- 1 | use solana_program::{account_info::AccountInfo, entrypoint::ProgramResult, pubkey::Pubkey}; 2 | 3 | use super::toggle_asset_state; 4 | use crate::{ 5 | instruction::{Lock, LockArgs}, 6 | state::TokenState, 7 | }; 8 | 9 | pub fn lock<'a>( 10 | program_id: &Pubkey, 11 | accounts: &'a [AccountInfo<'a>], 12 | args: LockArgs, 13 | ) -> ProgramResult { 14 | let context = Lock::to_context(accounts)?; 15 | 16 | match args { 17 | LockArgs::V1 { .. } => toggle_asset_state( 18 | program_id, 19 | super::ToggleAccounts { 20 | payer_info: context.accounts.payer_info, 21 | authority_info: context.accounts.authority_info, 22 | mint_info: context.accounts.mint_info, 23 | token_info: context.accounts.token_info, 24 | edition_info: context.accounts.edition_info, 25 | metadata_info: context.accounts.metadata_info, 26 | token_record_info: context.accounts.token_record_info, 27 | system_program_info: context.accounts.system_program_info, 28 | sysvar_instructions_info: context.accounts.sysvar_instructions_info, 29 | spl_token_program_info: context.accounts.spl_token_program_info, 30 | }, 31 | TokenState::Unlocked, 32 | TokenState::Locked, 33 | ), 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /mpl_token_metadata/src/mpl_token_metadata/processor/state/unlock.rs: -------------------------------------------------------------------------------- 1 | use solana_program::{account_info::AccountInfo, entrypoint::ProgramResult, pubkey::Pubkey}; 2 | 3 | use super::toggle_asset_state; 4 | use crate::{ 5 | instruction::{Unlock, UnlockArgs}, 6 | state::TokenState, 7 | }; 8 | 9 | pub fn unlock<'a>( 10 | program_id: &Pubkey, 11 | accounts: &'a [AccountInfo<'a>], 12 | args: UnlockArgs, 13 | ) -> ProgramResult { 14 | let context = Unlock::to_context(accounts)?; 15 | 16 | match args { 17 | UnlockArgs::V1 { .. } => toggle_asset_state( 18 | program_id, 19 | super::ToggleAccounts { 20 | payer_info: context.accounts.payer_info, 21 | authority_info: context.accounts.authority_info, 22 | mint_info: context.accounts.mint_info, 23 | token_info: context.accounts.token_info, 24 | edition_info: context.accounts.edition_info, 25 | metadata_info: context.accounts.metadata_info, 26 | token_record_info: context.accounts.token_record_info, 27 | system_program_info: context.accounts.system_program_info, 28 | sysvar_instructions_info: context.accounts.sysvar_instructions_info, 29 | spl_token_program_info: context.accounts.spl_token_program_info, 30 | }, 31 | TokenState::Locked, 32 | TokenState::Unlocked, 33 | ), 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /mpl_token_metadata/src/mpl_token_metadata/processor/uses/approve_use_authority.rs: -------------------------------------------------------------------------------- 1 | use mpl_utils::{assert_signer, create_or_allocate_account_raw}; 2 | use solana_program::{ 3 | account_info::AccountInfo, entrypoint::ProgramResult, program::invoke, pubkey::Pubkey, 4 | }; 5 | use spl_token_2022::instruction::approve; 6 | 7 | use crate::{ 8 | assertions::{ 9 | metadata::assert_currently_holding, 10 | uses::{assert_burner, assert_use_authority_derivation, process_use_authority_validation}, 11 | }, 12 | error::MetadataError, 13 | processor::all_account_infos, 14 | state::{ 15 | Key, Metadata, TokenMetadataAccount, UseAuthorityRecord, UseMethod, PREFIX, USER, 16 | USE_AUTHORITY_RECORD_SIZE, 17 | }, 18 | utils::SPL_TOKEN_ID, 19 | }; 20 | 21 | pub fn process_approve_use_authority( 22 | program_id: &Pubkey, 23 | accounts: &[AccountInfo], 24 | number_of_uses: u64, 25 | ) -> ProgramResult { 26 | all_account_infos!( 27 | accounts, 28 | use_authority_record_info, 29 | owner_info, 30 | payer, 31 | user_info, 32 | token_account_info, 33 | metadata_info, 34 | mint_info, 35 | program_as_burner, 36 | token_program_account_info, 37 | system_account_info 38 | ); 39 | 40 | let metadata: Metadata = Metadata::from_account_info(metadata_info)?; 41 | 42 | if metadata.uses.is_none() { 43 | return Err(MetadataError::Unusable.into()); 44 | } 45 | if *token_program_account_info.key != SPL_TOKEN_ID { 46 | return Err(MetadataError::InvalidTokenProgram.into()); 47 | } 48 | assert_signer(owner_info)?; 49 | assert_signer(payer)?; 50 | assert_currently_holding( 51 | program_id, 52 | owner_info, 53 | metadata_info, 54 | &metadata, 55 | mint_info, 56 | token_account_info, 57 | )?; 58 | let metadata_uses = metadata.uses.unwrap(); 59 | let bump_seed = assert_use_authority_derivation( 60 | program_id, 61 | use_authority_record_info, 62 | user_info, 63 | mint_info, 64 | )?; 65 | let use_authority_seeds = &[ 66 | PREFIX.as_bytes(), 67 | program_id.as_ref(), 68 | mint_info.key.as_ref(), 69 | USER.as_bytes(), 70 | user_info.key.as_ref(), 71 | &[bump_seed], 72 | ]; 73 | process_use_authority_validation(use_authority_record_info.data_len(), true)?; 74 | create_or_allocate_account_raw( 75 | *program_id, 76 | use_authority_record_info, 77 | system_account_info, 78 | payer, 79 | USE_AUTHORITY_RECORD_SIZE, 80 | use_authority_seeds, 81 | )?; 82 | if number_of_uses > metadata_uses.remaining { 83 | return Err(MetadataError::NotEnoughUses.into()); 84 | } 85 | if metadata_uses.use_method == UseMethod::Burn { 86 | assert_burner(program_as_burner.key)?; 87 | invoke( 88 | &approve( 89 | token_program_account_info.key, 90 | token_account_info.key, 91 | program_as_burner.key, 92 | owner_info.key, 93 | &[], 94 | 1, 95 | ) 96 | .unwrap(), 97 | &[ 98 | token_program_account_info.clone(), 99 | token_account_info.clone(), 100 | program_as_burner.clone(), 101 | owner_info.clone(), 102 | ], 103 | )?; 104 | } 105 | let mutable_data = &mut (*use_authority_record_info.try_borrow_mut_data()?); 106 | let mut record = UseAuthorityRecord::from_bytes(mutable_data)?; 107 | 108 | record.key = Key::UseAuthorityRecord; 109 | record.allowed_uses = number_of_uses; 110 | record.bump = bump_seed; 111 | borsh::to_writer(&mut mutable_data[..], &record)?; 112 | Ok(()) 113 | } 114 | -------------------------------------------------------------------------------- /mpl_token_metadata/src/mpl_token_metadata/processor/uses/mod.rs: -------------------------------------------------------------------------------- 1 | mod approve_use_authority; 2 | mod revoke_use_authority; 3 | mod utilize; 4 | 5 | pub use approve_use_authority::*; 6 | pub use revoke_use_authority::*; 7 | pub use utilize::*; 8 | -------------------------------------------------------------------------------- /mpl_token_metadata/src/mpl_token_metadata/processor/uses/revoke_use_authority.rs: -------------------------------------------------------------------------------- 1 | use mpl_utils::assert_signer; 2 | use solana_program::{ 3 | account_info::AccountInfo, entrypoint::ProgramResult, program::invoke, pubkey::Pubkey, 4 | }; 5 | use spl_token_2022::instruction::revoke; 6 | 7 | use crate::{ 8 | assertions::{ 9 | assert_owned_by, 10 | metadata::assert_currently_holding, 11 | uses::{ 12 | assert_use_authority_derivation, assert_valid_bump, process_use_authority_validation, 13 | }, 14 | }, 15 | error::MetadataError, 16 | processor::all_account_infos, 17 | state::{Key, Metadata, TokenMetadataAccount, UseAuthorityRecord, UseMethod}, 18 | utils::{close_program_account, SPL_TOKEN_ID}, 19 | }; 20 | 21 | pub fn process_revoke_use_authority( 22 | program_id: &Pubkey, 23 | accounts: &[AccountInfo], 24 | ) -> ProgramResult { 25 | all_account_infos!( 26 | accounts, 27 | use_authority_record_info, 28 | owner_info, 29 | user_info, 30 | token_account_info, 31 | mint_info, 32 | metadata_info, 33 | token_program_account_info 34 | ); 35 | 36 | let metadata = Metadata::from_account_info(metadata_info)?; 37 | if metadata.uses.is_none() { 38 | return Err(MetadataError::Unusable.into()); 39 | } 40 | if *token_program_account_info.key != SPL_TOKEN_ID { 41 | return Err(MetadataError::InvalidTokenProgram.into()); 42 | } 43 | assert_signer(owner_info)?; 44 | assert_currently_holding( 45 | program_id, 46 | owner_info, 47 | metadata_info, 48 | &metadata, 49 | mint_info, 50 | token_account_info, 51 | )?; 52 | let data = use_authority_record_info.try_borrow_mut_data()?; 53 | process_use_authority_validation(data.len(), false)?; 54 | assert_owned_by(use_authority_record_info, program_id)?; 55 | let canonical_bump = assert_use_authority_derivation( 56 | program_id, 57 | use_authority_record_info, 58 | user_info, 59 | mint_info, 60 | )?; 61 | let mut record = UseAuthorityRecord::from_bytes(&data)?; 62 | if record.bump_empty() { 63 | record.bump = canonical_bump; 64 | } 65 | assert_valid_bump(canonical_bump, &record)?; 66 | let metadata_uses = metadata.uses.unwrap(); 67 | if metadata_uses.use_method == UseMethod::Burn { 68 | invoke( 69 | &revoke( 70 | token_program_account_info.key, 71 | token_account_info.key, 72 | owner_info.key, 73 | &[], 74 | ) 75 | .unwrap(), 76 | &[ 77 | token_program_account_info.clone(), 78 | token_account_info.clone(), 79 | owner_info.clone(), 80 | ], 81 | )?; 82 | } 83 | 84 | // Drop use_authority_record_info account data borrow. 85 | drop(data); 86 | 87 | close_program_account( 88 | use_authority_record_info, 89 | owner_info, 90 | Key::UseAuthorityRecord, 91 | ) 92 | } 93 | -------------------------------------------------------------------------------- /mpl_token_metadata/src/mpl_token_metadata/processor/uses/utilize.rs: -------------------------------------------------------------------------------- 1 | use mpl_utils::{ 2 | assert_signer, 3 | token::{spl_token_burn, TokenBurnParams}, 4 | }; 5 | use solana_program::{ 6 | account_info::{next_account_info, AccountInfo}, 7 | entrypoint::ProgramResult, 8 | pubkey::Pubkey, 9 | rent::Rent, 10 | sysvar::SysvarId, 11 | }; 12 | 13 | use crate::{ 14 | assertions::{ 15 | assert_owned_by, 16 | metadata::assert_currently_holding, 17 | uses::{ 18 | assert_burner, assert_use_authority_derivation, assert_valid_bump, 19 | process_use_authority_validation, 20 | }, 21 | }, 22 | error::MetadataError, 23 | state::{Metadata, TokenMetadataAccount, UseAuthorityRecord, UseMethod, Uses, BURN, PREFIX}, 24 | utils::SPL_TOKEN_ID, 25 | }; 26 | 27 | pub fn process_utilize( 28 | program_id: &Pubkey, 29 | accounts: &[AccountInfo], 30 | number_of_uses: u64, 31 | ) -> ProgramResult { 32 | let account_info_iter = &mut accounts.iter().peekable(); 33 | 34 | let metadata_info = next_account_info(account_info_iter)?; 35 | let token_account_info = next_account_info(account_info_iter)?; 36 | let mint_info = next_account_info(account_info_iter)?; 37 | let user_info = next_account_info(account_info_iter)?; 38 | let owner_info = next_account_info(account_info_iter)?; 39 | let token_program_account_info = next_account_info(account_info_iter)?; 40 | let _ata_program_account_info = next_account_info(account_info_iter)?; 41 | let _system_program_account_info = next_account_info(account_info_iter)?; 42 | 43 | // consume the next account only if it is Rent 44 | let approved_authority_is_using = if account_info_iter 45 | .next_if(|info| info.key == &Rent::id()) 46 | .is_some() 47 | { 48 | // rent was passed in 49 | accounts.len() == 11 50 | } else { 51 | // necessary accounts is one less if rent isn't passed in. 52 | accounts.len() == 10 53 | }; 54 | 55 | let metadata: Metadata = Metadata::from_account_info(metadata_info)?; 56 | 57 | if metadata.uses.is_none() { 58 | return Err(MetadataError::Unusable.into()); 59 | } 60 | if *token_program_account_info.key != SPL_TOKEN_ID { 61 | return Err(MetadataError::InvalidTokenProgram.into()); 62 | } 63 | assert_signer(user_info)?; 64 | assert_currently_holding( 65 | program_id, 66 | owner_info, 67 | metadata_info, 68 | &metadata, 69 | mint_info, 70 | token_account_info, 71 | )?; 72 | let mut metadata = Metadata::from_account_info(metadata_info)?; 73 | let metadata_uses = metadata.uses.unwrap(); 74 | let must_burn = metadata_uses.use_method == UseMethod::Burn; 75 | if number_of_uses > metadata_uses.total || number_of_uses > metadata_uses.remaining { 76 | return Err(MetadataError::NotEnoughUses.into()); 77 | } 78 | let remaining_uses = metadata_uses 79 | .remaining 80 | .checked_sub(number_of_uses) 81 | .ok_or(MetadataError::NotEnoughUses)?; 82 | metadata.uses = Some(Uses { 83 | use_method: metadata_uses.use_method, 84 | total: metadata_uses.total, 85 | remaining: remaining_uses, 86 | }); 87 | if approved_authority_is_using { 88 | let use_authority_record_info = next_account_info(account_info_iter)?; 89 | let data = &mut *use_authority_record_info.try_borrow_mut_data()?; 90 | process_use_authority_validation(data.len(), false)?; 91 | assert_owned_by(use_authority_record_info, program_id)?; 92 | let canonical_bump = assert_use_authority_derivation( 93 | program_id, 94 | use_authority_record_info, 95 | user_info, 96 | mint_info, 97 | )?; 98 | let mut record = UseAuthorityRecord::from_bytes(data)?; 99 | // Migrates old UARs to having the bump stored 100 | if record.bump_empty() { 101 | record.bump = canonical_bump; 102 | } 103 | assert_valid_bump(canonical_bump, &record)?; 104 | record.allowed_uses = record 105 | .allowed_uses 106 | .checked_sub(number_of_uses) 107 | .ok_or(MetadataError::NotEnoughUses)?; 108 | borsh::to_writer(&mut data[..], &record)?; 109 | } else if user_info.key != owner_info.key { 110 | return Err(MetadataError::InvalidUser.into()); 111 | } 112 | metadata.save(&mut metadata_info.try_borrow_mut_data()?)?; 113 | if remaining_uses == 0 && must_burn { 114 | if approved_authority_is_using { 115 | let burn_authority_info = next_account_info(account_info_iter)?; 116 | let seed = assert_burner(burn_authority_info.key)?; 117 | let burn_bump_ref = &[ 118 | PREFIX.as_bytes(), 119 | program_id.as_ref(), 120 | BURN.as_bytes(), 121 | &[seed], 122 | ]; 123 | spl_token_burn(TokenBurnParams { 124 | mint: mint_info.clone(), 125 | amount: 1, 126 | authority: burn_authority_info.clone(), 127 | token_program: token_program_account_info.clone(), 128 | source: token_account_info.clone(), 129 | authority_signer_seeds: Some(burn_bump_ref), 130 | })?; 131 | } else { 132 | spl_token_burn(TokenBurnParams { 133 | mint: mint_info.clone(), 134 | amount: 1, 135 | authority: owner_info.clone(), 136 | token_program: token_program_account_info.clone(), 137 | source: token_account_info.clone(), 138 | authority_signer_seeds: None, 139 | })?; 140 | } 141 | } 142 | Ok(()) 143 | } 144 | -------------------------------------------------------------------------------- /mpl_token_metadata/src/mpl_token_metadata/processor/verification/creator.rs: -------------------------------------------------------------------------------- 1 | use mpl_utils::assert_signer; 2 | use solana_program::{entrypoint::ProgramResult, pubkey::Pubkey}; 3 | 4 | use crate::{ 5 | assertions::assert_owned_by, 6 | error::MetadataError, 7 | instruction::{Context, Unverify, Verify}, 8 | state::{Creator, Metadata, TokenMetadataAccount}, 9 | utils::clean_write_metadata, 10 | }; 11 | 12 | pub(crate) fn verify_creator_v1(program_id: &Pubkey, ctx: Context) -> ProgramResult { 13 | // Assert program ownership/signers. 14 | 15 | // Authority account is the creator and must be a signer. 16 | assert_signer(ctx.accounts.authority_info)?; 17 | assert_owned_by(ctx.accounts.metadata_info, program_id)?; 18 | 19 | // Deserialize item metadata. 20 | let mut metadata = Metadata::from_account_info(ctx.accounts.metadata_info)?; 21 | 22 | // Find creator in creator array and if found mark as verified. 23 | find_and_set_creator( 24 | &mut metadata.data.creators, 25 | *ctx.accounts.authority_info.key, 26 | true, 27 | )?; 28 | 29 | // Reserialize item metadata. 30 | clean_write_metadata(&mut metadata, ctx.accounts.metadata_info) 31 | } 32 | 33 | pub(crate) fn unverify_creator_v1(program_id: &Pubkey, ctx: Context) -> ProgramResult { 34 | // Assert program ownership/signers. 35 | 36 | // Authority account is the creator and must be a signer. 37 | assert_signer(ctx.accounts.authority_info)?; 38 | assert_owned_by(ctx.accounts.metadata_info, program_id)?; 39 | 40 | // Deserialize item metadata. 41 | let mut metadata = Metadata::from_account_info(ctx.accounts.metadata_info)?; 42 | 43 | // Find creator in creator array and if found mark as verified. 44 | find_and_set_creator( 45 | &mut metadata.data.creators, 46 | *ctx.accounts.authority_info.key, 47 | false, 48 | )?; 49 | 50 | // Reserialize item metadata. 51 | clean_write_metadata(&mut metadata, ctx.accounts.metadata_info) 52 | } 53 | 54 | fn find_and_set_creator( 55 | creators: &mut Option>, 56 | creator_to_match: Pubkey, 57 | verified: bool, 58 | ) -> ProgramResult { 59 | // Find creator in creator array and if found mark as verified. 60 | match creators { 61 | Some(creators) => { 62 | let creator = creators 63 | .iter_mut() 64 | .find(|c| c.address == creator_to_match) 65 | .ok_or(MetadataError::CreatorNotFound)?; 66 | 67 | creator.verified = verified; 68 | Ok(()) 69 | } 70 | None => Err(MetadataError::NoCreatorsPresentOnMetadata.into()), 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /mpl_token_metadata/src/mpl_token_metadata/processor/verification/mod.rs: -------------------------------------------------------------------------------- 1 | mod collection; 2 | mod creator; 3 | mod verify; 4 | 5 | pub use verify::*; 6 | -------------------------------------------------------------------------------- /mpl_token_metadata/src/mpl_token_metadata/processor/verification/verify.rs: -------------------------------------------------------------------------------- 1 | use solana_program::{account_info::AccountInfo, entrypoint::ProgramResult, pubkey::Pubkey}; 2 | 3 | use crate::{ 4 | instruction::{Unverify, VerificationArgs, Verify}, 5 | processor::verification::{ 6 | collection::{unverify_collection_v1, verify_collection_v1}, 7 | creator::{unverify_creator_v1, verify_creator_v1}, 8 | }, 9 | }; 10 | 11 | pub fn verify<'a>( 12 | program_id: &Pubkey, 13 | accounts: &'a [AccountInfo<'a>], 14 | args: VerificationArgs, 15 | ) -> ProgramResult { 16 | let context = Verify::to_context(accounts)?; 17 | 18 | match args { 19 | VerificationArgs::CreatorV1 => verify_creator_v1(program_id, context), 20 | VerificationArgs::CollectionV1 => verify_collection_v1(program_id, context), 21 | } 22 | } 23 | 24 | pub fn unverify<'a>( 25 | program_id: &Pubkey, 26 | accounts: &'a [AccountInfo<'a>], 27 | args: VerificationArgs, 28 | ) -> ProgramResult { 29 | let context = Unverify::to_context(accounts)?; 30 | 31 | match args { 32 | VerificationArgs::CreatorV1 => unverify_creator_v1(program_id, context), 33 | VerificationArgs::CollectionV1 => unverify_collection_v1(program_id, context), 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /mpl_token_metadata/src/mpl_token_metadata/state/asset_data.rs: -------------------------------------------------------------------------------- 1 | use borsh::BorshDeserialize; 2 | use substreams_solana_utils::pubkey::Pubkey; 3 | 4 | use super::*; 5 | 6 | /// Data representation of an asset. 7 | #[repr(C)] 8 | #[cfg_attr(feature = "serde-feature", derive(Serialize, Deserialize))] 9 | #[derive(BorshDeserialize, PartialEq, Eq, Debug, Clone)] 10 | pub struct AssetData { 11 | /// The name of the asset. 12 | pub name: String, 13 | /// The symbol for the asset. 14 | pub symbol: String, 15 | /// URI pointing to JSON representing the asset. 16 | pub uri: String, 17 | /// Royalty basis points that goes to creators in secondary sales (0-10000). 18 | pub seller_fee_basis_points: u16, 19 | /// Array of creators. 20 | pub creators: Option>, 21 | // Immutable, once flipped, all sales of this metadata are considered secondary. 22 | pub primary_sale_happened: bool, 23 | // Whether or not the data struct is mutable (default is not). 24 | pub is_mutable: bool, 25 | /// Type of the token. 26 | pub token_standard: TokenStandard, 27 | /// Collection information. 28 | pub collection: Option, 29 | /// Uses information. 30 | pub uses: Option, 31 | /// Collection item details. 32 | pub collection_details: Option, 33 | /// Programmable rule set for the asset. 34 | #[cfg_attr( 35 | feature = "serde-feature", 36 | serde( 37 | deserialize_with = "deser_option_pubkey", 38 | serialize_with = "ser_option_pubkey" 39 | ) 40 | )] 41 | pub rule_set: Option, 42 | } 43 | 44 | impl AssetData { 45 | pub fn new(token_standard: TokenStandard, name: String, symbol: String, uri: String) -> Self { 46 | Self { 47 | name, 48 | symbol, 49 | uri, 50 | seller_fee_basis_points: 0, 51 | creators: None, 52 | primary_sale_happened: false, 53 | is_mutable: true, 54 | token_standard, 55 | collection: None, 56 | uses: None, 57 | collection_details: None, 58 | rule_set: None, 59 | } 60 | } 61 | 62 | pub fn as_data_v2(&self) -> DataV2 { 63 | DataV2 { 64 | collection: self.collection.clone(), 65 | creators: self.creators.clone(), 66 | name: self.name.clone(), 67 | seller_fee_basis_points: self.seller_fee_basis_points, 68 | symbol: self.symbol.clone(), 69 | uri: self.uri.clone(), 70 | uses: self.uses.clone(), 71 | } 72 | } 73 | 74 | pub fn as_data(&self) -> Data { 75 | Data { 76 | name: self.name.clone(), 77 | symbol: self.symbol.clone(), 78 | uri: self.uri.clone(), 79 | seller_fee_basis_points: self.seller_fee_basis_points, 80 | creators: self.creators.clone(), 81 | } 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /mpl_token_metadata/src/mpl_token_metadata/state/collection.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | use super::super::utils::try_from_slice_checked; 3 | use substreams_solana_utils::pubkey::Pubkey; 4 | 5 | pub const COLLECTION_AUTHORITY_RECORD_SIZE: usize = 35; 6 | 7 | #[repr(C)] 8 | #[cfg_attr(feature = "serde-feature", derive(Serialize, Deserialize))] 9 | #[derive(BorshDeserialize, PartialEq, Eq, Debug, Clone)] 10 | pub struct Collection { 11 | pub verified: bool, 12 | #[cfg_attr(feature = "serde-feature", serde(with = "As::"))] 13 | pub key: Pubkey, 14 | } 15 | 16 | #[repr(C)] 17 | #[cfg_attr(feature = "serde-feature", derive(Serialize, Deserialize))] 18 | #[derive(BorshDeserialize, PartialEq, Eq, Debug, Clone)] 19 | pub struct CollectionAuthorityRecord { 20 | pub key: Key, //1 21 | pub bump: u8, //1 22 | pub update_authority: Option, //33 (1 + 32) 23 | } 24 | 25 | impl Default for CollectionAuthorityRecord { 26 | fn default() -> Self { 27 | CollectionAuthorityRecord { 28 | key: Key::CollectionAuthorityRecord, 29 | bump: 255, 30 | update_authority: None, 31 | } 32 | } 33 | } 34 | 35 | impl TokenMetadataAccount for CollectionAuthorityRecord { 36 | fn key() -> Key { 37 | Key::CollectionAuthorityRecord 38 | } 39 | 40 | fn size() -> usize { 41 | COLLECTION_AUTHORITY_RECORD_SIZE 42 | } 43 | } 44 | 45 | impl CollectionAuthorityRecord { 46 | pub fn from_bytes(b: &[u8]) -> Result { 47 | let ca: CollectionAuthorityRecord = try_from_slice_checked( 48 | b, 49 | Key::CollectionAuthorityRecord, 50 | COLLECTION_AUTHORITY_RECORD_SIZE, 51 | )?; 52 | Ok(ca) 53 | } 54 | } 55 | 56 | #[repr(C)] 57 | #[cfg_attr(feature = "serde-feature", derive(Serialize, Deserialize))] 58 | #[derive(BorshDeserialize, PartialEq, Eq, Debug, Clone)] 59 | pub enum CollectionDetails { 60 | #[deprecated( 61 | since = "1.13.1", 62 | note = "The collection size tracking feature is deprecated and will soon be removed." 63 | )] 64 | V1 { 65 | size: u64, 66 | }, 67 | V2 { 68 | padding: [u8; 8], 69 | }, 70 | } 71 | -------------------------------------------------------------------------------- /mpl_token_metadata/src/mpl_token_metadata/state/creator.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | use substreams_solana_utils::pubkey::Pubkey; 4 | 5 | pub const MAX_CREATOR_LIMIT: usize = 5; 6 | 7 | pub const MAX_CREATOR_LEN: usize = 32 + 1 + 1; 8 | 9 | #[repr(C)] 10 | #[cfg_attr(feature = "serde-feature", derive(Serialize, Deserialize))] 11 | #[derive(BorshDeserialize, PartialEq, Debug, Clone, Eq, Hash)] 12 | pub struct Creator { 13 | #[cfg_attr(feature = "serde-feature", serde(with = "As::"))] 14 | pub address: Pubkey, 15 | pub verified: bool, 16 | // In percentages, NOT basis points ;) Watch out! 17 | pub share: u8, 18 | } 19 | -------------------------------------------------------------------------------- /mpl_token_metadata/src/mpl_token_metadata/state/data.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | #[repr(C)] 4 | #[cfg_attr(feature = "serde-feature", derive(Serialize, Deserialize))] 5 | #[derive(BorshDeserialize, Default, PartialEq, Eq, Debug, Clone)] 6 | pub struct Data { 7 | /// The name of the asset 8 | pub name: String, 9 | /// The symbol for the asset 10 | pub symbol: String, 11 | /// URI pointing to JSON representing the asset 12 | pub uri: String, 13 | /// Royalty basis points that goes to creators in secondary sales (0-10000) 14 | pub seller_fee_basis_points: u16, 15 | /// Array of creators, optional 16 | pub creators: Option>, 17 | } 18 | 19 | #[repr(C)] 20 | #[cfg_attr(feature = "serde-feature", derive(Serialize, Deserialize))] 21 | #[derive(BorshDeserialize, PartialEq, Eq, Debug, Clone)] 22 | pub struct DataV2 { 23 | /// The name of the asset 24 | pub name: String, 25 | /// The symbol for the asset 26 | pub symbol: String, 27 | /// URI pointing to JSON representing the asset 28 | pub uri: String, 29 | /// Royalty basis points that goes to creators in secondary sales (0-10000) 30 | pub seller_fee_basis_points: u16, 31 | /// Array of creators, optional 32 | pub creators: Option>, 33 | /// Collection 34 | pub collection: Option, 35 | /// Uses 36 | pub uses: Option, 37 | } 38 | 39 | impl DataV2 { 40 | pub fn to_v1(&self) -> Data { 41 | let ns = self.clone(); 42 | Data { 43 | name: ns.name, 44 | symbol: ns.symbol, 45 | uri: ns.uri, 46 | seller_fee_basis_points: ns.seller_fee_basis_points, 47 | creators: ns.creators, 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /mpl_token_metadata/src/mpl_token_metadata/state/delegate.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | use substreams_solana_utils::pubkey::Pubkey; 3 | use super::super::utils::try_from_slice_checked; 4 | 5 | const SIZE: usize = 98; 6 | 7 | #[derive(BorshDeserialize, PartialEq, Eq, Debug, Clone)] 8 | /// SEEDS = [ 9 | /// "metadata", 10 | /// program id, 11 | /// mint id, 12 | /// delegate role, 13 | /// update authority id, 14 | /// delegate id 15 | /// ] 16 | pub struct MetadataDelegateRecord { 17 | pub key: Key, // 1 18 | pub bump: u8, // 1 19 | #[cfg_attr(feature = "serde-feature", serde(with = "As::"))] 20 | pub mint: Pubkey, // 32 21 | #[cfg_attr(feature = "serde-feature", serde(with = "As::"))] 22 | pub delegate: Pubkey, // 32 23 | #[cfg_attr(feature = "serde-feature", serde(with = "As::"))] 24 | pub update_authority: Pubkey, // 32 25 | } 26 | 27 | impl Default for MetadataDelegateRecord { 28 | fn default() -> Self { 29 | Self { 30 | key: Key::MetadataDelegate, 31 | bump: 255, 32 | mint: Pubkey::default(), 33 | delegate: Pubkey::default(), 34 | update_authority: Pubkey::default(), 35 | } 36 | } 37 | } 38 | 39 | impl TokenMetadataAccount for MetadataDelegateRecord { 40 | fn key() -> Key { 41 | Key::MetadataDelegate 42 | } 43 | 44 | fn size() -> usize { 45 | SIZE 46 | } 47 | } 48 | 49 | impl MetadataDelegateRecord { 50 | pub fn from_bytes(data: &[u8]) -> Result { 51 | let delegate: MetadataDelegateRecord = 52 | try_from_slice_checked(data, Key::MetadataDelegate, MetadataDelegateRecord::size())?; 53 | Ok(delegate) 54 | } 55 | } 56 | 57 | #[repr(C)] 58 | #[cfg_attr(feature = "serde-feature", derive(Serialize, Deserialize))] 59 | #[derive(BorshDeserialize, PartialEq, Eq, Debug, Clone)] 60 | /// SEEDS = [ 61 | /// "metadata", 62 | /// program id, 63 | /// mint id, 64 | /// delegate role, 65 | /// holder id, 66 | /// delegate id 67 | /// ] 68 | pub struct HolderDelegateRecord { 69 | pub key: Key, // 1 70 | pub bump: u8, // 1 71 | #[cfg_attr(feature = "serde-feature", serde(with = "As::"))] 72 | pub mint: Pubkey, // 32 73 | #[cfg_attr(feature = "serde-feature", serde(with = "As::"))] 74 | pub delegate: Pubkey, // 32 75 | #[cfg_attr(feature = "serde-feature", serde(with = "As::"))] 76 | pub update_authority: Pubkey, // 32 77 | } 78 | 79 | impl Default for HolderDelegateRecord { 80 | fn default() -> Self { 81 | Self { 82 | key: Key::HolderDelegate, 83 | bump: 255, 84 | mint: Pubkey::default(), 85 | delegate: Pubkey::default(), 86 | update_authority: Pubkey::default(), 87 | } 88 | } 89 | } 90 | 91 | impl TokenMetadataAccount for HolderDelegateRecord { 92 | fn key() -> Key { 93 | Key::HolderDelegate 94 | } 95 | 96 | fn size() -> usize { 97 | SIZE 98 | } 99 | } 100 | 101 | impl HolderDelegateRecord { 102 | pub fn from_bytes(data: &[u8]) -> Result { 103 | let delegate: HolderDelegateRecord = 104 | try_from_slice_checked(data, Key::HolderDelegate, HolderDelegateRecord::size())?; 105 | Ok(delegate) 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /mpl_token_metadata/src/mpl_token_metadata/state/edition.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | use substreams_solana_utils::pubkey::Pubkey; 3 | 4 | pub const MAX_EDITION_LEN: usize = 1 + 32 + 8 + 200; 5 | 6 | // The last byte of the account contains the token standard value for 7 | // pNFT assets. This is used to restrict legacy operations on the master 8 | // edition account. 9 | pub const TOKEN_STANDARD_INDEX_EDITION: usize = MAX_EDITION_LEN - 1; 10 | 11 | #[repr(C)] 12 | #[cfg_attr(feature = "serde-feature", derive(Serialize, Deserialize))] 13 | #[derive(Clone, Debug, PartialEq, Eq, BorshDeserialize)] 14 | /// All Editions should never have a supply greater than 1. 15 | /// To enforce this, a transfer mint authority instruction will happen when 16 | /// a normal token is turned into an Edition, and in order for a Metadata update authority 17 | /// to do this transaction they will also need to sign the transaction as the Mint authority. 18 | pub struct Edition { 19 | pub key: Key, 20 | 21 | /// Points at MasterEdition struct 22 | #[cfg_attr(feature = "serde-feature", serde(with = "As::"))] 23 | pub parent: Pubkey, 24 | 25 | /// Starting at 0 for master record, this is incremented for each edition minted. 26 | pub edition: u64, 27 | } 28 | 29 | impl Default for Edition { 30 | fn default() -> Self { 31 | Edition { 32 | key: Key::EditionV1, 33 | parent: Pubkey::default(), 34 | edition: 0, 35 | } 36 | } 37 | } 38 | 39 | impl TokenMetadataAccount for Edition { 40 | fn key() -> Key { 41 | Key::EditionV1 42 | } 43 | 44 | fn size() -> usize { 45 | MAX_EDITION_LEN 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /mpl_token_metadata/src/mpl_token_metadata/state/edition_marker.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | pub const MAX_EDITION_MARKER_SIZE: usize = 32; 4 | 5 | pub const EDITION_MARKER_BIT_SIZE: u64 = 248; 6 | 7 | #[repr(C)] 8 | #[cfg_attr(feature = "serde-feature", derive(Serialize, Deserialize))] 9 | #[derive(BorshSerialize, BorshDeserialize, PartialEq, Eq, Debug, Clone)] 10 | pub struct EditionMarker { 11 | pub key: Key, 12 | pub ledger: [u8; 31], 13 | } 14 | 15 | impl Default for EditionMarker { 16 | fn default() -> Self { 17 | Self { 18 | key: Key::EditionMarker, 19 | ledger: [0; 31], 20 | } 21 | } 22 | } 23 | 24 | impl TokenMetadataAccount for EditionMarker { 25 | fn key() -> Key { 26 | Key::EditionMarker 27 | } 28 | 29 | fn size() -> usize { 30 | MAX_EDITION_MARKER_SIZE 31 | } 32 | } 33 | 34 | impl EditionMarker { 35 | // fn get_edition_offset_from_starting_index(edition: u64) -> Result { 36 | // Ok(edition 37 | // .checked_rem(EDITION_MARKER_BIT_SIZE) 38 | // .ok_or(MetadataError::NumericalOverflowError)? as usize) 39 | // } 40 | 41 | // fn get_index(offset_from_start: usize) -> Result { 42 | // let index = offset_from_start 43 | // .checked_div(8) 44 | // .ok_or(MetadataError::NumericalOverflowError)?; 45 | 46 | // // With only EDITION_MARKER_BIT_SIZE bits, or 31 bytes, we have a max constraint here. 47 | // if index > 30 { 48 | // return Err(MetadataError::InvalidEditionIndex.into()); 49 | // } 50 | 51 | // Ok(index) 52 | // } 53 | 54 | // fn get_offset_from_right(offset_from_start: usize) -> Result { 55 | // // We're saying the left hand side of a u8 is the 0th index so to get a 1 in that 0th index 56 | // // you need to shift a 1 over 8 spots from the right hand side. To do that you actually 57 | // // need not 00000001 but 10000000 which you can get by simply multiplying 1 by 2^7, 128 and then ORing 58 | // // it with the current value. 59 | // Ok(7 - offset_from_start 60 | // .checked_rem(8) 61 | // .ok_or(MetadataError::NumericalOverflowError)? as u32) 62 | // } 63 | 64 | // pub fn get_index_and_mask(edition: u64) -> Result<(usize, u8), ProgramError> { 65 | // // How many editions off we are from edition at 0th index 66 | // let offset_from_start = EditionMarker::get_edition_offset_from_starting_index(edition)?; 67 | 68 | // // How many whole u8s we are from the u8 at the 0th index, which basically dividing by 8 69 | // let index = EditionMarker::get_index(offset_from_start)?; 70 | 71 | // // what position in the given u8 bitset are we (remainder math) 72 | // let my_position_in_index_starting_from_right = 73 | // EditionMarker::get_offset_from_right(offset_from_start)?; 74 | 75 | // Ok((index, u8::pow(2, my_position_in_index_starting_from_right))) 76 | // } 77 | 78 | // pub fn edition_taken(&self, edition: u64) -> Result { 79 | // let (index, mask) = EditionMarker::get_index_and_mask(edition)?; 80 | 81 | // // apply mask with bitwise and with a 1 to determine if it is set or not 82 | // let applied_mask = self.ledger[index] & mask; 83 | 84 | // // What remains should not equal 0. 85 | // Ok(applied_mask != 0) 86 | // } 87 | } 88 | -------------------------------------------------------------------------------- /mpl_token_metadata/src/mpl_token_metadata/state/edition_marker_v2.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | #[derive(BorshSerialize, BorshDeserialize, PartialEq, Eq, Debug, Clone)] 4 | pub struct EditionMarkerV2 { 5 | pub key: Key, 6 | pub ledger: Vec, 7 | } 8 | 9 | impl Default for EditionMarkerV2 { 10 | fn default() -> Self { 11 | Self { 12 | key: Key::EditionMarkerV2, 13 | ledger: vec![], 14 | } 15 | } 16 | } 17 | 18 | impl TokenMetadataAccount for EditionMarkerV2 { 19 | fn key() -> Key { 20 | Key::EditionMarkerV2 21 | } 22 | 23 | fn size() -> usize { 24 | 0 25 | } 26 | } 27 | 28 | impl EditionMarkerV2 { 29 | // fn get_index(offset_from_start: usize) -> Result { 30 | // let index = offset_from_start 31 | // .checked_div(8) 32 | // .ok_or(MetadataError::NumericalOverflowError)?; 33 | 34 | // Ok(index) 35 | // } 36 | 37 | // fn get_offset_from_right(offset_from_start: usize) -> Result { 38 | // // We're saying the left hand side of a u8 is the 0th index so to get a 1 in that 0th index 39 | // // you need to shift a 1 over 8 spots from the right hand side. To do that you actually 40 | // // need not 00000001 but 10000000 which you can get by simply multiplying 1 by 2^7, 128 and then ORing 41 | // // it with the current value. 42 | // Ok(7 - offset_from_start 43 | // .checked_rem(8) 44 | // .ok_or(MetadataError::NumericalOverflowError)? as u32) 45 | // } 46 | 47 | // pub fn get_index_and_mask(edition: u64) -> Result<(usize, u8), ProgramError> { 48 | // let edition = edition 49 | // .try_into() 50 | // .map_err(|_| MetadataError::NumericalOverflowError)?; 51 | // // How many whole u8s we are from the u8 at the 0th index, which basically dividing by 8 52 | // let index = EditionMarkerV2::get_index(edition)?; 53 | 54 | // // what position in the given u8 bitset are we (remainder math) 55 | // let my_position_in_index_starting_from_right = 56 | // EditionMarkerV2::get_offset_from_right(edition)?; 57 | 58 | // Ok((index, 1u8 << my_position_in_index_starting_from_right)) 59 | // } 60 | 61 | // pub fn edition_taken(&self, edition: u64) -> Result { 62 | // let (index, mask) = EditionMarkerV2::get_index_and_mask(edition)?; 63 | 64 | // // If the ledger is smaller than the index, then it's not taken. 65 | // if self.ledger.len() <= index { 66 | // Ok(false) 67 | // } else { 68 | // // apply mask with bitwise and with a 1 to determine if it is set or not 69 | // let applied_mask = self.ledger[index] & mask; 70 | 71 | // // What remains should not equal 0. 72 | // Ok(applied_mask != 0) 73 | // } 74 | // } 75 | } 76 | -------------------------------------------------------------------------------- /mpl_token_metadata/src/mpl_token_metadata/state/escrow.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | use substreams_solana_utils::pubkey::Pubkey; 3 | 4 | pub const ESCROW_POSTFIX: &str = "escrow"; 5 | 6 | #[repr(C)] 7 | #[cfg_attr(feature = "serde-feature", derive(Serialize, Deserialize))] 8 | #[derive(BorshDeserialize, PartialEq, Eq, Debug, Clone, Copy)] 9 | pub enum EscrowAuthority { 10 | TokenOwner, 11 | Creator(Pubkey), 12 | } 13 | 14 | impl EscrowAuthority { 15 | // pub fn to_seeds(&self) -> Vec<&[u8]> { 16 | // match self { 17 | // EscrowAuthority::TokenOwner => vec![&[0]], 18 | // EscrowAuthority::Creator(creator) => vec![&[1], creator.as_ref()], 19 | // } 20 | // } 21 | } 22 | 23 | #[repr(C)] 24 | #[cfg_attr(feature = "serde-feature", derive(Serialize, Deserialize))] 25 | #[derive(BorshDeserialize, PartialEq, Eq, Debug, Clone)] 26 | pub struct TokenOwnedEscrow { 27 | pub key: Key, 28 | pub base_token: Pubkey, 29 | pub authority: EscrowAuthority, 30 | pub bump: u8, 31 | } 32 | 33 | impl TokenMetadataAccount for TokenOwnedEscrow { 34 | fn key() -> Key { 35 | Key::TokenOwnedEscrow 36 | } 37 | 38 | fn size() -> usize { 39 | 0 40 | } 41 | 42 | fn is_correct_account_type(data: &[u8], data_type: Key, _data_size: usize) -> bool { 43 | let key: Option = Key::from_u8(data[0]); 44 | match key { 45 | Some(key) => key == data_type || key == Key::Uninitialized, 46 | None => false, 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /mpl_token_metadata/src/mpl_token_metadata/state/fee.rs: -------------------------------------------------------------------------------- 1 | // create_metadata_accounts_v3, create, print edition commands 2 | pub const CREATE_FEE: u64 = 10_000_000; 3 | 4 | pub const FEE_FLAG_SET: u8 = 1; 5 | pub const FEE_FLAG_CLEARED: u8 = 0; 6 | -------------------------------------------------------------------------------- /mpl_token_metadata/src/mpl_token_metadata/state/master_edition.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | use substreams_solana_utils::pubkey::Pubkey; 3 | 4 | // Large buffer because the older master editions have two pubkeys in them, 5 | // need to keep two versions same size because the conversion process actually 6 | // changes the same account by rewriting it. 7 | pub const MAX_MASTER_EDITION_LEN: usize = 1 + 9 + 8 + 264; 8 | 9 | // The last byte of the account containts the token standard value for 10 | // pNFT assets. This is used to restrict legacy operations on the master 11 | // edition account. 12 | pub const TOKEN_STANDARD_INDEX: usize = MAX_MASTER_EDITION_LEN - 1; 13 | 14 | // The second to last byte of the account contains the fee flag, indicating 15 | // if the account has fees available for retrieval. 16 | pub const MASTER_EDITION_FEE_FLAG_INDEX: usize = MAX_MASTER_EDITION_LEN - 2; 17 | 18 | pub trait MasterEdition { 19 | fn key(&self) -> Key; 20 | fn supply(&self) -> u64; 21 | fn set_supply(&mut self, supply: u64); 22 | fn max_supply(&self) -> Option; 23 | } 24 | 25 | #[derive(Clone, Debug, PartialEq, Eq, BorshDeserialize)] 26 | pub struct MasterEditionV2 { 27 | pub key: Key, 28 | 29 | pub supply: u64, 30 | 31 | pub max_supply: Option, 32 | } 33 | 34 | impl Default for MasterEditionV2 { 35 | fn default() -> Self { 36 | MasterEditionV2 { 37 | key: Key::MasterEditionV2, 38 | supply: 0, 39 | max_supply: Some(0), 40 | } 41 | } 42 | } 43 | 44 | impl TokenMetadataAccount for MasterEditionV2 { 45 | fn key() -> Key { 46 | Key::MasterEditionV2 47 | } 48 | 49 | fn size() -> usize { 50 | MAX_MASTER_EDITION_LEN 51 | } 52 | } 53 | 54 | impl MasterEdition for MasterEditionV2 { 55 | fn key(&self) -> Key { 56 | self.key 57 | } 58 | 59 | fn supply(&self) -> u64 { 60 | self.supply 61 | } 62 | 63 | fn set_supply(&mut self, supply: u64) { 64 | self.supply = supply; 65 | } 66 | 67 | fn max_supply(&self) -> Option { 68 | self.max_supply 69 | } 70 | } 71 | 72 | #[derive(Clone, Debug, PartialEq, Eq, BorshDeserialize)] 73 | pub struct MasterEditionV1 { 74 | pub key: Key, 75 | 76 | pub supply: u64, 77 | 78 | pub max_supply: Option, 79 | 80 | /// Can be used to mint tokens that give one-time permission to mint a single limited edition. 81 | pub printing_mint: Pubkey, 82 | 83 | /// If you don't know how many printing tokens you are going to need, but you do know 84 | /// you are going to need some amount in the future, you can use a token from this mint. 85 | /// Coming back to token metadata with one of these tokens allows you to mint (one time) 86 | /// any number of printing tokens you want. This is used for instance by Auction Manager 87 | /// with participation NFTs, where we dont know how many people will bid and need participation 88 | /// printing tokens to redeem, so we give it ONE of these tokens to use after the auction is over, 89 | /// because when the auction begins we just dont know how many printing tokens we will need, 90 | /// but at the end we will. At the end it then burns this token with token-metadata to 91 | /// get the printing tokens it needs to give to bidders. Each bidder then redeems a printing token 92 | /// to get their limited editions. 93 | pub one_time_printing_authorization_mint: Pubkey, 94 | } 95 | 96 | impl TokenMetadataAccount for MasterEditionV1 { 97 | fn key() -> Key { 98 | Key::MasterEditionV1 99 | } 100 | 101 | fn size() -> usize { 102 | MAX_MASTER_EDITION_LEN 103 | } 104 | } 105 | 106 | impl MasterEdition for MasterEditionV1 { 107 | fn key(&self) -> Key { 108 | self.key 109 | } 110 | 111 | fn supply(&self) -> u64 { 112 | self.supply 113 | } 114 | 115 | fn max_supply(&self) -> Option { 116 | self.max_supply 117 | } 118 | 119 | fn set_supply(&mut self, supply: u64) { 120 | self.supply = supply; 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /mpl_token_metadata/src/mpl_token_metadata/state/migrate.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | #[repr(C)] 4 | #[cfg_attr(feature = "serde-feature", derive(Serialize, Deserialize))] 5 | #[derive(Clone, BorshSerialize, BorshDeserialize, Debug, PartialEq, Eq)] 6 | pub enum MigrationType { 7 | CollectionV1, 8 | ProgrammableV1, 9 | } 10 | -------------------------------------------------------------------------------- /mpl_token_metadata/src/mpl_token_metadata/state/token_auth_payload.rs: -------------------------------------------------------------------------------- 1 | //! These types exist to give Shank a way to create the Payload type as it 2 | //! cannnot create it from the remote type from mpl-token-auth-rules. 3 | //! Care will need to be taken to ensure they stay synced with any changes in 4 | //! mpl-token-auth-rules. 5 | 6 | use std::collections::HashMap; 7 | 8 | use borsh::BorshDeserialize; 9 | #[cfg(feature = "serde-feature")] 10 | use serde::{Deserialize, Serialize}; 11 | use substreams_solana_utils::pubkey::Pubkey; 12 | 13 | #[repr(C)] 14 | #[cfg_attr(feature = "serde-feature", derive(Serialize, Deserialize))] 15 | #[derive(BorshDeserialize, PartialEq, Eq, Debug, Clone)] 16 | /// A seed path type used by the `DerivedKeyMatch` rule. 17 | struct SeedsVec { 18 | /// The vector of derivation seeds. 19 | seeds: Vec>, 20 | } 21 | 22 | #[repr(C)] 23 | #[cfg_attr(feature = "serde-feature", derive(Serialize, Deserialize))] 24 | #[derive(BorshDeserialize, PartialEq, Eq, Debug, Clone)] 25 | /// A proof type used by the `PubkeyTreeMatch` rule. 26 | struct ProofInfo { 27 | /// The merkle proof. 28 | proof: Vec<[u8; 32]>, 29 | } 30 | 31 | #[repr(C)] 32 | #[cfg_attr(feature = "serde-feature", derive(Serialize, Deserialize))] 33 | #[derive(BorshDeserialize, PartialEq, Eq, Debug, Clone)] 34 | /// Variants representing the different types represented in a payload. 35 | enum PayloadType { 36 | /// A plain `Pubkey`. 37 | Pubkey(Pubkey), 38 | /// PDA derivation seeds. 39 | Seeds(SeedsVec), 40 | /// A merkle proof. 41 | MerkleProof(ProofInfo), 42 | /// A plain `u64` used for `Amount`. 43 | Number(u64), 44 | } 45 | 46 | #[repr(C)] 47 | #[cfg_attr(feature = "serde-feature", derive(Serialize, Deserialize))] 48 | #[derive(BorshDeserialize, PartialEq, Eq, Debug, Clone, Default)] 49 | /// A wrapper type for the payload hashmap. 50 | struct Payload { 51 | /// The payload hashmap. 52 | map: HashMap, 53 | } 54 | -------------------------------------------------------------------------------- /mpl_token_metadata/src/mpl_token_metadata/state/uses.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | use super::super::utils::try_from_slice_checked; 3 | 4 | pub const USE_AUTHORITY_RECORD_SIZE: usize = 18; //8 byte padding 5 | 6 | #[repr(C)] 7 | #[cfg_attr(feature = "serde-feature", derive(Serialize, Deserialize))] 8 | #[derive(BorshSerialize, BorshDeserialize, PartialEq, Eq, Debug, Clone, FromPrimitive)] 9 | pub enum UseMethod { 10 | Burn, 11 | Multiple, 12 | Single, 13 | } 14 | 15 | #[repr(C)] 16 | #[cfg_attr(feature = "serde-feature", derive(Serialize, Deserialize))] 17 | #[derive(BorshSerialize, BorshDeserialize, PartialEq, Eq, Debug, Clone)] 18 | pub struct Uses { 19 | // 17 bytes + Option byte 20 | pub use_method: UseMethod, //1 21 | pub remaining: u64, //8 22 | pub total: u64, //8 23 | } 24 | 25 | #[repr(C)] 26 | #[cfg_attr(feature = "serde-feature", derive(Serialize, Deserialize))] 27 | #[derive(BorshSerialize, BorshDeserialize, PartialEq, Eq, Debug, Clone)] 28 | pub struct UseAuthorityRecord { 29 | pub key: Key, //1 30 | pub allowed_uses: u64, //8 31 | pub bump: u8, 32 | } 33 | 34 | impl Default for UseAuthorityRecord { 35 | fn default() -> Self { 36 | UseAuthorityRecord { 37 | key: Key::UseAuthorityRecord, 38 | allowed_uses: 0, 39 | bump: 255, 40 | } 41 | } 42 | } 43 | 44 | impl TokenMetadataAccount for UseAuthorityRecord { 45 | fn key() -> Key { 46 | Key::UseAuthorityRecord 47 | } 48 | 49 | fn size() -> usize { 50 | USE_AUTHORITY_RECORD_SIZE 51 | } 52 | } 53 | 54 | impl UseAuthorityRecord { 55 | pub fn from_bytes(b: &[u8]) -> Result { 56 | let ua: UseAuthorityRecord = 57 | try_from_slice_checked(b, Key::UseAuthorityRecord, USE_AUTHORITY_RECORD_SIZE)?; 58 | Ok(ua) 59 | } 60 | 61 | pub fn bump_empty(&self) -> bool { 62 | self.bump == 0 && self.key == Key::UseAuthorityRecord 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /mpl_token_metadata/src/mpl_token_metadata/utils.rs: -------------------------------------------------------------------------------- 1 | use super::state::{Key, TokenMetadataAccount}; 2 | use super::error::ProgramError; 3 | 4 | pub fn try_from_slice_checked( 5 | data: &[u8], 6 | data_type: Key, 7 | data_size: usize, 8 | ) -> Result { 9 | if !T::is_correct_account_type(data, data_type, data_size) { 10 | panic!(); 11 | } 12 | 13 | let mut data_mut = data; 14 | let result = T::deserialize(&mut data_mut).unwrap(); 15 | 16 | Ok(result) 17 | } 18 | -------------------------------------------------------------------------------- /mpl_token_metadata/src/pb/mod.rs: -------------------------------------------------------------------------------- 1 | // @generated 2 | // @@protoc_insertion_point(attribute:mpl_token_metadata) 3 | pub mod mpl_token_metadata { 4 | include!("mpl_token_metadata.rs"); 5 | // @@protoc_insertion_point(mpl_token_metadata) 6 | } 7 | -------------------------------------------------------------------------------- /mpl_token_metadata/substreams.yaml: -------------------------------------------------------------------------------- 1 | specVersion: v0.1.0 2 | package: 3 | name: 'mpl_token_metadata_events' 4 | version: v0.1.7 5 | description: MPL Token Metadata events substream 6 | url: https://github.com/0xpapercut/solana-substreams 7 | image: ./sol.png 8 | 9 | imports: 10 | sol: https://spkg.io/streamingfast/solana-common-v0.3.0.spkg 11 | 12 | protobuf: 13 | files: 14 | - mpl_token_metadata.proto 15 | importPaths: 16 | - ./proto 17 | 18 | binaries: 19 | default: 20 | type: wasm/rust-v1 21 | file: target/wasm32-unknown-unknown/release/mpl_token_metadata_substream.wasm 22 | 23 | modules: 24 | - name: mpl_token_metadata_events 25 | kind: map 26 | inputs: 27 | - map: sol:blocks_without_votes 28 | output: 29 | type: proto:mpl_token_metadata.MplTokenMetadataBlockEvents 30 | 31 | network: solana 32 | -------------------------------------------------------------------------------- /pumpfun/.gitignore: -------------------------------------------------------------------------------- 1 | *.spkg 2 | /replay.log 3 | target/ 4 | .idea 5 | .envrc 6 | -------------------------------------------------------------------------------- /pumpfun/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "pumpfun-substream" 3 | version.workspace = true 4 | edition.workspace = true 5 | 6 | [lib] 7 | name = "pumpfun_substream" 8 | crate-type = ["lib", "cdylib"] 9 | 10 | [dependencies] 11 | substreams = { workspace = true } 12 | substreams-solana = { workspace = true } 13 | substreams-solana-utils = { workspace = true } 14 | prost = { workspace = true } 15 | bs58 = { workspace = true } 16 | borsh = { workspace = true } 17 | lazy_static = { workspace = true } 18 | anyhow = { workspace = true } 19 | spl-token-substream = { path = "../spl_token"} 20 | system-program-substream = { path = "../system_program" } 21 | -------------------------------------------------------------------------------- /pumpfun/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 0xpapercut 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /pumpfun/Makefile: -------------------------------------------------------------------------------- 1 | ENDPOINT ?= mainnet.sol.streamingfast.io:443 2 | 3 | .PHONY: build 4 | build: 5 | CARGO_TARGET_DIR=./target cargo build --target wasm32-unknown-unknown --release 6 | 7 | .PHONY: stream 8 | stream: build 9 | if [ -n "$(STOP)" ]; then \ 10 | substreams run -e $(ENDPOINT) substreams.yaml pumpfun_events -s $(START) -t $(STOP); \ 11 | else \ 12 | substreams run -e $(ENDPOINT) substreams.yaml pumpfun_events -s $(START); \ 13 | fi 14 | 15 | .PHONY: protogen 16 | protogen: 17 | substreams protogen ./substreams.yaml --exclude-paths="sf/substreams,google" 18 | 19 | .PHONY: package 20 | package: 21 | substreams pack ./substreams.yaml 22 | -------------------------------------------------------------------------------- /pumpfun/README.md: -------------------------------------------------------------------------------- 1 | # pumpfun-substream 2 | Stream Pumpfun events with [substreams](https://substreams.streamingfast.io). 3 | 4 | ## Usage 5 | ```bash 6 | substreams gui pumpfun-events 7 | ``` 8 | If you see no output, please check that you have set a starting block, e.g. `substreams gui pumpfun-events -s 300000000`. 9 | -------------------------------------------------------------------------------- /pumpfun/buf.gen.yaml: -------------------------------------------------------------------------------- 1 | 2 | version: v1 3 | plugins: 4 | - plugin: buf.build/community/neoeinstein-prost:v0.2.2 5 | out: src/pb 6 | opt: 7 | - file_descriptor_set=false 8 | 9 | - plugin: buf.build/community/neoeinstein-prost-crate:v0.3.1 10 | out: src/pb 11 | opt: 12 | - no_features 13 | -------------------------------------------------------------------------------- /pumpfun/proto/pumpfun.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package pumpfun; 4 | 5 | message PumpfunBlockEvents { 6 | repeated PumpfunTransactionEvents transactions = 1; 7 | } 8 | 9 | message PumpfunTransactionEvents { 10 | string signature = 1; 11 | repeated PumpfunEvent events = 2; 12 | } 13 | 14 | message PumpfunEvent { 15 | oneof event { 16 | InitializeEvent initialize = 1; 17 | SetParamsEvent set_params = 2; 18 | SwapEvent swap = 3; 19 | WithdrawEvent withdraw = 4; 20 | CreateEvent create = 5; 21 | } 22 | } 23 | 24 | message CreateEvent { 25 | string user = 1; 26 | string name = 2; 27 | string symbol = 3; 28 | string uri = 4; 29 | string mint = 5; 30 | string bonding_curve = 6; 31 | string associated_bonding_curve = 7; 32 | string metadata = 8; 33 | } 34 | 35 | message InitializeEvent { 36 | string user = 1; 37 | } 38 | 39 | message SetParamsEvent { 40 | string user = 1; 41 | string fee_recipient = 2; 42 | uint64 initial_virtual_token_reserves = 3; 43 | uint64 initial_virtual_sol_reserves = 4; 44 | uint64 initial_real_token_reserves = 5; 45 | uint64 token_total_supply = 6; 46 | uint64 fee_basis_points = 7; 47 | } 48 | 49 | message SwapEvent { 50 | string user = 1; 51 | string mint = 2; 52 | string bonding_curve = 3; 53 | optional uint64 sol_amount = 4; 54 | uint64 token_amount = 5; 55 | string direction = 6; 56 | optional uint64 virtual_sol_reserves = 7; 57 | optional uint64 virtual_token_reserves = 8; 58 | optional uint64 real_sol_reserves = 9; 59 | optional uint64 real_token_reserves = 10; 60 | optional uint64 user_token_pre_balance = 11; 61 | } 62 | 63 | message WithdrawEvent { 64 | string mint = 1; 65 | } 66 | -------------------------------------------------------------------------------- /pumpfun/pumpfun.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0xbolt/solana-substreams/903c4ea30e3764f7dc290b5a461666b9f2060ce6/pumpfun/pumpfun.png -------------------------------------------------------------------------------- /pumpfun/src/pb/event.rs: -------------------------------------------------------------------------------- 1 | // @generated 2 | #[allow(clippy::derive_partial_eq_without_eq)] 3 | #[derive(Clone, PartialEq, ::prost::Message)] 4 | pub struct Events { 5 | #[prost(message, repeated, tag="1")] 6 | pub events: ::prost::alloc::vec::Vec, 7 | } 8 | #[allow(clippy::derive_partial_eq_without_eq)] 9 | #[derive(Clone, PartialEq, ::prost::Message)] 10 | pub struct Event { 11 | #[prost(string, tag="1")] 12 | pub signer: ::prost::alloc::string::String, 13 | #[prost(string, tag="2")] 14 | pub signature: ::prost::alloc::string::String, 15 | #[prost(uint64, tag="3")] 16 | pub slot: u64, 17 | #[prost(message, optional, tag="4")] 18 | pub event: ::core::option::Option, 19 | } 20 | #[allow(clippy::derive_partial_eq_without_eq)] 21 | #[derive(Clone, PartialEq, ::prost::Message)] 22 | pub struct RaydiumEvent { 23 | #[prost(string, tag="1")] 24 | pub amm: ::prost::alloc::string::String, 25 | #[prost(enumeration="EventType", tag="2")] 26 | pub r#type: i32, 27 | #[prost(oneof="raydium_event::Data", tags="3, 4, 5, 6")] 28 | pub data: ::core::option::Option, 29 | } 30 | /// Nested message and enum types in `RaydiumEvent`. 31 | pub mod raydium_event { 32 | #[allow(clippy::derive_partial_eq_without_eq)] 33 | #[derive(Clone, PartialEq, ::prost::Oneof)] 34 | pub enum Data { 35 | #[prost(message, tag="3")] 36 | Initialize(super::InitializeData), 37 | #[prost(message, tag="4")] 38 | Deposit(super::DepositData), 39 | #[prost(message, tag="5")] 40 | Withdraw(super::WithdrawData), 41 | #[prost(message, tag="6")] 42 | Swap(super::SwapData), 43 | } 44 | } 45 | #[allow(clippy::derive_partial_eq_without_eq)] 46 | #[derive(Clone, PartialEq, ::prost::Message)] 47 | pub struct InitializeData { 48 | #[prost(uint64, tag="1")] 49 | pub pc_init_amount: u64, 50 | #[prost(uint64, tag="2")] 51 | pub coin_init_amount: u64, 52 | #[prost(uint64, tag="3")] 53 | pub lp_init_amount: u64, 54 | } 55 | #[allow(clippy::derive_partial_eq_without_eq)] 56 | #[derive(Clone, PartialEq, ::prost::Message)] 57 | pub struct DepositData { 58 | #[prost(uint64, tag="1")] 59 | pub pc_amount: u64, 60 | #[prost(uint64, tag="2")] 61 | pub coin_amount: u64, 62 | #[prost(uint64, tag="3")] 63 | pub lp_amount: u64, 64 | } 65 | #[allow(clippy::derive_partial_eq_without_eq)] 66 | #[derive(Clone, PartialEq, ::prost::Message)] 67 | pub struct WithdrawData { 68 | #[prost(uint64, tag="1")] 69 | pub pc_amount: u64, 70 | #[prost(uint64, tag="2")] 71 | pub coin_amount: u64, 72 | #[prost(uint64, tag="3")] 73 | pub lp_amount: u64, 74 | } 75 | #[allow(clippy::derive_partial_eq_without_eq)] 76 | #[derive(Clone, PartialEq, ::prost::Message)] 77 | pub struct SwapData { 78 | #[prost(string, tag="1")] 79 | pub mint_in: ::prost::alloc::string::String, 80 | #[prost(string, tag="2")] 81 | pub mint_out: ::prost::alloc::string::String, 82 | #[prost(uint64, tag="3")] 83 | pub amount_in: u64, 84 | #[prost(uint64, tag="4")] 85 | pub amount_out: u64, 86 | } 87 | #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)] 88 | #[repr(i32)] 89 | pub enum EventType { 90 | Initialize = 0, 91 | Deposit = 1, 92 | Withdraw = 2, 93 | Swap = 3, 94 | } 95 | impl EventType { 96 | /// String value of the enum field names used in the ProtoBuf definition. 97 | /// 98 | /// The values are not transformed in any way and thus are considered stable 99 | /// (if the ProtoBuf definition does not change) and safe for programmatic use. 100 | pub fn as_str_name(&self) -> &'static str { 101 | match self { 102 | EventType::Initialize => "INITIALIZE", 103 | EventType::Deposit => "DEPOSIT", 104 | EventType::Withdraw => "WITHDRAW", 105 | EventType::Swap => "SWAP", 106 | } 107 | } 108 | /// Creates an enum from field names used in the ProtoBuf definition. 109 | pub fn from_str_name(value: &str) -> ::core::option::Option { 110 | match value { 111 | "INITIALIZE" => Some(Self::Initialize), 112 | "DEPOSIT" => Some(Self::Deposit), 113 | "WITHDRAW" => Some(Self::Withdraw), 114 | "SWAP" => Some(Self::Swap), 115 | _ => None, 116 | } 117 | } 118 | } 119 | // @@protoc_insertion_point(module) 120 | -------------------------------------------------------------------------------- /pumpfun/src/pb/mod.rs: -------------------------------------------------------------------------------- 1 | // @generated 2 | // @@protoc_insertion_point(attribute:pumpfun) 3 | pub mod pumpfun { 4 | include!("pumpfun.rs"); 5 | // @@protoc_insertion_point(pumpfun) 6 | } 7 | -------------------------------------------------------------------------------- /pumpfun/src/pb/pumpfun.rs: -------------------------------------------------------------------------------- 1 | // @generated 2 | #[allow(clippy::derive_partial_eq_without_eq)] 3 | #[derive(Clone, PartialEq, ::prost::Message)] 4 | pub struct PumpfunBlockEvents { 5 | #[prost(message, repeated, tag="1")] 6 | pub transactions: ::prost::alloc::vec::Vec, 7 | } 8 | #[allow(clippy::derive_partial_eq_without_eq)] 9 | #[derive(Clone, PartialEq, ::prost::Message)] 10 | pub struct PumpfunTransactionEvents { 11 | #[prost(string, tag="1")] 12 | pub signature: ::prost::alloc::string::String, 13 | #[prost(message, repeated, tag="2")] 14 | pub events: ::prost::alloc::vec::Vec, 15 | } 16 | #[allow(clippy::derive_partial_eq_without_eq)] 17 | #[derive(Clone, PartialEq, ::prost::Message)] 18 | pub struct PumpfunEvent { 19 | #[prost(oneof="pumpfun_event::Event", tags="1, 2, 3, 4, 5")] 20 | pub event: ::core::option::Option, 21 | } 22 | /// Nested message and enum types in `PumpfunEvent`. 23 | pub mod pumpfun_event { 24 | #[allow(clippy::derive_partial_eq_without_eq)] 25 | #[derive(Clone, PartialEq, ::prost::Oneof)] 26 | pub enum Event { 27 | #[prost(message, tag="1")] 28 | Initialize(super::InitializeEvent), 29 | #[prost(message, tag="2")] 30 | SetParams(super::SetParamsEvent), 31 | #[prost(message, tag="3")] 32 | Swap(super::SwapEvent), 33 | #[prost(message, tag="4")] 34 | Withdraw(super::WithdrawEvent), 35 | #[prost(message, tag="5")] 36 | Create(super::CreateEvent), 37 | } 38 | } 39 | #[allow(clippy::derive_partial_eq_without_eq)] 40 | #[derive(Clone, PartialEq, ::prost::Message)] 41 | pub struct CreateEvent { 42 | #[prost(string, tag="1")] 43 | pub user: ::prost::alloc::string::String, 44 | #[prost(string, tag="2")] 45 | pub name: ::prost::alloc::string::String, 46 | #[prost(string, tag="3")] 47 | pub symbol: ::prost::alloc::string::String, 48 | #[prost(string, tag="4")] 49 | pub uri: ::prost::alloc::string::String, 50 | #[prost(string, tag="5")] 51 | pub mint: ::prost::alloc::string::String, 52 | #[prost(string, tag="6")] 53 | pub bonding_curve: ::prost::alloc::string::String, 54 | #[prost(string, tag="7")] 55 | pub associated_bonding_curve: ::prost::alloc::string::String, 56 | #[prost(string, tag="8")] 57 | pub metadata: ::prost::alloc::string::String, 58 | } 59 | #[allow(clippy::derive_partial_eq_without_eq)] 60 | #[derive(Clone, PartialEq, ::prost::Message)] 61 | pub struct InitializeEvent { 62 | #[prost(string, tag="1")] 63 | pub user: ::prost::alloc::string::String, 64 | } 65 | #[allow(clippy::derive_partial_eq_without_eq)] 66 | #[derive(Clone, PartialEq, ::prost::Message)] 67 | pub struct SetParamsEvent { 68 | #[prost(string, tag="1")] 69 | pub user: ::prost::alloc::string::String, 70 | #[prost(string, tag="2")] 71 | pub fee_recipient: ::prost::alloc::string::String, 72 | #[prost(uint64, tag="3")] 73 | pub initial_virtual_token_reserves: u64, 74 | #[prost(uint64, tag="4")] 75 | pub initial_virtual_sol_reserves: u64, 76 | #[prost(uint64, tag="5")] 77 | pub initial_real_token_reserves: u64, 78 | #[prost(uint64, tag="6")] 79 | pub token_total_supply: u64, 80 | #[prost(uint64, tag="7")] 81 | pub fee_basis_points: u64, 82 | } 83 | #[allow(clippy::derive_partial_eq_without_eq)] 84 | #[derive(Clone, PartialEq, ::prost::Message)] 85 | pub struct SwapEvent { 86 | #[prost(string, tag="1")] 87 | pub user: ::prost::alloc::string::String, 88 | #[prost(string, tag="2")] 89 | pub mint: ::prost::alloc::string::String, 90 | #[prost(string, tag="3")] 91 | pub bonding_curve: ::prost::alloc::string::String, 92 | #[prost(uint64, optional, tag="4")] 93 | pub sol_amount: ::core::option::Option, 94 | #[prost(uint64, tag="5")] 95 | pub token_amount: u64, 96 | #[prost(string, tag="6")] 97 | pub direction: ::prost::alloc::string::String, 98 | #[prost(uint64, optional, tag="7")] 99 | pub virtual_sol_reserves: ::core::option::Option, 100 | #[prost(uint64, optional, tag="8")] 101 | pub virtual_token_reserves: ::core::option::Option, 102 | #[prost(uint64, optional, tag="9")] 103 | pub real_sol_reserves: ::core::option::Option, 104 | #[prost(uint64, optional, tag="10")] 105 | pub real_token_reserves: ::core::option::Option, 106 | #[prost(uint64, optional, tag="11")] 107 | pub user_token_pre_balance: ::core::option::Option, 108 | } 109 | #[allow(clippy::derive_partial_eq_without_eq)] 110 | #[derive(Clone, PartialEq, ::prost::Message)] 111 | pub struct WithdrawEvent { 112 | #[prost(string, tag="1")] 113 | pub mint: ::prost::alloc::string::String, 114 | } 115 | // @@protoc_insertion_point(module) 116 | -------------------------------------------------------------------------------- /pumpfun/src/pumpfun/constants.rs: -------------------------------------------------------------------------------- 1 | use substreams_solana_utils::pubkey::Pubkey; 2 | use substreams_solana::b58; 3 | 4 | pub const PUMPFUN_PROGRAM_ID: Pubkey = Pubkey(b58!("6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P")); 5 | -------------------------------------------------------------------------------- /pumpfun/src/pumpfun/instruction.rs: -------------------------------------------------------------------------------- 1 | use borsh::BorshDeserialize; 2 | use substreams_solana_utils::pubkey::Pubkey; 3 | 4 | #[derive(Debug, BorshDeserialize)] 5 | pub enum PumpfunInstruction { 6 | Initialize, 7 | SetParams(SetParamsInstruction), 8 | Create(CreateInstruction), 9 | Buy(BuyInstruction), 10 | Sell(SellInstruction), 11 | Withdraw, 12 | Unknown, 13 | } 14 | 15 | impl PumpfunInstruction { 16 | pub fn unpack(data: &[u8]) -> Result { 17 | let (tag, data) = data.split_at(8); 18 | match tag { 19 | [175, 175, 109, 31, 13, 152, 155, 237] => Ok(Self::Initialize), 20 | [165, 31, 134, 53, 189, 180, 130, 255] => Ok(Self::SetParams(SetParamsInstruction::unpack(data)?)), 21 | [24, 30, 200, 40, 5, 28, 7, 119] => Ok(Self::Create(CreateInstruction::unpack(data)?)), 22 | [102, 6, 61, 18, 1, 218, 235, 234] => Ok(Self::Buy(BuyInstruction::unpack(data)?)), 23 | [51, 230, 133, 164, 1, 127, 131, 173] => Ok(Self::Sell(SellInstruction::unpack(data)?)), 24 | [183, 18, 70, 156, 148, 109, 161, 34] => Ok(Self::Withdraw), 25 | _ => Ok(Self::Unknown), 26 | } 27 | } 28 | } 29 | 30 | #[derive(Debug, BorshDeserialize)] 31 | pub struct SetParamsInstruction { 32 | pub fee_recipient: Pubkey, 33 | pub initial_virtual_token_reserves: u64, 34 | pub initial_virtual_sol_reserves: u64, 35 | pub initial_real_token_reserves: u64, 36 | pub token_total_supply: u64, 37 | pub fee_basis_points: u64, 38 | } 39 | 40 | impl SetParamsInstruction { 41 | fn unpack(data: &[u8]) -> Result { 42 | Self::deserialize(&mut &data[..]).map_err(|_| "Failed to deserialize SetParamsInstruction.") 43 | } 44 | } 45 | 46 | #[derive(Debug, BorshDeserialize)] 47 | pub struct CreateInstruction { 48 | pub name: String, 49 | pub symbol: String, 50 | pub uri: String, 51 | } 52 | 53 | impl CreateInstruction { 54 | fn unpack(data: &[u8]) -> Result { 55 | Self::deserialize(&mut &data[..]).map_err(|_| "Failed to deserialize CreateInstruction.") 56 | } 57 | } 58 | 59 | #[derive(Debug, BorshDeserialize)] 60 | pub struct BuyInstruction { 61 | pub amount: u64, 62 | pub max_sol_cost: u64, 63 | } 64 | 65 | impl BuyInstruction { 66 | fn unpack(data: &[u8]) -> Result { 67 | Self::deserialize(&mut &data[..]).map_err(|_| "Failed to deserialize BuyInstruction.") 68 | } 69 | } 70 | 71 | #[derive(Debug, BorshDeserialize)] 72 | pub struct SellInstruction { 73 | pub amount: u64, 74 | pub min_sol_output: u64, 75 | } 76 | 77 | impl SellInstruction { 78 | fn unpack(data: &[u8]) -> Result { 79 | Self::deserialize(&mut &data[..]).map_err(|_| "Failed to deserialize SellInstruction.") 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /pumpfun/src/pumpfun/log.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::{self, Display}; 2 | use borsh::BorshDeserialize; 3 | 4 | #[derive(BorshDeserialize)] 5 | pub struct Pubkey(pub [u8; 32]); 6 | 7 | impl Display for Pubkey { 8 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 9 | write!(f, "{}", bs58::encode(self.0).into_string()) 10 | } 11 | } 12 | 13 | impl fmt::Debug for Pubkey { 14 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 15 | f.debug_tuple("Pubkey") 16 | .field(&bs58::encode(self.0).into_string()) 17 | .finish() 18 | } 19 | } 20 | 21 | #[derive(Debug)] 22 | pub enum PumpfunLog { 23 | Create(CreateLog), 24 | Trade(TradeLog), 25 | Complete(CompleteLog), 26 | SetParams(SetParamsLog), 27 | } 28 | 29 | impl PumpfunLog { 30 | pub fn unpack(data: &[u8]) -> Result { 31 | let (discriminator, data) = data.split_at(8); 32 | match discriminator { 33 | [27, 114, 169, 77, 222, 235, 99, 118] => CreateLog::try_from_slice(data).map(Self::Create).map_err(|_| "Failed to unpack CreateEvent."), 34 | [189, 219, 127, 211, 78, 230, 97, 238] => TradeLog::try_from_slice(data).map(Self::Trade).map_err(|_| "Failed to unpack TradeEvent."), 35 | [95, 114, 97, 156, 212, 46, 152, 8] => CompleteLog::try_from_slice(data).map(Self::Complete).map_err(|_| "Failed to unpack CompleteEvent."), 36 | [223, 195, 159, 246, 62, 48, 143, 131] => SetParamsLog::try_from_slice(data).map(Self::SetParams).map_err(|_| "Failed to unpack SetParamsEvent."), 37 | _ => Err("Unknown Pumpfun event."), 38 | } 39 | } 40 | } 41 | 42 | #[derive(Debug, BorshDeserialize)] 43 | pub struct CreateLog { 44 | pub name: String, 45 | pub symbol: String, 46 | pub uri: String, 47 | pub mint: Pubkey, 48 | pub bonding_curve: Pubkey, 49 | pub user: Pubkey, 50 | } 51 | 52 | #[derive(Debug, BorshDeserialize)] 53 | pub struct TradeLog { 54 | pub mint: Pubkey, 55 | pub sol_amount: u64, 56 | pub token_amount: u64, 57 | pub is_buy: bool, 58 | pub user: Pubkey, 59 | pub timestamp: i64, 60 | pub virtual_sol_reserves: u64, 61 | pub virtual_token_reserves: u64, 62 | pub real_sol_reserves: u64, 63 | pub real_token_reserves: u64, 64 | } 65 | 66 | #[derive(Debug, BorshDeserialize)] 67 | pub struct CompleteLog { 68 | pub user: Pubkey, 69 | pub mint: Pubkey, 70 | pub bonding_curve: Pubkey, 71 | pub timestamp: i64, 72 | } 73 | 74 | #[derive(Debug, BorshDeserialize)] 75 | pub struct SetParamsLog { 76 | pub fee_recipient: Pubkey, 77 | pub initial_virtual_token_reserves: u64, 78 | pub initial_virtual_sol_reserves: u64, 79 | pub initial_real_token_reserves: u64, 80 | pub token_total_supply: u64, 81 | pub fee_basis_points: u64, 82 | } 83 | -------------------------------------------------------------------------------- /pumpfun/src/pumpfun/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod instruction; 2 | pub mod constants; 3 | pub use constants::PUMPFUN_PROGRAM_ID; 4 | pub mod log; 5 | -------------------------------------------------------------------------------- /pumpfun/substreams.yaml: -------------------------------------------------------------------------------- 1 | specVersion: v0.1.0 2 | package: 3 | name: 'pumpfun_events' 4 | version: v0.1.7 5 | description: Pumpfun events substream 6 | url: https://github.com/0xpapercut/solana-substreams 7 | image: ./pumpfun.png 8 | 9 | imports: 10 | sol: https://spkg.io/streamingfast/solana-common-v0.3.0.spkg 11 | 12 | protobuf: 13 | files: 14 | - pumpfun.proto 15 | importPaths: 16 | - ./proto 17 | 18 | binaries: 19 | default: 20 | type: wasm/rust-v1 21 | file: target/wasm32-unknown-unknown/release/pumpfun_substream.wasm 22 | 23 | modules: 24 | - name: pumpfun_events 25 | kind: map 26 | inputs: 27 | - map: sol:blocks_without_votes 28 | output: 29 | type: proto:pumpfun.PumpfunBlockEvents 30 | 31 | network: solana 32 | -------------------------------------------------------------------------------- /raydium_amm/.gitignore: -------------------------------------------------------------------------------- 1 | *.spkg 2 | /replay.log 3 | target/ 4 | .idea 5 | .envrc 6 | -------------------------------------------------------------------------------- /raydium_amm/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "raydium-amm-substream" 3 | version.workspace = true 4 | edition.workspace = true 5 | 6 | [lib] 7 | name = "raydium_amm_substream" 8 | crate-type = ["lib", "cdylib"] 9 | 10 | [dependencies] 11 | substreams = { workspace = true } 12 | substreams-solana = { workspace = true } 13 | substreams-solana-utils = { workspace = true } 14 | prost = { workspace = true } 15 | bs58 = { workspace = true } 16 | bincode = { workspace = true } 17 | base64 = { workspace = true } 18 | serde = { workspace = true } 19 | arrayref = { workspace = true } 20 | safe-transmute = { workspace = true } 21 | bytemuck = { workspace = true } 22 | lazy_static = { workspace = true } 23 | regex = { workspace = true } 24 | anyhow = { workspace = true } 25 | spl-token-substream = { path = "../spl_token"} 26 | -------------------------------------------------------------------------------- /raydium_amm/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 0xpapercut 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /raydium_amm/Makefile: -------------------------------------------------------------------------------- 1 | ENDPOINT ?= mainnet.sol.streamingfast.io:443 2 | 3 | .PHONY: build 4 | build: 5 | CARGO_TARGET_DIR=./target cargo build --target wasm32-unknown-unknown --release 6 | 7 | .PHONY: stream 8 | stream: build 9 | if [ -n "$(STOP)" ]; then \ 10 | substreams run -e $(ENDPOINT) substreams.yaml raydium_amm_events -s $(START) -t $(STOP); \ 11 | else \ 12 | substreams run -e $(ENDPOINT) substreams.yaml raydium_amm_events -s $(START); \ 13 | fi 14 | 15 | .PHONY: protogen 16 | protogen: 17 | substreams protogen ./substreams.yaml --exclude-paths="sf/substreams,google" 18 | 19 | .PHONY: package 20 | package: 21 | substreams pack ./substreams.yaml 22 | -------------------------------------------------------------------------------- /raydium_amm/README.md: -------------------------------------------------------------------------------- 1 | # raydium-amm-substream 2 | Stream Raydium events with [substreams](https://substreams.streamingfast.io). 3 | 4 | ## Usage 5 | ```bash 6 | substreams gui raydium-amm-events 7 | ``` 8 | If you see no output, please check that you have set a starting block, e.g. `substreams gui raydium-amm-events -s 300000000`. 9 | 10 | Suported events include swap, initialize, deposit and withdraw. For more information, refer to the [protobuf specification](proto/raydium.proto). 11 | -------------------------------------------------------------------------------- /raydium_amm/buf.gen.yaml: -------------------------------------------------------------------------------- 1 | 2 | version: v1 3 | plugins: 4 | - plugin: buf.build/community/neoeinstein-prost:v0.2.2 5 | out: src/pb 6 | opt: 7 | - file_descriptor_set=false 8 | 9 | - plugin: buf.build/community/neoeinstein-prost-crate:v0.3.1 10 | out: src/pb 11 | opt: 12 | - no_features 13 | -------------------------------------------------------------------------------- /raydium_amm/proto/raydium_amm.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package raydium_amm; 4 | 5 | message RaydiumAmmBlockEvents { 6 | repeated RaydiumAmmTransactionEvents transactions = 2; 7 | } 8 | 9 | message RaydiumAmmTransactionEvents { 10 | string signature = 1; 11 | repeated RaydiumAmmEvent events = 2; 12 | } 13 | 14 | message RaydiumAmmEvent { 15 | oneof event { 16 | InitializeEvent initialize = 1; 17 | DepositEvent deposit = 2; 18 | WithdrawEvent withdraw = 3; 19 | WithdrawPnlEvent withdraw_pnl = 4; 20 | SwapEvent swap = 5; 21 | } 22 | } 23 | 24 | message InitializeEvent { 25 | string amm = 1; 26 | string user = 2; 27 | uint64 pcInitAmount = 3; 28 | uint64 coinInitAmount = 4; 29 | uint64 lpInitAmount = 5; 30 | string pcMint = 6; 31 | string coinMint = 7; 32 | string lpMint = 8; 33 | uint32 nonce = 9; 34 | optional string market = 10; 35 | optional uint64 userPcPreBalance = 11; 36 | optional uint64 userCoinPreBalance = 12; 37 | } 38 | 39 | message DepositEvent { 40 | string amm = 1; 41 | string user = 2; 42 | uint64 pcAmount = 3; 43 | uint64 coinAmount = 4; 44 | uint64 lpAmount = 5; 45 | string pcMint = 6; 46 | string coinMint = 7; 47 | string lpMint = 8; 48 | optional uint64 poolPcAmount = 9; 49 | optional uint64 poolCoinAmount = 10; 50 | optional uint64 poolLpAmount = 11; 51 | optional uint64 userPcPreBalance = 12; 52 | optional uint64 userCoinPreBalance = 13; 53 | } 54 | 55 | message WithdrawEvent { 56 | string amm = 1; 57 | string user = 2; 58 | uint64 pcAmount = 3; 59 | uint64 coinAmount = 4; 60 | uint64 lpAmount = 5; 61 | string pcMint = 6; 62 | string coinMint = 7; 63 | string lpMint = 8; 64 | optional uint64 poolPcAmount = 9; 65 | optional uint64 poolCoinAmount = 10; 66 | optional uint64 poolLpAmount = 11; 67 | optional uint64 userPcPreBalance = 12; 68 | optional uint64 userCoinPreBalance = 13; 69 | } 70 | 71 | message WithdrawPnlEvent { 72 | string amm = 1; 73 | string user = 2; 74 | optional uint64 pcAmount = 3; 75 | optional uint64 coinAmount = 4; 76 | optional string pcMint = 6; 77 | optional string coinMint = 7; 78 | } 79 | 80 | message SwapEvent { 81 | string amm = 1; 82 | string user = 2; 83 | string mintIn = 3; 84 | string mintOut = 4; 85 | uint64 amountIn = 5; 86 | uint64 amountOut = 6; 87 | string direction = 7; 88 | optional uint64 poolPcAmount = 8; 89 | optional uint64 poolCoinAmount = 9; 90 | string pcMint = 10; 91 | string coinMint = 11; 92 | optional uint64 userPreBalanceIn = 12; 93 | optional uint64 userPreBalanceOut = 13; 94 | } 95 | -------------------------------------------------------------------------------- /raydium_amm/raydium.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0xbolt/solana-substreams/903c4ea30e3764f7dc290b5a461666b9f2060ce6/raydium_amm/raydium.png -------------------------------------------------------------------------------- /raydium_amm/src/pb/event.rs: -------------------------------------------------------------------------------- 1 | // @generated 2 | #[allow(clippy::derive_partial_eq_without_eq)] 3 | #[derive(Clone, PartialEq, ::prost::Message)] 4 | pub struct Events { 5 | #[prost(message, repeated, tag="1")] 6 | pub events: ::prost::alloc::vec::Vec, 7 | } 8 | #[allow(clippy::derive_partial_eq_without_eq)] 9 | #[derive(Clone, PartialEq, ::prost::Message)] 10 | pub struct Event { 11 | #[prost(string, tag="1")] 12 | pub signer: ::prost::alloc::string::String, 13 | #[prost(string, tag="2")] 14 | pub signature: ::prost::alloc::string::String, 15 | #[prost(uint64, tag="3")] 16 | pub slot: u64, 17 | #[prost(message, optional, tag="4")] 18 | pub event: ::core::option::Option, 19 | } 20 | #[allow(clippy::derive_partial_eq_without_eq)] 21 | #[derive(Clone, PartialEq, ::prost::Message)] 22 | pub struct RaydiumEvent { 23 | #[prost(string, tag="1")] 24 | pub amm: ::prost::alloc::string::String, 25 | #[prost(enumeration="EventType", tag="2")] 26 | pub r#type: i32, 27 | #[prost(oneof="raydium_event::Data", tags="3, 4, 5, 6")] 28 | pub data: ::core::option::Option, 29 | } 30 | /// Nested message and enum types in `RaydiumEvent`. 31 | pub mod raydium_event { 32 | #[allow(clippy::derive_partial_eq_without_eq)] 33 | #[derive(Clone, PartialEq, ::prost::Oneof)] 34 | pub enum Data { 35 | #[prost(message, tag="3")] 36 | Initialize(super::InitializeData), 37 | #[prost(message, tag="4")] 38 | Deposit(super::DepositData), 39 | #[prost(message, tag="5")] 40 | Withdraw(super::WithdrawData), 41 | #[prost(message, tag="6")] 42 | Swap(super::SwapData), 43 | } 44 | } 45 | #[allow(clippy::derive_partial_eq_without_eq)] 46 | #[derive(Clone, PartialEq, ::prost::Message)] 47 | pub struct InitializeData { 48 | #[prost(uint64, tag="1")] 49 | pub pc_init_amount: u64, 50 | #[prost(uint64, tag="2")] 51 | pub coin_init_amount: u64, 52 | #[prost(uint64, tag="3")] 53 | pub lp_init_amount: u64, 54 | } 55 | #[allow(clippy::derive_partial_eq_without_eq)] 56 | #[derive(Clone, PartialEq, ::prost::Message)] 57 | pub struct DepositData { 58 | #[prost(uint64, tag="1")] 59 | pub pc_amount: u64, 60 | #[prost(uint64, tag="2")] 61 | pub coin_amount: u64, 62 | #[prost(uint64, tag="3")] 63 | pub lp_amount: u64, 64 | } 65 | #[allow(clippy::derive_partial_eq_without_eq)] 66 | #[derive(Clone, PartialEq, ::prost::Message)] 67 | pub struct WithdrawData { 68 | #[prost(uint64, tag="1")] 69 | pub pc_amount: u64, 70 | #[prost(uint64, tag="2")] 71 | pub coin_amount: u64, 72 | #[prost(uint64, tag="3")] 73 | pub lp_amount: u64, 74 | } 75 | #[allow(clippy::derive_partial_eq_without_eq)] 76 | #[derive(Clone, PartialEq, ::prost::Message)] 77 | pub struct SwapData { 78 | #[prost(string, tag="1")] 79 | pub mint_in: ::prost::alloc::string::String, 80 | #[prost(string, tag="2")] 81 | pub mint_out: ::prost::alloc::string::String, 82 | #[prost(uint64, tag="3")] 83 | pub amount_in: u64, 84 | #[prost(uint64, tag="4")] 85 | pub amount_out: u64, 86 | } 87 | #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)] 88 | #[repr(i32)] 89 | pub enum EventType { 90 | Initialize = 0, 91 | Deposit = 1, 92 | Withdraw = 2, 93 | Swap = 3, 94 | } 95 | impl EventType { 96 | /// String value of the enum field names used in the ProtoBuf definition. 97 | /// 98 | /// The values are not transformed in any way and thus are considered stable 99 | /// (if the ProtoBuf definition does not change) and safe for programmatic use. 100 | pub fn as_str_name(&self) -> &'static str { 101 | match self { 102 | EventType::Initialize => "INITIALIZE", 103 | EventType::Deposit => "DEPOSIT", 104 | EventType::Withdraw => "WITHDRAW", 105 | EventType::Swap => "SWAP", 106 | } 107 | } 108 | /// Creates an enum from field names used in the ProtoBuf definition. 109 | pub fn from_str_name(value: &str) -> ::core::option::Option { 110 | match value { 111 | "INITIALIZE" => Some(Self::Initialize), 112 | "DEPOSIT" => Some(Self::Deposit), 113 | "WITHDRAW" => Some(Self::Withdraw), 114 | "SWAP" => Some(Self::Swap), 115 | _ => None, 116 | } 117 | } 118 | } 119 | // @@protoc_insertion_point(module) 120 | -------------------------------------------------------------------------------- /raydium_amm/src/pb/mod.rs: -------------------------------------------------------------------------------- 1 | // @generated 2 | // @@protoc_insertion_point(attribute:raydium_amm) 3 | pub mod raydium_amm { 4 | include!("raydium_amm.rs"); 5 | // @@protoc_insertion_point(raydium_amm) 6 | } 7 | -------------------------------------------------------------------------------- /raydium_amm/src/raydium_amm/constants.rs: -------------------------------------------------------------------------------- 1 | use substreams_solana_utils::pubkey::Pubkey; 2 | use substreams_solana::b58; 3 | 4 | pub const RAYDIUM_AMM_PROGRAM_ID: Pubkey = Pubkey(b58!("675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8")); 5 | -------------------------------------------------------------------------------- /raydium_amm/src/raydium_amm/log.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | use bincode; 3 | use base64; 4 | 5 | pub const LOG_SIZE: usize = 256; 6 | 7 | /// LogType enum 8 | #[derive(Debug)] 9 | pub enum LogType { 10 | Init, 11 | Deposit, 12 | Withdraw, 13 | SwapBaseIn, 14 | SwapBaseOut, 15 | } 16 | 17 | #[derive(Debug)] 18 | pub enum RayLog { 19 | Init(InitLog), 20 | Deposit(DepositLog), 21 | Withdraw(WithdrawLog), 22 | SwapBaseIn(SwapBaseInLog), 23 | SwapBaseOut(SwapBaseOutLog), 24 | } 25 | 26 | impl LogType { 27 | pub fn from_u8(log_type: u8) -> Self { 28 | match log_type { 29 | 0 => LogType::Init, 30 | 1 => LogType::Deposit, 31 | 2 => LogType::Withdraw, 32 | 3 => LogType::SwapBaseIn, 33 | 4 => LogType::SwapBaseOut, 34 | _ => unreachable!(), 35 | } 36 | } 37 | 38 | pub fn into_u8(&self) -> u8 { 39 | match self { 40 | LogType::Init => 0u8, 41 | LogType::Deposit => 1u8, 42 | LogType::Withdraw => 2u8, 43 | LogType::SwapBaseIn => 3u8, 44 | LogType::SwapBaseOut => 4u8, 45 | } 46 | } 47 | } 48 | 49 | #[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)] 50 | pub struct InitLog { 51 | pub log_type: u8, 52 | pub time: u64, 53 | pub pc_decimals: u8, 54 | pub coin_decimals: u8, 55 | pub pc_lot_size: u64, 56 | pub coin_lot_size: u64, 57 | pub pc_amount: u64, 58 | pub coin_amount: u64, 59 | pub market: [u8; 32], 60 | } 61 | 62 | #[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)] 63 | pub struct DepositLog { 64 | pub log_type: u8, 65 | // input 66 | pub max_coin: u64, 67 | pub max_pc: u64, 68 | pub base: u64, 69 | // pool info 70 | pub pool_coin: u64, 71 | pub pool_pc: u64, 72 | pub pool_lp: u64, 73 | pub calc_pnl_x: u128, 74 | pub calc_pnl_y: u128, 75 | // calc result 76 | pub deduct_coin: u64, 77 | pub deduct_pc: u64, 78 | pub mint_lp: u64, 79 | } 80 | 81 | #[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)] 82 | pub struct WithdrawLog { 83 | pub log_type: u8, 84 | // input 85 | pub withdraw_lp: u64, 86 | // user info 87 | pub user_lp: u64, 88 | // pool info 89 | pub pool_coin: u64, 90 | pub pool_pc: u64, 91 | pub pool_lp: u64, 92 | pub calc_pnl_x: u128, 93 | pub calc_pnl_y: u128, 94 | // calc result 95 | pub out_coin: u64, 96 | pub out_pc: u64, 97 | } 98 | 99 | #[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)] 100 | pub struct SwapBaseInLog { 101 | pub log_type: u8, 102 | // input 103 | pub amount_in: u64, 104 | pub minimum_out: u64, 105 | pub direction: u64, 106 | // user info 107 | pub user_source: u64, 108 | // pool info 109 | pub pool_coin: u64, 110 | pub pool_pc: u64, 111 | // calc result 112 | pub out_amount: u64, 113 | } 114 | 115 | #[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)] 116 | pub struct SwapBaseOutLog { 117 | pub log_type: u8, 118 | // input 119 | pub max_in: u64, 120 | pub amount_out: u64, 121 | pub direction: u64, 122 | // user info 123 | pub user_source: u64, 124 | // pool info 125 | pub pool_coin: u64, 126 | pub pool_pc: u64, 127 | // calc result 128 | pub deduct_in: u64, 129 | } 130 | 131 | pub fn decode_ray_log(log: &str) -> RayLog { 132 | let bytes = base64::decode(log).unwrap(); 133 | match LogType::from_u8(bytes[0]) { 134 | LogType::Init => { 135 | let log: InitLog = bincode::deserialize(&bytes).unwrap(); 136 | return RayLog::Init(log); 137 | } 138 | LogType::Deposit => { 139 | let log: DepositLog = bincode::deserialize(&bytes).unwrap(); 140 | return RayLog::Deposit(log); 141 | } 142 | LogType::Withdraw => { 143 | let log: WithdrawLog = bincode::deserialize(&bytes).unwrap(); 144 | return RayLog::Withdraw(log); 145 | } 146 | LogType::SwapBaseIn => { 147 | let log: SwapBaseInLog = bincode::deserialize(&bytes).unwrap(); 148 | return RayLog::SwapBaseIn(log); 149 | } 150 | LogType::SwapBaseOut => { 151 | let log: SwapBaseOutLog = bincode::deserialize(&bytes).unwrap(); 152 | return RayLog::SwapBaseOut(log); 153 | } 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /raydium_amm/src/raydium_amm/mod.rs: -------------------------------------------------------------------------------- 1 | #![allow(deprecated)] 2 | 3 | pub mod state; 4 | pub mod log; 5 | pub mod instruction; 6 | pub mod constants; 7 | -------------------------------------------------------------------------------- /raydium_amm/substreams.yaml: -------------------------------------------------------------------------------- 1 | specVersion: v0.1.0 2 | package: 3 | name: 'raydium_amm_events' 4 | version: v0.1.7 5 | description: Raydium AMM events substream 6 | url: https://github.com/0xpapercut/solana-substreams 7 | image: ./raydium.png 8 | 9 | imports: 10 | sol: https://spkg.io/streamingfast/solana-common-v0.3.0.spkg 11 | 12 | protobuf: 13 | files: 14 | - raydium_amm.proto 15 | importPaths: 16 | - ./proto 17 | 18 | binaries: 19 | default: 20 | type: wasm/rust-v1 21 | file: target/wasm32-unknown-unknown/release/raydium_amm_substream.wasm 22 | 23 | modules: 24 | - name: raydium_amm_events 25 | kind: map 26 | inputs: 27 | - map: sol:blocks_without_votes 28 | output: 29 | type: proto:raydium_amm.RaydiumAmmBlockEvents 30 | 31 | network: solana 32 | -------------------------------------------------------------------------------- /rust-toolchain.toml: -------------------------------------------------------------------------------- 1 | [toolchain] 2 | channel = "1.75.0" 3 | components = [ "rustfmt" ] 4 | targets = [ "wasm32-unknown-unknown" ] 5 | -------------------------------------------------------------------------------- /spl_token/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | .envrc 3 | -------------------------------------------------------------------------------- /spl_token/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "spl-token-substream" 3 | version.workspace = true 4 | edition.workspace = true 5 | 6 | [lib] 7 | name = "spl_token_substream" 8 | crate-type = ["lib", "cdylib"] 9 | 10 | [dependencies] 11 | substreams = { workspace = true } 12 | substreams-solana = { workspace = true } 13 | substreams-solana-utils = { workspace = true } 14 | prost = { workspace = true } 15 | bs58 = { workspace = true } 16 | anyhow = { workspace = true } 17 | thiserror = { workspace = true } 18 | -------------------------------------------------------------------------------- /spl_token/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 0xpapercut 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /spl_token/Makefile: -------------------------------------------------------------------------------- 1 | ENDPOINT ?= mainnet.sol.streamingfast.io:443 2 | 3 | .PHONY: build 4 | build: 5 | CARGO_TARGET_DIR=./target cargo build --target wasm32-unknown-unknown --release 6 | 7 | .PHONY: stream 8 | stream: build 9 | if [ -n "$(STOP)" ]; then \ 10 | substreams run -e $(ENDPOINT) substreams.yaml spl_token_events -s $(START) -t $(STOP); \ 11 | else \ 12 | substreams run -e $(ENDPOINT) substreams.yaml spl_token_events -s $(START); \ 13 | fi 14 | 15 | .PHONY: protogen 16 | protogen: 17 | substreams protogen ./substreams.yaml --exclude-paths="sf/substreams,google" 18 | 19 | .PHONY: package 20 | package: 21 | substreams pack ./substreams.yaml 22 | -------------------------------------------------------------------------------- /spl_token/README.md: -------------------------------------------------------------------------------- 1 | # spl-token-substream 2 | Stream SPL Token Program events with [substreams](https://substreams.streamingfast.io). 3 | 4 | ## Usage 5 | ```bash 6 | substreams gui spl-token-events 7 | ``` 8 | If you see no output, please check that you have set a starting block, e.g. `substreams gui spl-token-events -s 300000000`. 9 | -------------------------------------------------------------------------------- /spl_token/buf.gen.yaml: -------------------------------------------------------------------------------- 1 | 2 | version: v1 3 | plugins: 4 | - plugin: buf.build/community/neoeinstein-prost:v0.2.2 5 | out: src/pb 6 | opt: 7 | - file_descriptor_set=false 8 | 9 | - plugin: buf.build/community/neoeinstein-prost-crate:v0.3.1 10 | out: src/pb 11 | opt: 12 | - no_features 13 | -------------------------------------------------------------------------------- /spl_token/proto/spl_token.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package spl_token; 4 | 5 | message SplTokenBlockEvents { 6 | repeated SplTokenTransactionEvents transactions = 1; 7 | } 8 | 9 | message SplTokenTransactionEvents { 10 | string signature = 1; 11 | repeated SplTokenEvent events = 3; 12 | } 13 | 14 | message SplTokenEvent { 15 | oneof event { 16 | TransferEvent transfer = 1; 17 | InitializeMintEvent initialize_mint = 2; 18 | InitializeImmutableOwnerEvent initialize_immutable_owner = 3; 19 | InitializeAccountEvent initialize_account = 4; 20 | InitializeMultisigEvent initialize_multisig = 5; 21 | ApproveEvent approve = 6; 22 | MintToEvent mint_to = 7; 23 | RevokeEvent revoke = 8; 24 | SetAuthorityEvent set_authority = 9; 25 | BurnEvent burn = 10; 26 | CloseAccountEvent close_account = 11; 27 | FreezeAccountEvent freeze_account = 12; 28 | ThawAccountEvent thaw_account = 13; 29 | SyncNativeEvent sync_native = 14; 30 | } 31 | } 32 | 33 | message InitializeMintEvent { 34 | string mint = 1; 35 | uint32 decimals = 2; 36 | string mint_authority = 3; 37 | optional string freeze_authority = 4; 38 | } 39 | 40 | message InitializeAccountEvent { 41 | TokenAccount account = 1; 42 | } 43 | 44 | message InitializeMultisigEvent { 45 | string multisig = 1; 46 | repeated string signers = 2; 47 | uint32 m = 3; 48 | } 49 | 50 | message TransferEvent { 51 | TokenAccount source = 1; 52 | TokenAccount destination = 2; 53 | string authority = 3; 54 | uint64 amount = 4; 55 | } 56 | 57 | message ApproveEvent { 58 | TokenAccount source = 1; 59 | string delegate = 2; 60 | uint64 amount = 3; 61 | } 62 | 63 | message RevokeEvent { 64 | TokenAccount source = 1; 65 | } 66 | 67 | message SetAuthorityEvent { 68 | string mint = 1; 69 | string authority = 2; 70 | AuthorityType authority_type = 3; 71 | optional string new_authority = 4; 72 | } 73 | 74 | message MintToEvent { 75 | string mint = 1; 76 | string mint_authority = 2; 77 | TokenAccount destination = 3; 78 | uint64 amount = 4; 79 | } 80 | 81 | message BurnEvent { 82 | TokenAccount source = 1; 83 | string authority = 3; 84 | uint64 amount = 2; 85 | } 86 | 87 | message CloseAccountEvent { 88 | TokenAccount source = 1; 89 | string destination = 2; 90 | // TODO: amount 91 | } 92 | 93 | message FreezeAccountEvent { 94 | TokenAccount source = 1; 95 | string freeze_authority = 2; 96 | } 97 | 98 | message ThawAccountEvent { 99 | TokenAccount source = 1; 100 | string freeze_authority = 2; 101 | } 102 | 103 | message InitializeImmutableOwnerEvent { 104 | TokenAccount account = 1; 105 | } 106 | 107 | message SyncNativeEvent { 108 | TokenAccount account = 1; 109 | // TODO: amount 110 | } 111 | 112 | message TokenAccount { 113 | string address = 1; 114 | string owner = 2; 115 | string mint = 3; 116 | optional uint64 pre_balance = 4; 117 | optional uint64 post_balance = 5; 118 | } 119 | 120 | enum AuthorityType { 121 | Null = 0; 122 | MintTokens = 1; 123 | FreezeAccount = 2; 124 | AccountOwner = 3; 125 | CloseAccount = 4; 126 | } 127 | -------------------------------------------------------------------------------- /spl_token/sol.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0xbolt/solana-substreams/903c4ea30e3764f7dc290b5a461666b9f2060ce6/spl_token/sol.png -------------------------------------------------------------------------------- /spl_token/src/pb/event.rs: -------------------------------------------------------------------------------- 1 | // @generated 2 | #[allow(clippy::derive_partial_eq_without_eq)] 3 | #[derive(Clone, PartialEq, ::prost::Message)] 4 | pub struct TransactionsWithEvents { 5 | #[prost(message, repeated, tag="1")] 6 | pub transactions: ::prost::alloc::vec::Vec, 7 | } 8 | #[allow(clippy::derive_partial_eq_without_eq)] 9 | #[derive(Clone, PartialEq, ::prost::Message)] 10 | pub struct TransactionWithEvents { 11 | #[prost(string, repeated, tag="1")] 12 | pub signers: ::prost::alloc::vec::Vec<::prost::alloc::string::String>, 13 | #[prost(uint64, tag="2")] 14 | pub slot: u64, 15 | #[prost(string, tag="3")] 16 | pub signature: ::prost::alloc::string::String, 17 | #[prost(message, repeated, tag="4")] 18 | pub events: ::prost::alloc::vec::Vec, 19 | } 20 | #[allow(clippy::derive_partial_eq_without_eq)] 21 | #[derive(Clone, PartialEq, ::prost::Message)] 22 | pub struct Event { 23 | #[prost(enumeration="EventType", tag="1")] 24 | pub r#type: i32, 25 | #[prost(oneof="event::Data", tags="2")] 26 | pub data: ::core::option::Option, 27 | } 28 | /// Nested message and enum types in `Event`. 29 | pub mod event { 30 | #[allow(clippy::derive_partial_eq_without_eq)] 31 | #[derive(Clone, PartialEq, ::prost::Oneof)] 32 | pub enum Data { 33 | #[prost(message, tag="2")] 34 | Transfer(super::TransferData), 35 | } 36 | } 37 | #[allow(clippy::derive_partial_eq_without_eq)] 38 | #[derive(Clone, PartialEq, ::prost::Message)] 39 | pub struct TransferData { 40 | #[prost(message, optional, tag="1")] 41 | pub source: ::core::option::Option, 42 | #[prost(message, optional, tag="2")] 43 | pub destination: ::core::option::Option, 44 | #[prost(uint64, tag="3")] 45 | pub amount: u64, 46 | } 47 | #[allow(clippy::derive_partial_eq_without_eq)] 48 | #[derive(Clone, PartialEq, ::prost::Message)] 49 | pub struct TokenAccount { 50 | #[prost(string, tag="1")] 51 | pub address: ::prost::alloc::string::String, 52 | #[prost(string, tag="2")] 53 | pub owner: ::prost::alloc::string::String, 54 | } 55 | #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)] 56 | #[repr(i32)] 57 | pub enum EventType { 58 | Transfer = 0, 59 | } 60 | impl EventType { 61 | /// String value of the enum field names used in the ProtoBuf definition. 62 | /// 63 | /// The values are not transformed in any way and thus are considered stable 64 | /// (if the ProtoBuf definition does not change) and safe for programmatic use. 65 | pub fn as_str_name(&self) -> &'static str { 66 | match self { 67 | EventType::Transfer => "TRANSFER", 68 | } 69 | } 70 | /// Creates an enum from field names used in the ProtoBuf definition. 71 | pub fn from_str_name(value: &str) -> ::core::option::Option { 72 | match value { 73 | "TRANSFER" => Some(Self::Transfer), 74 | _ => None, 75 | } 76 | } 77 | } 78 | // @@protoc_insertion_point(module) 79 | -------------------------------------------------------------------------------- /spl_token/src/pb/mod.rs: -------------------------------------------------------------------------------- 1 | // @generated 2 | // @@protoc_insertion_point(attribute:spl_token) 3 | pub mod spl_token { 4 | include!("spl_token.rs"); 5 | // @@protoc_insertion_point(spl_token) 6 | } 7 | -------------------------------------------------------------------------------- /spl_token/substreams.yaml: -------------------------------------------------------------------------------- 1 | specVersion: v0.1.0 2 | package: 3 | name: 'spl_token_events' 4 | version: v0.1.7 5 | description: SPL Token Program events substream 6 | url: https://github.com/0xpapercut/solana-substreams 7 | image: ./sol.png 8 | 9 | imports: 10 | sol: https://spkg.io/streamingfast/solana-common-v0.3.0.spkg 11 | 12 | protobuf: 13 | files: 14 | - spl_token.proto 15 | importPaths: 16 | - ./proto 17 | 18 | binaries: 19 | default: 20 | type: wasm/rust-v1 21 | file: target/wasm32-unknown-unknown/release/spl_token_substream.wasm 22 | 23 | modules: 24 | - name: spl_token_events 25 | kind: map 26 | inputs: 27 | - map: sol:blocks_without_votes 28 | output: 29 | type: proto:spl_token.SplTokenBlockEvents 30 | 31 | network: solana 32 | -------------------------------------------------------------------------------- /system_program/.gitignore: -------------------------------------------------------------------------------- 1 | *.spkg 2 | /replay.log 3 | target/ 4 | .idea 5 | .envrc 6 | -------------------------------------------------------------------------------- /system_program/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "system-program-substream" 3 | version.workspace = true 4 | edition.workspace = true 5 | 6 | [lib] 7 | name = "system_program_substream" 8 | crate-type = ["lib", "cdylib"] 9 | 10 | [dependencies] 11 | substreams = { workspace = true } 12 | substreams-solana = { workspace = true } 13 | substreams-solana-utils = { workspace = true } 14 | prost = { workspace = true } 15 | bs58 = { workspace = true } 16 | borsh = { workspace = true } 17 | lazy_static = { workspace = true } 18 | anyhow = { workspace = true } 19 | -------------------------------------------------------------------------------- /system_program/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 0xpapercut 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /system_program/Makefile: -------------------------------------------------------------------------------- 1 | ENDPOINT ?= mainnet.sol.streamingfast.io:443 2 | 3 | .PHONY: build 4 | build: 5 | CARGO_TARGET_DIR=./target cargo build --target wasm32-unknown-unknown --release 6 | 7 | .PHONY: stream 8 | stream: build 9 | if [ -n "$(STOP)" ]; then \ 10 | substreams run -e $(ENDPOINT) substreams.yaml system_program_events -s $(START) -t $(STOP); \ 11 | else \ 12 | substreams run -e $(ENDPOINT) substreams.yaml system_program_events -s $(START); \ 13 | fi 14 | 15 | .PHONY: protogen 16 | protogen: 17 | substreams protogen ./substreams.yaml --exclude-paths="sf/substreams,google" 18 | 19 | .PHONY: package 20 | package: 21 | substreams pack ./substreams.yaml 22 | -------------------------------------------------------------------------------- /system_program/README.md: -------------------------------------------------------------------------------- 1 | # solana-system-program-substream 2 | Stream System Program events with [substreams](https://substreams.streamingfast.io). 3 | 4 | ## Usage 5 | ```bash 6 | substreams gui system-program-events 7 | ``` 8 | If you see no output, please check that you have set a starting block, e.g. `substreams gui system-program-events -s 300000000`. 9 | -------------------------------------------------------------------------------- /system_program/buf.gen.yaml: -------------------------------------------------------------------------------- 1 | 2 | version: v1 3 | plugins: 4 | - plugin: buf.build/community/neoeinstein-prost:v0.2.2 5 | out: src/pb 6 | opt: 7 | - file_descriptor_set=false 8 | 9 | - plugin: buf.build/community/neoeinstein-prost-crate:v0.3.1 10 | out: src/pb 11 | opt: 12 | - no_features 13 | -------------------------------------------------------------------------------- /system_program/proto/system_program.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package system_program; 4 | 5 | message SystemProgramBlockEvents { 6 | uint64 slot = 1; 7 | repeated SystemProgramTransactionEvents transactions = 2; 8 | } 9 | 10 | message SystemProgramTransactionEvents { 11 | string signature = 1; 12 | uint32 transaction_index = 2; 13 | repeated SystemProgramEvent events = 3; 14 | } 15 | 16 | message SystemProgramEvent { 17 | uint32 instruction_index = 1; 18 | oneof event { 19 | CreateAccountEvent create_account = 2; 20 | AssignEvent assign = 3; 21 | TransferEvent transfer = 4; 22 | CreateAccountWithSeedEvent create_account_with_seed = 5; 23 | AdvanceNonceAccountEvent advance_nonce_account = 6; 24 | WithdrawNonceAccountEvent withdraw_nonce_account = 7; 25 | InitializeNonceAccountEvent initialize_nonce_account = 8; 26 | AuthorizeNonceAccountEvent authorize_nonce_account = 9; 27 | AllocateEvent allocate = 10; 28 | AllocateWithSeedEvent allocate_with_seed = 11; 29 | AssignWithSeedEvent assign_with_seed = 12; 30 | TransferWithSeedEvent transfer_with_seed = 13; 31 | UpgradeNonceAccountEvent upgrade_nonce_account = 14; 32 | } 33 | } 34 | 35 | message CreateAccountEvent { 36 | string funding_account = 1; 37 | string new_account = 2; 38 | uint64 lamports = 3; 39 | uint64 space = 4; 40 | string owner = 5; 41 | } 42 | 43 | message AssignEvent { 44 | string assigned_account = 1; 45 | string owner = 2; 46 | } 47 | 48 | message TransferEvent { 49 | string funding_account = 1; 50 | string recipient_account = 2; 51 | uint64 lamports = 3; 52 | AccountBalance funding_account_balance = 4; 53 | AccountBalance recipient_account_balance = 5; 54 | } 55 | 56 | message CreateAccountWithSeedEvent { 57 | string funding_account = 1; 58 | string created_account = 2; 59 | string base_account = 3; 60 | string seed = 4; 61 | uint64 lamports = 5; 62 | uint64 space = 6; 63 | string owner = 7; 64 | } 65 | 66 | message AdvanceNonceAccountEvent { 67 | string nonce_account = 1; 68 | string nonce_authority = 2; 69 | } 70 | 71 | message WithdrawNonceAccountEvent { 72 | string nonce_account = 1; 73 | string recipient_account = 2; 74 | string nonce_authority = 3; 75 | uint64 lamports = 4; 76 | } 77 | 78 | message InitializeNonceAccountEvent { 79 | string nonce_account = 1; 80 | string nonce_authority = 2; 81 | } 82 | 83 | message AuthorizeNonceAccountEvent { 84 | string nonce_account = 1; 85 | string nonce_authority = 2; 86 | string new_nonce_authority = 3; 87 | } 88 | 89 | message AllocateEvent { 90 | string account = 1; 91 | uint64 space = 2; 92 | } 93 | 94 | message AllocateWithSeedEvent { 95 | string allocated_account = 1; 96 | string base_account = 2; 97 | string seed = 3; 98 | uint64 space = 4; 99 | string owner = 5; 100 | } 101 | 102 | message AssignWithSeedEvent { 103 | string assigned_account = 1; 104 | string base_account = 2; 105 | string seed = 3; 106 | string owner = 4; 107 | } 108 | 109 | message TransferWithSeedEvent { 110 | string funding_account = 1; 111 | string base_account = 2; 112 | string recipient_account = 3; 113 | uint64 lamports = 4; 114 | string from_seed = 5; 115 | string from_owner = 6; 116 | AccountBalance funding_account_balance = 7; 117 | AccountBalance recipient_account_balance = 8; 118 | } 119 | 120 | message UpgradeNonceAccountEvent { 121 | string nonce_account = 1; 122 | } 123 | 124 | message AccountBalance { 125 | uint64 pre_balance = 1; 126 | uint64 post_balance = 2; 127 | } 128 | -------------------------------------------------------------------------------- /system_program/sol.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0xbolt/solana-substreams/903c4ea30e3764f7dc290b5a461666b9f2060ce6/system_program/sol.png -------------------------------------------------------------------------------- /system_program/src/pb/mod.rs: -------------------------------------------------------------------------------- 1 | // @generated 2 | pub mod sf { 3 | pub mod solana { 4 | pub mod r#type { 5 | // @@protoc_insertion_point(attribute:sf.solana.type.v1) 6 | pub mod v1 { 7 | include!("sf.solana.type.v1.rs"); 8 | // @@protoc_insertion_point(sf.solana.type.v1) 9 | } 10 | } 11 | } 12 | } 13 | pub mod sol { 14 | pub mod instructions { 15 | // @@protoc_insertion_point(attribute:sol.instructions.v1) 16 | pub mod v1 { 17 | include!("sol.instructions.v1.rs"); 18 | // @@protoc_insertion_point(sol.instructions.v1) 19 | } 20 | } 21 | pub mod transactions { 22 | // @@protoc_insertion_point(attribute:sol.transactions.v1) 23 | pub mod v1 { 24 | include!("sol.transactions.v1.rs"); 25 | // @@protoc_insertion_point(sol.transactions.v1) 26 | } 27 | } 28 | } 29 | // @@protoc_insertion_point(attribute:system_program) 30 | pub mod system_program { 31 | include!("system_program.rs"); 32 | // @@protoc_insertion_point(system_program) 33 | } 34 | -------------------------------------------------------------------------------- /system_program/src/pb/sol.instructions.v1.rs: -------------------------------------------------------------------------------- 1 | // @generated 2 | #[allow(clippy::derive_partial_eq_without_eq)] 3 | #[derive(Clone, PartialEq, ::prost::Message)] 4 | pub struct Instructions { 5 | #[prost(message, repeated, tag="1")] 6 | pub instructions: ::prost::alloc::vec::Vec, 7 | } 8 | #[allow(clippy::derive_partial_eq_without_eq)] 9 | #[derive(Clone, PartialEq, ::prost::Message)] 10 | pub struct Instruction { 11 | #[prost(string, tag="1")] 12 | pub program_id: ::prost::alloc::string::String, 13 | #[prost(string, repeated, tag="2")] 14 | pub accounts: ::prost::alloc::vec::Vec<::prost::alloc::string::String>, 15 | #[prost(bytes="vec", tag="3")] 16 | pub data: ::prost::alloc::vec::Vec, 17 | #[prost(string, tag="4")] 18 | pub tx_hash: ::prost::alloc::string::String, 19 | } 20 | // @@protoc_insertion_point(module) 21 | -------------------------------------------------------------------------------- /system_program/src/pb/sol.transactions.v1.rs: -------------------------------------------------------------------------------- 1 | // @generated 2 | #[allow(clippy::derive_partial_eq_without_eq)] 3 | #[derive(Clone, PartialEq, ::prost::Message)] 4 | pub struct Transactions { 5 | #[prost(message, repeated, tag="1")] 6 | pub transactions: ::prost::alloc::vec::Vec, 7 | } 8 | // @@protoc_insertion_point(module) 9 | -------------------------------------------------------------------------------- /system_program/substreams.yaml: -------------------------------------------------------------------------------- 1 | specVersion: v0.1.0 2 | package: 3 | name: 'system_program_events' 4 | version: v0.1.7 5 | description: System Program events substream 6 | url: https://github.com/0xpapercut/solana-substreams 7 | image: ./sol.png 8 | 9 | imports: 10 | sol: https://spkg.io/streamingfast/solana-common-v0.3.0.spkg 11 | 12 | protobuf: 13 | files: 14 | - system_program.proto 15 | importPaths: 16 | - ./proto 17 | 18 | binaries: 19 | default: 20 | type: wasm/rust-v1 21 | file: target/wasm32-unknown-unknown/release/system_program_substream.wasm 22 | 23 | modules: 24 | - name: system_program_events 25 | kind: map 26 | inputs: 27 | - map: sol:blocks_without_votes 28 | output: 29 | type: proto:system_program.SystemProgramBlockEvents 30 | 31 | network: solana 32 | -------------------------------------------------------------------------------- /token.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | export SUBSTREAMS_API_TOKEN=$(curl https://auth.streamingfast.io/v1/auth/issue -s --data-binary '{"api_key":"'$STREAMINGFAST_KEY'"}' | jq -r .token) 4 | echo "Token set on in SUBSTREAMS_API_TOKEN" 5 | --------------------------------------------------------------------------------