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

# Your First Deposit

> Install the SDK, make your first deposit, and verify the balance. Under 30 minutes from zero to working code.

Install the SDK, make your first deposit, and verify the balance. Under 30 minutes from zero to working code.

<Note>
  Before you start, you need a [portal account](/build/portal-setup) with at least one active account type and [credentials set up](/build/configure-credentials) (SIWE domain for frontend, API key for server).
</Note>

<Tabs>
  <Tab title="Frontend">
    <Steps>
      <Step title="Install the SDK">
        ```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
        pnpm add @blend-money/fe@3.0.1 viem@2.55.8
        ```

        The frontend SDK depends on [viem](https://viem.sh) for wallet interactions and chain management.
      </Step>

      <Step title="Initialize the SDK">
        ```ts blend.ts theme={"theme":{"light":"github-light","dark":"github-dark"}}
        import {
          BlendSdk,
          getPimlicoPaymasterEndpoints,
        } from "@blend-money/fe";

        const sdk = new BlendSdk({
          publishableKey: "pk_live_a1b2c3d4e5f6...",
          signMessage: (message) => wallet.signMessage({ message }),
          paymaster: getPimlicoPaymasterEndpoints("your-pimlico-key"),
        });
        ```

        The `signMessage` function connects your wallet adapter. If you use wagmi, pass `signMessageAsync` from the `useSignMessage` hook. Only the frontend SDK needs the paymaster registry. SDK 3.0.1 gets gas pricing from the bundler configured in that registry.
      </Step>

      <Step title="Sign in the user">
        ```ts sign-in.ts theme={"theme":{"light":"github-light","dark":"github-dark"}}
        import { BlendSdk } from "@blend-money/fe";

        const session = await sdk.signIn({
          address: "0x1111111111111111111111111111111111111111",
          chainId: 8453,
        });

        console.log(session.accountId);    // "550e8400-e29b-41d4-a716-446655440000"
        console.log(session.safeAddress);  // "0x..."
        ```

        This starts a SIWE flow. The user's wallet signs a challenge message, and Blend returns a session with their account ID and deterministic Safe address. This creates the account record if needed, but it does not deploy the Safe on-chain. Sessions last 1 hour and auto-refresh. If you omit `chainId`, the SDK uses Ethereum mainnet (`1`) in the SIWE message.

        The `chainId` here is for the SIWE challenge message, not for Safe deployment. Check `session.chainsDeployed` to see where the Safe is live. If the deposit chain isn't listed, request deployment first. See [Safe deployment](/build/best-practices#safe-deployment).
      </Step>

      <Step title="Discover chains and tokens">
        ```ts discover.ts theme={"theme":{"light":"github-light","dark":"github-dark"}}
        import { BlendSdk } from "@blend-money/fe";

        const chains = await sdk.discover.depositChains();
        const tokens = await sdk.discover.depositTokens(8453, {
          eoa: "0x1111111111111111111111111111111111111111",
        });
        ```

        `depositChains()` returns all supported deposit chains. `depositTokens()` returns tokens on that chain. Pass the user's address to see their balances. Sign in before calling discovery methods. Frontend API operations outside the SIWE challenge and verification calls require the active SIWE session.
      </Step>

      <Step title="Quote the deposit">
        ```ts quote.ts theme={"theme":{"light":"github-light","dark":"github-dark"}}
        import { BlendSdk, parseAmount } from "@blend-money/fe";

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

        The quote shows input/output amounts, fees, and estimated time. It expires after a few minutes. If you need to change the amount, call `quoteDeposit` again - the SDK reuses the same session.

        <Tip>
          Use `parseAmount` from the installed `@blend-money/fe` package to convert human-readable amounts to the smallest unit. USDC has 6 decimals, so `parseAmount("100", 6)` returns `"100000000"`.
        </Tip>
      </Step>

      <Step title="Execute the deposit">
        ```ts execute.ts theme={"theme":{"light":"github-light","dark":"github-dark"}}
        import { BlendSdk } from "@blend-money/fe";
        import {
          createPublicClient,
          createWalletClient,
          custom,
          http,
          type Chain,
        } from "viem";
        import { base } from "viem/chains";

        const signerAddress = "0x1111111111111111111111111111111111111111";
        const chainsById: Record<number, Chain> = {
          [base.id]: base,
        };

        const result = await sdk.execute(quote, {
          signerAddress,
          deriveSigner: async (chainId) => {
            const chain = chainsById[chainId];
            if (!chain) throw new Error(`Unsupported chain ${chainId}`);

            return {
              signer: createWalletClient({
                account: signerAddress,
                chain,
                transport: custom(window.ethereum),
              }),
              publicClient: createPublicClient({ chain, transport: http() }),
            };
          },
        });
        ```

        The SDK locks the session, builds the transaction, and submits it. `deriveSigner` is called once for deposits. Return clients for the requested chain and attach an account whose address matches `signerAddress`. Add every chain your account type supports to `chainsById`. For smart wallets such as Coinbase Wallet, set `isContractSigner: true`.
      </Step>

      <Step title="Check the balance">
        ```ts balance.ts theme={"theme":{"light":"github-light","dark":"github-dark"}}
        import { BlendSdk } from "@blend-money/fe";

        const balance = await sdk.account.balance();
        console.log(balance.total); // { USD: 100.05, EUR: 92.12 }
        ```

        The balance reflects the deposit after settlement. This usually takes under a minute for same-chain deposits.
      </Step>

      <Step title="Handle errors">
        ```ts errors.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);            // "FLOWPLAN_CONFLICT"
            console.error(error.getUserMessage()); // user-friendly text
            if (error.isRetryable()) { /* retry with backoff */ }
          }
        }
        ```

        `SdkError` gives you a machine-readable `code`, a `getUserMessage()` for displaying to users, and `isRetryable()` for handling transient failures like rate limits and server errors.
      </Step>
    </Steps>
  </Tab>

  <Tab title="Server">
    <Steps>
      <Step title="Install the SDK">
        ```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
        pnpm add @blend-money/node@3.0.1
        ```

        The server SDK is intended for Node.js backends.
      </Step>

      <Step title="Initialize the SDK">
        ```ts blend-server.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-usdc",
        });
        ```

        The `accountTypeId` is the slug you chose when creating your account type in the portal.
      </Step>

      <Step title="Look up the account">
        ```ts lookup.ts theme={"theme":{"light":"github-light","dark":"github-dark"}}
        import { BlendServerSdk } from "@blend-money/node";

        const account = await sdk.lookupAccount(
          "0x1111111111111111111111111111111111111111"
        );
        console.log(account.accountId);     // "550e8400-e29b-41d4-a716-446655440000"
        console.log(account.safeAddress);   // "0x..."
        console.log(account.chainsDeployed); // [8453]
        ```

        Pass the user's EOA address. Blend returns their account ID and Safe address. `lookupAccount` creates the account if it doesn't exist yet, but does not deploy a Safe. To deploy, scope the account with `forAccount` and call `safe.request(chainId)`, shown below.

        <Warning>
          `chainsDeployed` only lists chains where the Safe is live. Before your first deposit, check that the deposit chain is deployed:

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

          if (resolved.status === "not-deployed") {
            await client.account.safe.request(8453);
          }
          ```

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

      <Step title="Scope to the account">
        ```ts scope.ts theme={"theme":{"light":"github-light","dark":"github-dark"}}
        import { BlendServerSdk } from "@blend-money/node";

        const client = sdk.forAccount(account.accountId);
        ```

        All session and balance operations go through this scoped client. It has `client.discover`, `client.account`, and `client.sessions`.
      </Step>

      <Step title="Create a session and quote">
        ```ts session-quote.ts theme={"theme":{"light":"github-light","dark":"github-dark"}}
        import { BlendServerSdk } from "@blend-money/node";

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

        const quoted = await client.sessions.quoteDeposit(session.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.");
        }

        console.log(quoted.quoteSummary);
        ```

        The server SDK uses `inputAssetAddress`, not `tokenAddress`. Server quote methods return a `SessionResult`. Narrow on `type` before reading its quote summary or extracted action plan. The session stays open until you lock it, cancel it, or it expires, which usually takes about 15 minutes.
      </Step>

      <Step title="Execute the session">
        ```ts execute-server.ts theme={"theme":{"light":"github-light","dark":"github-dark"}}
        import { BlendServerSdk } from "@blend-money/node";

        const settled = await client.sessions.execute(quoted.intentId, {
          signerAddress: "0x1111111111111111111111111111111111111111",
          submitActionPlan: async (plan) => {
            // Execute plan.requiredApprovals, then plan.requiredTxns.
            return yourSigner.executeActionPlan(plan);
          },
        });

        console.log(settled.status); // "SETTLED"
        ```

        The server SDK locks the quote before it calls your signer, executes each action plan in order, and submits the returned transaction hashes. Vault accepts the hashes and moves the session directly from `LOCKED` to `SETTLED`. It does not verify transaction receipts. Repeating the same submission returns the settled session.
      </Step>

      <Step title="Check the balance">
        ```ts balance-server.ts theme={"theme":{"light":"github-light","dark":"github-dark"}}
        import { BlendServerSdk } from "@blend-money/node";

        const balance = await client.account.balance();
        console.log(balance.total); // { USD: 100.05 }
        ```

        The balance updates after Blend's indexer processes the on-chain events. This usually takes under a minute.
      </Step>
    </Steps>
  </Tab>
</Tabs>

## What just happened

Your deposit followed this path:

1. The SDK created a session and requested a quote from Blend's API.
2. Blend calculated the optimal route, including any bridge steps for cross-chain deposits.
3. The transaction was submitted on-chain. Gas was sponsored through the configured frontend bundler or paid by your server signer.
4. Funds landed in the user's Safe and were allocated to vaults based on your account type's allocation.

The user's Safe is a real [Gnosis Safe](/blend/security). Only whitelisted modules can move funds. The user retains ownership at all times.

## What to watch out for

**Don't** create a new session for every quote. Call `quoteDeposit` again on the same `OPEN` session to update the amount, token, or chain. Use `forceReset: true` only when you want to discard the active session, including when switching from a deposit to a withdrawal.

**Don't** skip error handling. Quotes expire, flow plans can conflict with deposits, and wallets can reject signatures. Check `error.isRetryable()` before retrying.

**Don't** hard-code chain IDs or token addresses. Use `discover.depositChains()` and `discover.depositTokens()` to build your UI dynamically.

## Go-live checklist

<Steps>
  <Step title="Verify your SIWE domain">
    Confirm the domain in **Settings > Credentials** matches your production URL. Not your staging URL. Not localhost.
  </Step>

  <Step title="Rotate credentials">
    Create fresh API keys for production. Don't reuse development keys. Deactivate any test keys.
  </Step>

  <Step title="Test end-to-end">
    Make a real deposit and withdrawal on each supported chain. Verify balances update correctly. Test with amounts under \$1 to keep costs low.
  </Step>

  <Step title="Confirm allocations">
    Check that your account type's vault config is **Active** (not Pending or Provisioning). Deposits to an account type without active infrastructure will fail.
  </Step>

  <Step title="Set flow plan mode">
    Decide whether to auto-approve flow plans or review them manually. Auto-approve is simpler but gives you less control over rebalancing timing.
  </Step>

  <Step title="Verify error handling">
    Test your app's behavior for expired quotes, rejected wallet signatures, and `FLOWPLAN_CONFLICT` errors. Users should see helpful messages, not stack traces.
  </Step>
</Steps>

<Check>
  Once you've completed the checklist, your integration is production-ready. Deposits, withdrawals, and rebalancing will work end-to-end.
</Check>

<CardGroup cols={2}>
  <Card title="Frontend SDK reference" icon="browser" href="/build/sdk/frontend">
    Full method reference for quoting, execution, and account data.
  </Card>

  <Card title="Server SDK reference" icon="server" href="/build/sdk/server">
    Full method reference for account management and session lifecycle.
  </Card>

  <Card title="Deposits and withdrawals" icon="arrow-right-arrow-left" href="/build/sdk/transactions">
    Cross-chain routing, re-quoting, and withdrawal coordination.
  </Card>

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