Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions crates/core/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -627,6 +627,10 @@ pub struct MinibfConfig {
pub token_registry_url: Option<String>,
pub url: Option<String>,
pub max_scan_items: Option<u64>,
/// Optional base path for all Blockfrost API endpoints (e.g., "/api/v0").
/// When set, all API routes will be nested under this path.
/// Set to "/api/v0" for full Blockfrost OpenAPI specification compliance.
pub base_path: Option<String>,
}

#[derive(Deserialize, Serialize, Clone)]
Expand Down
3 changes: 3 additions & 0 deletions crates/core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -308,6 +308,9 @@ impl WalError {

#[derive(Debug, Error)]
pub enum ServeError {
#[error("invalid configuration: {0}")]
ConfigError(String),

#[error("failed to bind listener")]
BindError(std::io::Error),

Expand Down
12 changes: 12 additions & 0 deletions crates/minibf/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,18 @@ Blockfrost-compatible HTTP API service for the Dolos Cardano data node.

`dolos-minibf` provides a REST API that mimics the Blockfrost API, allowing existing Cardano ecosystem tools to work seamlessly with Dolos without requiring code changes. It serves as an API compatibility layer for existing Blockfrost clients.

### Blockfrost Spec Compliance

The official [Blockfrost OpenAPI specification](https://github.com/blockfrost/openapi) requires all endpoints to be served under the `/api/v0` base path. Dolos supports this via the `base_path` configuration option:

```toml
[serve.minibf]
listen_address = "[::]:3000"
base_path = "/api/v0" # For full Blockfrost spec compliance
```

When `base_path` is set, **all routes** (including health and metrics) are served under that prefix (e.g., `/api/v0/blocks/latest`, `/api/v0/health`, `/api/v0/metrics`). If omitted, endpoints are served at the root for backward compatibility.

## Features

### API Compatibility
Expand Down
21 changes: 18 additions & 3 deletions crates/minibf/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -269,7 +269,7 @@ impl<D: Domain> Facade<D> {

pub struct Driver;

pub fn build_router<D>(cfg: MinibfConfig, domain: D) -> Router
pub fn build_router<D>(cfg: MinibfConfig, domain: D) -> Result<Router, ServeError>
where
D: Domain + SubmitExt + Clone + Send + Sync + 'static,
Option<AccountState>: From<D::Entity>,
Expand Down Expand Up @@ -477,7 +477,22 @@ where
} else {
CorsLayer::new()
});
app.layer(NormalizePathLayer::trim_trailing_slash())

// Optionally nest all routes under base_path
if let Some(base_path) = &cfg.base_path {
// Validate before using
if base_path.is_empty() || base_path == "/" || !base_path.starts_with('/') || base_path.contains('*') {
return Err(ServeError::ConfigError(format!(
"base_path must start with '/', must not be just '/', and must not contain wildcards; got: \"{}\"",
base_path
)));
}
// Only reach here if valid
Ok(Router::new().nest(base_path, app).layer(NormalizePathLayer::trim_trailing_slash()))
} else {
// No base_path configured
Ok(app.layer(NormalizePathLayer::trim_trailing_slash()))
}
}

impl<D: Domain + SubmitExt, C: CancelToken> dolos_core::Driver<D, C> for Driver
Expand All @@ -492,7 +507,7 @@ where
type Config = MinibfConfig;

async fn run(cfg: Self::Config, domain: D, cancel: C) -> Result<(), ServeError> {
let app = build_router(cfg.clone(), domain);
let app = build_router(cfg.clone(), domain)?;

let listener = tokio::net::TcpListener::bind(cfg.listen_address)
.await
Expand Down
19 changes: 11 additions & 8 deletions docs/content/apis/minibf.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -129,19 +129,21 @@ The endpoints required for most tx builders to work are supported. Libraries lik

The `serve.minibf` section controls the options for the MiniBF endpoint that can be used by clients.

| property | type | example |
| ------------------ | ------- | -------------------------------- |
| listen_address | string | "[::]:3000" |
| permissive_cors | boolean | true |
| token_registry_url | string | "https://token-registry.io" |
| url | string | "https://minibf.local" |
| max_scan_items | integer | 3000 |
| property | type | example | description |
| ------------------ | ------- | -------------------------------- | ----------------------------------------------------------------- |
| listen_address | string | "[::]:3000" | Local address to listen for incoming connections |
| permissive_cors | boolean | true | Allow cross-origin requests from any origin |
| token_registry_url | string | "https://token-registry.io" | Optional token registry base URL for off-chain asset metadata |
| url | string | "https://minibf.local" | Optional public URL used in the `/` root response |
| max_scan_items | integer | 3000 | Caps page-based scans for heavy endpoints (defaults to 3000) |
| base_path | string | "/api/v0" | Optional base path for API endpoints (Blockfrost spec compliance) |

- `listen_address`: the local address (`IP:PORT`) to listen for incoming connections (`[::]` represents any IP address).
- `permissive_cors`: allow cross-origin requests from any origin.
- `token_registry_url`: optional token registry base URL used for off-chain asset metadata.
- `token_registry_url`: optional token registry base URL used for off-chain asset metadata.
- `url`: optional public URL used in the `/` root response.
- `max_scan_items`: caps page-based scans for heavy endpoints (defaults to 3000 if unset).
- `base_path`: optional base path prefix for all endpoints. Set to `"/api/v0"` for full Blockfrost OpenAPI specification compliance. When configured, **all routes** (including health and metrics) will be available under this prefix (e.g., `/api/v0/blocks/latest`, `/api/v0/health`, `/api/v0/metrics`). If omitted, endpoints are served at the root (default behavior for backward compatibility).

This is an example of the `serve.minibf` fragment with a `dolos.toml` configuration file.

Expand All @@ -152,6 +154,7 @@ permissive_cors = true
token_registry_url = "https://token-registry.io"
url = "https://minibf.local"
max_scan_items = 3000
# base_path = "/api/v0" # Uncomment for Blockfrost OpenAPI spec compliance
```

Some endpoints require additional data tracked under the `chain.track` configuration. Tracking defaults to `true` for all fields, but you can disable specific datasets to reduce storage and CPU costs. Disabling a dataset can cause related endpoints to return `404` or empty results.
Expand Down
1 change: 1 addition & 0 deletions src/bin/dolos/init.rs
Original file line number Diff line number Diff line change
Expand Up @@ -330,6 +330,7 @@ impl ConfigEditor {
token_registry_url: None,
url: None,
max_scan_items: None,
base_path: None,
}
.into();
} else {
Expand Down
4 changes: 3 additions & 1 deletion src/bin/dolos/minibf.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,9 @@ pub async fn run(config: &RootConfig, args: &Args) -> miette::Result<()> {
.into_diagnostic()
.context("invalid minibf path")?;

let app = dolos_minibf::build_router(minibf.clone(), domain);
let app = dolos_minibf::build_router(minibf.clone(), domain)
.into_diagnostic()
.context("building minibf router")?;

let request = Request::builder()
.method("GET")
Expand Down