-
-
Notifications
You must be signed in to change notification settings - Fork 13
Blockfrost API Compatibility — Progress Tracker
This document is the single source of truth for tracking Blockfrost extension implementation progress, known gaps vs Blockfrost, and links to all related PRs. Update this file as modules are merged or gaps are resolved.
Per-module gap tracking docs PR : https://github.com/bloxbean/yaci-store/pull/958
| Module | Endpoints | PR | Merged |
|---|---|---|---|
| Epoch |
/epochs/* — latest, by number, params, stakes, blocks (10 endpoints) |
#780 | Feb 2026 |
| Address |
/addresses/* — info, extended, UTXOs, transactions, total (6 endpoints) |
#784 | Feb 2026 |
| Asset |
/assets/* — list, detail, history, txs, addresses, policy (7 endpoints) |
#780 | Feb 2026 |
| Block |
/blocks/* — latest, by hash/slot/number, txs, next/previous, addresses, CBOR (11 endpoints) |
#811 | Mar 2026 |
| Transaction |
/txs/* — detail, UTXOs, CBOR, metadata, metadata/cbor, redeemers, stakes, delegations, withdrawals, mirs, pool_updates, pool_retires, required_signers (13 endpoints) |
#818 | Apr 2026 |
| Account |
/accounts/{stake} + 11 sub-endpoints (rewards, history, delegations, registrations, withdrawals, mirs, addresses, assets, utxos, transactions) |
#836 | Apr 2026 |
| Metadata |
/metadata/txs/labels, /{label}, /{label}/cbor (3 endpoints) |
#872 | June 2026 |
| Scripts |
/scripts/* — list, detail, json, cbor, redeemers, datum, datum/cbor (7 endpoints) |
#852 | June 2026 |
| Module | Endpoints | Issue | PR | Status |
|---|---|---|---|---|
| Pools |
/pools/* — list, extended, retired, retiring, detail, history, metadata, relays, delegators, blocks, updates, votes (12 endpoints) |
#846 | #847 | Open — remaining diffs are known limitations/upstream gaps |
| Governance |
/governance/dreps/* + /governance/proposals/* (17 endpoints) |
#864 | #865 | Open — reviewers assigned |
| Network |
/, /genesis, /network, /network/eras (4 endpoints) |
— | #866 | Open — accepted limitations: static root identity, supply.locked stub, stake.live structural gap, Conway end null |
Each module now tracks its own mismatch set and known gaps in one place.
Epoch — PR #780 (merged)
Address — PR #784 (merged)
-
/addresses/{address}/extendedmirrors base response;decimalsandhas_nft_onchain_metadataunavailable without CIP-68 ingestion
Asset — PR #780 (merged)
- token metadata fields (
metadata,onchain_metadata) are null without token registry / CIP-68 ingestion -
/assetspagination drift from phantom assets indexed locally but absent on Blockfrost — tracked in #795
Block — PR #811 (merged)
-
/blocks/{hash}/txs/cborreturns body-only CBOR (a8xx) instead of full tx envelope (84xx) — shared issue tracked in #829
Transaction — PR #818 (merged)
-
/txs/{hash}/cborreturns body-only CBOR (a8xx) instead of full tx envelope (84xx) — tracked in #829 -
/txs/{hash}sizeis smaller than Blockfrost because stored CBOR size is body-only -
invalid_before = 0ambiguity cannot distinguish absent value from explicit slot0without schema change -
POST /tx/submitis unsupported (out of scope for indexer)
Account — PR #836 (merged)
- latest
/accounts/{stake}/rewardsrow may lag Blockfrost by one row on live data when AdaPot materialization is behind (accepted timing limitation) - follow-up architecture gap: shared
account_rewardsDB view is still missing for reusablewithdrawable_amountderivation
Pools — PR #847 (open)
Remaining mismatch set:
-
/pools/extended(stake semantics + off-chain metadata fields) -
/pools/retired(same-epoch tie-order drift) -
/pools/{id}(calidus_key+ live/active size semantics) -
/pools/{id}/metadata(off-chain metadata unsupported) -
/pools/{id}/votes(Blockfrost preprod returns404for all pools) -
/pools/{id}/relays(error is in upstreamyaci 0.4.0SingleHostAddrCBOR decoder: IPv6 2-byte groups decoded with wrong endianness; seedocs/issues/yaci-ipv6-relay-endianness.md) -
/pools/{id}/history(tiny floating serialization tail)
Scripts — PR #852 (merged)
Accepted limitation:
-
/scriptsfirst-page overlap is partial — local preprod DB has 136,214 of 137,732 scripts withNULLslot (genesis-era); Blockfrost has indexed further back; ordering within locally available scripts is correct. Will resolve as node finishes syncing.
Governance — PR #865 (open)
-
ratified_epoch/expired_epocharenullon/governance/proposals/*whenstore.governance-aggr.enabled=false - anchor
json_metadata/bytesare null for/governance/dreps/{id}/metadataand/governance/proposals/{id}/metadata(external fetch out of scope)
Network — PR #866 (open)
Accepted limitations:
-
GET /urlandversionreflect local service identity, not Blockfrost's hosted values — expected for RYO deployments -
supply.lockedreturns"0"— formula verified against DB (~355 ADA sync-lag diff); accepted as stub in this PR -
stake.livereturns"0"— requires the Cardano node's in-memory mark snapshot via Local State Query; not reproducible from chain tables (every DB approach overshoots by 43–101B) -
GET /network/erasConwayendisnull— Protocol Version 11 hardfork not yet fired at time of testing (scheduled epoch 285); Blockfrost pre-fills from ratified governance action; will auto-resolve when hardfork fires, no code change needed
Metadata — PR #872 (merged)
-
transaction_metadataduplicate rows can appear after reconnect/restart; rollback delete boundary is exclusive — tracked in #889 -
cip10remains null on/metadata/txs/labels(registry integration not included)
This project does not use Flyway for indexes. Indexes are managed via the admin CLI index applier:
# Apply all standard + extra indexes
yaci-store-admin-cli apply-indexes
# Apply only extra (optional heavy) indexes
yaci-store-admin-cli apply-extra-indexes
# Check which indexes are missing
yaci-store-admin-cli verify-indexesIndex definitions live in components/dbutils/src/main/resources/:
-
index.yml— standard indexes applied byapply-indexes -
extra-index.yml— optional heavy indexes (e.g., GIN onamounts)
| Index name | Table | Notes |
|---|---|---|
idx_address_utxo_owner_stake_addr |
address_utxo |
Covers /accounts/{stake}/addresses, /utxos
|
idx_withdrawal_address, idx_withdrawal_address_slot
|
withdrawal |
Covers /accounts/{stake}/withdrawals
|
idx_instant_reward_address_slot |
instant_reward |
Covers rewards_sum adapot query |
idx_reward_rest_address_slot |
reward_rest |
Covers rewards_sum adapot query |
All scripts use CREATE INDEX CONCURRENTLY IF NOT EXISTS — safe to run on live databases and idempotent.
TODO: Wire all scripts below into
extra-index.ymlso operators can apply them viaapply-extra-indexesinstead of running SQL manually.
Epoch — #780 / perf issue #791
📋 blockfrost-epoch-index-postgresql.sql
-- Target: block WHERE epoch = ? ORDER BY slot ASC/DESC LIMIT ?
-- Used by: GET /epochs/{number}/blocks, GET /epochs/{number}/blocks/{pool_id}
-- Without this: full scan on idx_block_slot with epoch filter — 654ms on preprod
-- NOTE: also created by block module index script (IF NOT EXISTS guard prevents conflict)
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_block_epoch_slot
ON block (epoch, slot);Address — #784
Covered by
index.yml(idx_address_utxo_owner_addr). No extra script needed.
Asset — #780
📋 blockfrost-asset-index-postgresql.sql
-- Target: amounts @> '[{"unit":"..."}]'::jsonb
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_address_utxo_amounts
ON address_utxo USING gin (amounts);
-- Target: assets WHERE unit = ? ORDER BY slot, tx_hash LIMIT ?
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_assets_unit_slot
ON assets (unit, slot) INCLUDE (tx_hash, mint_type, quantity);
-- Target: assets WHERE unit = ? AND policy = ?
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_assets_unit_policy
ON assets (unit, policy) INCLUDE (slot, tx_hash, quantity, asset_name, fingerprint);
-- Target: assets WHERE unit = ? (quantity aggregation)
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_assets_unit_qty
ON assets (unit) INCLUDE (quantity);
-- Target: assets WHERE policy = ? GROUP BY unit
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_assets_policy
ON assets (policy) INCLUDE (unit, slot, tx_hash, quantity);
-- Target: first minted tx per unit (DISTINCT ON unit ORDER BY slot, tx_hash)
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_assets_mint_unit_slot
ON assets (unit, slot) INCLUDE (tx_hash)
WHERE mint_type = 'MINT';
-- Target: candidate scan by mint time (ORDER BY slot, tx_hash)
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_assets_mint_slot_tx_hash
ON assets (slot, tx_hash) INCLUDE (unit)
WHERE mint_type = 'MINT';
-- Target: JOIN transaction ON tx_hash ORDER BY tx_index
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_transaction_tx_hash_tx_index
ON transaction (tx_hash, tx_index);Block — #811
📋 blockfrost-blocks-index-postgresql.sql
-- Target: /blocks/{hash}/txs ordered by tx_index, tx_hash
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_transaction_block_tx_index_tx_hash
ON transaction (block, tx_index, tx_hash);
-- Target: /blocks/epoch/{epoch}/slot/{slot}
-- NOTE: also created by epoch module index script (IF NOT EXISTS prevents conflict)
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_block_epoch_slot
ON block (epoch, slot);
-- Target: output-side address extraction for /blocks/{hash}/addresses
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_address_utxo_block
ON address_utxo (block) INCLUDE (owner_addr, owner_addr_full, tx_hash, output_index);
-- Target: spent-side address extraction for /blocks/{hash}/addresses
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_tx_input_spent_at_block_spent_tx_hash
ON tx_input (spent_at_block, spent_tx_hash) INCLUDE (tx_hash, output_index);Transaction — #818
📋 blockfrost-transaction-index-postgresql.sql
-- NOTE: Already covered — no index needed for these:
-- transaction.tx_hash → PRIMARY KEY
-- withdrawal.tx_hash → idx_withdrawal_tx_hash (index.yml)
-- assets.tx_hash → idx_assets_tx_hash (index.yml)
-- transaction_scripts.tx_hash → idx_txn_scripts_tx_hash (index.yml)
-- Target: transaction_cbor WHERE tx_hash = ?
-- Used by: GET /txs/{hash}/cbor
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_transaction_cbor_tx_hash
ON transaction_cbor (tx_hash);
-- Target: address_utxo WHERE tx_hash = ?
-- Used by: GET /txs/{hash}/utxos (outputs), output_amount aggregation
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_address_utxo_tx_hash
ON address_utxo (tx_hash);
-- Target: tx_input WHERE spent_tx_hash = ?
-- Used by: GET /txs/{hash}/utxos (inputs), input_count subquery
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_tx_input_spent_tx_hash
ON tx_input (spent_tx_hash);
-- Target: delegation WHERE tx_hash = ?
-- Used by: GET /txs/{hash}/delegations
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_delegation_tx_hash
ON delegation (tx_hash);
-- Target: stake_registration WHERE tx_hash = ?
-- Used by: GET /txs/{hash}/stakes
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_stake_registration_tx_hash
ON stake_registration (tx_hash);
-- Target: pool_registration WHERE tx_hash = ?
-- Used by: GET /txs/{hash}/pool_updates
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_pool_registration_tx_hash
ON pool_registration (tx_hash);
-- Target: pool_retirement WHERE tx_hash = ?
-- Used by: GET /txs/{hash}/pool_retires
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_pool_retirement_tx_hash
ON pool_retirement (tx_hash);
-- Target: drep_registration WHERE tx_hash = ?
-- Used by: deposit calculation in GET /txs/{hash}
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_drep_registration_tx_hash
ON drep_registration (tx_hash);Account — #836
📋 blockfrost-account-index-postgresql.sql
Account-specific extra indexes documented for this module:
| Index name | Table | Used by |
|---|---|---|
idx_address_stake_address_addr_full |
address |
/accounts/{stake}/addresses when store.utxo.save-address=true
|
idx_address_utxo_owner_stake_addr_owner_addr |
address_utxo |
/accounts/{stake}/addresses fallback path ordered by payment address |
idx_address_utxo_owner_stake_addr_slot_output_index |
address_utxo |
/accounts/{stake}/utxos, output-side account transaction batch scans |
idx_tx_input_tx_hash_output_index |
tx_input |
unspent checks and address_utxo -> tx_input joins |
idx_tx_input_spent_tx_hash |
tx_input |
spending-side transaction lookups in /accounts/{stake}/transactions
|
These are the extra account-module indexes documented in addition to indexes already covered by index.yml or existing primary keys.
-- NOTE: current account reader no longer uses address_tx_amount.
-- It reads from address, address_utxo, tx_input, transaction, reward_rest, instant_reward, and withdrawal.
--
-- Already covered in index.yml / existing PKs:
-- idx_address_utxo_owner_stake_addr
-- idx_withdrawal_address
-- idx_withdrawal_address_slot
-- idx_reward_rest_address_slot
-- idx_instant_reward_address_slot
-- idx_delegation_vote_drep_id
-- transaction.tx_hash (PRIMARY KEY)
-- Target: save-address fast path for /accounts/{stake}/addresses
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_address_stake_address_addr_full
ON address (stake_address, addr_full);
-- Target: /accounts/{stake}/addresses ordered by owner_addr
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_address_utxo_owner_stake_addr_owner_addr
ON address_utxo (owner_stake_addr, owner_addr);
-- Target: /accounts/{stake}/utxos and output-side transaction batch scans
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_address_utxo_owner_stake_addr_slot_output_index
ON address_utxo (owner_stake_addr, slot, output_index);
-- Target: unspent checks and joins from address_utxo -> tx_input
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_tx_input_tx_hash_output_index
ON tx_input (tx_hash, output_index);
-- Target: spending-side transaction lookups in /accounts/{stake}/transactions
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_tx_input_spent_tx_hash
ON tx_input (spent_tx_hash);Pools — #847
📋 blockfrost-pools-index-postgresql.sql
-- NOTE: Already covered — no index needed for these:
-- pool.pool_id → first column of PK (pool_id, tx_hash, cert_index, slot)
-- voting_procedure.gov_action_tx_hash → idx_voting_procedure_gov_action_tx_hash (index.yml)
-- epoch_stake.(epoch, pool_id) → idx_epoch_stake_epoch_pool_id (adapot migration)
-- Target: pool_registration WHERE pool_id = ? ORDER BY epoch DESC
-- Used by: GET /pools/{id}, /metadata, /relays, /updates
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_pool_registration_pool_id_epoch
ON pool_registration (pool_id, epoch DESC);
-- Target: pool_retirement WHERE pool_id = ?
-- Used by: GET /pools/{id} (retirement epoch), /updates
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_pool_retirement_pool_id
ON pool_retirement (pool_id);
-- Target: pool WHERE status = ? ORDER BY pool_id
-- Used by: GET /pools, /pools/retired, /pools/retiring
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_pool_status
ON pool (status, pool_id);
-- Target: epoch_stake WHERE pool_id = ? (no epoch filter — for MAX(epoch) scan)
-- Used by: GET /pools/{id}/delegators
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_epoch_stake_pool_id
ON epoch_stake (pool_id);
-- Target: block WHERE slot_leader = ?
-- Used by: GET /pools/{id}/blocks (looks up blocks by VRF key)
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_block_slot_leader
ON block (slot_leader);
-- Target: voting_procedure WHERE voter_hash = ? AND voter_type = 'STAKING_POOL_KEY_HASH'
-- Used by: GET /pools/{id}/votes
-- NOTE: also created by governance index script (IF NOT EXISTS prevents conflict)
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_voting_procedure_voter_hash_voter_type
ON voting_procedure (voter_hash, voter_type);Scripts — #852
📋 blockfrost-scripts-index-postgresql.sql
-- NOTE: Already covered — no index needed for these:
-- script.script_hash → PRIMARY KEY (leading column)
-- datum.datum_hash → PRIMARY KEY (leading column)
-- transaction_scripts.tx_hash → idx_txn_scripts_tx_hash (index.yml)
-- Target: transaction_scripts WHERE script_hash = ? AND purpose IS NOT NULL ORDER BY slot DESC
-- Used by: GET /scripts/{hash}/redeemers (paginated)
-- Partial index keeps it small — excludes rows where purpose IS NULL (non-redeemer script associations)
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_transaction_scripts_script_hash_slot
ON transaction_scripts (script_hash, slot DESC)
WHERE purpose IS NOT NULL;Governance — #865
📋 blockfrost-governance-index-postgresql.sql
-- NOTE: Already covered — no index needed for these:
-- drep.drep_hash → first column of PK (drep_hash, tx_hash, cert_index, slot)
-- local_drep_dist.drep_hash → first column of PK (drep_hash, epoch)
-- local_treasury_withdrawal.(gov_action_tx_hash, gov_action_index) → first two columns of PK
-- gov_action_proposal.tx_hash → idx_gov_action_proposal_txhash (index.yml)
-- voting_procedure.(gov_action_tx_hash, ...) → idx_voting_procedure_gov_action_tx_hash_gov_action_index (index.yml)
-- delegation_vote.drep_id → idx_delegation_vote_drep_id (index.yml)
-- Target: drep_registration WHERE drep_hash = ? ORDER BY slot DESC
-- Used by: GET /governance/dreps/{id}/updates, /governance/dreps/{id}/metadata (latest anchor)
-- PK is (tx_hash, cert_index) — drep_hash is not the leading column
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_drep_registration_drep_hash_slot
ON drep_registration (drep_hash, slot DESC);
-- Target: voting_procedure WHERE voter_hash = ? AND voter_type = 'DREP_KEY_HASH'
-- Used by: GET /governance/dreps/{id}/votes
-- NOTE: also created by pools index script (IF NOT EXISTS prevents conflict)
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_voting_procedure_voter_hash_voter_type
ON voting_procedure (voter_hash, voter_type);Network — #866
No extra indexes needed. Queries use primary keys on
epoch_paramandblock.
Metadata — #872
Covered by
index.yml(idx_txn_metadata_tx_hash,idx_txn_metadata_label). No extra script needed.
Update this file when PRs are merged or gaps are resolved. When opening a new gap-specific issue, link it back to this document for traceability.