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

# Frontend SDK

> Browser SDK for wallet authentication, deposit/withdrawal quoting, and on-chain execution.

The frontend SDK (`@blend-money/fe`) handles wallet authentication, deposit/withdrawal quoting, and on-chain execution in the browser.

## Quick-start flow

1. Install `@blend-money/fe` and `viem`
2. Create a `BlendSdk` instance with your publishable key and paymaster
3. Sign in with `sdk.signIn()`
4. Quote with `sdk.quoteDeposit()` or `sdk.quoteWithdraw()`
5. Execute with `sdk.execute(quote, { deriveSigner })`

## Install

<Snippet file="snippets/sdk-install.mdx" />

## Configuration

```ts theme={"theme":{"light":"github-light","dark":"github-dark"}}
import {
  BlendSdk,
  getPimlicoPaymasterEndpoints,
} from "@blend-money/fe";

const sdk = new BlendSdk({
  publishableKey: "pk_live_abc123...",
  signMessage: (msg) => walletClient.signMessage({ message: msg }),
  paymaster: getPimlicoPaymasterEndpoints("pimlico_api_key_here"),
});
```

| Field            | Type                                   | Required    | Description                                                                         |
| ---------------- | -------------------------------------- | ----------- | ----------------------------------------------------------------------------------- |
| `publishableKey` | `string`                               | Yes         | Client-safe key (`pk_live_`). Identifies your organization and account type.        |
| `signMessage`    | `(message: string) => Promise<string>` | For sign-in | Wallet signing function for SIWE. A restored, unexpired session can run without it. |
| `paymaster`      | `PaymasterRegistry`                    | Yes         | Pimlico or Alchemy bundler/paymaster endpoints keyed by chain ID                    |
| `baseUrl`        | `string`                               | No          | Blend API base URL. Defaults to production.                                         |
| `fiatCurrency`   | `string`                               | No          | ISO 4217 code (e.g. `"EUR"`). Adds this currency to monetary responses.             |
| `timeoutMs`      | `number`                               | No          | Request timeout in milliseconds. Default: `15000`.                                  |
| `retries`        | `number`                               | No          | Max retries for `429` and `5xx`. Default: `3`.                                      |

Use an Alchemy Gas Manager policy for Alchemy-backed chains:

```ts theme={"theme":{"light":"github-light","dark":"github-dark"}}
import {
  getAlchemyPaymasterEndpoints,
  getPimlicoPaymasterEndpoints,
} from "@blend-money/fe";

const paymaster = {
  ...getPimlicoPaymasterEndpoints("pimlico-key", [8453]),
  ...getAlchemyPaymasterEndpoints(
    "alchemy-key",
    { policyId: "policy-id" },
    [137],
  ),
};
```

Later entries replace earlier ones for the same chain. Execution throws `VALIDATION_ERROR` if the registry has no entry for a required chain.

SDK 3.0.1 gets gas prices from the configured bundler. It does not call a Blend gas-price method.

## Authentication

### signIn

Signs the user in with SIWE. The first sign-in creates the account record and deterministic Safe address. It does not deploy the Safe on-chain.

```ts theme={"theme":{"light":"github-light","dark":"github-dark"}}
const session = await sdk.signIn({
  address: "0x1111111111111111111111111111111111111111",
  chainId: 8453,
});
```

`chainId` defaults to `1`. It sets the SIWE message chain and does not deploy the Safe.

Returns an `AuthSession`:

| Field            | Type       | Description                   |
| ---------------- | ---------- | ----------------------------- |
| `address`        | `string`   | Authenticated wallet address  |
| `accountId`      | `string`   | Blend account ID              |
| `safeAddress`    | `string`   | On-chain Safe address         |
| `chainsDeployed` | `number[]` | Chains where the Safe is live |
| `expiresAt`      | `string`   | ISO 8601 expiry (1 hour)      |

Check `chainsDeployed` to see where the Safe is live. If you need the Safe on a chain that isn't listed, request deployment:

```ts theme={"theme":{"light":"github-light","dark":"github-dark"}}
await sdk.account.safe.request(137); // Deploy on Polygon
```

See [Safe deployment](/build/best-practices#safe-deployment) for the full pattern.

### signOut

Revokes the JWT server-side and clears local state.

```ts theme={"theme":{"light":"github-light","dark":"github-dark"}}
await sdk.signOut();
```

### isSignedIn

Read-only boolean. `true` if a session is active.

```ts theme={"theme":{"light":"github-light","dark":"github-dark"}}
if (sdk.isSignedIn) {
  // authenticated
}
```

### session

Returns the current `AuthSession`. Throws `SdkError` with code `AUTH_NOT_SIGNED_IN` if not authenticated.

```ts theme={"theme":{"light":"github-light","dark":"github-dark"}}
const { accountId, safeAddress } = sdk.session;
```

### exportSession / restoreSession

Persist and restore sessions across page reloads.

```ts theme={"theme":{"light":"github-light","dark":"github-dark"}}
// Save before unload
const snapshot = sdk.exportSession();
sessionStorage.setItem("blend", JSON.stringify(snapshot));

// Restore on next load
const saved = JSON.parse(sessionStorage.getItem("blend")!);
sdk.restoreSession(saved);
```

<Warning>
  Use `sessionStorage`, not `localStorage`. Clear the session when the wallet disconnects or the address changes.
</Warning>

## Quoting

### quoteDeposit

Quotes a deposit. Creates a session automatically if one doesn't exist.

```ts theme={"theme":{"light":"github-light","dark":"github-dark"}}
import { parseAmount } from "@blend-money/fe";

const quote = await sdk.quoteDeposit({
  chainId: 8453,
  tokenAddress: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
  amount: parseAmount("100", 6),
  externalRef: "order-123",
});
```

| Parameter      | Type      | Required | Description                                      |
| -------------- | --------- | -------- | ------------------------------------------------ |
| `chainId`      | `number`  | Yes      | Origin chain ID                                  |
| `tokenAddress` | `string`  | Yes      | Token contract on the origin chain               |
| `amount`       | `string`  | Yes      | Smallest unit, non-negative integer string       |
| `externalRef`  | `string`  | No       | Your reconciliation reference                    |
| `forceReset`   | `boolean` | No       | Cancel existing session first. Default: `false`. |

Returns a `DepositQuote`:

| Field                | Type        | Description                                           |
| -------------------- | ----------- | ----------------------------------------------------- |
| `type`               | `"DEPOSIT"` | Quote type                                            |
| `intentId`           | `string`    | Session ID                                            |
| `originChainId`      | `number`    | Chain the user sends from                             |
| `destinationChainId` | `number`    | Chain where funds arrive                              |
| `input`              | `object`    | `symbol`, `amount`, `amountUsd` the user sends        |
| `output`             | `object`    | `symbol`, `amount`, `amountUsd` arriving in the vault |
| `fees.totalUsd`      | `string`    | Total fees in USD                                     |
| `estimatedSeconds`   | `number`    | Estimated settlement time                             |
| `expiresAt`          | `string`    | When this quote expires                               |

### quoteWithdraw

Quotes a withdrawal. Blend picks which chains to pull funds from.

```ts theme={"theme":{"light":"github-light","dark":"github-dark"}}
const quote = await sdk.quoteWithdraw({
  destinationChainId: 8453,
  amount: parseAmount("50", 6),
});
```

For a full exit, set `isMaxWithdraw: true` and `amount: "0"`:

```ts theme={"theme":{"light":"github-light","dark":"github-dark"}}
const quote = await sdk.quoteWithdraw({
  destinationChainId: 8453,
  amount: "0",
  isMaxWithdraw: true,
});
```

A full withdrawal redeems every source-chain position. For any withdrawal, partial or full, where a source chain differs from `destinationChainId`, Vault embeds Relay bridge calldata inside the atomic withdrawal transaction. The returned step list may not contain a separate `bridge` step, but the execution still routes those funds to the requested destination.

Returns a `WithdrawQuote`:

| Field                | Type         | Description                                   |
| -------------------- | ------------ | --------------------------------------------- |
| `type`               | `"WITHDRAW"` | Quote type                                    |
| `intentId`           | `string`     | Session ID                                    |
| `destinationChainId` | `number`     | Chain where funds arrive                      |
| `totalAmount`        | `string`     | Total withdrawal in loan token smallest units |
| `totalFeesUsd`       | `string`     | Total fees in USD                             |
| `estimatedSeconds`   | `number`     | Estimated settlement time                     |
| `sourceChainCount`   | `number`     | Number of source chains                       |
| `sourceChainIds`     | `number[]`   | Which chains funds are pulled from            |
| `expiresAt`          | `string`     | When this quote expires                       |

## Execution

### execute

Locks the quote, submits transactions through the user's Safe, and records their hashes. New sessions settle as soon as the API accepts the hashes. Legacy `SUBMITTED` sessions can still require polling.

```ts theme={"theme":{"light":"github-light","dark":"github-dark"}}
const signerAddress = "0x1111111111111111111111111111111111111111";

const result = await sdk.execute(quote, {
  signerAddress,
  deriveSigner: async (chainId) => ({
    signer: getWalletClient({ chainId, account: signerAddress }),
    publicClient: getPublicClient({ chainId }),
  }),
  onStatusChange: (status) => console.log(status),
});
```

| Parameter          | Type                                               | Required | Description                                                           |
| ------------------ | -------------------------------------------------- | -------- | --------------------------------------------------------------------- |
| `signerAddress`    | `string`                                           | Yes      | EOA hex address. Must match the `deriveSigner` output.                |
| `deriveSigner`     | `(chainId: number) => Promise<DeriveSignerResult>` | Yes      | Returns a viem `WalletClient` and `PublicClient` for the target chain |
| `isContractSigner` | `boolean`                                          | No       | Set `true` for smart wallets (e.g. Coinbase). Default: `false`.       |
| `onStatusChange`   | `(status: ExecuteStatus) => void`                  | No       | Called on each status transition                                      |
| `pollIntervalMs`   | `number`                                           | No       | Polling interval. Default: `3000`.                                    |
| `pollTimeoutMs`    | `number`                                           | No       | Max polling time. Default: `300000` (5 min).                          |

`deriveSigner` is called once per action plan. For deposits, that means once. For withdrawals, once per source chain.

Return clients configured for the requested chain. The wallet client must have an attached account whose address matches `signerAddress`.

`DeriveSignerResult`:

| Field          | Type           | Description                            |
| -------------- | -------------- | -------------------------------------- |
| `signer`       | `WalletClient` | viem WalletClient for the target chain |
| `publicClient` | `PublicClient` | viem PublicClient for the target chain |

`ExecuteResult`:

| Field       | Type                                       | Description                  |
| ----------- | ------------------------------------------ | ---------------------------- |
| `status`    | `"settled" \| "failed" \| "cancelled"`     | Terminal status              |
| `txHashes`  | `Array<{ hash: string; chainId: number }>` | Submitted transaction hashes |
| `settledAt` | `string \| null`                           | ISO 8601 settlement time     |
| `error`     | `string \| null`                           | Error message if failed      |

## Discovery

These methods are available on `sdk.discover` after sign-in. Every frontend route outside challenge and verify requires the SIWE bearer session.

```ts theme={"theme":{"light":"github-light","dark":"github-dark"}}
const chains = await sdk.discover.depositChains();
const tokens = await sdk.discover.depositTokens(8453);
const destinations = await sdk.discover.withdrawDestinations();
const yieldData = await sdk.discover.yield();
```

See [SDK Reference](/build/sdk/reference#yield) for response shapes.

## Account data

These methods are available on `sdk.account` after sign-in.

```ts theme={"theme":{"light":"github-light","dark":"github-dark"}}
const balance = await sdk.account.balance();
const history = await sdk.account.balanceHistory();
const positions = await sdk.account.positions();
const returns = await sdk.account.returns();
```

See [SDK Reference](/build/sdk/reference#balance--positions) for response shapes.

## Cancel in-flight requests

Every async HTTP method accepts `RequestOptions` as its last argument. Pass an `AbortSignal` when a user changes an input during live quoting.

```ts theme={"theme":{"light":"github-light","dark":"github-dark"}}
const controller = new AbortController();

const quote = await sdk.quoteDeposit(
  {
    chainId: 8453,
    tokenAddress: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
    amount: "100000000",
  },
  { signal: controller.signal },
);
```

## Error handling

Every SDK method can throw `SdkError`. Catch it to get structured error info.

```ts theme={"theme":{"light":"github-light","dark":"github-dark"}}
import { SdkError } from "@blend-money/fe";

try {
  const quote = await sdk.quoteDeposit({ /* ... */ });
} catch (error) {
  if (error instanceof SdkError) {
    console.error(error.code);            // "INTENT_EXPIRED"
    console.error(error.status);          // 410
    console.error(error.getUserMessage()); // user-friendly text
    if (error.isRetryable()) { /* retry */ }
  }
}
```

See [SDK Reference](/build/sdk/reference#error-handling) for the full error codes table.

## What to watch out for

**Don't** create a new session to change the amount, token, or chain. Call the same quote method again while the session is `OPEN`. To switch between a deposit and withdrawal in the high-level frontend SDK, pass `forceReset: true`; this discards the opposite-type active session and starts a new one.

**Don't** store sessions in `localStorage`. Use `sessionStorage` and clear it when the wallet disconnects.

**Don't** treat `onStatusChange` as the final result. SDK 3.0.1 can emit the legacy-compatible `"confirming"` callback even when the API settles directly. Use the returned `ExecuteResult`, then use balances and positions to confirm indexed activity.

**Don't** ignore `isContractSigner`. If your users connect smart wallets (Coinbase Wallet, Safe), set it to `true` or signing will fail.

<CardGroup cols={2}>
  <Card title="Deposits & Withdrawals" icon="arrow-right-arrow-left" href="/build/sdk/transactions">
    Full deposit and withdrawal flows with cross-chain routing.
  </Card>

  <Card title="SDK Reference" icon="book" href="/build/sdk/reference">
    Auth, accounts, balances, error codes, and types.
  </Card>
</CardGroup>
