Skip to content

Commit 255691b

Browse files
authored
feat(torii-indexer): transaction calls and outside calls (#3085)
* feat(torii-indexer): transaction calls and outside calls * parse calls * contract class cache * c * parsing native and outside calls * support v2 and write db * correct decoding of raw felts * clean * feat: add support to graphql * f * use transaction calls table instead * add caller address for outisde exectuion and calls * fix: correct caller address * fetch entrypoints names * fix send for class abo * fix deadlock * fix block not found error on pending * only parse calldata for invoke tx * offset ovr fns * retrieving function name for legacy class * use cainome * add calls graphql * token transfers from tx * move tx to executro & broadcast * trnsaction subscription * f * chore * fix tx * tx subscirption and block number * fmt * subscribe to have speicfic caller * f * c * cleanup for merge * f * transaction c * opt memroy footprint * f
1 parent 366dc5a commit 255691b

File tree

19 files changed

+1060
-381
lines changed

19 files changed

+1060
-381
lines changed

Cargo.lock

+1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/torii/graphql/src/constants.rs

+3
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ pub const EVENT_TABLE: &str = "events";
88
pub const EVENT_MESSAGE_TABLE: &str = "event_messages";
99
pub const MODEL_TABLE: &str = "models";
1010
pub const TRANSACTION_TABLE: &str = "transactions";
11+
pub const TRANSACTION_CALLS_TABLE: &str = "transaction_calls";
12+
pub const TOKEN_TRANSFER_TABLE: &str = "token_transfers";
1113
pub const METADATA_TABLE: &str = "metadata";
1214
pub const CONTROLLER_TABLE: &str = "controllers";
1315

@@ -30,6 +32,7 @@ pub const CONTENT_TYPE_NAME: &str = "World__Content";
3032
pub const METADATA_TYPE_NAME: &str = "World__Metadata";
3133
pub const PAGE_INFO_TYPE_NAME: &str = "World__PageInfo";
3234
pub const TRANSACTION_TYPE_NAME: &str = "World__Transaction";
35+
pub const CALL_TYPE_NAME: &str = "World__Call";
3336
pub const QUERY_TYPE_NAME: &str = "World__Query";
3437
pub const SUBSCRIPTION_TYPE_NAME: &str = "World__Subscription";
3538
pub const MODEL_ORDER_TYPE_NAME: &str = "World__ModelOrder";

crates/torii/graphql/src/mapping.rs

+12
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,14 @@ lazy_static! {
6464
TypeData::Simple(TypeRef::named(GraphqlType::DateTime.to_string())),
6565
),
6666
]);
67+
pub static ref CALL_MAPPING: TypeMapping = IndexMap::from([
68+
(Name::new("transactionHash"), TypeData::Simple(TypeRef::named(TypeRef::STRING))),
69+
(Name::new("contractAddress"), TypeData::Simple(TypeRef::named(TypeRef::STRING))),
70+
(Name::new("entrypoint"), TypeData::Simple(TypeRef::named(TypeRef::STRING))),
71+
(Name::new("calldata"), TypeData::Simple(TypeRef::named_list(TypeRef::STRING))),
72+
(Name::new("callType"), TypeData::Simple(TypeRef::named(TypeRef::STRING))),
73+
(Name::new("callerAddress"), TypeData::Simple(TypeRef::named(TypeRef::STRING))),
74+
]);
6775
pub static ref TRANSACTION_MAPPING: TypeMapping = IndexMap::from([
6876
(Name::new("id"), TypeData::Simple(TypeRef::named(TypeRef::ID))),
6977
(
@@ -98,6 +106,10 @@ lazy_static! {
98106
Name::new("createdAt"),
99107
TypeData::Simple(TypeRef::named(GraphqlType::DateTime.to_string())),
100108
),
109+
(
110+
Name::new("blockNumber"),
111+
TypeData::Simple(TypeRef::named(TypeRef::STRING)),
112+
)
101113
]);
102114
pub static ref PAGE_INFO_TYPE_MAPPING: TypeMapping = TypeMapping::from([
103115
(Name::new("hasPreviousPage"), TypeData::Simple(TypeRef::named_nn(TypeRef::BOOLEAN))),

crates/torii/graphql/src/object/erc/token_balance.rs

+141-244
Large diffs are not rendered by default.

crates/torii/graphql/src/object/erc/token_transfer.rs

+130-63
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ use crate::object::connection::page_info::PageInfoObject;
1818
use crate::object::connection::{
1919
connection_arguments, cursor, parse_connection_arguments, ConnectionArguments,
2020
};
21-
use crate::object::erc::erc_token::Erc721Token;
21+
use crate::object::erc::erc_token::{Erc1155Token, Erc721Token};
2222
use crate::object::{BasicObject, ResolvableObject};
2323
use crate::query::order::{CursorDirection, Direction};
2424
use crate::types::TypeMapping;
@@ -240,72 +240,17 @@ fn token_transfers_connection_output<'a>(
240240

241241
for row in data {
242242
let row = TransferQueryResultRaw::from_row(row)?;
243-
let transaction_hash = get_transaction_hash_from_event_id(&row.id);
244243
let cursor = cursor::encode(&row.id, &row.id);
245244

246-
let transfer_node = match row.contract_type.to_lowercase().as_str() {
247-
"erc20" => {
248-
let token_metadata = ErcTokenType::Erc20(Erc20Token {
249-
contract_address: row.contract_address,
250-
name: row.name,
251-
symbol: row.symbol,
252-
decimals: row.decimals,
253-
amount: row.amount,
254-
});
255-
256-
TokenTransferNode {
257-
from: row.from_address,
258-
to: row.to_address,
259-
executed_at: row.executed_at,
260-
token_metadata,
261-
transaction_hash,
262-
}
263-
}
264-
"erc721" => {
265-
// contract_address:token_id
266-
let token_id = row.token_id.split(':').collect::<Vec<&str>>();
267-
assert!(token_id.len() == 2);
268-
269-
let metadata_str = row.metadata;
270-
let metadata: serde_json::Value =
271-
serde_json::from_str(&metadata_str).expect("metadata is always json");
272-
let metadata_name =
273-
metadata.get("name").map(|v| v.to_string().trim_matches('"').to_string());
274-
let metadata_description = metadata
275-
.get("description")
276-
.map(|v| v.to_string().trim_matches('"').to_string());
277-
let metadata_attributes =
278-
metadata.get("attributes").map(|v| v.to_string().trim_matches('"').to_string());
279-
280-
let image_path = format!("{}/{}", token_id.join("/"), "image");
281-
282-
let token_metadata = ErcTokenType::Erc721(Erc721Token {
283-
name: row.name,
284-
metadata: metadata_str.to_owned(),
285-
contract_address: row.contract_address,
286-
symbol: row.symbol,
287-
token_id: token_id[1].to_string(),
288-
metadata_name,
289-
metadata_description,
290-
metadata_attributes,
291-
image_path,
292-
});
293-
294-
TokenTransferNode {
295-
from: row.from_address,
296-
to: row.to_address,
297-
executed_at: row.executed_at,
298-
token_metadata,
299-
transaction_hash,
300-
}
245+
match token_transfer_mapping_from_row(&row) {
246+
Ok(transfer_node) => {
247+
edges.push(ConnectionEdge { node: transfer_node, cursor });
301248
}
302-
_ => {
303-
warn!("Unknown contract type: {}", row.contract_type);
249+
Err(err) => {
250+
warn!("Failed to transform row to TokenTransferNode: {}", err);
304251
continue;
305252
}
306-
};
307-
308-
edges.push(ConnectionEdge { node: transfer_node, cursor });
253+
}
309254
}
310255

311256
Ok(FieldValue::owned_any(Connection {
@@ -315,6 +260,128 @@ fn token_transfers_connection_output<'a>(
315260
}))
316261
}
317262

263+
/// Transforms a TransferQueryResultRaw into a TokenTransferNode
264+
pub fn token_transfer_mapping_from_row(
265+
row: &TransferQueryResultRaw,
266+
) -> Result<TokenTransferNode, String> {
267+
let transaction_hash = get_transaction_hash_from_event_id(&row.id);
268+
269+
match row.contract_type.to_lowercase().as_str() {
270+
"erc20" => {
271+
let token_metadata = ErcTokenType::Erc20(Erc20Token {
272+
contract_address: row.contract_address.clone(),
273+
name: row.name.clone(),
274+
symbol: row.symbol.clone(),
275+
decimals: row.decimals,
276+
amount: row.amount.clone(),
277+
});
278+
279+
Ok(TokenTransferNode {
280+
from: row.from_address.clone(),
281+
to: row.to_address.clone(),
282+
executed_at: row.executed_at.clone(),
283+
token_metadata,
284+
transaction_hash,
285+
})
286+
}
287+
"erc721" => {
288+
// contract_address:token_id
289+
let token_id = row.token_id.split(':').collect::<Vec<&str>>();
290+
if token_id.len() != 2 {
291+
return Err(format!("Invalid token_id format: {}", row.token_id));
292+
}
293+
294+
let metadata_str = &row.metadata;
295+
let metadata: serde_json::Value = match serde_json::from_str(metadata_str) {
296+
Ok(value) => value,
297+
Err(e) => return Err(format!("Failed to parse metadata as JSON: {}", e)),
298+
};
299+
300+
let metadata_name =
301+
metadata.get("name").map(|v| v.to_string().trim_matches('"').to_string());
302+
let metadata_description =
303+
metadata.get("description").map(|v| v.to_string().trim_matches('"').to_string());
304+
let metadata_attributes =
305+
metadata.get("attributes").map(|v| v.to_string().trim_matches('"').to_string());
306+
307+
let image_path = format!("{}/{}", token_id.join("/"), "image");
308+
309+
let token_metadata = ErcTokenType::Erc721(Erc721Token {
310+
name: row.name.clone(),
311+
metadata: metadata_str.to_owned(),
312+
contract_address: row.contract_address.clone(),
313+
symbol: row.symbol.clone(),
314+
token_id: token_id[1].to_string(),
315+
metadata_name,
316+
metadata_description,
317+
metadata_attributes,
318+
image_path,
319+
});
320+
321+
Ok(TokenTransferNode {
322+
from: row.from_address.clone(),
323+
to: row.to_address.clone(),
324+
executed_at: row.executed_at.clone(),
325+
token_metadata,
326+
transaction_hash,
327+
})
328+
}
329+
"erc1155" => {
330+
// contract_address:token_id
331+
let token_id = row.token_id.split(':').collect::<Vec<&str>>();
332+
if token_id.len() != 2 {
333+
return Err(format!("Invalid token_id format: {}", row.token_id));
334+
}
335+
336+
let metadata_str = &row.metadata;
337+
let (metadata_name, metadata_description, metadata_attributes, image_path) =
338+
if metadata_str.is_empty() {
339+
(None, None, None, String::new())
340+
} else {
341+
let metadata: serde_json::Value = match serde_json::from_str(metadata_str) {
342+
Ok(value) => value,
343+
Err(e) => return Err(format!("Failed to parse metadata as JSON: {}", e)),
344+
};
345+
346+
let metadata_name =
347+
metadata.get("name").map(|v| v.to_string().trim_matches('"').to_string());
348+
let metadata_description = metadata
349+
.get("description")
350+
.map(|v| v.to_string().trim_matches('"').to_string());
351+
let metadata_attributes = metadata
352+
.get("attributes")
353+
.map(|v| v.to_string().trim_matches('"').to_string());
354+
355+
let image_path = format!("{}/{}", token_id.join("/"), "image");
356+
357+
(metadata_name, metadata_description, metadata_attributes, image_path)
358+
};
359+
360+
let token_metadata = ErcTokenType::Erc1155(Erc1155Token {
361+
name: row.name.clone(),
362+
metadata: metadata_str.to_owned(),
363+
contract_address: row.contract_address.clone(),
364+
symbol: row.symbol.clone(),
365+
token_id: token_id[1].to_string(),
366+
amount: row.amount.clone(),
367+
metadata_name,
368+
metadata_description,
369+
metadata_attributes,
370+
image_path,
371+
});
372+
373+
Ok(TokenTransferNode {
374+
from: row.from_address.clone(),
375+
to: row.to_address.clone(),
376+
executed_at: row.executed_at.clone(),
377+
token_metadata,
378+
transaction_hash,
379+
})
380+
}
381+
_ => Err(format!("Unknown contract type: {}", row.contract_type)),
382+
}
383+
}
384+
318385
// TODO: This would be required when subscriptions are needed
319386
// impl ErcTransferObject {
320387
// pub fn value_mapping(entity: ErcBalance) -> ValueMapping {
@@ -325,7 +392,7 @@ fn token_transfers_connection_output<'a>(
325392

326393
#[derive(FromRow, Deserialize, Debug, Clone)]
327394
#[serde(rename_all = "camelCase")]
328-
struct TransferQueryResultRaw {
395+
pub struct TransferQueryResultRaw {
329396
pub id: String,
330397
pub contract_address: String,
331398
pub from_address: String,

crates/torii/graphql/src/object/mod.rs

+48-1
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,9 @@ use self::connection::{
2828
};
2929
use self::inputs::keys_input::parse_keys_argument;
3030
use self::inputs::order_input::parse_order_argument;
31-
use crate::query::data::{count_rows, fetch_multiple_rows, fetch_single_row};
31+
use crate::query::data::{
32+
count_rows, fetch_multiple_rows, fetch_single_row, fetch_single_row_with_joins, JoinConfig,
33+
};
3234
use crate::query::value_mapping_from_row;
3335
use crate::types::{TypeMapping, ValueMapping};
3436
use crate::utils::extract;
@@ -270,6 +272,51 @@ pub fn resolve_one(
270272
.argument(argument)
271273
}
272274

275+
// Resolves single object queries with joins, returns current object of type type_name with related
276+
// data
277+
pub fn resolve_one_with_joins(
278+
table_name: &str,
279+
id_column: &str,
280+
field_name: &str,
281+
type_name: &str,
282+
type_mapping: &TypeMapping,
283+
joins: Vec<JoinConfig>,
284+
select_columns: Option<Vec<String>>,
285+
) -> Field {
286+
let type_mapping = type_mapping.clone();
287+
let table_name = table_name.to_owned();
288+
let id_column = id_column.to_owned();
289+
let joins = joins.to_owned();
290+
let select_columns = select_columns.to_owned();
291+
let argument = InputValue::new(id_column.to_case(Case::Camel), TypeRef::named_nn(TypeRef::ID));
292+
293+
Field::new(field_name, TypeRef::named_nn(type_name), move |ctx| {
294+
let type_mapping = type_mapping.clone();
295+
let table_name = table_name.to_owned();
296+
let id_column = id_column.to_owned();
297+
let joins = joins.to_owned();
298+
let select_columns = select_columns.to_owned();
299+
300+
FieldFuture::new(async move {
301+
let mut conn = ctx.data::<Pool<Sqlite>>()?.acquire().await?;
302+
let id: String =
303+
extract::<String>(ctx.args.as_index_map(), &id_column.to_case(Case::Camel))?;
304+
let data = fetch_single_row_with_joins(
305+
&mut conn,
306+
&table_name,
307+
&id_column,
308+
&id,
309+
joins,
310+
select_columns,
311+
)
312+
.await?;
313+
let model = value_mapping_from_row(&data, &type_mapping, false, true)?;
314+
Ok(Some(Value::Object(model)))
315+
})
316+
})
317+
.argument(argument)
318+
}
319+
273320
// Resolves plural object queries, returns type of {type_name}Connection (eg "PlayerConnection")
274321
pub fn resolve_many(
275322
table_name: &str,

0 commit comments

Comments
 (0)