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

You need the following before integrating the Cardano wallet:

Setup

Install Dependencies

npm install @utxos/sdk @meshsdk/core

Configure 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_key

Initialize 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

MethodUse 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

ParameterTypeDescription
unsignedTxstringCBOR-encoded unsigned transaction

Sign Data Parameters

ParameterTypeDescription
payloadstringData to sign
addressstringOptional 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

MethodReturnsDescription
getBalance()BalanceTotal 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

MethodReturnsDescription
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

ErrorCauseSolution
Insufficient fundsBalance too lowCheck balance before building tx
Insufficient collateralMissing collateral UTXOsEnsure wallet has collateral
User declinedUser cancelled signingHandle gracefully in UI
Network errorProvider unavailableRetry 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_key

Proxy 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.

PracticeDescription
Validate addressesVerify address format (addr_test1 for preprod, addr1 for mainnet)
Use preprod firstTest thoroughly before mainnet deployment
Check balancesVerify sufficient ADA before building transactions
Secure API keysKeep Blockfrost keys server-side via proxy
Handle errorsImplement comprehensive error handling
Collateral managementEnsure 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");
}