Skip to main content
This page describes the current public flow exposed by @blend-money/sdk. The important split is:
  • Deposits: use client.deposit to discover chains and assets, check wallet holdings, and request a quote
  • Withdrawals: use client.withdraw.getCalldata() to receive ordered execution payloads

Prerequisites

pnpm add @blend-money/sdk viem
Initialize one client per neobank plus account type:
import { BlendClient } from "@blend-money/sdk";
import { http } from "viem";

const client = new BlendClient({
  baseUrl: "https://api.portal.blend.money",
  apiKey: "blend_xxx",
  neobankId: "acme-bank",
  accountTypeId: "uuid-xxx",
  // transports and paymasterTransport are required by the config shape.
  // They are only exercised when submitting on-chain transactions (e.g. via TransactionHandler).
  // All read methods, quote methods, and withdraw.getCalldata() are pure HTTP. No RPC needed.
  transports: {
    8453: http("https://base-mainnet.g.alchemy.com/v2/MY_KEY"),
  },
  paymasterTransport: http("https://api.pimlico.io/v2/8453/rpc?apikey=MY_KEY"),
});

Deposit flow

1. Get the Blend account

const account = await client.safe.account(userEoa);
This call gives you the accountId and Safe address required by the rest of the flow.

2. Discover depositable assets

// Featured tokens on Base (for the asset picker)
const tokens = await client.deposit.getTokens(8453);

// Tokens the user holds on Base (with balance and amountUsd fields)
const walletTokens = await client.deposit.getTokens(8453, userEoa);
Use getTokens(chainId) to populate the asset picker. Pass the eoa parameter to filter to tokens the user holds, enriched with balance and amountUsd fields.
When you pass eoa, the Blend backend fetches wallet balances for you. Your app does not need an RPC connection or wallet provider to check balances.

3. Request a deposit quote

const quote = await client.deposit.getQuote({
  chainId: 8453,
  inputAssetAddress: usdcAddress,
  eoa: userEoa,
  accountId: account.accountId,
  amount: "1000000",
});
accountId is mandatory. Blend resolves the destination Safe from account context before generating the quote.

4. Execute the deposit

The public SDK does not expose a client-built deposit() action. Your application should use the returned quote payload to drive deposit execution in your wallet, relay, or orchestration layer.

Withdrawal flow

Withdrawals are exposed through server-built calldata, not local action-plan construction.

1. Request withdrawal payloads

WithdrawCalldataParams:
FieldTypeDescription
accountIdstringBlend account UUID (from safe.account())
destinationChainIdnumberChain where the user wants to receive the withdrawn funds
amountstringTotal amount in underlying units as an integer string
isMaxWithdrawboolean?When true, redeems all shares on every chain regardless of amount
const result = await client.withdraw.getCalldata({
  accountId: account.accountId,
  destinationChainId: 8453,
  amount: "1000000000000000000",
});

2. Execute each payload in order

Each payload in result.payloads represents one source chain. Fields:
FieldTypeDescription
chainIdnumberSource chain for this payload
vaultAddressstringERC-4626 vault being exited
amountstringAmount allocated from this chain
timeEstimatenumberSettlement time in seconds. 0 when same chain
feesQuoteFees | nullBridge fee breakdown. null when no bridge needed
stepsWithdrawStep[]Ordered steps by kind: liquidityReset, approve, withdraw, approveReset, bridge
for (const payload of result.payloads) {
  for (const step of payload.steps) {
    switch (step.kind) {
      case "liquidityReset":
        // submit step.data as delegatecall to step.to
        break;
      case "approve":
      case "withdraw":
      case "approveReset":
        // submit step.data to step.to as a regular call
        break;
      case "bridge":
        // submit step.data to step.to with step.value as ETH value
        break;
    }
  }
}

3. Handle edge cases

Max withdrawals When you pass isMaxWithdraw: true, Blend redeems all shares across the selected chains.
For max withdrawals, the final redeemed amount is not known pre-settlement. Because of that, the response may omit the pre-built bridge quote. If you need to bridge the withdrawn funds afterward, fetch or build that bridge step after settlement using the actual amount received.
409 Conflict - rebalance already in progress client.withdraw.getCalldata() returns a 409 response when a rebalance flow plan is already active for the user’s account. This is a normal transient state, not a permanent error.
import { SdkError } from "@blend-money/sdk";

try {
  const result = await client.withdraw.getCalldata({
    accountId: account.accountId,
    destinationChainId: 8453,
    amount: "1000000000000000000",
  });
} catch (error) {
  const sdkError = error instanceof SdkError ? error : SdkError.fromAxiosError(error);
  if (sdkError.status === 409) {
    // Surface this to the user. Do not retry automatically.
    // "Withdrawal temporarily unavailable. A rebalance is in progress. Try again shortly."
  }
}
Do not silently retry a 409. Surface it as a product-level message. The rebalance will complete on its own and the withdrawal can be retried once the account is settled.

Error handling

import { SdkError } from "@blend-money/sdk";

try {
  const account = await client.safe.account(userEoa);
} catch (error) {
  const sdkError =
    error instanceof SdkError ? error : SdkError.fromAxiosError(error);
  console.error(sdkError.getUserMessage());
}

Common pitfalls

  • Do not call client.deposit.getQuote() without accountId
  • Do not assume the public SDK exposes client-built deposit or withdrawal action helpers
  • Do not assume a bridge step is always present in withdrawal payloads, especially when isMaxWithdraw is true

Next steps

Blend SDK

Review the current SDK surface.

Cross-chain Deposits

See the detailed quote-generation flow.

Integrations Overview

Understand how account types, accounts, and Safes fit together.
Last modified on March 20, 2026