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/fe

v1.1.4

Published

Blend SDK for B2B neobank integrations.

Readme

Blend SDK

npm version npm downloads per week npm downloads per month

TypeScript SDK for the Blend Protocol. Frontend-first, wallet-authenticated via SIWE.

Install

pnpm add @blend-money/fe

Peer dependencies: viem

Quick Start

import { BlendSdk } from "@blend-money/fe";

const sdk = new BlendSdk({
  publishableKey: "pk_live_...",
  signMessage: walletClient.signMessage,
  paymasterRpcUrl: "https://your-bundler.example.com/v2/8453/rpc?apikey=...",
});

// 1. Discover what's available
const chains = await sdk.discover.depositChains();
const tokens = await sdk.discover.depositTokens(8453);

// 2. Sign in with SIWE
const session = await sdk.signIn({ address: "0x...", chainId: 8453 });

// 3. Get a quote
const quote = await sdk.quoteDeposit({
  chainId: 8453,
  tokenAddress: "0xA0b86991...",
  amount: "1000000",
});

// 4. Show confirmation UI
console.log(quote.input); // { symbol: "USDC", amount: "1000000", amountUsd: "1" }
console.log(quote.output); // { symbol: "USDC", amount: "1000000", amountUsd: "1" }
console.log(quote.fees); // { totalUsd: "0.01" }

// 5. Execute
const result = await sdk.execute(quote, {
  signerAddress: address,
  deriveSigner: async (chainId) => ({
    signer: getWalletClient({ chainId }),
    publicClient: getPublicClient({ chainId }),
  }),
  onStatusChange: (status) => console.log(status),
});

if (result.status === "settled") {
  console.log("Done!", result.txHashes);
}

Configuration

const sdk = new BlendSdk({
  // Required
  publishableKey: "pk_live_...", // Safe for client-side code
  signMessage: (msg) => wallet.sign(msg), // SIWE signature function

  // Required for execute()
  paymasterRpcUrl: "https://...", // Any ERC-4337 bundler/paymaster

  // Optional
  baseUrl: "https://...", // API base URL override
  fiatCurrency: "EUR", // ISO 4217 code for multi-currency
  timeoutMs: 15000, // Request timeout (default: 15000)
  retries: 3, // Max retries for 429/5xx (default: 3)
});

The paymasterRpcUrl is optional for read-only usage (discovery, account data) but required when calling sdk.execute().

API Reference

Authentication

// Sign in — wallet prompted to sign a SIWE message
const session = await sdk.signIn({ address: "0x...", chainId: 8453 });
// session: { address, accountId, safeAddress, chainsDeployed, expiresAt }

// Sign out
await sdk.signOut();

// Check state
sdk.isSignedIn; // boolean
sdk.session; // AuthSession (throws if not signed in)

Session Persistence

Sessions are in-memory by default and lost on page reload. Use exportSession() and restoreSession() to persist across reloads. Expired tokens are automatically refreshed on the next API call. Use sessionStorage (cleared when the tab closes) unless you need cross-tab persistence — localStorage extends XSS exposure to the token's full lifetime.

// After sign-in, save the session
await sdk.signIn({ address: "0x...", chainId: 8453 });
sessionStorage.setItem("blend-session", JSON.stringify(sdk.exportSession()));

// On page load, restore the session
const stored = sessionStorage.getItem("blend-session");
if (stored) {
  try {
    sdk.restoreSession(JSON.parse(stored));
    // sdk.isSignedIn === true — no wallet prompt needed
  } catch {
    // Corrupt data — fall back to signIn
    sessionStorage.removeItem("blend-session");
  }
}

Discovery

No authentication required. When signed in, depositTokens automatically filters to tokens the wallet holds.

const chains = await sdk.discover.depositChains();
// [{ chainId, name, displayName, iconUrl }]

const tokens = await sdk.discover.depositTokens(8453);
// [{ chainId, address, symbol, name, decimals, logoURI, price, balance?, amount? }]

const destinations = await sdk.discover.withdrawDestinations();
// [{ chainId, name, loanTokenAddress }]

const yieldData = await sdk.discover.yield();
// { accountTypeId, yieldBreakdown: [{ chainId, vaultAddress, breakdown, summary }] }

Amount Helpers

The SDK requires amounts in the token's smallest unit (e.g. "1000000" for 1 USDC). Use parseAmount and formatAmount to convert between human-readable and smallest-unit strings without floating-point precision loss.

import { parseAmount, formatAmount } from "@blend-money/fe";

// Human-readable → smallest unit
const smallest = parseAmount("100.50", 6); // "100500000" (USDC has 6 decimals)

// Smallest unit → human-readable
const human = formatAmount("100500000", 6); // "100.5"

// Use with discovered tokens
const tokens = await sdk.discover.depositTokens(8453);
const usdc = tokens.find((t) => t.symbol === "USDC")!;
const amount = parseAmount(userInput, usdc.decimals);
const quote = await sdk.quoteDeposit({
  chainId: 8453,
  tokenAddress: usdc.address,
  amount,
});

Quoting

Requires authentication. Creates or reuses a session, then quotes it. Returns a clean object for your confirmation UI.

// Deposit quote
const quote = await sdk.quoteDeposit({
  chainId: 8453,
  tokenAddress: "0xA0b86991...",
  amount: "1000000",
  externalRef: "order-123", // optional — for reconciliation
  forceReset: false, // optional — cancel existing session first (default: false)
});
// DepositQuote: { type, intentId, originChainId, destinationChainId,
//   input, output, fees, estimatedSeconds, expiresAt }

// Withdrawal quote
const quote = await sdk.quoteWithdraw({
  destinationChainId: 8453,
  amount: "1000000",
  isMaxWithdraw: false, // optional — redeem all shares (default: false)
  forceReset: false, // optional
});
// WithdrawQuote: { type, intentId, destinationChainId, totalAmount,
//   totalFeesUsd, estimatedSeconds, sourceChainCount, expiresAt }

Live Quoting

Calling quoteDeposit or quoteWithdraw multiple times re-quotes the same open session with the new parameters. No need for forceReset just to change the amount or token.

// User types $1
const quote1 = await sdk.quoteDeposit({
  chainId: 8453,
  tokenAddress: "0xA0b86991...",
  amount: parseAmount("1", 6),
});

// User changes to $5 — same session, fresh quote
const quote2 = await sdk.quoteDeposit({
  chainId: 8453,
  tokenAddress: "0xA0b86991...",
  amount: parseAmount("5", 6),
});
// quote2 reflects the updated amount — same intentId, fresh prices

// Switching from withdraw to deposit also works on open sessions
const depositQuote = await sdk.quoteDeposit({ ... });

Execution

Pass a quote, a signer address, and a deriveSigner callback that returns viem clients for a given chain. The SDK handles session locking, on-chain submission (Safe UserOps via the paymaster), hash submission, and settlement polling internally. For multi-chain withdrawals, deriveSigner is called once per source chain.

const result = await sdk.execute(quote, {
  signerAddress: address, // EOA address (Hex)
  deriveSigner: async (chainId) => ({
    // returns viem clients for the chain
    signer: getWalletClient({ chainId }),
    publicClient: getPublicClient({ chainId }),
  }),
  isContractSigner: false, // optional — for smart contract wallets (e.g. Coinbase)
  onStatusChange: (status) => {
    // optional — "executing" | "confirming" | "settled" | "failed"
    updateUI(status);
  },
  pollIntervalMs: 3000, // optional (default: 3000)
  pollTimeoutMs: 300000, // optional (default: 300000)
});
// ExecuteResult: { status, txHashes, settledAt, error }

Account Data

Requires authentication.

const balance = await sdk.account.balance();
// { accountId, safeAddress, perChain, total }

const history = await sdk.account.balanceHistory({ startDate: "2025-01-01" });
// [{ id, createdAt, totalValue, totalYield, breakdown }]

const positions = await sdk.account.positions();
// { accountId, safeAddress, events }

const returns = await sdk.account.returns();
// { current, totalDeposited, totalWithdrawn, netDeposited, returns, returnsPct }

Safe Management

// Resolve Safe deployment on a chain (requires auth)
const resolution = await sdk.account.safe.resolve(8453);
// { status: "validated", accountId, userAddress, safeAddress, chainId }

// Request Safe deployment (requires auth)
await sdk.account.safe.request(8453);

// Look up account by EOA (no auth required)
const account = await sdk.account.safe.lookup("0x...");
// { accountId, safeAddress, chainsDeployed }

Power-User: Session Lifecycle

For advanced use cases, the full intent session lifecycle is available via sdk.sessions:

const session = await sdk.sessions.createSession({ forceReset: true });
const quoted = await sdk.sessions.quoteDeposit(session.intentId, { ... });
const locked = await sdk.sessions.lock(session.intentId, { signerAddress: "0x..." });
const submitted = await sdk.sessions.submit(session.intentId, { txHashes: [...] });
const result = await sdk.sessions.execute(session.intentId, { ... });
const sessions = await sdk.sessions.list();
await sdk.sessions.cancel(session.intentId);

Error Handling

import { SdkError, SESSION_ERROR_CODES } from "@blend-money/fe";

try {
  await sdk.quoteDeposit({ ... });
} catch (err) {
  if (err instanceof SdkError) {
    console.log(err.code);            // e.g. "AUTH_NOT_SIGNED_IN"
    console.log(err.status);          // HTTP status
    console.log(err.getUserMessage()); // human-readable message
    console.log(err.isRetryable());   // true for 429, 5xx, network errors
  }
}

Error codes: AUTH_NOT_SIGNED_IN, AUTH_CHALLENGE_FAILED, AUTH_VERIFICATION_FAILED, AUTH_TOKEN_EXPIRED, AUTH_INVALID_SESSION, INTENT_NOT_FOUND, INTENT_EXPIRED, INTENT_WRONG_STATUS, INTENT_CONCURRENT_TRANSITION, SESSION_NOT_QUOTED, SESSION_LOCKED_BY_OTHER, SETTLEMENT_TIMEOUT, FLOWPLAN_CONFLICT.

Development

From the monorepo root:

pnpm install                              # Install all dependencies
pnpm --filter @blend-money/fe build      # Build this package
pnpm --filter @blend-money/fe dev        # Watch mode
pnpm --filter @blend-money/fe test       # Run tests (interactive)
pnpm --filter @blend-money/fe test:run   # Run tests (CI)
pnpm --filter @blend-money/fe check-types # Type check

Or run pnpm build, pnpm test:run, etc. from the monorepo root to run across all packages.

License

MIT