npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@blend-money/sdk

v1.1.1

Published

Blend SDK for B2B neobank integrations.

Readme

Blend SDK

npm version npm downloads per week npm downloads per month

Type-safe SDK for interacting with the Blend protocol. Built with TypeScript and designed for precision in financial calculations.

What's inside?

The SDK provides:

  • Integration API client: Type-safe access to Blend's B2B API (/extern/:neobankId/:accountTypeId/*)
  • On-chain actions: Withdrawals and rebalances from Morpho ERC-4626 vaults via Safe wallets
  • Transaction orchestration: Safe multisend and ERC-4337 UserOperation submission
  • Precise math: Uses Decimal.js for exact financial calculations
  • Isomorphic: Works natively in both browser and Node.js environments

Design Principles

  • TypeScript-first: 100% TypeScript with strict type checking
  • Decimal.js integration: Precise financial calculations without floating-point errors
  • Modular design: Clean separation of concerns with focused modules
  • Production-ready: Comprehensive error handling, retry logic, and validation

Quick Start

# Install dependencies
pnpm install

# Build
pnpm build

# Run development mode
pnpm dev

Development

Prerequisites

  • Node.js 20+
  • pnpm (recommended package manager)

Available Scripts

  • pnpm build - Build the package
  • pnpm dev - Start development mode with watch
  • pnpm test - Run tests
  • pnpm test:ui - Run tests in UI mode
  • pnpm test:coverage - Run tests with coverage
  • pnpm test:run - Run tests once in CI mode
  • pnpm format - Format with Prettier
  • pnpm check-types - Type-check the package

Features

  • TypeScript: Full type safety across the SDK
  • Decimal.js: Precise decimal arithmetic for financial calculations
  • Axios: Robust HTTP client with retry logic and error handling
  • ESM: Modern ES modules for better tree-shaking
  • Isomorphic: Works in both browser and Node.js environments

Usage

Installation

pnpm add @blend-money/sdk

Dependencies: axios, decimal.js, permissionless, viem, zod

Basic Usage

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"),
});

// Get yield information
const yieldData = await client.yield.get();

// Get or create a Safe account for an EOA
const account = await client.safe.account(userEoa);
const balance = await client.balance.get(account.accountId);

Testing

  • Vitest with Node environment and MSW for HTTP mocking
  • Coverage provider: v8; global thresholds are 100%

Technical Reference

Overview

The Blend SDK (@blend-money/sdk) is a B2B neobank integration SDK for the Blend protocol. It provides:

  • Integration API client : Type-safe access to Blend's B2B API (/extern/:neobankId/:accountTypeId/*)
  • On-chain actions : Withdrawals and rebalances from Morpho ERC-4626 vaults via Safe wallets
  • Transaction orchestration : Safe multisend and ERC-4337 UserOperation submission

Principles

  • Per-user parameters : EOA and Safe addresses are passed as method arguments, never stored in config
  • Single config instance : Neobanks create one BlendClient with credentials and RPC transports
  • Isomorphic : Works in Node.js and browser environments

Architecture

flowchart LR
    Neobank(["Neobank App"])

    subgraph sdk ["Blend SDK"]
        direction TB
        BC["BlendClient"]
        IM["Integration API"]
        AM["On-chain Actions"]
        BC --> IM
        BC --> AM
    end

    subgraph withdraw ["On-chain"]
        Safe["Safe Wallet (ERC-4337)"]
        Vault["ERC-4626 Vault"]
        Safe --> Vault
    end

    Neobank --> BC
    IM -->|"REST + X-API-Key"| BlendAPI(["Blend API"])
    AM -->|"calldata via Blend API"| Safe

Module Layout

| Path | Purpose | | -------------------------------------- | ------------------------------------------------------------ | | src/blend-client.ts | Main client, wires all domain modules | | src/modules/ | One class per domain: safe, balance, deposit, withdraw, etc. | | src/utils/integration-http-client.ts | Axios client with retry and auth | | src/utils/safe.ts | Safe multisend via ERC-4337 / Pimlico | | src/utils/transaction.ts | TransactionHandler for submitting action plans | | src/utils/chain.ts | Chain utilities (waitForNextBlock) |


Configuration

BlendClientConfig is passed to BlendClient once per neobank:

| Field | Type | Required | Description | | -------------------- | --------------------------- | -------- | ----------------------------------------------------------------- | | baseUrl | string | No | Blend portal API base (default: https://api.portal.blend.money) | | apiKey | string | Yes | Sent as X-API-Key header | | neobankId | string | Yes | Path segment: /extern/:neobankId/ | | accountTypeId | string | Yes | Path segment: /extern/:neobankId/:accountTypeId/ | | transports | Record<number, Transport> | Yes | Viem transports keyed by chain ID | | paymasterTransport | Transport | Yes | Pimlico or similar bundler transport | | timeoutMs | number | No | Request timeout (default: 15000) | | retries | number | No | Max retries for retryable errors (default: 3) |

All HTTP requests to the integration API use:

baseUrl/extern/{neobankId}/{accountTypeId}/<route>

Integration API

The SDK exposes B2B endpoints under /extern/:neobankId/:accountTypeId/* as flat namespaces on BlendClient.

Safe Operations

| Method | Description | | --------------------------------------- | --------------------------------------------------------------------------------------- | | client.safe.account(address) | Get or create account for an EOA. Returns accountId, safeAddress, chainsDeployed. | | client.safe.resolve(address, chainId) | Resolve and validate a Safe for an EOA on a given chain | | client.safe.request(address, chainId) | Request creation of a new Safe wallet. Returns { message: string } |

The account method is usually the first call: its accountId is needed for balance, positions, and returns.

Balance

| Method | Description | | ----------------------------------------------- | ------------------------------------------------------------------ | | client.balance.get(accountId) | Aggregate balance per chain and total USD | | client.balance.getHistory(accountId, params?) | Historical balance snapshots. params: { startDate?, endDate? } |

Positions, Returns, Yield

| Method | Description | | --------------------------------- | ------------------------------------------------------------------------- | | client.positions.get(accountId) | Position events (deposits, withdrawals, rebalances) | | client.returns.get(accountId) | Returns summary (deposited, withdrawn, net, returns USD/%) | | client.yield.get() | Yield breakdown by configured vault strategy for the current account type |

Rebalance API

| Method | Description | | ---------------------------------------------- | ------------------------------------- | | client.rebalance.getCandidates() | Accounts with rebalance opportunities | | client.rebalance.createRequest(requestData?) | Create a rebalance request | | client.rebalance.getRequest(id) | Fetch rebalance request by ID |

Deposit

| Method | Description | | ---------------------------------------------- | -------------------------------------------------- | | client.deposit.getTokens(chainId?, address?) | Token catalog. Returns { chains, tokens } | | client.deposit.getBalances(eoa, chainId) | Non-zero ERC-20 balances for an EOA on a chain | | client.deposit.getQuote(params) | Quote for deposit. Requires accountId in params. |


Withdraw : Server-Built Calldata

The withdraw module requests pre-built calldata from the server. The server selects source chains, encodes all Safe transactions, and returns an ordered payload set for the neobank to submit.

const result = await client.withdraw.getCalldata({
  address: userEoa,
  destinationChainId: 8453,
  amount: "1000000000000000000",
  // isMaxWithdraw: true  // redeems all shares on every chain
});

for (const payload of result.payloads) {
  if (payload.liquidityReset) {
    // submit as delegatecall: payload.liquidityReset.target / .data
  }
  for (const txn of payload.withdraw) {
    // submit in order: txn.target / txn.data
  }
  if (payload.bridge) {
    // execute Relay bridge quote to move funds to result.destinationChainId
  }
}

Returns 409 if a rebalance flow plan is already active for the account.


Transaction Execution

TransactionHandler

import { TransactionHandler } from "@blend-money/sdk";

const handler = new TransactionHandler(paymasterTransport);
  • Purpose: Executes action plans with a Safe and RPC clients
  • Dependencies: Needs paymasterTransport (e.g. Pimlico) for UserOperations

submitActionPlan

const { receipts, approvals } = await handler.submitActionPlan(
  signer,
  client,
  safeAddress,
  actionPlans,
  options?
);
  • signer: Viem WalletClient (signs UserOperations)
  • client: Viem PublicClient (same chain as action plans)
  • safeAddress: Target Safe
  • actionPlans: Array of ActionPlan or () => Promise<ActionPlan> (lazy)
  • options.isContractSigner: Set true when signer is a contract (e.g. Coinbase Smart Wallet) to use EIP-1271 signatures

Behavior:

  • All action plans must use the same chain as client.chain.id
  • For each plan:
    • If deployType === "multisend": sends a UserOperation via SafeMultisendManager.submitMultisend()
    • Otherwise: sends approvals and main transactions directly with signer.sendTransaction()
  • Between plans, waits for the next block via waitForNextBlock()

submitEnableModuleTransaction

await handler.submitEnableModuleTransaction(
  chain,
  signer,
  client,
  safeAddress,
  moduleAddress,
);

Submits a UserOperation that calls enableModule on the Safe to enable a module.


SafeMultisendManager

Located in src/utils/safe.ts. Uses ERC-4337 (EntryPoint 0.7) and Pimlico:

  • Safe 1.4.1 : Uses SafeSmartAccount from permissionless
  • MultiSend : Batches calls via 0x38869bf66a61cF6bDB996A6aE40D5853Fd43B526
  • Contract signer support : EIP-712 SafeOp with EIP-1271 contract signatures for smart wallet signers

Action Plans

type ActionPlan = {
  deployType: "direct" | "multisend";
  requiredApprovals: Txn[];
  requiredTxns: Txn[];
  chainId: ChainId;
};

type Txn = {
  to: Hex;
  data: Hex;
  value: bigint;
  chainId: ChainId;
  account: Hex;
  isDelegateCall?: boolean;
};
  • deployType: "multisend" → execute via Safe multisend; "direct" → send each transaction separately
  • requiredApprovals: ERC-20 approvals or other prerequisite txns
  • requiredTxns: Main execution transactions
  • chainId: Chain where all transactions execute

combineActionPlans

import { combineActionPlans } from "@blend-money/sdk";

const combined = combineActionPlans([plan1, plan2, plan3]);

Merges multiple plans on the same chain. All inputs must share the same chainId. The result uses deployType: "multisend" if any input does.


Type System

Core Types

| Type | Description | | ------------------- | ---------------------------------------- | | Hex | 0x${string} for addresses and calldata | | ChainId | number | | BlendClientConfig | Client configuration | | Txn | Single transaction payload | | ActionPlan | Plan of approvals and transactions |

Domain Types

| Type | Description | | ----------------- | ----------------------------------------------- | | Token | address, symbol, decimals, chainId | | VaultConfig | vaultId, chainId, token, name, symbol | | TokenAmount | value (bigint), decimals | | AmountWithToken | amount + token |

Integration Types

Defined in src/types/integration.ts and aligned with API responses, e.g. SafeAccountResponse, BalanceResponse, PositionsResponse, ReturnsResponse, YieldResponse, ChainYieldBreakdown, YieldBreakdownSummary, CandidatesResult, RebalanceCandidate, RebalanceRequest, TokenCatalog, TokenCatalogChain, DepositToken, DepositBalance, DepositQuoteParams, and DepositQuoteResponse.


Error Handling

import { SdkError } from "@blend-money/sdk";

SdkError properties: message, status, code, response.

Static constructors:

  • SdkError.notImplemented(feature)
  • SdkError.networkError(message, originalError?)
  • SdkError.validationError(message, field?)
  • SdkError.rateLimited(retryAfter?)
  • SdkError.notFoundError(message)
  • SdkError.serverError(message)
  • SdkError.timeout(timeoutMs)
  • SdkError.fromAxiosError(error) : maps HTTP errors to SdkError

Instance methods:

  • isRetryable() : true for 429, 5xx, or network failures
  • getUserMessage() : human-readable message
  • toString() : structured string representation

HTTP Client & Retry

createIntegrationHttpClient():

  • Uses baseUrl/extern/{neobankId}/{accountTypeId}
  • Adds X-API-Key header
  • Retries on 429, 500, 502, 503, 504 with exponential backoff
  • Configurable timeoutMs and retries
  • Maps errors via SdkError.fromAxiosError()

Utilities

| Export | Purpose | | -------------------- | ------------------------------------ | | createHttpClient | Generic HTTP client factory | | combineActionPlans | Merge action plans on the same chain | | SdkError | Error type and factory methods |

Validation (Zod-based): validateAddress, validateChainId, validateAmount, validateToken, validateTokenAmount, validateVaultConfig.

Token helpers: areTokensEqual, isNativeToken, validateToken, isValidToken, validateTokenAmount, isValidTokenAmount.


Usage Examples

1. Get or Create Account and Balance

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

2. Withdraw Flow

const result = await client.withdraw.getCalldata({
  address: userEoa,
  destinationChainId: 8453,
  amount: "1000000000000000000",
});

for (const payload of result.payloads) {
  if (payload.liquidityReset) {
    // submit liquidityReset as delegatecall via your relay
  }
  for (const txn of payload.withdraw) {
    // submit each txn in order via your relay
  }
  if (payload.bridge) {
    // execute Relay bridge quote
  }
}

3. Deposit Quote

const account = await client.safe.account(userEoa);

const quote = await client.deposit.getQuote({
  chainId: 8453,
  inputAssetAddress: usdcAddress,
  eoa: userEoa,
  accountId: account.accountId,
  amount: "1000000",
});
// Pass quote to your bridge execution UI

4. Trigger Rebalance (async, scheduler-driven)

const request = await client.rebalance.createRequest();
// Poll status:
const status = await client.rebalance.getRequest(request.requestId);

Integrations Enum

import { Integrations } from "@blend-money/sdk";
// Integrations.MESA_CAPITAL
// Integrations.USX_MESA_CAPITAL

Used to identify available strategy platforms when resolving vault strategies.


Quick Reference

| Task | Method / Export | | --------------------- | -------------------------------------------------------------- | | Get account for EOA | client.safe.account(address) | | Get balance | client.balance.get(accountId) | | Deposit quote | client.deposit.getQuote(params) | | Withdraw calldata | client.withdraw.getCalldata(params) | | Trigger rebalance | client.rebalance.createRequest() | | Submit action plans | handler.submitActionPlan(signer, client, safeAddress, plans) | | Enable module on Safe | handler.submitEnableModuleTransaction(...) | | Combine plans | combineActionPlans(plans) | | Map HTTP errors | SdkError.fromAxiosError(error) |


Contributing

  1. Fork the repository
  2. Create a feature branch: git checkout -b feature/amazing-feature
  3. Make your changes and add tests
  4. Run the test suite: pnpm test
  5. Commit your changes: git commit -m 'Add amazing feature'
  6. Push to the branch: git push origin feature/amazing-feature
  7. Open a Pull Request

License

MIT License - see LICENSE file for details.