pccontext 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (32) hide show
  1. pccontext-0.1.0/PKG-INFO +110 -0
  2. pccontext-0.1.0/README.md +80 -0
  3. pccontext-0.1.0/pccontext/__init__.py +8 -0
  4. pccontext-0.1.0/pccontext/backend/__init__.py +9 -0
  5. pccontext-0.1.0/pccontext/backend/blockfrost.py +296 -0
  6. pccontext-0.1.0/pccontext/backend/cardano_cli.py +471 -0
  7. pccontext-0.1.0/pccontext/backend/koios.py +297 -0
  8. pccontext-0.1.0/pccontext/backend/kupo.py +267 -0
  9. pccontext-0.1.0/pccontext/backend/offline_transfer_file.py +216 -0
  10. pccontext-0.1.0/pccontext/backend/ogmios.py +518 -0
  11. pccontext-0.1.0/pccontext/backend/yaci_devkit.py +307 -0
  12. pccontext-0.1.0/pccontext/enums/__init__.py +5 -0
  13. pccontext-0.1.0/pccontext/enums/address_type_enum.py +12 -0
  14. pccontext-0.1.0/pccontext/enums/era_enum.py +17 -0
  15. pccontext-0.1.0/pccontext/enums/history_type_enum.py +108 -0
  16. pccontext-0.1.0/pccontext/enums/network_enum.py +20 -0
  17. pccontext-0.1.0/pccontext/enums/transaction_type_enum.py +20 -0
  18. pccontext-0.1.0/pccontext/exceptions.py +113 -0
  19. pccontext-0.1.0/pccontext/logging.py +18 -0
  20. pccontext-0.1.0/pccontext/models/__init__.py +8 -0
  21. pccontext-0.1.0/pccontext/models/address_info_model.py +227 -0
  22. pccontext-0.1.0/pccontext/models/base_model.py +186 -0
  23. pccontext-0.1.0/pccontext/models/genesis_parameters_model.py +97 -0
  24. pccontext-0.1.0/pccontext/models/offline_transfer_model.py +292 -0
  25. pccontext-0.1.0/pccontext/models/protocol_parameters_model.py +1105 -0
  26. pccontext-0.1.0/pccontext/models/stake_address_info_model.py +49 -0
  27. pccontext-0.1.0/pccontext/models/token_metadata_model.py +32 -0
  28. pccontext-0.1.0/pccontext/utils/__init__.py +10 -0
  29. pccontext-0.1.0/pccontext/utils/file_utils.py +158 -0
  30. pccontext-0.1.0/pccontext/utils/formatters.py +21 -0
  31. pccontext-0.1.0/pccontext/utils/validators.py +22 -0
  32. pccontext-0.1.0/pyproject.toml +102 -0
@@ -0,0 +1,110 @@
1
+ Metadata-Version: 2.1
2
+ Name: pccontext
3
+ Version: 0.1.0
4
+ Summary: Chain Contexts for PyCardano library
5
+ Home-page: https://github.com/Python-Cardano/pycardano
6
+ License: MIT
7
+ Keywords: python,cardano,blockchain,crypto
8
+ Author: Hareem Adderley
9
+ Author-email: haddderley@kingpinapps.com
10
+ Requires-Python: >=3.8.1,<4.0.0
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: License :: OSI Approved :: MIT License
13
+ Classifier: Natural Language :: English
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.9
16
+ Classifier: Programming Language :: Python :: 3.10
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Programming Language :: Python :: 3.8
20
+ Requires-Dist: cachetools (>=5.5.0,<6.0.0)
21
+ Requires-Dist: koios-python (>=2.0.0,<3.0.0)
22
+ Requires-Dist: ogmios (>=1.2.1,<2.0.0)
23
+ Requires-Dist: pycardano (>=0.12.0,<0.13.0)
24
+ Requires-Dist: requests (>=2.32.3,<3.0.0)
25
+ Requires-Dist: yaci-client (>=1.0,<2.0)
26
+ Project-URL: Documentation, https://pycardano.readthedocs.io/en/latest/
27
+ Project-URL: Repository, https://github.com/KINGH242/pccontext.git
28
+ Description-Content-Type: text/markdown
29
+
30
+ ## PyCardano Chain Contexts
31
+
32
+ This library contains the various Chain Contexts to use with the PyCardano library.
33
+
34
+ ### Basic Usage
35
+
36
+ #### Blockfrost
37
+
38
+ ```python
39
+ from pccontext import BlockFrostChainContext
40
+ from blockfrost import ApiUrls
41
+
42
+ chain_context = BlockFrostChainContext(
43
+ project_id="your_project_id",
44
+ base_url=ApiUrls.mainnet.value
45
+ )
46
+
47
+ ```
48
+
49
+ #### Cardano-CLI
50
+
51
+ ```python
52
+ from pccontext import CardanoCliChainContext, CardanoCliNetwork
53
+ from pathlib import Path
54
+
55
+ chain_context = CardanoCliChainContext(
56
+ binary=Path("cardano-cli"),
57
+ socket=Path("node.socket"),
58
+ config_file=Path("config.json"),
59
+ network=CardanoCliNetwork.MAINNET,
60
+ )
61
+
62
+ ```
63
+
64
+ #### Koios
65
+
66
+ ```python
67
+ from pccontext import KoiosChainContext
68
+
69
+ chain_context = KoiosChainContext(api_key="api_key")
70
+
71
+ ```
72
+
73
+ #### Ogmios
74
+
75
+ ```python
76
+ from pccontext import OgmiosChainContext
77
+
78
+ chain_context = OgmiosChainContext(host="localhost", port=1337)
79
+
80
+ ```
81
+
82
+ #### Kupo
83
+
84
+ ```python
85
+ from pccontext import OgmiosChainContext, KupoChainContextExtension
86
+
87
+ ogmios_chain_context = OgmiosChainContext(host="localhost", port=1337)
88
+ chain_context = KupoChainContextExtension(wrapped_backend=ogmios_chain_context)
89
+
90
+ ```
91
+
92
+ #### Offline Transfer File
93
+
94
+ ```python
95
+ from pathlib import Path
96
+ from pccontext import OfflineTransferFileContext
97
+
98
+ chain_context = OfflineTransferFileContext(offline_transfer_file=Path("offline-transfer.json"))
99
+
100
+ ```
101
+
102
+ #### Yaci Devkit
103
+
104
+ ```python
105
+ from pccontext import YaciDevkitChainContext
106
+
107
+ chain_context = YaciDevkitChainContext(api_url="http://localhost:8080")
108
+
109
+ ```
110
+
@@ -0,0 +1,80 @@
1
+ ## PyCardano Chain Contexts
2
+
3
+ This library contains the various Chain Contexts to use with the PyCardano library.
4
+
5
+ ### Basic Usage
6
+
7
+ #### Blockfrost
8
+
9
+ ```python
10
+ from pccontext import BlockFrostChainContext
11
+ from blockfrost import ApiUrls
12
+
13
+ chain_context = BlockFrostChainContext(
14
+ project_id="your_project_id",
15
+ base_url=ApiUrls.mainnet.value
16
+ )
17
+
18
+ ```
19
+
20
+ #### Cardano-CLI
21
+
22
+ ```python
23
+ from pccontext import CardanoCliChainContext, CardanoCliNetwork
24
+ from pathlib import Path
25
+
26
+ chain_context = CardanoCliChainContext(
27
+ binary=Path("cardano-cli"),
28
+ socket=Path("node.socket"),
29
+ config_file=Path("config.json"),
30
+ network=CardanoCliNetwork.MAINNET,
31
+ )
32
+
33
+ ```
34
+
35
+ #### Koios
36
+
37
+ ```python
38
+ from pccontext import KoiosChainContext
39
+
40
+ chain_context = KoiosChainContext(api_key="api_key")
41
+
42
+ ```
43
+
44
+ #### Ogmios
45
+
46
+ ```python
47
+ from pccontext import OgmiosChainContext
48
+
49
+ chain_context = OgmiosChainContext(host="localhost", port=1337)
50
+
51
+ ```
52
+
53
+ #### Kupo
54
+
55
+ ```python
56
+ from pccontext import OgmiosChainContext, KupoChainContextExtension
57
+
58
+ ogmios_chain_context = OgmiosChainContext(host="localhost", port=1337)
59
+ chain_context = KupoChainContextExtension(wrapped_backend=ogmios_chain_context)
60
+
61
+ ```
62
+
63
+ #### Offline Transfer File
64
+
65
+ ```python
66
+ from pathlib import Path
67
+ from pccontext import OfflineTransferFileContext
68
+
69
+ chain_context = OfflineTransferFileContext(offline_transfer_file=Path("offline-transfer.json"))
70
+
71
+ ```
72
+
73
+ #### Yaci Devkit
74
+
75
+ ```python
76
+ from pccontext import YaciDevkitChainContext
77
+
78
+ chain_context = YaciDevkitChainContext(api_url="http://localhost:8080")
79
+
80
+ ```
@@ -0,0 +1,8 @@
1
+ # flake8: noqa
2
+ __version__ = "0.1.0"
3
+ __app_name__ = "pccontext"
4
+
5
+ from .backend import *
6
+ from .enums import *
7
+ from .models import *
8
+ from .utils import *
@@ -0,0 +1,9 @@
1
+ # flake8: noqa
2
+
3
+ from .blockfrost import *
4
+ from .cardano_cli import *
5
+ from .koios import *
6
+ from .kupo import *
7
+ from .offline_transfer_file import *
8
+ from .ogmios import *
9
+ from .yaci_devkit import *
@@ -0,0 +1,296 @@
1
+ import os
2
+ import tempfile
3
+ import time
4
+ import warnings
5
+ from typing import Dict, List, Optional, Union
6
+
7
+ import cbor2
8
+ from blockfrost import ApiError, ApiUrls, BlockFrostApi
9
+ from blockfrost.utils import Namespace
10
+ from pycardano.address import Address
11
+ from pycardano.backend.base import ChainContext
12
+ from pycardano.backend.base import ProtocolParameters as PyCardanoProtocolParameters
13
+ from pycardano.exception import TransactionFailedException
14
+ from pycardano.hash import SCRIPT_HASH_SIZE, DatumHash, ScriptHash
15
+ from pycardano.nativescript import NativeScript
16
+ from pycardano.network import Network
17
+ from pycardano.plutus import (
18
+ ExecutionUnits,
19
+ PlutusV1Script,
20
+ PlutusV2Script,
21
+ PlutusV3Script,
22
+ script_hash,
23
+ )
24
+ from pycardano.serialization import RawCBOR
25
+ from pycardano.transaction import (
26
+ Asset,
27
+ AssetName,
28
+ MultiAsset,
29
+ TransactionInput,
30
+ TransactionOutput,
31
+ UTxO,
32
+ Value,
33
+ )
34
+ from pycardano.types import JsonDict
35
+
36
+ from pccontext.models import GenesisParameters, ProtocolParameters, StakeAddressInfo
37
+
38
+ __all__ = ["BlockFrostChainContext"]
39
+
40
+
41
+ def _try_fix_script(
42
+ scripth: str, script: Union[PlutusV1Script, PlutusV2Script, PlutusV3Script]
43
+ ) -> Union[PlutusV1Script, PlutusV2Script, PlutusV3Script]:
44
+ if str(script_hash(script)) == scripth:
45
+ return script
46
+ new_script = script.__class__(cbor2.loads(script))
47
+ if str(script_hash(new_script)) == scripth:
48
+ return new_script
49
+ else:
50
+ raise ValueError("Cannot recover script from hash.")
51
+
52
+
53
+ class BlockFrostChainContext(ChainContext):
54
+ """A `BlockFrost <https://blockfrost.io/>`_ API wrapper for the client code to interact with.
55
+
56
+ Args:
57
+ project_id (str): A BlockFrost project ID obtained from https://blockfrost.io.
58
+ network (Network): Network to use.
59
+ base_url (str): Base URL for the BlockFrost API. Defaults to the preprod url.
60
+ """
61
+
62
+ api: BlockFrostApi
63
+ _epoch_info: Namespace
64
+ _epoch: Optional[int] = None
65
+ _genesis_param: Optional[GenesisParameters] = None
66
+ _protocol_param: Optional[ProtocolParameters] = None
67
+
68
+ def __init__(
69
+ self,
70
+ project_id: str,
71
+ network: Optional[Network] = None,
72
+ base_url: Optional[str] = None,
73
+ ):
74
+ if network is not None:
75
+ warnings.warn(
76
+ "`network` argument will be deprecated in the future. Directly passing `base_url` is recommended."
77
+ )
78
+ self._network = network
79
+ else:
80
+ self._network = Network.TESTNET
81
+
82
+ self._project_id = project_id
83
+ self._base_url = base_url or (
84
+ ApiUrls.preprod.value
85
+ if self.network == Network.TESTNET
86
+ else ApiUrls.mainnet.value
87
+ )
88
+
89
+ # Set network value to mainnet if base_url contains "mainnet".
90
+ if "mainnet" in self._base_url:
91
+ self._network = Network.MAINNET
92
+
93
+ self.api = BlockFrostApi(project_id=self._project_id, base_url=self._base_url)
94
+ self._epoch_info = self.api.epoch_latest()
95
+ self._epoch = None
96
+ self._genesis_param = None
97
+ self._protocol_param = None
98
+
99
+ def _check_epoch_and_update(self):
100
+ if int(time.time()) < self._epoch_info.end_time:
101
+ return False
102
+ self._epoch_info = self.api.epoch_latest()
103
+ return True
104
+
105
+ @property
106
+ def network(self) -> Network:
107
+ return self._network
108
+
109
+ @property
110
+ def epoch(self) -> int:
111
+ if not self._epoch or self._check_epoch_and_update():
112
+ new_epoch: int = self.api.epoch_latest().epoch
113
+ self._epoch = new_epoch
114
+ return self._epoch
115
+
116
+ @property
117
+ def last_block_slot(self) -> int:
118
+ block = self.api.block_latest()
119
+ return block.slot
120
+
121
+ @property
122
+ def genesis_param(self) -> GenesisParameters:
123
+ if not self._genesis_param or self._check_epoch_and_update():
124
+ params = self.api.genesis(return_type="json")
125
+ self._genesis_param = GenesisParameters.from_json(params)
126
+ return self._genesis_param
127
+
128
+ @property
129
+ def protocol_param(self) -> PyCardanoProtocolParameters:
130
+ if not self._protocol_param or self._check_epoch_and_update():
131
+ params = self.api.epoch_latest_parameters(return_type="json")
132
+ self._protocol_param = ProtocolParameters.from_json(params)
133
+ return self._protocol_param.to_pycardano()
134
+
135
+ def _get_script(
136
+ self, script_hash: str
137
+ ) -> Union[PlutusV1Script, PlutusV2Script, PlutusV3Script, NativeScript]:
138
+ script_type = self.api.script(script_hash).type
139
+ if script_type == "plutusV1":
140
+ v1script = PlutusV1Script(
141
+ bytes.fromhex(self.api.script_cbor(script_hash).cbor)
142
+ )
143
+ return _try_fix_script(script_hash, v1script)
144
+ elif script_type == "plutusV2":
145
+ v2script = PlutusV2Script(
146
+ bytes.fromhex(self.api.script_cbor(script_hash).cbor)
147
+ )
148
+ return _try_fix_script(script_hash, v2script)
149
+ elif script_type == "plutusV3":
150
+ v3script = PlutusV3Script(
151
+ bytes.fromhex(self.api.script_cbor(script_hash).cbor)
152
+ )
153
+ return _try_fix_script(script_hash, v3script)
154
+ else:
155
+ script_json: JsonDict = self.api.script_json(
156
+ script_hash, return_type="json"
157
+ )["json"]
158
+ return NativeScript.from_dict(script_json)
159
+
160
+ def _utxos(self, address: str) -> List[UTxO]:
161
+ try:
162
+ results = self.api.address_utxos(address, gather_pages=True)
163
+ except ApiError as e:
164
+ if e.status_code == 404:
165
+ return []
166
+ else:
167
+ raise e
168
+
169
+ utxos = []
170
+
171
+ for result in results:
172
+ tx_in = TransactionInput.from_primitive(
173
+ [result.tx_hash, result.output_index]
174
+ )
175
+ amount = result.amount
176
+ lovelace_amount = 0
177
+ multi_assets = MultiAsset()
178
+ for item in amount:
179
+ if item.unit == "lovelace":
180
+ lovelace_amount = int(item.quantity)
181
+ else:
182
+ # The utxo contains Multi-asset
183
+ data = bytes.fromhex(item.unit)
184
+ policy_id = ScriptHash(data[:SCRIPT_HASH_SIZE])
185
+ asset_name = AssetName(data[SCRIPT_HASH_SIZE:])
186
+
187
+ if policy_id not in multi_assets:
188
+ multi_assets[policy_id] = Asset()
189
+ multi_assets[policy_id][asset_name] = int(item.quantity)
190
+
191
+ amount = Value(lovelace_amount, multi_assets)
192
+
193
+ datum_hash = (
194
+ DatumHash.from_primitive(result.data_hash)
195
+ if result.data_hash and result.inline_datum is None
196
+ else None
197
+ )
198
+
199
+ datum = None
200
+
201
+ if hasattr(result, "inline_datum") and result.inline_datum is not None:
202
+ datum = RawCBOR(bytes.fromhex(result.inline_datum))
203
+
204
+ script = None
205
+
206
+ if (
207
+ hasattr(result, "reference_script_hash")
208
+ and result.reference_script_hash
209
+ ):
210
+ script = self._get_script(result.reference_script_hash)
211
+
212
+ tx_out = TransactionOutput(
213
+ Address.from_primitive(address),
214
+ amount=amount,
215
+ datum_hash=datum_hash,
216
+ datum=datum,
217
+ script=script,
218
+ )
219
+ utxos.append(UTxO(tx_in, tx_out))
220
+
221
+ return utxos
222
+
223
+ def submit_tx_cbor(self, cbor: Union[bytes, str]) -> str:
224
+ """Submit a transaction.
225
+
226
+ Args:
227
+ cbor (Union[bytes, str]): The serialized transaction to be submitted.
228
+
229
+ Returns:
230
+ str: The transaction hash.
231
+
232
+ Raises:
233
+ :class:`TransactionFailedException`: When fails to submit the transaction.
234
+ """
235
+ if isinstance(cbor, str):
236
+ cbor = bytes.fromhex(cbor)
237
+ with tempfile.NamedTemporaryFile(delete=False) as f:
238
+ f.write(cbor)
239
+ try:
240
+ response = self.api.transaction_submit(f.name)
241
+ except ApiError as e:
242
+ os.remove(f.name)
243
+ raise TransactionFailedException(
244
+ f"Failed to submit transaction. Error code: {e.status_code}. Error message: {e.message}"
245
+ ) from e
246
+ os.remove(f.name)
247
+ return response
248
+
249
+ def evaluate_tx_cbor(self, cbor: Union[bytes, str]) -> Dict[str, ExecutionUnits]:
250
+ """Evaluate execution units of a transaction.
251
+
252
+ Args:
253
+ cbor (Union[bytes, str]): The serialized transaction to be evaluated.
254
+
255
+ Returns:
256
+ Dict[str, ExecutionUnits]: A list of execution units calculated for each of the transaction's redeemers
257
+
258
+ Raises:
259
+ :class:`TransactionFailedException`: When fails to evaluate the transaction.
260
+ """
261
+ if isinstance(cbor, bytes):
262
+ cbor = cbor.hex()
263
+ with tempfile.NamedTemporaryFile(delete=False, mode="w") as f:
264
+ f.write(cbor)
265
+ result = self.api.transaction_evaluate(f.name).result
266
+ os.remove(f.name)
267
+ return_val = {}
268
+ if not hasattr(result, "EvaluationResult"):
269
+ raise TransactionFailedException(result)
270
+ else:
271
+ for k in vars(result.EvaluationResult):
272
+ return_val[k] = ExecutionUnits(
273
+ getattr(result.EvaluationResult, k).memory,
274
+ getattr(result.EvaluationResult, k).steps,
275
+ )
276
+ return return_val
277
+
278
+ def stake_address_info(self, stake_address: str) -> List[StakeAddressInfo]:
279
+ """Get the stake address information.
280
+
281
+ Args:
282
+ stake_address (str): The stake address.
283
+
284
+ Returns:
285
+ List[StakeAddressInfo]: The stake address information.
286
+ """
287
+ rewards_state = self.api.accounts(stake_address)
288
+
289
+ return [
290
+ StakeAddressInfo(
291
+ address=rewards_state.get("stake_address", None),
292
+ stake_delegation=rewards_state.get("pool_id", None),
293
+ reward_account_balance=rewards_state.get("withdrawable_amount", None),
294
+ delegate_representative=rewards_state.get("drep_id", None),
295
+ )
296
+ ]