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

# Server SDK

> Node.js SDK for account management, session lifecycle, and backend integrations.

The server SDK (`@blend-money/node`) manages accounts and sessions from your backend. It does not sign transactions itself.

## Quick-start flow

1. Install `@blend-money/node`
2. Create a `BlendServerSdk` instance with your API key
3. Look up or create an account with `sdk.lookupAccount(address)`
4. Scope operations with `sdk.forAccount(accountId)`
5. Create and quote a session, then run it with `client.sessions.execute()`

## Install

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

## Configuration

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

const sdk = new BlendServerSdk({
  apiKey: process.env.BLEND_API_KEY!,
  accountTypeId: "savings-usd",
});
```

| Field           | Type     | Required | Description                                                             |
| --------------- | -------- | -------- | ----------------------------------------------------------------------- |
| `apiKey`        | `string` | Yes      | Server secret (`sk_live_`). Never expose in client code.                |
| `accountTypeId` | `string` | Yes      | The account type (product) to operate on                                |
| `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`.                          |

## Account management

### lookupAccount

Resolves an account by wallet address. Creates the account if it doesn't exist yet. Does not request Safe deployment on any chain.

```ts theme={"theme":{"light":"github-light","dark":"github-dark"}}
const account = await sdk.lookupAccount(
  "0x1111111111111111111111111111111111111111",
);
// account.accountId      -> "550e8400-e29b-41d4-a716-446655440000"
// account.safeAddress    -> "0x2222222222222222222222222222222222222222"
// account.chainsDeployed -> [8453]
```

Returns a `SafeAccountResponse`:

| Field            | Type       | Description                   |
| ---------------- | ---------- | ----------------------------- |
| `accountId`      | `string`   | Blend account UUID            |
| `safeAddress`    | `string`   | Deterministic Safe address    |
| `chainsDeployed` | `number[]` | Chains where the Safe is live |

<Warning>
  `chainsDeployed` only lists chains where the Safe has been deployed. A new account starts with no chains. To deploy on a chain, call `safe.request()` after lookup. See [Safe deployment](/build/best-practices#safe-deployment).
</Warning>

### Deploy a Safe

`lookupAccount` creates the account but does not deploy a Safe. To deploy on a chain, scope the account with `forAccount` and call `safe.request()`.

```ts theme={"theme":{"light":"github-light","dark":"github-dark"}}
const client = sdk.forAccount(account.accountId);
await client.account.safe.request(8453);
```

Safe deployment is asynchronous. The chain may not appear in `chainsDeployed` right away. Use `safe.resolve()` to confirm before your first transaction on that chain.

```ts theme={"theme":{"light":"github-light","dark":"github-dark"}}
const resolved = await client.account.safe.resolve(8453);
// resolved.status -> "validated" | "not-deployed" | "invalid" | "disconnected"
```

If you need the Safe on more chains, request each one:

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

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

### forAccount

Returns a scoped `BlendClient` for a specific account. The client has `discover`, `account`, and `sessions` modules.

```ts theme={"theme":{"light":"github-light","dark":"github-dark"}}
const client = sdk.forAccount("550e8400-e29b-41d4-a716-446655440000");
```

All session and account operations go through this scoped client.

## Session lifecycle

<Warning>
  The server SDK does not sign transactions. Prefer `client.sessions.execute()`, which manages the session lifecycle around your `submitActionPlan` callback. Use the manual methods only when you need direct lifecycle control.
</Warning>

### createSession

Creates or retrieves an active session for the scoped account.

```ts theme={"theme":{"light":"github-light","dark":"github-dark"}}
const client = sdk.forAccount("550e8400-e29b-41d4-a716-446655440000");

const session = await client.sessions.createSession({
  externalRef: "order-456",
});
```

Pass `forceReset: true` only when you intend to discard an active session before creating a fresh one. Re-quoting an `OPEN` session does not require a reset.

### quoteDeposit

Quotes a deposit on an open session. Uses `inputAssetAddress` (not `tokenAddress`).

```ts theme={"theme":{"light":"github-light","dark":"github-dark"}}
const quoted = await client.sessions.quoteDeposit(session.intentId, {
  chainId: 8453,
  inputAssetAddress: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
  eoa: "0x1111111111111111111111111111111111111111",
  amount: "100000000",
});
```

| Parameter           | Type     | Required | Description                                |
| ------------------- | -------- | -------- | ------------------------------------------ |
| `chainId`           | `number` | Yes      | Origin chain ID                            |
| `inputAssetAddress` | `string` | Yes      | Token contract on the origin chain         |
| `eoa`               | `string` | Yes      | The wallet address holding the tokens      |
| `amount`            | `string` | Yes      | Smallest unit, non-negative integer string |

### quoteWithdraw

Quotes a withdrawal on an open session.

```ts theme={"theme":{"light":"github-light","dark":"github-dark"}}
const quoted = await client.sessions.quoteWithdraw(session.intentId, {
  destinationChainId: 8453,
  amount: "50000000",
  isMaxWithdraw: false,
});
```

When `isMaxWithdraw` is `true`, the quote redeems every source-chain position. For any source chain that differs from the destination, regardless of `isMaxWithdraw`, Vault embeds Relay bridge calldata in the atomic withdrawal transaction. The returned step list may omit a separate `bridge` step even though execution still routes the funds to the destination.

### execute

Prefer the high-level orchestrator. It locks the quoted session, passes each action plan to your callback, submits the returned hashes, and resumes from the current state after a retry.

```ts theme={"theme":{"light":"github-light","dark":"github-dark"}}
const result = await client.sessions.execute(session.intentId, {
  signerAddress: "0x1111111111111111111111111111111111111111",
  submitActionPlan: async (plan) => {
    // Execute on-chain with your backend signer.
    const hash = await executeOnChain(plan);
    return [{ hash, chainId: plan.chainId }];
  },
  pollIntervalMs: 3000,
  pollTimeoutMs: 300000,
});
```

| Parameter          | Type                                                                      | Required | Description                                                                                   |
| ------------------ | ------------------------------------------------------------------------- | -------- | --------------------------------------------------------------------------------------------- |
| `signerAddress`    | `string`                                                                  | Yes      | EOA hex address                                                                               |
| `submitActionPlan` | `(plan: ActionPlan) => Promise<Array<{ hash: string; chainId: number }>>` | Yes      | Your on-chain execution callback                                                              |
| `onStatusChange`   | `(status: SessionStatus) => void`                                         | No       | Lifecycle callback. SDK 3.0.1 still emits the legacy `SUBMITTED` label after hash submission. |
| `pollIntervalMs`   | `number`                                                                  | No       | Polling interval for legacy `SUBMITTED` sessions. Default: `3000`.                            |
| `pollTimeoutMs`    | `number`                                                                  | No       | Max polling time for legacy `SUBMITTED` sessions. Default: `300000` (5 min).                  |

For current sessions, submit moves the session directly from `LOCKED` to `SETTLED`. `SETTLED` means the server recorded the transaction hashes. It does not mean the transactions were confirmed on-chain, and the session service does not verify receipts. Replaying the same hash and chain ID pairs returns the settled session. Read balances and positions for indexer-backed money state.

### Manual lifecycle

When you need direct control, quote first, narrow the `SessionResult`, check that its action plan is available, lock the session, execute on-chain, and submit the hashes.

```ts theme={"theme":{"light":"github-light","dark":"github-dark"}}
const manualSession = await client.sessions.createSession({});

const quoted = await client.sessions.quoteDeposit(manualSession.intentId, {
  chainId: 8453,
  inputAssetAddress: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
  eoa: "0x1111111111111111111111111111111111111111",
  amount: "100000000",
});

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

const { actionPlan } = quoted;

await client.sessions.lock(quoted.intentId, {
  signerAddress: "0x1111111111111111111111111111111111111111",
});

const hash = await executeOnChain(actionPlan);

const settled = await client.sessions.submit(quoted.intentId, {
  txHashes: [{ hash, chainId: actionPlan.chainId }],
});
```

### cancel

Cancels a session from any non-terminal state.

```ts theme={"theme":{"light":"github-light","dark":"github-dark"}}
const session = await client.sessions.cancel(intentId);
```

### get / list

Retrieve a session by ID or list sessions.

```ts theme={"theme":{"light":"github-light","dark":"github-dark"}}
const current = await client.sessions.get(session.intentId);

const active = await client.sessions.list({ status: "OPEN" });
```

### Abort an in-flight request

Session methods accept `RequestOptions` as their final argument. Pass an `AbortSignal` when a caller needs to discard a stale request.

```ts theme={"theme":{"light":"github-light","dark":"github-dark"}}
async function refreshDepositQuote(signal: AbortSignal) {
  return client.sessions.quoteDeposit(
    session.intentId,
    {
      chainId: 8453,
      inputAssetAddress: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
      eoa: "0x1111111111111111111111111111111111111111",
      amount: "100000000",
    },
    { signal },
  );
}
```

Aborting the HTTP request does not cancel or reset the session.

## Action plans

Quoted server sessions already expose `actionPlan` or `actionPlans` after you narrow on `type`. Use these utilities only when you need to convert a raw payload yourself.

```ts theme={"theme":{"light":"github-light","dark":"github-dark"}}
import {
  depositQuoteToActionPlan,
  withdrawCalldataToActionPlans,
  combineActionPlans,
} from "@blend-money/node";
```

### depositQuoteToActionPlan

Converts a raw deposit API response to a single `ActionPlan` with `deployType: "direct"`.

```ts theme={"theme":{"light":"github-light","dark":"github-dark"}}
if (session.type !== "DEPOSIT" || !session.payload) {
  throw new Error("Deposit payload is unavailable. Request a new quote.");
}

const plan = depositQuoteToActionPlan(
  session.payload,
  session.requestParams.eoa,
);
```

### withdrawCalldataToActionPlans

Converts a raw withdrawal API response to an `ActionPlan[]` (one per source chain) with `deployType: "multisend"`.

```ts theme={"theme":{"light":"github-light","dark":"github-dark"}}
if (session.type !== "WITHDRAW" || !session.payload) {
  throw new Error("Withdrawal payload is unavailable. Request a new quote.");
}

const plans = withdrawCalldataToActionPlans(session.payload);
```

### combineActionPlans

Merges plans on the same `chainId`. `"multisend"` takes priority over `"direct"`.

```ts theme={"theme":{"light":"github-light","dark":"github-dark"}}
const merged = combineActionPlans(plans);
```

## Discovery

Available on the SDK instance without account scoping.

```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

Available on the scoped client after `forAccount()`.

```ts theme={"theme":{"light":"github-light","dark":"github-dark"}}
const client = sdk.forAccount("550e8400-e29b-41d4-a716-446655440000");

const balance = await client.account.balance();
const history = await client.account.balanceHistory();
const positions = await client.account.positions();
const returns = await client.account.returns();
```

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

## What to watch out for

**Don't** try to call `sdk.execute()` on the root `BlendServerSdk` instance. Execution lives on the account-scoped session module. Use `client.sessions.execute()` with a `submitActionPlan` callback, or manage the lock/submit lifecycle manually.

**Don't** expose your API key (`sk_live_`) in client code, logs, or version control. It grants full access to your organization's accounts.

**Don't** skip `forAccount()`. Account-scoped operations like sessions and balances require a scoped client. Calling them on the root SDK will fail.

**Don't** forget that `quoteDeposit` uses `inputAssetAddress` on the server, not `tokenAddress`. The frontend SDK uses `tokenAddress` - the signatures differ.

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