Skip to content

Commit

Permalink
Index Token Inscriptions (metaplex-foundation#222)
Browse files Browse the repository at this point in the history
Program parser for inscriptions for blockbluster. Create and register
program parser with program tranformer. Add show_instrcitions flag to
the API to view the inscription information.
  • Loading branch information
Nagaprasadvr authored and kespinola committed Jan 7, 2025
1 parent 9654780 commit 9d8818d
Show file tree
Hide file tree
Showing 62 changed files with 1,591 additions and 77 deletions.
3 changes: 3 additions & 0 deletions blockbuster/src/programs/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,14 @@ use bubblegum::BubblegumInstruction;
use mpl_core_program::MplCoreAccountState;
use token_account::TokenProgramAccount;
use token_extensions::TokenExtensionsProgramAccount;
use token_inscriptions::TokenInscriptionAccount;
use token_metadata::TokenMetadataAccountState;

pub mod bubblegum;
pub mod mpl_core_program;
pub mod token_account;
pub mod token_extensions;
pub mod token_inscriptions;
pub mod token_metadata;

// Note: `ProgramParseResult` used to contain the following variants that have been deprecated and
Expand All @@ -30,5 +32,6 @@ pub enum ProgramParseResult<'a> {
TokenMetadata(&'a TokenMetadataAccountState),
TokenProgramAccount(&'a TokenProgramAccount),
TokenExtensionsProgramAccount(&'a TokenExtensionsProgramAccount),
TokenInscriptionAccount(&'a TokenInscriptionAccount),
Unknown,
}
141 changes: 141 additions & 0 deletions blockbuster/src/programs/token_inscriptions/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
use serde::{Deserialize, Serialize};
use solana_sdk::{pubkey::Pubkey, pubkeys};

use crate::{
error::BlockbusterError,
program_handler::{ParseResult, ProgramParser},
};

use super::ProgramParseResult;

pubkeys!(
inscription_program_id,
"inscokhJarcjaEs59QbQ7hYjrKz25LEPRfCbP8EmdUp"
);

pub struct TokenInscriptionParser;

#[derive(Debug, Serialize, Deserialize)]
pub struct InscriptionData {
pub authority: String,
pub root: String,
pub content: String,
pub encoding: String,
pub inscription_data: String,
pub order: u64,
pub size: u32,
pub validation_hash: Option<String>,
}

impl InscriptionData {
pub const BASE_SIZE: usize = 121;
pub const INSCRIPTION_ACC_DATA_DISC: [u8; 8] = [232, 120, 205, 47, 153, 239, 229, 224];

pub fn try_unpack_data(data: &[u8]) -> Result<Self, BlockbusterError> {
let acc_disc = &data[0..8];

if acc_disc != Self::INSCRIPTION_ACC_DATA_DISC {
return Err(BlockbusterError::InvalidAccountType);
}

if data.len() < Self::BASE_SIZE {
return Err(BlockbusterError::CustomDeserializationError(
"Inscription Data is too short".to_string(),
));
}

let authority = Pubkey::try_from(&data[8..40]).unwrap();
let mint = Pubkey::try_from(&data[40..72]).unwrap();
let inscription_data = Pubkey::try_from(&data[72..104]).unwrap();
let order = u64::from_le_bytes(data[104..112].try_into().unwrap());
let size = u32::from_le_bytes(data[112..116].try_into().unwrap());
let content_type_len = u32::from_le_bytes(data[116..120].try_into().unwrap()) as usize;
let content = String::from_utf8(data[120..120 + content_type_len].to_vec()).unwrap();
let encoding_len = u32::from_le_bytes(
data[120 + content_type_len..124 + content_type_len]
.try_into()
.unwrap(),
) as usize;

let encoding = String::from_utf8(
data[124 + content_type_len..124 + content_type_len + encoding_len].to_vec(),
)
.unwrap();

let validation_exists = u8::from_le_bytes(
data[124 + content_type_len + encoding_len..124 + content_type_len + encoding_len + 1]
.try_into()
.unwrap(),
);

let validation_hash = if validation_exists == 1 {
let validation_hash_len = u32::from_le_bytes(
data[124 + content_type_len + encoding_len + 1
..128 + content_type_len + encoding_len + 1]
.try_into()
.unwrap(),
) as usize;
Some(
String::from_utf8(
data[128 + content_type_len + encoding_len + 1
..128 + content_type_len + encoding_len + 1 + validation_hash_len]
.to_vec(),
)
.unwrap(),
)
} else {
None
};
Ok(InscriptionData {
authority: authority.to_string(),
root: mint.to_string(),
content,
encoding,
inscription_data: inscription_data.to_string(),
order,
size,
validation_hash,
})
}
}

pub struct TokenInscriptionAccount {
pub data: InscriptionData,
}

impl ParseResult for TokenInscriptionAccount {
fn result(&self) -> &Self
where
Self: Sized,
{
self
}
fn result_type(&self) -> ProgramParseResult {
ProgramParseResult::TokenInscriptionAccount(self)
}
}

impl ProgramParser for TokenInscriptionParser {
fn key(&self) -> Pubkey {
inscription_program_id()
}
fn key_match(&self, key: &Pubkey) -> bool {
key == &inscription_program_id()
}

fn handles_account_updates(&self) -> bool {
true
}

fn handles_instructions(&self) -> bool {
false
}

fn handle_account(
&self,
account_data: &[u8],
) -> Result<Box<(dyn ParseResult + 'static)>, BlockbusterError> {
let data = InscriptionData::try_unpack_data(account_data)?;
Ok(Box::new(TokenInscriptionAccount { data }))
}
}
12 changes: 12 additions & 0 deletions digital_asset_types/src/dao/extensions/asset.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use crate::dao::{
asset, asset_authority, asset_creators, asset_data, asset_grouping,
asset_v1_account_attachments,
sea_orm_active_enums::{OwnerType, RoyaltyTargetType},
token_accounts,
};

#[derive(Copy, Clone, Debug, EnumIter)]
Expand All @@ -13,6 +14,7 @@ pub enum Relation {
AssetAuthority,
AssetCreators,
AssetGrouping,
TokenAccounts,
}

impl RelationTrait for Relation {
Expand All @@ -22,6 +24,10 @@ impl RelationTrait for Relation {
.from(asset::Column::AssetData)
.to(asset_data::Column::Id)
.into(),
Self::TokenAccounts => asset::Entity::belongs_to(token_accounts::Entity)
.from(asset::Column::Id)
.to(token_accounts::Column::Mint)
.into(),
Self::AssetV1AccountAttachments => {
asset::Entity::has_many(asset_v1_account_attachments::Entity).into()
}
Expand Down Expand Up @@ -62,6 +68,12 @@ impl Related<asset_grouping::Entity> for asset::Entity {
}
}

impl Related<token_accounts::Entity> for asset::Entity {
fn to() -> RelationDef {
Relation::TokenAccounts.def()
}
}

impl Default for RoyaltyTargetType {
fn default() -> Self {
Self::Creators
Expand Down
1 change: 1 addition & 0 deletions digital_asset_types/src/dao/extensions/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@ pub mod asset_data;
pub mod asset_grouping;
pub mod asset_v1_account_attachment;
pub mod instruction;
pub mod token_accounts;
25 changes: 25 additions & 0 deletions digital_asset_types/src/dao/extensions/token_accounts.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
use sea_orm::{EntityTrait, EnumIter, Related, RelationDef, RelationTrait};

use crate::dao::{asset, token_accounts};

#[derive(Copy, Clone, Debug, EnumIter)]
pub enum Relation {
Asset,
}

impl RelationTrait for Relation {
fn def(&self) -> RelationDef {
match self {
Self::Asset => token_accounts::Entity::belongs_to(asset::Entity)
.from(token_accounts::Column::Mint)
.to(asset::Column::Id)
.into(),
}
}
}

impl Related<asset::Entity> for token_accounts::Entity {
fn to() -> RelationDef {
Relation::Asset.def()
}
}
7 changes: 6 additions & 1 deletion digital_asset_types/src/dao/full_asset.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
use crate::dao::{asset, asset_authority, asset_creators, asset_data, asset_grouping};

use super::asset_v1_account_attachments;
use super::tokens;

#[derive(Clone, Debug, PartialEq)]
pub struct FullAsset {
pub asset: asset::Model,
pub data: Option<asset_data::Model>,
pub data: asset_data::Model,
pub token_info: Option<tokens::Model>,
pub authorities: Vec<asset_authority::Model>,
pub creators: Vec<asset_creators::Model>,
pub groups: Vec<asset_grouping::Model>,
pub inscription: Option<asset_v1_account_attachments::Model>,
}
#[derive(Clone, Debug, PartialEq)]
pub struct AssetRelated {
Expand Down
2 changes: 2 additions & 0 deletions digital_asset_types/src/dao/generated/sea_orm_active_enums.rs
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,8 @@ pub enum V1AccountAttachments {
MasterEditionV1,
#[sea_orm(string_value = "master_edition_v2")]
MasterEditionV2,
#[sea_orm(string_value = "token_inscription")]
TokenInscription,
#[sea_orm(string_value = "unknown")]
Unknown,
}
Expand Down
Loading

0 comments on commit 9d8818d

Please sign in to comment.