1#![allow(non_snake_case, clippy::large_enum_variant)]
2use candid::{self, CandidType, Deserialize, Principal};
3use ic_cdk::api::call::{call_with_payment128, CallResult as Result};
4
5pub type Regex = String;
6#[derive(CandidType, Deserialize, Debug, Clone)]
7pub enum LogFilter {
8 ShowAll,
9 HideAll,
10 ShowPattern(Regex),
11 HidePattern(Regex),
12}
13
14#[derive(CandidType, Deserialize, Debug, Clone)]
15pub struct RegexSubstitution {
16 pub pattern: Regex,
17 pub replacement: String,
18}
19
20#[derive(CandidType, Deserialize, Debug, Clone)]
21pub struct OverrideProvider {
22 pub overrideUrl: Option<RegexSubstitution>,
23}
24
25#[derive(CandidType, Deserialize, Debug, Clone)]
26pub struct InstallArgs {
27 pub logFilter: Option<LogFilter>,
28 pub demo: Option<bool>,
29 pub manageApiKeys: Option<Vec<Principal>>,
30 pub overrideProvider: Option<OverrideProvider>,
31 pub nodesInSubnet: Option<u32>,
32}
33
34#[derive(CandidType, Deserialize, Debug, Clone)]
35pub enum EthSepoliaService {
36 Alchemy,
37 BlockPi,
38 PublicNode,
39 Ankr,
40 Sepolia,
41}
42
43#[derive(CandidType, Deserialize, Debug, Clone)]
44pub enum L2MainnetService {
45 Alchemy,
46 Llama,
47 BlockPi,
48 PublicNode,
49 Ankr,
50}
51
52pub type ChainId = u64;
53#[derive(CandidType, Deserialize, Debug, Clone)]
54pub struct HttpHeader {
55 pub value: String,
56 pub name: String,
57}
58
59#[derive(CandidType, Deserialize, Debug, Clone)]
60pub struct RpcApi {
61 pub url: String,
62 pub headers: Option<Vec<HttpHeader>>,
63}
64
65#[derive(CandidType, Deserialize, Debug, Clone)]
66pub enum EthMainnetService {
67 Alchemy,
68 Llama,
69 BlockPi,
70 Cloudflare,
71 PublicNode,
72 Ankr,
73}
74
75#[derive(CandidType, Deserialize, Debug, Clone)]
76pub enum RpcServices {
77 EthSepolia(Option<Vec<EthSepoliaService>>),
78 BaseMainnet(Option<Vec<L2MainnetService>>),
79 Custom {
80 chainId: ChainId,
81 services: Vec<RpcApi>,
82 },
83 OptimismMainnet(Option<Vec<L2MainnetService>>),
84 ArbitrumOne(Option<Vec<L2MainnetService>>),
85 EthMainnet(Option<Vec<EthMainnetService>>),
86}
87
88#[derive(CandidType, Deserialize, Debug, Clone)]
89pub enum ConsensusStrategy {
90 Equality,
91 Threshold { min: u8, total: Option<u8> },
92}
93
94#[derive(CandidType, Deserialize, Debug, Clone)]
95pub struct RpcConfig {
96 pub responseConsensus: Option<ConsensusStrategy>,
97 pub responseSizeEstimate: Option<u64>,
98}
99
100#[derive(CandidType, Deserialize, Debug, Clone)]
101pub struct AccessListEntry {
102 pub storageKeys: Vec<String>,
103 pub address: String,
104}
105
106#[derive(CandidType, Deserialize, Debug, Clone, Default)]
107pub struct TransactionRequest {
108 pub to: Option<String>,
109 pub gas: Option<candid::Nat>,
110 pub maxFeePerGas: Option<candid::Nat>,
111 pub gasPrice: Option<candid::Nat>,
112 pub value: Option<candid::Nat>,
113 pub maxFeePerBlobGas: Option<candid::Nat>,
114 pub from: Option<String>,
115 pub r#type: Option<String>,
116 pub accessList: Option<Vec<AccessListEntry>>,
117 pub nonce: Option<candid::Nat>,
118 pub maxPriorityFeePerGas: Option<candid::Nat>,
119 pub blobs: Option<Vec<String>>,
120 pub input: Option<String>,
121 pub chainId: Option<candid::Nat>,
122 pub blobVersionedHashes: Option<Vec<String>>,
123}
124
125#[derive(CandidType, Deserialize, Debug, Clone)]
126pub enum BlockTag {
127 Earliest,
128 Safe,
129 Finalized,
130 Latest,
131 Number(candid::Nat),
132 Pending,
133}
134
135#[derive(CandidType, Deserialize, Debug, Clone)]
136pub struct CallArgs {
137 pub transaction: TransactionRequest,
138 pub block: Option<BlockTag>,
139}
140
141#[derive(CandidType, Deserialize, Debug, Clone)]
142pub struct JsonRpcError {
143 pub code: i64,
144 pub message: String,
145}
146
147#[derive(CandidType, Deserialize, Debug, Clone)]
148pub enum ProviderError {
149 TooFewCycles {
150 expected: candid::Nat,
151 received: candid::Nat,
152 },
153 InvalidRpcConfig(String),
154 MissingRequiredProvider,
155 ProviderNotFound,
156 NoPermission,
157}
158
159#[derive(CandidType, Deserialize, Debug, Clone)]
160pub enum ValidationError {
161 Custom(String),
162 InvalidHex(String),
163}
164
165#[derive(CandidType, Deserialize, Debug, Clone)]
166pub enum RejectionCode {
167 NoError,
168 CanisterError,
169 SysTransient,
170 DestinationInvalid,
171 Unknown,
172 SysFatal,
173 CanisterReject,
174}
175
176#[derive(CandidType, Deserialize, Debug, Clone)]
177pub enum HttpOutcallError {
178 IcError {
179 code: RejectionCode,
180 message: String,
181 },
182 InvalidHttpJsonRpcResponse {
183 status: u16,
184 body: String,
185 parsingError: Option<String>,
186 },
187}
188
189#[derive(CandidType, Deserialize, Debug, Clone)]
190pub enum RpcError {
191 JsonRpcError(JsonRpcError),
192 ProviderError(ProviderError),
193 ValidationError(ValidationError),
194 HttpOutcallError(HttpOutcallError),
195}
196
197#[derive(CandidType, Deserialize, Debug, Clone)]
198pub enum CallResult {
199 Ok(String),
200 Err(RpcError),
201}
202
203pub type ProviderId = u64;
204#[derive(CandidType, Deserialize, Debug, Clone)]
205pub enum RpcService {
206 EthSepolia(EthSepoliaService),
207 BaseMainnet(L2MainnetService),
208 Custom(RpcApi),
209 OptimismMainnet(L2MainnetService),
210 ArbitrumOne(L2MainnetService),
211 EthMainnet(EthMainnetService),
212 Provider(ProviderId),
213}
214
215#[derive(CandidType, Deserialize, Debug, Clone)]
216pub enum MultiCallResult {
217 Consistent(CallResult),
218 Inconsistent(Vec<(RpcService, CallResult)>),
219}
220
221#[derive(CandidType, Deserialize, Debug, Clone)]
222pub struct FeeHistoryArgs {
223 pub blockCount: candid::Nat,
224 pub newestBlock: BlockTag,
225 pub rewardPercentiles: Option<serde_bytes::ByteBuf>,
226}
227
228#[derive(CandidType, Deserialize, Debug, Clone)]
229pub struct FeeHistory {
230 pub reward: Vec<Vec<candid::Nat>>,
231 pub gasUsedRatio: Vec<f64>,
232 pub oldestBlock: candid::Nat,
233 pub baseFeePerGas: Vec<candid::Nat>,
234}
235
236#[derive(CandidType, Deserialize, Debug, Clone)]
237pub enum FeeHistoryResult {
238 Ok(FeeHistory),
239 Err(RpcError),
240}
241
242#[derive(CandidType, Deserialize, Debug, Clone)]
243pub enum MultiFeeHistoryResult {
244 Consistent(FeeHistoryResult),
245 Inconsistent(Vec<(RpcService, FeeHistoryResult)>),
246}
247
248#[derive(CandidType, Deserialize, Debug, Clone)]
249pub struct Block {
250 pub miner: String,
251 pub totalDifficulty: Option<candid::Nat>,
252 pub receiptsRoot: String,
253 pub stateRoot: String,
254 pub hash: String,
255 pub difficulty: Option<candid::Nat>,
256 pub size: candid::Nat,
257 pub uncles: Vec<String>,
258 pub baseFeePerGas: Option<candid::Nat>,
259 pub extraData: String,
260 pub transactionsRoot: Option<String>,
261 pub sha3Uncles: String,
262 pub nonce: candid::Nat,
263 pub number: candid::Nat,
264 pub timestamp: candid::Nat,
265 pub transactions: Vec<String>,
266 pub gasLimit: candid::Nat,
267 pub logsBloom: String,
268 pub parentHash: String,
269 pub gasUsed: candid::Nat,
270 pub mixHash: String,
271}
272
273#[derive(CandidType, Deserialize, Debug, Clone)]
274pub enum GetBlockByNumberResult {
275 Ok(Block),
276 Err(RpcError),
277}
278
279#[derive(CandidType, Deserialize, Debug, Clone)]
280pub enum MultiGetBlockByNumberResult {
281 Consistent(GetBlockByNumberResult),
282 Inconsistent(Vec<(RpcService, GetBlockByNumberResult)>),
283}
284
285pub type Topic = Vec<String>;
286#[derive(CandidType, Deserialize, Debug, Clone)]
287pub struct GetLogsArgs {
288 pub fromBlock: Option<BlockTag>,
289 pub toBlock: Option<BlockTag>,
290 pub addresses: Vec<String>,
291 pub topics: Option<Vec<Topic>>,
292}
293
294#[derive(CandidType, Deserialize, Debug, Clone, PartialEq)]
295pub struct LogEntry {
296 pub transactionHash: Option<String>,
297 pub blockNumber: Option<candid::Nat>,
298 pub data: String,
299 pub blockHash: Option<String>,
300 pub transactionIndex: Option<candid::Nat>,
301 pub topics: Vec<String>,
302 pub address: String,
303 pub logIndex: Option<candid::Nat>,
304 pub removed: bool,
305}
306
307#[derive(CandidType, Deserialize, Debug, Clone)]
308pub enum GetLogsResult {
309 Ok(Vec<LogEntry>),
310 Err(RpcError),
311}
312
313#[derive(CandidType, Deserialize, Debug, Clone)]
314pub enum MultiGetLogsResult {
315 Consistent(GetLogsResult),
316 Inconsistent(Vec<(RpcService, GetLogsResult)>),
317}
318
319#[derive(CandidType, Deserialize, Debug, Clone)]
320pub struct GetTransactionCountArgs {
321 pub address: String,
322 pub block: BlockTag,
323}
324
325#[derive(CandidType, Deserialize, Debug, Clone)]
326pub enum GetTransactionCountResult {
327 Ok(candid::Nat),
328 Err(RpcError),
329}
330
331#[derive(CandidType, Deserialize, Debug, Clone)]
332pub enum MultiGetTransactionCountResult {
333 Consistent(GetTransactionCountResult),
334 Inconsistent(Vec<(RpcService, GetTransactionCountResult)>),
335}
336
337#[derive(CandidType, Deserialize, Debug, Clone)]
338pub struct TransactionReceipt {
339 pub to: Option<String>,
340 pub status: Option<candid::Nat>,
341 pub transactionHash: String,
342 pub blockNumber: candid::Nat,
343 pub from: String,
344 pub logs: Vec<LogEntry>,
345 pub blockHash: String,
346 pub r#type: String,
347 pub transactionIndex: candid::Nat,
348 pub effectiveGasPrice: candid::Nat,
349 pub logsBloom: String,
350 pub contractAddress: Option<String>,
351 pub gasUsed: candid::Nat,
352}
353
354#[derive(CandidType, Deserialize, Debug, Clone)]
355pub enum GetTransactionReceiptResult {
356 Ok(Option<TransactionReceipt>),
357 Err(RpcError),
358}
359
360#[derive(CandidType, Deserialize, Debug, Clone)]
361pub enum MultiGetTransactionReceiptResult {
362 Consistent(GetTransactionReceiptResult),
363 Inconsistent(Vec<(RpcService, GetTransactionReceiptResult)>),
364}
365
366#[derive(CandidType, Deserialize, Debug, Clone)]
367pub enum SendRawTransactionStatus {
368 Ok(Option<String>),
369 NonceTooLow,
370 NonceTooHigh,
371 InsufficientFunds,
372}
373
374#[derive(CandidType, Deserialize, Debug, Clone)]
375pub enum SendRawTransactionResult {
376 Ok(SendRawTransactionStatus),
377 Err(RpcError),
378}
379
380#[derive(CandidType, Deserialize, Debug, Clone)]
381pub enum MultiSendRawTransactionResult {
382 Consistent(SendRawTransactionResult),
383 Inconsistent(Vec<(RpcService, SendRawTransactionResult)>),
384}
385
386#[derive(CandidType, Deserialize, Debug, Clone)]
387pub struct Metrics {
388 pub responses: Vec<((String, String, String), u64)>,
389 pub inconsistentResponses: Vec<((String, String), u64)>,
390 pub cyclesCharged: Vec<((String, String), candid::Nat)>,
391 pub requests: Vec<((String, String), u64)>,
392 pub errHttpOutcall: Vec<((String, String, RejectionCode), u64)>,
393}
394
395#[derive(CandidType, Deserialize, Debug, Clone)]
396pub enum RpcAuth {
397 BearerToken { url: String },
398 UrlParameter { urlPattern: String },
399}
400
401#[derive(CandidType, Deserialize, Debug, Clone)]
402pub enum RpcAccess {
403 Authenticated {
404 publicUrl: Option<String>,
405 auth: RpcAuth,
406 },
407 Unauthenticated {
408 publicUrl: String,
409 },
410}
411
412#[derive(CandidType, Deserialize, Debug, Clone)]
413pub struct Provider {
414 pub access: RpcAccess,
415 pub alias: Option<RpcService>,
416 pub chainId: ChainId,
417 pub providerId: ProviderId,
418}
419
420#[derive(CandidType, Deserialize, Debug, Clone)]
421pub enum RequestResult {
422 Ok(String),
423 Err(RpcError),
424}
425
426#[derive(CandidType, Deserialize, Debug, Clone)]
427pub enum RequestCostResult {
428 Ok(candid::Nat),
429 Err(RpcError),
430}
431
432#[derive(Debug, Clone)]
433pub struct EvmRpcCanister(pub Principal);
434impl EvmRpcCanister {
435 pub async fn eth_call(
436 &self,
437 arg0: RpcServices,
438 arg1: Option<RpcConfig>,
439 arg2: CallArgs,
440 cycles: u128,
441 ) -> Result<(MultiCallResult,)> {
442 call_with_payment128(self.0, "eth_call", (arg0, arg1, arg2), cycles).await
443 }
444 pub async fn eth_fee_history(
445 &self,
446 arg0: RpcServices,
447 arg1: Option<RpcConfig>,
448 arg2: FeeHistoryArgs,
449 cycles: u128,
450 ) -> Result<(MultiFeeHistoryResult,)> {
451 call_with_payment128(self.0, "eth_feeHistory", (arg0, arg1, arg2), cycles).await
452 }
453 pub async fn eth_get_block_by_number(
454 &self,
455 arg0: RpcServices,
456 arg1: Option<RpcConfig>,
457 arg2: BlockTag,
458 cycles: u128,
459 ) -> Result<(MultiGetBlockByNumberResult,)> {
460 call_with_payment128(self.0, "eth_getBlockByNumber", (arg0, arg1, arg2), cycles).await
461 }
462 pub async fn eth_get_logs(
463 &self,
464 arg0: RpcServices,
465 arg1: Option<RpcConfig>,
466 arg2: GetLogsArgs,
467 cycles: u128,
468 ) -> Result<(MultiGetLogsResult,)> {
469 call_with_payment128(self.0, "eth_getLogs", (arg0, arg1, arg2), cycles).await
470 }
471 pub async fn eth_get_transaction_count(
472 &self,
473 arg0: RpcServices,
474 arg1: Option<RpcConfig>,
475 arg2: GetTransactionCountArgs,
476 cycles: u128,
477 ) -> Result<(MultiGetTransactionCountResult,)> {
478 call_with_payment128(
479 self.0,
480 "eth_getTransactionCount",
481 (arg0, arg1, arg2),
482 cycles,
483 )
484 .await
485 }
486 pub async fn eth_get_transaction_receipt(
487 &self,
488 arg0: RpcServices,
489 arg1: Option<RpcConfig>,
490 arg2: String,
491 cycles: u128,
492 ) -> Result<(MultiGetTransactionReceiptResult,)> {
493 call_with_payment128(
494 self.0,
495 "eth_getTransactionReceipt",
496 (arg0, arg1, arg2),
497 cycles,
498 )
499 .await
500 }
501 pub async fn eth_send_raw_transaction(
502 &self,
503 arg0: RpcServices,
504 arg1: Option<RpcConfig>,
505 arg2: String,
506 cycles: u128,
507 ) -> Result<(MultiSendRawTransactionResult,)> {
508 call_with_payment128(self.0, "eth_sendRawTransaction", (arg0, arg1, arg2), cycles).await
509 }
510 pub async fn get_metrics(&self) -> Result<(Metrics,)> {
511 ic_cdk::call(self.0, "getMetrics", ()).await
512 }
513 pub async fn get_nodes_in_subnet(&self) -> Result<(u32,)> {
514 ic_cdk::call(self.0, "getNodesInSubnet", ()).await
515 }
516 pub async fn get_providers(&self) -> Result<(Vec<Provider>,)> {
517 ic_cdk::call(self.0, "getProviders", ()).await
518 }
519 pub async fn get_service_provider_map(&self) -> Result<(Vec<(RpcService, ProviderId)>,)> {
520 ic_cdk::call(self.0, "getServiceProviderMap", ()).await
521 }
522 pub async fn request(
523 &self,
524 arg0: RpcService,
525 arg1: String,
526 arg2: u64,
527 cycles: u128,
528 ) -> Result<(RequestResult,)> {
529 call_with_payment128(self.0, "request", (arg0, arg1, arg2), cycles).await
530 }
531 pub async fn request_cost(
532 &self,
533 arg0: RpcService,
534 arg1: String,
535 arg2: u64,
536 ) -> Result<(RequestCostResult,)> {
537 ic_cdk::call(self.0, "requestCost", (arg0, arg1, arg2)).await
538 }
539 pub async fn update_api_keys(&self, arg0: Vec<(ProviderId, Option<String>)>) -> Result<()> {
540 ic_cdk::call(self.0, "updateApiKeys", (arg0,)).await
541 }
542}
543
544pub const CANISTER_ID: Principal =
545 Principal::from_slice(b"\x00\x00\x00\x00\x02\x30\x00\xCC\x01\x01"); pub const EVM_RPC: EvmRpcCanister = EvmRpcCanister(CANISTER_ID);
547
548#[test]
549fn test_candid_interface() {
551 fn source_to_str(source: &candid_parser::utils::CandidSource) -> String {
552 match source {
553 candid_parser::utils::CandidSource::File(f) => {
554 std::fs::read_to_string(f).unwrap_or_else(|_| "".to_string())
555 }
556 candid_parser::utils::CandidSource::Text(t) => t.to_string(),
557 }
558 }
559
560 fn check_service_compatible(
561 new_name: &str,
562 new: candid_parser::utils::CandidSource,
563 old_name: &str,
564 old: candid_parser::utils::CandidSource,
565 ) {
566 let new_str = source_to_str(&new);
567 let old_str = source_to_str(&old);
568 match candid_parser::utils::service_compatible(new, old) {
569 Ok(_) => {}
570 Err(e) => {
571 eprintln!(
572 "{} is not compatible with {}!\n\n\
573 {}:\n\
574 {}\n\n\
575 {}:\n\
576 {}\n",
577 new_name, old_name, new_name, new_str, old_name, old_str
578 );
579 panic!("{:?}", e);
580 }
581 }
582 }
583
584 let client = reqwest::blocking::Client::new();
586 let new_interface = client
587 .get("https://github.com/dfinity/evm-rpc-canister/releases/latest/download/evm_rpc.did")
588 .send()
589 .unwrap()
590 .text()
591 .unwrap();
592
593 let old_interface =
595 std::path::PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").unwrap()).join("evm_rpc.did");
596
597 check_service_compatible(
598 "actual ledger candid interface",
599 candid_parser::utils::CandidSource::Text(&new_interface),
600 "declared candid interface in evm_rpc.did file",
601 candid_parser::utils::CandidSource::File(old_interface.as_path()),
602 );
603}