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 assets, inspect user balances, and request a quote
  • Withdrawals: use client.withdraw.getCalldata() to receive ordered execution payloads
  • Rebalances: use client.rebalance to create and monitor async rebalance requests

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

const tokenCatalog = await client.deposit.getTokens(8453);
const balances = await client.deposit.getBalances(userEoa, 8453);
Use the catalog to populate the asset picker, and getBalances() to show the user’s available ERC-20 balances on the source chain.
getBalances() routes through the Blend backend, meaning your application does not need to connect to an RPC provider to check the user’s wallet 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
liquidityReset{ target, data } | nullDelegatecall to flush vault positions to liquid assets
withdrawArray<{ target, data }>Ordered ERC-4626 transactions (approve, multicall, approve-reset)
bridgeunknown | nullRelay bridge quote when source chain differs from destinationChainId
for (const payload of result.payloads) {
  if (payload.liquidityReset) {
    // submit liquidityReset.data as delegatecall to liquidityReset.target
  }

  for (const txn of payload.withdraw) {
    // submit txn.data to txn.target in order
  }

  if (payload.bridge) {
    // execute the bridge quote when present
  }
}

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.

Rebalance flow

Rebalances are async and scheduler-driven. createRequest() returns { requestId: string | null, candidateCount: number }requestId is null when there are no open candidates.
const request = await client.rebalance.createRequest();

if (request.requestId) {
  const status = await client.rebalance.getRequest(request.requestId);
}

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 treat client.deposit.getTokens() as a flat array response
  • Do not assume the public SDK exposes client-built deposit or withdrawal action helpers
  • Do not assume payload.bridge is always present, especially when isMaxWithdraw is true

Next steps

Last modified on March 10, 2026