Skip to content

Commit b8b52cf

Browse files
authored
Merge pull request #37 from blockfrost/chore/improvements
Add missing methods for mempool, add tx_submit_cbor and tx_evaluate_cbor accepting cbor bytes/string
2 parents 6b3e054 + 5fecac7 commit b8b52cf

File tree

7 files changed

+495
-44
lines changed

7 files changed

+495
-44
lines changed

blockfrost/api/__init__.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,10 @@ def root(self, **kwargs):
9595
epoch_protocol_parameters
9696
from .cardano.ledger import \
9797
genesis
98+
from .cardano.mempool import \
99+
mempool, \
100+
mempool_address, \
101+
mempool_tx
98102
from .cardano.metadata import \
99103
metadata_labels, \
100104
metadata_label_json, \
@@ -125,8 +129,11 @@ def root(self, **kwargs):
125129
transaction_metadata, \
126130
transaction_metadata_cbor, \
127131
transaction_submit, \
132+
transaction_submit_cbor, \
128133
transaction_redeemers, \
129-
transaction_evaluate
134+
transaction_evaluate, \
135+
transaction_evaluate_cbor, \
136+
transaction_evaluate_utxos
130137
from .cardano.scripts import \
131138
scripts, \
132139
script, \

blockfrost/api/cardano/mempool.py

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
import requests
2+
from blockfrost.utils import list_request_wrapper
3+
4+
5+
@list_request_wrapper
6+
def mempool(self, **kwargs):
7+
"""
8+
Obtains transactions that are currently stored in Blockfrost mempool, waiting to be included in a newly minted block.
9+
Returns only transactions submitted via Blockfrost.io.
10+
11+
https://docs.blockfrost.io/#tag/Cardano-Mempool/paths/~1mempool/get
12+
13+
:param return_type: Optional. "object", "json" or "pandas". Default: "object".
14+
:type return_type: str
15+
:param gather_pages: Optional. Default: false. Will collect all pages into one return
16+
:type gather_pages: bool
17+
:param count: Optional. Default: 100. The number of results displayed on one page.
18+
:type count: int
19+
:param page: Optional. The page number for listing the results.
20+
:type page: int
21+
:returns A list of objects.
22+
:rtype [Namespace]
23+
:raises ApiError: If API fails
24+
:raises Exception: If the API response is somehow malformed.
25+
"""
26+
return requests.get(
27+
url=f"{self.url}/mempool",
28+
params=self.query_parameters(kwargs),
29+
headers=self.default_headers
30+
)
31+
32+
@list_request_wrapper
33+
def mempool_tx(self, hash: str, **kwargs):
34+
"""
35+
Obtains mempool transaction
36+
37+
https://docs.blockfrost.io/#tag/Cardano-Mempool/paths/~1mempool~1%7Bhash%7D/get
38+
39+
:param hash: Hash of the requested transaction.
40+
:type hash: str
41+
:param return_type: Optional. "object", "json" or "pandas". Default: "object".
42+
:type return_type: str
43+
:param gather_pages: Optional. Default: false. Will collect all pages into one return
44+
:type gather_pages: bool
45+
:param count: Optional. Default: 100. The number of results displayed on one page.
46+
:type count: int
47+
:param page: Optional. The page number for listing the results.
48+
:type page: int
49+
:returns A list of objects.
50+
:rtype [Namespace]
51+
:raises ApiError: If API fails
52+
:raises Exception: If the API response is somehow malformed.
53+
"""
54+
return requests.get(
55+
url=f"{self.url}/mempool/{hash}",
56+
params=self.query_parameters(kwargs),
57+
headers=self.default_headers
58+
)
59+
60+
@list_request_wrapper
61+
def mempool_address(self, address: str, **kwargs):
62+
"""
63+
Obtains list of mempool transactions where at least one of the transaction inputs or outputs belongs to the address (paginated).
64+
Shows only transactions submitted via Blockfrost.io.
65+
66+
https://docs.blockfrost.io/#tag/Cardano-Mempool/paths/~1mempool~1addresses~1%7Baddress%7D/get
67+
68+
:param address: Bech32 address.
69+
:type address: str
70+
:param return_type: Optional. "object", "json" or "pandas". Default: "object".
71+
:type return_type: str
72+
:param gather_pages: Optional. Default: false. Will collect all pages into one return
73+
:type gather_pages: bool
74+
:param count: Optional. Default: 100. The number of results displayed on one page.
75+
:type count: int
76+
:param page: Optional. The page number for listing the results.
77+
:type page: int
78+
:returns A list of objects.
79+
:rtype [Namespace]
80+
:raises ApiError: If API fails
81+
:raises Exception: If the API response is somehow malformed.
82+
"""
83+
return requests.get(
84+
url=f"{self.url}/mempool/addresses/{address}",
85+
params=self.query_parameters(kwargs),
86+
headers=self.default_headers
87+
)
88+

blockfrost/api/cardano/transactions.py

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import requests
2+
from typing import Union
23
from blockfrost.utils import request_wrapper, list_request_wrapper
34

45

@@ -268,6 +269,36 @@ def transaction_submit(self, file_path: str, **kwargs):
268269
)
269270

270271

272+
@request_wrapper
273+
def transaction_submit_cbor(self, tx_cbor: Union[bytes, str], **kwargs):
274+
"""
275+
Submit an already serialized transaction to the network.
276+
277+
https://docs.blockfrost.io/#tag/Cardano-Transactions/paths/~1tx~1submit/post
278+
279+
:param tx_cbor: Transaction in CBOR format, either as a hex-encoded string or as bytes.
280+
:type tx_cbor: Union[str, bytes]
281+
:returns str object.
282+
:rtype str
283+
:raises ApiError: If API fails
284+
:raises Exception: If the API response is somehow malformed.
285+
"""
286+
287+
# Convert to bytes
288+
if isinstance(tx_cbor, str):
289+
data = bytes.fromhex(tx_cbor)
290+
else:
291+
data = tx_cbor
292+
293+
header = self.default_headers
294+
header['Content-Type'] = 'application/cbor'
295+
return requests.post(
296+
url=f"{self.url}/tx/submit",
297+
headers=header,
298+
data=data,
299+
)
300+
301+
271302
@request_wrapper
272303
def transaction_evaluate(self, file_path: str, **kwargs):
273304
"""
@@ -290,3 +321,74 @@ def transaction_evaluate(self, file_path: str, **kwargs):
290321
headers=header,
291322
data=file,
292323
)
324+
325+
326+
@request_wrapper
327+
def transaction_evaluate_cbor(self, tx_cbor: Union[bytes, str], **kwargs):
328+
"""
329+
Submit an already serialized transaction to evaluate how much execution units it requires.
330+
331+
https://docs.blockfrost.io/#tag/Cardano-Utilities/paths/~1utils~1txs~1evaluate/post
332+
333+
:param tx_cbor: Transaction in CBOR format, either as a hex-encoded string or as bytes.
334+
:type tx_cbor: Union[str, bytes]
335+
:returns str object.
336+
:rtype str
337+
:raises ApiError: If API fails
338+
:raises Exception: If the API response is somehow malformed.
339+
"""
340+
header = self.default_headers
341+
header['Content-Type'] = 'application/cbor'
342+
343+
# Convert bytes to hex
344+
if isinstance(tx_cbor, bytes):
345+
data = tx_cbor.hex()
346+
else:
347+
data = tx_cbor
348+
349+
return requests.post(
350+
url=f"{self.url}/utils/txs/evaluate",
351+
headers=header,
352+
data=data,
353+
)
354+
355+
356+
@request_wrapper
357+
def transaction_evaluate_utxos(self, tx_cbor: Union[bytes, str], additional_utxo_set: list, **kwargs):
358+
"""
359+
Submits a transaction CBOR and additional utxo set to evaluate how much execution units it requires.
360+
361+
https://docs.blockfrost.io/#tag/Cardano-Utilities/paths/~1utils~1txs~1evaluate~1utxos/post
362+
https://ogmios.dev/mini-protocols/local-tx-submission/#evaluatetx
363+
364+
:param tx_cbor: Transaction in CBOR format, either as a hex-encoded string or as bytes.
365+
:type tx_cbor: Union[bytes, str]
366+
:param additional_utxo_set: Additional UTXO as an array of tuples [TxIn, TxOut] https://ogmios.dev/mini-protocols/local-tx-submission/#additional-utxo-set.
367+
:type additional_utxo_set: list
368+
:returns: Result of Ogmios EvaluateTx
369+
:rtype dict
370+
:raises ApiError: If API fails
371+
:raises Exception: If the API response is somehow malformed.
372+
"""
373+
374+
# Convert bytes to hex
375+
if isinstance(tx_cbor, bytes):
376+
data = tx_cbor.hex()
377+
else:
378+
data = tx_cbor
379+
380+
headers = {
381+
'Content-type': 'application/json',
382+
**self.default_headers
383+
}
384+
385+
payload = {
386+
'cbor': data,
387+
'additionalUtxoSet': additional_utxo_set
388+
}
389+
390+
return requests.post(
391+
url=f"{self.url}/utils/txs/evaluate/utxos",
392+
headers=headers,
393+
json=payload
394+
)

blockfrost/utils.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ def convert_json_to_pandas(json_response):
4747

4848

4949
def simple_request_wrapper(func):
50+
@wraps(func)
5051
def error_wrapper(*args, **kwargs):
5152
request_response: Response = func(*args, **kwargs)
5253
if request_response.status_code != 200:
@@ -58,6 +59,7 @@ def error_wrapper(*args, **kwargs):
5859

5960

6061
def request_wrapper(func):
62+
@wraps(func)
6163
def error_wrapper(*args, **kwargs):
6264
request_response: Response = func(*args, **kwargs)
6365
if request_response.status_code != 200:
@@ -77,6 +79,7 @@ def error_wrapper(*args, **kwargs):
7779

7880

7981
def list_request_wrapper(func):
82+
@wraps(func)
8083
def pagination(*args, **kwargs):
8184
def recursive_append(json_list, *args, **kwargs):
8285
request_response: Response = func(*args, **kwargs)
@@ -128,7 +131,8 @@ def __init__(
128131
):
129132
self.project_id = project_id if project_id else os.environ.get(
130133
'BLOCKFROST_PROJECT_ID')
131-
self.api_version = api_version
134+
self.api_version = api_version if api_version else os.environ.get('BLOCKFROST_API_VERSION',
135+
default=DEFAULT_API_VERSION)
132136
self.base_url = base_url
133137

134138
@property

tests/test_cardano_addresses.py

Lines changed: 30 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
from blockfrost import BlockFrostApi, ApiError
44
from blockfrost.utils import convert_json_to_object
55

6-
address = 'addr1qyptln5t5s0mastzc9rksn6wdqp9ynt67ahw0nhzukar5keu7yzv8ay6qvmlywtgvt7exaxt783dxuzv03qal7muda5sl42hg6'
6+
address = 'addr1qxk49ptelk7uda7acrczz30a7fu778sax5aapa38nhmve3eu7yzv8ay6qvmlywtgvt7exaxt783dxuzv03qal7muda5srnx35s'
77
stake_address = 'stake1ux3g2c9dx2nhhehyrezyxpkstartcqmu9hk63qgfkccw5rqttygt7'
88
asset = 'f4988f549728dc76b58d7677849443caf6e5385dc67e6c25f6aa901e506978656c54696c653235'
99

@@ -32,7 +32,8 @@ def test_address(requests_mock):
3232

3333
def test_integration_address():
3434
if os.getenv('BLOCKFROST_PROJECT_ID_MAINNET'):
35-
api = BlockFrostApi(project_id=os.getenv('BLOCKFROST_PROJECT_ID_MAINNET'))
35+
api = BlockFrostApi(project_id=os.getenv(
36+
'BLOCKFROST_PROJECT_ID_MAINNET'))
3637
assert api.address(address=address)
3738

3839

@@ -58,13 +59,16 @@ def test_address_extended(requests_mock):
5859
"type": "shelley",
5960
"script": False
6061
}
61-
requests_mock.get(f"{api.url}/addresses/{address}/extended", json=mock_data)
62-
assert api.address_extended(address=address) == convert_json_to_object(mock_data)
62+
requests_mock.get(
63+
f"{api.url}/addresses/{address}/extended", json=mock_data)
64+
assert api.address_extended(
65+
address=address) == convert_json_to_object(mock_data)
6366

6467

6568
def test_integration_address_extended():
6669
if os.getenv('BLOCKFROST_PROJECT_ID_MAINNET'):
67-
api = BlockFrostApi(project_id=os.getenv('BLOCKFROST_PROJECT_ID_MAINNET'))
70+
api = BlockFrostApi(project_id=os.getenv(
71+
'BLOCKFROST_PROJECT_ID_MAINNET'))
6872
assert api.address_extended(address=address)
6973

7074

@@ -95,12 +99,14 @@ def test_address_total(requests_mock):
9599
"tx_count": 12
96100
}
97101
requests_mock.get(f"{api.url}/addresses/{address}/total", json=mock_data)
98-
assert api.address_total(address=address) == convert_json_to_object(mock_data)
102+
assert api.address_total(
103+
address=address) == convert_json_to_object(mock_data)
99104

100105

101106
def test_integration_address_total():
102107
if os.getenv('BLOCKFROST_PROJECT_ID_MAINNET'):
103-
api = BlockFrostApi(project_id=os.getenv('BLOCKFROST_PROJECT_ID_MAINNET'))
108+
api = BlockFrostApi(project_id=os.getenv(
109+
'BLOCKFROST_PROJECT_ID_MAINNET'))
104110
assert api.address_total(address=address)
105111

106112

@@ -152,13 +158,15 @@ def test_address_utxos(requests_mock):
152158
}
153159
]
154160
requests_mock.get(f"{api.url}/addresses/{address}/utxos", json=mock_data)
155-
assert api.address_utxos(address=address) == convert_json_to_object(mock_data)
161+
assert api.address_utxos(
162+
address=address) == convert_json_to_object(mock_data)
156163

157164

158165
def test_integration_address_utxos():
159166
if os.getenv('BLOCKFROST_PROJECT_ID_MAINNET'):
160-
api = BlockFrostApi(project_id=os.getenv('BLOCKFROST_PROJECT_ID_MAINNET'))
161-
assert api.address_utxos(address=address)
167+
api = BlockFrostApi(project_id=os.getenv(
168+
'BLOCKFROST_PROJECT_ID_MAINNET'))
169+
assert api.address_utxos(address=address) == []
162170

163171

164172
def test_address_utxos_asset(requests_mock):
@@ -205,13 +213,16 @@ def test_address_utxos_asset(requests_mock):
205213
"data_hash": None
206214
}
207215
]
208-
requests_mock.get(f"{api.url}/addresses/{address}/utxos/{asset}", json=mock_data)
209-
assert api.address_utxos_asset(address=address, asset=asset) == convert_json_to_object(mock_data)
216+
requests_mock.get(
217+
f"{api.url}/addresses/{address}/utxos/{asset}", json=mock_data)
218+
assert api.address_utxos_asset(
219+
address=address, asset=asset) == convert_json_to_object(mock_data)
210220

211221

212222
def test_integration_address_utxos_asset():
213223
if os.getenv('BLOCKFROST_PROJECT_ID_MAINNET'):
214-
api = BlockFrostApi(project_id=os.getenv('BLOCKFROST_PROJECT_ID_MAINNET'))
224+
api = BlockFrostApi(project_id=os.getenv(
225+
'BLOCKFROST_PROJECT_ID_MAINNET'))
215226
assert api.address_utxos_asset(address=address, asset=asset) == []
216227

217228

@@ -237,11 +248,14 @@ def test_address_transactions(requests_mock):
237248
"block_time": 1834505492
238249
}
239250
]
240-
requests_mock.get(f"{api.url}/addresses/{address}/transactions", json=mock_data)
241-
assert api.address_transactions(address=address) == convert_json_to_object(mock_data)
251+
requests_mock.get(
252+
f"{api.url}/addresses/{address}/transactions", json=mock_data)
253+
assert api.address_transactions(
254+
address=address) == convert_json_to_object(mock_data)
242255

243256

244257
def test_integration_address_transactions():
245258
if os.getenv('BLOCKFROST_PROJECT_ID_MAINNET'):
246-
api = BlockFrostApi(project_id=os.getenv('BLOCKFROST_PROJECT_ID_MAINNET'))
259+
api = BlockFrostApi(project_id=os.getenv(
260+
'BLOCKFROST_PROJECT_ID_MAINNET'))
247261
assert api.address_transactions(address=address)

0 commit comments

Comments
 (0)