How Wallet Settings and User Preferences Are Stored in Nautilus Wallet

Nautilus Wallet persists global user preferences in the browser's extension storage (browser.storage.local) via the useWebExtStorage composable, while per-wallet settings like address reuse policies are stored in IndexedDB through the walletsDbService, with both layers synchronized through Pinia stores.

Nautilus Wallet is an open-source browser extension for managing Ergo blockchain assets. Understanding how wallet settings and user preferences stored in Nautilus Wallet persist across sessions requires examining its dual-layer architecture that separates application-wide configuration from wallet-specific behavior.

Global User Preferences in Extension Storage

Global preferences—such as UI theme, language, and zero-confirmation settings—apply to the entire extension instance. These values live in the browser's extension storage area and are managed reactively through Pinia.

The useWebExtStorage Composable

In src/stores/appStore.ts, the global settings object is initialized using the useWebExtStorage composable:

// src/stores/appStore.ts
const settings = useWebExtStorage<Settings>("settings", DEFAULT_SETTINGS, {
  mergeDefaults: true
});

This composable, defined in src/composables/useWebExtStorage.ts, wraps the WebExtension Storage API (browser.storage.local). It serializes values using VueUse StorageSerializers and ensures that reads and writes are reactive within the Vue ecosystem.

Default Settings and Migration

Default values are defined in src/constants/settings.ts:

// src/constants/settings.ts
export const DEFAULT_SETTINGS = {
  lastOpenedWalletId: 0,
  isKyaAccepted: false,
  conversionCurrency: "usd",
  devMode: !MAINNET,
  graphQLServer: DEFAULT_SERVER_URL,
  explorerUrl: MAINNET ? "https://sigmaspace.io/en" : "https://testnet.ergoplatform.com",
  ipfsGateway: "https://ipfs.io/ipfs/",
  hideBalances: false,
  blacklistedTokensLists: ["nsfw", "scam"],
  zeroConf: false,
  locale: "auto" as const,
  colorMode: "auto" as const,
  extension: { viewMode: "popup" as const },
  ledger: { transport: "webusb" as const }
};

The extension includes migration logic to move legacy data from localStorage to the new extension storage. During initialization in appStore.ts, if isKyaAccepted is false but wallets exist, the store checks for old localStorage settings and migrates them before clearing the legacy storage.

Reading and Updating Global Preferences

To read a global setting, access the reactive ref through the app store:

import { useAppStore } from "@/stores/appStore";
import { computed } from "vue";

const appStore = useAppStore();
const isDarkMode = computed(() => appStore.settings.colorMode === "dark");

Updates are automatically persisted:

// Enable zero-confirmation transactions
appStore.settings.zeroConf = true;
// Changes are immediately written to browser.storage.local via useWebExtStorage

Per-Wallet Settings in IndexedDB

While global preferences apply to the entire extension, per-wallet settings control behavior specific to individual wallets, such as address reuse policies and change address indices. These are stored within the wallet's database record.

The WalletSettings Type and Database Schema

The type definition in src/types/internal.ts defines the structure:

// src/types/internal.ts
export type WalletSettings = {
  avoidAddressReuse: boolean;
  addressFilter: AddressFilter;   // "all" | "active" | "unused"
  defaultChangeIndex: number;
};

The database schema in src/types/database.ts nests these settings inside the wallet record:

// src/types/database.ts
export interface IDbWallet {
  id: number;
  name: string;
  type: WalletType;
  publicKey: string;
  chainCode: string;
  mnemonic?: string;
  settings: WalletSettings;   // Persisted per-wallet configuration
}

Persistence Flow via walletsDbService

When a wallet is created in src/stores/appStore.ts, default settings are embedded in the database record:

settings: {
  avoidAddressReuse: false,
  addressFilter: "all",
  defaultChangeIndex: 0
}

The walletsDbService in src/database/walletsDbService.ts handles IndexedDB operations. When settings change, appStore.updateWallet triggers a partial update to the settings column of the wallet record.

Accessing Wallet-Specific Configuration

The walletStore in src/stores/walletStore.ts loads the wallet record and exposes a reactive settings ref:

// Inside walletStore load()
settings.value = wlt.settings;

Components bind directly to these reactive properties. For example, to check address reuse settings:

import { useWalletStore } from "@/stores/walletStore";
import { computed } from "vue";

const walletStore = useWalletStore();
const avoidReuse = computed(() => walletStore.settings.avoidAddressReuse);

Changes trigger automatic persistence through a deep watcher in walletStore.ts:

watch(
  [name, settings, () => privateState.lastSynced],
  async () => {
    if (privateState.loading) return;
    appStore.updateWallet(privateState.id, {
      name: name.value,
      lastSynced: privateState.lastSynced,
      settings: toRaw(settings.value)
    });
  },
  { deep: true }
);

How the Storage Layers Interact

Nautilus Wallet maintains a strict architectural separation between global and wallet-specific configuration:

  • Global preferences in browser.storage.local affect the entire extension instance. These include theme settings, language, and global flags like zeroConf. They are accessible via appStore.settings.

  • Per-wallet settings in IndexedDB affect only the active wallet. These include address derivation policies and filters. They are accessible via walletStore.settings.

The UI components in src/views/settings/SettingsView.vue and src/views/wallet/WalletSettings.vue read from the appropriate Pinia store based on whether the setting is global or wallet-specific. Writes flow back through the same stores, ensuring persistence happens automatically without manual database calls.

Summary

  • Global user preferences are stored in browser.storage.local via the useWebExtStorage composable and accessed through appStore.settings.
  • Per-wallet settings are embedded in the IDbWallet records in IndexedDB, managed by walletsDbService, and exposed via walletStore.settings.
  • Automatic persistence occurs through Pinia's reactivity system: global settings write immediately to extension storage, while wallet settings trigger IndexedDB updates through deep watchers.
  • Migration support exists in appStore.ts for upgrading legacy localStorage data to the modern extension storage API.

Frequently Asked Questions

Where does Nautilus Wallet store theme and language preferences?

Theme and language preferences are stored in the browser's extension storage (browser.storage.local) as part of the global settings object. The useWebExtStorage composable in src/composables/useWebExtStorage.ts wraps this API, exposing a reactive settings ref in src/stores/appStore.ts that includes colorMode for themes and locale for language settings.

How are per-wallet address reuse settings persisted?

Per-wallet address reuse settings are stored in IndexedDB as part of the IDbWallet record. The WalletSettings type in src/types/internal.ts defines the avoidAddressReuse boolean, which is nested inside the IDbWallet interface from src/types/database.ts. When modified via walletStore.settings, a deep watcher triggers walletsDbService to update the specific wallet record in IndexedDB.

Does Nautilus Wallet migrate old localStorage data?

Yes. During initialization in src/stores/appStore.ts, the extension checks if isKyaAccepted is false while wallets exist in the database. If legacy localStorage data is detected, the store parses the old settings, merges them with DEFAULT_SETTINGS, writes them to browser.storage.local via useWebExtStorage, and clears the legacy localStorage to complete the migration.

Can I access wallet settings programmatically from a content script?

No. Wallet settings in IndexedDB and global settings in browser.storage.local are isolated to the extension's internal contexts. Content scripts run in the context of web pages and cannot directly access IndexedDB or extension storage without explicit permissions and messaging APIs. All programmatic access to settings should occur within the extension's background scripts or internal pages using the provided appStore and walletStore Pinia stores.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →