Selecting UTXOs for Transactions
Selecting UTXOs for Transactions.
Overview
Unspent Transaction Outputs (UTXOs) are a fundamental concept in Cardano's accounting model. Unlike an account-based model that tracks balances, the UTXO model tracks individual outputs of transactions that haven't been spent yet.
When building a Cardano transaction with Anvil API, you have two options for specifying transaction inputs:
Provide UTXO List - Explicitly specify which UTXOs to use (required in production)
Automatic Selection - Let Anvil choose UTXOs for you (testing environments only)
Automatic UTXO Selection in Testing
In test environments (preprod, preview), the utxos
parameter in the transaction building API is optional. If you don't provide it, Anvil will:
Automatically fetch UTXOs associated with the
changeAddress
Select appropriate UTXOs to cover the transaction
Handle all input management for you
This simplifies development and testing by removing the need to manage UTXO selection logic until you're ready for production.
If you are using a multi-account wallet (e.g., Eternl multi-account setting), you will need to provide the utxos
parameter in the transaction building API. Anvil will not be able to automatically select UTXOs for you in this case.
// Example: Let Anvil handle UTXO selection (test environments only)
const response = await fetch('https://preprod.api.ada-anvil.app/v2/services/transactions/build', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Api-Key': 'YOUR_API_KEY'
},
body: JSON.stringify({
// Just provide a change address - no UTXOs needed!
changeAddress: 'addr_test...',
outputs: [
{
address: 'recipient_address',
lovelace: 5000000 // 5 ADA
}
]
})
});
Production Requirement
For production environments, you must explicitly provide UTXOs using the utxos
parameter:
// Production example: Explicitly provide UTXOs
const response = await fetch('https://prod.api.ada-anvil.app/v2/services/transactions/build', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Api-Key': 'YOUR_API_KEY'
},
body: JSON.stringify({
changeAddress: 'addr...',
// UTXO list is required in production
utxos: ['8282...', '8282...'],
outputs: [
{
address: 'recipient_address',
lovelace: 5000000 // 5 ADA
}
]
})
});
Fetching UTXOs: Multiple Approaches
Here are three efficient ways to retrieve UTXOs in your Cardano applications:
Using Weld
Weld is a bridge to browser-based wallets that implements the CIP-30 standard. It provides a unified interface for wallet interactions. Here's how to retrieve UTXOs from a connected wallet:
const wallet = useWallet("isConnected", "handler");
const interact = async () => {
if (!wallet.isConnected) {
return;
}
const utxos = await wallet.handler.getUtxos();
// Build Transaction using utxos
// Sign Transaction
const res = await wallet.handler.signTx("TX_CBOR_HERE");
};
This code snippet uses the Weld wallet connector to:
Check if a wallet is connected using the
isConnected
propertyRetrieve the complete list of UTXOs associated with the wallet's addresses using
getUtxos()
Demonstrate how these UTXOs could then be used in transaction signing with
signTx()
Weld simplifies wallet interactions by providing a consistent API regardless of which Cardano wallet the user has connected.
Using BlockFrost
BlockFrost is a popular API-as-a-service for Cardano that provides access to on-chain data. The following example demonstrates fetching UTXOs via the BlockFrost API and converting them to the format expected by cardano-serialization-lib
(CSL):
import { Buffer } from "node:buffer";
import {
TransactionUnspentOutput,
TransactionInput,
TransactionOutput,
TransactionHash,
TransactionUnspentOutputs,
MultiAsset,
Value,
Assets,
AssetName,
BigNum,
ScriptHash,
Address,
} from "@emurgo/cardano-serialization-lib-nodejs";
export function hex_to_uint8(input: string): Uint8Array {
return new Uint8Array(Buffer.from(input, "hex"));
}
export function assets_to_value(
multi_asset: MultiAsset,
assets: Asset[],
): Value {
const qt = assets.find((asset) => asset.unit === "lovelace")?.quantity;
if (!qt) {
throw new Error("No lovelace found in the provided utxo");
}
const assets_to_add: Record<string, Assets> = {};
for (const asset of assets) {
if (asset.unit !== "lovelace") {
const policy_hex = asset.unit.slice(0, 56);
const asset_hex = hex_to_uint8(asset.unit.slice(56));
if (!assets_to_add[policy_hex]) {
assets_to_add[policy_hex] = Assets.new();
}
assets_to_add[policy_hex].insert(
AssetName.new(asset_hex),
BigNum.from_str(asset.quantity),
);
}
}
for (const key of Object.keys(assets_to_add)) {
multi_asset.insert(ScriptHash.from_hex(key), assets_to_add[key]);
}
return Value.new_with_assets(BigNum.from_str(qt), multi_asset);
}
export type Asset = {
unit: string;
quantity: string;
};
export type Utxo = {
address: string;
tx_hash: string;
output_index: number;
amount: Asset[];
block?: string;
data_hash?: string | null;
inline_datum?: string | null;
reference_script_hash?: string | null;
};
export async function get_utxos_api(
blockfrost_base_url: string,
blockfrost_api_key: string,
address: string,
): Promise<Utxo[]> {
const get = async (data: object[] = [], page = 1) => {
const res = await fetch(
`${blockfrost_base_url}/addresses/${address}/utxos?page=${page}`,
{
method: "GET",
headers: {
"Content-Type": "application/json",
Accept: "application/json",
project_id: blockfrost_api_key,
},
},
);
if (res.status < 199 || res.status > 200) {
throw new Error("Unable to get utxos for specified address");
}
const json = await res.json();
if (json.length > 0) {
return await get([...data, ...json], page + 1);
}
return [...data, ...json];
};
return await get();
}
const utxos = await get_utxos_api(
"https://cardano-mainnet.blockfrost.io/api/v0",
Deno.env.get("BLOCKFROST_PROJECT_ID"),
"addr1q96gmvs93t96txjv43sw5gtf6pfzwmsu07t635pmlvwsucup2645cqyrknctcsd3s4e6wc23fxqsr3mywdlx9lnme6sshlyndu",
);
console.log("UTXOs Found:", utxos.length);
const parsedUtxo: TransactionUnspentOutputs = TransactionUnspentOutputs.new();
for (const utxo of utxos) {
const multi_assets = MultiAsset.new();
const { tx_hash, output_index, amount, address } = utxo as Utxo;
// If we have enough utxos, we can proceed.
parsedUtxo.add(
TransactionUnspentOutput.new(
TransactionInput.new(TransactionHash.from_hex(tx_hash), output_index),
TransactionOutput.new(
Address.from_bech32(address),
assets_to_value(multi_assets, amount),
),
),
);
}
This code performs several key operations:
Defines utility functions to convert between different data formats, including the critical
assets_to_value
function that transforms BlockFrost's asset representation to CSL'sValue
typeImplements a paginated API request to BlockFrost's
/addresses/{address}/utxos
endpoint with proper authenticationConstructs
TransactionUnspentOutput
objects from the returned data, which include:Transaction inputs (with transaction hash and output index)
Transaction outputs (with address and value information)
Combines these into a
TransactionUnspentOutputs
collection that can be used for transaction building
This approach is particularly useful for server-side applications where you need to query UTXOs without a connected wallet.
Using Dolos
Dolos is a specialized Cardano "Data Node" designed for efficient data access. It works with UTXOrpc, a standardized protocol for querying UTXO-based blockchains. The example below shows how to use the UTXOrpc SDK to retrieve UTXOs:
import { CardanoQueryClient } from "@utxorpc/sdk";
import { Buffer } from "node:buffer";
import {
TransactionUnspentOutput,
TransactionInput,
TransactionOutput,
TransactionHash,
TransactionUnspentOutputs,
} from "@emurgo/cardano-serialization-lib-nodejs";
const queryClient = new CardanoQueryClient({
// Dolos Endpoint
uri: Deno.env.get("DOLOS_ENDPOINT"),
});
console.time("dolos");
const utxos = await queryClient.searchUtxosByAddress(
Buffer.from(
// Fetched from browser using wallet.getChangeAddress()
"01748db2058acba59a4cac60ea2169d052276e1c7f97a8d03bfb1d0e638156ab4c0083b4f0bc41b18573a76151498101c764737e62fe7bcea1",
"hex",
),
);
console.log("UTXOs Found:", utxos.length);
const parsedUtxo: TransactionUnspentOutputs = TransactionUnspentOutputs.new();
for (const utxo of utxos) {
// UTXOs from dolos
const bytes = utxo.nativeBytes;
if (!bytes) {
throw new Error("No native bytes");
}
parsedUtxo.add(
TransactionUnspentOutput.new(
TransactionInput.new(
TransactionHash.from_bytes(utxo.txoRef.hash),
utxo.txoRef.index,
),
TransactionOutput.from_bytes(bytes),
),
);
}
const utxoHexes: string[] = [];
for (let i = 0; i < parsedUtxo.len(); i++) {
utxoHexes.push(parsedUtxo.get(i).to_hex());
}
console.log(utxoHexes);
This example demonstrates:
Initializing a
CardanoQueryClient
from the UTXOrpc SDK, pointing to a Dolos endpointQuerying UTXOs for a specific address (the address is provided in hex format)
Converting the returned UTXOs directly to
TransactionUnspentOutput
objects using the native byte formatCreating a collection of UTXOs that can be used for transaction building
Converting each UTXO to its hexadecimal representation for debugging or further processing
Dolos with UTXOrpc offers a more lightweight and efficient approach to data retrieval compared to running a full Cardano node. It's particularly well-suited for applications that need high performance UTXO retrieval with lower resource requirements.
Comparison and Considerations
Weld: Best for client-side/browser applications where a user's wallet is already connected. Simplest API for direct wallet interaction.
BlockFrost: Good for server-side applications or when no wallet is connected. Requires an API key and performs well for most use cases.
Dolos: Optimal for high-performance applications. Requires running a Dolos instance or connecting to a hosted one. Provides native format UTXOs that minimize conversion overhead.
Choose the approach that best fits your application architecture, performance requirements, and deployment environment.
Related Resources
TransactionUtility FunctionsLast updated
Was this helpful?