> ## Documentation Index
> Fetch the complete documentation index at: https://docs.trustware.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Build custom deposit UI with the headless core

> Use Trustware routing, wallet plumbing, transaction orchestration, and settlement automations without any widget UI by calling the Trustware core API directly.

The headless core gives you Trustware's routing and transaction logic without the prebuilt widget. You build your own UI and the SDK handles route construction, quotes, wallet calls, settlement, and transaction submission.

## When to use this pattern

Choose this pattern when:

* you want full control over the deposit UI
* your design system requires a custom amount entry or confirmation flow
* you want to embed deposit logic into an existing interface without a floating widget
* you need a flow the widget does not provide, such as [withdrawing from an embedded wallet](/guides/embedded-wallet-withdrawals)

If you want built-in wallet selection, token picker, amount entry, and confirmation screens, use the [drop-in widget](/integration/drop-in-widget) instead.

## Setup

The headless core still requires `TrustwareProvider` for config context. Mount it once near the root of your app; you do not need to render `TrustwareWidget`.

```tsx theme={null}
import { TrustwareProvider, type TrustwareConfigOptions } from "@trustware/sdk";

const trustwareConfig = {
  apiKey: process.env.NEXT_PUBLIC_TRUSTWARE_API_KEY!,
  routes: {
    toChain: "8453",
    toToken: "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE",
    defaultSlippage: 1,
    options: {
      routeRefreshMs: 15000,
    },
  },
} satisfies TrustwareConfigOptions;

export function App() {
  return (
    <TrustwareProvider config={trustwareConfig}>
      {/* your custom UI goes here */}
    </TrustwareProvider>
  );
}
```

Then import the core in any component or hook:

```ts theme={null}
import { Trustware } from "@trustware/sdk";
```

## Wallet setup

You have two options for attaching a wallet to the headless core.

### Bring your own wallet

Bridge an existing Wagmi wallet client into Trustware using `useWallet`:

```ts theme={null}
import { useEffect } from "react";
import { useWalletClient } from "wagmi";
import { useWagmi } from "@trustware/sdk/wallet";
import { Trustware } from "@trustware/sdk";

export function useTrustwareWalletBridge() {
  const { data } = useWalletClient();

  useEffect(() => {
    if (!data) return;
    Trustware.useWallet(useWagmi(data));
  }, [data]);
}
```

### Let Trustware detect wallets

If you do not manage wallet state, call `autoDetect` once at startup:

```ts theme={null}
await Trustware.autoDetect();
```

## Core operations

### 1. Build a route

`buildRoute` constructs a route. Use this when you want to inspect route details before asking the user to confirm.

```ts theme={null}
const route = await Trustware.buildRoute({
  fromChain: "1",
  toChain: "8453",
  fromToken: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
  toToken: "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE",
  fromAmount: "1000000", // amount in token's smallest unit
  fromAddress: await Trustware.getAddress(),
  toAddress: "0xDestination...",
});
```

**`BuildRouteBody` shape:** (`?` indicates optional)

```ts theme={null}
export type BuildRouteBody = {
  fromChain: string;
  toChain: string;
  fromToken: string;
  toToken: string;
  fromAmount: string;
  fromAddress: string;
  toAddress: string;
  fromAmountUsd?: string;
  fromAmountUSD?: string;
  refundAddress?: string;
  direction?: string;
  slippage?: number;
  slippageBps?: number;
  memo?: string;
};
```

### 2. Inspect route details

The returned `BuildRouteResult` contains exchange rate information you can display to the user before they confirm.

```ts theme={null}
const route = await Trustware.buildRoute({
  fromChain: "1",
  toChain: "8453",
  fromToken: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
  toToken: "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE",
  fromAmount: "1000000",
  fromAddress: await Trustware.getAddress(),
  toAddress: "0xDestination...",
});

// Show the user what they will receive before confirming
console.log(route.finalExchangeRate.fromAmountUSD);
console.log(route.finalExchangeRate.toAmountMinUSD);
```

**`BuildRouteResult` shape:**

```ts theme={null}
type BuildRouteResult = {
  intentId: string;              // pass to receipt and status calls
  txReq: TxRequest;              // transaction object to sign and broadcast
  actions: unknown[];
  finalExchangeRate: {
    fromAmountUSD?: string;
    toAmountMinUSD?: string;
  };
  route: RoutePlan | undefined;
};
```

### 3. Run the full flow

`runTopUp` handles route construction, wallet approval, transaction submission, and status polling in a single call. Use this when you want the SDK to orchestrate the full deposit path.

Only `fromAmount` is required; every other field is optional and falls back to the corresponding value in your `TrustwareConfigOptions.routes` config when omitted.

```ts theme={null}
const result = await Trustware.runTopUp({
  fromAmount: "1000000",                                       // required: amount in token's smallest unit
  fromChain: "1",                                              // optional: overrides config.routes.fromChain
  toChain: "8453",                                             // optional: overrides config.routes.toChain
  fromToken: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",     // optional: overrides config.routes.fromToken
  toToken: "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE",       // optional: overrides config.routes.toToken
  toAddress: "0xDestination...",                               // optional: overrides config.routes.toAddress
});

if (result.status === "success") {
  console.log("Deposit complete:", result.destTxHash);
}
```

**Full signature:**

```ts theme={null}
Trustware.runTopUp(params: {
  fromAmount: string | number;  // required
  fromChain?: string;            // overrides config.routes.fromChain
  toChain?: string;              // overrides config.routes.toChain
  fromToken?: string;            // overrides config.routes.fromToken
  toToken?: string;              // overrides config.routes.toToken
  toAddress?: string;            // overrides config.routes.toAddress
});
```

## Working with chains and tokens

The same chain and token discovery the widget uses internally is available to your own UI through three helpers on the `Trustware` facade. All three respect the active `TrustwareProvider` config and reuse its cache.

### `Trustware.useChains()`

React hook that returns the supported chain list, split into popular and other groups, with loading and error state. Use it to drive your own chain selector.

```tsx theme={null}
import { Trustware } from "@trustware/sdk";

const useTrustwareChains = Trustware.useChains;

function ChainPicker() {
  const { popularChains, otherChains, chains, isLoading, error } =
    useTrustwareChains();

  if (isLoading) return <Spinner />;
  if (error) return <ErrorState message={error} />;
  return <ChainList chains={[...popularChains, ...otherChains]} />;
}
```

### `Trustware.useTokens(chainId)`

React hook that returns the token list for a given chain with built-in pagination and search. Pass `null` to skip fetching.

```tsx theme={null}
const {
  filteredTokens,
  hasNextPage,
  isLoading,
  isLoadingMore,
  error,
  loadMore,
  searchQuery,
  setSearchQuery,
} = Trustware.useTokens(activeChain?.chainId ?? null);
```

### `Trustware.validateAddressForChain(address, chain)`

Synchronous helper that returns `{ isValid: boolean; error?: string }` for the given destination address against the rules of the selected chain (EVM checksum, Solana base58, Bitcoin, Cosmos prefixes, etc.). Use it for live form validation.

```ts theme={null}
const validation = Trustware.validateAddressForChain(
  destinationAddress,
  activeChain,
);

if (!validation.isValid) {
  showError(validation.error);
}
```

## Lifecycle callbacks

`TrustwareConfigOptions` exposes three optional callbacks you can pass alongside your routes config to react to SDK activity:

```ts theme={null}
onError?: (error: TrustwareError) => void;
onSuccess?: (transaction: Transaction) => void;
onEvent?: (event: TrustwareEvent) => void;
```

* **`onEvent`** fires for every lifecycle event the SDK emits. It receives the full `TrustwareEvent` discriminated union, which you can narrow by `type` (`transaction_started`, `transaction_success`, `wallet_connected`, etc.).
* **`onSuccess`** fires once when a deposit settles on the destination chain. It receives the resolved `Transaction` with `destTxHash` populated.
* **`onError`** fires for any `TrustwareError` thrown during a route or transaction operation. Use it as a single place to log errors or push them into your UI state. `RateLimitError` extends native `Error` (not `TrustwareError`) and does not flow through this callback; handle it with the imperative `try/catch` pattern below.

```ts theme={null}
const config = {
  apiKey: process.env.NEXT_PUBLIC_TRUSTWARE_API_KEY!,
  routes: {
    toChain: "8453",
    toToken: "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE",
  },
  onEvent: (event) => {
    if (event.type === "transaction_success") {
      console.log("TX hash:", event.txHash);
    }
    if (event.type === "wallet_connected") {
      console.log("Wallet:", event.address);
    }
  },
  onSuccess: (transaction) => {
    console.log("Deposit complete:", transaction.destTxHash);
  },
  onError: (error) => {
    console.error("Trustware error:", error.code, error.message);
  },
} satisfies TrustwareConfigOptions;
```

`onError` runs in addition to any `try/catch` you wrap around individual core calls; see [Error handling](#error-handling) below for the imperative pattern. See [lifecycle events](/events-errors/lifecycle-events) for the full list of event types.

## Error handling

Wrap core calls in try/catch. Import `RateLimitError` to handle rate-limiting specifically:

```ts theme={null}
import { RateLimitError, Trustware } from "@trustware/sdk";

try {
  const route = await Trustware.buildRoute({
    fromChain: "1",
    toChain: "8453",
    fromToken: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
    toToken: "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE",
    fromAmount: "1000000",
    fromAddress: await Trustware.getAddress(),
    toAddress: "0xDestination...",
  });
} catch (error) {
  if (error instanceof RateLimitError) {
    console.error("Rate limited", error.rateLimitInfo);
  }
}
```

## When to use the widget instead

<Tip>
  If you want built-in wallet selection, token picker, amount input, and processing/success screens, the widget patterns require significantly less code. The headless core is best when your UI requirements cannot be met by the prebuilt widget.
</Tip>

| You want                    | Use                                                 |
| --------------------------- | --------------------------------------------------- |
| Full built-in UX            | [Drop-in widget](/integration/drop-in-widget)       |
| Widget with your own wallet | [Host wallet](/integration/host-wallet)             |
| Programmatic open/close     | [Controlled widget](/integration/controlled-widget) |
| Custom UI, SDK routing      | Headless core (this page)                           |
