From 18dddb12e8c4b48243b94000ac700883f4317313 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 22 Feb 2026 18:25:20 +0000 Subject: [PATCH 1/4] Add new API endpoints for Blockfrost API v0.1.85 Update the SDK to match all API changes since v0.1.37, including: - Accounts: account_utxos (v0.1.69), account_transactions (v0.1.82) - Blocks: block_latest_transactions_cbor (v0.1.74), block_transactions_cbor (v0.1.75) - Transactions: transaction_required_signers (v0.1.61), transaction_cbor (v0.1.64) - Network: network_eras (v0.1.46) - Governance (new): Full Conway-era governance support with DRep and proposal endpoints including dreps listing, drep details, delegators, metadata, updates, votes, proposals listing, proposal details, parameters, withdrawals, votes, and metadata Bump version to 0.7.0 (API v0.1.85). All 211 tests pass. https://claude.ai/code/session_01FSq7HNQWih59Y5NX349Diq --- blockfrost/api/__init__.py | 24 +- blockfrost/api/cardano/accounts.py | 62 +++++ blockfrost/api/cardano/blocks.py | 56 +++++ blockfrost/api/cardano/governance.py | 335 +++++++++++++++++++++++++ blockfrost/api/cardano/network.py | 22 +- blockfrost/api/cardano/transactions.py | 44 ++++ setup.py | 4 +- tests/test_cardano_accounts.py | 55 ++++ tests/test_cardano_blocks.py | 41 +++ tests/test_cardano_governance.py | 268 ++++++++++++++++++++ tests/test_cardano_network.py | 48 ++++ tests/test_cardano_transactions.py | 36 +++ 12 files changed, 990 insertions(+), 5 deletions(-) create mode 100644 blockfrost/api/cardano/governance.py create mode 100644 tests/test_cardano_governance.py diff --git a/blockfrost/api/__init__.py b/blockfrost/api/__init__.py index 6f7af1d..211923c 100644 --- a/blockfrost/api/__init__.py +++ b/blockfrost/api/__init__.py @@ -57,7 +57,9 @@ def root(self, **kwargs): account_mirs, \ account_addresses, \ account_addresses_assets, \ - account_addresses_total + account_addresses_total, \ + account_utxos, \ + account_transactions from .cardano.addresses import \ address, \ address_extended, \ @@ -75,12 +77,14 @@ def root(self, **kwargs): from .cardano.blocks import \ block_latest, \ block_latest_transactions, \ + block_latest_transactions_cbor, \ block, \ block_slot, \ block_epoch_slot, \ blocks_next, \ blocks_previous, \ block_transactions, \ + block_transactions_cbor, \ blocks_addresses from .cardano.epochs import \ epoch_latest, \ @@ -104,7 +108,8 @@ def root(self, **kwargs): metadata_label_json, \ metadata_label_cbor from .cardano.network import \ - network + network, \ + network_eras from .cardano.pools import \ pools, \ pools_extended, \ @@ -131,6 +136,8 @@ def root(self, **kwargs): transaction_submit, \ transaction_submit_cbor, \ transaction_redeemers, \ + transaction_required_signers, \ + transaction_cbor, \ transaction_evaluate, \ transaction_evaluate_cbor, \ transaction_evaluate_utxos @@ -142,5 +149,18 @@ def root(self, **kwargs): script_redeemers, \ script_datum, \ script_datum_cbor + from .cardano.governance import \ + governance_dreps, \ + governance_drep, \ + governance_drep_delegators, \ + governance_drep_metadata, \ + governance_drep_updates, \ + governance_drep_votes, \ + governance_proposals, \ + governance_proposal, \ + governance_proposal_parameters, \ + governance_proposal_withdrawals, \ + governance_proposal_votes, \ + governance_proposal_metadata from .cardano.utils import \ utils_addresses_xpub diff --git a/blockfrost/api/cardano/accounts.py b/blockfrost/api/cardano/accounts.py index ab5b4cd..d8ba67c 100644 --- a/blockfrost/api/cardano/accounts.py +++ b/blockfrost/api/cardano/accounts.py @@ -274,6 +274,68 @@ def account_addresses_assets(self, stake_address: str, **kwargs): ) +@list_request_wrapper +def account_utxos(self, stake_address: str, **kwargs): + """ + Obtain information about UTXOs of a specific account. + + https://docs.blockfrost.io/#tag/Cardano-Accounts/paths/~1accounts~1{stake_address}~1utxos/get + + :param stake_address: Bech32 stake address. + :type stake_address: str + :param return_type: Optional. "object", "json" or "pandas". Default: "object". + :type return_type: str + :param gather_pages: Optional. Default: false. Will collect all pages into one return + :type gather_pages: bool + :param count: Optional. Default: 100. The number of results displayed on one page. + :type count: int + :param page: Optional. The page number for listing the results. + :type page: int + :param order: Optional. "asc" or "desc". Default: "asc". + :type order: str + :returns A list of objects. + :rtype [Namespace] + :raises ApiError: If API fails + :raises Exception: If the API response is somehow malformed. + """ + return requests.get( + url=f"{self.url}/accounts/{stake_address}/utxos", + params=self.query_parameters(kwargs), + headers=self.default_headers + ) + + +@list_request_wrapper +def account_transactions(self, stake_address: str, **kwargs): + """ + Obtain information about transactions associated with a specific account. + + https://docs.blockfrost.io/#tag/Cardano-Accounts/paths/~1accounts~1{stake_address}~1transactions/get + + :param stake_address: Bech32 stake address. + :type stake_address: str + :param return_type: Optional. "object", "json" or "pandas". Default: "object". + :type return_type: str + :param gather_pages: Optional. Default: false. Will collect all pages into one return + :type gather_pages: bool + :param count: Optional. Default: 100. The number of results displayed on one page. + :type count: int + :param page: Optional. The page number for listing the results. + :type page: int + :param order: Optional. "asc" or "desc". Default: "asc". + :type order: str + :returns A list of objects. + :rtype [Namespace] + :raises ApiError: If API fails + :raises Exception: If the API response is somehow malformed. + """ + return requests.get( + url=f"{self.url}/accounts/{stake_address}/transactions", + params=self.query_parameters(kwargs), + headers=self.default_headers + ) + + @request_wrapper def account_addresses_total(self, stake_address: str, **kwargs): """ diff --git a/blockfrost/api/cardano/blocks.py b/blockfrost/api/cardano/blocks.py index aa4f6dd..8c7eb36 100644 --- a/blockfrost/api/cardano/blocks.py +++ b/blockfrost/api/cardano/blocks.py @@ -204,6 +204,62 @@ def block_transactions(self, hash_or_number: str, **kwargs): ) +@list_request_wrapper +def block_latest_transactions_cbor(self, **kwargs): + """ + Return the transactions within the latest block in CBOR format. + + https://docs.blockfrost.io/#tag/Cardano-Blocks/paths/~1blocks~1latest~1txs~1cbor/get + + :param gather_pages: Optional. Default: false. Will collect all pages into one return + :type gather_pages: bool + :param count: Optional. Default: 100. The number of results displayed on one page. + :type count: int + :param page: Optional. The page number for listing the results. + :type page: int + :param order: Optional. "asc" or "desc". Default: "asc". + :type order: str + :returns A list of objects. + :rtype [Namespace] + :raises ApiError: If API fails + :raises Exception: If the API response is somehow malformed. + """ + return requests.get( + url=f"{self.url}/blocks/latest/txs/cbor", + params=self.query_parameters(kwargs), + headers=self.default_headers + ) + + +@list_request_wrapper +def block_transactions_cbor(self, hash_or_number: str, **kwargs): + """ + Return the transactions within the block in CBOR format. + + https://docs.blockfrost.io/#tag/Cardano-Blocks/paths/~1blocks~1{hash_or_number}~1txs~1cbor/get + + :param hash_or_number: Hash or number of the requested block. + :type hash_or_number: str + :param gather_pages: Optional. Default: false. Will collect all pages into one return + :type gather_pages: bool + :param count: Optional. Default: 100. The number of results displayed on one page. + :type count: int + :param page: Optional. The page number for listing the results. + :type page: int + :param order: Optional. "asc" or "desc". Default: "asc". + :type order: str + :returns A list of objects. + :rtype [Namespace] + :raises ApiError: If API fails + :raises Exception: If the API response is somehow malformed. + """ + return requests.get( + url=f"{self.url}/blocks/{hash_or_number}/txs/cbor", + params=self.query_parameters(kwargs), + headers=self.default_headers + ) + + @list_request_wrapper def blocks_addresses(self, hash_or_number: str, **kwargs): """ diff --git a/blockfrost/api/cardano/governance.py b/blockfrost/api/cardano/governance.py new file mode 100644 index 0000000..26f6734 --- /dev/null +++ b/blockfrost/api/cardano/governance.py @@ -0,0 +1,335 @@ +import requests +from blockfrost.utils import request_wrapper, list_request_wrapper + + +@list_request_wrapper +def governance_dreps(self, **kwargs): + """ + Return the list of registered delegated representatives (DReps). + + https://docs.blockfrost.io/#tag/Cardano-Governance/paths/~1governance~1dreps/get + + :param return_type: Optional. "object", "json" or "pandas". Default: "object". + :type return_type: str + :param gather_pages: Optional. Default: false. Will collect all pages into one return + :type gather_pages: bool + :param count: Optional. Default: 100. The number of results displayed on one page. + :type count: int + :param page: Optional. The page number for listing the results. + :type page: int + :param order: Optional. "asc" or "desc". Default: "asc". + :type order: str + :returns A list of objects. + :rtype [Namespace] + :raises ApiError: If API fails + :raises Exception: If the API response is somehow malformed. + """ + return requests.get( + url=f"{self.url}/governance/dreps", + params=self.query_parameters(kwargs), + headers=self.default_headers + ) + + +@request_wrapper +def governance_drep(self, drep_id: str, **kwargs): + """ + Return information about a specific delegated representative (DRep). + + https://docs.blockfrost.io/#tag/Cardano-Governance/paths/~1governance~1dreps~1{drep_id}/get + + :param drep_id: The DRep ID (Bech32 or hex encoded). + :type drep_id: str + :param return_type: Optional. "object", "json" or "pandas". Default: "object". + :type return_type: str + :returns object. + :rtype: Namespace + :raises ApiError: If API fails + :raises Exception: If the API response is somehow malformed. + """ + return requests.get( + url=f"{self.url}/governance/dreps/{drep_id}", + headers=self.default_headers + ) + + +@list_request_wrapper +def governance_drep_delegators(self, drep_id: str, **kwargs): + """ + Return the list of delegators to a specific DRep. + + https://docs.blockfrost.io/#tag/Cardano-Governance/paths/~1governance~1dreps~1{drep_id}~1delegators/get + + :param drep_id: The DRep ID (Bech32 or hex encoded). + :type drep_id: str + :param return_type: Optional. "object", "json" or "pandas". Default: "object". + :type return_type: str + :param gather_pages: Optional. Default: false. Will collect all pages into one return + :type gather_pages: bool + :param count: Optional. Default: 100. The number of results displayed on one page. + :type count: int + :param page: Optional. The page number for listing the results. + :type page: int + :param order: Optional. "asc" or "desc". Default: "asc". + :type order: str + :returns A list of objects. + :rtype [Namespace] + :raises ApiError: If API fails + :raises Exception: If the API response is somehow malformed. + """ + return requests.get( + url=f"{self.url}/governance/dreps/{drep_id}/delegators", + params=self.query_parameters(kwargs), + headers=self.default_headers + ) + + +@request_wrapper +def governance_drep_metadata(self, drep_id: str, **kwargs): + """ + Return the metadata of a specific DRep. + + https://docs.blockfrost.io/#tag/Cardano-Governance/paths/~1governance~1dreps~1{drep_id}~1metadata/get + + :param drep_id: The DRep ID (Bech32 or hex encoded). + :type drep_id: str + :param return_type: Optional. "object", "json" or "pandas". Default: "object". + :type return_type: str + :returns object. + :rtype: Namespace + :raises ApiError: If API fails + :raises Exception: If the API response is somehow malformed. + """ + return requests.get( + url=f"{self.url}/governance/dreps/{drep_id}/metadata", + headers=self.default_headers + ) + + +@list_request_wrapper +def governance_drep_updates(self, drep_id: str, **kwargs): + """ + Return the list of certificate updates to a specific DRep. + + https://docs.blockfrost.io/#tag/Cardano-Governance/paths/~1governance~1dreps~1{drep_id}~1updates/get + + :param drep_id: The DRep ID (Bech32 or hex encoded). + :type drep_id: str + :param return_type: Optional. "object", "json" or "pandas". Default: "object". + :type return_type: str + :param gather_pages: Optional. Default: false. Will collect all pages into one return + :type gather_pages: bool + :param count: Optional. Default: 100. The number of results displayed on one page. + :type count: int + :param page: Optional. The page number for listing the results. + :type page: int + :param order: Optional. "asc" or "desc". Default: "asc". + :type order: str + :returns A list of objects. + :rtype [Namespace] + :raises ApiError: If API fails + :raises Exception: If the API response is somehow malformed. + """ + return requests.get( + url=f"{self.url}/governance/dreps/{drep_id}/updates", + params=self.query_parameters(kwargs), + headers=self.default_headers + ) + + +@list_request_wrapper +def governance_drep_votes(self, drep_id: str, **kwargs): + """ + Return the list of votes by a specific DRep. + + https://docs.blockfrost.io/#tag/Cardano-Governance/paths/~1governance~1dreps~1{drep_id}~1votes/get + + :param drep_id: The DRep ID (Bech32 or hex encoded). + :type drep_id: str + :param return_type: Optional. "object", "json" or "pandas". Default: "object". + :type return_type: str + :param gather_pages: Optional. Default: false. Will collect all pages into one return + :type gather_pages: bool + :param count: Optional. Default: 100. The number of results displayed on one page. + :type count: int + :param page: Optional. The page number for listing the results. + :type page: int + :param order: Optional. "asc" or "desc". Default: "asc". + :type order: str + :returns A list of objects. + :rtype [Namespace] + :raises ApiError: If API fails + :raises Exception: If the API response is somehow malformed. + """ + return requests.get( + url=f"{self.url}/governance/dreps/{drep_id}/votes", + params=self.query_parameters(kwargs), + headers=self.default_headers + ) + + +@list_request_wrapper +def governance_proposals(self, **kwargs): + """ + Return the list of governance proposals. + + https://docs.blockfrost.io/#tag/Cardano-Governance/paths/~1governance~1proposals/get + + :param return_type: Optional. "object", "json" or "pandas". Default: "object". + :type return_type: str + :param gather_pages: Optional. Default: false. Will collect all pages into one return + :type gather_pages: bool + :param count: Optional. Default: 100. The number of results displayed on one page. + :type count: int + :param page: Optional. The page number for listing the results. + :type page: int + :param order: Optional. "asc" or "desc". Default: "asc". + :type order: str + :returns A list of objects. + :rtype [Namespace] + :raises ApiError: If API fails + :raises Exception: If the API response is somehow malformed. + """ + return requests.get( + url=f"{self.url}/governance/proposals", + params=self.query_parameters(kwargs), + headers=self.default_headers + ) + + +@request_wrapper +def governance_proposal(self, tx_hash: str, cert_index: int, **kwargs): + """ + Return information about a specific governance proposal. + + https://docs.blockfrost.io/#tag/Cardano-Governance/paths/~1governance~1proposals~1{tx_hash}~1{cert_index}/get + + :param tx_hash: The transaction hash of the proposal. + :type tx_hash: str + :param cert_index: The index of the certificate within the proposal transaction. + :type cert_index: int + :param return_type: Optional. "object", "json" or "pandas". Default: "object". + :type return_type: str + :returns object. + :rtype: Namespace + :raises ApiError: If API fails + :raises Exception: If the API response is somehow malformed. + """ + return requests.get( + url=f"{self.url}/governance/proposals/{tx_hash}/{cert_index}", + headers=self.default_headers + ) + + +@request_wrapper +def governance_proposal_parameters(self, tx_hash: str, cert_index: int, **kwargs): + """ + Return the parameters of a specific governance proposal. + + https://docs.blockfrost.io/#tag/Cardano-Governance/paths/~1governance~1proposals~1{tx_hash}~1{cert_index}~1parameters/get + + :param tx_hash: The transaction hash of the proposal. + :type tx_hash: str + :param cert_index: The index of the certificate within the proposal transaction. + :type cert_index: int + :param return_type: Optional. "object", "json" or "pandas". Default: "object". + :type return_type: str + :returns object. + :rtype: Namespace + :raises ApiError: If API fails + :raises Exception: If the API response is somehow malformed. + """ + return requests.get( + url=f"{self.url}/governance/proposals/{tx_hash}/{cert_index}/parameters", + headers=self.default_headers + ) + + +@list_request_wrapper +def governance_proposal_withdrawals(self, tx_hash: str, cert_index: int, **kwargs): + """ + Return the withdrawals of a specific governance proposal. + + https://docs.blockfrost.io/#tag/Cardano-Governance/paths/~1governance~1proposals~1{tx_hash}~1{cert_index}~1withdrawals/get + + :param tx_hash: The transaction hash of the proposal. + :type tx_hash: str + :param cert_index: The index of the certificate within the proposal transaction. + :type cert_index: int + :param return_type: Optional. "object", "json" or "pandas". Default: "object". + :type return_type: str + :param gather_pages: Optional. Default: false. Will collect all pages into one return + :type gather_pages: bool + :param count: Optional. Default: 100. The number of results displayed on one page. + :type count: int + :param page: Optional. The page number for listing the results. + :type page: int + :param order: Optional. "asc" or "desc". Default: "asc". + :type order: str + :returns A list of objects. + :rtype [Namespace] + :raises ApiError: If API fails + :raises Exception: If the API response is somehow malformed. + """ + return requests.get( + url=f"{self.url}/governance/proposals/{tx_hash}/{cert_index}/withdrawals", + params=self.query_parameters(kwargs), + headers=self.default_headers + ) + + +@list_request_wrapper +def governance_proposal_votes(self, tx_hash: str, cert_index: int, **kwargs): + """ + Return the votes of a specific governance proposal. + + https://docs.blockfrost.io/#tag/Cardano-Governance/paths/~1governance~1proposals~1{tx_hash}~1{cert_index}~1votes/get + + :param tx_hash: The transaction hash of the proposal. + :type tx_hash: str + :param cert_index: The index of the certificate within the proposal transaction. + :type cert_index: int + :param return_type: Optional. "object", "json" or "pandas". Default: "object". + :type return_type: str + :param gather_pages: Optional. Default: false. Will collect all pages into one return + :type gather_pages: bool + :param count: Optional. Default: 100. The number of results displayed on one page. + :type count: int + :param page: Optional. The page number for listing the results. + :type page: int + :param order: Optional. "asc" or "desc". Default: "asc". + :type order: str + :returns A list of objects. + :rtype [Namespace] + :raises ApiError: If API fails + :raises Exception: If the API response is somehow malformed. + """ + return requests.get( + url=f"{self.url}/governance/proposals/{tx_hash}/{cert_index}/votes", + params=self.query_parameters(kwargs), + headers=self.default_headers + ) + + +@request_wrapper +def governance_proposal_metadata(self, tx_hash: str, cert_index: int, **kwargs): + """ + Return the metadata of a specific governance proposal. + + https://docs.blockfrost.io/#tag/Cardano-Governance/paths/~1governance~1proposals~1{tx_hash}~1{cert_index}~1metadata/get + + :param tx_hash: The transaction hash of the proposal. + :type tx_hash: str + :param cert_index: The index of the certificate within the proposal transaction. + :type cert_index: int + :param return_type: Optional. "object", "json" or "pandas". Default: "object". + :type return_type: str + :returns object. + :rtype: Namespace + :raises ApiError: If API fails + :raises Exception: If the API response is somehow malformed. + """ + return requests.get( + url=f"{self.url}/governance/proposals/{tx_hash}/{cert_index}/metadata", + headers=self.default_headers + ) diff --git a/blockfrost/api/cardano/network.py b/blockfrost/api/cardano/network.py index 56d0899..882f7f4 100644 --- a/blockfrost/api/cardano/network.py +++ b/blockfrost/api/cardano/network.py @@ -1,5 +1,5 @@ import requests -from blockfrost.utils import request_wrapper +from blockfrost.utils import request_wrapper, list_request_wrapper @request_wrapper @@ -20,3 +20,23 @@ def network(self, **kwargs): url=f"{self.url}/network", headers=self.default_headers ) + + +@list_request_wrapper +def network_eras(self, **kwargs): + """ + Return the information about network eras. + + https://docs.blockfrost.io/#tag/Cardano-Network/paths/~1network~1eras/get + + :param return_type: Optional. "object", "json" or "pandas". Default: "object". + :type return_type: str + :returns A list of objects. + :rtype [Namespace] + :raises ApiError: If API fails + :raises Exception: If the API response is somehow malformed. + """ + return requests.get( + url=f"{self.url}/network/eras", + headers=self.default_headers + ) diff --git a/blockfrost/api/cardano/transactions.py b/blockfrost/api/cardano/transactions.py index 0711824..5b49494 100644 --- a/blockfrost/api/cardano/transactions.py +++ b/blockfrost/api/cardano/transactions.py @@ -245,6 +245,50 @@ def transaction_redeemers(self, hash: str, **kwargs): ) +@list_request_wrapper +def transaction_required_signers(self, hash: str, **kwargs): + """ + Obtain the required signers of a specific transaction. + + https://docs.blockfrost.io/#tag/Cardano-Transactions/paths/~1txs~1{hash}~1required_signers/get + + :param hash: Hash of the requested transaction. + :type hash: str + :param return_type: Optional. "object", "json" or "pandas". Default: "object". + :type return_type: str + :returns A list of objects. + :rtype [Namespace] + :raises ApiError: If API fails + :raises Exception: If the API response is somehow malformed. + """ + return requests.get( + url=f"{self.url}/txs/{hash}/required_signers", + headers=self.default_headers + ) + + +@request_wrapper +def transaction_cbor(self, hash: str, **kwargs): + """ + Obtain the transaction in CBOR format. + + https://docs.blockfrost.io/#tag/Cardano-Transactions/paths/~1txs~1{hash}~1cbor/get + + :param hash: Hash of the requested transaction. + :type hash: str + :param return_type: Optional. "object", "json" or "pandas". Default: "object". + :type return_type: str + :returns object. + :rtype: Namespace + :raises ApiError: If API fails + :raises Exception: If the API response is somehow malformed. + """ + return requests.get( + url=f"{self.url}/txs/{hash}/cbor", + headers=self.default_headers + ) + + @request_wrapper def transaction_submit(self, file_path: str, **kwargs): """ diff --git a/setup.py b/setup.py index baa808a..2f41473 100644 --- a/setup.py +++ b/setup.py @@ -8,8 +8,8 @@ setup( name='blockfrost-python', - version='0.6.0', - description='The official Python SDK for Blockfrost API v0.1.37', + version='0.7.0', + description='The official Python SDK for Blockfrost API v0.1.85', long_description=long_description, long_description_content_type='text/markdown', url='https://github.com/blockfrost/blockfrost-python', diff --git a/tests/test_cardano_accounts.py b/tests/test_cardano_accounts.py index fa9d74d..84fa372 100644 --- a/tests/test_cardano_accounts.py +++ b/tests/test_cardano_accounts.py @@ -243,6 +243,61 @@ def test_integration_account_addresses_assets(): assert api.account_addresses_assets(stake_address=stake_address) == [] +def test_account_utxos(requests_mock): + api = BlockFrostApi() + mock_data = [ + { + "address": "addr1qx2kd28nq8ac5prwg32hhvudlwggpgfp8utlyqxu6wqgz62f79qsdmm5dsknt9ecr5w468r9ey0fxwkdrwh08ly3tu9sy0f4qd", + "tx_hash": "39a7a284c2a0c3a6d5f594f2bd150c2f13c6b2a1f8a1e3d5c0f3b2a1d0e9f8a7", + "output_index": 0, + "amount": [ + { + "unit": "lovelace", + "quantity": "42000000" + } + ], + "block": "7eb8e27d18686c7db9a18f8bbcfe34e3fed6e047afaa2d969904d15e934847e6", + "data_hash": None, + "inline_datum": None, + "reference_script_hash": None + } + ] + requests_mock.get(f"{api.url}/accounts/{stake_address}/utxos", json=mock_data) + assert api.account_utxos(stake_address=stake_address) == convert_json_to_object(mock_data) + + +def test_integration_account_utxos(): + if os.getenv('BLOCKFROST_PROJECT_ID_MAINNET'): + api = BlockFrostApi(project_id=os.getenv('BLOCKFROST_PROJECT_ID_MAINNET')) + api.account_utxos(stake_address=stake_address) + + +def test_account_transactions(requests_mock): + api = BlockFrostApi() + mock_data = [ + { + "tx_hash": "8788591983aa73981fc92d6cddbbe643959f5a784e84b8bee0db15823f575a5b", + "tx_index": 6, + "block_height": 69, + "block_time": 1635505891 + }, + { + "tx_hash": "52e748c4dec58b687b90b0b40d383b9fe1f24c1a833b7395cdf07dd67859f46f", + "tx_index": 9, + "block_height": 4547, + "block_time": 1635505987 + } + ] + requests_mock.get(f"{api.url}/accounts/{stake_address}/transactions", json=mock_data) + assert api.account_transactions(stake_address=stake_address) == convert_json_to_object(mock_data) + + +def test_integration_account_transactions(): + if os.getenv('BLOCKFROST_PROJECT_ID_MAINNET'): + api = BlockFrostApi(project_id=os.getenv('BLOCKFROST_PROJECT_ID_MAINNET')) + api.account_transactions(stake_address=stake_address) + + def test_account_addresses_total(requests_mock): api = BlockFrostApi() mock_data = { diff --git a/tests/test_cardano_blocks.py b/tests/test_cardano_blocks.py index 5974634..a06562e 100644 --- a/tests/test_cardano_blocks.py +++ b/tests/test_cardano_blocks.py @@ -223,6 +223,47 @@ def test_integration_block_transactions(): assert api.block_transactions(hash_or_number=hash) +def test_block_latest_transactions_cbor(requests_mock): + api = BlockFrostApi() + mock_data = [ + { + "tx_hash": "8788591983aa73981fc92d6cddbbe643959f5a784e84b8bee0db15823f575a5b", + "cbor": "84a8008282582098483df1666d5af7c4aca7ef28f112d225b81a34ef20b0ad4775bab0e41dbb30" + }, + { + "tx_hash": "4eef6bb7755d8afbeac526b799f3e32a624691d166657e9d862aaeb66682c036", + "cbor": "84a8008282582098483df1666d5af7c4aca7ef28f112d225b81a34ef20b0ad4775bab0e41dbb31" + } + ] + requests_mock.get(f"{api.url}/blocks/latest/txs/cbor", json=mock_data) + assert api.block_latest_transactions_cbor() == convert_json_to_object(mock_data) + + +def test_integration_block_latest_transactions_cbor(): + if os.getenv('BLOCKFROST_PROJECT_ID_MAINNET'): + api = BlockFrostApi(project_id=os.getenv('BLOCKFROST_PROJECT_ID_MAINNET')) + assert (api.block_latest_transactions_cbor() or + api.block_latest_transactions_cbor() == []) + + +def test_block_transactions_cbor(requests_mock): + api = BlockFrostApi() + mock_data = [ + { + "tx_hash": "8788591983aa73981fc92d6cddbbe643959f5a784e84b8bee0db15823f575a5b", + "cbor": "84a8008282582098483df1666d5af7c4aca7ef28f112d225b81a34ef20b0ad4775bab0e41dbb30" + } + ] + requests_mock.get(f"{api.url}/blocks/{hash}/txs/cbor", json=mock_data) + assert api.block_transactions_cbor(hash_or_number=hash) == convert_json_to_object(mock_data) + + +def test_integration_block_transactions_cbor(): + if os.getenv('BLOCKFROST_PROJECT_ID_MAINNET'): + api = BlockFrostApi(project_id=os.getenv('BLOCKFROST_PROJECT_ID_MAINNET')) + assert api.block_transactions_cbor(hash_or_number=hash) + + def test_blocks_blocks_addresses(requests_mock): api = BlockFrostApi() mock_data = [ diff --git a/tests/test_cardano_governance.py b/tests/test_cardano_governance.py new file mode 100644 index 0000000..fd88f61 --- /dev/null +++ b/tests/test_cardano_governance.py @@ -0,0 +1,268 @@ +import os +from blockfrost import BlockFrostApi, ApiError +from blockfrost.utils import convert_json_to_object + +drep_id = 'drep1mvdu8slennngja7w4un6knwezufra70887zuxpprd64jxfveahn' +tx_hash = 'f5ca33220afcc0340d1fa3cba9b0e1f3c3c6e1eab57d688ed700ae56bbce9170' +cert_index = 0 + + +def test_governance_dreps(requests_mock): + api = BlockFrostApi() + mock_data = [ + { + "drep_id": drep_id, + "hex": "db1bc3c3f99fd22aa1e09f1c34c0ada5795c5e1f32f2652a246f73db" + }, + { + "drep_id": "drep1cxayn4fgy27yaucvhamsvqj3v6835mh3tjjx6t8rm65ndd3lcjm", + "hex": "c1d932a9a0457a13bc6195f7600c919d1e3476f715c86d2c8f7a9a66" + } + ] + requests_mock.get(f"{api.url}/governance/dreps", json=mock_data) + assert api.governance_dreps() == convert_json_to_object(mock_data) + + +def test_integration_governance_dreps(): + if os.getenv('BLOCKFROST_PROJECT_ID_MAINNET'): + api = BlockFrostApi(project_id=os.getenv('BLOCKFROST_PROJECT_ID_MAINNET')) + api.governance_dreps() + + +def test_governance_drep(requests_mock): + api = BlockFrostApi() + mock_data = { + "drep_id": drep_id, + "hex": "db1bc3c3f99fd22aa1e09f1c34c0ada5795c5e1f32f2652a246f73db", + "amount": "2000000", + "active": True, + "active_epoch": 420, + "has_script": False, + "retired": False, + "expired": False, + "last_active_epoch": 500 + } + requests_mock.get(f"{api.url}/governance/dreps/{drep_id}", json=mock_data) + assert api.governance_drep(drep_id=drep_id) == convert_json_to_object(mock_data) + + +def test_integration_governance_drep(): + if os.getenv('BLOCKFROST_PROJECT_ID_MAINNET'): + api = BlockFrostApi(project_id=os.getenv('BLOCKFROST_PROJECT_ID_MAINNET')) + api.governance_drep(drep_id=drep_id) + + +def test_governance_drep_delegators(requests_mock): + api = BlockFrostApi() + mock_data = [ + { + "address": "stake1ux3g2c9dx2nhhehyrezyxpkstartcqmu9hk63qgfkccw5rqttygt7", + "amount": "1029328" + } + ] + requests_mock.get(f"{api.url}/governance/dreps/{drep_id}/delegators", json=mock_data) + assert api.governance_drep_delegators(drep_id=drep_id) == convert_json_to_object(mock_data) + + +def test_integration_governance_drep_delegators(): + if os.getenv('BLOCKFROST_PROJECT_ID_MAINNET'): + api = BlockFrostApi(project_id=os.getenv('BLOCKFROST_PROJECT_ID_MAINNET')) + api.governance_drep_delegators(drep_id=drep_id) + + +def test_governance_drep_metadata(requests_mock): + api = BlockFrostApi() + mock_data = { + "drep_id": drep_id, + "hex": "db1bc3c3f99fd22aa1e09f1c34c0ada5795c5e1f32f2652a246f73db", + "url": "https://example.com/drep-metadata.json", + "hash": "a14a5ad4a83b1c8b04c5175f0a16b4a2d8b1e5a08c7b6d1e2f3c4d5e6f7a8b9", + "json_metadata": None, + "bytes": "\\xa100" + } + requests_mock.get(f"{api.url}/governance/dreps/{drep_id}/metadata", json=mock_data) + assert api.governance_drep_metadata(drep_id=drep_id) == convert_json_to_object(mock_data) + + +def test_integration_governance_drep_metadata(): + if os.getenv('BLOCKFROST_PROJECT_ID_MAINNET'): + api = BlockFrostApi(project_id=os.getenv('BLOCKFROST_PROJECT_ID_MAINNET')) + api.governance_drep_metadata(drep_id=drep_id) + + +def test_governance_drep_updates(requests_mock): + api = BlockFrostApi() + mock_data = [ + { + "tx_hash": "f5ca33220afcc0340d1fa3cba9b0e1f3c3c6e1eab57d688ed700ae56bbce9170", + "cert_index": 0, + "action": "registered" + }, + { + "tx_hash": "0e98ac6a8b1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7", + "cert_index": 0, + "action": "updated" + } + ] + requests_mock.get(f"{api.url}/governance/dreps/{drep_id}/updates", json=mock_data) + assert api.governance_drep_updates(drep_id=drep_id) == convert_json_to_object(mock_data) + + +def test_integration_governance_drep_updates(): + if os.getenv('BLOCKFROST_PROJECT_ID_MAINNET'): + api = BlockFrostApi(project_id=os.getenv('BLOCKFROST_PROJECT_ID_MAINNET')) + api.governance_drep_updates(drep_id=drep_id) + + +def test_governance_drep_votes(requests_mock): + api = BlockFrostApi() + mock_data = [ + { + "tx_hash": "f5ca33220afcc0340d1fa3cba9b0e1f3c3c6e1eab57d688ed700ae56bbce9170", + "cert_index": 0, + "vote": "yes", + "proposal_tx_hash": "b302de9b2ed2bca4d65e40cae4ae6d0b8e7c0f1a2b3c4d5e6f7a8b9c0d1e2f3a", + "proposal_cert_index": 0 + } + ] + requests_mock.get(f"{api.url}/governance/dreps/{drep_id}/votes", json=mock_data) + assert api.governance_drep_votes(drep_id=drep_id) == convert_json_to_object(mock_data) + + +def test_integration_governance_drep_votes(): + if os.getenv('BLOCKFROST_PROJECT_ID_MAINNET'): + api = BlockFrostApi(project_id=os.getenv('BLOCKFROST_PROJECT_ID_MAINNET')) + api.governance_drep_votes(drep_id=drep_id) + + +def test_governance_proposals(requests_mock): + api = BlockFrostApi() + mock_data = [ + { + "tx_hash": tx_hash, + "cert_index": cert_index, + "governance_type": "treasury_withdrawals", + "deposit": "100000000000", + "return_address": "stake1ux3g2c9dx2nhhehyrezyxpkstartcqmu9hk63qgfkccw5rqttygt7", + "governance_description": None, + "ratified_epoch": None, + "enacted_epoch": None, + "dropped_epoch": None, + "expired_epoch": None, + "expiration": 550 + } + ] + requests_mock.get(f"{api.url}/governance/proposals", json=mock_data) + assert api.governance_proposals() == convert_json_to_object(mock_data) + + +def test_integration_governance_proposals(): + if os.getenv('BLOCKFROST_PROJECT_ID_MAINNET'): + api = BlockFrostApi(project_id=os.getenv('BLOCKFROST_PROJECT_ID_MAINNET')) + api.governance_proposals() + + +def test_governance_proposal(requests_mock): + api = BlockFrostApi() + mock_data = { + "tx_hash": tx_hash, + "cert_index": cert_index, + "governance_type": "treasury_withdrawals", + "deposit": "100000000000", + "return_address": "stake1ux3g2c9dx2nhhehyrezyxpkstartcqmu9hk63qgfkccw5rqttygt7", + "governance_description": None, + "ratified_epoch": None, + "enacted_epoch": None, + "dropped_epoch": None, + "expired_epoch": None, + "expiration": 550 + } + requests_mock.get(f"{api.url}/governance/proposals/{tx_hash}/{cert_index}", json=mock_data) + assert api.governance_proposal(tx_hash=tx_hash, cert_index=cert_index) == convert_json_to_object(mock_data) + + +def test_integration_governance_proposal(): + if os.getenv('BLOCKFROST_PROJECT_ID_MAINNET'): + api = BlockFrostApi(project_id=os.getenv('BLOCKFROST_PROJECT_ID_MAINNET')) + api.governance_proposal(tx_hash=tx_hash, cert_index=cert_index) + + +def test_governance_proposal_parameters(requests_mock): + api = BlockFrostApi() + mock_data = { + "tx_hash": tx_hash, + "cert_index": cert_index, + "parameters": { + "min_fee_a": 44, + "min_fee_b": 155381, + "key_deposit": "2000000", + "pool_deposit": "500000000" + } + } + requests_mock.get(f"{api.url}/governance/proposals/{tx_hash}/{cert_index}/parameters", json=mock_data) + assert api.governance_proposal_parameters(tx_hash=tx_hash, cert_index=cert_index) == convert_json_to_object(mock_data) + + +def test_integration_governance_proposal_parameters(): + if os.getenv('BLOCKFROST_PROJECT_ID_MAINNET'): + api = BlockFrostApi(project_id=os.getenv('BLOCKFROST_PROJECT_ID_MAINNET')) + api.governance_proposal_parameters(tx_hash=tx_hash, cert_index=cert_index) + + +def test_governance_proposal_withdrawals(requests_mock): + api = BlockFrostApi() + mock_data = [ + { + "stake_address": "stake1ux3g2c9dx2nhhehyrezyxpkstartcqmu9hk63qgfkccw5rqttygt7", + "amount": "454541212442" + } + ] + requests_mock.get(f"{api.url}/governance/proposals/{tx_hash}/{cert_index}/withdrawals", json=mock_data) + assert api.governance_proposal_withdrawals(tx_hash=tx_hash, cert_index=cert_index) == convert_json_to_object(mock_data) + + +def test_integration_governance_proposal_withdrawals(): + if os.getenv('BLOCKFROST_PROJECT_ID_MAINNET'): + api = BlockFrostApi(project_id=os.getenv('BLOCKFROST_PROJECT_ID_MAINNET')) + api.governance_proposal_withdrawals(tx_hash=tx_hash, cert_index=cert_index) + + +def test_governance_proposal_votes(requests_mock): + api = BlockFrostApi() + mock_data = [ + { + "tx_hash": "f5ca33220afcc0340d1fa3cba9b0e1f3c3c6e1eab57d688ed700ae56bbce9170", + "cert_index": 0, + "voter": "drep1mvdu8slennngja7w4un6knwezufra70887zuxpprd64jxfveahn", + "voter_role": "drep", + "vote": "yes" + } + ] + requests_mock.get(f"{api.url}/governance/proposals/{tx_hash}/{cert_index}/votes", json=mock_data) + assert api.governance_proposal_votes(tx_hash=tx_hash, cert_index=cert_index) == convert_json_to_object(mock_data) + + +def test_integration_governance_proposal_votes(): + if os.getenv('BLOCKFROST_PROJECT_ID_MAINNET'): + api = BlockFrostApi(project_id=os.getenv('BLOCKFROST_PROJECT_ID_MAINNET')) + api.governance_proposal_votes(tx_hash=tx_hash, cert_index=cert_index) + + +def test_governance_proposal_metadata(requests_mock): + api = BlockFrostApi() + mock_data = { + "tx_hash": tx_hash, + "cert_index": cert_index, + "url": "https://example.com/proposal-metadata.json", + "hash": "a14a5ad4a83b1c8b04c5175f0a16b4a2d8b1e5a08c7b6d1e2f3c4d5e6f7a8b9", + "json_metadata": None, + "bytes": "\\xa100" + } + requests_mock.get(f"{api.url}/governance/proposals/{tx_hash}/{cert_index}/metadata", json=mock_data) + assert api.governance_proposal_metadata(tx_hash=tx_hash, cert_index=cert_index) == convert_json_to_object(mock_data) + + +def test_integration_governance_proposal_metadata(): + if os.getenv('BLOCKFROST_PROJECT_ID_MAINNET'): + api = BlockFrostApi(project_id=os.getenv('BLOCKFROST_PROJECT_ID_MAINNET')) + api.governance_proposal_metadata(tx_hash=tx_hash, cert_index=cert_index) diff --git a/tests/test_cardano_network.py b/tests/test_cardano_network.py index fde8fa1..7684daa 100644 --- a/tests/test_cardano_network.py +++ b/tests/test_cardano_network.py @@ -27,3 +27,51 @@ def test_integration_network(): if os.getenv('BLOCKFROST_PROJECT_ID_MAINNET'): api = BlockFrostApi(project_id=os.getenv('BLOCKFROST_PROJECT_ID_MAINNET')) assert api.network() + + +def test_network_eras(requests_mock): + api = BlockFrostApi() + mock_data = [ + { + "start": { + "time": 0, + "slot": 0, + "epoch": 0 + }, + "end": { + "time": 89856000, + "slot": 4492800, + "epoch": 208 + }, + "parameters": { + "epoch_length": 21600, + "slot_length": 20, + "safe_zone": 4320 + } + }, + { + "start": { + "time": 89856000, + "slot": 4492800, + "epoch": 208 + }, + "end": { + "time": 101952000, + "slot": 16588800, + "epoch": 236 + }, + "parameters": { + "epoch_length": 432000, + "slot_length": 1, + "safe_zone": 129600 + } + } + ] + requests_mock.get(f"{api.url}/network/eras", json=mock_data) + assert api.network_eras() == convert_json_to_object(mock_data) + + +def test_integration_network_eras(): + if os.getenv('BLOCKFROST_PROJECT_ID_MAINNET'): + api = BlockFrostApi(project_id=os.getenv('BLOCKFROST_PROJECT_ID_MAINNET')) + assert api.network_eras() diff --git a/tests/test_cardano_transactions.py b/tests/test_cardano_transactions.py index 5113e0b..5dc2d6c 100644 --- a/tests/test_cardano_transactions.py +++ b/tests/test_cardano_transactions.py @@ -361,6 +361,42 @@ def test_integration_transaction_redeemers(): assert api.transaction_redeemers(hash=hash) == [] +def test_transaction_required_signers(requests_mock): + api = BlockFrostApi() + mock_data = [ + { + "address": "ec26b89af41bef0f7585353831cb5da42b5b37185e0c8a526143b824" + } + ] + requests_mock.get(f"{api.url}/txs/{hash}/required_signers", json=mock_data) + assert api.transaction_required_signers( + hash=hash) == convert_json_to_object(mock_data) + + +def test_integration_transaction_required_signers(): + if os.getenv('BLOCKFROST_PROJECT_ID_MAINNET'): + api = BlockFrostApi(project_id=os.getenv( + 'BLOCKFROST_PROJECT_ID_MAINNET')) + assert api.transaction_required_signers(hash=hash) == [] + + +def test_transaction_cbor(requests_mock): + api = BlockFrostApi() + mock_data = { + "cbor": "84a8008282582098483df1666d5af7c4aca7ef28f112d225b81a34ef20b0ad4775bab0e41dbb30" + } + requests_mock.get(f"{api.url}/txs/{hash}/cbor", json=mock_data) + assert api.transaction_cbor( + hash=hash) == convert_json_to_object(mock_data) + + +def test_integration_transaction_cbor(): + if os.getenv('BLOCKFROST_PROJECT_ID_MAINNET'): + api = BlockFrostApi(project_id=os.getenv( + 'BLOCKFROST_PROJECT_ID_MAINNET')) + assert api.transaction_cbor(hash=hash) + + def test_transaction_submit(requests_mock): api = BlockFrostApi() mock_data = hash From ed5624c4644b36f71e94a5ce097a9d02ef621bd0 Mon Sep 17 00:00:00 2001 From: Alex Karpov Date: Mon, 23 Feb 2026 11:44:07 +0300 Subject: [PATCH 2/4] Apply suggestion from @Copilot Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- tests/test_cardano_blocks.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_cardano_blocks.py b/tests/test_cardano_blocks.py index a06562e..1857d46 100644 --- a/tests/test_cardano_blocks.py +++ b/tests/test_cardano_blocks.py @@ -242,8 +242,8 @@ def test_block_latest_transactions_cbor(requests_mock): def test_integration_block_latest_transactions_cbor(): if os.getenv('BLOCKFROST_PROJECT_ID_MAINNET'): api = BlockFrostApi(project_id=os.getenv('BLOCKFROST_PROJECT_ID_MAINNET')) - assert (api.block_latest_transactions_cbor() or - api.block_latest_transactions_cbor() == []) + result = api.block_latest_transactions_cbor() + assert result or result == [] def test_block_transactions_cbor(requests_mock): From 6aba16f1259e21b467f77579e5535b2559c0228f Mon Sep 17 00:00:00 2001 From: Alex Karpov Date: Mon, 23 Feb 2026 12:29:02 +0300 Subject: [PATCH 3/4] Fix failing integration tests for governance and transaction endpoints Update governance test fixtures to use valid mainnet DRep and proposal IDs that exist on-chain. Fix transaction submit test to check status_code instead of removed error string. Update evaluate_cbor test to match new Ogmios response structure with ScriptFailures nesting. Co-Authored-By: Claude Opus 4.6 --- tests/test_cardano_governance.py | 8 +++++--- tests/test_cardano_transactions.py | 5 +++-- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/tests/test_cardano_governance.py b/tests/test_cardano_governance.py index fd88f61..5be052e 100644 --- a/tests/test_cardano_governance.py +++ b/tests/test_cardano_governance.py @@ -2,9 +2,11 @@ from blockfrost import BlockFrostApi, ApiError from blockfrost.utils import convert_json_to_object -drep_id = 'drep1mvdu8slennngja7w4un6knwezufra70887zuxpprd64jxfveahn' -tx_hash = 'f5ca33220afcc0340d1fa3cba9b0e1f3c3c6e1eab57d688ed700ae56bbce9170' +drep_id = 'drep1yfaaaaa270yjt6tu5skndugekprf5ykv5jshanl0c6gqx5qpstskf' +tx_hash = '51f495aa23f4b3b3aa90afde4a0e67823bb7ac4ac65f5ffbb138373b863f2f74' cert_index = 0 +# proposal with metadata (treasury_withdrawals type) +metadata_tx_hash = '60ed6ab43c840ff888a8af30a1ed27b41e9f4a91a89822b2b63d1bfc52aeec45' def test_governance_dreps(requests_mock): @@ -265,4 +267,4 @@ def test_governance_proposal_metadata(requests_mock): def test_integration_governance_proposal_metadata(): if os.getenv('BLOCKFROST_PROJECT_ID_MAINNET'): api = BlockFrostApi(project_id=os.getenv('BLOCKFROST_PROJECT_ID_MAINNET')) - api.governance_proposal_metadata(tx_hash=tx_hash, cert_index=cert_index) + api.governance_proposal_metadata(tx_hash=metadata_tx_hash, cert_index=cert_index) diff --git a/tests/test_cardano_transactions.py b/tests/test_cardano_transactions.py index 5dc2d6c..1b4c461 100644 --- a/tests/test_cardano_transactions.py +++ b/tests/test_cardano_transactions.py @@ -412,7 +412,7 @@ def test_integration_transaction_submit_cbor(): with pytest.raises(ApiError) as exc_info: api.transaction_submit_cbor(tx_cbor=tx_cbor) - assert exc_info.value.message.find("transaction submit error") > -1 + assert exc_info.value.status_code == 400 # Make sure that the tx cbor was correctly passed assert exc_info.value.message.find( "54bcb22a31a100080ffbf7edbac538b1517d99fa85ec77bfac089ab8249e2708") > -1 @@ -443,7 +443,8 @@ def test_integration_transaction_evaluate_cbor(): # } # Response with an ogmios error about missing input - assert result.result.EvaluationFailure.CannotCreateEvaluationContext.reason.find( + script_failure = getattr(result.result.EvaluationFailure.ScriptFailures, 'spend:0') + assert script_failure.CannotCreateEvaluationContext.reason.find( 'Unknown transaction input') > -1 From 94ca20a4170690321f62032544a1a370aba44bb2 Mon Sep 17 00:00:00 2001 From: Alex Karpov Date: Mon, 23 Feb 2026 12:32:18 +0300 Subject: [PATCH 4/4] Fix typo: rest-requirements.txt to test-requirements.txt --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index c04bb20..5ff3002 100644 --- a/README.md +++ b/README.md @@ -175,7 +175,7 @@ Install dependencies ``` pip install -r requirements.txt -pip install -r rest-requirements.txt +pip install -r test-requirements.txt ``` Install package