Please use `cardano-cli transaction build` instead of `cardano-cli transaction build-raw`

Especially if you are writing a walk-through for beginners, please advise them to use build instead of build-raw. It’s a PITA debugging all those cases, where the balances don’t add up.

The old way:

$ cardano-cli query utxo --mainnet --address addr1vxlgahj760v86tzcpr5xd9u80kcmgm2gsl6ulhvxnya26pq3h39nq
                           TxHash                                 TxIx        Amount
--------------------------------------------------------------------------------------
82ad4e06c9a7319e765114aa15910a46391a1bc23900d808deeb86315c1ae158     0        13933006 lovelace + TxOutDatumNone
82ad4e06c9a7319e765114aa15910a46391a1bc23900d808deeb86315c1ae158     1        4000000 lovelace + 10000 a80398fd0dd6c71bbe067f6181274d0d9aff81d351d8c6749d6581dd.c39e6f7220436f696e + TxOutDatumNone
$ cardano-cli transaction build-raw \
> --tx-in 82ad4e06c9a7319e765114aa15910a46391a1bc23900d808deeb86315c1ae158#1 \
> --tx-out "addr1q89vv6r6e7040j7lm5m3s9c9cv7l0hdchwrwt62xm4v7h2df36408rhdrmchm7pxj8huuyz4jsgzqjllta5xfgrjysqsznluu4+2000000+3000 a80398fd0dd6c71bbe067f6181274d0d9aff81d351d8c6749d6581dd.c39e6f7220436f696e" \
> --tx-out "addr1vxlgahj760v86tzcpr5xd9u80kcmgm2gsl6ulhvxnya26pq3h39nq+2000000+7000 a80398fd0dd6c71bbe067f6181274d0d9aff81d351d8c6749d6581dd.c39e6f7220436f696e" \
> --fee 200000 \
> --out-file trans1-tx.raw
$ cardano-cli query protocol-parameters --mainnet --out-file protocol.json
$ cardano-cli transaction calculate-min-fee --mainnet \
> --tx-body-file trans1-tx.raw \
> --tx-in-count 1 --tx-out-count 2 --witness-count 1 \
> --protocol-params-file protocol.json
179845 Lovelace
$ cardano-cli transaction build-raw \
> --tx-in 82ad4e06c9a7319e765114aa15910a46391a1bc23900d808deeb86315c1ae158#1 \
> --tx-out "addr1q89vv6r6e7040j7lm5m3s9c9cv7l0hdchwrwt62xm4v7h2df36408rhdrmchm7pxj8huuyz4jsgzqjllta5xfgrjysqsznluu4+2000000+3000 a80398fd0dd6c71bbe067f6181274d0d9aff81d351d8c6749d6581dd.c39e6f7220436f696e" \
> --tx-out "addr1vxlgahj760v86tzcpr5xd9u80kcmgm2gsl6ulhvxnya26pq3h39nq+1820155+7000 a80398fd0dd6c71bbe067f6181274d0d9aff81d351d8c6749d6581dd.c39e6f7220436f696e" \
> --fee 179845 \
> --out-file trans1-tx.raw
$ cardano-cli transaction sign --mainnet \
> --tx-body-file trans1-tx.raw \
> --signing-key-file ../mint.skey \
> --out-file trans1-tx.signed
$ cardano-cli transaction submit --mainnet --tx-file trans1-tx.signed
Transaction successfully submitted.

We have to download protocol parameters, calculate the minimal fee by hand, adjust the outputs, …
Especially if people use the army of shell variables used for some reason in a lot of guides, there are so many places, where something can go wrong.

The “new” way:
(in fact, not so new at all)

$ cardano-cli query utxo --mainnet --address addr1vxlgahj760v86tzcpr5xd9u80kcmgm2gsl6ulhvxnya26pq3h39nq
                           TxHash                                 TxIx        Amount
--------------------------------------------------------------------------------------
38c6e56642afdbe796414271834c092f8481b68c3fd97d36348ccbda535f0176     1        1820155 lovelace + 7000 a80398fd0dd6c71bbe067f6181274d0d9aff81d351d8c6749d6581dd.c39e6f7220436f696e + TxOutDatumNone
82ad4e06c9a7319e765114aa15910a46391a1bc23900d808deeb86315c1ae158     0        13933006 lovelace + TxOutDatumNone
$ cardano-cli transaction build --mainnet \
> --tx-in 38c6e56642afdbe796414271834c092f8481b68c3fd97d36348ccbda535f0176#1 \
> --tx-in 82ad4e06c9a7319e765114aa15910a46391a1bc23900d808deeb86315c1ae158#0 \
> --tx-out "addr1q89vv6r6e7040j7lm5m3s9c9cv7l0hdchwrwt62xm4v7h2df36408rhdrmchm7pxj8huuyz4jsgzqjllta5xfgrjysqsznluu4+2000000+3000 a80398fd0dd6c71bbe067f6181274d0d9aff81d351d8c6749d6581dd.c39e6f7220436f696e" \
> --tx-out "addr1vxlgahj760v86tzcpr5xd9u80kcmgm2gsl6ulhvxnya26pq3h39nq+2000000+4000 a80398fd0dd6c71bbe067f6181274d0d9aff81d351d8c6749d6581dd.c39e6f7220436f696e" \
> --change-address addr1vxlgahj760v86tzcpr5xd9u80kcmgm2gsl6ulhvxnya26pq3h39nq \
> --out-file trans2-tx.raw
Estimated transaction fee: Lovelace 178657
$ cardano-cli transaction sign --mainnet \
> --tx-body-file trans2-tx.raw \
> --signing-key-file ../mint.skey \
> --out-file trans2-tx.signed
$ cardano-cli transaction submit --mainnet --tx-file trans2-tx.signed
Transaction successfully submitted.

No fee calculation by hand, no balancing of ADA by hand, no download of protocol parameters, the rest just flows into a UTxO at the given --change-address.

(Note: Unfortunately, non-ADA assets still have to be balanced by hand up to now. Otherwise we could have saved one --tx-out to the --change-address itself in this command.)

6 Likes

thanks @HeptaSean - that’s a good suggestion & I was happy to pursue it a while ago. Yet we ended up sticking with the original raw transaction building for a reason I didn’t anticipate… buildling a raw transaction to use complete UTxO’s always results in a single UTxO in the address used for your “change” while keeping the balance at the same address every time.

i.e. as time goes on, the same payment address (especially important for the pledge address of a stake pool) will always have a single UTxO in it with an easily human-readable Tx ID and balance, for further use in manual transactions… which is important if you feel more comfortable doing it that way.

The manual error can be reduced or removed by copy/pasting the CLI commands & keeping the parameters as variables, so users can retain the satisfaction and auditability of using the cardano-cli commands without relying on scripts to formulate the commands or total the UTxO’s at an address, e.g.:

Though we do this in the operations of our own pool, and invite others to do the same, we don’t claim that this is a better way of doing things… it’s just an argument that both the “build” and “build-raw” approaches will have uses in scripts and tutorials :nerd_face:

1 Like

If there are no other --tx-outs targeting the same address as --change-address, the build way should also consistently result in just a single UTxO at that address.

As soon as assets other than ADA are involved that doesn’t work anymore as my example already shows. It would be really good if future versions could also balance non-ADA assets to make it even more comfortable.

2 Likes

thanks; we’ve updated our template at the web page above accordingly :sunglasses:

1 Like

hey @HeptaSean (or anyone), I’m adapting some of our old scripts along these lines & noticed something I can’t figure out in our offline environment, running cardano-cli transaction build like so:

cardano-cli transaction build \
--mainnet \
--tx-in $(cat payment.utxo) \
--tx-out $(cat wallet.addr)+$(cat rbal) \
--change-address $(cat low/payment.addr) \
--withdrawal $(cat low/stake.addr)+$(cat rbal) \
--out-file tx.draft

It gets this error, as if we weren’t properly pointed to a live node to submit a transaction:

Command failed: transaction build  Error: Error while looking up environment variable:
CARDANO_NODE_SOCKET_PATH  Error: "CARDANO_NODE_SOCKET_PATH"

Why would any transaction building, designed to work offline, require a connection to a live Cardano node?

We didn’t notice this before since our automation was still using the old transaction model of building the dummy transaction first & are finally getting rid of that old stuff now.

If there was a memo about cardano-cli transaction build only working on a node machine, or with a connection to one, then I definitely missed it. Hopefully I’m making some dumb mistake regarding the syntax or context.

I was thinking it might need a timing reference from a live node if --invalid-hereafter were not specified, but it gets the same error even using that option. :thinking:

I think, build only works with a synchronised node. There could be two reasons:

They integrated the minimum fee calculation. In the build-raw variant, you need to get the protocol parameters from a synchronised node (but can then copy around the file).

In order to calculate the transaction, it has to be known what assets are on the input UTxOs. I don’t know how build-raw does it. Maybe then transactions fail only on submission, while build tries to ensure that they will not fail.

Don’t know any way around it, other than building on the hot node and doing only the signing air-gapped. Sorry!

3 Likes

I would first try to just define the variable and run the same command again.

please don’t be sorry… this was just the confirmation I was looking for. I think they could have made transaction build work on a cold machine if the CLI gave the user the benefit of a doubt about the transaction amounts being correct, allowing the submitted transaction to fail if those preconditions were not met (just like it already does with transaction build-raw). I just needed to be sure about how it was implemented & that I wasn’t missing anything that could make it work cold.

It’s an adaptation to our workflow, rather than a real problem, since we’re already gathering parameters for our transaction on the relay, and just need to move 1 extra step to build the transaction there. Maybe someone would post here someday if build is ever detached from the requirement for a live node connection. :sunglasses:

2 Likes