Pycardano - A lightweight Cardano client in Python

Overview

PyCardano


PyCardano is a standalone Cardano client written in Python. The library is able to create and sign transactions without depending on third-party Cardano serialization tools, such as cardano-cli and cardano-serialization-lib, making it a light-weight library that is easy and fast to set up in all kinds of environments.

Current goal of this project is to enable developers to write off-chain and testing code only in Python for their DApps. Nevertheless, we see the potential in expanding this project to a full Cardano node implementation in Python, which might be beneficial for faster R&D iterations.

Installation

Examples

Documentations


Development

Workspace setup

Clone the repository:

git clone https://github.com/cffls/pycardano.git

PyCardano uses poetry to manage its dependencies. Install poetry for osx / linux / bashonwindows:

curl -sSL https://raw.githubusercontent.com/python-poetry/poetry/master/get-poetry.py | python -

Go to poetry installation for more details.

Change directory into the repo, install all dependencies using poetry, and you are done!

cd pycardano && poetry install

Test

PyCardano uses pytest for unit testing.

When testing or running any program, it is recommended to enter a poetry shell in which all python dependencies are automatically configured: poetry shell.

Run all tests:

pytest

Run all tests including doctests:

pytest --doctest-modules

Run all tests in a specific test file:

pytest test/pycardano/test_transaction.py

Run a specific test function:

pytest -k "test_transaction_body"

Run a specific test function in a test file:

pytest test/pycardano/test_transaction.py -k "test_transaction_body"

Test coverage

Test coverage could be checked by running:

pytest --cov=pycardano

A coverage report visualized in html could be generated by running:

pytest --cov=pycardano --cov-report html:cov_html

The generated report will be in folder ./cov_html.

Style guidelines

The package uses Google style docstring.

The code style could be checked by flake8: flake8 pycardano

Docs generation

The majority of package documentation is created by the docstrings in python files. We use sphinx with Read the Docs theme to generate the html pages.

Build htmls:

cd docs && make html

Go to the main page:

open build/html/index.html

Roadmap

Feature Status
Shelly address Supported
Transaction builder Supported
Transaction signing Supported
Multi-asset minting Planned
Native script Planned
Plutus Planned
Byron Address Planned
Reward withdraw Planned
HD Wallet
Staking certificates
Protocol proposal update
Comments
  • Blockfrost ApiError 5 ADA redeeming transaction fortytwo.py example

    Blockfrost ApiError 5 ADA redeeming transaction fortytwo.py example

    Describe the bug Sending 10ADA to the fortytwo plutus script on TESTNET works fine. However, when I attempt to redeem 5ADA from the plutus script I get a blockfrost ApiError.

    To Reproduce fortytwo_test.py script under Additional context

    Logs Traceback (most recent call last): File "fortytwo_test.py", line 120, in submit_tx(signed_tx) File "fortytwo_test.py", line 42, in submit_tx chain_context.submit_tx(tx.to_cbor()) File "/home/zlac116/anaconda3/envs/cardanopy/lib/python3.8/site-packages/pycardano/backend/blockfrost.py", line 205, in submit_tx self.api.transaction_submit(f.name) File "/home/zlac116/anaconda3/envs/cardanopy/lib/python3.8/site-packages/blockfrost/utils.py", line 63, in error_wrapper raise ApiError(request_response) blockfrost.utils.ApiError: {'error': 'Bad Request', 'message': '"transaction submit error ShelleyTxValidationError ShelleyBasedEraBabbage (ApplyTxError [UtxowFailure (FromAlonzoUtxowFail (PPViewHashesDontMatch (SJust (SafeHash \"0067cdab1a1ae15069bf96bd33cbc059ec3a8677ab198b56081e6716016b0410\")) (SJust (SafeHash \"2b25c2e22a3a08ca6d9fa0bcf299dff8ab2d5cbe06d5c2c5e255af46bb16cafd\"))))])"', 'status_code': 400}

    Expected behavior 5ADA is sent to the taker address

    Environment and software version (please complete the following information):

    • OS: Ubuntu 20.04.3 LTS
    • PyCardano Version 0.6.0
    • Python Version 3.8.12

    Additional context python script: fortytwo_test.py

    '''
    Off-chain code of taker and giver in fortytwo
    
    '''
    import os
    import cbor2
    from retry import retry
    from dotenv import load_dotenv
    
    import pycardano as pc
    
    load_dotenv()
    
    NETWORK = pc.Network.TESTNET
    
    def get_env_val(key):
        val = os.getenv(key)
        if not val:
            raise Exception(f"Environment variable {key} is not set!")
        return val
    
    payment_skey = pc.PaymentSigningKey.load(get_env_val("PAYMENT_KEY_PATH"))
    payment_vkey = pc.PaymentVerificationKey.from_signing_key(payment_skey)
    
    stake_skey = pc.StakeSigningKey.load(get_env_val('STAKE_KEY_PATH'))
    stake_vkey = pc.StakeVerificationKey.from_signing_key(stake_skey)
    
    chain_context = pc.BlockFrostChainContext(
        project_id=get_env_val("BLOCKFROST_ID"), network=NETWORK
    )
    
    @retry(delay=20)
    def wait_for_tx(tx_id):
        chain_context.api.transaction(tx_id)
        print(f"Transaction {tx_id} has been successfully included in the blockchain.")
    
    def submit_tx(tx):
        print("############### Transaction created ###############")
        print(tx)
        print(tx.to_cbor())
        print("############### Submitting transaction ###############")
        chain_context.submit_tx(tx.to_cbor())
        wait_for_tx(str(tx.id))
    
    
    def utxos(address):
        amount = sum([i.output.amount.coin for i in chain_context.utxos(str(address)) if not i.output.amount.multi_asset is None]) / 1e6
        print(f'UTXO value: {amount} ADA')
    
    def find_collateral(target_address):
        for utxo in chain_context.utxos(str(target_address)):
            # A collateral should contain no multi asset
            if not utxo.output.amount.multi_asset:
                return utxo
        return None
    
    def create_collateral(target_address, skey):
        collateral_builder = pc.TransactionBuilder(chain_context)
    
        collateral_builder.add_input_address(target_address)
        collateral_builder.add_output(pc.TransactionOutput(target_address, 5000000))
    
        submit_tx(collateral_builder.build_and_sign([skey], target_address))
    
    # ----------- Giver sends 10 ADA to a script address ---------------
    with open("fortytwo.plutus", "r") as f:
        script_hex = f.read()
        forty_two_script = cbor2.loads(bytes.fromhex(script_hex))
    
    script_hash = pc.plutus_script_hash(forty_two_script)
    
    script_address = pc.Address(script_hash, network=NETWORK)
    
    giver_address = pc.Address(payment_vkey.hash(), stake_vkey.hash(), network=NETWORK)
    
    builder = pc.TransactionBuilder(chain_context)
    builder.add_input_address(giver_address)
    datum = pc.PlutusData()  # A Unit type "()" in Haskell
    builder.add_output(
        pc.TransactionOutput(script_address, 10000000, datum_hash=pc.datum_hash(datum))
    )
    
    utxos(giver_address)
    
    signed_tx = builder.build_and_sign([payment_skey], giver_address)
    
    submit_tx(signed_tx)
    
    
    
    # ----------- Taker takes 10 ADA from the script address ---------------
    
    # taker_address could be any address. In this example, we will use the same address as giver.
    taker_address = giver_address
    
    # Notice that transaction builder will automatically estimate execution units (num steps & memory) for a redeemer if
    # no execution units are provided in the constructor of Redeemer.
    # Put integer 42 (the secret that unlocks the fund) in the redeemer.
    redeemer = pc.Redeemer(pc.RedeemerTag.SPEND, 42)
    
    utxo_to_spend = chain_context.utxos(str(script_address))[1]
    
    builder = pc.TransactionBuilder(chain_context)
    builder.add_script_input(utxo_to_spend, forty_two_script, datum, redeemer)
    
    # Send 5 ADA to taker address. The remaining ADA (~4.7) will be sent as change.
    take_output = pc.TransactionOutput(taker_address, 5000000)
    builder.add_output(take_output)
    
    non_nft_utxo = find_collateral(taker_address)
    
    if non_nft_utxo is None:
        create_collateral(taker_address, payment_skey)
        non_nft_utxo = find_collateral(taker_address)
    
    builder.collaterals.append(non_nft_utxo)
    
    signed_tx = builder.build_and_sign([payment_skey], taker_address)
    
    submit_tx(signed_tx)
    utxos(giver_address)
    
    bug 
    opened by zlac116 16
  • TransactionBuilder doesn't work if there's no index 0 utxo input

    TransactionBuilder doesn't work if there's no index 0 utxo input

    Hello there, hope all is smooth.

    I'm running into an issue with the builder.. seems I need > 1 utxo in order to send ada to an address.

    Successful tx has utxo data like this:

    [{'input': {'index': 1,
     'transaction_id': TransactionId(hex='41cb004bec7051621b19b46aea28f0657a586a05ce2013152ea9b9f1a5614cc7')},
     'output': {'address': addr1qytqt3v9ej3kzefxcy8f59h9atf2knracnj5snkgtaea6p4r8g3mu652945v3gldw7v88dn5lrfudx0un540ak9qt2kqhfjl0d,
     'amount': 2991353,
     'datum_hash': None}}, {'input': {'index': 0,
     'transaction_id': TransactionId(hex='ed2d5e7738f12dfbf988b8f634812b26dd805e53fa633c0d4d2d8df6e2a74596')},
     'output': {'address': addr1qytqt3v9ej3kzefxcy8f59h9atf2knracnj5snkgtaea6p4r8g3mu652945v3gldw7v88dn5lrfudx0un540ak9qt2kqhfjl0d,
     'amount': 1000000,
     'datum_hash': None}}]
    

    Unsuccessful utxo data looks like this:

    [{'input': {'index': 1,
     'transaction_id': TransactionId(hex='9d255cdacd8a575ee86f4ad0a61b14c7be037c623059f71b1bc9ce8d4e53cb6c')},
     'output': {'address': addr1qytqt3v9ej3kzefxcy8f59h9atf2knracnj5snkgtaea6p4r8g3mu652945v3gldw7v88dn5lrfudx0un540ak9qt2kqhfjl0d,
     'amount': 2821804,
     'datum_hash': None}}]
    
    

    So, I suspect the problem comes when there's no index[0] in the list.. and I get this UTxOSelectionException error:

    Traceback (most recent call last):
      File "/Library/Frameworks/Python.framework/Versions/3.10/lib/python3.10/site-packages/pycardano/txbuilder.py", line 658, in build
        selected, _ = selector.select(
      File "/Library/Frameworks/Python.framework/Versions/3.10/lib/python3.10/site-packages/pycardano/coinselection.py", line 109, in select
        additional, _ = self.select(
      File "/Library/Frameworks/Python.framework/Versions/3.10/lib/python3.10/site-packages/pycardano/coinselection.py", line 94, in select
        raise InsufficientUTxOBalanceException("UTxO Balance insufficient!")
    pycardano.exception.InsufficientUTxOBalanceException: UTxO Balance insufficient!
    
    During handling of the above exception, another exception occurred:
    
    Traceback (most recent call last):
      File "/Users/zzzz/a/carpy-js/python/createtx.py", line 30, in <module>
        signed_tx = builder.build_and_sign([sk], change_address=address)
      File "/Library/Frameworks/Python.framework/Versions/3.10/lib/python3.10/site-packages/pycardano/txbuilder.py", line 767, in build_and_sign
        tx_body = self.build(change_address=change_address)
      File "/Library/Frameworks/Python.framework/Versions/3.10/lib/python3.10/site-packages/pycardano/txbuilder.py", line 690, in build
        raise UTxOSelectionException(
    pycardano.exception.UTxOSelectionException: All UTxO selectors failed.
    Requested output:
     {'coin': 1158901, 'multi_asset': {}} 
    Pre-selected inputs:
     {'coin': 0, 'multi_asset': {}} 
    Additional UTxO pool:
     [{'input': {'index': 1,
     'transaction_id': TransactionId(hex='41cb004bec7051621b19b46aea28f0657a586a05ce2013152ea9b9f1a5614cc7')},
     'output': {'address': addr1qytqt3v9ej3kzefxcy8f59h9atf2knracnj5snkgtaea6p4r8g3mu652945v3gldw7v88dn5lrfudx0un540ak9qt2kqhfjl0d,
     'amount': 2991353,
     'datum_hash': None}}] 
    Unfulfilled amount:
     {'coin': -1832452, 'multi_asset': {}}
    

    My script is essentially the same as your example:

    from pycardano import BlockFrostChainContext, Network, PaymentSigningKey, PaymentVerificationKey, Address, TransactionBuilder, TransactionOutput, Value
    
    network = Network.MAINNET
    context = BlockFrostChainContext("mainnetqEZ4wDDoRdtWqh2SNVLNqfQbhlNmTbza", network)
    
    sk = PaymentSigningKey.from_cbor('abcdef0123456789')
    vk = PaymentVerificationKey.from_signing_key(sk)
    address = Address.from_primitive('addr1qytqt3v9ej3kzefxcy8f59h9atf2knracnj5snkgtaea6p4r8g3mu652945v3gldw7v88dn5lrfudx0un540ak9qt2kqhfjl0d')
    
    builder = TransactionBuilder(context)
    builder.add_input_address(address)
    utxos = context.utxos(str(address))
    
    # builder.add_input(utxos[0])
    builder.add_output(
        TransactionOutput(
            Address.from_primitive(
    "addr1qyady0evsaxqsfmz0z8rvmq62fmuas5w8n4m8z6qcm4wrt3e8dlsen8n464ucw69acfgdxgguscgfl5we3rwts4s57ashysyee"
            ),
            Value.from_primitive(
                [
                    1000000,
                ]
            ),
        )
    )
    signed_tx = builder.build_and_sign([sk], change_address=address)
    context.submit_tx(signed_tx.to_cbor())
    
    bug 
    opened by 34r7h 14
  • decodeVerKeyDSIGN: wrong length, expected 32 bytes but got 0

    decodeVerKeyDSIGN: wrong length, expected 32 bytes but got 0

    I am trying to make a simple transaction (lovelace only) using generated payment keys.

    CHAIN_CONTEXT = BlockFrostChainContext(
      project_id=BLOCKFROST_PROJECT_ID,
      network=Network.TESTNET,
      base_url="https://cardano-preprod.blockfrost.io/api",
    )
    
    payment_key_pair = PaymentKeyPair.generate()
    payment_signing_key = payment_key_pair.signing_key
    payment_verification_key = payment_key_pair.verification_key
    
    stake_key_pair = StakeKeyPair.generate()
    stake_signing_key = stake_key_pair.signing_key
    stake_verification_key = stake_key_pair.verification_key
    
    address = Address(payment_part=payment_verification_key.hash(),
                             staking_part=stake_verification_key.hash(),
                             network=Network.TESTNET)
    
    toAddress = "..."
    ...
    builder = TransactionBuilder(CHAIN_CONTEXT)
    builder.add_input_address(address)
    builder.add_output(TransactionOutput.from_primitive([toAddress, 10000000]))
    builder.build_and_sign([payment_signing_key], change_address=address)
    
    

    The tx can be built, but submitting it to Blockfrost throws this error

    blockfrost.utils.ApiError: {'error': 'Bad Request', 'message': '"transaction read error RawCborDecodeError [DecoderErrorDeserialiseFailure \\"Byron Tx\\" (DeserialiseFailure 1 \\"Size mismatch when decoding TxAux.\\\\nExpected 2, but found 4.\\"),DecoderErrorDeserialiseFailure \\"Shelley Tx\\" (DeserialiseFailure 1 \\"Size mismatch when decoding \\\\nRecord RecD.\\\\nExpected 4, but found 3.\\"),DecoderErrorDeserialiseFailure \\"Shelley Tx\\" (DeserialiseFailure 1 \\"Size mismatch when decoding \\\\nRecord RecD.\\\\nExpected 4, but found 3.\\"),DecoderErrorDeserialiseFailure \\"Shelley Tx\\" (DeserialiseFailure 1 \\"Size mismatch when decoding \\\\nRecord RecD.\\\\nExpected 4, but found 3.\\"),DecoderErrorDeserialiseFailure \\"Shelley Tx\\" (DeserialiseFailure 193 \\"decodeVerKeyDSIGN: wrong length, expected 32 bytes but got 0\\"),DecoderErrorDeserialiseFailure \\"Shelley Tx\\" (Deseria', 'status_code': 400}
    

    I can see it hast to do with the verification key and I bet I am doing something wrong. Any idea what it is? 🙇

    bug 
    opened by robinboening 10
  • Fixing inconsistency between generated entropy value type and the expected HDWallet.entropy value type

    Fixing inconsistency between generated entropy value type and the expected HDWallet.entropy value type

    mnemonic Python package generates an entropy value in bytearray type. However, HDWallet.entropy class is expecting a string value and it is used as a string throughout the entire bip32.py module codebase.

    This merge request explicitly converts generated entropy value as a string value to be consistent with the original implementer's intention.

    opened by daehan-koreapool 9
  • Unfulfilled amount error

    Unfulfilled amount error

    Hello Jerry. Hope all is good.

    Maybe this is solved in a recent commit but pip is still giving me version 0.5.0. Please push if you tackled this.

    I'm still getting funky errors on creating txs with addresses that have one or few txs. The index of available inputs is out of line. I'm guessing this is a problem with blockfrost's context. One address has 2 txs, indexed at 1 and 3 respectively. I'm pretty sure that's causing txs to fail.

    Screen Shot 2022-06-29 at 19 23 38 Screen Shot 2022-06-29 at 19 23 22
    opened by 34r7h 9
  • Add message signing and verification

    Add message signing and verification

    This PR adds functionality for both the signing and verification of messages as layed out in CIP-0008. It also attempts to follow CIP-0030 such that messages signed by both pycardano vkeys and dApp wallets could be verified in python. There is no consensus yet on the exact details of how this should work, and currently there are two approaches both of which are addressed here.

    The aforementioned CIPs use COSE (CBOR Object Signing and Encryption), the algorithms for which are already written in a well-maintained python repository called pycose (poetry add cose).

    Later goals include generating and verifying JWT-like tokens generated by web3-cardano-token.

    The current approach

    I just wanted to get some code out, and my guess is that the current approach will need to go through significant changes to increase ease of use.

    I've created a class called Message which accepts either message: str or signed_message: Union[str, dict]. If you want to sign a message with pycardano initialize the class with message, and then use Message.sign() to sign it.

    If you want to verify an already-signed message. initialize it with signed_message and then use Message.verify().

    The parameter signed_message accepts both str and dict because as of now, there are two main ways of signing messages employed by browser wallets:

    1. You include the wallet's verification key in the header of the signature (Eternl and Flint). the final result is a hex string.
    2. You include the key separately and return a dict of the form {"signature": signature, "key": key}. (Nami)

    In order to specify which one you want while signing or verifying, use the parameter cose_key_separate: bool to decide whether or not to attach it to the cose message header (case 1) or keep it separate (case 2). See the added tests for examples. (Couldn't think of a better name for this parameter...)

    I feel like this isn't the most elegant solution for implementing this, but it's the best I could think of at the moment. Let me know if you have suggestions or ideas!

    opened by astrojarred 9
  • How to use payment extended signing key

    How to use payment extended signing key

    Hi,

    I would like to use your library. I create my own keys with the script written below. In the end, my payment and stake signing keys are extended versions. I think it's caused by using mnemonics to generate keys. However, in the library, NaCL cannot use them.

    Do you have any idea to use or convert PaymentExtendedSigningKeyShelley_ed25519_bip32?

    #Root Private Key
    cardano-wallet key from-recovery-phrase Shelley < mnemonics.prv > root.prv
    
    #Root Public Key
    cardano-address key public --with-chain-code < root.prv > root.pub
    
    # Address Private and Public Keys
    cat root.prv \
      | cardano-wallet key child 1852H/1815H/0H/0/0 \
      | tee address.prv \
      | cardano-wallet key public --with-chain-code > address.pub
    cardano-cli key convert-cardano-address-key --shelley-payment-key --signing-key-file address.prv --out-file payment.skey
    cardano-cli key verification-key --signing-key-file payment.skey --verification-key-file extended_payment.vkey
    cardano-cli key non-extended-key --extended-verification-key-file extended_payment.vkey --verification-key-file payment.vkey
    
    #Staking Private and Public Keys
    cat root.prv \
      | cardano-wallet key child 1852H/1815H/0H/2/0 \
      | tee staking.prv \
      | cardano-wallet key public --with-chain-code > staking.pub
    cardano-cli key convert-cardano-address-key --shelley-stake-key --signing-key-file staking.prv --out-file stake.skey
    cardano-cli key verification-key --signing-key-file stake.skey --verification-key-file extended_stake.vkey
    cardano-cli key non-extended-key --extended-verification-key-file extended_stake.vkey --verification-key-file stake.vkey
    
    cardano-cli address build --payment-verification-key-file payment.vkey --stake-verification-key-file stake.vkey --out-file address.addr --mainnet
    
    question Feature request 
    opened by miracatici 9
  • Add a utility to conveniently create UTxO from the return of CIP30 api's getUtxos.

    Add a utility to conveniently create UTxO from the return of CIP30 api's getUtxos.

    This PR is to:

    • make it easier to convert the utxos coming from the frontend via CIP30 api

    I got this idea while looking for a way to save on blockfrost api calls, & a way to get utxos from the account itself to use in the tx builder as an alternative to add_input_address when an unused address i provided.

    opened by markrufino 8
  • Error sending simple Tx of tADA

    Error sending simple Tx of tADA

    Describe the bug I'm trying to send some tADA on PreProd context but I got this error. If I try to send the same CBOR signed Tx using curl and Blockfrost API I get the same error.

    To Reproduce My code (it is a simple example test):

    
    NETWORK = Network.TESTNET
    
    context = BlockFrostChainContext(
        project_id="preproXXXXXXXXXXXXXXXXXXXXXX",
        network=NETWORK,
        base_url="https://cardano-preprod.blockfrost.io/api",
    )
    
    # Read keys to memory
    # Assume there is a payment.skey file sitting in current directory
    psk = PaymentSigningKey.load("payment.skey")
    pvk = PaymentVerificationKey.from_signing_key(psk)
    
    # Wallet in the node (sender)
    wallet = "addr_test1vrppcwynx539fvwpppXXXXXXXXXXXXXXXXX"
    
    # Create a transaction builder
    builder = TransactionBuilder(context)
    
    # Tell the builder that transaction input will come from a specific address, assuming that there are some ADA and native
    builder.add_input_address(wallet)
    
    # Get all UTxOs currently sitting at this address
    utxos = context.utxos(wallet)
    
    builder.add_input(utxos[0])
    
    # Nami Wallet
    nami ="addr_test1qr6uhu9ru5v0XXXXXXXXXXXXXXXX"
    
    # Send 1.5 ADA to a Nami address.
    builder.add_output( TransactionOutput.from_primitive([nami, 1500000]))
    
    # Create final signed transaction
    signed_tx = builder.build_and_sign([psk], change_address=billetera)
    
    print("############### Transaction created ###############")
    print(f"Tx: {signed_tx}")
    print(f"Signed Tx: {signed_tx.to_cbor()}")
    
    # Submit signed transaction to the network
    print("############### Submitting transaction ###############")
    context.submit_tx(signed_tx.to_cbor())
    

    Error

    
    blockfrost.utils.ApiError: {'error': 'Bad Request', 'message': '"transaction read error RawCborDecodeError [DecoderErrorDeserialiseFailure \\"Byron Tx\\" (DeserialiseFailure 1 \\"Size mismatch when decoding TxAux.\\\\nExpected 2, but found 4.\\"),DecoderErrorDeserialiseFailure \\"Shelley Tx\\" (DeserialiseFailure 1 \\"Size mismatch when decoding \\\\nRecord RecD.\\\\nExpected 4, but found 3.\\"),DecoderErrorDeserialiseFailure \\"Shelley Tx\\" (DeserialiseFailure 1 \\"Size mismatch when decoding \\\\nRecord RecD.\\\\nExpected 4, but found 3.\\"),DecoderErrorDeserialiseFailure \\"Shelley Tx\\" (DeserialiseFailure 1 \\"Size mismatch when decoding \\\\nRecord RecD.\\\\nExpected 4, but found 3.\\"),DecoderErrorDeserialiseFailure \\"Shelley Tx\\" (DeserialiseFailure 108 \\"expected string\\"),DecoderErrorDeserialiseFailure \\"Shelley Tx\\" (DeserialiseFailure 108 \\"expected string\\")]"', 'status_code': 400}
    

    Environment and software version (please complete the following information):

    • OS: [Ubuntu 20.04.3 LTS]
    • PyCardano Version [0.6.2]
    opened by QuixoteSystems 7
  • Merge change into already existing output when possible

    Merge change into already existing output when possible

    The proposed changes do the following:

    • If the change address is already included in one of the outputs, the change (minus the fee) will be added to last output directed towards the change address.
    • If no outputs are provided but a change address is provided, the entirety of all input UTxOs will be sent to the change address
    • If you want to force the change address into a separate output, create a dummy output like so as the last output added builder.add_output(TransactionOutput(change_address, 0)). This will force the change into a new output UTxO even if the change address is already included among the outputs.

    FYI I also added a method to transaction.Output called copy() which essentially deep copies itself. I found it useful for estimating the fees but I'm definitely open to other ways of doing this.

    @cffls I updated all the relevant tests in test_txbuilder.py to reflect the new fees and outputs. If you have the chance to take a look and make sure I didn't fundamentally alter the purpose of any of them... Especially test_tx_add_change_split_nfts as I couldn't figure out exactly what it was trying to achieve.

    opened by astrojarred 7
  • Can't submit transaction with raw json datum

    Can't submit transaction with raw json datum

    I am trying to make a transaction with datum attached to output (not spend a plutus UTxO, but like making a regular transaction with --tx-out-datum-embed-file from cardano-cli). This is required when submitting to many of the existing smart contracts on Cardano. This is my attempt:

    
    network = pc.Network.TESTNET 
    psk = pc.PaymentExtendedSigningKey.load("testnet-keys/00.skey")
    ssk = pc.StakeExtendedSigningKey.load('testnet-keys/stake.skey')
    pvk = pc.PaymentExtendedVerificationKey.from_signing_key(psk)
    svk = pc.StakeExtendedVerificationKey.from_signing_key(ssk)
    address = pc.Address(pvk.hash(), svk.hash(), network)
    context = pc.BlockFrostChainContext(my_token, network)
    builder = pc.TransactionBuilder(context)
    
    builder.add_input_address(address)
    
    datum = {b'fields': [], b'constructor': 0}
    datum_hash = pc.DatumHash(blake2b(cbor2.dumps(datum, default=default_encoder), 32, encoder=RawEncoder))
    
    
    builder.add_output(pc.TransactionOutput(pc.Address.from_primitive("addr_test1vrm9x2zsux7va6w892g38tvchnzahvcd9tykqf3ygnmwtaqyfg52x"),
                             amount=pc.Value(10000000), datum_hash=datum_hash))
    
    
    tx_body = builder.build(change_address=address)
    signature = psk.sign(tx_body.hash())
    vk_witnesses = [pc.VerificationKeyWitness(pvk, signature)]
    tx = pc.Transaction(tx_body, pc.TransactionWitnessSet(vkey_witnesses=vk_witnesses,
                               plutus_data=[datum]))
    
    context.submit_tx(tx.to_cbor())
    

    It results in a node error on submission saying the fees were miscalculated:

    ApiError: {'error': 'Bad Request', 'message': '"transaction submit error ShelleyTxValidationError ShelleyBasedEraAlonzo (ApplyTxError [UtxowFailure (PPViewHashesDontMatch SNothing (SJust (SafeHash \\"7e58e4a25bc56a14475e7461cda5aeeaf59fff97285560887a9eedd2ddea1d9f\\"))),UtxowFailure (WrappedShelleyEraFailure (UtxoFailure (FeeTooSmallUTxO (Coin 169241) (Coin 168317))))])"', 'status_code': 400}
    

    I can temporarily fix this by making sure datum is included when calculating fee (this will need a proper fix):

    class MyTransactionBuilder(pc.TransactionBuilder):     
        def _build_fake_witness_set(self) -> pc.TransactionWitnessSet:
            return pc.TransactionWitnessSet(
                vkey_witnesses=self._build_fake_vkey_witnesses(),
                native_scripts=self.native_scripts,
                plutus_data=self.plutus_data
            )
    # ... add stuff to builder
    builder.plutus_data = [datum]
    # .. build tx and submit
    

    Then I am left with this error:

    ApiError: {'error': 'Bad Request', 'message': '"transaction submit error ShelleyTxValidationError ShelleyBasedEraAlonzo (ApplyTxError [UtxowFailure (PPViewHashesDontMatch SNothing (SJust (SafeHash \\"7e58e4a25bc56a14475e7461cda5aeeaf59fff97285560887a9eedd2ddea1d9f\\")))])"', 'status_code': 400}
    

    A quick google search indicate that this might have something to do with the cost model not being in protocol params. It doesn't look like the protocol params are used anywhere when creating the transaction with TransactionBuilder ? I know Plutus is not all supported yet in PyCardano, but I thought adding a raw json datum to the transaction would be different.

    • PyCardano Version 0.2.0

    Thanks.

    opened by grananqvist 7
  •  `add_inputs`, `add_outputs` & `apply` method to facilitate method chaining

    `add_inputs`, `add_outputs` & `apply` method to facilitate method chaining

    Motivation

    To facilitate method chaining syntax when building transactions. Without apply it is less convenient to apply certain logic to a tx. The proposed .apply method takes a callback function to apply to the transaction.

    A minimal working example

    from pycardano import (
      BlockFrostChainContext,
      Network,
      PaymentSigningKey,
      PaymentVerificationKey,
      Address,
      TransactionBuilder,
      Transaction,
      TransactionOutput
    )
    
    chain_context = BlockFrostChainContext(
      project_id = "preprodAVsU2w89ixOk15AIOsq4pgzEGKBrv7Rd",
      network = Network.TESTNET,
      base_url = "https://cardano-preprod.blockfrost.io/api/"
    )
    
    usr1_skey = PaymentSigningKey.load(f"keys/usr1.skey")
    usr1_vkey = PaymentVerificationKey.from_signing_key(usr1_skey)
    usr1_addr = Address(usr1_vkey.hash(), network=Network.TESTNET)
    
    usr2_skey = PaymentSigningKey.load(f"keys/usr2.skey")
    usr2_vkey = PaymentVerificationKey.from_signing_key(usr2_skey)
    usr2_addr = Address(usr2_vkey.hash(), network=Network.TESTNET)
    
    
    class CustomTxBuilder:
    
      def add_utxos_with_ten_ada(self, builder):
        # a callback function with arbitrary logic to apply to the tx.
        # in this example a builder parameter must be added to accept builder1/builder2.
        # the callback shouldn't return anything (None), as nothing will be done
        # with the return value.
        for utxo in chain_context.utxos(str(usr1_addr)):
          if utxo.output.amount == 10_000_000:
            builder.add_input(utxo)
    
    
      def construct_tx1(self) -> Transaction:
        builder1 = TransactionBuilder(chain_context)
        (
        builder1
          # .add_input(chain_context.utxos(str(usr1_addr))) # list of UTxOs
          .apply(lambda: self.add_utxos_with_ten_ada(builder1))
          .add_output(
            TransactionOutput(
              address = usr2_addr,
              amount = 20_000_000
            )
          ) 
          .add_output(
            TransactionOutput(
              address = usr2_addr,
              amount = 20_000_000
            )
          ) 
        )
        return builder1.build_and_sign(signing_keys=[usr1_skey], change_address=usr1_addr)
    
      def construct_tx2(self) -> Transaction:
        builder2 = TransactionBuilder(chain_context)
        # etc.
    
    
    custom_builder = CustomTxBuilder()
    signed_tx = custom_builder.construct_tx1()
    print(signed_tx.transaction_body.inputs)
    chain_context.submit_tx(signed_tx.to_cbor())
    print("Tx submitted.")
    
    enhancement 
    opened by Et9797 11
  • Docs for adding arbitrary datum value to .add_output method

    Docs for adding arbitrary datum value to .add_output method

    This works:

    builder.add_output(TransactionOutput.from_primitive([outaddress, 99900000]))
    

    But now I would like to add some arbitrary datum value to my output UTXO. This doesn't work:

    builder.add_output(TransactionOutput.from_primitive([outaddress, 99900000, datum="my_value"]))
    

    Neither does:

    builder.add_output(TransactionOutput(Datum.from_primitive("my_value")))
    
    documentation 
    opened by peterVG 0
  • Add support for Imperator scripts (including evaluation internally)

    Add support for Imperator scripts (including evaluation internally)

    Is your feature request related to a problem? Please describe. Cardano developers may have a hard time debugging their script, as they need to understand the whole Haskell/Pluto stack.

    Describe the solution you'd like Imperator/pyscc provides a compiler from a subset of Python into UPLC, in other words a programming language for Smart Contracts on Cardano, that IS python.

    When adding an Imperator script as a script input to a transaction in pycardano, this means that we could offer to evaluate the script during transaction building and signing. This includes being able to step through the Script as it is being evaluated with a normal python debugger.

    What we need for this?

    • A flag to signify an added script input is actually an imperator script (and the corresponding source file)
    • A hook inside the transaction builder that calls the imperator script with the correct parameters (i.e. ScriptContext etc)
    • Optimally: Imperator/pycardano using the same definition for PlutusData

    Describe alternatives you've considered None

    Additional context I am developing Imperator/pyscc and can provide the parts of support that are needed on that end. Currently, I am looking into integrating the pycardano definitions of PlutusData and looking for a place where I could add the evaluation hook.

    enhancement Feature request 
    opened by nielstron 1
  • Add wallet classes

    Add wallet classes

    This is still a WIP but I wanted to get the ball rolling on this! Lots of basic functionality is now here, along with the first few tests and examples. See /examples/wallet.py for some examples of the most basic functionality.

    Current capabilities:

    • [x] Generate keys
    • [x] Load keys
    • [x] Query utxos
    • [x] Send ada
    • [x] Send specific UTxOs
    • [x] Get senders of all UTxOs
    • [x] Get metadata for all held tokens
    • [x] Get utxo block times and sort utxos
    • [x] Mint / Burn tokens
    • [x] Automatically load in token polices where wallet is a signer
    • [x] Automatically create BlockFrost Chain Context (mainnet, preprod, and preview)
    • [x] Attach messages to transactions
    • [x] Sign messages
    • [x] Add custom metadata fields
    • [x] Multi-output transactions
    • [x] Register wallet
    • [x] Stake wallet
    • [x] Withdraw rewards
    • [x] Generate fully manual transactions that can do any / all of the above
    • [x] Integrate strict type checking
    • [ ] Write tests
    • [ ] Add more examples

    Future additions (possibly later PRs):

    • [ ] Draft and sign multi-sig transactions
    • [ ] Interaction with native scripts
    • [ ] Interaction with plutus scripts
    • [ ] Load wallets with mnemonic phrase (integrating HDWallet)

    Now currently figuring out how to write tests for the transaction methods, using test_transaction.py for inspiration.

    enhancement 
    opened by astrojarred 10
Releases(v0.7.2)
  • v0.7.2(Dec 5, 2022)

  • v0.7.1(Nov 23, 2022)

    [0.7.1] - 2022-11-23

    A major improvement of this version is the enforcement of static typing on some modules. Special thanks to daehan-koreapool!

    Implemented enhancements:

    Fixed bugs:

    • decodeVerKeyDSIGN: wrong length, expected 32 bytes but got 0 #113

    Closed issues:

    • Document how to add reference_inputs when using TransactionBuilder #118
    • config option to choose local cardano-node for transactions #102

    Merged pull requests:

    Source code(tar.gz)
    Source code(zip)
  • v0.7.0(Nov 8, 2022)

    [0.7.0] - 2022-10-16

    Added

    • Support HDWallets and mnemonic phrases. (#85)

    Fixed

    • Fix key error when there are duplicates in reference scripts.
    • If merging change into existing outputs is enabled, do not enforce min_utxo on changes.
    • Make script estimation more accurate.
    Source code(tar.gz)
    Source code(zip)
  • v0.6.3(Oct 11, 2022)

    [0.6.3] - 2022-10-02

    Added

    • Support cbor serializable for UTxO. (#84)

    Fixed

    • Add required signers as part of fee estimation.
    • Fix insufficient min_utxo amount when using Blockfrost context.

    Changed

    • Change the default calculation of min_lovelace to Vasil era. This is a backward compatible change, and it will reduce the amount of min_lovelace required for transactions.
    Source code(tar.gz)
    Source code(zip)
  • v0.6.2(Sep 15, 2022)

  • v0.6.1(Sep 13, 2022)

  • v0.6.0(Aug 29, 2022)

    [0.6.0] - 2022-08-28

    v0.6.0 is update for Vasil hard fork.

    Added

    • Support for reference inputs (CIP31).
    • Support for inline datum (CIP32).
    • Support for reference scripts (CIP33).
    • Vasil changes for Ogmios.
    • Vasil changes for blockforst.
    • Add type "RawPlutusData", which is used as the default type for datum deserialized from cbor.
    • TransactionOutput now has two new fields, datum and script, which could be added to the transaction output.
    • Blockfrost chain context now supports custom API url.

    Changed

    • Improved the format of transaction representation.
    • Method add_script_input in TransactionBuilder no longer requires script field to be set. If absent, the transaction builder will try to find it from chain context.
    • Similarly, method add_minting_script in TransactionBuilder no longer requires script field to be set. If absent, the transaction builder will try to find it from chain context.
    Source code(tar.gz)
    Source code(zip)
  • v0.5.1(Jul 10, 2022)

  • v0.5.0(Jun 15, 2022)

    [0.5.0] - 2022-06-15

    Added

    • Staking certificates.
    • Add an option to merge change into already existing output. (#38).
    • Enable UTxO query with Kupo (#39).
    • Add 'add_minting_script' to txbuilder.
    • Add usage guides for Plutus (#46).
    • Add message signing and verification (CIP8) (#45).

    Changed

    • amount in TransactionOutput will be converted to type Value even when an int is passed in (#42).
    • Add unknown fields to ArraySerializable if more values are provided.

    Fixed

    • Prevent 'Transaction.from_cbor' from dropping data in datum.
    • Add fake fee to fake transaction when fee is 0.
    Source code(tar.gz)
    Source code(zip)
  • v0.4.1(May 4, 2022)

  • v0.4.0(May 1, 2022)

    Added

    • Support mint redeemer
    • Add execution units estimation
    • Fee Estimation Improvement (#27)
    • Add blockfrost support for transaction evaluation

    Changed

    • Refactor transaction builder to a dataclass
    • Upgrade Blockfrost to 0.4.4

    Fixed

    • Do not modify multiassets when being added or subtracted
    • Restore empty datum in redeemer
    Source code(tar.gz)
    Source code(zip)
  • v0.3.1(Mar 31, 2022)

    [0.3.1] - 2022-03-31

    Some minor improvements in transaction builder.

    Added

    • Add more details to the message of exception when UTxO selectors failed.
    • Validate output value is non-negative.
    Source code(tar.gz)
    Source code(zip)
  • v0.3.0(Mar 22, 2022)

    [0.3.0] - 2022-03-21

    Added

    • Incorporate change split logic #7.
    • Plutus
      • Datum support for transaction inputs and transaction outputs.
      • New function add_script_input in tx builder to support spending of Plutus script input.
      • Add collateral to tx builder for script transaction.
      • Add plutus_script_hash that calculates the hash of a Plutus script.
      • Include script execution steps and memory into fee calculation.
    • Add build_and_sign to tx builder.

    Changed

    • Remove positional argument index from Redeemer's constructor.
    Source code(tar.gz)
    Source code(zip)
  • v0.2.0(Mar 13, 2022)

    [0.2.0] - 2022-03-13

    This release added essential features for Plutus script interactions.

    Added

    • Plutus
      • Serialization, deserialization, and customization of plutus data and redeemer
      • Plutus cost model
      • Calculation of script data hash
      • JSON compatibility
    • Extended key support

    Changed

    • Sort multi-assets based on policy id and asset names

    Fixed

    • Fail tx builder when input amount is not enough to cover outputs and tx fee
    Source code(tar.gz)
    Source code(zip)
  • v0.1.2(Feb 20, 2022)

    [0.1.2] - 2022-02-20

    Added

    • Metadata and native script to docs
    • A full stack example (flask + PyCardano + BlockFrost + React + Nami wallet)
    • Continuous integration
    • Ogmios backend support

    Fixed

    • Minor fix in native token example
    Source code(tar.gz)
    Source code(zip)
Owner
null
Scheduled Block Checker for Cardano Stakepool Operators

ScheduledBlocks Scheduled Block Checker for Cardano Stakepool Operators Lightweight and Portable Scheduled Blocks Checker for Current Epoch. No cardan

SNAKE (Cardano Stakepool) 4 Oct 18, 2022
Unirest in Python: Simplified, lightweight HTTP client library.

Unirest for Python Unirest is a set of lightweight HTTP libraries available in multiple languages, built and maintained by Mashape, who also maintain

Kong 432 Dec 21, 2022
Zendesk Ticket Viewer is a lightweight commandline client for fetching and displaying tickets from a Zendesk account provided by the user

Zendesk Ticket Viewer is a lightweight commandline client for fetching and displaying tickets from a Zendesk account provided by the user.

Parthesh Soni 1 Jan 24, 2022
Dns-Client-Server - Dns Client Server For Python

Dns-client-server DNS Server: supporting all types of queries and replies. Shoul

Nishant Badgujar 1 Feb 15, 2022
Raphtory-client - The python client for the Raphtory project

Raphtory Client This is the python client for the Raphtory project Install via p

Raphtory 5 Apr 28, 2022
Drcom-pt-client - Drcom Pt version client with refresh timer

drcom-pt-client Drcom Pt version client with refresh timer Dr.com Pt版本客户端 可用于网页认

null 4 Nov 16, 2022
A lightweight, dependency-free Python library (and command-line utility) for downloading YouTube Videos.

24 July 2020 Actively soliciting contributers! Ping @ronncc if you would like to help out! pytube pytube is a very serious, lightweight, dependency-fr

pytube 7.9k Jan 2, 2023
🚀 A fast, flexible and lightweight Discord API wrapper for Python.

Krema A fast, flexible and lightweight Discord API wrapper for Python. Installation Unikorn unikorn add kremayard krema -no-confirmation Pip pip insta

Krema 20 Sep 4, 2022
A lightweight Python wrapper for the IG Markets API

trading_ig A lightweight Python wrapper for the IG Markets API. Simplifies access to the IG REST and Streaming APIs with a live or demo account. What

IG Python 247 Dec 8, 2022
A Simple, LightWeight, Statically-Typed Python3 API wrapper for GogoAnime.

AniKimi API A Simple, LightWeight, Statically-Typed Python3 API wrapper for GogoAnime The v2 of gogoanimeapi (depreciated) Made with JavaScript and Py

null 17 Dec 9, 2022
ChairBot is designed to be reliable, easy to use, and lightweight for every user, and easliy to code add-ons for ChairBot.

ChairBot is designed to be reliable, easy to use, and lightweight for every user, and easliy to code add-ons for ChairBot. Ready to see whats possible with ChairBot?

null 1 Nov 8, 2021
A simple, lightweight Discord bot running with only 512 MB memory on Heroku

Haruka This used to be a music bot, but people keep using it for NSFW content. Can't everyone be less horny? Bot commands See the built-in help comman

Haruka 4 Dec 26, 2022
A free, minimal, lightweight, cross-platform, easily expandable Twitch IRC/API bot.

parky's twitch bot A free, minimal, lightweight, cross-platform, easily expandable Twitch IRC/API bot. Features ?? Connect to Twitch IRC chat! ?? Conn

Andreas Schneider 10 Dec 30, 2022
Pincer-ext-commands - A simple, lightweight package for pincer prefixed commands

pincer.ext.commands A reimagining of pincer's command system and bot system. Ins

Vincent 2 Jan 11, 2022
Kevin L. 3 Jul 14, 2022
Official Python client for the MonkeyLearn API. Build and consume machine learning models for language processing from your Python apps.

MonkeyLearn API for Python Official Python client for the MonkeyLearn API. Build and run machine learning models for language processing from your Pyt

MonkeyLearn 157 Nov 22, 2022
🖥️ Python - P1 Monitor API Asynchronous Python Client

??️ Asynchronous Python client for the P1 Monitor

Klaas Schoute 9 Dec 12, 2022
🐍 The official Python client library for Google's discovery based APIs.

Google API Client This is the Python client library for Google's discovery based APIs. To get started, please see the docs folder. These client librar

Google APIs 6.2k Jan 8, 2023
Python client for Arista eAPI

Arista eAPI Python Library The Python library for Arista's eAPI command API implementation provides a client API work using eAPI and communicating wit

Arista Networks EOS+ 124 Nov 23, 2022