diff --git a/crates/torii/graphql/src/constants.rs b/crates/torii/graphql/src/constants.rs index a6e4170d0c..3396743bf5 100644 --- a/crates/torii/graphql/src/constants.rs +++ b/crates/torii/graphql/src/constants.rs @@ -41,6 +41,7 @@ pub const TOKEN_UNION_TYPE_NAME: &str = "ERC__Token"; pub const ERC20_TYPE_NAME: &str = "ERC20__Token"; pub const ERC721_TYPE_NAME: &str = "ERC721__Token"; +pub const ERC1155_TYPE_NAME: &str = "ERC1155__Token"; // objects' single and plural names pub const ENTITY_NAMES: (&str, &str) = ("entity", "entities"); @@ -55,6 +56,7 @@ pub const PAGE_INFO_NAMES: (&str, &str) = ("pageInfo", ""); pub const ERC20_TOKEN_NAME: (&str, &str) = ("erc20Token", ""); pub const ERC721_TOKEN_NAME: (&str, &str) = ("erc721Token", ""); +pub const ERC1155_TOKEN_NAME: (&str, &str) = ("erc1155Token", ""); pub const TOKEN_BALANCE_NAME: (&str, &str) = ("", "tokenBalances"); pub const TOKEN_TRANSFER_NAME: (&str, &str) = ("", "tokenTransfers"); diff --git a/crates/torii/graphql/src/mapping.rs b/crates/torii/graphql/src/mapping.rs index 5085c19cea..e42dc3cd94 100644 --- a/crates/torii/graphql/src/mapping.rs +++ b/crates/torii/graphql/src/mapping.rs @@ -177,6 +177,19 @@ lazy_static! { (Name::new("imagePath"), TypeData::Simple(TypeRef::named_nn(TypeRef::STRING))), ]); + pub static ref ERC1155_TOKEN_TYPE_MAPPING: TypeMapping = IndexMap::from([ + (Name::new("name"), TypeData::Simple(TypeRef::named_nn(TypeRef::STRING))), + (Name::new("symbol"), TypeData::Simple(TypeRef::named_nn(TypeRef::STRING))), + (Name::new("tokenId"), TypeData::Simple(TypeRef::named_nn(TypeRef::STRING))), + (Name::new("contractAddress"), TypeData::Simple(TypeRef::named_nn(TypeRef::STRING))), + (Name::new("amount"), TypeData::Simple(TypeRef::named_nn(TypeRef::STRING))), + (Name::new("metadata"), TypeData::Simple(TypeRef::named_nn(TypeRef::STRING))), + (Name::new("metadataName"), TypeData::Simple(TypeRef::named(TypeRef::STRING))), + (Name::new("metadataDescription"), TypeData::Simple(TypeRef::named(TypeRef::STRING))), + (Name::new("metadataAttributes"), TypeData::Simple(TypeRef::named(TypeRef::STRING))), + (Name::new("imagePath"), TypeData::Simple(TypeRef::named_nn(TypeRef::STRING))), + ]); + pub static ref EMPTY_MAPPING: TypeMapping = IndexMap::from([ (Name::new("id"), TypeData::Simple(TypeRef::named(TypeRef::ID))), ]); diff --git a/crates/torii/graphql/src/object/erc/erc_token.rs b/crates/torii/graphql/src/object/erc/erc_token.rs index af18bfc0ff..a0bf77634e 100644 --- a/crates/torii/graphql/src/object/erc/erc_token.rs +++ b/crates/torii/graphql/src/object/erc/erc_token.rs @@ -8,13 +8,17 @@ use sqlx::{Pool, Row, Sqlite, SqliteConnection}; use tokio_stream::StreamExt; use torii_sqlite::simple_broker::SimpleBroker; use torii_sqlite::types::Token; +use tracing::warn; use super::handle_cursor; use crate::constants::{ - DEFAULT_LIMIT, ERC20_TOKEN_NAME, ERC20_TYPE_NAME, ERC721_TOKEN_NAME, ERC721_TYPE_NAME, - ID_COLUMN, + DEFAULT_LIMIT, ERC1155_TOKEN_NAME, ERC1155_TYPE_NAME, ERC20_TOKEN_NAME, ERC20_TYPE_NAME, + ERC721_TOKEN_NAME, ERC721_TYPE_NAME, ID_COLUMN, +}; +use crate::mapping::{ + ERC1155_TOKEN_TYPE_MAPPING, ERC20_TOKEN_TYPE_MAPPING, ERC721_TOKEN_TYPE_MAPPING, + TOKEN_TYPE_MAPPING, }; -use crate::mapping::{ERC20_TOKEN_TYPE_MAPPING, ERC721_TOKEN_TYPE_MAPPING, TOKEN_TYPE_MAPPING}; use crate::object::connection::page_info::PageInfoObject; use crate::object::connection::{ connection_arguments, cursor, parse_connection_arguments, ConnectionArguments, @@ -58,10 +62,28 @@ impl BasicObject for Erc721TokenObject { } } +#[derive(Debug)] +pub struct Erc1155TokenObject; + +impl BasicObject for Erc1155TokenObject { + fn name(&self) -> (&str, &str) { + ERC1155_TOKEN_NAME + } + + fn type_name(&self) -> &str { + ERC1155_TYPE_NAME + } + + fn type_mapping(&self) -> &TypeMapping { + &ERC1155_TOKEN_TYPE_MAPPING + } +} + #[derive(Debug, Clone)] pub enum ErcTokenType { Erc20(Erc20Token), Erc721(Erc721Token), + Erc1155(Erc1155Token), } #[derive(Debug, Clone)] @@ -86,6 +108,20 @@ pub struct Erc721Token { pub image_path: String, } +#[derive(Debug, Clone)] +pub struct Erc1155Token { + pub name: String, + pub symbol: String, + pub token_id: String, + pub contract_address: String, + pub amount: String, + pub metadata: String, + pub metadata_name: Option, + pub metadata_description: Option, + pub metadata_attributes: Option, + pub image_path: String, +} + impl ErcTokenType { pub fn to_field_value<'a>(self) -> FieldValue<'a> { match self { @@ -122,6 +158,30 @@ impl ErcTokenType { ]))), ERC721_TYPE_NAME.to_string(), ), + ErcTokenType::Erc1155(token) => FieldValue::with_type( + FieldValue::value(Value::Object(ValueMapping::from([ + (Name::new("name"), Value::String(token.name)), + (Name::new("symbol"), Value::String(token.symbol)), + (Name::new("tokenId"), Value::String(token.token_id)), + (Name::new("contractAddress"), Value::String(token.contract_address)), + (Name::new("amount"), Value::String(token.amount)), + (Name::new("metadata"), Value::String(token.metadata)), + ( + Name::new("metadataName"), + token.metadata_name.map(Value::String).unwrap_or(Value::Null), + ), + ( + Name::new("metadataDescription"), + token.metadata_description.map(Value::String).unwrap_or(Value::Null), + ), + ( + Name::new("metadataAttributes"), + token.metadata_attributes.map(Value::String).unwrap_or(Value::Null), + ), + (Name::new("imagePath"), Value::String(token.image_path)), + ]))), + ERC1155_TYPE_NAME.to_string(), + ), } } } @@ -446,7 +506,65 @@ impl ResolvableObject for TokenObject { }; ErcTokenType::Erc721(token) } - _ => return None, + "erc1155" => { + let id = row.get::("id"); + let token_id = + id.split(':').collect::>()[1].to_string(); + + let metadata_str: String = row.get("metadata"); + let ( + metadata_str, + metadata_name, + metadata_description, + metadata_attributes, + image_path, + ) = if metadata_str.is_empty() { + (String::new(), None, None, None, String::new()) + } else { + let metadata: serde_json::Value = + serde_json::from_str(&metadata_str) + .expect("metadata is always json"); + let metadata_name = metadata.get("name").map(|v| { + v.to_string().trim_matches('"').to_string() + }); + let metadata_description = + metadata.get("description").map(|v| { + v.to_string().trim_matches('"').to_string() + }); + let metadata_attributes = + metadata.get("attributes").map(|v| { + v.to_string().trim_matches('"').to_string() + }); + + let image_path = + format!("{}/image", id.replace(":", "/")); + ( + metadata_str, + metadata_name, + metadata_description, + metadata_attributes, + image_path, + ) + }; + + let token = Erc1155Token { + name: row.get("name"), + metadata: metadata_str, + contract_address: row.get("contract_address"), + symbol: row.get("symbol"), + token_id, + amount: "0".to_string(), + metadata_name, + metadata_description, + metadata_attributes, + image_path, + }; + ErcTokenType::Erc1155(token) + } + _ => { + warn!("Unknown contract type: {}", contract_type); + return None; + } }; Some(Ok(FieldValue::owned_any(token_metadata))) @@ -516,6 +634,52 @@ fn create_token_metadata_from_row(row: &SqliteRow) -> sqlx::Result }; ErcTokenType::Erc721(token) } - _ => return Err(sqlx::Error::RowNotFound), + "erc1155" => { + // contract_address:token_id + let id = row.get::("id"); + let token_id = id.split(':').collect::>()[1].to_string(); + + let metadata_str: String = row.get("metadata"); + let ( + metadata_str, + metadata_name, + metadata_description, + metadata_attributes, + image_path, + ) = if metadata_str.is_empty() { + (String::new(), None, None, None, String::new()) + } else { + let metadata: serde_json::Value = + serde_json::from_str(&metadata_str).expect("metadata is always json"); + let metadata_name = + metadata.get("name").map(|v| v.to_string().trim_matches('"').to_string()); + let metadata_description = metadata + .get("description") + .map(|v| v.to_string().trim_matches('"').to_string()); + let metadata_attributes = + metadata.get("attributes").map(|v| v.to_string().trim_matches('"').to_string()); + + let image_path = format!("{}/image", id.replace(":", "/")); + (metadata_str, metadata_name, metadata_description, metadata_attributes, image_path) + }; + + let token = Erc1155Token { + name: row.get("name"), + metadata: metadata_str, + contract_address: row.get("contract_address"), + symbol: row.get("symbol"), + token_id, + amount: "0".to_string(), + metadata_name, + metadata_description, + metadata_attributes, + image_path, + }; + ErcTokenType::Erc1155(token) + } + _ => { + warn!("Unknown contract type: {}", contract_type); + return Err(sqlx::Error::RowNotFound); + } }) } diff --git a/crates/torii/graphql/src/object/erc/token_balance.rs b/crates/torii/graphql/src/object/erc/token_balance.rs index cbe0897467..fb61d31bd5 100644 --- a/crates/torii/graphql/src/object/erc/token_balance.rs +++ b/crates/torii/graphql/src/object/erc/token_balance.rs @@ -22,7 +22,7 @@ use crate::object::connection::page_info::PageInfoObject; use crate::object::connection::{ connection_arguments, cursor, parse_connection_arguments, ConnectionArguments, }; -use crate::object::erc::erc_token::Erc721Token; +use crate::object::erc::erc_token::{Erc1155Token, Erc721Token}; use crate::object::{BasicObject, ResolvableObject}; use crate::query::data::count_rows; use crate::query::filter::{Comparator, Filter, FilterValue}; @@ -213,7 +213,66 @@ impl ResolvableObject for ErcBalanceObject { }; ErcTokenType::Erc721(token_metadata) } - _ => return None, + "erc1155" => { + let token_id = + row.token_id.split(':').collect::>(); + assert!(token_id.len() == 2); + + let metadata_str = row.metadata; + let ( + metadata_str, + metadata_name, + metadata_description, + metadata_attributes, + image_path, + ) = if metadata_str.is_empty() { + (String::new(), None, None, None, String::new()) + } else { + let metadata: serde_json::Value = + serde_json::from_str(&metadata_str) + .expect("metadata is always json"); + let metadata_name = metadata.get("name").map(|v| { + v.to_string().trim_matches('"').to_string() + }); + let metadata_description = + metadata.get("description").map(|v| { + v.to_string().trim_matches('"').to_string() + }); + let metadata_attributes = + metadata.get("attributes").map(|v| { + v.to_string().trim_matches('"').to_string() + }); + + let image_path = + format!("{}/{}", token_id.join("/"), "image"); + + ( + metadata_str, + metadata_name, + metadata_description, + metadata_attributes, + image_path, + ) + }; + + let token_metadata = Erc1155Token { + name: row.name, + metadata: metadata_str, + contract_address: row.contract_address, + symbol: row.symbol, + token_id: token_id[1].to_string(), + amount: row.balance, + metadata_name, + metadata_description, + metadata_attributes, + image_path, + }; + ErcTokenType::Erc1155(token_metadata) + } + _ => { + warn!("Unknown contract type: {}", row.contract_type); + return None; + } }; Some(Ok(FieldValue::owned_any(balance_value))) @@ -427,6 +486,58 @@ fn token_balances_connection_output<'a>( ErcTokenType::Erc721(token_metadata) } + "erc1155" => { + // contract_address:token_id + let token_id = row.token_id.split(':').collect::>(); + assert!(token_id.len() == 2); + + let metadata_str = row.metadata; + let ( + metadata_str, + metadata_name, + metadata_description, + metadata_attributes, + image_path, + ) = if metadata_str.is_empty() { + (String::new(), None, None, None, String::new()) + } else { + let metadata: serde_json::Value = + serde_json::from_str(&metadata_str).expect("metadata is always json"); + let metadata_name = + metadata.get("name").map(|v| v.to_string().trim_matches('"').to_string()); + let metadata_description = metadata + .get("description") + .map(|v| v.to_string().trim_matches('"').to_string()); + let metadata_attributes = metadata + .get("attributes") + .map(|v| v.to_string().trim_matches('"').to_string()); + + let image_path = format!("{}/{}", token_id.join("/"), "image"); + + ( + metadata_str, + metadata_name, + metadata_description, + metadata_attributes, + image_path, + ) + }; + + let token_metadata = Erc1155Token { + name: row.name, + metadata: metadata_str.to_owned(), + contract_address: row.contract_address, + symbol: row.symbol, + token_id: token_id[1].to_string(), + amount: row.balance, + metadata_name, + metadata_description, + metadata_attributes, + image_path, + }; + + ErcTokenType::Erc1155(token_metadata) + } _ => { warn!("Unknown contract type: {}", row.contract_type); continue; diff --git a/crates/torii/graphql/src/schema.rs b/crates/torii/graphql/src/schema.rs index 3ff6e1fe4a..fd37dfeb30 100644 --- a/crates/torii/graphql/src/schema.rs +++ b/crates/torii/graphql/src/schema.rs @@ -11,12 +11,14 @@ use super::object::model_data::ModelDataObject; use super::types::ScalarType; use super::utils; use crate::constants::{ - EMPTY_TYPE_NAME, ERC20_TYPE_NAME, ERC721_TYPE_NAME, QUERY_TYPE_NAME, SUBSCRIPTION_TYPE_NAME, - TOKEN_UNION_TYPE_NAME, + EMPTY_TYPE_NAME, ERC1155_TYPE_NAME, ERC20_TYPE_NAME, ERC721_TYPE_NAME, QUERY_TYPE_NAME, + SUBSCRIPTION_TYPE_NAME, TOKEN_UNION_TYPE_NAME, }; use crate::object::controller::ControllerObject; use crate::object::empty::EmptyObject; -use crate::object::erc::erc_token::{Erc20TokenObject, Erc721TokenObject, TokenObject}; +use crate::object::erc::erc_token::{ + Erc1155TokenObject, Erc20TokenObject, Erc721TokenObject, TokenObject, +}; use crate::object::erc::token_balance::ErcBalanceObject; use crate::object::erc::token_transfer::ErcTransferObject; use crate::object::event_message::EventMessageObject; @@ -131,6 +133,7 @@ async fn build_objects(pool: &SqlitePool) -> Result<(Vec, Vec Result<(Vec, Vec