# Local

Canonical: https://docs.projectbabbage.com/docs/guides/kvstore/local

Use SDK 4.0.0 LocalKVStore to read, save and remove encrypted wallet-backed values; understand fees, recovery, privacy and cross-device conflict limits.

Store small, wallet-owned application values with **`LocalKVStore`**, exported directly from `@bsv/sdk`. This guide was checked against **SDK 4.0.0 on October 26, 2026**. Use a maintained, patched Node.js 22 or 24 release and install `npm install @bsv/sdk@4.0.0`.

## What local means

This is not browser localStorage. Values are represented by PushDrop transaction outputs tracked in a wallet basket. The context names that basket, and the connected wallet supplies permissions, signing, encryption and its storage/recovery path. The same wallet account and restored wallet data are needed on another device; the browser's old cache is not the source of truth.

This is also not a public shared database or a distributed locking service. Use it for small preferences, drafts or user-owned app records. Public multi-user discovery belongs in an [overlay](https://docs.projectbabbage.com/docs/concepts/overlays), with the appropriate application protocol.

## A complete typed example

The SDK exports the class without an internal-file import or an `as any` workaround. The constructor arguments below are wallet, context, value encryption, optional originator and delayed-broadcast policy. Keep the normal requesting origin; do not impersonate another origin to bypass permission decisions.

```ts
import { LocalKVStore, WalletClient } from '@bsv/sdk'

const wallet = new WalletClient('auto')
const preferences = new LocalKVStore(
  wallet,
  'example app preferences',
  true,      // encrypt values
  undefined, // use the normal app origin
  false      // request immediate broadcast for writes
)

async function readTheme() {
  await wallet.waitForAuthentication()
  return preferences.get('theme', 'system')
}

async function saveTheme(theme: 'light' | 'dark' | 'system') {
  await wallet.waitForAuthentication()
  const outpoint = await preferences.set('theme', theme)
  if (await preferences.get('theme') !== theme) {
    throw new Error('The saved preference could not be read back')
  }
  return outpoint
}

async function removeTheme() {
  await wallet.waitForAuthentication()
  return preferences.remove('theme')
}

// These helpers are not called automatically. Saving/removing requests wallet actions.
```

The first successful read returns the stored value or `'system'` when the key is absent. `set` returns an outpoint in `txid.vout` form; `remove` returns transaction IDs and returns an empty array when there is nothing to remove. Read back an important record before acknowledging to your user that it is safely stored.

## Fees, consent and privacy

A new value creates a **1-satoshi output plus network fees**. Updating normally spends the previous output(s) into a replacement; removing spends existing output(s) without replacing the value. These are real wallet actions and can require funding and approval. The output amount is not a fixed total price. In 4.0.0, setting an already stored identical value can reuse its existing outpoint without another transaction.

Encryption is on by default, but on-chain transaction existence, amounts and timing remain observable. Do not place recovery secrets in examples, context names or keys. Disabling encryption deliberately publishes plaintext values. Removing a key does not erase historical blockchain bytes, backups or other retained copies.

## Concurrency and conflicting state

The SDK queues operations for a key **within that LocalKVStore instance**. This does not provide a distributed lock between tabs, separate instances or devices. A `get`, increment and `set` sequence is not an atomic distributed counter. Coordinate writes in your application, or use independent immutable record keys and an explicit merge protocol when concurrent-device history must be preserved.

`get` can report ambiguous or invalid output state. Treat that as an error, not an empty value. Although a deliberate `set` can collapse multiple valid outputs, it is a mutation and can overwrite meaning; do not automatically reset or relinquish recovery outputs to make an error disappear. Preserve the original error and records, check the wallet/profile, context and data restoration, then resolve the conflict deliberately.

## Recovery and uncertain outcomes

Keep the context, key scheme, encryption policy and derivation assumptions stable across app releases. Back up and restore the wallet account and its supported data/storage service. A private key or browser bookmark alone is not a promise that all tracked outputs and proof data are present.

A timeout does not prove a write was never created. Inspect wallet action history and read the key before submitting another mutation. Allowing delayed broadcast is a separate application choice; it does not make persistence, confirmation or cross-device availability immediate. Errors must remain visible instead of silently becoming default values.

Mountaintops uses this wallet-backed pattern for address metadata, with its own recovery and merge rules. Its [funding/recovery guide](https://docs.projectbabbage.com/docs/metanet-client/funding) explains the user-facing boundaries; do not assume a generic key-value example implements those rules for you.

See the maintained [LocalKVStore source](https://github.com/bsv-blockchain/ts-stack/blob/main/packages/sdk/src/kvstore/LocalKVStore.ts) and [wallet API](https://bsv-blockchain.github.io/ts-sdk/reference/wallet/) before extending the example.
