Blend’s public integration surface is designed for neobanks, wallets, and fintech products that want user-isolated Safe accounts with Blend-managed infrastructure.
The current public implementation is centered on:
- a single SDK package:
@blend-money/sdk
- integration API routes under
/extern/:neobankId/:accountTypeId/*
- account-scoped reads, quotes, and rebalance requests
- server-built withdrawal payloads for Safe-side exits
pnpm add @blend-money/sdk viem
The operating model
Blend’s integration model has four core entities:
| Entity | What it represents | Managed by |
|---|
Risk Architect | The organization defining strategy intent (equivalent to a curator or risk manager in Morpho) | Risk Architect admin portal |
Basket | The strategy template linked to a product | Risk Architect |
AccountType | Your product configuration for end users | Integrating neobank |
Account | A single end-user account under one account type | Created per user |
Safe | The deterministic Safe wallet tied to that user account | Blend + on-chain deployment flow |
What your application owns
Your application is responsible for:
- creating and managing the user experience
- collecting the user’s EOA address
- calling Blend with your API key,
neobankId, and accountTypeId
- persisting any product-specific mappings between your internal user IDs and Blend
accountIds
- executing deposit quote payloads and withdrawal calldata payloads in your wallet or relay layer
Blend is responsible for:
- account creation and account-scoped API reads
- deterministic Safe addressing
- Safe deployment requests and validation
- deposit quote generation
- withdrawal calldata generation
- rebalance request generation
Recommended request flow
In the current public SDK, the normal lifecycle is:
- Instantiate one
BlendClient per neobank plus account type
- Call
client.safe.account(userEoa) to get the user’s accountId and Safe address
- Use
account.accountId for balances, positions, returns, deposit quotes, and rebalance request flows
- Use
client.withdraw.getCalldata(...) when the user wants to exit funds
const account = await client.safe.account(userEoa);
const balance = await client.balance.get(account.accountId);
const quote = await client.deposit.getQuote({
chainId: 8453,
inputAssetAddress: usdcAddress,
eoa: userEoa,
accountId: account.accountId,
amount: "1000000",
});
Safe lifecycle
The Safe-related endpoints are intentionally split:
safe.account(address): returns the Blend account record and deterministic Safe address
safe.resolve(address, chainId): checks whether the Safe is already deployed on a specific chain
safe.request(address, chainId): requests Safe deployment on a specific chain
Use safe.account() as your default first call. Only reach for safe.resolve() or safe.request() when you need chain-specific deployment behavior.
HTTP vs RPC: what requires what
This is the most important architectural distinction in the SDK. Getting this wrong is the most common integration mistake.
Group 1 — Pure backend HTTP calls: no wallet, no RPC, no user signature needed
Every method in this group makes an Axios HTTP request to the Blend backend. They work from a Node.js server, a Next.js API route, or a browser. The user does not need to connect their wallet for any of these.
| SDK method | What it does |
|---|
client.safe.account(eoa) | Gets or creates the account record. Returns accountId, safeAddress, chainsDeployed. |
client.safe.resolve(eoa, chainId) | Checks Safe deployment status on a specific chain. |
client.safe.request(eoa, chainId) | Requests Safe deployment on a new chain. |
client.balance.get(accountId) | Per-chain balances and total USD. |
client.balance.getHistory(accountId, params?) | Historical balance snapshots. |
client.positions.get(accountId) | All deposit, withdraw, and rebalance events. |
client.returns.get(accountId) | Cumulative return metrics. |
client.yield.get() | Current yield breakdown for the account type. No accountId needed. |
client.deposit.getTokens(chainId?) | Token catalog. Heavily cached — safe to call on page load. |
client.deposit.getBalances(eoa, chainId) | Non-zero ERC-20 balances for the user’s EOA. Blend’s backend fetches this — no RPC connection required on the client. |
client.deposit.getQuote(params) | Relay-backed deposit route quote. Returns a quote payload for your bridge execution UI. |
client.withdraw.getCalldata(params) | Pre-built, ABI-encoded withdrawal calldata across chains. Returns calldata — does not execute it. |
client.rebalance.getCandidates() | Accounts with open rebalance opportunities. |
client.rebalance.createRequest() | Creates a rebalance request. Returns a requestId. |
client.rebalance.getRequest(id) | Polls a rebalance request by ID. |
Group 2 — On-chain execution: requires Viem transports and a paymaster
The transports and paymasterTransport fields in BlendClientConfig exist solely for on-chain execution. They are not used by any of the read or quote methods above.
| Scenario | What you need |
|---|
Submitting calldata from withdraw.getCalldata() via a relay or TransactionHandler | paymasterTransport (e.g. Pimlico) |
Enabling a Safe module via TransactionHandler.submitEnableModuleTransaction() | Viem WalletClient, PublicClient, and paymasterTransport |
Executing action plans via TransactionHandler.submitActionPlan() | Viem WalletClient, PublicClient, and paymasterTransport |
transports is keyed by chain ID and points to your RPC provider (e.g. Alchemy). paymasterTransport points to your ERC-4337 bundler (e.g. Pimlico). Both are required in BlendClientConfig even if you only use read methods, because they are part of the config shape — but they are only exercised when executing on-chain transactions.
Summary
Use these namespaces for reads, quotes, and calldata generation — no wallet connection needed:
client.safe
client.balance
client.positions
client.returns
client.yield
client.deposit
client.rebalance
client.withdraw (returns calldata only)
Use TransactionHandler and SafeMultisendManager when you need to submit UserOperations on-chain. The deposit quote returned by client.deposit.getQuote() is executed via your Relay integration, not the SDK.
There is no public client-built deposit() action in the current @blend-money/sdk surface. Deposits use quote APIs and withdrawals use server-built calldata payloads.
Integration risks to design around
accountId is required for client.deposit.getQuote() because Blend resolves the destination Safe from account context
client.deposit.getTokens() returns a TokenCatalog with { chains, tokens }, not a flat array
client.rebalance.getCandidates() returns { candidates: [...] }, not a bare array
client.rebalance.createRequest() returns { requestId: string | null, candidateCount } — requestId is null when no candidates exist
client.safe.request() returns { message }, not a status wrapper inside data
client.withdraw.getCalldata() may omit bridge on max withdrawals because the final amount is not known until settlement
Next steps