Skip to content

Blockfrost API Compatibility — Progress Tracker

ducpm2303 edited this page Apr 10, 2026 · 11 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.


✅ 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

🔄 In Progress (Open PRs)

Module Endpoints Issue PR Status
Account /accounts/{stake} + 11 sub-endpoints (rewards, history, delegations, registrations, withdrawals, mirs, addresses, assets, utxos, transactions) #835 #836 Open — validated on 6 preprod datasets, 141 / 144 checks matched; only accepted AdaPot /rewards timing limitation remains
Pools /pools/* — list, extended, retired, retiring, detail, history, metadata, relays, delegators, blocks, updates, votes (12 endpoints) #846 #847 Open — needs review
Scripts /scripts/* — list, detail, json, cbor, redeemers, datum, datum/cbor (7 endpoints) #851 #852 Open — ready to merge
Governance /governance/dreps/* + /governance/proposals/* (17 endpoints) #864 #865 Open — reviewers assigned
Network /, /genesis, /network, /network/eras (4 endpoints) #866 Open — partially stubbed
Metadata /metadata/txs/labels, /{label}, /{label}/cbor (3 endpoints) #872 Open — ready to merge

⚠️ Known Gaps vs Blockfrost Compatibility

These are persistent limitations across all merged and in-progress modules. They are tracked here for traceability.

Structural / Architectural Gaps

Gap Affected endpoints Root cause Fix path
CBOR format mismatch — returns body-only CBOR (a8xx) instead of full tx envelope (84xx = body + witnesses + metadata) /blocks/{hash}/txs/cbor, /txs/{hash}/cbor TransactionProcessor stores body-only CBOR Requires schema + processor change (#829)
tx.size smaller than Blockfrost /txs/{hash} TRANSACTION_CBOR.CBOR_SIZE is body-only, not full tx size Fix alongside CBOR format above
Off-chain pool metadata always nullname, ticker, homepage, description /pools/{id}, /pools/{id}/metadata, /txs/{hash}/pool_updates yaci-store does not crawl off-chain pool metadata URLs Requires separate metadata crawler
Token metadata always nullmetadata, onchain_metadata /assets/{asset} No token registry / CIP-68 metadata ingestion Requires token metadata store
POST /tx/submit not supported /tx/submit Requires direct node submit API integration Out of scope for indexer

Data Gaps (require optional stores enabled)

Gap Affected endpoints Required config Notes
active_stake null /epochs/* store.adapot.enabled=true Graceful degradation when disabled
Latest /accounts/{stake}/rewards row can lag Blockfrost by 1 row /accounts/{stake}/rewards store.adapot.enabled=true Accepted AdaPot timing limitation on live data. Reproduced on preprod dataset validation for C_many_regs, F_balanced_history, and L_mixed_reward_history during #836
live_stake / live_delegators always "0" /pools/{id}, /pools/extended Requires real-time ledger state Not available in yaci-store — tracked in #847
calidus_key always null /pools/{id} CIP-0119 not indexed Out of scope
ratified_epoch / expired_epoch null /governance/proposals/* store.governance-aggr.enabled=true Graceful degradation — tracked in #865
json_metadata / bytes null on governance anchors /governance/dreps/{id}/metadata, /governance/proposals/{id}/metadata External metadata fetching out of scope On-chain anchor URL/hash returned only

Account follow-up (architecture, not current parity blocker)

Item Affected endpoints Notes
Shared account_rewards DB view is still missing /accounts/{stake} withdrawable_amount currently matches tested preprod datasets, but it is still computed in the reader instead of a reusable DB view. Follow-up issue should add account_rewards for withdrawable_amount and keep same-slot tie-breaking as later work.

Minor / Cosmetic Gaps

Gap Affected endpoints Notes
epoch.total_fees mismatch /epochs/{number} DB epoch.total_fees wrong; correct: SUM(block.total_fees)#781 (open, assigned)
Block distribution query slow /epochs/{number}/blocks, /epochs/{number}/blocks/{pool_id} Full scan on block table when filtering by epoch + ordering by slot — #791 (open). Fix: idx_block_epoch_slot already shipped in blockfrost-blocks-index-postgresql.sql
Phantom assets cause pagination shift /assets 6 assets from failed/invalid transactions indexed by yaci-store but not by Blockfrost — causes row count drift on deep pages — #795 (open, assigned)
transaction_metadata duplicate rows on restart /metadata/txs/labels, /{label}, /{label}/cbor MetadataRollbackProcessor uses deleteBySlotGreaterThan (exclusive) but the node re-delivers the block at the cursor slot on reconnect — inserts exact duplicates. No unique constraint on (tx_hash, label)#889 (open)
invalid_before = 0 ambiguity /txs/{hash} Cannot distinguish absent from explicitly set to slot 0 without schema change
cip10 always null /metadata/txs/labels CIP-10 community registry not integrated — #872
supply.circulating / supply.locked / stake.live stub /network Stubs returning "0"#866
Plutus JSON field ordering cosmetic diff /scripts/{hash}/json constructor before fields vs Blockfrost reverse — accepted deviation
/addresses/{address}/extended mirrors base response /addresses/{address}/extended decimals and has_nft_onchain_metadata unavailable without CIP-68 ingestion

📋 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.


📊 Summary

State Modules Approx. Endpoints
Merged in main 5 ~47
In open PRs 6 ~54
Total when all merged 11 ~101

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.

Home

Extensions

Clone this wiki locally