evm_rpc_canister_types/
lib.rs

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"); // 7hfb6-caaaa-aaaar-qadga-cai
546pub const EVM_RPC: EvmRpcCanister = EvmRpcCanister(CANISTER_ID);
547
548#[test]
549// #[ignore = "existing candid mismatch"]
550fn 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    // fetch public interface from github
585    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    // check the public interface against the actual one
594    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}