> ## 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.

# Deposits & Withdrawals

> Quote and execute deposits and withdrawals across chains with the frontend and server SDKs.

Quote and execute deposits and withdrawals. Cross-chain routing is handled automatically.

## Deposits

### Discover chains and tokens

Find which chains and tokens are available for deposits.

<Tabs>
  <Tab title="Frontend">
    ```ts theme={"theme":{"light":"github-light","dark":"github-dark"}}
    const chains = await sdk.discover.depositChains();
    const tokens = await sdk.discover.depositTokens(8453);

    const usdc = tokens.find((t) => t.symbol === "USDC")!;
    ```

    `depositTokens` includes wallet balances when the user is signed in.
  </Tab>

  <Tab title="Server">
    ```ts theme={"theme":{"light":"github-light","dark":"github-dark"}}
    const chains = await sdk.discover.depositChains();
    const tokens = await sdk.discover.depositTokens(8453);

    const usdc = tokens.find((t) => t.symbol === "USDC")!;
    ```
  </Tab>
</Tabs>

### Quoting a deposit

<Tabs>
  <Tab title="Frontend">
    ```ts theme={"theme":{"light":"github-light","dark":"github-dark"}}
    import { parseAmount } from "@blend-money/fe";

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

    The frontend SDK uses `tokenAddress`. It creates a session automatically.
  </Tab>

  <Tab title="Server">
    ```ts theme={"theme":{"light":"github-light","dark":"github-dark"}}
    import { parseAmount } from "@blend-money/node";

    const client = sdk.forAccount("550e8400-e29b-41d4-a716-446655440000");
    const session = await client.sessions.createSession({});

    const quoted = await client.sessions.quoteDeposit(session.intentId, {
      chainId: 8453,
      inputAssetAddress: usdc.address,
      eoa: "0x1111111111111111111111111111111111111111",
      amount: parseAmount("100", usdc.decimals),
    });

    if (quoted.type !== "DEPOSIT" || !quoted.actionPlan) {
      throw new Error("Deposit action plan is unavailable. Request a new quote.");
    }
    ```

    The server SDK uses `inputAssetAddress`. It returns a `SessionResult`, so narrow on `type` and check the extracted action plan before using deposit-specific fields.
  </Tab>
</Tabs>

The frontend and server methods return different shapes.

#### Frontend `DepositQuote`

The frontend quote is a display-ready handle that you pass to `sdk.execute()`:

| Field              | Type     | Description                                         |
| ------------------ | -------- | --------------------------------------------------- |
| `intentId`         | `string` | Session UUID                                        |
| `input`            | `object` | Symbol, amount, and USD value the user sends        |
| `output`           | `object` | Symbol, amount, and USD value arriving in the vault |
| `fees.totalUsd`    | `string` | Total fees in USD                                   |
| `estimatedSeconds` | `number` | Estimated time to settlement                        |
| `expiresAt`        | `string` | When this quote expires                             |

#### Server `SessionResult`

The server quote returns the complete session union:

| Field           | Type                            | Description                                                          |
| --------------- | ------------------------------- | -------------------------------------------------------------------- |
| `intentId`      | `string`                        | Session UUID                                                         |
| `type`          | `"DEPOSIT"`                     | Discriminator after a successful deposit quote                       |
| `status`        | `SessionStatus`                 | Current lifecycle status                                             |
| `requestParams` | `DepositRequestParams`          | Original server quote parameters                                     |
| `quoteSummary`  | `DepositQuoteSummary`           | Origin and destination chains, input and output amounts, and fees    |
| `payload`       | `DepositApiQuoteResult \| null` | Raw quote payload, or `null` after its cache expires                 |
| `actionPlan`    | `ActionPlan \| null`            | Extracted executable plan, or `null` when the payload is unavailable |

### Executing a deposit

<Tabs>
  <Tab title="Frontend">
    ```ts theme={"theme":{"light":"github-light","dark":"github-dark"}}
    const signerAddress = walletClient.account.address;

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

  <Tab title="Server">
    ```ts theme={"theme":{"light":"github-light","dark":"github-dark"}}
    const result = await client.sessions.execute(quoted.intentId, {
      signerAddress: "0x1111111111111111111111111111111111111111",
      submitActionPlan: async (plan) => {
        const hash = await executeOnChain(plan);
        return [{ hash, chainId: plan.chainId }];
      },
    });
    ```
  </Tab>
</Tabs>

Prefer `client.sessions.execute()` for server integrations. It locks before invoking your callback and submits the returned hashes. Submit moves a current session directly from `LOCKED` to `SETTLED`. The session service records the hashes but does not verify transaction receipts. Use balances and positions for indexer-backed money state.

### Re-quoting the same session

Call `quoteDeposit()` again while the session is `OPEN` to update prices, amounts, tokens, or chains. The session stays the same. Don't create a new session for these changes.

<Tabs>
  <Tab title="Frontend">
    ```ts theme={"theme":{"light":"github-light","dark":"github-dark"}}
    // Same session, fresh prices
    const freshQuote = await sdk.quoteDeposit({
      chainId: 8453,
      tokenAddress: usdc.address,
      amount: parseAmount("200", usdc.decimals),
    });
    ```
  </Tab>

  <Tab title="Server">
    ```ts theme={"theme":{"light":"github-light","dark":"github-dark"}}
    const freshQuote = await client.sessions.quoteDeposit(session.intentId, {
      chainId: 8453,
      inputAssetAddress: usdc.address,
      eoa: "0x1111111111111111111111111111111111111111",
      amount: parseAmount("200", usdc.decimals),
    });
    ```
  </Tab>
</Tabs>

`forceReset` only discards an active session before starting a fresh one. Use it when you intentionally want to abandon that session, not to refresh a price or change an amount, token, or chain.

## Withdrawals

### Discover destinations

Find which chains accept withdrawals and which loan token the user will receive.

```ts theme={"theme":{"light":"github-light","dark":"github-dark"}}
const destinations = await sdk.discover.withdrawDestinations();
// [{ chainId: 8453, name: "Base", loanTokenAddress: "0x..." }]
```

### Quoting a withdrawal

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

    For a full exit:

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

  <Tab title="Server">
    ```ts theme={"theme":{"light":"github-light","dark":"github-dark"}}
    const session = await client.sessions.createSession({});

    const quoted = await client.sessions.quoteWithdraw(session.intentId, {
      destinationChainId: 8453,
      amount: parseAmount("50", 6),
    });

    if (quoted.type !== "WITHDRAW" || !quoted.actionPlans?.length) {
      throw new Error("Withdrawal action plans are unavailable. Request a new quote.");
    }
    ```
  </Tab>
</Tabs>

With `isMaxWithdraw: true`, Blend redeems all shares on every source chain. For any source chain that differs from the destination, whether the withdrawal is partial or full, Vault embeds Relay bridge calldata inside the atomic withdrawal transaction. The returned step list may omit a separate `bridge` step, but execution still routes those funds to the requested destination.

The result shapes also differ for withdrawals.

#### Frontend `WithdrawQuote`

| Field              | Type       | Description                                   |
| ------------------ | ---------- | --------------------------------------------- |
| `intentId`         | `string`   | Session UUID                                  |
| `totalAmount`      | `string`   | Total withdrawal in loan token smallest units |
| `totalFeesUsd`     | `string`   | Total fees in USD                             |
| `estimatedSeconds` | `number`   | Estimated time to settlement                  |
| `sourceChainIds`   | `number[]` | Which chains Blend pulls funds from           |
| `sourceChainCount` | `number`   | Number of source chains                       |

#### Server `SessionResult`

| Field           | Type                                | Description                                                                    |
| --------------- | ----------------------------------- | ------------------------------------------------------------------------------ |
| `intentId`      | `string`                            | Session UUID                                                                   |
| `type`          | `"WITHDRAW"`                        | Discriminator after a successful withdrawal quote                              |
| `status`        | `SessionStatus`                     | Current lifecycle status                                                       |
| `requestParams` | `WithdrawRequestParams`             | Destination, amount, and max-withdraw flag                                     |
| `quoteSummary`  | `WithdrawQuoteSummary`              | Safe, destination, expected source chains, amount, fees, and payload count     |
| `payload`       | `WithdrawApiCalldataResult \| null` | Raw calldata payload, or `null` after its cache expires                        |
| `actionPlans`   | `ActionPlan[] \| null`              | One extracted plan per source chain, or `null` when the payload is unavailable |

### Executing a withdrawal

Withdrawals may span multiple chains. The SDK handles multi-chain signing automatically.

<Tabs>
  <Tab title="Frontend">
    ```ts theme={"theme":{"light":"github-light","dark":"github-dark"}}
    const signerAddress = walletClient.account.address;

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

    `deriveSigner` is called once per source chain. Handle wallet chain-switching inside the callback, return clients for the requested chain, and attach the account that matches `signerAddress`.
  </Tab>

  <Tab title="Server">
    Withdrawals produce multiple action plans (one per source chain). Execute each plan and collect all hashes.

    ```ts theme={"theme":{"light":"github-light","dark":"github-dark"}}
    const result = await client.sessions.execute(quoted.intentId, {
      signerAddress: "0x1111111111111111111111111111111111111111",
      submitActionPlan: async (plan) => {
        const hash = await executeOnChain(plan);
        return [{ hash, chainId: plan.chainId }];
      },
    });
    ```
  </Tab>
</Tabs>

### Flow plan conflicts

If a rebalance is in progress, withdrawal quoting returns a `409` with error code `FLOWPLAN_CONFLICT`. Show a "try again shortly" message to your user. Don't auto-retry - wait for the rebalance to finish.

## Cross-chain

Blend routes deposits and withdrawals across chains using the [Relay](https://relay.link) bridge. In any cross-chain withdrawal, partial or full, the bridge call can be embedded inside the atomic `liquidityReset` calldata instead of appearing as a separate step.

### Bridge adapters

| Adapter    | Protocol                                                               | When it's used                                                    |
| ---------- | ---------------------------------------------------------------------- | ----------------------------------------------------------------- |
| **Across** | [Across Protocol](https://across.to)                                   | Primary bridge for cross-chain transfers. Fast finality.          |
| **CCTP**   | [Circle CCTP](https://www.circle.com/en/cross-chain-transfer-protocol) | Native USDC bridging between supported chains. No wrapped tokens. |

The SDK picks the best adapter based on the token and route. You don't need to choose.

### Safe address determinism

Safe addresses are deterministic. The same wallet address produces the same Safe address on every chain. This means cross-chain deposits route to the correct Safe automatically, even if the Safe hasn't been deployed on the destination chain yet.

### liquidityReset as delegateCall

During withdrawals, the `liquidityReset` step flushes vault positions back to the loan token. This step runs as a `delegateCall` through the Safe (the only step that does). The `isDelegateCall: true` flag in the action plan's transaction marks it.

## Testing

Test these scenarios before going to production:

| Scenario            | How to test                                 | Expected result                                                                    |
| ------------------- | ------------------------------------------- | ---------------------------------------------------------------------------------- |
| Same-chain deposit  | Deposit USDC on Base to a Base-native vault | Single action plan, fast settlement                                                |
| Cross-chain deposit | Deposit USDC on Ethereum to a Base vault    | Bridge routing, longer settlement                                                  |
| Partial withdrawal  | Withdraw 50% of balance                     | Funds pulled from fewest chains possible                                           |
| Full withdrawal     | `isMaxWithdraw: true`                       | All positions closed; cross-chain sources route funds to the requested destination |
| Quote expiration    | Wait past `expiresAt`                       | Re-quote required, same session ID                                                 |
| Flow plan conflict  | Withdraw during an active rebalance         | `409` with `FLOWPLAN_CONFLICT`                                                     |

## What to watch out for

**Don't** use `forceReset: true` to update prices or change an amount, token, or chain. Re-quote the existing `OPEN` session. Use `forceReset` only when you intend to discard the active session.

**Don't** auto-retry on `FLOWPLAN_CONFLICT`. The rebalance needs to finish first. Show a message and let the user try again.

**Don't** assume single-chain for withdrawals. Funds may be spread across chains. Your `deriveSigner` (frontend) or `submitActionPlan` (server) callback must handle multiple chains.

**Don't** skip the `amount` field when using `isMaxWithdraw: true`. Pass `"0"` as the amount.

**Don't** treat a `SETTLED` session as proof of on-chain confirmation. It records hash ingestion. Check indexer-backed balances or positions for the resulting money state.

<CardGroup cols={2}>
  <Card title="Frontend SDK" icon="browser" href="/build/sdk/frontend">
    Frontend configuration, auth, and execution details.
  </Card>

  <Card title="Server SDK" icon="server" href="/build/sdk/server">
    Server configuration, account management, and session lifecycle.
  </Card>

  <Card title="SDK Reference" icon="book" href="/build/sdk/reference">
    Error codes, types, and utility functions.
  </Card>

  <Card title="Best Practices" icon="shield-check" href="/build/best-practices">
    Production patterns for session management and error recovery.
  </Card>
</CardGroup>
