Cardano Wallet As A Service
Ideal for both client and server-side applications, or any JavaScript/TypeScript environment where you need programmatic wallet access.
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/provider2. 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 mainnet3. 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
.envfile to expose newBLOCKFROST_API_KEYto the server - Find out how to setup blockfrost
4. 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
projectId: process.env.NEXT_PUBLIC_UTXOS_PROJECT_ID, // https://utxos.dev/dashboard
fetcher: provider,
submitter: provider,
};
// Enable the wallet
const wallet = await Web3Wallet.enable(options);
return wallet;
} catch (error) {
console.error("Failed to initialize wallet:", error);
throw error;
}
}
// Usage
const wallet = await initializeWallet();5. Verify Wallet Connection
Test that your wallet is working correctly, for example by fetching the wallet address:
const address = await wallet.cardano.getChangeAddress();
console.log("Change Address:", address);
const utxos = await wallet.cardano.getUtxos();
console.log("UTXOs:", utxos);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 change address for transaction outputs
const changeAddress = await wallet.cardano.getChangeAddress();
// Get wallet addresses
const addresses = await wallet.cardano.getUsedAddresses();
// Get unused addresses for receiving funds
const unusedAddresses = await wallet.cardano.getUnusedAddresses();UTXO And Assets
// Get all UTXOs for the wallet
const utxos = await wallet.cardano.getUtxos();
// Get collateral UTXOs
const collateral = await wallet.cardano.getCollateral();Sign Operations
// Sign a transaction
const signedTx = await wallet.cardano.signTx(
unsignedTx,
);
// Sign arbitrary data (for authentication/verification)
const signature = await wallet.cardano.signData(address, message);
// Submit a signed transaction to the Cardano network
const txHash = await wallet.cardano.submitTx(signedTx);Asset Information
// 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("cardano");Wallet Disable
// Logs user out of their JWT
await wallet.disable();Cache wallet
It is common to first connect to and store a users wallet, making it globally accessible in frontend applications. The preliminary “connect wallet” step used by many today.
Connect, store, and pass around
// connect, close window, and store
const wallet = await initializeWallet();
const MyContext = createContext(wallet);
// build transactions here
// open window to sign
const wallet = useContext(MyContext);
const signedTx = await wallet.cardano.signData("unsigned-data");Keep Window Open
It’s also possible to flow directly from unauthenticated -> sign tx in a single iframe, reducing the need to first connect. Simply add keepWindowOpen to the enable options and immediately use Web3Wallet in pure functions.
// step 1: enable, do things, and sign
const wallet = await Web3Wallet.enable({
networkId: parseInt(process.env.NEXT_PUBLIC_NETWORK_ID)
projectId: process.env.NEXT_PUBLIC_UTXOS_PROJECT_ID,
keepWindowOpen: true // default = false
});
// iframe will stay open while we perform async operations expecting a follow up operation.
const signedData = await wallet.cardano.signData('unsigned-data');Hello world example
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 enableWallet(){
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),
fetcher: provider,
submitter: provider,
};
const wallet = await Web3Wallet.enable(options);
return wallet;
}
async function sendADA(wallet, recipientAddress, amountADA) {
try {
// 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.cardano.getChangeAddress())
.selectUtxosFrom(await wallet.cardano.getUtxos());
// Complete, sign, and submit
const unsignedTx = await tx.complete();
const signedTx = await wallet.cardano.signTx(unsignedTx);
const txHash = await wallet.cardano.submitTx(signedTx);
console.log("Transaction submitted:", txHash);
return txHash;
} catch (error) {
console.error("Transaction failed:", error);
throw error;
}
}
// Usage: button onclick to execute each function
const wallet = await enableWallet();
const txHash = await sendADA(wallet, "addr_test1...", 5); // Send 5 ADASecurity Considerations
Never expose your API keys in client-side code
Always use server-side environment variables and proxy requests through secure API routes to keep your keys safe.
Set Up Blockchain Data Provider on server-side
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_KEYwithout 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.tsand 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!