1
1
// 8:12 PM - COLOCATION: Contract Read Operations
2
2
3
- use aide:: { axum:: IntoApiResponse , transform:: TransformOperation } ;
4
3
use alloy:: dyn_abi:: FunctionExt ;
5
4
use alloy:: primitives:: { Address , ChainId , address} ;
6
5
use alloy:: providers:: RootProvider ;
7
6
use alloy:: {
8
7
providers:: Provider , rpc:: types:: eth:: TransactionRequest as AlloyTransactionRequest , sol,
9
8
sol_types:: SolCall ,
10
9
} ;
11
- use axum:: { extract:: State , http:: StatusCode , response:: Json } ;
10
+ use axum:: {
11
+ extract:: State ,
12
+ http:: StatusCode ,
13
+ response:: { IntoResponse , Json } ,
14
+ } ;
12
15
use engine_core:: {
13
16
chain:: { Chain , ChainService } ,
14
17
defs:: AddressDef ,
@@ -52,7 +55,7 @@ const MULTICALL3_DEFAULT_ADDRESS: Address = address!("0xcA11bde05977b36311670288
52
55
// ===== REQUEST/RESPONSE TYPES =====
53
56
54
57
/// Options for reading from smart contracts
55
- #[ derive( Debug , Clone , Serialize , Deserialize , JsonSchema ) ]
58
+ #[ derive( Debug , Clone , Serialize , Deserialize , JsonSchema , utoipa :: ToSchema ) ]
56
59
#[ serde( rename_all = "camelCase" ) ]
57
60
pub struct ReadOptions {
58
61
/// The blockchain network ID to read from
@@ -63,13 +66,15 @@ pub struct ReadOptions {
63
66
/// which is deployed on most networks
64
67
#[ serde( default = "default_multicall_address" ) ]
65
68
#[ schemars( with = "AddressDef" ) ]
69
+ #[ schema( value_type = AddressDef ) ]
66
70
pub multicall_address : Address ,
67
71
/// Optional address to use as the caller for view functions
68
72
///
69
73
/// This can be useful for functions that return different values
70
74
/// based on the caller's address or permissions
71
75
#[ serde( skip_serializing_if = "Option::is_none" ) ]
72
76
#[ schemars( with = "Option<AddressDef>" ) ]
77
+ #[ schema( value_type = Option <AddressDef >) ]
73
78
pub from : Option < Address > ,
74
79
}
75
80
@@ -78,7 +83,7 @@ fn default_multicall_address() -> Address {
78
83
}
79
84
80
85
/// Request to read from multiple smart contracts
81
- #[ derive( Debug , Clone , Serialize , Deserialize , JsonSchema ) ]
86
+ #[ derive( Debug , Clone , Serialize , Deserialize , JsonSchema , utoipa :: ToSchema ) ]
82
87
#[ serde( rename_all = "camelCase" ) ]
83
88
pub struct ReadRequest {
84
89
/// Configuration options for the read operation
@@ -94,18 +99,19 @@ pub struct ReadRequest {
94
99
/// Each result can either be successful (containing the function return value)
95
100
/// or failed (containing detailed error information). The `success` field
96
101
/// indicates which case applies.
97
- #[ derive( Debug , Clone , Serialize , Deserialize , JsonSchema ) ]
102
+ #[ derive( Debug , Clone , Serialize , Deserialize , JsonSchema , utoipa :: ToSchema ) ]
98
103
#[ serde( untagged) ]
99
104
pub enum ReadResultItem {
100
105
Success ( ReadResultSuccessItem ) ,
101
106
Failure ( ReadResultFailureItem ) ,
102
107
}
103
108
104
109
/// Successful result from a contract read operation
105
- #[ derive( Debug , Clone , Serialize , Deserialize , JsonSchema ) ]
110
+ #[ derive( Debug , Clone , Serialize , Deserialize , JsonSchema , utoipa :: ToSchema ) ]
106
111
pub struct ReadResultSuccessItem {
107
112
/// Always true for successful operations
108
113
#[ schemars( with = "bool" ) ]
114
+ #[ schema( value_type = bool ) ]
109
115
pub success : serde_bool:: True ,
110
116
/// The decoded return value from the contract function
111
117
///
@@ -115,10 +121,11 @@ pub struct ReadResultSuccessItem {
115
121
}
116
122
117
123
/// Failed result from a contract read operation
118
- #[ derive( Debug , Clone , Serialize , Deserialize , JsonSchema ) ]
124
+ #[ derive( Debug , Clone , Serialize , Deserialize , JsonSchema , utoipa :: ToSchema ) ]
119
125
pub struct ReadResultFailureItem {
120
126
/// Always false for failed operations
121
127
#[ schemars( with = "bool" ) ]
128
+ #[ schema( value_type = bool ) ]
122
129
pub success : serde_bool:: False ,
123
130
/// Detailed error information describing what went wrong
124
131
///
@@ -128,7 +135,7 @@ pub struct ReadResultFailureItem {
128
135
}
129
136
130
137
/// Collection of results from multiple contract read operations
131
- #[ derive( Debug , Clone , Serialize , Deserialize , JsonSchema ) ]
138
+ #[ derive( Debug , Clone , Serialize , Deserialize , JsonSchema , utoipa :: ToSchema ) ]
132
139
pub struct ReadResults {
133
140
/// Array of results, one for each input contract call
134
141
///
@@ -137,7 +144,7 @@ pub struct ReadResults {
137
144
}
138
145
139
146
/// Response from the contract read endpoint
140
- #[ derive( Debug , Clone , Serialize , Deserialize , JsonSchema ) ]
147
+ #[ derive( Debug , Clone , Serialize , Deserialize , JsonSchema , utoipa :: ToSchema ) ]
141
148
pub struct ReadResponse {
142
149
/// Container for all read operation results
143
150
pub result : ReadResults ,
@@ -179,12 +186,29 @@ impl ReadResultItem {
179
186
180
187
// ===== ROUTE HANDLER =====
181
188
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
+ ///
182
206
/// Read from multiple smart contracts using multicall
183
207
pub async fn read_contract (
184
208
State ( state) : State < EngineServerState > ,
185
209
OptionalRpcCredentialsExtractor ( rpc_credentials) : OptionalRpcCredentialsExtractor ,
186
210
EngineJson ( request) : EngineJson < ReadRequest > ,
187
- ) -> Result < impl IntoApiResponse , ApiEngineError > {
211
+ ) -> Result < impl IntoResponse , ApiEngineError > {
188
212
let auth: Option < ThirdwebAuth > = rpc_credentials. and_then ( |creds| match creds {
189
213
engine_core:: chain:: RpcCredentials :: Thirdweb ( auth) => Some ( auth) ,
190
214
} ) ;
@@ -355,44 +379,3 @@ fn process_multicall_result(
355
379
}
356
380
}
357
381
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