Skip to main content
@blend-money/sdk is the public SDK package for Blend neobank integrations. It exports one class, BlendClient, with flat domain namespaces:
  • client.safe
  • client.balance
  • client.positions
  • client.returns
  • client.yield
  • client.rebalance
  • client.deposit
  • client.withdraw

Install

pnpm add @blend-money/sdk

Initialize

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: {
    8453: http("https://base-mainnet.g.alchemy.com/v2/MY_KEY"),
  },
  paymasterTransport: http("https://api.pimlico.io/v2/8453/rpc?apikey=MY_KEY"),
});
Create one client per neobank plus account type. Per-user values like the EOA, accountId, and Safe address are passed as method arguments, never stored in config.

Configuration fields

FieldTypeRequiredDescription
baseUrlstringYesBlend API base URL (e.g. https://api.portal.blend.money)
apiKeystringYesSent as X-API-Key header on every request
neobankIdstringYesYour neobank slug — used in the /extern/:neobankId/ path segment
accountTypeIdstringYesAccount type UUID — used in /extern/:neobankId/:accountTypeId/
transportsRecord<number, Transport>YesViem transports keyed by chain ID — your RPC infrastructure
paymasterTransportTransportYesViem transport for the paymaster / bundler (e.g. Pimlico)
timeoutMsnumberNoRequest timeout in milliseconds (default: 15000)
retriesnumberNoMax retry attempts for retryable errors (default: 3)
The SDK automatically retries on 429, 500, 502, 503, and 504 responses with exponential backoff up to retries attempts. You do not need to implement retry logic on top of the client.

Account and Safe APIs

client.safe.account(address)

Fetches or creates the Blend account for a user EOA. This is the usual entry point for a user session. Returns SafeAccountResponse:
FieldTypeDescription
accountIdstringBlend account UUID — required by balance, positions, returns, and deposit APIs
safeAddressHexDeterministic Safe wallet address
chainsDeployednumber[]Chain IDs where the Safe is already deployed
const account = await client.safe.account(userEoa);
// account.accountId
// account.safeAddress
// account.chainsDeployed

client.safe.resolve(accountId, chainId)

Checks Safe deployment state for a specific chain. Returns a discriminated union keyed on status:
StatusMeaning
"validated"Safe is deployed and live — includes accountId, userAddress, safeAddress, chainId
"not-deployed"Safe address is known but not yet deployed on this chain
"invalid"Safe address does not match expected derivation
"disconnected"Safe exists but the role-modifier module is not enabled
const resolved = await client.safe.resolve(account.accountId, 8453);

if (resolved.status === "validated") {
  // resolved.accountId, resolved.safeAddress, resolved.chainId
}

client.safe.request(accountId, chainId)

Requests Safe deployment on a specific chain.
await client.safe.request(account.accountId, 8453);

Account-scoped reads

All reads in this section (balances, positions, returns, yield) are routed through the Blend backend API. The client does not need an RPC connection, a viem provider, or a connected wallet to fetch these state values.

client.balance.get(accountId)

const balance = await client.balance.get(account.accountId);

client.balance.getHistory(accountId, params?)

const history = await client.balance.getHistory(account.accountId, {
  startDate: "2026-03-01",
  endDate: "2026-03-09",
});

client.positions.get(accountId)

const positions = await client.positions.get(account.accountId);

client.returns.get(accountId)

const returns = await client.returns.get(account.accountId);

client.yield.get()

const yieldInfo = await client.yield.get();

Deposit APIs

client.deposit.getTokens(chainId?, address?)

Returns a token catalog with chains and tokens arrays.
const catalog = await client.deposit.getTokens(8453);

client.deposit.getBalances(eoa, chainId)

Returns non-zero ERC-20 balances for the user EOA on the source chain.
Like account-scoped reads, this query is handled by the Blend backend. It does not require an RPC connection or wallet provider on the client.
const balances = await client.deposit.getBalances(userEoa, 8453);

client.deposit.getQuote(params)

Requests a deposit quote. accountId is required because Blend resolves the destination Safe and account-type configuration from it. DepositQuoteParams:
FieldTypeDescription
chainIdnumberOrigin chain the user is depositing from
inputAssetAddressstringToken contract address on the origin chain
eoastringUser’s EOA address
accountIdstringBlend account UUID (from safe.account())
amountstringAmount in the input token’s smallest units
const account = await client.safe.account(userEoa);

const quote = await client.deposit.getQuote({
  chainId: 8453,
  inputAssetAddress: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
  eoa: userEoa,
  accountId: account.accountId,
  amount: "1000000",
});

Withdraw API

client.withdraw.getCalldata(params)

Returns server-built withdrawal execution payloads. The server resolves the Safe and user context from the accountId, selects source chains, and returns an ordered payload set for the integrator to execute.
const result = await client.withdraw.getCalldata({
  accountId: account.accountId,
  destinationChainId: 8453,
  amount: "1000000000000000000",
  // isMaxWithdraw: true,
});

for (const payload of result.payloads) {
  if (payload.liquidityReset) {
    // submit delegatecall first
  }
  for (const txn of payload.withdraw) {
    // then submit each withdraw transaction in order
  }
  if (payload.bridge) {
    // finally execute the bridge quote when present
  }
}
Each WithdrawCalldataPayload contains:
FieldTypeDescription
chainIdnumberSource chain for this payload
vaultAddressstringERC-4626 vault being exited
amountstringAmount allocated from this chain
liquidityReset{ target, data } | nullRebalance calldata to flush positions — submit as delegatecall
withdrawArray<{ target, data }>Ordered ERC-4626 withdraw transactions (approve, multicall, approve-reset)
bridgeunknown | nullRelay bridge quote when source chain differs from destinationChainId
Important behavior:
  • amount is an integer string in underlying token units
  • isMaxWithdraw: true redeems all shares across chains
  • a 409 response means a rebalance flow plan is already active
  • for max withdraws, bridge payloads may be omitted because the exact post-settlement amount is not known up front

Rebalance APIs

client.rebalance.getCandidates()

const { candidates } = await client.rebalance.getCandidates();

client.rebalance.createRequest(requestData?)

Triggers a scheduler-driven rebalance request. Returns { requestId: string | null, candidateCount: number }.
requestId is null when there are no open candidates. Always check before polling with getRequest().
const request = await client.rebalance.createRequest();
if (request.requestId) {
  const status = await client.rebalance.getRequest(request.requestId);
}

client.rebalance.getRequest(id)

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

  if (sdkError.isRetryable()) {
    // retry with backoff
  } else {
    console.error(sdkError.getUserMessage());
  }
}

Types

All major config, domain, and integration types are exported directly from @blend-money/sdk, including:
  • BlendClientConfig
  • SafeAccountResponse
  • BalanceResponse
  • PositionsResponse
  • ReturnsResponse
  • YieldResponse
  • CandidatesResult
  • RebalanceRequest
  • TokenCatalog
  • DepositToken
  • DepositBalance
  • DepositQuoteParams
  • DepositQuoteResponse
  • WithdrawCalldataParams
  • WithdrawCalldataPayload
  • WithdrawCalldataResult
  • SdkError

Advanced utilities

The SDK still exports advanced Safe utilities such as TransactionHandler, SafeMultisendManager, combineActionPlans, ActionPlan, and Txn. These are advanced building blocks. The primary public withdrawal path is client.withdraw.getCalldata(), not client-built action plans.
Last modified on March 10, 2026