Skip to main content
Service Worker support enables your wallet to run in the background, providing persistent connectivity and event handling. The ServiceWorkerWallet exposes getVtxoManager() for VTXO lifecycle management from the worker context.

Overview

Running your wallet in a service worker enables:
  • Background Processing: Keep your wallet active even when the main application is closed
  • Persistent Storage: Use IndexedDB for reliable data persistence
  • Event Handling: Process incoming payments and settlements in the background
  • Improved UX: Faster response times and better reliability

Quick Setup

The v0.4 SDK provides ultra-simplified service worker setup with automatic registration and identity management:
// main.js
import { ServiceWorkerWallet, MnemonicIdentity } from '@arkade-os/sdk'
import { generateMnemonic } from '@scure/bip39'
import { wordlist } from '@scure/bip39/wordlists/english'

// Create your identity
const identity = MnemonicIdentity.fromMnemonic(generateMnemonic(wordlist))

const wallet = await ServiceWorkerWallet.setup({
  serviceWorkerPath: '/service-worker.js',
  arkServerUrl: 'https://arkade.computer',
  identity
})

// That's it! Ready to use immediately:
const address = await wallet.getAddress()
const balance = await wallet.getBalance()

Service Worker Implementation

Create a service worker file that handles communication:
// service-worker.js
import { Worker } from '@arkade-os/sdk'

// Worker handles communication between the main thread and service worker
new Worker().start()
That’s all you need! The Worker class automatically:
  • Handles message routing
  • Manages wallet state
  • Processes events
  • Handles cleanup

Example: Storage and Identity Management

Here’s a complete example with storage and identity management:
// main.js
import { ServiceWorkerWallet, MnemonicIdentity } from '@arkade-os/sdk'
import { LocalStorageAdapter } from '@arkade-os/sdk/adapters/localStorage'
import { generateMnemonic } from '@scure/bip39'
import { wordlist } from '@scure/bip39/wordlists/english'

const storage = new LocalStorageAdapter()

// Load or create identity
let mnemonic = await storage.getItem('mnemonic')
if (!mnemonic) {
  mnemonic = generateMnemonic(wordlist)
  await storage.setItem('mnemonic', mnemonic)
}

const identity = MnemonicIdentity.fromMnemonic(mnemonic)

// Setup service worker wallet
const wallet = await ServiceWorkerWallet.setup({
  serviceWorkerPath: '/service-worker.js',
  arkServerUrl: 'https://arkade.computer',
  identity
})

// Use wallet normally
const address = await wallet.getAddress()
console.log('Wallet address:', address)

const balance = await wallet.getBalance()
console.log('Balance:', balance.total)

Using IndexedDB Storage

For service workers, IndexedDB is recommended over LocalStorage:
import { ServiceWorkerWallet, MnemonicIdentity } from '@arkade-os/sdk'
import { IndexedDBStorageAdapter } from '@arkade-os/sdk/adapters/indexedDB'
import { generateMnemonic } from '@scure/bip39'
import { wordlist } from '@scure/bip39/wordlists/english'

const storage = new IndexedDBStorageAdapter('wallet-db', 1)

let mnemonic = await storage.getItem('mnemonic')
if (!mnemonic) {
  mnemonic = generateMnemonic(wordlist)
  await storage.setItem('mnemonic', mnemonic)
}

const identity = MnemonicIdentity.fromMnemonic(mnemonic)

const wallet = await ServiceWorkerWallet.setup({
  serviceWorkerPath: '/service-worker.js',
  arkServerUrl: 'https://arkade.computer',
  identity,
  storage // IndexedDB storage
})
IndexedDB is recommended for service workers because it’s accessible from both the main thread and service worker context, and can store larger amounts of data.

VTXO Management in the Service Worker

Available since v0.4.8. The VtxoManager is now accessible directly from the service worker wallet, enabling programmatic VTXO lifecycle operations alongside the automatic background renewal.
The service worker runs automatic VTXO renewal and boarding UTXO sweeps on startup. In addition, you can access the VtxoManager API directly for manual control:
import { ServiceWorkerWallet } from '@arkade-os/sdk'

const wallet = await ServiceWorkerWallet.setup({ /* ... */ })

// Access the VtxoManager exposed by the service worker
const vtxoManager = await wallet.getVtxoManager()

// Check which VTXOs are expiring soon
const expiring = await vtxoManager.getExpiringVtxos()
console.log(`${expiring.length} VTXOs expiring soon`)

// Manually trigger renewal
if (expiring.length > 0) {
  const txid = await vtxoManager.renewVtxos()
  console.log('Renewed:', txid)
}

// Check recoverable balance (swept/sub-dust VTXOs)
const { recoverable } = await vtxoManager.getRecoverableBalance()
if (recoverable > 0n) {
  await vtxoManager.recoverVtxos()
}

How Background Automation Works

When the service worker starts, VtxoManager begins eagerly:
  1. Auto-renewal — scans for VTXOs within the expiry threshold and renews them automatically
  2. Boarding sweep — settles any pending boarding UTXOs into the Ark
All VTXO-submitting operations (send, settle, renewVtxos) are serialized internally to prevent race conditions. You don’t need to coordinate between background automation and user-initiated sends.
See VTXO Management for the full VtxoManager API including getExpiringVtxos, recoverVtxos, and getRecoverableBalance.

Handling Events

Service workers can process wallet events in the background:
// main.js
import { waitForIncomingFunds } from '@arkade-os/sdk'

// Listen for incoming funds
const incomingFunds = await waitForIncomingFunds(wallet)

if (incomingFunds.type === "vtxo") {
  console.log("VTXOs received:", incomingFunds.newVtxos)
  // Show notification or update UI
} else if (incomingFunds.type === "utxo") {
  console.log("UTXOs received:", incomingFunds.coins)
  // Show notification or update UI
}

Advanced: Custom Service Worker

If you need custom service worker logic, you can extend the Worker class:
// service-worker.js
import { Worker } from '@arkade-os/sdk'

class CustomWorker extends Worker {
  async onMessage(event) {
    // Custom message handling
    console.log('Received message:', event.data)

    // Call parent handler
    await super.onMessage(event)
  }

  async onInstall(event) {
    console.log('Service worker installed')
  }

  async onActivate(event) {
    console.log('Service worker activated')
  }
}

new CustomWorker().start()

Best Practices

Always use IndexedDB for service worker wallets:
// ✅ Good - IndexedDB works in service worker context
const storage = new IndexedDBStorageAdapter('wallet-db', 1)

// ❌ Bad - LocalStorage not available in service workers
const storage = new LocalStorageAdapter()
Store your identity securely and load it on initialization:
const storage = new IndexedDBStorageAdapter('wallet-db', 1)

// Always check if identity exists before creating new one
let mnemonic = await storage.getItem('mnemonic')
if (!mnemonic) {
  mnemonic = generateMnemonic(wordlist)
  await storage.setItem('mnemonic', mnemonic)
}
Implement proper error handling for service worker operations. As of v0.4.8, the SDK exports explicit error types for clearer failure handling:
import { ServiceWorkerWallet, WalletError, WorkerError } from '@arkade-os/sdk'

try {
  const wallet = await ServiceWorkerWallet.setup({
    serviceWorkerPath: '/service-worker.js',
    arkServerUrl: 'https://arkade.computer',
    identity
  })
} catch (error) {
  if (error instanceof WorkerError) {
    // Service worker is dead or unresponsive — the SDK will auto-recover
    // on the next operation via a preflight health check
    console.error('Worker error:', error.message)
  } else if (error instanceof WalletError) {
    console.error('Wallet error:', error.message)
  } else {
    // Fallback to regular wallet
    console.error('Unknown error:', error)
  }
}

Troubleshooting

If the service worker fails to register:
  1. Ensure the service worker file path (serviceWorkerPath) is correct and accessible
  2. Service workers require HTTPS (or localhost for dev)
  3. Check browser console for registration errors
  4. Verify the service worker script imports Worker from @arkade-os/sdk and calls .start()
If you encounter IndexedDB errors:
  1. Check browser console for specific error messages
  2. Ensure the database name and version are consistent
  3. Clear browser data if the database schema changed
If you experience message timeouts:
  1. v0.4.8+: The SDK now auto-recovers from a dead service worker using a preflight PING before every operation. Persistent timeouts may indicate a network issue, not a crashed worker.
  2. Check that the Worker is properly started in the service worker file
  3. Verify network connectivity
  4. Check for errors in the service worker console
  5. If using custom error handling, check for WorkerError (worker unresponsive) vs WalletError (wallet-level failure) to distinguish root causes

Next Steps