Skip to main content

Generating Transactions

TransactionBuilder API#

In order to simplify transaction creation, we provide a TransactionBuilder struct that manages witnesses, fee calculation, change addresses and such. Assume we have instantiated an instance under the variable builder for this explanation. The TransactionBuilder requires several protocol parameters governing Cardano to be created which is shown in the following section. These are specified initially in the genesis file for Cardano nodes.

The minimum required for a valid transaction is to add inputs, outputs, time-to-live and either set the fee explicitly with builder.set_fee(fee), or calculate it implicitly using builder.add_change_if_needed(address). Optionally a transaction can also have certificates, reward withdrawals, and metadata added to it. Any change made to the builder can impact the size and thus the fee so the fee should be the last thing set. If implicitly setting the fee any extra ada (inputs + withdrawals - outputs + refund - deposit - min fee) is sent to the provided change address. Fees must be sufficient, i.e. inputs + withdrawals + refund >= outputs + deposit + fee which must be manually ensured if you explicitly set the fee. Any extra fee is not necessary and the extra ada beyond that will be burned. Once the transaction is ready, const body = builder.build() can be called to return a ready TransactionBody.

Withdrawals are ada withdrawn as part of the rewards generated by staking and deposits are refundable ada locked while resources such as stake certificates or pool registrations exist on the blockchain. They are returned as refunds when these resources are deregistered/retired.

To get to a transaction ready to post on the blockchain, we must create a Transaction from that, which consists of the TransactionBody, a matching TransactionWitnessSet and optionally a TransactionMetadata. The witnesses and optional metadata must match those provided to the builder. The witnesses must sign the hash of the transaction body returned by hash_transaction(body). In addition to the witnesses for inputs, withdrawals and some certificates require witnesses as well. For example, staking address registration does not require a witness while stake address de-registration requires one. For any questions or doubts about the rules governing fees, deposits, rewards, certificates or which witness types are required refer to the shelley specs, specifically the Shelley design specification for general design. The formal specification could be useful for specific details as well. The design spec contains details about which certificates require which type of witnesses in the Certificates and Registrations section.

Example code#

The example below builds a transaction with all 2 of the 3 input types: key and bootstrap. Multisig (script) inputs are essentially identical to key inputs, but using the scripthash instead of the keyhash, however they are not supported for implicit fee calculation yet. Fees are automatically calculated and sent to a change address in the example.

// instantiate the tx builder with the Cardano protocol parameters - these may change later onconst txBuilder = CardanoWasm.TransactionBuilder.new(    // all of these are taken from the mainnet genesis settings    // linear fee parameters (a*size + b)    CardanoWasm.LinearFee.new(CardanoWasm.BigNum.from_str('44'), CardanoWasm.BigNum.from_str('155381')),    // minimum utxo value    CardanoWasm.BigNum.from_str('1000000'),    // pool deposit    CardanoWasm.BigNum.from_str('500000000'),    // key deposit    CardanoWasm.BigNum.from_str('2000000'));
// add a keyhash input - for ada held in a Shelley-era normal address (Base, Enterprise, Pointer)const prvKey = CardanoWasm.PrivateKey.from_bech32("ed25519e_sk16rl5fqqf4mg27syjzjrq8h3vq44jnnv52mvyzdttldszjj7a64xtmjwgjtfy25lu0xmv40306lj9pcqpa6slry9eh3mtlqvfjz93vuq0grl80");txBuilder.add_key_input(    prvKey.to_public().hash(),    CardanoWasm.TransactionInput.new(        CardanoWasm.TransactionHash.from_bytes(            Buffer.from("8561258e210352fba2ac0488afed67b3427a27ccf1d41ec030c98a8199bc22ec", "hex")        ), // tx hash        0, // index    ),    CardanoWasm.Value.new(CardanoWasm.BigNum.from_str('3000000')));
// add a bootstrap input - for ada held in a Byron-era addressconst byronAddress = CardanoWasm.ByronAddress.from_base58("Ae2tdPwUPEZLs4HtbuNey7tK4hTKrwNwYtGqp7bDfCy2WdR3P6735W5Yfpe");txBuilder.add_bootstrap_input(    byronAddress,    CardanoWasm.TransactionInput.new(    CardanoWasm.TransactionHash.from_bytes(        Buffer.from("488afed67b342d41ec08561258e210352fba2ac030c98a8199bc22ec7a27ccf1", "hex"),    ), // tx hash    0, // index    ),    CardanoWasm.Value.new(CardanoWasm.BigNum.from_str('3000000')));
// base addressconst shelleyOutputAddress = CardanoWasm.Address.from_bech32("addr_test1qpu5vlrf4xkxv2qpwngf6cjhtw542ayty80v8dyr49rf5ewvxwdrt70qlcpeeagscasafhffqsxy36t90ldv06wqrk2qum8x5w");// pointer addressconst shelleyChangeAddress = CardanoWasm.Address.from_bech32("addr_test1gz2fxv2umyhttkxyxp8x0dlpdt3k6cwng5pxj3jhsydzerspqgpsqe70et");
// add output to the txtxBuilder.add_output(    CardanoWasm.TransactionOutput.new(    shelleyOutputAddress,    CardanoWasm.Value.new(CardanoWasm.BigNum.from_str('1000000'))        ),);
// set the time to live - the absolute slot value before the tx becomes invalidtxBuilder.set_ttl(410021);
// calculate the min fee required and send any change to an addresstxBuilder.add_change_if_needed(shelleyChangeAddress)
// once the transaction is ready, we build it to get the tx body without witnessesconst txBody = txBuilder.build();const txHash = CardanoWasm.hash_transaction(txBody);const witnesses = CardanoWasm.TransactionWitnessSet.new();
// add keyhash witnessesconst vkeyWitnesses = CardanoWasm.VkeyWitnesses.new();const vkeyWitness = CardanoWasm.make_vkey_witness(txHash, prvKey);vkeyWitnesses.add(vkeyWitness);witnesses.set_vkeys(vkeyWitnesses);
// add bootstrap (Byron-era) witnessesconst bootstrapWitnesses = CardanoWasm.BootstrapWitnesses.new();const bootstrapWitness = CardanoWasm.make_icarus_bootstrap_witness(txHash,byronAddress,getCip1852Account());bootstrapWitnesses.add(bootstrapWitness);witnesses.set_bootstraps(bootstrapWitnesses);
// create the finalized transaction with witnessesconst transaction = CardanoWasm.Transaction.new(    txBody,    witnesses,    undefined, // transaction metadata);

A note on fees#

Fees in Cardano Shelley are based directly on the size of the final encoded transaction. It is important to note that a transaction created by this library potentially can vary in size compared to one built with other tools. This is because transactions, as well as other Cardano Shelley structures, are encoded using CBOR a binary JSON-like encoding. Due to arrays and maps allowing both definite or indefinite length encoding in the encoded transaction created by the library, the size can vary. This is because definite encoding consists of a tag containing the size of the array/map which can be 1 or more bytes long depending on the number of elements the size of the encoded structure, while indefinite length encoding consists of a 1 byte starting tag and after all elements are listed, a 1 byte ending tag. These variances should should only be a couple bytes and cardano-serialization-lib uses definite encoding which is the same length or smaller for any reasonable sized transaction.