Wallet As A Service Usage Guide
UTXOS Wallet as a Service provides a hosted wallet solution that eliminates the need for users to install browser extensions or manage private keys. It’s perfect for onboarding new users to your Cardano dApp with minimal friction.
Integration Options
There are three main ways to integrate UTXOS wallet as a service:
@meshsdk/web3-sdk
- For any TypeScript/JavaScript applications@meshsdk/react
- For React/Next.js applications with pre-built UI components
Prerequisites
Before you begin, you need to set up a project:
1. Create A Free Account
- Create an account by logging in with one of the supported providers:

2. Create A Project
From the dashboard:
- Click “Create New Project”
- Enter your project name and
http:localhost:3000/
for now

3. Get Your UTXOS Credentials
- Copy your Project ID from project settings - Used to identify your project

- Save this value into your codebase .env file
# .env
NEXT_PUBLIC_UTXOS_PROJECT_ID=your_project_id # safe to expose on client
4. Blockchain Data Provider
This is boilerplate to work with Cardano and not specific to UTXOS usage
- Get a free API key from Blockfrost or any another supported provider
- Edit your .env file to expose new
BLOCKFROST_API_KEY
to the server - Find out how to setup blockfrost
Method 1: Using @meshsdk/web3-sdk
This method is ideal for both client and server-side applications, or any JavaScript/TypeScript environment where you need programmatic wallet access.
TLDR: Minimal required code to integrate wallet as a service:
import { EnableWeb3WalletOptions, Web3Wallet } from "@meshsdk/web3-sdk";
// Configure UTXOS wallet options
const options: EnableWeb3WalletOptions = {
networkId: parseInt(process.env.NEXT_PUBLIC_NETWORK_ID) || 0, // 0: preprod, 1: mainnet
projectId: process.env.NEXT_PUBLIC_UTXOS_PROJECT_ID, // https://utxos.dev/dashboard
};
// Enable the wallet
const wallet = await Web3Wallet.enable(options);
1. Install Dependencies
Install the required packages:
npm install @meshsdk/web3-sdk @meshsdk/provider
2. Set Up Environment Variables
Edit your .env
file with your project id, blockchain provider api key, and the network:
# .env
NEXT_PUBLIC_UTXOS_PROJECT_ID=your_project_id # https://utxos.dev/dashboard
BLOCKFROST_API_KEY_PREPROD=your_blockfrost_api_key # https://blockfrost.io/dashboard
NEXT_PUBLIC_NETWORK_ID=0 # 0 for preprod, 1 for mainnet
3. Initialize The Wallet
Create a wallet instance with proper error handling:
import { EnableWeb3WalletOptions, Web3Wallet } from "@meshsdk/web3-sdk";
import { BlockfrostProvider } from "@meshsdk/provider";
async function initializeWallet() {
try {
// Initialize the blockchain data provider with secure api endpoint
// const provider = new BlockfrostProvider(process.env.BLOCKFROST_API_KEY_PREPROD); // quick start method (insecure)
const provider = new BlockfrostProvider(`/api/blockfrost/preprod/`);
// Configure UTXOS wallet options
const options: EnableWeb3WalletOptions = {
networkId: parseInt(process.env.NEXT_PUBLIC_NETWORK_ID) || 0, // 0: preprod, 1: mainnet
fetcher: provider,
submitter: provider,
projectId: process.env.NEXT_PUBLIC_UTXOS_PROJECT_ID, // https://utxos.dev/dashboard
};
// Enable the wallet
const wallet = await Web3Wallet.enable(options);
console.log("Wallet initialized successfully");
return wallet;
} catch (error) {
console.error("Failed to initialize wallet:", error);
throw error;
}
}
// Usage
const wallet = await initializeWallet();
4. Verify Wallet Connection
Test that your wallet is working correctly, for example by fetching the wallet address:
const address = await wallet.getChangeAddress();
// Chain-specific operations must use the appropriate chain wallet
// For Cardano
if (wallet.cardano) {
const utxos = await wallet.cardano.getUtxos();
console.log("Cardano UTXOs:", utxos);
}
// For Bitcoin
if (wallet.bitcoin) {
const utxos = await wallet.bitcoin.getUtxos();
console.log("Bitcoin UTXOs:", utxos);
}
Method 2: Using @meshsdk/react
This method provides pre-built React components and hooks, making it perfect for React/Next.js applications with minimal setup.
TLDR: Minimal required code to integrate wallet as a service:
import { CardanoWallet, useWallet } from "@meshsdk/react";
import { BlockfrostProvider } from "@meshsdk/core";
const provider = new BlockfrostProvider(`/api/blockfrost/preprod/` || `YOUR_API_KEY`);
<CardanoWallet
web3Services={{
networkId: parseInt(process.env.NEXT_PUBLIC_NETWORK_ID) || 0,
fetcher: provider,
submitter: provider,
projectId: process.env.NEXT_PUBLIC_UTXOS_PROJECT_ID,
}}
/>
1. Install Dependencies
npm install @meshsdk/core @meshsdk/react
2. Set Up The Wallet Provider
Wrap your app with the MeshProvider
to make wallet context available throughout your application:
// app/layout.js or pages/_app.js (Next.js) or App.jsx (React)
import "@meshsdk/react/styles.css";
import { MeshProvider } from "@meshsdk/react";
export default function App({ Component, pageProps }) {
return (
<MeshProvider>
<Component {...pageProps} />
</MeshProvider>
);
}
3. Create A Wallet Connection Component
Build a component that handles wallet connection with user feedback:
import { CardanoWallet, useWallet } from "@meshsdk/react";
import { BlockfrostProvider } from "@meshsdk/core";
import { useState, useEffect } from "react";
// const provider = new BlockfrostProvider(process.env.NEXT_PUBLIC_BLOCKFROST_API_KEY_PREPROD); // Quick start method (insecure)
const provider = new BlockfrostProvider(`/api/blockfrost/preprod/`);
export default function WalletConnection() {
const { wallet, connected, connecting } = useWallet();
const [balance, setBalance] = useState(null);
const [error, setError] = useState(null);
// Fetch wallet balance when connected
useEffect(() => {
async function fetchBalance() {
if (connected && wallet) {
try {
const utxos = await wallet.getUtxos();
const totalLovelace = utxos.reduce((sum, utxo) => {
const lovelace = utxo.output.amount.find(
(asset) => asset.unit === "lovelace",
);
return sum + parseInt(lovelace?.quantity || "0");
}, 0);
setBalance(totalLovelace / 1_000_000);
} catch (err) {
setError("Failed to fetch balance");
console.error(err);
}
}
}
fetchBalance();
}, [connected, wallet]);
return (
<div className="rounded-lg bg-white p-6 shadow-md">
<h2 className="mb-4 text-xl font-bold">Wallet Connection</h2>
{!connected ? (
<div>
<p className="mb-4 text-gray-600">
Connect your wallet to start using the application
</p>
<CardanoWallet
web3Services={{
networkId: parseInt(process.env.NEXT_PUBLIC_NETWORK_ID) || 0,
fetcher: provider,
submitter: provider,
projectId: process.env.NEXT_PUBLIC_UTXOS_PROJECT_ID,
}}
/>
{connecting && (
<p className="mt-2 text-blue-600">Connecting wallet...</p>
)}
</div>
) : (
<div>
<p className="font-medium text-green-600">✅ Wallet Connected</p>
{balance !== null && (
<p className="mt-2">Balance: {balance.toFixed(2)} ADA</p>
)}
{error && <p className="mt-2 text-red-600">{error}</p>}
</div>
)}
</div>
);
}
4. Use Wallet In Components
Access wallet functionality throughout your app:
import { useWallet } from "@meshsdk/react";
export default function TransactionComponent() {
const { wallet, connected } = useWallet();
const handleTransaction = async () => {
if (!connected || !wallet) {
alert("Please connect your wallet first");
return;
}
try {
const address = await wallet.getChangeAddress();
console.log("Sending from:", address);
// Your transaction logic here
const tx = '';
const signedTx = await wallet.signTx(tx);
} catch (error) {
console.error("Transaction failed:", error);
}
};
return (
<button
onClick={handleTransaction}
disabled={!connected}
className="rounded bg-blue-500 px-4 py-2 text-white disabled:opacity-50"
>
Test Transaction
</button>
);
}
Wallet API Reference
The wallet provides a comprehensive API for blockchain interactions. See all available methods in the SDK documentation.
Here are some common operations you can perform with the wallet:
Address Management
// Get the primary wallet address (works for any chain)
const changeAddress = await wallet.getChangeAddress();
// Chain-specific address operations
// Cardano operations
if (wallet.cardano) {
// Get all wallet addresses (if multiple)
const addresses = await wallet.cardano.getUsedAddresses();
// Get unused addresses for receiving funds
const unusedAddresses = await wallet.cardano.getUnusedAddresses();
}
// Bitcoin operations
if (wallet.bitcoin) {
// Get Bitcoin address
const bitcoinAddress = wallet.bitcoin.getAddress();
}
UTXO And Assets
// Chain-specific UTXO operations
// Cardano operations
if (wallet.cardano) {
// Get all UTXOs for the wallet
const utxos = await wallet.cardano.getUtxos();
// Get collateral UTXOs (for smart contract interactions)
const collateral = await wallet.cardano.getCollateral();
}
// Bitcoin operations
if (wallet.bitcoin) {
// Bitcoin UTXOs follow a different structure
const utxos = await wallet.bitcoin.getUtxos();
}
Sign Operations
// Sign a transaction (available in main wallet for both chains)
const signedTx = await wallet.signTx(unsignedTx, (partialSign = false));
// Chain-specific transaction operations
// Cardano operations
if (wallet.cardano) {
// Submit a signed transaction to the Cardano network
const txHash = await wallet.cardano.submitTx(signedTx);
}
// Sign arbitrary data (for authentication/verification)
// Available in main wallet for both chains
const signature = await wallet.signData(address, message);
Asset Information
// Chain-specific asset operations
// Cardano operations
if (wallet.cardano) {
// Get wallet balance (including native assets)
const balance = await wallet.cardano.getBalance();
// Get specific asset balance
const assetBalance = await wallet.cardano.getAssets();
// Get policy IDs of assets in wallet
const policyIds = await wallet.cardano.getPolicyIds();
// Get assets from a specific policy ID
const assets = await wallet.cardano.getPolicyIdAssets("asset_policy_id");
}
Wallet Export
Allow user to view their private keys for backup or migration purposes:
// Export wallet data
await wallet.exportWallet();
This will open a secure Iframe for the user to export their private keys
Complete Examples
Example 1: Simple Transaction
Here’s a complete example of sending ADA from the wallet:
import { Web3Wallet, EnableWeb3WalletOptions } from "@meshsdk/web3-sdk";
import { BlockfrostProvider, MeshTxBuilder } from "@meshsdk/core";
async function sendADA(recipientAddress, amountADA) {
try {
// Initialize provider and wallet
const provider = new BlockfrostProvider(`/api/blockfrost/preprod/`);
const options: EnableWeb3WalletOptions = {
networkId: 0, // preprod
fetcher: provider,
submitter: provider,
projectId: process.env.NEXT_PUBLIC_UTXOS_PROJECT_ID,
};
const wallet = await Web3Wallet.enable(options);
// Build transaction
const tx = new MeshTxBuilder({
fetcher: provider,
});
const amountLovelace = (amountADA * 1_000_000).toString();
// Build the asset unit (policy ID + asset name)
const assetUnit = policyId + assetName;
tx.txOut(recipientAddress, [
{ unit: "lovelace", quantity: amountLovelace }
])
tx.txOut(recipientAddress, [
{ unit: "lovelace", quantity: "1500000" },
{ unit: assetUnit, quantity: "1" }, // NFT
])
.changeAddress(await wallet.getChangeAddress())
.selectUtxosFrom(await wallet.cardano.getUtxos());
// Complete, sign, and submit
const unsignedTx = await tx.complete();
console.log("Transaction built successfully");
const signedTx = await wallet.signTx(unsignedTx);
console.log("Transaction signed");
const txHash = await wallet.cardano.submitTx(signedTx);
console.log("Transaction submitted:", txHash);
return txHash;
} catch (error) {
console.error("Transaction failed:", error);
throw error;
}
}
// Usage
await sendADA("addr_test1...", 5); // Send 5 ADA
Example 2: Smart Contract Interaction
Interact with a smart contract using the wallet:
import { PlutusScript, resolvePaymentKeyHash } from "@meshsdk/core";
async function interactWithContract(contractAddress, datum, redeemer) {
try {
const provider = new BlockfrostProvider(`/api/blockfrost/preprod/`);
const wallet = await Web3Wallet.enable({
networkId: 0,
fetcher: provider,
submitter: provider,
projectId: process.env.NEXT_PUBLIC_UTXOS_PROJECT_ID,
});
// Get script UTXOs at the contract address
const scriptUtxos = await provider.fetchUTXOs(contractAddress);
if (scriptUtxos.length === 0) {
throw new Error("No UTXOs found at contract address");
}
const tx = new MeshTxBuilder({ fetcher: provider });
// Consume from script
tx
.txIn(
scriptUtxos[0].input.txHash,
scriptUtxos[0].input.outputIndex,
scriptUtxos[0].output.amount,
contractAddress,
)
.spendingPlutusScript(PlutusScript.fromCbor("your_script_cbor"))
.txInInlineDatumPresent()
.txInRedeemerValue(redeemer)
// Add collateral for script execution
.txInCollateral(
(await wallet.cardano.getCollateral())[0].input.txHash,
(await wallet.cardano.getCollateral())[0].input.outputIndex,
(await wallet.cardano.getCollateral())[0].output.amount,
(await wallet.cardano.getCollateral())[0].output.address,
)
// Output back to wallet
.txOut(await wallet.getChangeAddress(), [
{ unit: "lovelace", quantity: "2000000" },
])
.changeAddress(await wallet.getChangeAddress())
.selectUtxosFrom(await wallet.cardano.getUtxos())
.requiredSignerHash(
resolvePaymentKeyHash(await wallet.getChangeAddress()),
);
const unsignedTx = await tx.complete();
const signedTx = await wallet.signTx(unsignedTx);
const txHash = await wallet.submitTx(signedTx);
console.log("Contract interaction successful:", txHash);
return txHash;
} catch (error) {
console.error("Contract interaction failed:", error);
throw error;
}
}
Security Considerations
Never expose your API keys in client-side code
- Never expose your API keys in client-side code - Always validate addresses before sending transactions - Implement proper error handling for production applications - Use environment variables for sensitive configuration - Test thoroughly on testnet before mainnet deployment - Verify the correct chain is used when working with multi-chain applications
Set Up Blockchain Data Provider
This is boilerplate to work with Cardano and not specific to UTXOS usage
Edit your .env file to expose new BLOCKFROST_API_KEY
to the server
# .env
NEXT_PUBLIC_UTXOS_PROJECT_ID=your_project_id
BLOCKFROST_API_KEY_PREPROD=your_blockfrost_api_key # never expose on client
- We need a safe way to consume
BLOCKFROST_API_KEY
without exposing it’s value. The solution varies slightly for your environment. In Next.js (App directory) we create a new file calledapp/api/blockfrost/[...slug]/route.ts
and paste in this function.
async function handleBlockfrostRequest(
request,
context,
) {
try {
const { params } = context;
const slug = params.slug || [];
const network = slug[0];
// Network configuration
const networkConfig = getNetworkConfig(network);
if (!networkConfig.key) {
return {
status: 500,
headers: { "Content-Type": "application/json" },
body: { error: `Missing Blockfrost API key for network: ${network}` },
};
}
// Construct endpoint
const endpointPath = slug.slice(1).join("/") || "";
const queryString = getQueryString(request.url);
const endpoint = endpointPath + queryString;
// Set headers
const headers = {
project_id: networkConfig.key,
};
if (endpointPath === "tx/submit" || endpointPath === "utils/txs/evaluate") {
headers["Content-Type"] = "application/cbor";
} else {
headers["Content-Type"] = "application/json";
}
// Forward request to Blockfrost
const url = `${networkConfig.baseUrl}/${endpoint}`;
const blockfrostResponse = await fetch(url, {
method: request.method,
headers,
body: request.method !== "GET" ? request.body : undefined,
});
// Handle 404 for UTXOs as empty wallet
if (blockfrostResponse.status === 404 && endpointPath.includes("/utxos")) {
return {
status: 200,
headers: { "Content-Type": "application/json" },
body: [],
};
}
// Handle errors
if (!blockfrostResponse.ok) {
const errorBody = await blockfrostResponse.text();
return {
status: blockfrostResponse.status,
headers: { "Content-Type": "application/json" },
body: {
error: `Blockfrost API error: ${blockfrostResponse.status} ${blockfrostResponse.statusText}`,
details: errorBody,
},
};
}
// Handle CBOR endpoints
if (endpointPath === "utils/txs/evaluate" || endpointPath === "tx/submit") {
const responseData = await blockfrostResponse.text();
return {
status: blockfrostResponse.status,
headers: { "Content-Type": "application/json" },
body: responseData,
};
}
// Handle JSON responses
const responseData = await blockfrostResponse.json();
return {
status: 200,
headers: { "Content-Type": "application/json" },
body: responseData,
};
} catch (error: unknown) {
console.error("Blockfrost API route error:", error);
const errorMessage = error instanceof Error ? error.message : String(error);
return {
status: 500,
headers: { "Content-Type": "application/json" },
body: { error: errorMessage },
};
}
}
// Helper functions
function getQueryString(url) {
const qIndex = url.indexOf("?");
return qIndex !== -1 ? url.substring(qIndex) : "";
}
function getNetworkConfig(network) {
switch (network) {
case "mainnet":
return {
key: process.env.BLOCKFROST_API_KEY_MAINNET,
baseUrl: "https://cardano-mainnet.blockfrost.io/api/v0",
};
// add different networks
default: // preprod
return {
key: process.env.BLOCKFROST_API_KEY_PREPROD,
baseUrl: "https://cardano-preprod.blockfrost.io/api/v0",
};
}
}
// Next.js App router specific exports
export async function GET(
request,
{ params },
) {
return createAppRouterHandler(request, params);
}
export async function POST(
request,
{ params },
) {
return createAppRouterHandler(request, params);
}
Now we can initialize BlockfrostProvider with our hosted api route and keep our credentials secure!