unionlabs/union · error · Error

No wallet available

Error message

No wallet available

What it means

Thrown in the BTCfi Step3 flow (app2) when preparing to sign a Babylon ownership message: the app takes the stored wallet name from cosmosStore.connectedWallet and looks up the injected provider via window[connectedWallet]; if that global is absent, there is no wallet API to call signArbitrary on and the Effect fails synchronously.

Source

Thrown at app2/src/routes/btcfi/step/Step3.svelte:107

  }

  if (!isEvmAddressValid) {
    return
  }

  const addressToUse = evmAddress.trim()
  isLoading = true

  const message =
    `I verify that I own this Babylon address and want to receive BTCfi rewards on Ethereum address: ${addressToUse}`

  runPromise(
    pipe(
      Effect.sync(() => {
        const walletApi = cosmosStore.connectedWallet
          && (window as any)[cosmosStore.connectedWallet]
        if (!walletApi) {
          throw new Error("No wallet available")
        }
        return walletApi
      }),
      Effect.flatMap((walletApi) =>
        Effect.tryPromise({
          try: () => {
            const chainId = "bbn-1"
            return walletApi.signArbitrary(chainId, walletAddress, message)
          },
          catch: (error) => new Error(`Failed to sign message: ${error}`),
        })
      ),
      Effect.flatMap((signature) =>
        verifyBTCFIWallet({
          bbnAddress: walletAddress,
          message,
          signature: JSON.stringify(signature),
          evmAddress: addressToUse,

View on GitHub (pinned to 031785bb6d)

Solutions

  1. Reconnect the wallet (trigger the connect flow again) so connectedWallet matches a currently injected provider
  2. Verify the extension is installed and enabled for the site, then reload
  3. Clear stale connection state when window[connectedWallet] is missing instead of attempting to sign

Example fix

// before
const walletApi = cosmosStore.connectedWallet && (window as any)[cosmosStore.connectedWallet]

// after (gate the flow and re-connect on miss)
const walletApi = cosmosStore.connectedWallet && (window as any)[cosmosStore.connectedWallet]
if (!walletApi) {
  cosmosStore.connectedWallet = null // clear stale state
  await reconnect() // run the wallet-connect flow again
  return
}
Defensive patterns

Strategy: validation

Validate before calling

const walletName = cosmosStore.connectedWallet
const walletApi = walletName && (window as any)[walletName]
if (!walletApi || typeof walletApi.signArbitrary !== "function") {
  cosmosStore.connectedWallet = null // clear stale state
  // trigger the connect flow instead of signing
}

Type guard

const isInjectedCosmosWallet = (w: unknown): w is { signArbitrary: (chainId: string, signer: string, message: string) => Promise<unknown> } =>
  typeof w === "object" && w !== null && typeof (w as any).signArbitrary === "function"

Try / catch

try {
  // Effect pipeline already routes this via Effect.orElse; for plain code:
  await walletApi.signArbitrary("bbn-1", address, message)
} catch (error) {
  if ((error as Error).message === "No wallet available") {
    // prompt reconnect; do not retry until window global exists
  }
  throw error
}

Prevention

When it happens

Trigger: cosmosStore.connectedWallet is set (e.g. "keplr" or "leap") but the corresponding window global is missing: extension uninstalled/disabled, injection not finished at call time, the name restored from persisted state after a browser profile change, or a non-cosmos wallet was connected.

Common situations: User disabled the extension between sessions; page loaded and user clicked through before injection (race); localStorage/session persisted connectedWallet stale; mobile in-app browser without the extension.

Related errors


AI-assisted analysis of unionlabs/union@031785bb6d (2026-08-16). Data as JSON: /api/errors/c7131e9dec08400f. Report an issue: GitHub.