Skip to content

Blockfrost API Compatibility — Progress Tracker

Satya edited this page Jun 4, 2026 · 9 revisions

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


✅ Merged into main

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

🔄 In Progress (Open PRs)

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

⚠️ Module-by-module mismatches & known gaps

Each module now tracks its own mismatch set and known gaps in one place.

Epoch — PR #780 (merged)
  • active_stake can be null on /epochs/* when store.adapot.enabled=false (expected graceful degradation)
  • epoch.total_fees mismatch on /epochs/{number} — tracked in #781
  • performance gap on /epochs/{number}/blocks* — tracked in #791
Address — PR #784 (merged)
  • /addresses/{address}/extended mirrors base response; decimals and has_nft_onchain_metadata unavailable without CIP-68 ingestion
Asset — PR #780 (merged)
  • token metadata fields (metadata, onchain_metadata) are null without token registry / CIP-68 ingestion
  • /assets pagination drift from phantom assets indexed locally but absent on Blockfrost — tracked in #795
Block — PR #811 (merged)
  • /blocks/{hash}/txs/cbor returns body-only CBOR (a8xx) instead of full tx envelope (84xx) — shared issue tracked in #829
Transaction — PR #818 (merged)
  • /txs/{hash}/cbor returns body-only CBOR (a8xx) instead of full tx envelope (84xx) — tracked in #829
  • /txs/{hash} size is smaller than Blockfrost because stored CBOR size is body-only
  • invalid_before = 0 ambiguity cannot distinguish absent value from explicit slot 0 without schema change
  • POST /tx/submit is unsupported (out of scope for indexer)
Account — PR #836 (merged)
  • latest /accounts/{stake}/rewards row may lag Blockfrost by one row on live data when AdaPot materialization is behind (accepted timing limitation)
  • follow-up architecture gap: shared account_rewards DB view is still missing for reusable withdrawable_amount derivation
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 returns 404 for all pools)
  • /pools/{id}/relays (error is in upstream yaci 0.4.0 SingleHostAddr CBOR decoder: IPv6 2-byte groups decoded with wrong endianness; see docs/issues/yaci-ipv6-relay-endianness.md)
  • /pools/{id}/history (tiny floating serialization tail)
Scripts — PR #852 (merged)

Accepted limitation:

  • /scripts first-page overlap is partial — local preprod DB has 136,214 of 137,732 scripts with NULL slot (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_epoch are null on /governance/proposals/* when store.governance-aggr.enabled=false
  • anchor json_metadata / bytes are null for /governance/dreps/{id}/metadata and /governance/proposals/{id}/metadata (external fetch out of scope)
Network — PR #866 (open)

Accepted limitations:

  • GET / url and version reflect local service identity, not Blockfrost's hosted values — expected for RYO deployments
  • supply.locked returns "0" — formula verified against DB (~355 ADA sync-lag diff); accepted as stub in this PR
  • stake.live returns "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/eras Conway end is null — 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_metadata duplicate rows can appear after reconnect/restart; rollback delete boundary is exclusive — tracked in #889
  • cip10 remains null on /metadata/txs/labels (registry integration not included)

📋 Index Management

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-indexes

Index definitions live in components/dbutils/src/main/resources/:

  • index.yml — standard indexes applied by apply-indexes
  • extra-index.yml — optional heavy indexes (e.g., GIN on amounts)

Already covered in index.yml (no action needed)

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

Blockfrost-specific indexes — per module (Proposal, will update)

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.yml so operators can apply them via apply-extra-indexes instead 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_param and block.


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.