├── .gitignore ├── examples ├── README.md ├── simple_request.rs ├── submit_transaction.rs ├── ipfs.rs ├── fetch_all.rs └── all_requests.rs ├── default.nix ├── src ├── api │ ├── endpoints │ │ ├── mod.rs │ │ ├── mempool.rs │ │ ├── ledger.rs │ │ ├── network.rs │ │ ├── utils.rs │ │ ├── health.rs │ │ ├── metrics.rs │ │ ├── scripts.rs │ │ ├── nutlink.rs │ │ ├── metadata.rs │ │ ├── blocks.rs │ │ ├── assets.rs │ │ ├── addresses.rs │ │ ├── pools.rs │ │ ├── epochs.rs │ │ ├── accounts.rs │ │ └── transactions.rs │ └── mod.rs ├── types.rs ├── pagination.rs ├── lib.rs ├── utils.rs ├── settings.rs ├── error.rs ├── request.rs ├── url.rs └── ipfs.rs ├── rustfmt.toml ├── .vscode └── settings.json ├── nix ├── sources.json └── sources.nix ├── Cargo.toml ├── .github └── workflows │ └── ci.yaml ├── README.md ├── CHANGELOG.md ├── ENDPOINTS.md └── LICENSE /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | blockfrost.toml 3 | .blockfrost.toml 4 | -------------------------------------------------------------------------------- /examples/README.md: -------------------------------------------------------------------------------- 1 | # Examples 2 | 3 | Before checking the examples be sure that you are using a compatible version of 4 | this crate, otherwise, the examples may not work on your project. 5 | -------------------------------------------------------------------------------- /default.nix: -------------------------------------------------------------------------------- 1 | let 2 | pkgs = import { }; 3 | sources = import ./nix/sources.nix; 4 | naersk = pkgs.callPackage sources.naersk { }; 5 | in 6 | naersk.buildPackage { 7 | src = ./.; 8 | buildInputs = [ pkgs.pkg-config pkgs.openssl ]; 9 | } 10 | -------------------------------------------------------------------------------- /src/api/endpoints/mod.rs: -------------------------------------------------------------------------------- 1 | mod accounts; 2 | mod addresses; 3 | mod assets; 4 | mod blocks; 5 | mod epochs; 6 | mod health; 7 | mod ledger; 8 | mod mempool; 9 | mod metadata; 10 | mod metrics; 11 | mod network; 12 | mod nutlink; 13 | mod pools; 14 | mod scripts; 15 | mod transactions; 16 | mod utils; 17 | -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | # Sets the maximum line length. Code will be wrapped beyond this length. 2 | max_width = 100 3 | 4 | # Use spaces for indentation rather than tabs. 5 | hard_tabs = false 6 | 7 | # Configure the visual indentation of arguments and parameters. 8 | fn_params_layout = "Compressed" 9 | 10 | # Controls whether to put `else` on a new line. 11 | newline_style = "Auto" 12 | -------------------------------------------------------------------------------- /examples/simple_request.rs: -------------------------------------------------------------------------------- 1 | use blockfrost::{BlockfrostAPI, BlockfrostResult}; 2 | 3 | fn build_api() -> BlockfrostResult { 4 | let api = BlockfrostAPI::new( 5 | "mainnetxvMK4xOpp5mHJgihi055KDLU64JJv2be", 6 | Default::default(), 7 | ); 8 | 9 | Ok(api) 10 | } 11 | 12 | #[tokio::main] 13 | async fn main() -> BlockfrostResult<()> { 14 | let api = build_api()?; 15 | let genesis = api.genesis().await?; 16 | 17 | println!("{genesis:#?}"); 18 | Ok(()) 19 | } 20 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "cSpell.words": [ 3 | "ADAUSD", 4 | "Adress", 5 | "Bech", 6 | "blockfrost", 7 | "CARDANO", 8 | "cbor", 9 | "Clippy", 10 | "decentralisation", 11 | "delegators", 12 | "deregistration", 13 | "IFPS", 14 | "ipfs", 15 | "Lovelaces", 16 | "mirs", 17 | "naersk", 18 | "nixpkgs", 19 | "Nutlink", 20 | "openapi", 21 | "preprodnet", 22 | "previewnet", 23 | "println", 24 | "redemeers", 25 | "reqwest", 26 | "retriving", 27 | "rustfmt", 28 | "serialised", 29 | "stakenuts", 30 | "thiserror", 31 | "timelock", 32 | "UTXO", 33 | "utxos", 34 | "xpub" 35 | ] 36 | } 37 | -------------------------------------------------------------------------------- /examples/submit_transaction.rs: -------------------------------------------------------------------------------- 1 | use blockfrost::{BlockFrostSettings, BlockfrostAPI, BlockfrostResult}; 2 | 3 | fn build_api() -> BlockfrostResult { 4 | let api = BlockfrostAPI::new( 5 | "mainnetxvMK4xOpp5mHJgihi055KDLU64JJv2be", 6 | BlockFrostSettings::new(), 7 | ); 8 | Ok(api) 9 | } 10 | 11 | #[tokio::main] 12 | async fn main() -> BlockfrostResult<()> { 13 | let api = build_api()?; 14 | 15 | // Should contain the correct cbor contents 16 | let transaction_data = vec![0; 1024]; // Just an example (will fail) 17 | let transaction_hash = api.transactions_submit(transaction_data).await?; 18 | 19 | // At this point, you should probably save the transaction_hash 20 | println!("{transaction_hash}"); 21 | 22 | Ok(()) 23 | } 24 | -------------------------------------------------------------------------------- /src/api/endpoints/mempool.rs: -------------------------------------------------------------------------------- 1 | use crate::*; 2 | use blockfrost_openapi::models::{ 3 | mempool_content_inner::MempoolContentInner, mempool_tx_content::MempoolTxContent, 4 | }; 5 | 6 | impl BlockfrostAPI { 7 | pub async fn mempool( 8 | &self, pagination: Pagination, 9 | ) -> BlockfrostResult> { 10 | self.call_paged_endpoint("/mempool", pagination).await 11 | } 12 | 13 | pub async fn mempool_hash(&self, hash: &str) -> BlockfrostResult { 14 | self.call_endpoint(format!("/mempool/{hash}").as_str()) 15 | .await 16 | } 17 | pub async fn mempool_addresses_address( 18 | &self, address: &str, pagination: Pagination, 19 | ) -> BlockfrostResult> { 20 | self.call_paged_endpoint(format!("/mempool/addresses/{address}").as_str(), pagination) 21 | .await 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/api/endpoints/ledger.rs: -------------------------------------------------------------------------------- 1 | use crate::*; 2 | use blockfrost_openapi::models::genesis_content::GenesisContent; 3 | 4 | impl BlockfrostAPI { 5 | pub async fn genesis(&self) -> BlockfrostResult { 6 | self.call_endpoint("/genesis").await 7 | } 8 | } 9 | 10 | #[cfg(test)] 11 | mod tests { 12 | use super::*; 13 | use serde_json::json; 14 | 15 | #[test] 16 | fn test_genesis() { 17 | let json_value = json!({ 18 | "active_slots_coefficient": 0.05, 19 | "update_quorum": 5, 20 | "max_lovelace_supply": "45000000000000000", 21 | "network_magic": 764824073, 22 | "epoch_length": 432000, 23 | "system_start": 1506203091, 24 | "slots_per_kes_period": 129600, 25 | "slot_length": 1, 26 | "max_kes_evolutions": 62, 27 | "security_param": 2160 28 | }); 29 | 30 | serde_json::from_value::(json_value).unwrap(); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /nix/sources.json: -------------------------------------------------------------------------------- 1 | { 2 | "naersk": { 3 | "branch": "master", 4 | "description": "Build rust crates in Nix. No configuration, no code generation, no IFD. Sandbox friendly. [maintainer: @Patryk27]", 5 | "homepage": "", 6 | "owner": "nix-community", 7 | "repo": "naersk", 8 | "rev": "6944160c19cb591eb85bbf9b2f2768a935623ed3", 9 | "sha256": "01i282zrx651mpvnmlgk4fgwg56nbr1yljpzcj5irqxf18cqx3gn", 10 | "type": "tarball", 11 | "url": "https://github.com/nix-community/naersk/archive/6944160c19cb591eb85bbf9b2f2768a935623ed3.tar.gz", 12 | "url_template": "https://github.com///archive/.tar.gz" 13 | }, 14 | "nixpkgs": { 15 | "branch": "release-21.05", 16 | "description": "Nix Packages collection", 17 | "homepage": "", 18 | "owner": "NixOS", 19 | "repo": "nixpkgs", 20 | "rev": "5f244caea76105b63d826911b2a1563d33ff1cdc", 21 | "sha256": "1xlgynfw9svy7nvh9nkxsxdzncv9hg99gbvbwv3gmrhmzc3sar75", 22 | "type": "tarball", 23 | "url": "https://github.com/NixOS/nixpkgs/archive/5f244caea76105b63d826911b2a1563d33ff1cdc.tar.gz", 24 | "url_template": "https://github.com///archive/.tar.gz" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/api/endpoints/network.rs: -------------------------------------------------------------------------------- 1 | use crate::*; 2 | use blockfrost_openapi::models::{network::Network, network_eras_inner::NetworkErasInner}; 3 | 4 | impl BlockfrostAPI { 5 | pub async fn network(&self) -> BlockfrostResult { 6 | self.call_endpoint("/network").await 7 | } 8 | 9 | pub async fn network_eras(&self) -> BlockfrostResult> { 10 | self.call_endpoint("/network/eras").await 11 | } 12 | } 13 | 14 | #[cfg(test)] 15 | mod tests { 16 | use blockfrost_openapi::models::network::Network; 17 | use serde_json::json; 18 | 19 | #[test] 20 | fn test_network() { 21 | let json_value = json!({ 22 | "supply": { 23 | "max": "45000000000000000", 24 | "total": "32890715183299160", 25 | "circulating": "32412601976210393", 26 | "locked": "125006953355", 27 | "treasury": "98635632000000", 28 | "reserves": "46635632000000" 29 | }, 30 | "stake": { 31 | "live": "23204950463991654", 32 | "active": "22210233523456321" 33 | } 34 | }); 35 | 36 | serde_json::from_value::(json_value).unwrap(); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/types.rs: -------------------------------------------------------------------------------- 1 | // Use this module as an interface to export all types declared inside of endpoints/ 2 | // 3 | // These are not used in here, just exporting 4 | pub use crate::ipfs::{IpfsAdd, IpfsPinList, IpfsPinState, IpfsPinUpdate}; 5 | 6 | /// Enum for any possible JSON value. 7 | /// 8 | /// Declared as the following: 9 | /// 10 | /// ```no_run 11 | /// # use f64 as Number; 12 | /// # use std::collections::HashMap as Map; 13 | /// pub enum Value { 14 | /// Null, 15 | /// Bool(bool), 16 | /// Number(Number), 17 | /// String(String), 18 | /// Array(Vec), 19 | /// Object(Map), 20 | /// } 21 | /// ``` 22 | pub type JsonValue = serde_json::Value; 23 | 24 | /// Integer used in other types. 25 | /// 26 | /// A signed 128-bit integer can store up to 2^127, or 340282366920938463463374607431768211456. 27 | /// 28 | /// This integer can store a number with 38 digits, the max supply of ADA is 45 billion (11 digits). 29 | pub type Integer = i128; 30 | 31 | /// Float used in other types. 32 | pub type Float = f64; 33 | 34 | /// JSON Map (or JSON object) made of key-value pairs. 35 | /// 36 | /// Used in types: 37 | /// [`EpochParameters`] 38 | /// [`AssetDetails`] 39 | pub type JsonMap = serde_json::Map; 40 | -------------------------------------------------------------------------------- /src/pagination.rs: -------------------------------------------------------------------------------- 1 | use crate::{DEFAULT_ORDER, DEFAULT_PAGINATION_PAGE_COUNT, DEFAULT_PAGINATION_PAGE_ITEMS_COUNT}; 2 | 3 | #[derive(Clone, Copy)] 4 | pub struct Pagination { 5 | pub fetch_all: bool, 6 | pub count: usize, 7 | pub page: usize, 8 | pub order: Order, 9 | } 10 | 11 | impl Default for Pagination { 12 | fn default() -> Self { 13 | Pagination { 14 | fetch_all: false, 15 | count: DEFAULT_PAGINATION_PAGE_ITEMS_COUNT, 16 | page: DEFAULT_PAGINATION_PAGE_COUNT, 17 | order: DEFAULT_ORDER, 18 | } 19 | } 20 | } 21 | 22 | impl Pagination { 23 | pub fn new(order: Order, page: usize, count: usize) -> Self { 24 | Pagination { 25 | fetch_all: false, 26 | order, 27 | page, 28 | count, 29 | } 30 | } 31 | 32 | pub fn all() -> Self { 33 | Pagination { 34 | fetch_all: true, 35 | ..Default::default() 36 | } 37 | } 38 | 39 | pub fn order_to_string(&self) -> String { 40 | match self.order { 41 | Order::Asc => "asc".to_string(), 42 | Order::Desc => "desc".to_string(), 43 | } 44 | } 45 | } 46 | 47 | #[derive(Clone, Copy)] 48 | pub enum Order { 49 | Asc, 50 | Desc, 51 | } 52 | -------------------------------------------------------------------------------- /examples/ipfs.rs: -------------------------------------------------------------------------------- 1 | use blockfrost::{BlockfrostIPFS, BlockfrostResult, IpfsSettings}; 2 | use std::fs; 3 | 4 | fn build_ipfs() -> BlockfrostResult { 5 | let api = BlockfrostIPFS::new( 6 | "mainnetxvMK4xOpp5mHJgihi055KDLU64JJv2be", 7 | IpfsSettings::new(), 8 | ); 9 | Ok(api) 10 | } 11 | 12 | #[tokio::main] 13 | async fn main() -> BlockfrostResult<()> { 14 | let ipfs = build_ipfs()?; 15 | let file = fs::read_to_string("/etc/fstab")?.into_bytes(); 16 | 17 | // Add file 18 | let added_file = ipfs.add(file).await?; 19 | println!("{added_file:#?}"); 20 | let hash = &added_file.ipfs_hash; 21 | 22 | // Pin it 23 | let pinned_file = ipfs.pin_add(hash).await?; 24 | println!("{pinned_file:#?}"); 25 | let hash = &pinned_file.ipfs_hash; 26 | 27 | // List pins 28 | let pin_list = ipfs.pin_list().await?; 29 | println!("{pin_list:#?}"); 30 | 31 | // List pin by ipfs_hash (id) 32 | let pin_list_by_id = ipfs.pin_list_by_id(hash).await?; 33 | println!("{pin_list_by_id:#?}"); 34 | 35 | // Query contents 36 | let gateway = ipfs.gateway(hash).await?; 37 | let string = String::from_utf8(gateway).unwrap(); 38 | println!("content: '{string:#?}'"); 39 | 40 | // Remove pin 41 | let pin_removed = ipfs.pin_remove(hash).await?; 42 | println!("{pin_removed:#?}"); 43 | 44 | Ok(()) 45 | } 46 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "blockfrost" 3 | version = "1.1.0" 4 | edition = "2021" 5 | keywords = ["blockfrost", "blockchain", "cardano", "ipfs", "nft"] 6 | categories = ["api-bindings", "asynchronous", "cryptography::cryptocurrencies"] 7 | description = "A Rust SDK for Blockfrost.io API" 8 | license = "Apache-2.0" 9 | repository = "https://github.com/blockfrost/blockfrost-rust" 10 | homepage = "https://blockfrost.io" 11 | 12 | [dependencies] 13 | blockfrost-openapi = "0.1.75" 14 | futures = "0.3.31" 15 | futures-timer = "3.0.3" 16 | reqwest = { version = "0.12.9", default-features = false, features = [ 17 | "http2", 18 | "charset", 19 | "macos-system-configuration", 20 | "multipart", 21 | "json", 22 | ] } 23 | serde = { version = "1.0.130", features = ["derive"] } 24 | serde_json = "1.0.128" 25 | url = "2.5.2" 26 | thiserror = "2.0.3" 27 | 28 | [dev-dependencies] 29 | httpmock = "0.7.0" 30 | rstest = "0.25.0" 31 | tokio = { version = "1.12.0", features = ["macros", "rt-multi-thread"] } 32 | 33 | [features] 34 | default = ["default-tls"] 35 | default-tls = ["reqwest/default-tls"] 36 | rustls-tls = ["reqwest/rustls-tls"] 37 | rustls-tls-manual-roots = ["reqwest/rustls-tls-manual-roots"] 38 | rustls-tls-webpki-roots = ["reqwest/rustls-tls-webpki-roots"] 39 | rustls-tls-native-roots = ["reqwest/rustls-tls-native-roots"] 40 | 41 | [lints.clippy] 42 | uninlined_format_args = "deny" 43 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #![doc( 2 | html_logo_url = "https://raw.githubusercontent.com/blockfrost/blockfrost-rust/master/docs-logo.svg" 3 | )] 4 | #![doc = include_str!("../README.md")] 5 | mod api; 6 | mod ipfs; 7 | mod pagination; 8 | mod request; 9 | mod settings; 10 | mod url; 11 | mod utils; 12 | 13 | pub mod error; 14 | pub mod types; 15 | pub use api::*; 16 | pub use error::*; 17 | pub use ipfs::BlockfrostIPFS; 18 | pub use pagination::Order; 19 | pub use pagination::Pagination; 20 | pub use settings::*; 21 | pub use types::*; 22 | 23 | pub const CARDANO_MAINNET_URL: &str = "https://cardano-mainnet.blockfrost.io/api/v0"; 24 | pub const CARDANO_PREPROD_URL: &str = "https://cardano-preprod.blockfrost.io/api/v0"; 25 | pub const CARDANO_PREVIEW_URL: &str = "https://cardano-preview.blockfrost.io/api/v0"; 26 | pub const CARDANO_TESTNET_URL: &str = "https://cardano-testnet.blockfrost.io/api/v0"; 27 | pub const IPFS_URL: &str = "https://ipfs.blockfrost.io/api/v0"; 28 | 29 | pub const DEFAULT_API_VERSION: u32 = 0; 30 | pub const DEFAULT_BATCH_SIZE: u32 = 10; 31 | pub const DEFAULT_ORDER: Order = Order::Asc; 32 | pub const DEFAULT_PAGINATION_PAGE_COUNT: usize = 1; 33 | pub const DEFAULT_PAGINATION_PAGE_ITEMS_COUNT: usize = 100; 34 | 35 | /// SDK version being used. 36 | /// 37 | /// This is sent on every request as a header. 38 | pub const USER_AGENT: &str = concat!("blockfrost-rust/", env!("CARGO_PKG_VERSION")); 39 | -------------------------------------------------------------------------------- /src/api/endpoints/utils.rs: -------------------------------------------------------------------------------- 1 | use crate::{request::send_request, url::Url, *}; 2 | use blockfrost_openapi::models::utils_addresses_xpub::UtilsAddressesXpub; 3 | use reqwest::{header::HeaderValue, Body, Method}; 4 | use serde_json::{from_str as json_from, Value}; 5 | 6 | impl BlockfrostAPI { 7 | pub async fn derive_address( 8 | &self, xpub: &str, role: &str, index: &str, 9 | ) -> BlockfrostResult { 10 | self.call_endpoint(&format!("/utils/addresses/xpub/{xpub}/{role}/{index}",)) 11 | .await 12 | } 13 | 14 | pub async fn utils_tx_evaluate(&self, transaction_data: Vec) -> BlockfrostResult { 15 | let body = Body::from(transaction_data); 16 | let url = Url::from_endpoint(self.base_url.as_str(), "/utils/txs/evaluate")?; 17 | 18 | let request = self 19 | .client 20 | .request(Method::POST, &url) 21 | .header("Content-Type", HeaderValue::from_static("application/cbor")) 22 | .body(body); 23 | 24 | let (status, text) = send_request(request, self.settings.retry_settings) 25 | .await 26 | .map_err(|reason| BlockfrostError::Reqwest { 27 | url: url.clone(), 28 | reason, 29 | })?; 30 | 31 | if !status.is_success() { 32 | return Err(process_error_response(&text, status, &url)); 33 | } 34 | 35 | json_from(&text).map_err(|reason| json_error(url, text, reason)) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /examples/fetch_all.rs: -------------------------------------------------------------------------------- 1 | use blockfrost::{BlockFrostSettings, BlockfrostAPI, BlockfrostResult, Pagination}; 2 | 3 | fn build_api_preprod() -> BlockfrostResult { 4 | let settings = BlockFrostSettings::new(); 5 | let api = BlockfrostAPI::new("preprododflHjPhpRp4NzRFL1m9zzd6ZJb1RjYi", settings); 6 | 7 | Ok(api) 8 | } 9 | 10 | fn build_api_preview() -> BlockfrostResult { 11 | let settings = BlockFrostSettings::new(); 12 | let api = BlockfrostAPI::new("previewy2pbyga8FifUwJSverBCwhESegV6I7gT", settings); 13 | 14 | Ok(api) 15 | } 16 | 17 | #[tokio::main] 18 | async fn main() -> BlockfrostResult<()> { 19 | let pagination_all = Pagination::all(); 20 | 21 | println!("Fetching ..."); 22 | 23 | let all_utxos_for_asset = build_api_preprod()? 24 | .addresses_utxos_asset( 25 | "addr_test1wz2mzj532enpgu5vgwxuh249fpknx5ft9wxse2876z0mp2q89ye7k", 26 | "c6e65ba7878b2f8ea0ad39287d3e2fd256dc5c4160fc19bdf4c4d87e7447454e53", 27 | pagination_all, 28 | ) 29 | .await; 30 | 31 | println!( 32 | "count of all all_utxos_for_asset: {:#?}", 33 | all_utxos_for_asset?.len() 34 | ); 35 | 36 | let all_utxos = build_api_preview()? 37 | .addresses_utxos( 38 | "addr_test1qqcxj57pwhesgkty9uzyyzq6hnl983nnzfug7f99dsntxayxvw0zufye5rc6nz7hcetdsx0ex03wjdrg8xrw9jen2lyslqawdk", 39 | pagination_all, 40 | ) 41 | .await; 42 | 43 | println!("count of all utxos: {:#?}", all_utxos?.len()); 44 | 45 | Ok(()) 46 | } 47 | -------------------------------------------------------------------------------- /src/api/endpoints/health.rs: -------------------------------------------------------------------------------- 1 | use crate::*; 2 | use blockfrost_openapi::models::{ 3 | __get_200_response::Get200Response, _health_clock_get_200_response::HealthClockGet200Response, 4 | _health_get_200_response::HealthGet200Response, 5 | }; 6 | 7 | impl BlockfrostAPI { 8 | /// Root endpoint, points end users to documentation. 9 | pub async fn root(&self) -> BlockfrostResult { 10 | self.call_endpoint("/").await 11 | } 12 | 13 | /// Backend health status as a boolean. 14 | pub async fn health(&self) -> BlockfrostResult { 15 | self.call_endpoint("/health").await 16 | } 17 | 18 | /// Current backend time. 19 | pub async fn health_clock(&self) -> BlockfrostResult { 20 | self.call_endpoint("/health/clock").await 21 | } 22 | } 23 | 24 | #[cfg(test)] 25 | mod tests { 26 | use super::*; 27 | use serde_json::json; 28 | 29 | #[test] 30 | fn test_root() { 31 | let json_value = json!({ 32 | "url": "https://blockfrost.io/", 33 | "version": "0.1.0" 34 | }); 35 | 36 | serde_json::from_value::(json_value).unwrap(); 37 | } 38 | 39 | #[test] 40 | fn test_health() { 41 | let json_value = json!({ 42 | "is_healthy": true, 43 | }); 44 | 45 | serde_json::from_value::(json_value).unwrap(); 46 | } 47 | 48 | #[test] 49 | fn test_health_clock() { 50 | let json_value = json!({ 51 | "is_healthy": true, 52 | "server_time": "1603400958947", 53 | }); 54 | 55 | serde_json::from_value::(json_value).unwrap(); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/utils.rs: -------------------------------------------------------------------------------- 1 | use crate::USER_AGENT; 2 | use reqwest::{ 3 | header::{HeaderMap, HeaderName, HeaderValue}, 4 | Client, 5 | }; 6 | use serde_json::{from_str as json_from, Value as JsonValue}; 7 | use std::{collections::HashMap, str::FromStr}; 8 | 9 | pub(crate) fn try_formatting_json(text: &str) -> serde_json::Result { 10 | let json = json_from::(text)?; 11 | 12 | serde_json::to_string_pretty(&json) 13 | } 14 | 15 | pub(crate) fn create_client_with_project_id( 16 | project_id: impl AsRef, headers: &HashMap, 17 | ) -> Client { 18 | let header_map = build_header_map(project_id.as_ref(), headers); 19 | // Safety: This unwrap is guaranteed to never fail if we only call .default_headers() 20 | Client::builder() 21 | .default_headers(header_map) 22 | .build() 23 | .unwrap() 24 | } 25 | 26 | pub(crate) fn build_header_map(project_id: &str, headers: &HashMap) -> HeaderMap { 27 | let mut header_map = HeaderMap::new(); 28 | let mut project_id = HeaderValue::from_str(project_id).unwrap_or_else(|_| { 29 | panic!( 30 | "Cannot create reqwest::Client because given project_id '{project_id}'cannot be parsed as HeaderValue", 31 | ) 32 | }); 33 | project_id.set_sensitive(true); 34 | let user_agent = HeaderValue::from_static(USER_AGENT); 35 | 36 | header_map.insert("project_id", project_id); 37 | header_map.insert("User-Agent", user_agent); 38 | for (key, val) in headers.iter() { 39 | let (Ok(key), Ok(val)) = (HeaderName::from_str(key), HeaderValue::from_str(val)) else { 40 | panic!("Header \"{key}: {val}\" is invalid") 41 | }; 42 | header_map.insert(key, val); 43 | } 44 | header_map 45 | } 46 | -------------------------------------------------------------------------------- /src/api/endpoints/metrics.rs: -------------------------------------------------------------------------------- 1 | use crate::*; 2 | use blockfrost_openapi::models::{ 3 | metrics_endpoints_inner::MetricsEndpointsInner, metrics_inner::MetricsInner, 4 | }; 5 | 6 | impl BlockfrostAPI { 7 | pub async fn metrics(&self, pagination: Pagination) -> BlockfrostResult> { 8 | self.call_paged_endpoint("/metrics", pagination).await 9 | } 10 | 11 | pub async fn metrics_endpoints( 12 | &self, pagination: Pagination, 13 | ) -> BlockfrostResult> { 14 | self.call_paged_endpoint("/metrics/endpoints", pagination) 15 | .await 16 | } 17 | } 18 | 19 | #[cfg(test)] 20 | mod tests { 21 | use super::*; 22 | use serde_json::json; 23 | 24 | #[test] 25 | fn test_metrics() { 26 | let json_value = json!([ 27 | { 28 | "time": 1612543884, 29 | "calls": 42 30 | }, 31 | { 32 | "time": 1614523884, 33 | "calls": 6942 34 | } 35 | ]); 36 | 37 | serde_json::from_value::>(json_value).unwrap(); 38 | } 39 | 40 | #[test] 41 | fn test_metrics_endpoints() { 42 | let json_value = json!([ 43 | { 44 | "time": 1612543814, 45 | "calls": 182, 46 | "endpoint": "block" 47 | }, 48 | { 49 | "time": 1612543814, 50 | "calls": 42, 51 | "endpoint": "epoch" 52 | }, 53 | { 54 | "time": 1612543812, 55 | "calls": 775, 56 | "endpoint": "block" 57 | }, 58 | { 59 | "time": 1612523884, 60 | "calls": 4, 61 | "endpoint": "epoch" 62 | }, 63 | { 64 | "time": 1612553884, 65 | "calls": 89794, 66 | "endpoint": "block" 67 | } 68 | ]); 69 | 70 | serde_json::from_value::>(json_value).unwrap(); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /.github/workflows/ci.yaml: -------------------------------------------------------------------------------- 1 | on: [push] 2 | name: Continuous Integration 3 | 4 | jobs: 5 | # Check 6 | check: 7 | name: Check 8 | runs-on: ubuntu-latest 9 | steps: 10 | - name: Checkout sources 11 | uses: actions/checkout@v2 12 | 13 | - name: Install stable toolchain 14 | uses: actions-rs/toolchain@v1 15 | with: 16 | profile: minimal 17 | toolchain: stable 18 | override: true 19 | 20 | - name: Run cargo check 21 | uses: actions-rs/cargo@v1 22 | with: 23 | command: check 24 | 25 | # Unit tests 26 | test: 27 | name: Unit Tests 28 | runs-on: ubuntu-latest 29 | steps: 30 | - name: Checkout sources 31 | uses: actions/checkout@v2 32 | 33 | - name: Install stable toolchain 34 | uses: actions-rs/toolchain@v1 35 | with: 36 | profile: minimal 37 | toolchain: stable 38 | override: true 39 | 40 | - name: Run cargo test 41 | uses: actions-rs/cargo@v1 42 | with: 43 | command: test 44 | 45 | # Lints and Clippy 46 | lints: 47 | name: Lints 48 | runs-on: ubuntu-latest 49 | steps: 50 | - name: Checkout sources 51 | uses: actions/checkout@v2 52 | 53 | - name: Install stable toolchain 54 | uses: actions-rs/toolchain@v1 55 | with: 56 | profile: minimal 57 | toolchain: stable 58 | override: true 59 | components: rustfmt, clippy 60 | 61 | - name: Run cargo fmt 62 | uses: actions-rs/cargo@v1 63 | with: 64 | command: fmt 65 | args: --all -- --check 66 | 67 | - name: Run cargo clippy 68 | uses: actions-rs/cargo@v1 69 | with: 70 | command: clippy 71 | args: -- -D warnings 72 | 73 | # Nix build 74 | nix-build: 75 | name: Nix Build 76 | runs-on: ubuntu-latest 77 | steps: 78 | - uses: actions/checkout@v2 79 | - uses: cachix/install-nix-action@v13 80 | with: 81 | nix_path: nixpkgs=channel:nixos-unstable 82 | - run: nix-build 83 | -------------------------------------------------------------------------------- /src/settings.rs: -------------------------------------------------------------------------------- 1 | use std::{collections::HashMap, time::Duration}; 2 | 3 | #[derive(Debug, Clone)] 4 | #[non_exhaustive] 5 | pub struct BlockFrostSettings { 6 | pub base_url: Option, 7 | pub retry_settings: RetrySettings, 8 | pub headers: HashMap, 9 | } 10 | 11 | impl BlockFrostSettings { 12 | pub fn new() -> Self { 13 | Self { 14 | base_url: None, 15 | retry_settings: RetrySettings::default(), 16 | headers: HashMap::new(), 17 | } 18 | } 19 | } 20 | 21 | #[derive(Debug, Clone)] 22 | #[non_exhaustive] 23 | pub struct IpfsSettings { 24 | pub retry_settings: RetrySettings, 25 | pub headers: HashMap, 26 | } 27 | 28 | impl IpfsSettings { 29 | /// Create a customizable [`IpfsSettings`]. 30 | /// 31 | /// # Default settings: 32 | /// 33 | /// - Network: [`IPFS_NETWORK`]. 34 | /// - Query parameters: empty. 35 | /// - Retry settings: disabled. 36 | pub fn new() -> Self { 37 | Self { 38 | retry_settings: RetrySettings::default(), 39 | headers: HashMap::new(), 40 | } 41 | } 42 | } 43 | 44 | /// Uses the default network [`CARDANO_MAINNET_NETWORK`]. 45 | impl Default for BlockFrostSettings { 46 | fn default() -> Self { 47 | Self::new() 48 | } 49 | } 50 | 51 | /// Uses the default network [`IPFS_NETWORK`]. 52 | impl Default for IpfsSettings { 53 | fn default() -> Self { 54 | Self::new() 55 | } 56 | } 57 | 58 | /// Settings for retrying when API rate limit is reached. 59 | /// 60 | /// Amount and delay are set to zero by default, you will need to change both to enable retrying. 61 | /// 62 | /// Check different BlockFrost plans and their limits at . 63 | /// 64 | /// Note: You can disable delay between retries with [`Duration::ZERO`]. 65 | #[derive(Default, Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] 66 | pub struct RetrySettings { 67 | pub amount: u64, 68 | pub delay: Duration, 69 | } 70 | 71 | impl RetrySettings { 72 | /// Create a new `RetrySettings`, with retry amount and delay. 73 | pub fn new(amount: u64, delay: Duration) -> Self { 74 | Self { amount, delay } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 | Crates.io link 4 | 5 | 6 | Docs.rs link 7 | 8 | 9 | License 10 | 11 |

12 | 13 | 14 | 15 | # blockfrost-rust 16 | 17 |
18 |

A Rust SDK for Blockfrost.io API.

19 |

20 | Getting started • 21 | Installation • 22 | Examples • 23 | Endpoints 24 |

25 | 26 | ## Getting started 27 | 28 | To use this SDK you need to login at [blockfrost.io](https://blockfrost.io) 29 | and create a new project to receive an API key. 30 | 31 | 32 | 33 | ## Installation 34 | 35 | Add to your project's `Cargo.toml`: 36 | 37 | ```toml 38 | blockfrost = "1.1.0" 39 | ``` 40 | 41 | ## Examples 42 | 43 | All the examples are located at the [`examples/`] folder. 44 | 45 | You might want to check [`all_requests.rs`] and [`ipfs.rs`]. 46 | 47 | Here is [`simple_request.rs`] with the basic setup necessary and no settings 48 | customization: 49 | 50 | ```rust 51 | use blockfrost::{BlockfrostAPI, BlockfrostResult}; 52 | 53 | fn build_api() -> BlockfrostResult { 54 | let api = BlockfrostAPI::new("mainnetxvMK4xOpp5mHJgihi055KDLU64JJv2be", Default::default()); 55 | Ok(api) 56 | } 57 | 58 | #[tokio::main] 59 | async fn main() -> blockfrost::BlockfrostResult<()> { 60 | let api = build_api()?; 61 | let genesis = api.genesis().await?; 62 | 63 | println!("{:#?}", genesis); 64 | Ok(()) 65 | } 66 | ``` 67 | 68 | [`examples/`]: https://github.com/blockfrost/blockfrost-rust/tree/master/examples 69 | [`all_requests.rs`]: https://github.com/blockfrost/blockfrost-rust/blob/master/examples/all_requests.rs 70 | [`ipfs.rs`]: https://github.com/blockfrost/blockfrost-rust/blob/master/examples/ipfs.rs 71 | [`simple_request.rs`]: https://github.com/blockfrost/blockfrost-rust/blob/master/examples/simple_request.rs 72 | -------------------------------------------------------------------------------- /src/api/mod.rs: -------------------------------------------------------------------------------- 1 | pub(super) mod endpoints; 2 | use crate::{ 3 | pagination::Pagination, 4 | request::{fetch_all_pages, send_get_request}, 5 | url::Url, 6 | utils::build_header_map, 7 | utils::create_client_with_project_id, 8 | BlockFrostSettings, BlockfrostError, 9 | }; 10 | use reqwest::ClientBuilder; 11 | 12 | #[derive(Debug, Clone)] 13 | pub struct BlockfrostAPI { 14 | base_url: String, 15 | settings: BlockFrostSettings, 16 | client: reqwest::Client, 17 | } 18 | 19 | impl BlockfrostAPI { 20 | pub fn new(project_id: &str, settings: BlockFrostSettings) -> Self { 21 | let client = create_client_with_project_id(project_id, &settings.headers); 22 | let base_url = settings 23 | .base_url 24 | .clone() 25 | .unwrap_or_else(|| Url::get_base_url_from_project_id(project_id)); 26 | 27 | Self { 28 | settings, 29 | client, 30 | base_url, 31 | } 32 | } 33 | 34 | pub fn new_with_client( 35 | project_id: &str, settings: BlockFrostSettings, client_builder: ClientBuilder, 36 | ) -> reqwest::Result { 37 | let base_url = settings 38 | .base_url 39 | .clone() 40 | .unwrap_or_else(|| Url::get_base_url_from_project_id(project_id)); 41 | 42 | client_builder 43 | .default_headers(build_header_map(project_id, &settings.headers)) 44 | .build() 45 | .map(|client| Self { 46 | settings, 47 | client, 48 | base_url, 49 | }) 50 | } 51 | 52 | async fn call_endpoint(&self, url_endpoint: &str) -> Result 53 | where 54 | T: for<'de> serde::Deserialize<'de> + serde::de::DeserializeOwned, 55 | { 56 | let url = Url::from_endpoint(self.base_url.as_str(), url_endpoint)?; 57 | 58 | send_get_request(&self.client, url, self.settings.retry_settings).await 59 | } 60 | 61 | async fn call_paged_endpoint( 62 | &self, url_endpoint: &str, pagination: Pagination, 63 | ) -> Result, BlockfrostError> 64 | where 65 | T: for<'de> serde::Deserialize<'de> + serde::de::DeserializeOwned, 66 | { 67 | let url = Url::from_paginated_endpoint(self.base_url.as_str(), url_endpoint, pagination)?; 68 | 69 | if pagination.fetch_all { 70 | fetch_all_pages( 71 | &self.client, 72 | &url, 73 | self.settings.retry_settings, 74 | pagination, 75 | 10, 76 | ) 77 | .await 78 | } else { 79 | send_get_request(&self.client, url, self.settings.retry_settings).await 80 | } 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 6 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 7 | 8 | ## Unreleased 9 | 10 | ## 1.1.0 - 2025-08-05 11 | 12 | ## Changed 13 | 14 | - Updated pagination with tests 15 | - Updated dependencies 16 | - Updated openapi 17 | 18 | ## 1.0.6 - 2025-07-03 19 | 20 | ## Changed 21 | 22 | - blockfrost-openapi 0.1.75 23 | 24 | ## Added 25 | 26 | - Posibility to change the base url 27 | 28 | ## 1.0.5 - 2025-11-20 29 | 30 | ## Fixed 31 | 32 | - Readme version 33 | 34 | ## 1.0.4 - 2023-11-20 35 | 36 | ## Fixed 37 | 38 | - Off-by-one when using Pagination::all(). Thanks [SupernaviX](https://github.com/SupernaviX) 💖 39 | 40 | ## Added 41 | 42 | - Transactions cbor by @SupernaviX in https://github.com/blockfrost/blockfrost-rust/pull/51 43 | 44 | ## Changed 45 | 46 | - Rate limit handling by @SupernaviX in https://github.com/blockfrost/blockfrost-rust/pull/52 47 | 48 | ## 1.0.3 - 2023-10-05 49 | 50 | ### Changed 51 | 52 | - Updated dependencies to latest versions. 53 | 54 | ## 1.0.2 - 2023-05-31 55 | 56 | ### Added 57 | 58 | - Added `derive_address` and `utils_tx_evaluate` endpoints. 59 | 60 | ## 1.0.1 - 2023-01-01 61 | 62 | ### Fixed 63 | 64 | - Return types for asset and tx endpoints 65 | 66 | ## 1.0.0 - 2023-12-18 67 | 68 | ### Changed 69 | 70 | - Implemented pagination for each endpoint. 71 | - Integrated GitHub Continuous Integration for automated testing and deployment. 72 | - Applied Cargo Clippy and rustfmt for code linting and formatting. 73 | - Removed macros. 74 | - Expanded test coverage with additional tests. 75 | - Enhanced data fetching methods. 76 | - Conducted code cleanup and refactoring. 77 | - Refactored settings for improved clarity and efficiency. 78 | - Updated data structures and schemas to the latest standards. 79 | - Provided more comprehensive examples for better user understanding. 80 | - Enhanced unit tests for more robust code testing. 81 | - Implemented additional linting measures for code quality assurance. 82 | - ENDPOINTS.md now contains a table of implemented endpoints. 83 | - Added missing endpoints: 84 | - `accounts_addresses_total` 85 | - `addresses_extended` 86 | - `addresses_utxos_asset` 87 | - `network_eras` 88 | - `mempool` 89 | - `mempool_hash` 90 | - `mempool_addresses_address` 91 | - `scripts_hash_json` 92 | - `scripts_hash_cbor` 93 | - `scripts_datum_hash` 94 | - `scripts_datum_hash_cbor` 95 | - `utils_tx_evaluate` 96 | - `utils_tx_evaluate_utxos` 97 | 98 | ## 0.2.1 - 2023-05-02 99 | 100 | ### Changed 101 | 102 | `use_previewnet` -> `use_preview` 103 | `use_preprodnet` -> `use_preprod` 104 | 105 | ### Fixed 106 | 107 | redemeers -> redeemers 108 | -------------------------------------------------------------------------------- /src/api/endpoints/scripts.rs: -------------------------------------------------------------------------------- 1 | use crate::*; 2 | use blockfrost_openapi::models::{ 3 | script::Script, script_redeemers_inner::ScriptRedeemersInner, scripts_inner::ScriptsInner, 4 | }; 5 | 6 | impl BlockfrostAPI { 7 | pub async fn scripts(&self, pagination: Pagination) -> BlockfrostResult> { 8 | self.call_paged_endpoint("/scripts", pagination).await 9 | } 10 | 11 | pub async fn scripts_by_id(&self, script_hash: &str) -> BlockfrostResult