Every SDK operation that touches a specific user requires an accountId. You get that by calling client.safe.account() first. This is the entry point for your integration.
Call client.safe.account(userEoa) before anything else. Deposit quotes, balance reads, position reads, and withdrawals all require the accountId it returns. Nothing works without it.
The Blend SDK and API must be called from your backend server. Never expose your API key in frontend code. Embedding the SDK in a browser or mobile client leaks your credentials and compromises your integration.
Bootstrap the account
client.safe.account(address)
This is the first call you make for every user session. It fetches or creates the Blend account for a user EOA and returns everything you need to drive the rest of the SDK.
const account = await client.safe.account(userEoa);
// account.accountId → pass this to every subsequent call
// account.safeAddress → the user's deterministic Safe address
// account.chainsDeployed → chains where the Safe is already live
Returns SafeAccountResponse:
| Field | Type | Description |
|---|
accountId | string | Blend account UUID. Required by balance, positions, returns, and deposit APIs |
safeAddress | Hex | Deterministic Safe address |
chainsDeployed | number[] | Chain IDs where the Safe is already deployed |
How accountId flows through the SDK
Once you have the accountId, it unlocks every other call:
// 1. Bootstrap
const account = await client.safe.account(userEoa);
// 2. Read
const balance = await client.balance.get(account.accountId);
const positions = await client.positions.get(account.accountId);
const returns = await client.returns.get(account.accountId);
// 3. Deposit
const quote = await client.deposit.getQuote({
accountId: account.accountId,
chainId: 8453,
inputAssetAddress: usdcAddress,
eoa: userEoa,
amount: "1000000",
});
// 4. Withdraw
const calldata = await client.withdraw.getCalldata({
accountId: account.accountId,
chainId: 8453,
amount: "1000000",
});
Store the accountId against your internal user record. You only need to call client.safe.account() once per user to get it, then reuse it for all future requests.
Chain-specific Safe operations
client.safe.resolve(accountId, chainId)
Checks Safe deployment state for a specific chain. Returns a discriminated union keyed on status:
| Status | Meaning |
|---|
"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 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 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();
Next steps