npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@bsv/expo-wallet-toolbox

v0.10.0

Published

Wallet screens and P2P local-payment backend for Expo/React Native apps, extracted from BSV Wallet.

Readme

@bsv/expo-wallet-toolbox

Wallet screens and P2P local-payment backend for Expo/React Native apps — storage, local WiFi/Bluetooth payment transport, offline queueing, backup, hardware-key vault, and the 11 wallet screens, extracted from BSV Wallet so a second app (e.g. BSV Browser) can install the same wallet instead of re-implementing or forking it.

Two subpath exports, one npm package:

  • @bsv/expo-wallet-toolbox (root/core) — headless: context providers, storage, services, localpay/peerpay/offline/backup/vault logic. No screen components.
  • @bsv/expo-wallet-toolbox/ui — the 11 screen components and their UI dependencies (components/wallet, components/pay, components/vault, components/ui, components/security, QRScanner). Imports core.

This package ships raw TypeScript (not precompiled JS) — Metro and your app's own TypeScript config transpile it like any other workspace source.

Install

npm install @bsv/expo-wallet-toolbox @bsv/react-native-localpay-transport react-native-engine-native react-native-secp-native react-native-nitro-modules

react-native-yubikey is optional — the vault subsystem (services/vault/driver.ts) detects its absence at runtime and disables vault/hardware-key features gracefully; install it only if you want YubiKey PIV custody support.

The command above is not the whole story. core/ui import roughly four dozen packages beyond the four listed — Expo native modules (camera, clipboard, secure-store, haptics, ...), state/i18n libraries (mobx, i18next), and BSV SDK packages. The Peer dependencies table below is the full, audited list — package.json's peerDependencies now declares all 50 entries (47 directly imported + 3 required only for native linking — see the Nitro row below), so a plain npm install @bsv/expo-wallet-toolbox surfaces the rest as peer-dependency warnings instead of silently missing them.

Peer dependencies

Versions shown are what this repo currently builds against (root package.json) — known-good, not hard requirements; align to your own Expo SDK line.

React / Expo core (required)

| Package | Version | | --- | --- | | react | 19.2.0 | | react-native | 0.83.6 | | expo-router | ~55.0.16 |

BSV SDK & protocol libraries (required)

| Package | Version | | --- | --- | | @bsv/sdk | ^2.4.1 | | @bsv/wallet-toolbox-mobile | ^2.4.3 | | @bsv/btms-permission-module | ^1.1.0 | | @bsv/air-gap | ^0.1.1 | | @bsv/backup-cache-client | ^0.1.0 | | @bsv/message-box-client | ^2.2.1 | | @bsv/templates | ^1.10.0 | | @noble/curves | ^2.3.0 | | qrcode | ^1.5.4 |

Expo native modules (required — each needs its own autolinking/config)

| Package | Version | | --- | --- | | expo-sqlite | ~55.0.16 | | expo-secure-store | ~55.0.14 | | expo-camera | ~55.0.19 | | expo-audio | ~55.0.16 | | expo-clipboard | ~55.0.13 | | expo-crypto | ~55.0.15 | | expo-device | ~55.0.17 | | expo-document-picker | ~55.0.13 | | expo-file-system | ~55.0.20 | | expo-haptics | ~55.0.14 | | expo-local-authentication | ~55.0.14 | | expo-localization | ~55.0.15 | | expo-print | ~55.0.15 | | expo-sharing | ~55.0.19 | | expo-status-bar | ~55.0.6 | | expo-blur | ~55.0.14 | | @expo/vector-icons | ^15.0.3 |

React Native native modules (required)

| Package | Version | | --- | --- | | @react-native-async-storage/async-storage | 2.2.0 | | @react-native-clipboard/clipboard | ^1.16.2 | | @react-native-community/netinfo | 11.5.2 | | react-native-gesture-handler | ~2.30.0 | | react-native-localize | ^3.4.1 | | react-native-qrcode-svg | ^6.3.15 | | react-native-reanimated | 4.2.1 | | react-native-safe-area-context | ~5.6.0 | | react-native-svg | 15.15.3 | | react-native-sse | ^1.2.1 |

Pure JS rendering (required)

| Package | Version | | --- | --- | | @urbit/sigil-js | ^2.2.0 |

Draws the counterparty avatar on activity rows. This package imports only its ./core entry: the package root pulls in a DOM renderer that touches document at import time. ./core ships CJS, so Jest needs neither a transformIgnorePatterns entry nor a mock for it (see Jest configuration).

./core exists only as an entry in the package's exports map (there is no core.js at the package root), so Metro's package-exports resolution must stay enabled (resolver.unstable_enablePackageExports, on by default since Expo SDK 53). A host that has switched it off to work around another dual-package problem gets Unable to resolve module @urbit/sigil-js/core at bundle time; the fix is a resolveRequest that maps @urbit/sigil-js/core to @urbit/sigil-js/dist/core.js.

State & i18n (required)

| Package | Version | | --- | --- | | mobx | ^6.13.7 | | mobx-react-lite | ^4.1.0 | | i18next | ^25.2.1 | | react-i18next | ^15.5.3 | | date-fns | ^4.1.0 |

Nitro native modules — this repo's own packages (required)

| Package | Notes | | --- | --- | | react-native-nitro-modules | ^0.35.2. Nitro codegen runtime the three below build on. | | @bsv/react-native-localpay-transport | Local WiFi/Bluetooth/BLE P2P payment transport. Published independently — see packages/react-native-localpay-transport. | | react-native-engine-native | Native wallet-toolbox engine bindings. | | react-native-secp-native | Native secp256k1 signing. |

react-native-engine-native and react-native-secp-native ship as sibling packages/ in this repo and are not yet published to npm independently — when they are, pin to their published version. Each of the three declares react-native and react-native-nitro-modules as its own peer dependencies.

Optional

| Package | Notes | | --- | --- | | react-native-yubikey | Vault/hardware-key support. Absence is detected at runtime and vault features degrade gracefully. |

Configuration

Call configureToolbox once, at your app entry point, before anything from this package renders or builds a wallet:

import { configureToolbox } from '@bsv/expo-wallet-toolbox'

configureToolbox({
  backupUrl: process.env.EXPO_PUBLIC_BACKUP_URL ?? null,
  services: {
    main: {
      arcUrl: process.env.EXPO_PUBLIC_ARC_URL,
      arcApiKey: process.env.EXPO_PUBLIC_ARC_API_KEY,
      chaintracksUrl: process.env.EXPO_PUBLIC_CHAINTRACKS_URL,
      whatsOnChainApiKey: process.env.EXPO_PUBLIC_WOC_API_KEY,
      // A distinct key: TAAL and WhatsOnChain are different origins, and this
      // one is never used as a fallback for the other.
      taalApiKey: process.env.EXPO_PUBLIC_TAAL_API_KEY
    }
  }
})

The EXPO_PUBLIC_* reads must live in your app source, not in this package. Expo's Babel preset refuses to inline EXPO_PUBLIC_* for any file whose path contains node_modules (babel-preset-expo/build/common.js: !isNodeModule && ...). A production bundle defines only NODE_ENV on process.env, so an env read inside an installed copy of this package is undefined — silently, and only in production, since Expo's dev serializer injects the variables at runtime. That asymmetry is why this package reads no environment of its own.

backupUrl is required and accepts null, which disables backup entirely: no monitor task is registered, nothing is sent, and the backup UI does not render. It is not optional and has no default, so every build states its endpoint — including stating that it has none. It must be a bare origin: the BRC-103/104 handshake posts to the origin root, so a path, query or fragment is rejected.

Every services field is optional and falls back to the built-in public endpoint for that chain. Chains are main, test and teratest.

handleRegistry names the paymail domain this build's handles live under and the host that serves it, per chain:

configureToolbox({
  backupUrl: null,
  handleRegistry: {
    test: { domain: 'deggen.com', url: 'https://messagebox.bsvblockchain.tech' }
  }
})

Both fields are required together — a domain with no URL is a registry nothing can reach, and a URL with no domain is a host whose certificates nothing can be checked against — and the URL must be https unless it points at localhost, 127.0.0.1, 10.0.2.2 or RFC 1918 space, which is a development allowance. A chain with no complete entry shows "not available yet" on the Profile screen and adds no registry tier to Pay's recipient search.

Reading configuration before configureToolbox has run throws. An unconfigured build fails loudly rather than behaving like a deliberately disabled one.

Usage

import {
  WalletContextProvider,
  ThemeProvider,
  LocalStorageProvider,
  LanguageProvider,
  UserContextProvider,
  ExchangeRateContextProvider,
  WalletConnectionProvider,
  VaultProvider
} from '@bsv/expo-wallet-toolbox'
import {
  PermissionSheet,
  VaultCeremonySheet,
  AlertHost,
  ToastHost,
  showToast,
  ErrorBoundary
} from '@bsv/expo-wallet-toolbox/ui'

Provider setup

Compose the providers in this order (outer→inner) around your expo-router <Stack> — this is the exact nesting bsv-wallet itself uses in app/_layout.tsx:

<LanguageProvider>
  <LocalStorageProvider>
    <UserContextProvider nativeHandlers={nativeHandlers} appVersion={appVersion} appName="Your App">
      <ExchangeRateContextProvider>
        <WalletContextProvider onToast={showToast}>
          <ThemeProvider>
            <WalletConnectionProvider walletName="Your App">
              <VaultProvider onToast={showToast}>
                {/* mount once, anywhere inside VaultProvider: */}
                <PermissionSheet />
                <VaultCeremonySheet />
                <AlertHost />

                <Stack />

                <ToastHost />
              </VaultProvider>
            </WalletConnectionProvider>
          </ThemeProvider>
        </WalletContextProvider>
      </ExchangeRateContextProvider>
    </UserContextProvider>
  </LocalStorageProvider>
</LanguageProvider>

Notes on the non-obvious props (all technically optional with defaults, but you almost always want to set them):

  • UserContextProvider — appVersion/appName default to 'unknown'/'App'; pass your own package.json version and app name. nativeHandlers is an optional NativeHandlers object (isFocused, onFocusRequested, onFocusRelinquished, onDownloadFile) — omit it to use no-op defaults.
  • WalletContextProvider and VaultProvider — onToast is an optional callback of shape (message: string, opts?: { type?: 'success' | 'error' | 'info' | ... }) => void. Wire it to showToast from @bsv/expo-wallet-toolbox/ui (or your own toast) so background events (offline-payment internalization, vault ceremony results) surface to the user. Neither context imports a UI component directly — that's a deliberate core/ui boundary.
  • WalletConnectionProvider — walletName defaults to 'App'; set it to your app's display name (shown to QR-paired desktop sessions).

Screens

@bsv/expo-wallet-toolbox/ui exports 11 screen components. Create thin app/*.tsx files in your expo-router app that just re-export them — this is the pattern bsv-wallet's own route files use:

// app/index.tsx
export { WalletHomeScreen as default } from '@bsv/expo-wallet-toolbox/ui'

| Screen | Source route in this repo | | --- | --- | | WalletHomeScreen | app/index.tsx | | PayScreen | app/pay.tsx | | SettingsScreen | app/settings.tsx | | WalletConfigScreen | app/wallet-config.tsx | | VaultScreen | app/vault.tsx | | VaultTransferScreen | app/vault-transfer.tsx | | ConnectionsScreen | app/connections.tsx | | PairScreen | app/pair.tsx | | TrustScreen | app/trust.tsx | | LogsScreen | app/logs.tsx |

Note: ConnectionsScreen accepts pairing codes using bsv-wallet:// or bsv-browser://. OS-level links also require native scheme registration in the host app. This repo routes external pair links to /connections through app/+native-intent.ts; in-app QR scanning uses the same pairing parameter validation. A consumer using another pairing scheme must update the parser and its own native registration together.

Each route file is the one-line export { XScreen as default } from '@bsv/expo-wallet-toolbox/ui' pattern shown above — no other wiring needed per screen; navigation between them uses expo-router's file-based routes, so your route file names determine the paths (/pay, /settings, etc.), not the exported screen names.

Required app.json configuration

Expo config (Info.plist keys, Android permissions, config plugins, expo-build-properties) can't be installed by an npm package — it stays declared in your own app's app.json. This is the exact config bsv-wallet's app.json and plugins/ currently carry:

iOS infoPlist

{
  "NSCameraUsageDescription": "This app needs camera access to scan QR codes.",
  "NSPhotoLibraryAddUsageDescription": "This app needs permission to save images to your photo library.",
  "NSMicrophoneUsageDescription": "This app needs microphone access for video recording features.",
  "NSFaceIDUsageDescription": "This app uses Face ID/Touch ID to securely authenticate you and protect your wallet.",
  "NSLocationWhenInUseUsageDescription": "...enable features like discovering nearby devices/services...",
  "NSBonjourServices": ["_bsvpay._tcp"],
  "NSLocalNetworkUsageDescription": "...local network to send and receive payments directly between nearby devices.",
  "NFCReaderUsageDescription": "...NFC to unlock your vault with your YubiKey...",
  "com.apple.developer.nfc.readersession.iso7816.select-identifiers": [
    "A000000308",
    "A000000308000010000100",
    "A0000006472F0001",
    "A0000005272101",
    "A000000527471117",
    "A0000005272001"
  ]
}

NFCReaderUsageDescription and the AID list are only needed if you ship vault/hardware-key support (i.e. installed react-native-yubikey); omit both if you didn't.

Also add CFBundleURLTypes for your own deep-link scheme(s) — do not reuse bsv-wallet://, bsv-browser://, or peerpay://, those are this repo's own app identity, not the package's:

"CFBundleURLTypes": [
  { "CFBundleURLName": "com.yourcompany.yourapp", "CFBundleURLSchemes": ["yourapp"] }
]

iOS entitlement

com.apple.developer.nfc.readersession.formats: ["TAG"] — needed only alongside the NFC/vault config above. Add it via a config plugin equivalent to this repo's plugins/withNfcReaderEntitlement.js, which wraps @expo/config-plugins' withEntitlementsPlist:

const { withEntitlementsPlist } = require('@expo/config-plugins')

module.exports = (config) =>
  withEntitlementsPlist(config, (mod) => {
    mod.modResults['com.apple.developer.nfc.readersession.formats'] = ['TAG']
    return mod
  })

This is a real, provisionable iOS capability ("NFC Tag Reading") — your App ID needs it enabled and the provisioning profile regenerated, or codesign/App Store submission will reject it.

Android permissions

[
  "android.permission.INTERNET",
  "android.permission.SYSTEM_ALERT_WINDOW",
  "android.permission.ACCESS_COARSE_LOCATION",
  "android.permission.ACCESS_FINE_LOCATION",
  "android.permission.CAMERA",
  "android.permission.RECORD_AUDIO",
  "android.permission.MODIFY_AUDIO_SETTINGS",
  "android.permission.BLUETOOTH_ADVERTISE",
  "android.permission.BLUETOOTH_CONNECT",
  "android.permission.BLUETOOTH_SCAN",
  "android.permission.NEARBY_WIFI_DEVICES",
  "android.permission.ACCESS_WIFI_STATE",
  "android.permission.CHANGE_WIFI_STATE"
]

Android intent filter

Register your own deep-link scheme (VIEW/BROWSABLE/DEFAULT) — the local-payment QR/nearby flows deep-link back into your app, not this repo's:

{
  "action": "VIEW",
  "data": [{ "scheme": "yourapp" }],
  "category": ["BROWSABLE", "DEFAULT"]
}

expo-build-properties

{
  "ios": {
    "useFrameworks": "static",
    "useModularHeaders": true
  }
}

Required for the Nitro/xcframework native modules (@bsv/react-native-localpay-transport, react-native-engine-native, react-native-secp-native, react-native-yubikey) to build on iOS.

metro.config.js

The package's crypto code expects node:crypto to resolve to react-native-quick-crypto, and node:buffer/node:process to resolve to an empty stub (not the real Node polyfills) — mirror this repo's metro.config.js:

const { getDefaultConfig } = require('expo/metro-config')
const path = require('path')

const config = getDefaultConfig(__dirname)

config.resolver.extraNodeModules = {
  crypto: require.resolve('react-native-quick-crypto'),
  stream: require.resolve('stream-browserify'),
  buffer: require.resolve('buffer'),
  ...config.resolver.extraNodeModules
}

const emptyShim = path.resolve(__dirname, 'metro-shims/empty.js') // module.exports = {}
const quickCryptoMain = require.resolve('react-native-quick-crypto')

const upstream = config.resolver.resolveRequest
config.resolver.resolveRequest = (context, moduleName, platform) => {
  if (moduleName === 'node:crypto') {
    return { type: 'sourceFile', filePath: quickCryptoMain }
  }
  if (moduleName === 'node:buffer' || moduleName === 'node:process') {
    return { type: 'sourceFile', filePath: emptyShim }
  }
  if (typeof upstream === 'function') return upstream(context, moduleName, platform)
  return context.resolveRequest(context, moduleName, platform)
}

module.exports = config

react-native-quick-crypto, stream-browserify, and buffer must be installed in your app for the extraNodeModules lines above to resolve; add them alongside the peer dependencies list.

Jest configuration

If your app runs Jest against code that imports this package, two things are required — both hard-won during this package's extraction:

  1. transformIgnorePatterns must include @bsv/expo-wallet-toolbox, because this package ships raw TypeScript, not precompiled JS — Jest's default node_modules exclusion will otherwise fail to transform it. This repo's own (proven-working) pattern, verbatim from package.json:

    transformIgnorePatterns: [
      "node_modules/(?!((jest-)?react-native|@react-native(-community)?|expo(nent)?|@expo(nent)?/.*|@expo-google-fonts/.*|react-navigation|@react-navigation/.*|@sentry/react-native|native-base|react-native-svg|react-native-reanimated|react-native-gesture-handler|react-native-worklets|expo-modules-core|@noble/secp256k1|@noble/curves|@noble/hashes|@bsv/backup-cache-client|@bsv/auth|@bsv/expo-wallet-toolbox|@bsv/react-native-localpay-transport)/)",
      "/node_modules/react-native-reanimated/plugin/"
    ]

    The second array entry excludes react-native-reanimated's own babel plugin directory from transformation — unrelated to this package specifically, but part of the same reference config and needed because react-native-reanimated is a required peer dependency. You don't need every name in that big alternation — most come from the jest-expo preset's own defaults or other-app concerns — but @bsv/expo-wallet-toolbox, @bsv/react-native-localpay-transport (also raw TypeScript — core/localpay/transport/select.ts imports it directly), and @bsv/backup-cache-client (see below) must be in whatever pattern you end up with.

    @urbit/sigil-js, the peer added with the activity-row sigil avatars, needs no entry here: the ./core entry this package imports ships CJS, so Jest loads it untransformed. Install it alongside the other peers and nothing else in this section changes.

  2. Native-module and ESM-only-package mocks — anything the package touches that has no pure-JS Jest-safe implementation, or that ships as ESM-only with no require export condition, needs a moduleNameMapper entry. This repo's full set, verbatim:

    moduleNameMapper: {
      '^@bsv/backup-cache-client$':
        '<rootDir>/node_modules/@bsv/backup-cache-client/dist/index.js',
      '^react-native-reanimated$': '<rootDir>/__tests__/__mocks__/reanimated.js',
      '^react-native-worklets$':
        '<rootDir>/node_modules/react-native-worklets/lib/module/mock.js',
      '^expo-audio$': '<rootDir>/__tests__/__mocks__/expo-audio.js',
      '^expo-sqlite$': '<rootDir>/__tests__/__mocks__/expo-sqlite.js',
      '^@react-native-async-storage/async-storage$':
        '<rootDir>/node_modules/@react-native-async-storage/async-storage/jest/async-storage-mock.js'
    }

    @bsv/backup-cache-client is the one entry every consumer needs even before writing a single test that touches wallet behavior: core/backup/client.ts statically imports it, and it's re-exported through the core barrel (core/index.ts), so any test that imports anything from @bsv/expo-wallet-toolbox pulls it in. The package is "type": "module" (ESM-only, no CJS require export condition), which Jest's default CJS-based resolver can't load — hence the explicit redirect straight to its dist/index.js build. This bites on the very first jest run in a consumer app, unlike the other entries below, which only bite once a test actually exercises that native API.

    expo-sqlite/expo-audio need hand-written mocks (see this repo's __tests__/__mocks__/ for working examples) since they wrap native modules with no official Jest mock; async-storage and react-native-worklets ship their own mock you can point to directly; react-native-reanimated's official jest.setup.js mock doesn't cover every API this package uses, hence the local override.

    This repo uses the jest-expo preset as its base ("preset": "jest-expo" in package.json), which already covers most other Expo native modules — start from that preset before adding the overrides above.

Breaking changes

0.10.0: verifyFramePayment takes { wallet, chainTracker } (FrameVerifyDeps) as its first argument instead of a single wallet, and DerivingWallet no longer includes getServices(). Pass the permissions manager as wallet and await storage.getServices().getChainTracker() as chainTracker. The 0.8.0 note below about DerivingWallet gaining getServices() no longer applies.

0.9.0: useWallet().logout now returns Promise<boolean> (it was () => void). It resolves once teardown has finished, and resolves to false when the secrets layer could not confirm erasure. Await it before treating sign-out or Delete Wallet as complete. The @bsv/wallet-toolbox-mobile peer range moves to ^2.14.3, and hosts must copy patches/@bsv+wallet-toolbox-mobile+2.14.3.patch in place of the 2.14.0 patch. 0.8.0 was never published, so hosts upgrading from 0.7.0 get its breaking changes below as well.

0.8.0: three API points break. DerivingWallet (the interface core/localpay/verify.ts's verifyFramePayment accepts) gains a required getServices(): { getChainTracker(): Promise<ChainTracker> | ChainTracker } — a host that built its own object against the old, narrower shape (just getPublicKey) must add it before its build will typecheck. RestoreWalletDeps gains a required hasStoredIdentity(): Promise<boolean> and RestorePrompts gains a required confirmReplace(): Promise<'replace' | 'keep'> — a host supplying its own RestoreWalletDeps, or building its own RestorePrompts instead of using ui/recoveryPrompts.ts's restorePrompts(t), must implement both. core/storage/LocalStorageAdapter.ts and its exports (initializeLocalStorage, isLocalStorage, getStorageDisplayName, LocalStorageConfig) are removed — dead code with no real caller in this app, but a host that imported these names directly gets a build error.

restoreWallet/recoverWallet also change host-visible behaviour for anything that goes through them (the app's own mnemonic and scan-shares screens, and any host reusing the core/recovery/ module directly): recovering or importing over an already-built wallet now rebuilds instead of silently no-op'ing, storing one secret kind now deletes the other (exactly one secret survives a restore), a failed backup replay's "skip" now also works for hex/WIF imports, an attestation failure after a successful build is reported non-fatally instead of failing the whole attempt, and recoverWallet now asks the new confirmReplace prompt before overwriting a secret already on the device — declining cancels before anything is written. An external pairing deep link no longer auto-connects from Connections; it now lands on /pair and requires the same explicit Approve/Reject tap a scanned QR gets. validateShareCompatibility is deprecated (still works, returns English prose) in favour of checkShareCompatibility, which returns a code for the caller to translate. See the CHANGELOG's 0.8.0 entry for the full list, including the additive review-fix changes (Nearby/QR payment verification, offline-drain and declined-payment handling, auto-approve bounds, exchange-rate/header/backup fixes, and more) that ship alongside these three breaking points.

0.4.0: the package no longer reads process.env at all. DEFAULT_BACKUP_URL is removed, and the EXPO_PUBLIC_* service reads in walletServiceConfig are gone. The host supplies these through configureToolbox({ backupUrl, services }) at app entry — see Configuration. Removing the export rather than deprecating it is deliberate: a host that upgrades gets a build error, not another empty string. Hosts that installed this package from npm were silently running with backup disabled and WhatsOnChain/Taal unkeyed, because Expo does not inline EXPO_PUBLIC_* inside node_modules.

0.1.1: react-native-localpay-transport (unscoped, private, hand-synced out of this repo) became @bsv/react-native-localpay-transport (published independently). This is a native-module identity change, not just an npm rename: the pod name (LocalPayTransport) and Nitro hybrid object registrations are unchanged, so a consumer that still has its own local copy of the old module installed alongside the new package will get a duplicate pod and duplicate native registration. Delete any local copy before installing the new package — do not run both at once.

Publishing notes

This package deliberately ships no exports map in package.json. Resolution relies on plain main/types (root → core/index.ts) plus Node/Metro's default directory-index resolution for the /ui subpath (@bsv/expo-wallet-toolbox/ui → ui/index.ts).

This is a conscious choice, not an oversight: roughly 50 files under ui/ import shared primitives (useTheme, useWallet, spacing, haptics, etc.) from the bare @bsv/expo-wallet-toolbox specifier — i.e. the package imports itself by name, relying on the workspace's self-referencing node_modules/@bsv/expo-wallet-toolbox symlink (or, once published, plain node_modules directory-walk resolution — the installed copy sits inside a consumer's node_modules and is found there like any other dependency, not through Node's dedicated package-self-reference feature). That feature only kicks in when an exports map is present and includes a "." entry — adding an exports map without one would silently break every one of those ~50 imports at publish time, and adding one with only "." (no "./ui" entry) would break every consumer's /ui subpath import instead.

If a future change adds an exports map, it must include at minimum:

"exports": {
  ".": "./core/index.ts",
  "./ui": "./ui/index.ts"
}

and must be re-verified against the full Jest suite, the packageResolution.test.ts canary (CANARY_UI/CANARY_CORE barrel-import check), and a real npx expo export Metro bundle — the self-reference resolution path is exactly what both currently exercise successfully without an exports map, and Metro's own exports support has its own quirks worth re-testing explicitly rather than assuming Node's behavior carries over.

files is ["core", "ui", "assets", "CHANGELOG.md"]; npm always includes package.json and README.md regardless of files. __tests__/ and tsconfig.json live at the package root, outside every files entry, so they are excluded from the published tarball — confirmed with npm pack --dry-run.