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

You need the following before integrating the Spark wallet:

Setup

Install Dependencies

npm install @utxos/sdk

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

Initialize 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

NetworkNew FormatLegacy Format
Mainnetspark1…sprt1…
Regtestsparkrt1…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

FieldTypeDescription
balancebigintBitcoin balance in satoshis
tokenBalancesMapToken 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

ParameterTypeDescription
receiverSparkAddressstringRecipient Spark address
amountSatsnumberAmount 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

ParameterTypeDescription
tokenIdentifierstringBech32m token identifier
tokenAmountnumberAmount to transfer
receiverSparkAddressstringRecipient 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

ParameterTypeDescription
messagestringMessage 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

ParameterTypeDescription
exitSpeedstringSpeed tier: FAST, NORMAL, or SLOW
amountSatsnumberAmount in satoshis
deductFeeFromWithdrawalAmountbooleanDeduct fee from amount
withdrawalAddressstringBitcoin L1 address

Exit Speed Tiers

SpeedDescriptionFee
FASTImmediate withdrawalHighest
NORMALStandard processingMedium
SLOWBatch processingLowest

API Reference

Methods

MethodReturnsDescription
getAddress()AddressInfoGet 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

CodeErrorSolution
2Wrong responseCheck wallet state
3User declinedHandle cancellation in UI
5No API keyConfigure 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_key

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

PracticeDescription
Validate addressesVerify Spark address format before sending
Use regtest firstTest thoroughly before mainnet deployment
Secure API keysKeep Sparkscan key server-side via proxy
Handle errorsImplement comprehensive error handling
Verify withdrawalsDouble-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");
}