11// 8:12 PM - COLOCATION: Contract Read Operations
22
3- use aide:: { axum:: IntoApiResponse , transform:: TransformOperation } ;
43use alloy:: dyn_abi:: FunctionExt ;
54use alloy:: primitives:: { Address , ChainId , address} ;
65use alloy:: providers:: RootProvider ;
76use alloy:: {
87 providers:: Provider , rpc:: types:: eth:: TransactionRequest as AlloyTransactionRequest , sol,
98 sol_types:: SolCall ,
109} ;
11- use axum:: { extract:: State , http:: StatusCode , response:: Json } ;
10+ use axum:: {
11+ extract:: State ,
12+ http:: StatusCode ,
13+ response:: { IntoResponse , Json } ,
14+ } ;
1215use engine_core:: {
1316 chain:: { Chain , ChainService } ,
1417 defs:: AddressDef ,
@@ -52,7 +55,7 @@ const MULTICALL3_DEFAULT_ADDRESS: Address = address!("0xcA11bde05977b36311670288
5255// ===== REQUEST/RESPONSE TYPES =====
5356
5457/// Options for reading from smart contracts
55- #[ derive( Debug , Clone , Serialize , Deserialize , JsonSchema ) ]
58+ #[ derive( Debug , Clone , Serialize , Deserialize , JsonSchema , utoipa :: ToSchema ) ]
5659#[ serde( rename_all = "camelCase" ) ]
5760pub struct ReadOptions {
5861 /// The blockchain network ID to read from
@@ -63,13 +66,15 @@ pub struct ReadOptions {
6366 /// which is deployed on most networks
6467 #[ serde( default = "default_multicall_address" ) ]
6568 #[ schemars( with = "AddressDef" ) ]
69+ #[ schema( value_type = AddressDef ) ]
6670 pub multicall_address : Address ,
6771 /// Optional address to use as the caller for view functions
6872 ///
6973 /// This can be useful for functions that return different values
7074 /// based on the caller's address or permissions
7175 #[ serde( skip_serializing_if = "Option::is_none" ) ]
7276 #[ schemars( with = "Option<AddressDef>" ) ]
77+ #[ schema( value_type = Option <AddressDef >) ]
7378 pub from : Option < Address > ,
7479}
7580
@@ -78,7 +83,7 @@ fn default_multicall_address() -> Address {
7883}
7984
8085/// Request to read from multiple smart contracts
81- #[ derive( Debug , Clone , Serialize , Deserialize , JsonSchema ) ]
86+ #[ derive( Debug , Clone , Serialize , Deserialize , JsonSchema , utoipa :: ToSchema ) ]
8287#[ serde( rename_all = "camelCase" ) ]
8388pub struct ReadRequest {
8489 /// Configuration options for the read operation
@@ -94,18 +99,19 @@ pub struct ReadRequest {
9499/// Each result can either be successful (containing the function return value)
95100/// or failed (containing detailed error information). The `success` field
96101/// indicates which case applies.
97- #[ derive( Debug , Clone , Serialize , Deserialize , JsonSchema ) ]
102+ #[ derive( Debug , Clone , Serialize , Deserialize , JsonSchema , utoipa :: ToSchema ) ]
98103#[ serde( untagged) ]
99104pub enum ReadResultItem {
100105 Success ( ReadResultSuccessItem ) ,
101106 Failure ( ReadResultFailureItem ) ,
102107}
103108
104109/// Successful result from a contract read operation
105- #[ derive( Debug , Clone , Serialize , Deserialize , JsonSchema ) ]
110+ #[ derive( Debug , Clone , Serialize , Deserialize , JsonSchema , utoipa :: ToSchema ) ]
106111pub struct ReadResultSuccessItem {
107112 /// Always true for successful operations
108113 #[ schemars( with = "bool" ) ]
114+ #[ schema( value_type = bool ) ]
109115 pub success : serde_bool:: True ,
110116 /// The decoded return value from the contract function
111117 ///
@@ -115,10 +121,11 @@ pub struct ReadResultSuccessItem {
115121}
116122
117123/// Failed result from a contract read operation
118- #[ derive( Debug , Clone , Serialize , Deserialize , JsonSchema ) ]
124+ #[ derive( Debug , Clone , Serialize , Deserialize , JsonSchema , utoipa :: ToSchema ) ]
119125pub struct ReadResultFailureItem {
120126 /// Always false for failed operations
121127 #[ schemars( with = "bool" ) ]
128+ #[ schema( value_type = bool ) ]
122129 pub success : serde_bool:: False ,
123130 /// Detailed error information describing what went wrong
124131 ///
@@ -128,7 +135,7 @@ pub struct ReadResultFailureItem {
128135}
129136
130137/// Collection of results from multiple contract read operations
131- #[ derive( Debug , Clone , Serialize , Deserialize , JsonSchema ) ]
138+ #[ derive( Debug , Clone , Serialize , Deserialize , JsonSchema , utoipa :: ToSchema ) ]
132139pub struct ReadResults {
133140 /// Array of results, one for each input contract call
134141 ///
@@ -137,7 +144,7 @@ pub struct ReadResults {
137144}
138145
139146/// Response from the contract read endpoint
140- #[ derive( Debug , Clone , Serialize , Deserialize , JsonSchema ) ]
147+ #[ derive( Debug , Clone , Serialize , Deserialize , JsonSchema , utoipa :: ToSchema ) ]
141148pub struct ReadResponse {
142149 /// Container for all read operation results
143150 pub result : ReadResults ,
@@ -179,12 +186,29 @@ impl ReadResultItem {
179186
180187// ===== ROUTE HANDLER =====
181188
189+ #[ utoipa:: path(
190+ post,
191+ operation_id = "readContract" ,
192+ path = "/read/contract" ,
193+ tag = "Read" ,
194+ request_body( content = ReadRequest , description = "Read contract request" , content_type = "application/json" ) ,
195+ responses(
196+ ( status = 200 , description = "Successfully read contract data" , body = ReadResponse , content_type = "application/json" ) ,
197+ ) ,
198+ params(
199+ ( "x-thirdweb-client-id" = Option <String >, Header , description = "Thirdweb client ID, passed along with the service key" ) ,
200+ ( "x-thirdweb-service-key" = Option <String >, Header , description = "Thirdweb service key, passed when using the client ID" ) ,
201+ ( "x-thirdweb-secret-key" = Option <String >, Header , description = "Thirdweb secret key, passed standalone" ) ,
202+ )
203+ ) ]
204+ /// Read Contract
205+ ///
182206/// Read from multiple smart contracts using multicall
183207pub async fn read_contract (
184208 State ( state) : State < EngineServerState > ,
185209 OptionalRpcCredentialsExtractor ( rpc_credentials) : OptionalRpcCredentialsExtractor ,
186210 EngineJson ( request) : EngineJson < ReadRequest > ,
187- ) -> Result < impl IntoApiResponse , ApiEngineError > {
211+ ) -> Result < impl IntoResponse , ApiEngineError > {
188212 let auth: Option < ThirdwebAuth > = rpc_credentials. and_then ( |creds| match creds {
189213 engine_core:: chain:: RpcCredentials :: Thirdweb ( auth) => Some ( auth) ,
190214 } ) ;
@@ -355,44 +379,3 @@ fn process_multicall_result(
355379 }
356380}
357381
358- // ===== DOCUMENTATION =====
359-
360- pub fn read_contract_docs ( op : TransformOperation ) -> TransformOperation {
361- op. id ( "readContract" )
362- . description (
363- "Read from multiple smart contracts using multicall.\n \n \
364- This endpoint allows you to efficiently call multiple read-only contract functions \
365- in a single request. All calls are batched using the Multicall3 contract for \
366- optimal gas usage and performance.\n \n \
367- ## Features\n \
368- - Batch multiple contract calls in one request\n \
369- - Automatic ABI resolution or use provided ABIs\n \
370- - Support for function names, signatures, or full function declarations\n \
371- - Detailed error information for each failed call\n \
372- - Preserves original parameter order in results\n \n \
373- ## Authentication\n \
374- - Optional: Provide `x-thirdweb-secret-key` or `x-thirdweb-client-id` + `x-thirdweb-service-key`\n \
375- - If provided, will be used for ABI resolution from verified contracts\n \n \
376- ## Error Handling\n \
377- - Individual call failures don't affect other calls\n \
378- - Each result includes success status and detailed error information\n \
379- - Preparation errors (ABI resolution, parameter encoding) are preserved\n \
380- - Execution errors (multicall failures) are clearly identified"
381- )
382- . summary ( "Batch read contract functions" )
383- . response_with :: < 200 , Json < ReadResponse > , _ > ( |res| {
384- res. description ( "Successfully read contract data" )
385- . example ( ReadResponse {
386- result : ReadResults {
387- results : vec ! [ ] ,
388- } ,
389- } )
390- } )
391- . response_with :: < 400 , Json < ErrorResponse > , _ > ( |res| {
392- res. description ( "Bad request - invalid parameters or chain ID" )
393- } )
394- . response_with :: < 500 , Json < ErrorResponse > , _ > ( |res| {
395- res. description ( "Internal server error" )
396- } )
397- . tag ( "Contract Operations" )
398- }
0 commit comments