diff --git a/crates/metaboard/src/metaboard_client.rs b/crates/metaboard/src/metaboard_client.rs index 514f7aee..fbb7fe57 100644 --- a/crates/metaboard/src/metaboard_client.rs +++ b/crates/metaboard/src/metaboard_client.rs @@ -1,6 +1,10 @@ use crate::cynic_client::{CynicClient, CynicClientError}; use crate::types::metas::*; -use alloy::primitives::hex::{decode, encode, FromHexError}; +use alloy::primitives::{ + hex::{decode, encode, FromHexError}, + Address, +}; +use core::str::FromStr; use reqwest::Url; use thiserror::Error; @@ -26,6 +30,17 @@ pub enum MetaboardSubgraphClientError { #[source] source: FromHexError, }, + #[error("Error parsing metaboard address {address}: {source}")] + AddressParseError { + address: String, + #[source] + source:
::Err, + }, + #[error("Request error fetching metaboard addresses: {source}")] + RequestErrorMetaBoards { + #[source] + source: CynicClientError, + }, } pub struct MetaboardSubgraphClient { @@ -111,6 +126,35 @@ impl MetaboardSubgraphClient { Ok(meta_bytes) } + + /// Fetch MetaBoard contract addresses from the subgraph. + pub async fn get_metaboard_addresses( + &self, + first: Option, + skip: Option, + ) -> Result, MetaboardSubgraphClientError> { + let data = + self.query::( + MetaBoardAddressesVariables { first, skip }, + ) + .await + .map_err(|e| MetaboardSubgraphClientError::RequestErrorMetaBoards { source: e })?; + + let mut addresses = Vec::with_capacity(data.meta_boards.len()); + for board in data.meta_boards { + let address_hex = board.address.0; + let address = Address::from_str(&address_hex).map_err(|e| { + MetaboardSubgraphClientError::AddressParseError { + address: address_hex.clone(), + source: e, + } + })?; + + addresses.push(address); + } + + Ok(addresses) + } } #[cfg(test)] @@ -289,4 +333,69 @@ mod tests { _ => panic!("Unexpected result: {:?}", result), } } + + #[tokio::test] + async fn test_get_metaboard_addresses_success() { + let server = MockServer::start_async().await; + let url = Url::parse(&server.url("/")).unwrap(); + + server.mock(|when, then| { + when.method(POST).path("/").body_contains("metaBoards"); + then.status(200).json_body_obj(&{ + serde_json::json!({ + "data": { + "metaBoards": [ + { + "address": "0x0000000000000000000000000000000000000001", + }, + { + "address": "0x0000000000000000000000000000000000000002", + } + ] + } + }) + }); + }); + + let client = MetaboardSubgraphClient::new(url); + + let result = client + .get_metaboard_addresses(Some(10), Some(0)) + .await + .unwrap(); + + assert_eq!(result.len(), 2); + + assert_eq!( + result[0], + Address::from_str("0x0000000000000000000000000000000000000001").unwrap() + ); + assert_eq!( + result[1], + Address::from_str("0x0000000000000000000000000000000000000002").unwrap() + ); + } + + #[tokio::test] + async fn test_get_metaboard_addresses_empty() { + let server = MockServer::start_async().await; + let url = Url::parse(&server.url("/")).unwrap(); + + server.mock(|when, then| { + when.method(POST).path("/").body_contains("metaBoards"); + then.status(200).json_body_obj(&{ + serde_json::json!({ + "data": { + "metaBoards": [] + } + }) + }); + }); + + let client = MetaboardSubgraphClient::new(url); + + let result = client.get_metaboard_addresses(Some(5), None).await.unwrap(); + + assert!(result.is_empty()); + } } diff --git a/crates/metaboard/src/types/metas.rs b/crates/metaboard/src/types/metas.rs index 37164c1c..e8a71eb3 100644 --- a/crates/metaboard/src/types/metas.rs +++ b/crates/metaboard/src/types/metas.rs @@ -23,6 +23,19 @@ pub struct MetasBySubject { pub meta_v1_s: Vec, } +#[derive(cynic::QueryVariables, Debug, Default)] +pub struct MetaBoardAddressesVariables { + pub first: Option, + pub skip: Option, +} + +#[derive(cynic::QueryFragment, Debug)] +#[cynic(graphql_type = "Query", variables = "MetaBoardAddressesVariables")] +pub struct MetaBoardAddresses { + #[arguments(first: $first, skip: $skip)] + pub meta_boards: Vec, +} + #[derive(cynic::QueryFragment, Debug)] pub struct MetaV1 { pub meta_hash: Bytes,