Cardano Wallet
The UTXOS Cardano wallet provides address management, UTXO handling, native asset support, transaction signing, and smart contract interaction. It integrates with the MeshJS ecosystem for transaction building.
TLDR: Enable a Cardano wallet and get your address.
import { Web3Wallet } from "@utxos/sdk";
const wallet = await Web3Wallet.enable({
projectId: process.env.NEXT_PUBLIC_UTXOS_PROJECT_ID,
networkId: 0, // 0 = preprod, 1 = mainnet
});
const address = await wallet.cardano.getChangeAddress();
console.log("Address:", address);Table of Contents
- Prerequisites
- Setup
- Address Management
- UTXOs and Assets
- Transaction Signing
- Asset Information
- Wallet Management
- API Reference
- Complete Examples
- Error Handling
- Data Provider Setup
- Security
Prerequisites
You need the following before integrating the Cardano wallet:
- UTXOS project ID from utxos.dev/dashboard
- Blockfrost API key for blockchain data queries
Setup
Install Dependencies
npm install @utxos/sdk @meshsdk/coreConfigure Environment
# .env
NEXT_PUBLIC_UTXOS_PROJECT_ID=your_project_id
NEXT_PUBLIC_NETWORK_ID=0 # 0 = preprod, 1 = mainnet
# Server-side only (for Blockfrost proxy)
BLOCKFROST_API_KEY_PREPROD=your_preprod_key
BLOCKFROST_API_KEY_MAINNET=your_mainnet_keyInitialize the Wallet
import { Web3Wallet, EnableWeb3WalletOptions } from "@utxos/sdk";
import { BlockfrostProvider } from "@meshsdk/core";
async function initCardanoWallet() {
// Configure provider via proxy (keeps API keys server-side)
const provider = new BlockfrostProvider("/api/blockfrost/preprod/");
const options: EnableWeb3WalletOptions = {
projectId: process.env.NEXT_PUBLIC_UTXOS_PROJECT_ID,
networkId: parseInt(process.env.NEXT_PUBLIC_NETWORK_ID) || 0,
fetcher: provider,
submitter: provider,
};
const wallet = await Web3Wallet.enable(options);
return wallet.cardano;
}Verify Connection
const cardano = await initCardanoWallet();
const address = await cardano.getChangeAddress();
console.log("Address:", address);
const utxos = await cardano.getUtxos();
console.log("UTXOs:", utxos.length);Address Management
Get wallet addresses for transactions and receiving funds.
// Get change address (for transaction outputs)
const changeAddress = await cardano.getChangeAddress();
// Get all used addresses
const usedAddresses = await cardano.getUsedAddresses();
// Get unused addresses (for receiving funds)
const unusedAddresses = await cardano.getUnusedAddresses();Address Types
| Method | Use Case |
|---|---|
getChangeAddress() | Transaction change outputs |
getUsedAddresses() | All addresses with transaction history |
getUnusedAddresses() | Fresh addresses for receiving funds |
UTXOs and Assets
Query wallet UTXOs and collateral for transactions.
// Get all UTXOs
const utxos = await cardano.getUtxos();
console.log("UTXO count:", utxos.length);
// Get collateral UTXOs (for smart contract transactions)
const collateral = await cardano.getCollateral();
console.log("Collateral UTXOs:", collateral.length);Transaction Signing
Sign and submit transactions to the Cardano network.
// Sign an unsigned transaction
const signedTx = await cardano.signTx(unsignedTx);
// Submit the signed transaction
const txHash = await cardano.submitTx(signedTx);
console.log("Transaction hash:", txHash);
// Sign arbitrary data (for authentication)
const signature = await cardano.signData(payload, address);Sign Transaction Parameters
| Parameter | Type | Description |
|---|---|---|
| unsignedTx | string | CBOR-encoded unsigned transaction |
Sign Data Parameters
| Parameter | Type | Description |
|---|---|---|
| payload | string | Data to sign |
| address | string | Optional address to sign with |
Asset Information
Query wallet balance and native assets.
// Get total balance (lovelace + native assets)
const balance = await cardano.getBalance();
// Get all assets in wallet
const assets = await cardano.getAssets();
// Get policy IDs of assets in wallet
const policyIds = await cardano.getPolicyIds();
// Get assets from a specific policy
const policyAssets = await cardano.getPolicyIdAssets("policy_id_here");Asset Methods
| Method | Returns | Description |
|---|---|---|
getBalance() | Balance | Total balance including native assets |
getAssets() | Asset[] | All native assets in wallet |
getPolicyIds() | string[] | Unique policy IDs in wallet |
getPolicyIdAssets(policyId) | Asset[] | Assets from specific policy |
Wallet Management
Export wallet data and manage sessions.
// Export wallet for backup (shows private keys to user)
await wallet.exportWallet("cardano");
// Disable wallet (logs out user, clears JWT)
await wallet.disable();Session Patterns
Pattern 1: Connect and Store
Store the wallet instance for use across your application.
import { createContext, useContext } from "react";
// Connect and store wallet
const wallet = await initCardanoWallet();
const WalletContext = createContext(wallet);
// Use wallet elsewhere
function useWallet() {
return useContext(WalletContext);
}
// Sign transactions later
const wallet = useWallet();
const signature = await wallet.signData("payload-to-sign");Pattern 2: Keep Window Open
Flow directly from authentication to signing without closing the wallet window.
const wallet = await Web3Wallet.enable({
projectId: process.env.NEXT_PUBLIC_UTXOS_PROJECT_ID,
networkId: parseInt(process.env.NEXT_PUBLIC_NETWORK_ID) || 0,
keepWindowOpen: true, // Window stays open for follow-up operations
});
// Immediately sign without reopening
const signature = await wallet.cardano.signData("payload-to-sign");API Reference
Methods
| Method | Returns | Description |
|---|---|---|
getChangeAddress() | Promise<string> | Get change address |
getUsedAddresses() | Promise<string[]> | Get used addresses |
getUnusedAddresses() | Promise<string[]> | Get unused addresses |
getUtxos() | Promise<UTxO[]> | Get all UTXOs |
getCollateral() | Promise<UTxO[]> | Get collateral UTXOs |
getBalance() | Promise<Balance> | Get wallet balance |
getAssets() | Promise<Asset[]> | Get native assets |
getPolicyIds() | Promise<string[]> | Get policy IDs |
getPolicyIdAssets(id) | Promise<Asset[]> | Get assets by policy |
signTx(tx) | Promise<string> | Sign a transaction |
signData(payload, addr?) | Promise<Signature> | Sign arbitrary data |
submitTx(tx) | Promise<string> | Submit transaction |
Types
interface UTxO {
input: {
txHash: string;
outputIndex: number;
};
output: {
address: string;
amount: Asset[];
};
}
interface Asset {
unit: string;
quantity: string;
}
interface Balance {
lovelace: string;
assets: Asset[];
}Complete Examples
Example 1: Send ADA
import { Web3Wallet } from "@utxos/sdk";
import { BlockfrostProvider, MeshTxBuilder } from "@meshsdk/core";
const provider = new BlockfrostProvider("/api/blockfrost/preprod/");
async function sendADA(recipientAddress: string, amountADA: number) {
const wallet = await Web3Wallet.enable({
projectId: process.env.NEXT_PUBLIC_UTXOS_PROJECT_ID,
networkId: 0,
fetcher: provider,
submitter: provider,
});
const cardano = wallet.cardano;
// Build transaction
const tx = new MeshTxBuilder({ fetcher: provider });
const amountLovelace = (amountADA * 1_000_000).toString();
tx.txOut(recipientAddress, [{ unit: "lovelace", quantity: amountLovelace }])
.changeAddress(await cardano.getChangeAddress())
.selectUtxosFrom(await cardano.getUtxos());
// Complete, sign, and submit
const unsignedTx = await tx.complete();
const signedTx = await cardano.signTx(unsignedTx);
const txHash = await cardano.submitTx(signedTx);
console.log("Transaction submitted:", txHash);
return txHash;
}
// Usage
const txHash = await sendADA("addr_test1...", 5);Example 2: Query Wallet Assets
import { Web3Wallet } from "@utxos/sdk";
import { BlockfrostProvider } from "@meshsdk/core";
async function getWalletAssets() {
const provider = new BlockfrostProvider("/api/blockfrost/preprod/");
const wallet = await Web3Wallet.enable({
projectId: process.env.NEXT_PUBLIC_UTXOS_PROJECT_ID,
networkId: 0,
fetcher: provider,
submitter: provider,
});
const cardano = wallet.cardano;
// Get balance
const balance = await cardano.getBalance();
console.log("Balance:", balance);
// Get all assets
const assets = await cardano.getAssets();
console.log("Assets:", assets);
// Get unique policy IDs
const policyIds = await cardano.getPolicyIds();
console.log("Policy IDs:", policyIds);
// Query assets by policy
for (const policyId of policyIds) {
const policyAssets = await cardano.getPolicyIdAssets(policyId);
console.log(`Policy ${policyId}:`, policyAssets);
}
return { balance, assets, policyIds };
}Example 3: Sign Data for Authentication
import { Web3Wallet } from "@utxos/sdk";
import { BlockfrostProvider } from "@meshsdk/core";
async function authenticateWithSignature(challenge: string) {
const provider = new BlockfrostProvider("/api/blockfrost/preprod/");
const wallet = await Web3Wallet.enable({
projectId: process.env.NEXT_PUBLIC_UTXOS_PROJECT_ID,
networkId: 0,
fetcher: provider,
submitter: provider,
});
const cardano = wallet.cardano;
const address = await cardano.getChangeAddress();
// Sign the challenge
const signature = await cardano.signData(challenge, address);
return {
address,
signature,
challenge,
};
}
// Verify on server
async function verifySignature(address: string, signature: any, challenge: string) {
// Server-side verification logic
// Use a library like @meshsdk/core to verify
}Error Handling
Handle common Cardano wallet errors gracefully.
import { ApiError } from "@utxos/sdk";
async function safeCardanoOperation() {
try {
const unsignedTx = await buildTransaction();
const signedTx = await cardano.signTx(unsignedTx);
const txHash = await cardano.submitTx(signedTx);
return txHash;
} catch (error) {
if (error.message.includes("Insufficient")) {
throw new Error("Not enough ADA for this transaction");
}
if (error.message.includes("collateral")) {
throw new Error("Insufficient collateral for smart contract");
}
if (error.message.includes("User declined")) {
throw new Error("Transaction was cancelled by user");
}
throw error;
}
}Common Errors
| Error | Cause | Solution |
|---|---|---|
| Insufficient funds | Balance too low | Check balance before building tx |
| Insufficient collateral | Missing collateral UTXOs | Ensure wallet has collateral |
| User declined | User cancelled signing | Handle gracefully in UI |
| Network error | Provider unavailable | Retry or show offline message |
Set Up Blockchain Data Provider on Server-Side
Configure Blockfrost through a secure proxy to keep API keys server-side.
Keep API keys server-side. Never expose them in client code.
Environment Variables
# .env
BLOCKFROST_API_KEY_PREPROD=your_preprod_key
BLOCKFROST_API_KEY_MAINNET=your_mainnet_keyProxy Route (Next.js App Router)
Create app/api/blockfrost/[...slug]/route.ts:
import { NextRequest } from "next/server";
export async function GET(
request: NextRequest,
{ params }: { params: { slug: string[] } }
) {
return handleBlockfrostRequest(request, params.slug, "GET");
}
export async function POST(
request: NextRequest,
{ params }: { params: { slug: string[] } }
) {
return handleBlockfrostRequest(request, params.slug, "POST");
}
async function handleBlockfrostRequest(
request: NextRequest,
slug: string[],
method: string
) {
const network = slug[0]; // "preprod" | "mainnet"
const config = getNetworkConfig(network);
if (!config.key) {
return Response.json(
{ error: `Missing Blockfrost API key for ${network}` },
{ status: 500 }
);
}
const endpointPath = slug.slice(1).join("/") || "";
const queryString = getQueryString(request.url);
const url = `${config.baseUrl}/${endpointPath}${queryString}`;
// Determine content type
const isCborEndpoint =
endpointPath === "tx/submit" || endpointPath === "utils/txs/evaluate";
const headers: Record<string, string> = {
project_id: config.key,
"Content-Type": isCborEndpoint ? "application/cbor" : "application/json",
};
const response = await fetch(url, {
method,
headers,
body: method !== "GET" ? request.body : undefined,
// @ts-ignore - duplex is required for streaming body
duplex: method !== "GET" ? "half" : undefined,
});
// Handle 404 for UTXOs as empty wallet
if (response.status === 404 && endpointPath.includes("/utxos")) {
return Response.json([]);
}
if (!response.ok) {
const errorBody = await response.text();
return Response.json(
{ error: `Blockfrost error: ${response.status}`, details: errorBody },
{ status: response.status }
);
}
// Handle CBOR endpoints
if (isCborEndpoint) {
const data = await response.text();
return Response.json(data);
}
const data = await response.json();
return Response.json(data);
}
function getQueryString(url: string): string {
const index = url.indexOf("?");
return index !== -1 ? url.substring(index) : "";
}
function getNetworkConfig(network: string) {
switch (network) {
case "mainnet":
return {
key: process.env.BLOCKFROST_API_KEY_MAINNET,
baseUrl: "https://cardano-mainnet.blockfrost.io/api/v0",
};
default:
return {
key: process.env.BLOCKFROST_API_KEY_PREPROD,
baseUrl: "https://cardano-preprod.blockfrost.io/api/v0",
};
}
}Use the Proxy
import { BlockfrostProvider } from "@meshsdk/core";
import { Web3Wallet } from "@utxos/sdk";
const provider = new BlockfrostProvider("/api/blockfrost/preprod/");
const wallet = await Web3Wallet.enable({
projectId: process.env.NEXT_PUBLIC_UTXOS_PROJECT_ID,
networkId: 0,
fetcher: provider,
submitter: provider,
});With the proxy in place, you can safely use Cardano functionality without exposing your Blockfrost API key.
Security
Follow these security practices for production deployments.
| Practice | Description |
|---|---|
| Validate addresses | Verify address format (addr_test1 for preprod, addr1 for mainnet) |
| Use preprod first | Test thoroughly before mainnet deployment |
| Check balances | Verify sufficient ADA before building transactions |
| Secure API keys | Keep Blockfrost keys server-side via proxy |
| Handle errors | Implement comprehensive error handling |
| Collateral management | Ensure adequate collateral for smart contracts |
Integration Test
Verify your setup is working correctly.
import { Web3Wallet } from "@utxos/sdk";
import { BlockfrostProvider } from "@meshsdk/core";
async function testCardanoIntegration() {
const provider = new BlockfrostProvider("/api/blockfrost/preprod/");
const wallet = await Web3Wallet.enable({
projectId: process.env.NEXT_PUBLIC_UTXOS_PROJECT_ID,
networkId: 0,
fetcher: provider,
submitter: provider,
});
const cardano = wallet.cardano;
// Test address
const address = await cardano.getChangeAddress();
console.assert(
address.startsWith("addr_test1"),
"Invalid preprod address"
);
console.log("Address: OK");
// Test UTXOs
const utxos = await cardano.getUtxos();
console.assert(Array.isArray(utxos), "Invalid UTXO format");
console.log("UTXOs: OK");
// Test balance
const balance = await cardano.getBalance();
console.assert(balance !== undefined, "Invalid balance");
console.log("Balance: OK");
// Test data signing
const signature = await cardano.signData("test", address);
console.assert(signature !== undefined, "Invalid signature");
console.log("Data signing: OK");
console.log("All tests passed");
}