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:
| Field | Type | Description |
|---|
accountId | string | Blend account UUID (from safe.account()) |
destinationChainId | number | Chain where the user wants to receive the withdrawn funds |
amount | string | Total amount in underlying units as an integer string |
isMaxWithdraw | boolean? | 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:
| Field | Type | Description |
|---|
chainId | number | Source chain for this payload |
vaultAddress | string | ERC-4626 vault being exited |
amount | string | Amount allocated from this chain |
liquidityReset | { target, data } | null | Delegatecall to flush vault positions to liquid assets |
withdraw | Array<{ target, data }> | Ordered ERC-4626 transactions (approve, multicall, approve-reset) |
bridge | unknown | null | Relay 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