Skip to main content
Authentication, account management, balance queries, error handling, and TypeScript type definitions.

Authentication

SIWE flow (frontend)

The frontend SDK uses Sign-In with Ethereum. The user’s wallet signs a challenge message. Blend returns a JWT that binds the wallet to a Blend account. The JWT contains the wallet address, account ID, and account type. Every SDK call after sign-in includes it automatically. You never pass an account ID.

API key auth (server)

The server SDK authenticates with an API key (sk_live_) in the X-API-Key header. No challenge/verify step. The API key replaces both the publishable key and the SIWE session.
import { BlendServerSdk } from "@blend-money/node";

const sdk = new BlendServerSdk({
  apiKey: process.env.BLEND_API_KEY!,
  accountTypeId: "savings-usd",
});
You pass accountId explicitly to every account-scoped call via sdk.forAccount(accountId).

Rate limits

EndpointLimitScoped by
SIWE challenge (POST /auth/challenge)10 / 60 secondsIP address
SIWE verify (POST /auth/verify)5 / 60 secondsIP address
Account-scoped routes (frontend)100 / 60 secondsWallet address
Account-scoped routes (server)100 / 60 secondsAPI key
Exceeding limits returns a 429 with error code RATE_LIMITED or RATE_LIMIT.

Session security

  • Keep JWTs in memory. Use sessionStorage for persistence across reloads. Avoid localStorage.
  • Clear the session when the wallet disconnects or address changes.
  • Never share an exported session across users or account types.

Auth error codes

CodeStatusWhat to do
AUTH_NOT_SIGNED_IN401Call sdk.signIn().
AUTH_CHALLENGE_FAILED500Check publishable key and network. Retry.
AUTH_VERIFICATION_FAILED401User may have cancelled signing or wallet address mismatch.
AUTH_TOKEN_EXPIRED401JWT expired. The SDK auto-refreshes, but call signIn() if it persists.
AUTH_INVALID_SESSION400Malformed data in restoreSession(). Clear and sign in again.

Accounts

Resolution

How Blend maps wallet addresses to accounts differs by SDK. Frontend (automatic): The publishable key identifies your organization and account type. SIWE proves wallet ownership. After sign-in, the account is implicit. Server (manual): The API key identifies your organization. The accountTypeId in the SDK config selects the product. You call sdk.lookupAccount(address) to resolve the account (creates it if new, but does not deploy a Safe). Then call sdk.forAccount(accountId) to scope operations, and safe.request(chainId) to deploy a Safe.

Safe submodule

Check and trigger Safe deployments on specific chains.
// Check if Safe is deployed on a chain
const result = await sdk.account.safe.resolve(8453);
resolve returns a discriminated union:
StatusMeaning
"validated"Safe exists on this chain. Returns accountId, userAddress, safeAddress, chainId.
"not-deployed"Safe hasn’t been deployed on this chain yet.
"invalid"Contract at the address is not a valid Safe.
"disconnected"Could not reach the chain RPC.
// Request deployment on a new chain
await sdk.account.safe.request(8453);
request is fire-and-forget. Deployment happens in the background.

Balance & positions

balance

Current aggregate balance with per-chain breakdown.
const balance = await sdk.account.balance();
FieldTypeDescription
accountIdstringBlend account ID
safeAddressstringOn-chain Safe address
perChainBalancePerChain[]Per-chain vault breakdown with fiat values
totalFiatAmountAggregate balance across all chains
heldAssetsHeldAsset[]Assets currently held in the Safe

balanceHistory

Time-series balance snapshots. Use startDate and endDate to filter.
const history = await sdk.account.balanceHistory({
  startDate: "2025-01-01",
  endDate: "2025-03-01",
});

positions

All position events (deposits, withdrawals, rebalances) sorted newest first.
const positions = await sdk.account.positions();
Returns deposit, withdrawal, and rebalance events with transaction hashes, amounts, and timestamps.

returns

Profit and loss metrics for the account.
const returns = await sdk.account.returns();
FieldTypeDescription
currentFiatAmountCurrent balance value
totalDepositedFiatAmountSum of all deposits
totalWithdrawnFiatAmountSum of all withdrawals
netDepositedFiatAmounttotalDeposited - totalWithdrawn
returnsFiatAmountcurrent - netDeposited
returnsPctnumberreturns / netDeposited * 100

Yield

Account-type-level yield data. Not per-account.
const yieldData = await sdk.discover.yield();
FieldTypeDescription
accountTypeIdstringThe account type
yieldBreakdownChainYieldBreakdown[]Per-chain APY breakdown with base and boosted rates

Error handling

SdkError

All SDK methods throw SdkError on failure.
import { SdkError } from "@blend-money/core";

try {
  await sdk.quoteDeposit({ /* ... */ });
} catch (error) {
  if (error instanceof SdkError) {
    error.status;           // HTTP status code (0 for network)
    error.code;             // machine-readable code string
    error.message;          // human-readable description
    error.response;         // raw API response body
    error.isRetryable();    // true for 429, 5xx, network errors
    error.getUserMessage(); // user-friendly message
  }
}

Error codes

Session errors:
CodeStatusMeaning
FLOWPLAN_CONFLICT409Rebalance in progress. Retry after it settles.
INTENT_NOT_FOUND404Session UUID not found.
INTENT_EXPIRED410Quote has expired. Re-quote.
INTENT_WRONG_STATUS409Session cannot be modified in current state.
INTENT_CONCURRENT_TRANSITION409Session status changed concurrently. Retry.
SESSION_NOT_QUOTED409Must quote before locking or executing.
SESSION_LOCKED_BY_OTHER409Locked by a different signer address.
SETTLEMENT_TIMEOUT408Polling exceeded timeout (default 5 min).
Auth errors:
CodeStatusMeaning
AUTH_NOT_SIGNED_IN401Not authenticated. Call signIn().
AUTH_CHALLENGE_FAILED500SIWE challenge request failed. Check publishable key.
AUTH_VERIFICATION_FAILED401Wallet signature verification failed.
AUTH_TOKEN_EXPIRED401JWT expired. Auto-refresh will attempt re-auth.
AUTH_INVALID_SESSION400Malformed session data in restoreSession().
General errors:
CodeStatusMeaning
NETWORK_ERROR0No response from server.
VALIDATION_ERROR400Client-side validation failed.
RATE_LIMITED / RATE_LIMIT429Too many requests.
NOT_FOUND404Resource not found.
TIMEOUT408Request timed out.
SERVER_ERROR500Internal server error.
NOT_IMPLEMENTED501Feature not available.

Amount utilities

Convert between human-readable amounts and smallest-unit strings.
import { parseAmount, formatAmount } from "@blend-money/core";

parseAmount("100.50", 6);   // "100500000"  (USDC: 6 decimals)
parseAmount("1.5", 18);     // "1500000000000000000"  (18 decimals)

formatAmount("100500000", 6);           // "100.5"
formatAmount("1500000000000000000", 18); // "1.5"
parseAmount rejects fractional digits exceeding the token’s decimal count.

Key types

SessionStatus

type SessionStatus = "OPEN" | "LOCKED" | "SUBMITTED" | "SETTLED" | "FAILED" | "CANCELLED";
Terminal states: SETTLED, FAILED, CANCELLED.

DepositQuote

type DepositQuote = {
  readonly type: "DEPOSIT";
  readonly intentId: string;
  readonly originChainId: number;
  readonly destinationChainId: number;
  readonly input: { readonly symbol: string; readonly amount: string; readonly amountUsd: string };
  readonly output: { readonly symbol: string; readonly amount: string; readonly amountUsd: string };
  readonly fees: { readonly totalUsd: string };
  readonly estimatedSeconds: number;
  readonly expiresAt: string;
};

WithdrawQuote

type WithdrawQuote = {
  readonly type: "WITHDRAW";
  readonly intentId: string;
  readonly destinationChainId: number;
  readonly totalAmount: string;
  readonly totalFeesUsd: string;
  readonly estimatedSeconds: number;
  readonly sourceChainCount: number;
  readonly sourceChainIds: readonly number[];
  readonly expiresAt: string;
};

ExecuteResult

type ExecuteResult = {
  status: "settled" | "failed" | "cancelled";
  txHashes: Array<{ hash: string; chainId: number }>;
  settledAt: string | null;
  error: string | null;
};

ActionPlan

type ActionPlan = {
  deployType: "direct" | "multisend";
  requiredApprovals: Txn[];
  requiredTxns: Txn[];
  chainId: number;
};
Deposits produce a single ActionPlan with deployType: "direct". Withdrawals produce an ActionPlan[] with deployType: "multisend".

BalanceResponse

type BalanceResponse = {
  accountId: string;
  safeAddress: string;
  perChain: BalancePerChain[];
  total: FiatAmount;
  heldAssets: HeldAsset[];
};

Frontend SDK

Frontend configuration, auth, and execution.

Server SDK

Server configuration, account management, and sessions.
Last modified on May 28, 2026