Spark Wallet
The UTXOS Spark wallet provides Bitcoin Layer 2 functionality for fast, low-cost transfers with Bitcoin security. It supports Bitcoin transfers, Spark tokens, deposits, withdrawals, and message signing via the Spark Wallet API.
TLDR: Enable a Spark 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 = regtest, 1 = mainnet
baseUrl: "/api/sparkscan",
});
const address = wallet.spark.getAddress();
console.log("Spark Address:", address.address);Table of Contents
- Prerequisites
- Setup
- Wallet Information
- Balance Management
- Bitcoin Transfers
- Token Transfers
- Message Signing
- Advanced Operations
- API Reference
- Complete Examples
- Error Handling
- API Proxy Setup
- Security
Prerequisites
You need the following before integrating the Spark wallet:
- UTXOS project ID from utxos.dev/dashboard
- Sparkscan API key for balance and history queries
Setup
Install Dependencies
npm install @utxos/sdkConfigure Environment
# .env
NEXT_PUBLIC_UTXOS_PROJECT_ID=your_project_id
NEXT_PUBLIC_NETWORK_ID=0 # 0 = regtest, 1 = mainnet
# Server-side only (for Sparkscan proxy)
SPARKSCAN_API_KEY=your_sparkscan_api_keyInitialize the Wallet
import { Web3Wallet, EnableWeb3WalletOptions } from "@utxos/sdk";
async function initSparkWallet() {
const options: EnableWeb3WalletOptions = {
projectId: process.env.NEXT_PUBLIC_UTXOS_PROJECT_ID,
networkId: parseInt(process.env.NEXT_PUBLIC_NETWORK_ID) || 0,
baseUrl: "/api/sparkscan", // Proxy to keep API key server-side
};
const wallet = await Web3Wallet.enable(options);
return wallet.spark;
}Verify Connection
const spark = await initSparkWallet();
const addressInfo = spark.getAddress();
console.log("Address:", addressInfo.address);
console.log("Network:", addressInfo.network);
const balance = await spark.getBalance();
console.log("Balance:", balance.balance.toString(), "sats");Wallet Information
Get Spark address and network information.
// Get address info (synchronous)
const addressInfo = spark.getAddress();
console.log("Spark Address:", addressInfo.address);
console.log("Network:", addressInfo.network); // "mainnet" | "regtest"
console.log("Public Key:", addressInfo.publicKey);Address Formats
| Network | New Format | Legacy Format |
|---|---|---|
| Mainnet | spark1… | sprt1… |
| Regtest | sparkrt1… | sprt1… |
Balance Management
Query Bitcoin and token balances.
const balanceData = await spark.getBalance();
// Bitcoin balance in satoshis
console.log("Bitcoin:", balanceData.balance.toString(), "sats");
// Token balances
balanceData.tokenBalances.forEach((tokenData, tokenId) => {
console.log("Token ID:", tokenId);
console.log(" Balance:", tokenData.balance);
console.log(" Name:", tokenData.tokenInfo.tokenName);
console.log(" Ticker:", tokenData.tokenInfo.tokenTicker);
console.log(" Decimals:", tokenData.tokenInfo.decimals);
});Balance Response
| Field | Type | Description |
|---|---|---|
| balance | bigint | Bitcoin balance in satoshis |
| tokenBalances | Map | Token ID to balance/info mapping |
Bitcoin Transfers
Send Bitcoin on the Spark Layer 2 network.
const result = await spark.transfer({
receiverSparkAddress: "sprt1...",
amountSats: 100000, // 0.001 BTC
});
console.log("Transaction ID:", result.txid);Transfer Parameters
| Parameter | Type | Description |
|---|---|---|
| receiverSparkAddress | string | Recipient Spark address |
| amountSats | number | Amount in satoshis |
Token Transfers
Send custom tokens on the Spark network.
const result = await spark.transferToken({
tokenIdentifier: "btkn1...", // Bech32m token identifier
tokenAmount: 1000,
receiverSparkAddress: "sprt1...",
});
console.log("Transaction ID:", result.txid);Token Transfer Parameters
| Parameter | Type | Description |
|---|---|---|
| tokenIdentifier | string | Bech32m token identifier |
| tokenAmount | number | Amount to transfer |
| receiverSparkAddress | string | Recipient Spark address |
Message Signing
Sign messages for authentication or verification.
const result = await spark.signMessage({
message: "Sign this to authenticate",
});
console.log("Signature:", result.signature);Sign Message Parameters
| Parameter | Type | Description |
|---|---|---|
| message | string | Message to sign |
Advanced Operations
Claim Bitcoin Deposits
Claim Bitcoin sent to your Spark static deposit address.
const result = await spark.claimStaticDeposit({
txId: "your_bitcoin_txid",
});
console.log("Transfer ID:", result.transferId);Withdraw to Bitcoin L1
Withdraw from Spark Layer 2 back to Bitcoin mainchain.
const result = await spark.withdrawToBitcoin({
exitSpeed: "FAST", // "FAST" | "NORMAL" | "SLOW"
amountSats: 100000,
deductFeeFromWithdrawalAmount: true,
withdrawalAddress: "bc1q...",
});
console.log("Withdrawal ID:", result.withdrawalId);Withdrawal Parameters
| Parameter | Type | Description |
|---|---|---|
| exitSpeed | string | Speed tier: FAST, NORMAL, or SLOW |
| amountSats | number | Amount in satoshis |
| deductFeeFromWithdrawalAmount | boolean | Deduct fee from amount |
| withdrawalAddress | string | Bitcoin L1 address |
Exit Speed Tiers
| Speed | Description | Fee |
|---|---|---|
| FAST | Immediate withdrawal | Highest |
| NORMAL | Standard processing | Medium |
| SLOW | Batch processing | Lowest |
API Reference
Methods
| Method | Returns | Description |
|---|---|---|
getAddress() | AddressInfo | Get wallet address (sync) |
getBalance() | Promise<BalanceData> | Get Bitcoin and token balances |
transfer(params) | Promise<{txid: string}> | Send Bitcoin on Spark |
transferToken(params) | Promise<{txid: string}> | Send tokens on Spark |
signMessage(params) | Promise<{signature: string}> | Sign a message |
claimStaticDeposit(params) | Promise<{transferId: string}> | Claim Bitcoin deposit |
withdrawToBitcoin(params) | Promise<{withdrawalId: string}> | Withdraw to L1 |
Types
interface AddressInfo {
address: string;
network: "mainnet" | "regtest";
publicKey: string;
}
interface BalanceData {
balance: bigint;
tokenBalances: Map<string, TokenBalance>;
}
interface TokenBalance {
balance: bigint;
tokenInfo: {
tokenName: string;
tokenTicker: string;
decimals: number;
};
}Complete Examples
Example 1: Basic Spark Operations
import { Web3Wallet } from "@utxos/sdk";
async function sparkOperations() {
const wallet = await Web3Wallet.enable({
projectId: process.env.NEXT_PUBLIC_UTXOS_PROJECT_ID,
networkId: 0,
baseUrl: "/api/sparkscan",
});
const spark = wallet.spark;
// Get address
const addressInfo = spark.getAddress();
console.log("Address:", addressInfo.address);
// Get balance
const balance = await spark.getBalance();
console.log("Balance:", balance.balance.toString(), "sats");
// Transfer Bitcoin
const result = await spark.transfer({
receiverSparkAddress: "sprt1...",
amountSats: 10000,
});
console.log("Transfer ID:", result.txid);
// Sign message
const signature = await spark.signMessage({
message: "Hello Spark",
});
console.log("Signature:", signature.signature);
return { addressInfo, balance };
}Example 2: Deposit and Withdraw Flow
import { Web3Wallet } from "@utxos/sdk";
async function depositWithdrawFlow() {
const wallet = await Web3Wallet.enable({
projectId: process.env.NEXT_PUBLIC_UTXOS_PROJECT_ID,
networkId: 1, // mainnet
baseUrl: "/api/sparkscan",
});
const spark = wallet.spark;
// After sending Bitcoin to your static deposit address,
// claim it on Spark L2
const claimResult = await spark.claimStaticDeposit({
txId: "your_bitcoin_txid",
});
console.log("Claimed deposit:", claimResult.transferId);
// Check new balance
const balance = await spark.getBalance();
console.log("New balance:", balance.balance.toString(), "sats");
// Withdraw back to Bitcoin L1
const withdrawResult = await spark.withdrawToBitcoin({
exitSpeed: "NORMAL",
amountSats: 50000,
deductFeeFromWithdrawalAmount: true,
withdrawalAddress: "bc1q...",
});
console.log("Withdrawal ID:", withdrawResult.withdrawalId);
return { claimResult, withdrawResult };
}Example 3: Token Operations
import { Web3Wallet } from "@utxos/sdk";
async function tokenOperations() {
const wallet = await Web3Wallet.enable({
projectId: process.env.NEXT_PUBLIC_UTXOS_PROJECT_ID,
networkId: 0,
baseUrl: "/api/sparkscan",
});
const spark = wallet.spark;
// Get all token balances
const balanceData = await spark.getBalance();
console.log("Token balances:");
balanceData.tokenBalances.forEach((tokenData, tokenId) => {
const formatted =
Number(tokenData.balance) / Math.pow(10, tokenData.tokenInfo.decimals);
console.log(` ${tokenData.tokenInfo.tokenTicker}: ${formatted}`);
});
// Transfer a token
const result = await spark.transferToken({
tokenIdentifier: "btkn1...",
tokenAmount: 100,
receiverSparkAddress: "sprt1...",
});
console.log("Token transfer ID:", result.txid);
return result;
}Error Handling
Handle common Spark wallet errors gracefully.
import { ApiError } from "@utxos/sdk";
async function safeSparkOperation() {
try {
const result = await spark.transfer({
receiverSparkAddress: "sprt1...",
amountSats: 75000,
});
return result;
} catch (error) {
if (error instanceof ApiError) {
switch (error.json.code) {
case 2:
throw new Error("Invalid response from wallet");
case 3:
throw new Error("User declined the operation");
case 5:
throw new Error("API key not configured for balance queries");
default:
throw new Error(`Spark error: ${error.json.info}`);
}
}
throw error;
}
}Common Errors
| Code | Error | Solution |
|---|---|---|
| 2 | Wrong response | Check wallet state |
| 3 | User declined | Handle cancellation in UI |
| 5 | No API key | Configure Sparkscan API key |
Set Up Sparkscan API Proxy
Configure a proxy to keep your Sparkscan API key server-side.
Keep API keys server-side. Never expose them in client code.
Environment Variables
# .env
SPARKSCAN_API_KEY=your_sparkscan_api_keyProxy Route (Next.js App Router)
Create app/api/sparkscan/[...slug]/route.ts:
import { NextRequest } from "next/server";
export async function GET(
request: NextRequest,
{ params }: { params: { slug: string[] } }
) {
return handleSparkscanRequest(request, params.slug, "GET");
}
export async function POST(
request: NextRequest,
{ params }: { params: { slug: string[] } }
) {
return handleSparkscanRequest(request, params.slug, "POST");
}
async function handleSparkscanRequest(
request: NextRequest,
slug: string[],
method: string
) {
const apiKey = process.env.SPARKSCAN_API_KEY;
if (!apiKey) {
return Response.json(
{
error: "Missing Sparkscan API key",
message: "Add SPARKSCAN_API_KEY to your .env file",
},
{ status: 500 }
);
}
const endpoint = slug.join("/");
const baseUrl = "https://api.sparkscan.io/v1";
const url = `${baseUrl}/${endpoint}`;
const body = method !== "GET" ? await request.json() : undefined;
const response = await fetch(url, {
method,
headers: {
Authorization: `Bearer ${apiKey}`,
Accept: "application/json",
...(method !== "GET" && { "Content-Type": "application/json" }),
},
body: body ? JSON.stringify(body) : undefined,
});
const data = await response.json();
return Response.json(data, { status: response.status });
}Use the Proxy
import { Web3Wallet } from "@utxos/sdk";
const wallet = await Web3Wallet.enable({
projectId: process.env.NEXT_PUBLIC_UTXOS_PROJECT_ID,
networkId: 0,
baseUrl: "/api/sparkscan", // Uses secure proxy
});
const spark = wallet.spark;With the proxy in place, you can safely use Spark functionality without exposing your Sparkscan API key.
Security
Follow these security practices for production deployments.
| Practice | Description |
|---|---|
| Validate addresses | Verify Spark address format before sending |
| Use regtest first | Test thoroughly before mainnet deployment |
| Secure API keys | Keep Sparkscan key server-side via proxy |
| Handle errors | Implement comprehensive error handling |
| Verify withdrawals | Double-check L1 addresses before withdrawing |
Address Validation
function isValidSparkAddress(address: string, network: "mainnet" | "regtest") {
if (network === "mainnet") {
return address.startsWith("spark1") || address.startsWith("sprt1");
}
return address.startsWith("sparkrt1") || address.startsWith("sprt1");
}
// Usage
const recipient = "sprt1...";
if (!isValidSparkAddress(recipient, "regtest")) {
throw new Error("Invalid Spark address for regtest");
}Integration Test
Verify your setup is working correctly.
import { Web3Wallet } from "@utxos/sdk";
async function testSparkIntegration() {
const wallet = await Web3Wallet.enable({
projectId: process.env.NEXT_PUBLIC_UTXOS_PROJECT_ID,
networkId: 0,
baseUrl: "/api/sparkscan",
});
const spark = wallet.spark;
// Test address
const addressInfo = spark.getAddress();
console.assert(addressInfo.address.length > 0, "No address returned");
console.assert(
addressInfo.network === "regtest",
"Wrong network"
);
console.log("Address: OK");
// Test balance (requires API key)
const balance = await spark.getBalance();
console.assert(typeof balance.balance === "bigint", "Invalid balance type");
console.log("Balance: OK");
// Test message signing
const signResult = await spark.signMessage({
message: "test",
});
console.assert(
typeof signResult.signature === "string",
"Invalid signature"
);
console.log("Message signing: OK");
console.log("All tests passed");
}