12. Smart contract transaction data
For our final query, we will inspect the details of a specific transaction, namely, the transaction id 52385f2dd6b69eb035474b529249afda132cde54f54c727842393edc51bebb57
. Note we are not modifying the blockchain in any way, we are just reading data from it.
For most users, the wallet is their gateway when interacting with dApps (decentralized applications) on Cardano. There is even a CIP-30 (opens in a new tab) outlining a consistent standard for developers to follow. For example, when a user swaps a token on a dApp, the wallet securely signs the transaction, and this transaction typically triggers smart contract(s). Most smart contracts on Cardano are written in Aiken. To learn more, head over to aiken-lang.org (opens in a new tab) where you will find some excellent primers and guides. Learn how Aiken's compiler and standard library enforce safe patterns to aid in secure code. Add to that a full-blown property test framework, you can be confident Aiken is fully equipped for high assurance.
🔎 As usual, you can also take a closer look using the Cardano Explorer (opens in a new tab)
12.1 Transaction information
Let's start by retrieving the transaction details:
- transaction hash
- timestamp
- block
- fee
- epoch
- slot
- source address
- destination address
- assets
- amount
- UTXOs used
SELECT tx_detail.* FROM (
SELECT
'destination_address' AS direction,
tx.hash AS txhash,
tx_out.address AS address,
sa."view" AS stake_address,
tx_out.value AS ADA_amount,
ma.name AS asset_name,
sum(mto.quantity) AS quantity,
b.time AS time_stamp,
tx.fee AS transaction_fee,
b.block_no AS block,
b.slot_no AS slot,
b.epoch_no AS epoch,
NULL AS utxo
FROM tx
LEFT JOIN tx_out ON tx_out.tx_id = tx.id
LEFT JOIN stake_address sa ON sa.id = tx_out.stake_address_id
LEFT JOIN ma_tx_out mto ON mto.tx_out_id = tx_out.id
LEFT JOIN multi_asset ma ON ma.id = mto.ident
LEFT JOIN block b ON b.id = tx.block_id
WHERE
tx.hash = '52385f2dd6b69eb035474b529249afda132cde54f54c727842393edc51bebb57'
GROUP BY direction, txhash, address, stake_address, ADA_amount, asset_name, time_stamp, transaction_fee, block, slot, epoch, utxo
UNION ALL
SELECT
'source_address' AS direction,
tx.hash AS txhash,
tx_out.address AS address,
sa."view" AS stake_address,
tx_out.value ADA_amount,
ma.name AS asset_name,
sum(mto.quantity) AS quantity,
b.time AS time_stamp,
tx.fee AS transaction_fee,
b.block_no AS block,
b.slot_no AS slot,
b.epoch_no AS epoch,
txin.hash AS utxo
FROM tx
LEFT JOIN tx_in ti ON ti.tx_in_id = tx.id
LEFT JOIN tx_out ON tx_out.tx_id = ti.tx_out_id AND tx_out."index" = ti.tx_out_index
LEFT JOIN tx txin ON txin.id = tx_out.tx_id
LEFT JOIN stake_address sa ON sa.id = tx_out.stake_address_id
LEFT JOIN ma_tx_out mto ON mto.tx_out_id = tx_out.id
LEFT JOIN multi_asset ma ON ma.id = mto.ident
LEFT JOIN block b ON b.id = tx.block_id
WHERE
tx.hash = '52385f2dd6b69eb035474b529249afda132cde54f54c727842393edc51bebb57'
GROUP BY direction, txhash, address, stake_address, ADA_amount, asset_name, time_stamp, transaction_fee, block, slot, epoch, utxo
) AS tx_detail
ORDER BY tx_detail.direction ASC
Expected results format
direction | txhash | address | stake_address |ada_amount|asset_name|quantity|time_stamp |tx_fee |block | slot |epoch|utxo
--------------------+--------------------+-----------------------+----------------------------+----------+----------+--------+-------------------+------------------+-----------+-----+----------
destination_address | 52385f2dd....ebb57 |addr1q94ga....grqd9dw0d|stake1u9d9...sapektypsmfx9qr|20,000,000| | |2024-07-13 15:33:54|395,771|10,568,064|129,318,543| 496 |
source_address | 52385f2dd....ebb57 |addr1qyvl8....vgsyxh7q9|stake1uxw4...8f43pxkyg5842yn|33,080,297| | |2024-07-13 15:33:54|395,771|10,568,064|129,318,543| 496 |9d8...57a
(Note: full results trimmed for readability)
🔎 Let's zoom in to see what the query does step-by-step:
Overall query structure
To find the information we need, the query focuses on two perspectives:
- Destination Addresses: Where did the funds/assets end up?
- Source Addresses: Where did the funds/assets come from?
UNION ALL
combines the results from the two separate SELECT
statements. Each SELECT
statement targets one of the perspectives above.
Destination Addresses, the first SELECT
It fetches details about the outputs tx_out (opens in a new tab) of the transaction. It then links to other tables to fetch further information such as:
- Stake address associated with the output
- Asset names and quantities
- Block, slot, and epoch details of the transaction
- Transaction fee
Source Addresses, the second SELECT
This SELECT
statement looks back to the inputs tx_in (opens in a new tab) of the transaction. It traces the chain of transactions to find the original outputs tx_out (opens in a new tab) that were spent as inputs in this transaction. It retrieves similar information as the first SELECT
, but additionally includes the 'utxo' (unspent transaction output) hash which represents the specific output that was spent.
Result returned
The two result sets are combined with a UNION ALL
clause, and the final output is sorted by direction
so that destination_address
rows appear before source_address
rows in the results.
12.2 Contracts details
For this query, we are looking for the Redeemer, datum hash and datum associated with the transaction. A good recap on there terms is available on Aiken-lang.org (opens in a new tab).
SELECT
re.script_hash AS scriptHash,
txOut.address AS address,
re.purpose as purpose,
encode(rd.bytes, 'hex') as redeemerData,
re.unit_mem as redeemerMem,
re.unit_steps as redeemerSteps,
d.hash as datumHash,
encode(d.bytes, 'hex') as datum
FROM tx
INNER JOIN redeemer re ON re.tx_id = tx.id
LEFT JOIN redeemer_data rd ON re.redeemer_data_id = rd.id
LEFT JOIN tx_in txIn ON (txIn.redeemer_id = re.id and txIn.tx_in_id = tx.id)
LEFT JOIN tx_out txOut ON txIn.tx_out_id = txOut.tx_id AND txIn.tx_out_index = txOut.index
LEFT JOIN datum d ON (txOut.data_hash = d.hash OR txOut.inline_datum_id = d.id)
WHERE tx.hash = '52385f2dd6b69eb035474b529249afda132cde54f54c727842393edc51bebb57'
Expected results format
scripthash | address |purpose|redeemerbytes| redeemermem| redeemersteps | datumhash | datumbytes
--------------+--------------+-------+-------------+------------+----------------+-------------------+------------------------
9068...ce138ad|addr1zx...5plm|spend |d87a80 | 2,463,014 | 689,900,093 | 3c27703297...1fce | d8799f581c...00ffffffff
(Note: full results trimmed for readability)
🔎 Let's zoom in to see what the query does step-by-step:
Target transaction
WHERE tx.hash = '...'
filters to the specific transaction we are interested in.
Redeemer details
INNER JOIN redeemer re ON re.tx_id = tx.id
ensures we only consider redeemers associated with the target transaction. re.script_hash
, re.purpose
, re.unit_mem
, re.unit_steps
fetch the script hash the redeemer interacts with, its purpose, memory usage, and computational steps, respectively.
Redeemer Data
LEFT JOIN redeemer_data rd ON re.redeemer_data_id = rd.id
brings in redeemer data if it exists. encode(rd.bytes, 'hex') as redeemerData
converts the raw redeemer data into a human-readable hexadecimal format.
Transaction input and output JOIN
LEFT JOIN tx_in txIn ON ...
joins the transaction input that triggered the redeemer to the transaction itself. LEFT JOIN tx_out txOut ON ...
links this input with the specific transaction output it spends from. txOut.address
extracts the address of the output being spent.
Datum information
LEFT JOIN datum d ON ...
attempts to find a datum linked to the spent output, either through a hash reference or an inline datum.
d.hash
and encode(d.bytes, 'hex') as datum
provide the datum hash and its hexadecimal representation.
Output
The query will produce a result set containing:
scriptHash
...the script hash the redeemer interacts with.address
...the address of the UTxO spent by the transaction input.purpose
...the purpose of the redeemerredeemerData
...the Hex-encoded redeemer data (if present).redeemerMem
&redeemerSteps
...the redeemer's memory & computational step usage.datumHash
...the hash of the datum associated with the spent output.datum
...the hex-encoded datum content (if present).
Remember you can also review the results in any Cardano explorer. The Cardano Developer Portal has compiled a list (opens in a new tab) in their 'Showcase' section. For example, you can review review the results of Queries 12.1 and 12.2 at this link (opens in a new tab) 🔎
12.3 Collateral input & output
The collateral mechanism is an important feature that has been designed to ensure successful smart contract execution. You can read more about it in the documentation (opens in a new tab).
This query retrieves information about the inputs and outputs associated with our transaction, identified by its hash 52385f2dd6b69eb035474b529249afda132cde54f54c727842393edc51bebb57
. The inputs and outputs are categorized in the results in the 'collateral' column.
SELECT
'input' AS collateral,
txOut.address AS address,
txOut.value AS value,
txOut.index AS index
FROM unconsume_tx_in cti
INNER JOIN tx_out txOut on txOut.tx_id = cti.tx_out_id and txOut.index = cti.tx_out_index
LEFT JOIN tx ON cti.tx_in_id = tx.id
JOIN tx txin ON cti.tx_out_id = txin.id
WHERE
tx.hash = '52385f2dd6b69eb035474b529249afda132cde54f54c727842393edc51bebb57'
UNION
SELECT
'output' AS collateral,
failedTxOut.address AS address,
failedTxOut.value AS value,
failedTxOut.index AS index
FROM failed_tx_out failedTxOut
INNER JOIN tx ON failedTxOut.tx_id = tx.id
WHERE tx.hash = '52385f2dd6b69eb035474b529249afda132cde54f54c727842393edc51bebb57'
Expected results format
collateral | address |value |index
-----------+---------------------------------------------------------------------------------------------------------+------------+-----
input | addr1qyvl8lmwpfq7pahuadpjwul9xlj7c0atr5hvu4r3cjsjlfua276aqezvwjsfucvq76welxmytfyt7ug5835wntzzdvgsyxh7q9 | 33,080,297 | 0
output | addr1qyvl8lmwpfq7pahuadpjwul9xlj7c0atr5hvu4r3cjsjlfua276aqezvwjsfucvq76welxmytfyt7ug5835wntzzdvgsyxh7q9 | 32,486,640 | 5
🔎 Let's zoom in to see what the query does step-by-step:
First SELECT
The FROM unconsume_tx_in cti
clause starts by looking at unspent transaction outputs (UTXOs) that have been used as inputs in some other transaction. The INNER JOIN tx_out txOut ...
joins with the tx_out (opens in a new tab) table to get details about these UTXOs, ie. address, value, index within the transaction.
The LEFT JOIN tx ON ...
attempts to join with the tx (opens in a new tab) table to see if there's a transaction where this UTXO was used as an input.
The JOIN tx txin ON ...
clause also joins with the tx (opens in a new tab) table again (alias txin
) to get details about the transaction where this UTXO was originally created as an output.
The WHERE tx.hash = ...
clause filters the results to only include UTXOs that were used as inputs in the specific transaction we specified. The 'input' AS collateral
clause labels these results as 'input' in the collateral
column.
Second SELECT
The FROM failed_tx_out failedTxOut
looks at outputs from transactions that failed. The INNER JOIN tx ON ...
clause joins with the tx (opens in a new tab) table to get details about the failed transaction.
The WHERE tx.hash = ...
clause filters the results to only include outputs from the specific failed transaction we're interested in. The 'output' AS collateral
clause labels these results as 'output' in the collateral
column.
UNION
The UNION
clause combines the results from the two parts into a single result set:
collateral
indicates whether the row represents a transaction input or an outputaddress
...ie, the address associated with the input or outputvalue
is the amount of ADA (or other assets) associated with the input or outputindex
tells us the position of the input or output within its transaction
12.4 Reference input and contract reference
Our final query aims to retrieve details about the outputs (UTXOs) that were spent as inputs in our specific transaction, identified by its hash 52385f2dd6b69eb035474b529249afda132cde54f54c727842393edc51bebb57
.
SELECT
txOut.address as address,
tx.hash as txHash,
txOut.value as value,
r.tx_out_index as index
FROM reference_tx_in r
JOIN tx_out txOut ON r.tx_out_id = txOut.tx_id AND txOut.index = r.tx_out_index
JOIN tx ON txOut.tx_id = tx.id
JOIN tx txin ON txin.id = r.tx_in_id
WHERE
txin.hash = '52385f2dd6b69eb035474b529249afda132cde54f54c727842393edc51bebb57'
Expected results format
address | txhash |value |index
--------------------------------------------------------------------------------------------------------+-----------------------+------------+-----
addr1zxgx3far7qygq0k6epa0zcvcvrevmn0ypsnfsue94nsn3tvpw288a4x0xf8pxgcntelxmyclq83s0ykeehchz2wtspks905plm | 9a3245....b0fc1d06f5b | 40,000,000 | 0
(Note: results abbreviated for readability)
🔎 Let's zoom in to see what the query does step-by-step:
Query structure
The FROM reference_tx_in r
starts by looking at the reference_tx_in (opens in a new tab) table, which stores information about transaction inputs and the UTXOs they reference.
JOIN tx_out txOut ON ...
joins with the tx_out (opens in a new tab) table to get details about these UTXOs, ie. address, value, index within the transaction.
JOIN tx ON txOut.tx_id = tx.id
joins with the tx (opens in a new tab) table to get information about the transaction where these UTXOs were created as outputs.
JOIN tx txin ON txin.id = r.tx_in_id
joins with the tx (opens in a new tab) table again (aliase txin
) to get details about the transaction where these UTXOs were spent as inputs.
The WHERE txin.hash = ...
clause filters the results to only include UTXOs that were spent as inputs in the specific transaction we're interested in.
Results
address
is the address associated with the spent UTXOtxHash
is the hash of the transaction where this UTXO was created as an outputvalue
is the amount of ADA (or other assets) associated with the spent UTXOindex
is the position of the UTXO within the transaction where it was created