diff --git a/bin/torii/src/main.rs b/bin/torii/src/main.rs index 1d5b2f1322..c605d58079 100644 --- a/bin/torii/src/main.rs +++ b/bin/torii/src/main.rs @@ -232,9 +232,7 @@ async fn main() -> anyhow::Result<()> { args.max_concurrent_tasks, ) .await?; - tokio::spawn(async move { - executor.run().await.unwrap(); - }); + let executor_handle = tokio::spawn(async move { executor.run().await }); let model_cache = Arc::new(ModelCache::new(pool.clone())); let db = Sql::new(pool.clone(), sender.clone(), &args.indexing.contracts, model_cache.clone()) @@ -350,6 +348,7 @@ async fn main() -> anyhow::Result<()> { tokio::select! { res = engine_handle => res??, + res = executor_handle => res??, res = proxy_server_handle => res??, res = graphql_server_handle => res?, res = grpc_server_handle => res??, diff --git a/crates/torii/core/src/constants.rs b/crates/torii/core/src/constants.rs new file mode 100644 index 0000000000..8248b09f7d --- /dev/null +++ b/crates/torii/core/src/constants.rs @@ -0,0 +1,3 @@ +pub const TOKEN_BALANCE_TABLE: &str = "token_balances"; +pub const TOKEN_TRANSFER_TABLE: &str = "token_transfers"; +pub const TOKENS_TABLE: &str = "tokens"; diff --git a/crates/torii/core/src/executor/mod.rs b/crates/torii/core/src/executor/mod.rs index b2c3bf7093..c823aa1255 100644 --- a/crates/torii/core/src/executor/mod.rs +++ b/crates/torii/core/src/executor/mod.rs @@ -18,6 +18,7 @@ use tokio::task::JoinSet; use tokio::time::Instant; use tracing::{debug, error}; +use crate::constants::TOKENS_TABLE; use crate::simple_broker::SimpleBroker; use crate::sql::utils::{felt_to_sql_string, I256}; use crate::types::{ @@ -603,9 +604,9 @@ impl<'c, P: Provider + Sync + Send + 'static> Executor<'c, P> { QueryType::RegisterErc721Token(register_erc721_token) => { let semaphore = self.semaphore.clone(); let provider = self.provider.clone(); - let res = sqlx::query_as::<_, (String, String)>( - "SELECT name, symbol FROM tokens WHERE contract_address = ?", - ) + let res = sqlx::query_as::<_, (String, String)>(&format!( + "SELECT name, symbol FROM {TOKENS_TABLE} WHERE contract_address = ?" + )) .bind(felt_to_sql_string(®ister_erc721_token.contract_address)) .fetch_one(&mut **tx) .await; diff --git a/crates/torii/core/src/lib.rs b/crates/torii/core/src/lib.rs index 0615f98b4e..fbf9a1e14b 100644 --- a/crates/torii/core/src/lib.rs +++ b/crates/torii/core/src/lib.rs @@ -1,5 +1,6 @@ #![warn(unused_crate_dependencies)] +pub mod constants; pub mod engine; pub mod error; pub mod executor; diff --git a/crates/torii/core/src/sql/cache.rs b/crates/torii/core/src/sql/cache.rs index 23da95bd34..76ea4a0574 100644 --- a/crates/torii/core/src/sql/cache.rs +++ b/crates/torii/core/src/sql/cache.rs @@ -6,6 +6,7 @@ use sqlx::{Pool, Sqlite, SqlitePool}; use starknet_crypto::Felt; use tokio::sync::RwLock; +use crate::constants::TOKEN_BALANCE_TABLE; use crate::error::{Error, ParseError, QueryError}; use crate::model::{parse_sql_model_members, SqlModelMember}; use crate::sql::utils::I256; @@ -133,27 +134,24 @@ pub struct LocalCache { impl Clone for LocalCache { fn clone(&self) -> Self { - Self { erc_cache: HashMap::new(), token_id_registry: HashSet::new() } + Self { erc_cache: HashMap::new(), token_id_registry: self.token_id_registry.clone() } } } impl LocalCache { pub async fn new(pool: Pool) -> Self { // read existing token_id's from balances table and cache them - let token_id_registry: Vec<(String,)> = sqlx::query_as("SELECT token_id FROM balances") - .fetch_all(&pool) - .await - .expect("Should be able to read token_id's from blances table"); + let token_id_registry: Vec<(String,)> = + sqlx::query_as(&format!("SELECT token_id FROM {TOKEN_BALANCE_TABLE}")) + .fetch_all(&pool) + .await + .expect("Should be able to read token_id's from blances table"); let token_id_registry = token_id_registry.into_iter().map(|token_id| token_id.0).collect(); Self { erc_cache: HashMap::new(), token_id_registry } } - pub fn empty() -> Self { - Self { erc_cache: HashMap::new(), token_id_registry: HashSet::new() } - } - pub fn contains_token_id(&self, token_id: &str) -> bool { self.token_id_registry.contains(token_id) } diff --git a/crates/torii/core/src/sql/erc.rs b/crates/torii/core/src/sql/erc.rs index 0a7acc643d..cef58f281a 100644 --- a/crates/torii/core/src/sql/erc.rs +++ b/crates/torii/core/src/sql/erc.rs @@ -9,6 +9,7 @@ use starknet::providers::Provider; use super::utils::{u256_to_sql_string, I256}; use super::{Sql, FELT_DELIMITER}; +use crate::constants::TOKEN_TRANSFER_TABLE; use crate::executor::{ ApplyBalanceDiffQuery, Argument, QueryMessage, QueryType, RegisterErc20TokenQuery, RegisterErc721TokenQuery, @@ -249,9 +250,10 @@ impl Sql { block_timestamp: u64, event_id: &str, ) -> Result<()> { - let insert_query = "INSERT INTO erc_transfers (id, contract_address, from_address, \ - to_address, amount, token_id, executed_at) VALUES (?, ?, ?, ?, ?, ?, \ - ?)"; + let insert_query = format!( + "INSERT INTO {TOKEN_TRANSFER_TABLE} (id, contract_address, from_address, to_address, \ + amount, token_id, executed_at) VALUES (?, ?, ?, ?, ?, ?, ?)" + ); self.executor.send(QueryMessage::new( insert_query.to_string(), diff --git a/crates/torii/core/src/utils.rs b/crates/torii/core/src/utils.rs index d66d294509..2556caa8f0 100644 --- a/crates/torii/core/src/utils.rs +++ b/crates/torii/core/src/utils.rs @@ -7,7 +7,6 @@ use ipfs_api_backend_hyper::{IpfsApi, IpfsClient, TryFromUri}; use tokio_util::bytes::Bytes; use tracing::info; -// pub const IPFS_URL: &str = "https://cartridge.infura-ipfs.io/ipfs/"; pub const IPFS_URL: &str = "https://ipfs.io/ipfs/"; pub const MAX_RETRY: u8 = 3; diff --git a/crates/torii/graphql/src/constants.rs b/crates/torii/graphql/src/constants.rs index fc5a376f56..1e89a0bea5 100644 --- a/crates/torii/graphql/src/constants.rs +++ b/crates/torii/graphql/src/constants.rs @@ -9,8 +9,6 @@ pub const EVENT_MESSAGE_TABLE: &str = "event_messages"; pub const MODEL_TABLE: &str = "models"; pub const TRANSACTION_TABLE: &str = "transactions"; pub const METADATA_TABLE: &str = "metadata"; -pub const ERC_BALANCE_TABLE: &str = "balances"; -pub const ERC_TRANSFER_TABLE: &str = "erc_transfers"; pub const ID_COLUMN: &str = "id"; pub const EVENT_ID_COLUMN: &str = "event_id"; @@ -35,9 +33,10 @@ pub const QUERY_TYPE_NAME: &str = "World__Query"; pub const SUBSCRIPTION_TYPE_NAME: &str = "World__Subscription"; pub const MODEL_ORDER_TYPE_NAME: &str = "World__ModelOrder"; pub const MODEL_ORDER_FIELD_TYPE_NAME: &str = "World__ModelOrderField"; -pub const ERC_BALANCE_TYPE_NAME: &str = "ERC__Balance"; -pub const ERC_TRANSFER_TYPE_NAME: &str = "ERC__Transfer"; -pub const ERC_TOKEN_TYPE_NAME: &str = "ERC__Token"; +pub const TOKEN_BALANCE_TYPE_NAME: &str = "Token__Balance"; +pub const TOKEN_TRANSFER_TYPE_NAME: &str = "Token__Transfer"; +pub const TOKEN_TYPE_NAME: &str = "ERC__Token"; +pub const ERC721_METADATA_TYPE_NAME: &str = "ERC721__Metadata"; // objects' single and plural names pub const ENTITY_NAMES: (&str, &str) = ("entity", "entities"); @@ -49,10 +48,11 @@ pub const CONTENT_NAMES: (&str, &str) = ("content", "contents"); pub const METADATA_NAMES: (&str, &str) = ("metadata", "metadatas"); pub const TRANSACTION_NAMES: (&str, &str) = ("transaction", "transactions"); pub const PAGE_INFO_NAMES: (&str, &str) = ("pageInfo", ""); +pub const TOKEN_NAME: (&str, &str) = ("token", "tokens"); +pub const TOKEN_BALANCE_NAME: (&str, &str) = ("", "tokenBalances"); +pub const TOKEN_TRANSFER_NAME: (&str, &str) = ("", "tokenTransfers"); -pub const ERC_BALANCE_NAME: (&str, &str) = ("ercBalance", ""); -pub const ERC_TOKEN_NAME: (&str, &str) = ("ercToken", ""); -pub const ERC_TRANSFER_NAME: (&str, &str) = ("ercTransfer", ""); +pub const ERC721_METADATA_NAME: (&str, &str) = ("erc721Metadata", ""); // misc pub const ORDER_DIR_TYPE_NAME: &str = "OrderDirection"; diff --git a/crates/torii/graphql/src/mapping.rs b/crates/torii/graphql/src/mapping.rs index 089d2a6b51..adb8b37bc1 100644 --- a/crates/torii/graphql/src/mapping.rs +++ b/crates/torii/graphql/src/mapping.rs @@ -4,7 +4,9 @@ use async_graphql::Name; use dojo_types::primitive::Primitive; use lazy_static::lazy_static; -use crate::constants::{CONTENT_TYPE_NAME, ERC_TOKEN_TYPE_NAME, SOCIAL_TYPE_NAME}; +use crate::constants::{ + CONTENT_TYPE_NAME, ERC721_METADATA_TYPE_NAME, SOCIAL_TYPE_NAME, TOKEN_TYPE_NAME, +}; use crate::types::{GraphqlType, TypeData, TypeMapping}; lazy_static! { @@ -145,27 +147,39 @@ lazy_static! { ), ]); - pub static ref ERC_BALANCE_TYPE_MAPPING: TypeMapping = IndexMap::from([ + pub static ref TOKEN_BALANCE_TYPE_MAPPING: TypeMapping = IndexMap::from([ (Name::new("balance"), TypeData::Simple(TypeRef::named_nn(TypeRef::STRING))), (Name::new("type"), TypeData::Simple(TypeRef::named_nn(TypeRef::STRING))), - (Name::new("tokenMetadata"), TypeData::Simple(TypeRef::named_nn(ERC_TOKEN_TYPE_NAME))), + (Name::new("tokenMetadata"), TypeData::Simple(TypeRef::named_nn(TOKEN_TYPE_NAME))), ]); - pub static ref ERC_TRANSFER_TYPE_MAPPING: TypeMapping = IndexMap::from([ + pub static ref TOKEN_TRANSFER_TYPE_MAPPING: TypeMapping = IndexMap::from([ (Name::new("from"), TypeData::Simple(TypeRef::named_nn(TypeRef::STRING))), (Name::new("to"), TypeData::Simple(TypeRef::named_nn(TypeRef::STRING))), (Name::new("amount"), TypeData::Simple(TypeRef::named_nn(TypeRef::STRING))), (Name::new("type"), TypeData::Simple(TypeRef::named_nn(TypeRef::STRING))), (Name::new("executedAt"), TypeData::Simple(TypeRef::named_nn(TypeRef::STRING))), - (Name::new("tokenMetadata"), TypeData::Simple(TypeRef::named_nn(ERC_TOKEN_TYPE_NAME))), + (Name::new("tokenMetadata"), TypeData::Simple(TypeRef::named_nn(TOKEN_TYPE_NAME))), (Name::new("transactionHash"), TypeData::Simple(TypeRef::named_nn(TypeRef::STRING))), ]); - pub static ref ERC_TOKEN_TYPE_MAPPING: TypeMapping = IndexMap::from([ + pub static ref 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("decimals"), TypeData::Simple(TypeRef::named_nn(TypeRef::STRING))), (Name::new("contractAddress"), TypeData::Simple(TypeRef::named_nn(TypeRef::STRING))), + (Name::new("decimals"), TypeData::Simple(TypeRef::named(TypeRef::STRING))), + ( + Name::new("erc721"), + TypeData::Nested((TypeRef::named(ERC721_METADATA_TYPE_NAME), IndexMap::new())) + ), + ]); + + pub static ref ERC721_METADATA_TYPE_MAPPING: TypeMapping = IndexMap::from([ + (Name::new("tokenId"), TypeData::Simple(TypeRef::named_nn(TypeRef::STRING))), + (Name::new("name"), TypeData::Simple(TypeRef::named_nn(TypeRef::STRING))), + (Name::new("description"), TypeData::Simple(TypeRef::named_nn(TypeRef::STRING))), + (Name::new("attributes"), TypeData::Simple(TypeRef::named_nn(TypeRef::STRING))), + (Name::new("imagePath"), TypeData::Simple(TypeRef::named_nn(TypeRef::STRING))), + (Name::new("metadata"), TypeData::Simple(TypeRef::named_nn(TypeRef::STRING))), ]); } diff --git a/crates/torii/graphql/src/object/erc/erc_token.rs b/crates/torii/graphql/src/object/erc/erc_token.rs index 14b8de7877..d1a312b932 100644 --- a/crates/torii/graphql/src/object/erc/erc_token.rs +++ b/crates/torii/graphql/src/object/erc/erc_token.rs @@ -1,5 +1,7 @@ -use crate::constants::{ERC_TOKEN_NAME, ERC_TOKEN_TYPE_NAME}; -use crate::mapping::ERC_TOKEN_TYPE_MAPPING; +use crate::constants::{ + ERC721_METADATA_NAME, ERC721_METADATA_TYPE_NAME, TOKEN_NAME, TOKEN_TYPE_NAME, +}; +use crate::mapping::{ERC721_METADATA_TYPE_MAPPING, TOKEN_TYPE_MAPPING}; use crate::object::BasicObject; use crate::types::TypeMapping; @@ -8,14 +10,31 @@ pub struct ErcTokenObject; impl BasicObject for ErcTokenObject { fn name(&self) -> (&str, &str) { - ERC_TOKEN_NAME + TOKEN_NAME } fn type_name(&self) -> &str { - ERC_TOKEN_TYPE_NAME + TOKEN_TYPE_NAME } fn type_mapping(&self) -> &TypeMapping { - &ERC_TOKEN_TYPE_MAPPING + &TOKEN_TYPE_MAPPING + } +} + +#[derive(Debug)] +pub struct Erc721MetadataObject; + +impl BasicObject for Erc721MetadataObject { + fn name(&self) -> (&str, &str) { + ERC721_METADATA_NAME + } + + fn type_name(&self) -> &str { + ERC721_METADATA_TYPE_NAME + } + + fn type_mapping(&self) -> &TypeMapping { + &ERC721_METADATA_TYPE_MAPPING } } diff --git a/crates/torii/graphql/src/object/erc/mod.rs b/crates/torii/graphql/src/object/erc/mod.rs index 3e85722cca..72d0eb1fab 100644 --- a/crates/torii/graphql/src/object/erc/mod.rs +++ b/crates/torii/graphql/src/object/erc/mod.rs @@ -1,9 +1,9 @@ use super::connection::cursor; use crate::query::order::CursorDirection; -pub mod erc_balance; pub mod erc_token; -pub mod erc_transfer; +pub mod token_balance; +pub mod token_transfer; fn handle_cursor( cursor: &str, diff --git a/crates/torii/graphql/src/object/erc/erc_balance.rs b/crates/torii/graphql/src/object/erc/token_balance.rs similarity index 78% rename from crates/torii/graphql/src/object/erc/erc_balance.rs rename to crates/torii/graphql/src/object/erc/token_balance.rs index 848303e71a..44818d9eee 100644 --- a/crates/torii/graphql/src/object/erc/erc_balance.rs +++ b/crates/torii/graphql/src/object/erc/token_balance.rs @@ -6,14 +6,13 @@ use serde::Deserialize; use sqlx::sqlite::SqliteRow; use sqlx::{FromRow, Pool, Row, Sqlite, SqliteConnection}; use starknet_crypto::Felt; +use torii_core::constants::TOKEN_BALANCE_TABLE; use torii_core::sql::utils::felt_to_sql_string; use tracing::warn; use super::handle_cursor; -use crate::constants::{ - DEFAULT_LIMIT, ERC_BALANCE_NAME, ERC_BALANCE_TABLE, ERC_BALANCE_TYPE_NAME, ID_COLUMN, -}; -use crate::mapping::ERC_BALANCE_TYPE_MAPPING; +use crate::constants::{DEFAULT_LIMIT, ID_COLUMN, TOKEN_BALANCE_NAME, TOKEN_BALANCE_TYPE_NAME}; +use crate::mapping::TOKEN_BALANCE_TYPE_MAPPING; use crate::object::connection::page_info::PageInfoObject; use crate::object::connection::{ connection_arguments, cursor, parse_connection_arguments, ConnectionArguments, @@ -30,15 +29,15 @@ pub struct ErcBalanceObject; impl BasicObject for ErcBalanceObject { fn name(&self) -> (&str, &str) { - ERC_BALANCE_NAME + TOKEN_BALANCE_NAME } fn type_name(&self) -> &str { - ERC_BALANCE_TYPE_NAME + TOKEN_BALANCE_TYPE_NAME } fn type_mapping(&self) -> &TypeMapping { - &ERC_BALANCE_TYPE_MAPPING + &TOKEN_BALANCE_TYPE_MAPPING } } @@ -51,7 +50,7 @@ impl ResolvableObject for ErcBalanceObject { ); let mut field = Field::new( - self.name().0, + self.name().1, TypeRef::named(format!("{}Connection", self.type_name())), move |ctx| { FieldFuture::new(async move { @@ -69,12 +68,12 @@ impl ResolvableObject for ErcBalanceObject { }]; let total_count = - count_rows(&mut conn, ERC_BALANCE_TABLE, &None, &Some(filter)).await?; + count_rows(&mut conn, TOKEN_BALANCE_TABLE, &None, &Some(filter)).await?; let (data, page_info) = - fetch_erc_balances(&mut conn, address, &connection, total_count).await?; + fetch_token_balances(&mut conn, address, &connection, total_count).await?; - let results = erc_balance_connection_output(&data, total_count, page_info)?; + let results = token_balances_connection_output(&data, total_count, page_info)?; Ok(Some(Value::Object(results))) }) @@ -87,18 +86,18 @@ impl ResolvableObject for ErcBalanceObject { } } -async fn fetch_erc_balances( +async fn fetch_token_balances( conn: &mut SqliteConnection, address: Felt, connection: &ConnectionArguments, total_count: i64, ) -> sqlx::Result<(Vec, PageInfo)> { - let table_name = ERC_BALANCE_TABLE; + let table_name = TOKEN_BALANCE_TABLE; let id_column = format!("b.{}", ID_COLUMN); let mut query = format!( "SELECT b.id, t.contract_address, t.name, t.symbol, t.decimals, b.balance, b.token_id, \ - c.contract_type + t.metadata, c.contract_type FROM {table_name} b JOIN tokens t ON b.token_id = t.id JOIN contracts c ON t.contract_address = c.contract_address" @@ -207,7 +206,7 @@ async fn fetch_erc_balances( } } -fn erc_balance_connection_output( +fn token_balances_connection_output( data: &[SqliteRow], total_count: i64, page_info: PageInfo, @@ -222,10 +221,9 @@ fn erc_balance_connection_output( let token_metadata = Value::Object(ValueMapping::from([ (Name::new("name"), Value::String(row.name)), (Name::new("symbol"), Value::String(row.symbol)), - // for erc20 there is no token_id - (Name::new("tokenId"), Value::Null), (Name::new("decimals"), Value::String(row.decimals.to_string())), (Name::new("contractAddress"), Value::String(row.contract_address.clone())), + (Name::new("erc721"), Value::Null), ])); Value::Object(ValueMapping::from([ @@ -239,12 +237,42 @@ fn erc_balance_connection_output( let token_id = row.token_id.split(':').collect::>(); assert!(token_id.len() == 2); + let metadata: serde_json::Value = + serde_json::from_str(&row.metadata).expect("metadata is always json"); + let erc721_name = + metadata.get("name").map(|v| v.to_string().trim_matches('"').to_string()); + let erc721_description = metadata + .get("description") + .map(|v| v.to_string().trim_matches('"').to_string()); + let erc721_attributes = + metadata.get("attributes").map(|v| v.to_string().trim_matches('"').to_string()); + + let image_path = format!("{}/{}", token_id.join("/"), "image"); let token_metadata = Value::Object(ValueMapping::from([ (Name::new("contractAddress"), Value::String(row.contract_address.clone())), (Name::new("name"), Value::String(row.name)), (Name::new("symbol"), Value::String(row.symbol)), - (Name::new("tokenId"), Value::String(token_id[1].to_string())), (Name::new("decimals"), Value::String(row.decimals.to_string())), + ( + Name::new("erc721"), + Value::Object(ValueMapping::from([ + (Name::new("imagePath"), Value::String(image_path)), + (Name::new("tokenId"), Value::String(token_id[1].to_string())), + (Name::new("metadata"), Value::String(row.metadata)), + ( + Name::new("name"), + erc721_name.map(Value::String).unwrap_or(Value::Null), + ), + ( + Name::new("description"), + erc721_description.map(Value::String).unwrap_or(Value::Null), + ), + ( + Name::new("attributes"), + erc721_attributes.map(Value::String).unwrap_or(Value::Null), + ), + ])), + ), ])); Value::Object(ValueMapping::from([ @@ -291,4 +319,5 @@ struct BalanceQueryResultRaw { pub token_id: String, pub balance: String, pub contract_type: String, + pub metadata: String, } diff --git a/crates/torii/graphql/src/object/erc/erc_transfer.rs b/crates/torii/graphql/src/object/erc/token_transfer.rs similarity index 79% rename from crates/torii/graphql/src/object/erc/erc_transfer.rs rename to crates/torii/graphql/src/object/erc/token_transfer.rs index e1c950db39..2dc8d8bd13 100644 --- a/crates/torii/graphql/src/object/erc/erc_transfer.rs +++ b/crates/torii/graphql/src/object/erc/token_transfer.rs @@ -6,15 +6,14 @@ use serde::Deserialize; use sqlx::sqlite::SqliteRow; use sqlx::{FromRow, Pool, Row, Sqlite, SqliteConnection}; use starknet_crypto::Felt; +use torii_core::constants::TOKEN_TRANSFER_TABLE; use torii_core::engine::get_transaction_hash_from_event_id; use torii_core::sql::utils::felt_to_sql_string; use tracing::warn; use super::handle_cursor; -use crate::constants::{ - DEFAULT_LIMIT, ERC_TRANSFER_NAME, ERC_TRANSFER_TABLE, ERC_TRANSFER_TYPE_NAME, ID_COLUMN, -}; -use crate::mapping::ERC_TRANSFER_TYPE_MAPPING; +use crate::constants::{DEFAULT_LIMIT, ID_COLUMN, TOKEN_TRANSFER_NAME, TOKEN_TRANSFER_TYPE_NAME}; +use crate::mapping::TOKEN_TRANSFER_TYPE_MAPPING; use crate::object::connection::page_info::PageInfoObject; use crate::object::connection::{ connection_arguments, cursor, parse_connection_arguments, ConnectionArguments, @@ -29,15 +28,15 @@ pub struct ErcTransferObject; impl BasicObject for ErcTransferObject { fn name(&self) -> (&str, &str) { - ERC_TRANSFER_NAME + TOKEN_TRANSFER_NAME } fn type_name(&self) -> &str { - ERC_TRANSFER_TYPE_NAME + TOKEN_TRANSFER_TYPE_NAME } fn type_mapping(&self) -> &TypeMapping { - &ERC_TRANSFER_TYPE_MAPPING + &TOKEN_TRANSFER_TYPE_MAPPING } } @@ -50,7 +49,7 @@ impl ResolvableObject for ErcTransferObject { ); let mut field = Field::new( - self.name().0, + self.name().1, TypeRef::named(format!("{}Connection", self.type_name())), move |ctx| { FieldFuture::new(async move { @@ -61,10 +60,10 @@ impl ResolvableObject for ErcTransferObject { &account_address.to_case(Case::Camel), )?; - let total_count: (i64,) = sqlx::query_as( - "SELECT COUNT(*) FROM erc_transfers WHERE from_address = ? OR to_address \ - = ?", - ) + let total_count: (i64,) = sqlx::query_as(&format!( + "SELECT COUNT(*) FROM {TOKEN_TRANSFER_TABLE} WHERE from_address = ? OR \ + to_address = ?" + )) .bind(felt_to_sql_string(&address)) .bind(felt_to_sql_string(&address)) .fetch_one(&mut *conn) @@ -72,8 +71,8 @@ impl ResolvableObject for ErcTransferObject { let total_count = total_count.0; let (data, page_info) = - fetch_erc_transfers(&mut conn, address, &connection, total_count).await?; - let results = erc_transfer_connection_output(&data, total_count, page_info)?; + fetch_token_transfers(&mut conn, address, &connection, total_count).await?; + let results = token_transfers_connection_output(&data, total_count, page_info)?; Ok(Some(Value::Object(results))) }) @@ -86,13 +85,13 @@ impl ResolvableObject for ErcTransferObject { } } -async fn fetch_erc_transfers( +async fn fetch_token_transfers( conn: &mut SqliteConnection, address: Felt, connection: &ConnectionArguments, total_count: i64, ) -> sqlx::Result<(Vec, PageInfo)> { - let table_name = ERC_TRANSFER_TABLE; + let table_name = TOKEN_TRANSFER_TABLE; let id_column = format!("et.{}", ID_COLUMN); let mut query = format!( @@ -108,7 +107,8 @@ SELECT t.name, t.symbol, t.decimals, - c.contract_type + c.contract_type, + t.metadata FROM {table_name} et JOIN @@ -227,7 +227,7 @@ JOIN } } -fn erc_transfer_connection_output( +fn token_transfers_connection_output( data: &[SqliteRow], total_count: i64, page_info: PageInfo, @@ -248,6 +248,7 @@ fn erc_transfer_connection_output( (Name::new("tokenId"), Value::Null), (Name::new("decimals"), Value::String(row.decimals.to_string())), (Name::new("contractAddress"), Value::String(row.contract_address.clone())), + (Name::new("erc721"), Value::Null), ])); Value::Object(ValueMapping::from([ @@ -265,12 +266,42 @@ fn erc_transfer_connection_output( let token_id = row.token_id.split(':').collect::>(); assert!(token_id.len() == 2); + let image_path = format!("{}/{}", token_id.join("/"), "image"); + let metadata: serde_json::Value = + serde_json::from_str(&row.metadata).expect("metadata is always json"); + let erc721_name = + metadata.get("name").map(|v| v.to_string().trim_matches('"').to_string()); + let erc721_description = metadata + .get("description") + .map(|v| v.to_string().trim_matches('"').to_string()); + let erc721_attributes = + metadata.get("attributes").map(|v| v.to_string().trim_matches('"').to_string()); + let token_metadata = Value::Object(ValueMapping::from([ (Name::new("name"), Value::String(row.name)), (Name::new("symbol"), Value::String(row.symbol)), - (Name::new("tokenId"), Value::String(token_id[1].to_string())), (Name::new("decimals"), Value::String(row.decimals.to_string())), (Name::new("contractAddress"), Value::String(row.contract_address.clone())), + ( + Name::new("erc721"), + Value::Object(ValueMapping::from([ + (Name::new("imagePath"), Value::String(image_path)), + (Name::new("tokenId"), Value::String(token_id[1].to_string())), + (Name::new("metadata"), Value::String(row.metadata)), + ( + Name::new("name"), + erc721_name.map(Value::String).unwrap_or(Value::Null), + ), + ( + Name::new("description"), + erc721_description.map(Value::String).unwrap_or(Value::Null), + ), + ( + Name::new("attributes"), + erc721_attributes.map(Value::String).unwrap_or(Value::Null), + ), + ])), + ), ])); Value::Object(ValueMapping::from([ @@ -324,4 +355,5 @@ struct TransferQueryResultRaw { pub symbol: String, pub decimals: u8, pub contract_type: String, + pub metadata: String, } diff --git a/crates/torii/graphql/src/schema.rs b/crates/torii/graphql/src/schema.rs index 5f70c49908..7d9b90b883 100644 --- a/crates/torii/graphql/src/schema.rs +++ b/crates/torii/graphql/src/schema.rs @@ -10,9 +10,9 @@ use super::object::model_data::ModelDataObject; use super::types::ScalarType; use super::utils; use crate::constants::{QUERY_TYPE_NAME, SUBSCRIPTION_TYPE_NAME}; -use crate::object::erc::erc_balance::ErcBalanceObject; -use crate::object::erc::erc_token::ErcTokenObject; -use crate::object::erc::erc_transfer::ErcTransferObject; +use crate::object::erc::erc_token::{Erc721MetadataObject, ErcTokenObject}; +use crate::object::erc::token_balance::ErcBalanceObject; +use crate::object::erc::token_transfer::ErcTransferObject; use crate::object::event_message::EventMessageObject; use crate::object::metadata::content::ContentObject; use crate::object::metadata::social::SocialObject; @@ -122,6 +122,7 @@ async fn build_objects(pool: &SqlitePool) -> Result<(Vec, Vec, ) -> anyhow::Result { - let query = sqlx::query_as::<_, (String,)>("SELECT metadata FROM tokens WHERE id = ?") - .bind(token_id) - .fetch_one(&pool) - .await - .with_context(|| { - format!("Failed to fetch metadata from database for token_id: {}", token_id) - })?; + let query = sqlx::query_as::<_, (String,)>(&format!( + "SELECT metadata FROM {TOKENS_TABLE} WHERE id = ?" + )) + .bind(token_id) + .fetch_one(&pool) + .await + .with_context(|| { + format!("Failed to fetch metadata from database for token_id: {}", token_id) + })?; let metadata: serde_json::Value = serde_json::from_str(&query.0).context("Failed to parse metadata")?;