Skip to content

Commit e638cd9

Browse files
slowbackspaceclaude
andcommitted
feat: add new endpoints for Blockfrost API v0.1.86
Add pool_votes, governance proposal endpoints by gov_action_id (CIP-0129), and update API version reference in setup.py. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent cc5bf6f commit e638cd9

6 files changed

Lines changed: 294 additions & 3 deletions

File tree

blockfrost/api/__init__.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,8 @@ def root(self, **kwargs):
121121
pool_relays, \
122122
pool_delegators, \
123123
pool_blocks, \
124-
pool_updates
124+
pool_updates, \
125+
pool_votes
125126
from .cardano.transactions import \
126127
transaction, \
127128
transaction_utxos, \
@@ -161,6 +162,11 @@ def root(self, **kwargs):
161162
governance_proposal_parameters, \
162163
governance_proposal_withdrawals, \
163164
governance_proposal_votes, \
164-
governance_proposal_metadata
165+
governance_proposal_metadata, \
166+
governance_proposal_by_gov_action_id, \
167+
governance_proposal_parameters_by_gov_action_id, \
168+
governance_proposal_withdrawals_by_gov_action_id, \
169+
governance_proposal_votes_by_gov_action_id, \
170+
governance_proposal_metadata_by_gov_action_id
165171
from .cardano.utils import \
166172
utils_addresses_xpub

blockfrost/api/cardano/governance.py

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -333,3 +333,131 @@ def governance_proposal_metadata(self, tx_hash: str, cert_index: int, **kwargs):
333333
url=f"{self.url}/governance/proposals/{tx_hash}/{cert_index}/metadata",
334334
headers=self.default_headers
335335
)
336+
337+
338+
@request_wrapper
339+
def governance_proposal_by_gov_action_id(self, gov_action_id: str, **kwargs):
340+
"""
341+
Return information about a specific governance proposal by GovActionID.
342+
343+
https://docs.blockfrost.io/#tag/cardano--governance/GET/governance/proposals/{gov_action_id}
344+
345+
:param gov_action_id: Governance Action Identifier (CIP-0129).
346+
:type gov_action_id: str
347+
:param return_type: Optional. "object", "json" or "pandas". Default: "object".
348+
:type return_type: str
349+
:returns object.
350+
:rtype: Namespace
351+
:raises ApiError: If API fails
352+
:raises Exception: If the API response is somehow malformed.
353+
"""
354+
return requests.get(
355+
url=f"{self.url}/governance/proposals/{gov_action_id}",
356+
headers=self.default_headers
357+
)
358+
359+
360+
@request_wrapper
361+
def governance_proposal_parameters_by_gov_action_id(self, gov_action_id: str, **kwargs):
362+
"""
363+
Return the parameters of a specific governance proposal by GovActionID.
364+
365+
https://docs.blockfrost.io/#tag/cardano--governance/GET/governance/proposals/{gov_action_id}/parameters
366+
367+
:param gov_action_id: Governance Action Identifier (CIP-0129).
368+
:type gov_action_id: str
369+
:param return_type: Optional. "object", "json" or "pandas". Default: "object".
370+
:type return_type: str
371+
:returns object.
372+
:rtype: Namespace
373+
:raises ApiError: If API fails
374+
:raises Exception: If the API response is somehow malformed.
375+
"""
376+
return requests.get(
377+
url=f"{self.url}/governance/proposals/{gov_action_id}/parameters",
378+
headers=self.default_headers
379+
)
380+
381+
382+
@list_request_wrapper
383+
def governance_proposal_withdrawals_by_gov_action_id(self, gov_action_id: str, **kwargs):
384+
"""
385+
Return the withdrawals of a specific governance proposal by GovActionID.
386+
387+
https://docs.blockfrost.io/#tag/cardano--governance/GET/governance/proposals/{gov_action_id}/withdrawals
388+
389+
:param gov_action_id: Governance Action Identifier (CIP-0129).
390+
:type gov_action_id: str
391+
:param return_type: Optional. "object", "json" or "pandas". Default: "object".
392+
:type return_type: str
393+
:param gather_pages: Optional. Default: false. Will collect all pages into one return
394+
:type gather_pages: bool
395+
:param count: Optional. Default: 100. The number of results displayed on one page.
396+
:type count: int
397+
:param page: Optional. The page number for listing the results.
398+
:type page: int
399+
:param order: Optional. "asc" or "desc". Default: "asc".
400+
:type order: str
401+
:returns A list of objects.
402+
:rtype [Namespace]
403+
:raises ApiError: If API fails
404+
:raises Exception: If the API response is somehow malformed.
405+
"""
406+
return requests.get(
407+
url=f"{self.url}/governance/proposals/{gov_action_id}/withdrawals",
408+
params=self.query_parameters(kwargs),
409+
headers=self.default_headers
410+
)
411+
412+
413+
@list_request_wrapper
414+
def governance_proposal_votes_by_gov_action_id(self, gov_action_id: str, **kwargs):
415+
"""
416+
Return the votes of a specific governance proposal by GovActionID.
417+
418+
https://docs.blockfrost.io/#tag/cardano--governance/GET/governance/proposals/{gov_action_id}/votes
419+
420+
:param gov_action_id: Governance Action Identifier (CIP-0129).
421+
:type gov_action_id: str
422+
:param return_type: Optional. "object", "json" or "pandas". Default: "object".
423+
:type return_type: str
424+
:param gather_pages: Optional. Default: false. Will collect all pages into one return
425+
:type gather_pages: bool
426+
:param count: Optional. Default: 100. The number of results displayed on one page.
427+
:type count: int
428+
:param page: Optional. The page number for listing the results.
429+
:type page: int
430+
:param order: Optional. "asc" or "desc". Default: "asc".
431+
:type order: str
432+
:returns A list of objects.
433+
:rtype [Namespace]
434+
:raises ApiError: If API fails
435+
:raises Exception: If the API response is somehow malformed.
436+
"""
437+
return requests.get(
438+
url=f"{self.url}/governance/proposals/{gov_action_id}/votes",
439+
params=self.query_parameters(kwargs),
440+
headers=self.default_headers
441+
)
442+
443+
444+
@request_wrapper
445+
def governance_proposal_metadata_by_gov_action_id(self, gov_action_id: str, **kwargs):
446+
"""
447+
Return the metadata of a specific governance proposal by GovActionID.
448+
449+
https://docs.blockfrost.io/#tag/cardano--governance/GET/governance/proposals/{gov_action_id}/metadata
450+
451+
:param gov_action_id: Governance Action Identifier (CIP-0129).
452+
:type gov_action_id: str
453+
:param return_type: Optional. "object", "json" or "pandas". Default: "object".
454+
:type return_type: str
455+
:returns object.
456+
:rtype: Namespace
457+
:raises ApiError: If API fails
458+
:raises Exception: If the API response is somehow malformed.
459+
"""
460+
return requests.get(
461+
url=f"{self.url}/governance/proposals/{gov_action_id}/metadata",
462+
headers=self.default_headers
463+
)

blockfrost/api/cardano/pools.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -284,3 +284,32 @@ def pool_updates(self, pool_id: str, **kwargs):
284284
params=self.query_parameters(kwargs),
285285
headers=self.default_headers
286286
)
287+
288+
289+
@list_request_wrapper
290+
def pool_votes(self, pool_id: str, **kwargs):
291+
"""
292+
History of stake pool votes.
293+
294+
https://docs.blockfrost.io/#tag/cardano--pools/GET/pools/{pool_id}/votes
295+
296+
:param pool_id: Bech32 or hexadecimal pool ID.
297+
:type pool_id: str
298+
:param gather_pages: Optional. Default: false. Will collect all pages into one return
299+
:type gather_pages: bool
300+
:param count: Optional. Default: 100. The number of results displayed on one page.
301+
:type count: int
302+
:param page: Optional. The page number for listing the results.
303+
:type page: int
304+
:param order: Optional. "asc" or "desc". Default: "asc".
305+
:type order: str
306+
:returns A list of objects.
307+
:rtype [Namespace]
308+
:raises ApiError: If API fails
309+
:raises Exception: If the API response is somehow malformed.
310+
"""
311+
return requests.get(
312+
url=f"{self.url}/pools/{pool_id}/votes",
313+
params=self.query_parameters(kwargs),
314+
headers=self.default_headers
315+
)

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
setup(
1010
name='blockfrost-python',
1111
version='0.7.0',
12-
description='The official Python SDK for Blockfrost API v0.1.85',
12+
description='The official Python SDK for Blockfrost API v0.1.86',
1313
long_description=long_description,
1414
long_description_content_type='text/markdown',
1515
url='https://github.com/blockfrost/blockfrost-python',

tests/test_cardano_governance.py

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
cert_index = 0
88
# proposal with metadata (treasury_withdrawals type)
99
metadata_tx_hash = '60ed6ab43c840ff888a8af30a1ed27b41e9f4a91a89822b2b63d1bfc52aeec45'
10+
gov_action_id = 'gov_action1zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zygsq6dmejn'
1011

1112

1213
def test_governance_dreps(requests_mock):
@@ -268,3 +269,109 @@ def test_integration_governance_proposal_metadata():
268269
if os.getenv('BLOCKFROST_PROJECT_ID_MAINNET'):
269270
api = BlockFrostApi(project_id=os.getenv('BLOCKFROST_PROJECT_ID_MAINNET'))
270271
api.governance_proposal_metadata(tx_hash=metadata_tx_hash, cert_index=cert_index)
272+
273+
274+
def test_governance_proposal_by_gov_action_id(requests_mock):
275+
api = BlockFrostApi()
276+
mock_data = {
277+
"tx_hash": tx_hash,
278+
"cert_index": cert_index,
279+
"governance_type": "treasury_withdrawals",
280+
"deposit": "100000000000",
281+
"return_address": "stake1ux3g2c9dx2nhhehyrezyxpkstartcqmu9hk63qgfkccw5rqttygt7",
282+
"governance_description": None,
283+
"ratified_epoch": None,
284+
"enacted_epoch": None,
285+
"dropped_epoch": None,
286+
"expired_epoch": None,
287+
"expiration": 550
288+
}
289+
requests_mock.get(f"{api.url}/governance/proposals/{gov_action_id}", json=mock_data)
290+
assert api.governance_proposal_by_gov_action_id(gov_action_id=gov_action_id) == convert_json_to_object(mock_data)
291+
292+
293+
def test_integration_governance_proposal_by_gov_action_id():
294+
if os.getenv('BLOCKFROST_PROJECT_ID_MAINNET'):
295+
api = BlockFrostApi(project_id=os.getenv('BLOCKFROST_PROJECT_ID_MAINNET'))
296+
api.governance_proposal_by_gov_action_id(gov_action_id=gov_action_id)
297+
298+
299+
def test_governance_proposal_parameters_by_gov_action_id(requests_mock):
300+
api = BlockFrostApi()
301+
mock_data = {
302+
"tx_hash": tx_hash,
303+
"cert_index": cert_index,
304+
"parameters": {
305+
"min_fee_a": 44,
306+
"min_fee_b": 155381,
307+
"key_deposit": "2000000",
308+
"pool_deposit": "500000000"
309+
}
310+
}
311+
requests_mock.get(f"{api.url}/governance/proposals/{gov_action_id}/parameters", json=mock_data)
312+
assert api.governance_proposal_parameters_by_gov_action_id(gov_action_id=gov_action_id) == convert_json_to_object(mock_data)
313+
314+
315+
def test_integration_governance_proposal_parameters_by_gov_action_id():
316+
if os.getenv('BLOCKFROST_PROJECT_ID_MAINNET'):
317+
api = BlockFrostApi(project_id=os.getenv('BLOCKFROST_PROJECT_ID_MAINNET'))
318+
api.governance_proposal_parameters_by_gov_action_id(gov_action_id=gov_action_id)
319+
320+
321+
def test_governance_proposal_withdrawals_by_gov_action_id(requests_mock):
322+
api = BlockFrostApi()
323+
mock_data = [
324+
{
325+
"stake_address": "stake1ux3g2c9dx2nhhehyrezyxpkstartcqmu9hk63qgfkccw5rqttygt7",
326+
"amount": "454541212442"
327+
}
328+
]
329+
requests_mock.get(f"{api.url}/governance/proposals/{gov_action_id}/withdrawals", json=mock_data)
330+
assert api.governance_proposal_withdrawals_by_gov_action_id(gov_action_id=gov_action_id) == convert_json_to_object(mock_data)
331+
332+
333+
def test_integration_governance_proposal_withdrawals_by_gov_action_id():
334+
if os.getenv('BLOCKFROST_PROJECT_ID_MAINNET'):
335+
api = BlockFrostApi(project_id=os.getenv('BLOCKFROST_PROJECT_ID_MAINNET'))
336+
api.governance_proposal_withdrawals_by_gov_action_id(gov_action_id=gov_action_id)
337+
338+
339+
def test_governance_proposal_votes_by_gov_action_id(requests_mock):
340+
api = BlockFrostApi()
341+
mock_data = [
342+
{
343+
"tx_hash": "f5ca33220afcc0340d1fa3cba9b0e1f3c3c6e1eab57d688ed700ae56bbce9170",
344+
"cert_index": 0,
345+
"voter": "drep1mvdu8slennngja7w4un6knwezufra70887zuxpprd64jxfveahn",
346+
"voter_role": "drep",
347+
"vote": "yes"
348+
}
349+
]
350+
requests_mock.get(f"{api.url}/governance/proposals/{gov_action_id}/votes", json=mock_data)
351+
assert api.governance_proposal_votes_by_gov_action_id(gov_action_id=gov_action_id) == convert_json_to_object(mock_data)
352+
353+
354+
def test_integration_governance_proposal_votes_by_gov_action_id():
355+
if os.getenv('BLOCKFROST_PROJECT_ID_MAINNET'):
356+
api = BlockFrostApi(project_id=os.getenv('BLOCKFROST_PROJECT_ID_MAINNET'))
357+
api.governance_proposal_votes_by_gov_action_id(gov_action_id=gov_action_id)
358+
359+
360+
def test_governance_proposal_metadata_by_gov_action_id(requests_mock):
361+
api = BlockFrostApi()
362+
mock_data = {
363+
"tx_hash": tx_hash,
364+
"cert_index": cert_index,
365+
"url": "https://example.com/proposal-metadata.json",
366+
"hash": "a14a5ad4a83b1c8b04c5175f0a16b4a2d8b1e5a08c7b6d1e2f3c4d5e6f7a8b9",
367+
"json_metadata": None,
368+
"bytes": "\\xa100"
369+
}
370+
requests_mock.get(f"{api.url}/governance/proposals/{gov_action_id}/metadata", json=mock_data)
371+
assert api.governance_proposal_metadata_by_gov_action_id(gov_action_id=gov_action_id) == convert_json_to_object(mock_data)
372+
373+
374+
def test_integration_governance_proposal_metadata_by_gov_action_id():
375+
if os.getenv('BLOCKFROST_PROJECT_ID_MAINNET'):
376+
api = BlockFrostApi(project_id=os.getenv('BLOCKFROST_PROJECT_ID_MAINNET'))
377+
api.governance_proposal_metadata_by_gov_action_id(gov_action_id=gov_action_id)

tests/test_cardano_pools.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -281,3 +281,24 @@ def test_integration_pool_updates():
281281
if os.getenv('BLOCKFROST_PROJECT_ID_MAINNET'):
282282
api = BlockFrostApi(project_id=os.getenv('BLOCKFROST_PROJECT_ID_MAINNET'))
283283
assert api.pool_updates(pool_id=pool_id)
284+
285+
286+
def test_pool_votes(requests_mock):
287+
api = BlockFrostApi()
288+
mock_data = [
289+
{
290+
"tx_hash": "f5ca33220afcc0340d1fa3cba9b0e1f3c3c6e1eab57d688ed700ae56bbce9170",
291+
"cert_index": 0,
292+
"vote": "yes",
293+
"proposal_tx_hash": "b302de9b2ed2bca4d65e40cae4ae6d0b8e7c0f1a2b3c4d5e6f7a8b9c0d1e2f3a",
294+
"proposal_cert_index": 0
295+
}
296+
]
297+
requests_mock.get(f"{api.url}/pools/{pool_id}/votes", json=mock_data)
298+
assert api.pool_votes(pool_id=pool_id) == convert_json_to_object(mock_data)
299+
300+
301+
def test_integration_pool_votes():
302+
if os.getenv('BLOCKFROST_PROJECT_ID_MAINNET'):
303+
api = BlockFrostApi(project_id=os.getenv('BLOCKFROST_PROJECT_ID_MAINNET'))
304+
api.pool_votes(pool_id=pool_id)

0 commit comments

Comments
 (0)