# Troubleshooting

Common errors and fixes.

## SDK errors

### `MissingEvmWalletError: bridgeToCore requires evmWallet`

You called `kit.bridgeToCore` without passing `evmWallet` to `createUsdhKit`. Wallet stacks often separate signing (`Signer`) and broadcasting (`EvmWallet`); the kit asks for both explicitly.

```ts
const kit = createUsdhKit({
  network: 'mainnet',
  signer,
  evmWallet: { address: account.address, sendTransaction },
})
```

### `InvalidInputError: bridgeFromCore requires signer.address to match accountAddress`

`bridgeFromCore()` signs a user-owned HyperCore `sendAsset` action. Approved agent wallets are useful for spot orders, but they cannot bridge funds out for a master account. Create a separate kit with the master wallet as `signer` before calling `bridgeFromCore()`.

`bridgeFromCore()` returns `status: 'submitted'` after Hyperliquid accepts the action. The HyperEVM-side credit is asynchronous, so confirm the EVM balance before starting a dependent HyperEVM transaction.

Browser apps that use an approved agent should also pass `accountAddress` so bridge ownership and balance reads stay tied to the master wallet:

```ts
const kit = createUsdhKit({
  network: 'mainnet',
  signer: agentSigner,
  accountAddress: account.address,
  evmWallet: masterEvmWallet,
})
```

### `BridgeTimeoutError: HyperCore did not credit within 180s`

The EVM bridge transaction succeeded but the HyperCore credit did not land within the polling window. Funds are safe — they are waiting for Hyperliquid/Circle bridge indexing. Most bridges land within roughly 1-2 minutes; longer points to congestion or a relayer hiccup.

Refresh balances first. If the timeout came through `bridgeAndSwap()`, retry `bridgeAndSwap()` with the same arguments; route selection will use the now-credited HyperCore balance and continue to the swap when enough USDC is available. Do not blindly retry a low-level `bridgeToCore()` call unless you still intend to send another bridge deposit.

When the timeout comes through `bridgeAndSwap()`, it is available as `err.cause` on a `BridgeAndSwapError` with `phase === 'bridging'`.

### `BridgeAndSwapError: bridgeAndSwap failed during bridging: ...`

`bridgeAndSwap()` wraps unexpected route, bridge, and swap failures with lifecycle context. Use `isBridgeAndSwapError(err)` to narrow safely, then inspect `phase` for UI copy and `cause` for the underlying typed error. Do not parse the message string.

```ts
import { BridgeTimeoutError, isBridgeAndSwapError } from '@usdh-kit/sdk'

try {
  await kit.bridgeAndSwap({ from: 'USDC', amount, onProgress })
} catch (err) {
  if (isBridgeAndSwapError(err)) {
    console.error(err.phase, err.route, err.bridge, err.cause)

    if (err.phase === 'bridging' && err.cause instanceof BridgeTimeoutError) {
      showPendingBridge(err.cause.txHash)
      // Calling bridgeAndSwap again with the same input re-checks HC credit
      // before deciding whether another bridge is still needed.
    }
  }
}
```

Preflight blockers still throw their original classes (`MissingEvmWalletError`, `InsufficientBalanceError`) so existing `instanceof` checks keep working.

### `InsufficientBalanceError: Insufficient USDC: 1000000 needed, 500000 available`

Pre-flight balance check failed. The widget's source-chain pill exposes both EVM and HC balances side by side so users can flip to the chain they're funded on. CLI consumers should top up the source wallet before the call.

### `InvalidInputError: amount must be a positive bigint`

The amount is `0n`, negative, or not a `bigint`. The kit accepts `bigint` only for amounts (no number coercion) to avoid silent precision loss.

```ts
// wrong
kit.swap({ from: 'USDC', amount: 1.5 })

// right (1.5 USDC = 1_500_000 in 6-decimal smallest unit)
kit.swap({ from: 'USDC', amount: 1_500_000n })
```

### `SigningError: ...`

The signer rejected or returned an invalid signature. For viem `LocalAccount`, double-check the typed-data passthrough — the example apps include the right adapter shape:

```ts
const signer: Signer = {
  address: account.address,
  signTypedData: (args) => account.signTypedData(args as any),
  signMessage: (m) => account.signMessage({ message: typeof m === 'string' ? m : { raw: m } }),
}
```

If this happens in a browser wallet during `swap()`, do not fall back to direct wallet L1 order signing. Hyperliquid spot orders use the `Exchange` typed-data domain with chain ID `1337`; some wallets reject that while connected to HyperEVM. Use `approveAgent()` once with the master wallet, then create the kit with the approved agent as `signer` and the master wallet as `accountAddress`.

### `NetworkError: HL error: Order would immediately match resting order at worse than limit`

The orderbook moved between quote and submission — your slippage tolerance was too tight. Widen `slippageBps` (the widget exposes 10/30/50/100 + custom) and retry. The kit passes through the protocol-level message so you can act on it.

### `NetworkError: HL error: Insufficient margin`

You're swapping more than the resolved HC balance net of open orders. The widget's `useUsdcBalances` already subtracts the `hold` field; CLI consumers reading `spotClearinghouseState` directly should do the same.

### `NotImplementedError: USDT swap lands in a follow-up PR`

You called `swap({ from: 'USDT', ... })`. USDT support is deferred (USDT/USDC/USDH double-hop). Use USDC for now.

### Why do I see two Rabby popups for a HyperEVM bridge?

USDC on HyperEVM uses Circle's native bridge path:

1. `approve` USDC to the CoreDepositWallet when allowance is not sufficient
2. `deposit` the USDC into HyperCore spot

The final USDH order is signed by the approved Hyperliquid agent session, so it should not open a third Rabby popup. A follow-up optimization can skip the approval transaction when existing allowance already covers the amount.

## Widget errors

### Console: "An empty string was passed to the href attribute"

The demo's `wagmi` chain definitions are missing `blockExplorers`, so ConnectKit renders an explorer link with an empty href. Dev-mode only — not a runtime issue. Fix by passing real explorer URLs in your chain definitions:

```ts
defineChain({
  id: 999,
  name: 'HyperEVM',
  blockExplorers: {
    default: { name: 'Hyperscan', url: 'https://www.hyperscan.com' },
  },
  // ...
})
```

### Borders render bright white in dark mode

The widget's CSS variables are not loaded. The compiled stylesheet must be imported at your app root:

```ts
import '@usdh-kit/widget/styles.css'
```

Without this, every `var(--usdh-*)` falls back to `currentColor`. See [theming](/usdh-kit/reference/theming.md) for the full setup.

### The widget palette doesn't match my OS theme

Default `theme="auto"` follows `prefers-color-scheme`. If your app's page background is hardcoded dark while your OS is in light mode, you'll see a light widget on a dark page. Either:

* Make your page background follow `prefers-color-scheme` too (recommended), or
* Force the widget with `<USDHSwap network="mainnet" theme="dark" />`

### SSR flash light → dark on first paint

Standard `prefers-color-scheme` tradeoff. See the cookie-based fix in [theming](/usdh-kit/reference/theming.md#avoiding-the-ssr-flash).

## Performance

### Quote refreshes feel slow

`getQuote` debounces by 400ms in the widget. If you're calling the SDK directly, you control the cadence — `getQuote` is a single `/info` round-trip with no signing.

### Bundle size is too large

The widget currently ships around 55KB raw ESM before gzip. The browser agent-session flow and dual-token balance display are included; viem/wagmi remain peer dependencies. If your app already uses viem and wagmi, bundlers should de-duplicate them.


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://usdh-kit.gitbook.io/usdh-kit/reference/troubleshooting.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
