Cometa.py is a lightweight, high-performance Python library binding for the libcardano-c library, designed to simplify blockchain development on Cardano.
Cometa.py packages libcardano-c using CFFI bindings, providing a fully documented, developer-friendly Pythonic API with type hints for excellent IDE support.
Example:
builder = TxBuilder(protocol_params, provider)
unsigned_tx = (
builder
.set_change_address(sender_address)
.set_utxos(utxos)
.send_lovelace(recipient_address, 12_000_000)
.set_valid_until(current_time + 3600)
.build()
)Cometa.py supports all features up to the Conway era, which is the current era of the Cardano blockchain. Conway era brought decentralized governance to Cardano, including:
- Register as DRep (PubKey)
- Register as DRep (Script)
- Submit governance action proposal (Withdrawing from treasury)
- Vote for proposal (PubKey DRep)
- Vote for proposal (Script DRep)
These are some of the examples illustrated in the examples directory. However, you should be able to build any valid transaction for the current era. See the Documentation for more information.
You can install Cometa.py using pip:
pip install biglup-cometaOnce installed, you can import it into your application:
import cometa
version = cometa.get_lib_version()
print(f"Library version: {version}")The primary component for creating transactions is the TxBuilder. It provides a fluent (chainable) API that simplifies the complex process of assembling inputs, outputs, and calculating fees.
First, establish a connection to the Cardano network using a Provider:
from cometa import BlockfrostProvider, NetworkMagic
provider = BlockfrostProvider(
network=NetworkMagic.Preprod,
project_id="YOUR_BLOCKFROST_PROJECT_ID"
)Tip: You can create your own providers by implementing the
Providerprotocol.
The BlockfrostProvider also supports remote transaction evaluation for Plutus scripts. For local evaluation, see the Aiken Script Support section.
Create your addresses and fetch UTxOs:
from cometa import Address
sender_address = Address.from_bech32("addr_test1...")
recipient_address = Address.from_bech32("addr_test1...")
# Fetch UTxOs from the provider
utxos = provider.get_utxos(sender_address)
protocol_params = provider.get_protocol_parameters()Build your transaction using the fluent API:
from cometa import TxBuilder
builder = TxBuilder(protocol_params, provider)
unsigned_tx = (
builder
.set_change_address(sender_address)
.set_utxos(utxos)
.send_lovelace(recipient_address, 2_000_000) # Send 2 ADA
.set_valid_until(current_time + 7200) # Set TTL
.build()
)Sign and submit the transaction:
tx_hash = provider.submit_transaction(signed_tx)
print(f"Transaction submitted! TxHash: {tx_hash}")The TxBuilder supports a wide range of transaction types. Here are some common patterns:
builder = TxBuilder(protocol_params, provider)
tx = (
builder
.set_change_address(sender_address)
.set_utxos(utxos)
.send_lovelace("addr_test1qz...", 5_000_000) # Send 5 ADA
.send_lovelace("addr_test1qp...", 10_000_000) # Send 10 ADA
.send_lovelace("addr_test1qr...", 2_000_000) # Send 2 ADA
.expires_in(7200) # Expires in 2 hours
.build()
)from cometa import ScriptAll, ScriptPubkey, Value
# Create a native script policy
pub_key_hash = payment_key.to_hash()
native_script = ScriptAll.new([
ScriptPubkey.new(pub_key_hash)
])
policy_id = native_script.hash
# Mint tokens
tx = (
builder
.set_change_address(sender_address)
.set_utxos(utxos)
.mint_token(amount=100, policy_id=policy_id, asset_name=b"MyToken")
.add_script(native_script)
.send_value(
address=str(sender_address),
value=Value.from_dict([2_000_000, {policy_id: {b"MyToken": 100}}])
)
.expires_in(3600)
.build()
)# Burn tokens (negative amount)
tx = (
builder
.set_change_address(sender_address)
.set_utxos(utxos)
.mint_token(amount=-50, policy_id=policy_id, asset_name=b"MyToken")
.add_script(native_script)
.expires_in(3600)
.build()
)# CIP-25 NFT metadata
nft_metadata = {
policy_id.hex(): {
"MyNFT": {
"name": "My Awesome NFT",
"image": "ipfs://QmXyz...",
"description": "A unique digital artwork"
}
}
}
tx = (
builder
.set_change_address(sender_address)
.set_utxos(utxos)
.mint_token(amount=1, policy_id=policy_id, asset_name=b"MyNFT")
.add_script(native_script)
.set_metadata(metadata=nft_metadata, tag=721) # CIP-25 uses tag 721
.send_value(
address=recipient_address,
value=Value.from_dict([2_000_000, {policy_id: {b"MyNFT": 1}}])
)
.expires_in(3600)
.build()
)from cometa import DRep
# Delegate stake to a pool
tx = (
builder
.set_change_address(sender_address)
.set_utxos(utxos)
.delegate_stake(
reward_address=reward_address,
pool_id="pool1..."
)
.build()
)
# Delegate voting power to a DRep (Conway era)
tx = (
builder
.set_change_address(sender_address)
.set_utxos(utxos)
.delegate_voting_power(
reward_address=reward_address,
drep=DRep.from_key_hash("drep_key_hash_hex...")
)
.build()
)# Donate to the Cardano treasury
tx = (
builder
.set_change_address(sender_address)
.set_utxos(utxos)
.donate(1_000_000_000) # Donate 1000 ADA
.build()
)Cometa.py supports Plutus V1, V2, and V3 scripts for smart contract interactions. Plutus scripts can be used for spending validation, minting policies, and staking operations.
from cometa import PlutusV3Script, PlutusV2Script, PlutusV1Script, Script
# Load a Plutus V3 script from CBOR hex
plutus_v3 = PlutusV3Script.from_hex("590dff010000323232...")
script = Script.from_plutus_v3(plutus_v3)
# Load Plutus V2 or V1 similarly
plutus_v2 = PlutusV2Script.from_hex("...")
script_v2 = Script.from_plutus_v2(plutus_v2)
# Get the script hash (used as policy ID for minting)
script_hash = script.hash
print(f"Script hash: {script_hash.hex()}")from cometa import EnterpriseAddress, Credential, NetworkId
# Create a credential from the script hash
script_credential = Credential.from_script_hash(script_hash)
# Create an enterprise address (no staking) for the script
script_address = EnterpriseAddress.from_credentials(
NetworkId.TESTNET,
script_credential
).to_address()
print(f"Script address: {script_address}")from cometa import ConstrPlutusData
# Get UTXOs locked at the script address
script_utxos = provider.get_unspent_outputs(str(script_address))
# Create a redeemer (argument to the script)
# ConstrPlutusData(0) is a simple constructor - your script defines the format
redeemer = ConstrPlutusData(0)
# Build transaction to spend from the script
spend_tx = (
builder
.set_change_address(sender_address)
.set_utxos(utxos)
.add_input(utxo=script_utxos[0], redeemer=redeemer) # Script input with redeemer
.send_lovelace(address=recipient_address, amount=5_000_000)
.add_script(script) # Include the script for validation
.expires_in(3600)
.build()
)from cometa import ConstrPlutusData, PlutusList, PlutusMap
# Constructor with fields (most common pattern)
# Represents: data MyDatum = MyDatum { owner: PubKeyHash, amount: Integer }
datum = ConstrPlutusData(
0, # Constructor index
[
b"pubkey_hash_here", # owner
1000000 # amount
]
)
# List of integers
int_list = PlutusList.from_list([
1,
2,
3
])
# Map (key-value pairs)
plutus_map = PlutusMap()
plutus_map[b"key1"] = 100
plutus_map[b"key2"] = 200Cometa.py provides some utilities to parameterize scripts and local transaction evaluation using the Aiken UPLC evaluator.
Many contracts are parameterized—they require configuration data to be applied before deployment. Use apply_params_to_script to apply parameters to compiled scripts:
from cometa import PlutusV2Script, Script, PlutusList, PlutusData, ConstrPlutusData
from cometa.aiken import apply_params_to_script
# Load your compiled script
compiled_code = "590221010000323232..."
# Build parameters as Plutus Data
# Example: Gift Card contract requires token_name and utxo_ref
token_name = "MyToken"
output_ref = ConstrPlutusData(0, [
ConstrPlutusData(0, [PlutusData.from_hex(utxo.input.transaction_id.hex())]),
PlutusData.from_int(utxo.input.index)
])
params = PlutusList.from_list([
PlutusData.from_string(token_name),
PlutusData.from_constr(output_ref)
])
# Apply parameters to get the final script
parameterized_code = apply_params_to_script(params, compiled_code)
# Create the script object
plutus_script = PlutusV2Script.from_hex(parameterized_code)
script = Script.from_plutus_v2(plutus_script)
# Get the policy ID (script hash)
policy_id = script.hash
print(f"Policy ID: {policy_id.hex()}")from cometa import (
BlockfrostProvider, NetworkMagic, TxBuilder,
PlutusV2Script, Script, Value,
PlutusList, PlutusData, ConstrPlutusData,
)
from cometa.aiken import AikenTxEvaluator, SlotConfig, apply_params_to_script
# Setup provider
provider = BlockfrostProvider(
network=NetworkMagic.PREPROD,
project_id="YOUR_PROJECT_ID"
)
# Get protocol parameters and UTXOs
protocol_params = provider.get_parameters()
utxos = provider.get_unspent_outputs(sender_address)
# Select a UTXO to use as parameter (ensures script uniqueness)
param_utxo = utxos[0]
# Build script parameters
token_name = "MyNFT"
output_ref = ConstrPlutusData(0, [
ConstrPlutusData(0, [PlutusData.from_hex(param_utxo.input.transaction_id.hex())]),
PlutusData.from_int(param_utxo.input.index)
])
params = PlutusList.from_list([
PlutusData.from_string(token_name),
PlutusData.from_constr(output_ref)
])
# Apply parameters and create script
compiled_code = "590221010000323232..."
parameterized = apply_params_to_script(params, compiled_code)
script = Script.from_plutus_v2(PlutusV2Script.from_hex(parameterized))
# Create evaluator (This step is optiona you can also just use the default evaluator in the TX builder)
evaluator = AikenTxEvaluator(
cost_models=protocol_params.cost_models,
slot_config=SlotConfig.preprod(),
max_tx_ex_units=protocol_params.max_tx_ex_units,
)
# Build mint transaction
policy_id = script.hash
asset_name = token_name.encode("utf-8")
mint_redeemer = ConstrPlutusData(0) # Mint action
builder = TxBuilder(protocol_params, provider)
builder.set_evaluator(evaluator)
tx = (
builder
.set_change_address(sender_address)
.set_utxos(utxos)
.add_input(param_utxo) # Must spend the referenced UTXO
.mint_token(policy_id=policy_id, asset_name=asset_name, amount=1, redeemer=mint_redeemer)
.add_script(script)
.send_value(
address=str(recipient_address),
value=Value.from_dict([2_000_000, {policy_id: {asset_name: 1}}])
)
.expires_in(3600)
.build()
)
# Sign and submit
signed_tx = sign_transaction(tx, private_key)
tx_hash = provider.submit_transaction(signed_tx.serialize_to_cbor())
print(f"Transaction submitted: {tx_hash}")Cardano's Conway era introduced decentralized governance. Cometa.py supports all governance features including DRep registration, voting, and proposal submission.
Delegated Representatives (DReps) vote on governance proposals on behalf of delegators:
from cometa import DRep, DRepType, Credential, Anchor, Blake2bHash, Ed25519PublicKey
# Create DRep credential from your DRep key
drep_pub_key = Ed25519PublicKey.from_hex("your_drep_public_key_hex")
drep_credential = Credential.from_key_hash(drep_pub_key.to_hash())
# Create the DRep object
drep = DRep.new(drep_type=DRepType.KEY_HASH, credential=drep_credential)
print(f"DRep ID: {drep.to_cip129_string()}")
# Create an anchor (metadata URL + hash)
anchor = Anchor.new(
url="https://example.com/drep-metadata.jsonld",
hash_value=Blake2bHash.from_hex("metadata_hash_hex...")
)
# Register as a DRep
register_tx = (
builder
.set_change_address(sender_address)
.set_utxos(utxos)
.register_drep(drep=drep, anchor=anchor)
.build()
)ADA holders can delegate their voting power to a DRep:
from cometa import DRep
# Delegate to a specific DRep
drep = DRep.from_string("drep1...") # DRep ID in CIP-129 format
# First register your stake key (if not already registered)
register_stake_tx = (
builder
.set_change_address(sender_address)
.set_utxos(utxos)
.register_reward_address(reward_address=reward_address)
.build()
)
# Then delegate voting power
delegate_tx = (
builder
.set_change_address(sender_address)
.set_utxos(utxos)
.delegate_voting_power(
drep=drep,
reward_address=reward_address
)
.build()
)
# You can also delegate to special DReps
from cometa import DRep
# Delegate to "Abstain" (participate in quorum but don't vote)
abstain_drep = DRep.new_abstain()
# Delegate to "No Confidence" (vote no on everything)
no_confidence_drep = DRep.new_no_confidence()DReps can vote on governance proposals:
from cometa import (
Voter, VoterType, Vote, VotingProcedure,
GovernanceActionId, Anchor, Blake2bHash
)
# Create the voter (DRep credential)
voter = Voter.new(VoterType.DREP_KEY_HASH, drep_credential)
# Reference the governance action to vote on
action_id = GovernanceActionId.from_bech32(
"gov_action1u8gafgcskj6sqvgwqse7adc0h9438m535lg97czcvxntscvw7f5sqgf2n7j"
)
# Create voting procedure with rationale anchor
rationale_anchor = Anchor.new(
url="https://example.com/vote-rationale.jsonld",
hash_value=Blake2bHash.from_hex("rationale_hash...")
)
voting_procedure = VotingProcedure.new(Vote.YES, rationale_anchor)
# Cast the vote
vote_tx = (
builder
.set_change_address(sender_address)
.set_utxos(utxos)
.vote(
voter=voter,
action_id=action_id,
voting_procedure=voting_procedure
)
.build()
)Submit proposals for on-chain governance:
from cometa import Anchor, Blake2bHash
# All proposals require an anchor with metadata
proposal_anchor = Anchor.new(
url="https://example.com/proposal-metadata.jsonld",
hash_value=Blake2bHash.from_hex("metadata_hash...")
)
# Info action (non-binding poll)
info_tx = (
builder
.set_change_address(sender_address)
.set_utxos(utxos)
.propose_info(
reward_address=reward_address, # Deposit refund address
anchor=proposal_anchor
)
.build()
)# Deregister and reclaim your deposit
deregister_tx = (
builder
.set_change_address(sender_address)
.set_utxos(utxos)
.deregister_drep(drep=drep)
.build()
)# Get current rewards balance
rewards_balance = provider.get_rewards_balance(reward_address)
# Withdraw all rewards
withdraw_tx = (
builder
.set_change_address(sender_address)
.set_utxos(utxos)
.withdraw_rewards(
amount=rewards_balance,
reward_address=reward_address
)
.build()
)
# Optionally deregister stake key to reclaim deposit
deregister_stake_tx = (
builder
.set_change_address(sender_address)
.set_utxos(utxos)
.withdraw_rewards(amount=rewards_balance, reward_address=reward_address)
.deregister_reward_address(reward_address=reward_address)
.build()
)You can see the full capabilities of the transaction builder in the TxBuilder API documentation.
Cardano uses CBOR (Concise Binary Object Representation) for serialization. Cometa.py provides CborReader and CborWriter for encoding and decoding CBOR data.
from cometa import CborWriter
# Create a writer
writer = CborWriter()
# Write primitive values
writer.write_int(42)
writer.write_str("Hello, Cardano!")
writer.write_bytes(b"\x01\x02\x03")
writer.write_bool(True)
writer.write_null()
# Get the encoded bytes
cbor_bytes = writer.encode()
cbor_hex = writer.to_hex()from cometa import CborReader, CborReaderState
# Create a reader from hex or bytes
reader = CborReader.from_hex("83010203") # Array [1, 2, 3]
# Check what type of data is next
state = reader.peek_state()
if state == CborReaderState.START_ARRAY:
length = reader.read_array_len()
for _ in range(length):
value = reader.read_uint()
print(value)
reader.read_array_end()Cometa.py provides comprehensive cryptographic primitives for Cardano development, including Ed25519 key pairs, BIP32 hierarchical deterministic keys, and BIP39 mnemonic support.
BIP39 mnemonics are human-readable word sequences that encode cryptographic entropy:
from cometa import mnemonic_to_entropy, entropy_to_mnemonic
# Convert a mnemonic phrase to entropy
mnemonic_words = [
"abandon", "abandon", "abandon", "abandon",
"abandon", "abandon", "abandon", "abandon",
"abandon", "abandon", "abandon", "about"
]
entropy = mnemonic_to_entropy(mnemonic_words)
print(f"Entropy: {entropy.hex()}")Cardano uses CIP-1852 for HD wallet key derivation:
from cometa import (
mnemonic_to_entropy,
Bip32PrivateKey,
harden,
BaseAddress,
NetworkId,
Credential
)
# Convert mnemonic to entropy
mnemonic = "your 24 word mnemonic phrase here...".split()
entropy = mnemonic_to_entropy(mnemonic)
# Create root key from entropy
root_key = Bip32PrivateKey.from_bip39_entropy(b"optional-passphrase", entropy)
# Derive account key using CIP-1852 path: m/1852'/1815'/0'
# 1852' = Cardano purpose, 1815' = ADA coin type, 0' = account 0
account_key = root_key.derive([
harden(1852), # Purpose
harden(1815), # Coin type (ADA)
harden(0) # Account index
])
# Get account public key (can be shared safely)
account_pub_key = account_key.get_public_key()
# Derive payment key: m/1852'/1815'/0'/0/0
payment_key = account_pub_key.derive([0, 0]) # External chain, address 0
# Derive staking key: m/1852'/1815'/0'/2/0
staking_key = account_pub_key.derive([2, 0]) # Staking chain, index 0
# Create credentials from key hashes
payment_credential = Credential.from_key_hash(
payment_key.to_ed25519_key().to_hash()
)
staking_credential = Credential.from_key_hash(
staking_key.to_ed25519_key().to_hash()
)
# Create a base address
address = BaseAddress.from_credentials(
NetworkId.MAINNET,
payment_credential,
staking_credential
)
print(f"Address: {address.to_bech32()}")Ed25519 signatures are used for transaction signing and message authentication:
from cometa import Ed25519PrivateKey, Ed25519PublicKey
# Create a private key (in practice, derive from HD wallet)
private_key = Ed25519PrivateKey.from_normal_bytes(bytes(32))
# Get the corresponding public key
public_key = private_key.get_public_key()
print(f"Public key hash: {public_key.to_hash().to_hex()}")
# Sign a message
message = b"Hello, Cardano!"
signature = private_key.sign(message)
print(f"Signature: {signature.to_hex()}")
# Verify the signature
is_valid = public_key.verify(signature, message)
print(f"Signature valid: {is_valid}")
# Verification fails with wrong message
is_valid = public_key.verify(signature, b"Wrong message")
print(f"Wrong message valid: {is_valid}") # FalseThe TxBuilder API allows you to override its core logic for coin selection and transaction evaluation. If these custom implementations are not provided, the builder uses the following defaults:
- Coin Selection: A "Largest First" strategy via
LargeFirstCoinSelector - Transaction Evaluation: A remote service via the configured Provider (e.g., Blockfrost)
The coin selector is responsible for choosing which UTxOs to spend to cover the value required by the transaction's outputs. You can provide your own strategy by implementing the CoinSelector protocol:
from typing import List
from cometa import Utxo, TransactionOutput, Value
class MyCoinSelector:
"""Custom coin selection strategy."""
@property
def name(self) -> str:
return "MyCustomSelector"
def select(
self,
pre_selected_utxo: List[Utxo],
available_utxo: List[Utxo],
target: Value,
) -> Tuple[List[Utxo], List[Utxo]]:
# Your custom selection logic here
...Attach your custom selector to the builder:
my_selector = MyCoinSelector()
builder.set_coin_selector(my_selector)The transaction evaluator calculates execution units (ExUnits) for Plutus scripts. Cometa.py provides two built-in options:
Option 1: Remote Evaluation with BlockfrostProvider
By default, when you pass a BlockfrostProvider to TxBuilder, it uses Blockfrost's remote evaluation API:
from cometa import BlockfrostProvider, NetworkMagic, TxBuilder
provider = BlockfrostProvider(
network=NetworkMagic.PREPROD,
project_id="YOUR_PROJECT_ID"
)
protocol_params = provider.get_parameters()
# Provider is used for both UTxO fetching and transaction evaluation
builder = TxBuilder(protocol_params, provider)
# Build transaction with Plutus scripts - evaluation happens remotely
tx = (
builder
.set_change_address(sender_address)
.set_utxos(utxos)
.add_input(script_utxo, redeemer=redeemer)
.add_script(script)
.build()
)Option 2: Local Evaluation with AikenTxEvaluator
For faster, offline evaluation, use AikenTxEvaluator:
from cometa import BlockfrostProvider, NetworkMagic, TxBuilder
from cometa.aiken import AikenTxEvaluator, SlotConfig
provider = BlockfrostProvider(
network=NetworkMagic.PREPROD,
project_id="YOUR_PROJECT_ID"
)
protocol_params = provider.get_parameters()
# Create local evaluator
evaluator = AikenTxEvaluator(
cost_models=protocol_params.cost_models,
slot_config=SlotConfig.preprod(),
max_tx_ex_units=protocol_params.max_tx_ex_units,
)
# Override the default provider-based evaluation
builder = TxBuilder(protocol_params, provider)
builder.set_evaluator(evaluator)
# Build transaction - evaluation happens locally
tx = (
builder
.set_change_address(sender_address)
.set_utxos(utxos)
.add_input(script_utxo, redeemer=redeemer)
.add_script(script)
.build()
)Option 3: Custom Evaluator
You can also implement your own evaluator by following the TxEvaluator protocol:
from typing import List, Optional
from cometa import Transaction, Utxo, Redeemer
class MyTxEvaluator:
"""Custom transaction evaluator."""
def get_name(self) -> str:
return "MyCustomEvaluator"
def evaluate(
self,
transaction: Transaction,
additional_utxos: Optional[List[Utxo]] = None,
) -> List[Redeemer]:
# Your custom evaluation logic here
...Attach your custom evaluator to the builder:
my_evaluator = MyTxEvaluator()
builder.set_evaluator(my_evaluator)While the underlying libcardano-c library has its own comprehensive test suite, Cometa.py maintains a separate, dedicated suite of tests. These binding-level tests verify the correctness of the Python-to-C interface and ensure the high-level API functions as expected.
To build and run the tests, use the following commands:
pip install -e ".[dev]"
pytestTo run the linter:
pylint src/cometaCometa.py is licensed under the Apache 2.0 License. See the LICENSE file for more information.