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

@concrete-xyz/sdk

v2.1.0

Published

Concrete SDK for interacting with vault contracts

Readme

@concrete-xyz/sdk

SDK for interacting with vault contracts on multiple EVM networks.

Read the 1.x migration guide before upgrading to 2.x.

⚠️ Beta Warning: This package is currently in closed beta. If you encounter any issues, please contact us for support.

Installation

The SDK requires Node 20.10 or later.

Core SDK

npm install @concrete-xyz/sdk viem@^2

viem ^2 is a peer dependency: the SDK takes viem PublicClient and WalletClient instances and does not bundle viem.

React Integration

npm install @concrete-xyz/sdk viem@^2
# Then import from the react subpath
import { useVault } from "@concrete-xyz/sdk/react";

Wagmi Integration

npm install @concrete-xyz/sdk viem@^2 wagmi@^2 @tanstack/react-query@^5
# Then import from the wagmi subpath
import { useVault, useVaultQuery } from "@concrete-xyz/sdk/wagmi";

viem and wagmi are both ^2. wagmi ^3 is not supported.

react (^18 || ^19), wagmi and @tanstack/react-query are optional peer dependencies, needed only for the subpath that uses them. Keep a single copy of each at the top level of your app — two copies of React break hooks, and two copies of wagmi split the WagmiProvider context.

Error reporting

The SDK reports nothing by default. Register a sink to receive instrumented errors:

import { setErrorReporter } from "@concrete-xyz/sdk";

setErrorReporter((error, report) => Sentry.captureException(error, report));

Quick Start

1. Core SDK (Vanilla JavaScript/TypeScript)

For direct usage without React or Wagmi:

import { getVault } from "@concrete-xyz/sdk";
import { createPublicClient, http } from "viem";
import { mainnet } from "viem/chains";

// Create public client
const publicClient = createPublicClient({ chain: mainnet, transport: http("YOUR_RPC_URL") });

// Create vault instance
const vault = getVault(
  "v1", // vault version ("v1" or "v2")
  "0x15cE9bE6609db102b70D68ca75a39c555bEa5Fac", // vault address
  1, // chainId
  publicClient, // viem PublicClient for read operations
);

// Get vault details
const vaultDetails = await vault.getVaultDetails();
console.log("Vault Symbol:", vaultDetails.symbolDetails);

React Integration

For React applications, the SDK provides a custom hook useVault that integrates seamlessly with your existing viem clients.

Key Features:

  • Takes explicit viem client instances: a PublicClient for reads and a WalletClient for writes
  • Full control over network configuration
  • Integrates with any React state management solution

React: useVault(version, address, chainId, publicClient, walletClient)

The React hook for interacting with vault contracts. Takes the same arguments as getVault and memoizes the instance.

Parameters:

  • version ("v1" | "v2"): The vault contract version to use
  • address (string): The vault contract address
  • chainId (number): The blockchain network chain ID
  • publicClient (PublicClient, optional): viem public client for read operations
  • walletClient (WalletClient, optional): viem wallet client with an account for write operations

Returns: Vault instance with all available methods

Usage Example:

The wallet client is created after mount and stored in state. Without an injected provider, the vault remains available for read operations. Replace 0xYOUR_ADDRESS with a connected account.

"use client";

import { useEffect, useState } from "react";
import { useVault } from "@concrete-xyz/sdk/react";
import { createPublicClient, createWalletClient, custom, http, type WalletClient } from "viem";
import { mainnet } from "viem/chains";

import "viem/window";

const publicClient = createPublicClient({ chain: mainnet, transport: http("YOUR_RPC_URL") });

export default function VaultComponent() {
  const [walletClient, setWalletClient] = useState<WalletClient>();

  useEffect(() => {
    if (!window.ethereum) return;

    setWalletClient(
      createWalletClient({
        account: "0xYOUR_ADDRESS",
        chain: mainnet,
        transport: custom(window.ethereum),
      }),
    );
  }, []);

  const vault = useVault("v1", "0x15cE9bE6609db102b70D68ca75a39c555bEa5Fac", 1, publicClient, walletClient);

  return <div>Vault: {vault.getAddress()}</div>;
}

Wagmi Integration

For applications using Wagmi, the SDK provides a simplified hook that automatically uses Wagmi's context.

Key Features:

  • Automatic public and wallet client detection
  • Seamless integration with Wagmi's ecosystem
  • Minimal configuration required
  • Built-in TanStack Query integration with useVaultQuery
  • Automatic caching and state management

Wagmi: useVault(config)

The Wagmi hook for interacting with vault contracts. Automatically uses Wagmi's configured public and wallet clients.

Parameters:

  • config (object):
    • version ("v1" | "v2"): The vault contract version to use
    • address (string): The vault contract address
    • chainId (number): The blockchain network chain ID
    • fallbackRpcUrl (string, optional): Fallback RPC URL if wallet is not connected

Returns: Vault instance with all available methods

Usage Example:

import { useVault } from "@concrete-xyz/sdk/wagmi";

export default function VaultComponent() {
  const vault = useVault({
    version: "v1",
    address: "0x585934AfBf1FA9f563b80283F8B916Dd8F66a9b6",
    chainId: 80084, // Berachain
  });

  // Use vault methods
  const vaultDetails = await vault.getVaultDetails();

  return <div>Vault: {String(vault)}</div>;
}

Wagmi: useVaultQuery(options)

The Wagmi hook for querying vault data with automatic caching and state management. Built on top of TanStack Query for optimal performance.

Parameters:

  • options (object):
    • vault (object): Vault configuration
      • version ("v1" | "v2"): The vault contract version to use
      • address (string): The vault contract address
      • chainId (number): The blockchain network chain ID
      • fallbackRpcUrl (string, optional): Fallback RPC URL if wallet is not connected
    • queryKey (array): Additional key segments for TanStack Query caching
    • queryFn (function): Function that receives the vault instance and returns the data to query
    • ...other TanStack Query options

Returns: TanStack Query result object with data, isLoading, error, etc.

Usage Example:

import { useVaultQuery } from "@concrete-xyz/sdk/wagmi";

export default function VaultComponent() {
  const vaultQuery = useVaultQuery({
    vault: {
      version: "v1",
      address: "0x585934AfBf1FA9f563b80283F8B916Dd8F66a9b6",
      chainId: 80084, // Berachain
    },
    queryKey: ["vaultDetails"],
    queryFn: async (vault) => await vault.getVaultDetails(),
  });

  return (
    <div>
      <pre>
        {JSON.stringify(vaultQuery.data, null, 2)}
      </pre>
    </div>
  );
}

Hook Comparison

Key Differences:

  • React version: Accepts optional publicClient and walletClient instances as separate parameters
  • Wagmi version: Uses a config object and automatically uses Wagmi's configured public and wallet clients
  • Wagmi version: Includes additional useVaultQuery hook for TanStack Query integration
  • Wagmi version: Supports fallbackRpcUrl for scenarios when the wallet is not connected
  • Both versions: Require vault version ("v1" or "v2") and return the same vault instance with identical methods

Vanilla Examples

1. Get Vault Data

import { getVault } from "@concrete-xyz/sdk";
import { createPublicClient, http } from "viem";
import { mainnet } from "viem/chains";

const publicClient = createPublicClient({ chain: mainnet, transport: http("YOUR_RPC_URL") });
const vault = getVault("v1", "0x15cE9bE6609db102b70D68ca75a39c555bEa5Fac", 1, publicClient); // chainId 1 = Ethereum

// Get complete vault information
const vaultDetails = await vault.getVaultDetails();
console.log("Vault Symbol:", vaultDetails.vaultAsset.symbol);
console.log("Underlying Asset:", vaultDetails.underlying.symbol);

2. Preview Deposit

const depositAmount = 10n ** BigInt(await vault.getUnderlyingDecimals());
const vaultTokensReceiving = await vault.getPreviewDeposit(depositAmount);

console.log(`Input: ${await vault.toUnderlyingDecimals(depositAmount)} ${vaultDetails.underlying.symbol}`);
console.log(`Output: ${vaultTokensReceiving} ${vaultDetails.vaultAsset.symbol}`);

getPreviewDeposit resolves a number on v1 and a bigint on v2. Read the value through String() or narrow on the version if you support both.

3. Execute Deposit

import { createWalletClient } from "viem";
import { privateKeyToAccount } from "viem/accounts";

// You need a wallet client with an account for write operations
const walletClient = createWalletClient({
  account: privateKeyToAccount("0xYOUR_PRIVATE_KEY"),
  chain: mainnet,
  transport: http("YOUR_RPC_URL"),
});
const vaultWithWallet = getVault("v1", "0x15cE9bE6609db102b70D68ca75a39c555bEa5Fac", 1, publicClient, walletClient); // chainId 1 = Ethereum

// Approve tokens first
const underlying = await vaultWithWallet.getUnderlyingErc20();
const approveTx = await underlying.approve(vaultWithWallet.getAddress(), depositAmount);
await approveTx.wait();

// Execute deposit
const depositTx = await vaultWithWallet.deposit(depositAmount);
const receipt = await depositTx.wait(); // viem TransactionReceipt; throws if the transaction reverted
console.log("Deposit successful:", receipt.transactionHash);

4. Execute Withdrawal

const withdrawAmount = 10n ** BigInt(await vault.decimals());
const underlyingReceiving = await vault.getPreviewRedeem(withdrawAmount);

console.log(`Input: ${await vault.applyDecimals(withdrawAmount)} ${vaultDetails.vaultAsset.symbol}`);
console.log(`Output: ${underlyingReceiving} ${vaultDetails.underlying.symbol}`);

// Execute withdrawal (using the same wallet client from the previous example)
const withdrawTx = await vaultWithWallet.redeem(withdrawAmount);
const receipt = await withdrawTx.wait();
console.log("Withdrawal successful:", receipt.transactionHash);

4.1 Withdrawal queue (v2)

V2 vaults use an epoch-based withdrawal queue. After requesting a withdrawal, funds are processed per epoch; when the epoch is finalized you can claim underlying, or cancel while the epoch is still active/inactive.

const vault = getVault("v2", vaultAddress, chainId, publicClient, walletClient);
const requests = await vault.getAllWithdrawQueueRequests(walletClient.account.address);
// Each request: { amount, epoch, epochState, claimable, cancelable, claim(), cancel(), ... }

Claim (when request.claimable is true):

const request = requests.find((r) => r.claimable);
if (request) {
  const tx = await request.claim();
  await tx.wait();
}

Cancel (when request.cancelable is true):

const request = requests.find((r) => r.cancelable);
if (request) {
  const tx = await request.cancel();
  await tx.wait();
}

5. Get APY Details

Use expectedApy for live APY and apy for historical APY. Both are decimal strings: "0.085" represents 8.5%. When expectedApy is missing, display the live rate as unavailable. A returned "0" is a valid rate.

import { getVault } from "@concrete-xyz/sdk";

const vault = getVault("v2", vaultAddress, chainId, publicClient);

const apyDetails = await vault.getApyDetails();
console.log({
  liveApy: apyDetails.expectedApy,
  liveApy7Days: apyDetails.expectedApy7Days,
  historicalApy: apyDetails.apy,
  asOf: apyDetails.timestamp,
});

The API returns a snapshot with a timestamp. The SDK passes through its fee treatment, so confirm that treatment before labeling a rate as net of fees. Concrete's live APY UI uses expectedApy; CMS overrides and display rounding remain outside the SDK.

The same live fields are available in getConcreteApi().apy.getAllVaultsApy(), indexed by chain ID and lowercase vault address.

6. Get Oracle Price

import { getVault } from "@concrete-xyz/sdk";
import { createPublicClient, http } from "viem";
import { arbitrum } from "viem/chains";

const publicClient = createPublicClient({ chain: arbitrum, transport: http("https://arb1.arbitrum.io/rpc") });

const vault = getVault("v1", "0xE2d8267D285a7ae1eDf48498fF044241d04e9608", 42161, publicClient); // chainId 42161 = Arbitrum

// Get underlying asset price from oracle
const underlyingPrice = await vault.getUnderlyingPrice();
console.log("Underlying Price:", underlyingPrice);

7. Read the V2 deposit cooldown

A deposit cooldown locks newly deposited shares for a configured period. Resolve the vault's DepositLockWithFeeHook to read that duration in seconds:

const lock = await vault.getDepositLockWithFeeHook();
const cooldownSeconds = lock ? await lock.depositLockDuration() : undefined;

An unresolved hook means this adapter cannot provide cooldown information. A nonzero duration requires the relevant deposit/mint hook flags to enforce locking. A zero duration disables locks on new deposits, but existing locks can remain active.

For async vaults, the Withdrawal Queue processes requests in separate accounting periods called Epochs. The deposit cooldown alone does not determine when a withdrawal pays out.

| Read | Method | Result | | ----------------- | ------------------------------------- | -------------------------------------------------------------- | | Locked shares | effectiveTotalLocked(account) | Shares still locked | | Unlocked shares | getUnlockedShares(account) | Shares available without early unlocking | | Stored lock count | storedLockCount(account) | Number of stored records, including expired locks | | Individual lock | getStoredLock(account, index) | { shares, unlockTimestamp, duration }, with times in seconds | | Early unlock fee | previewEarlyUnlock(account, shares) | Fee in vault-share base units |

Read earlyUnlockEnabled() to check whether early unlocking is allowed. getEnabledDetails().enabled describes early unlocking with a fee; use the lock duration and hook flags to assess deposit locking.

8. Read the V2 TVL cap and remaining capacity

The deposit cap limits the total assets the vault accepts across all depositors. Read the cap and remaining capacity in underlying-token units:

import { formatUnits, maxUint256 } from "viem";

const [capRaw, minimumDepositRaw] = await vault.getDepositLimits();
const remainingCapacityRaw = await vault.maxDeposit(receiverAddress);
const decimals = await vault.getUnderlyingDecimals();

console.log({
  cap: capRaw === maxUint256 ? "unlimited" : formatUnits(capRaw, decimals),
  minimumDeposit: formatUnits(minimumDepositRaw, decimals),
  remainingCapacity: formatUnits(remainingCapacityRaw, decimals),
});

Keep raw bigint values for calculations and use underlying-token decimals for display. The cap is denominated in the underlying token, rather than USD. A zero cap disables deposits. maxDeposit(receiverAddress) returns zero when deposits are paused, the cap is reached, or remaining capacity is below the minimum deposit.

getWithdrawLimits() instead returns maximum and minimum per-transaction withdrawal amounts. For deposits routed through additional hooks, including multi-asset deposits, check those hooks' restrictions separately.

9. Get a withdrawal forecast

getWithdrawalForecast reads the ordinary v2 async Withdrawal Queue forecast from the public API. It returns the schedule, queue pressure, receiver positions, forecast portions, and current claimable assets. No RPC client or wallet is required.

import { getVault, type WithdrawalForecast } from "@concrete-xyz/sdk";

const vault = getVault("v2", vaultAddress, chainId, undefined, undefined, true);
const current: WithdrawalForecast = await vault.getWithdrawalForecast({ account });
const preview = await vault.getWithdrawalForecast({ account, additionalShares: 1_000_000n });

account is the withdrawal receiver. A preview adds positive additionalShares to that receiver's existing requests and assumes the caller, share owner, and receiver are all account. An ineligible preview does not remove existing requests or claims.

Amounts are bigint in share or underlying-asset base units. Use shareDecimals and assetDecimals to format them. Epoch IDs and block numbers are also bigint. Timestamps are UTC ISO 8601 strings. snapshot.maxAgeSeconds is in seconds. policy.thresholdPercent retains the exact decimal percentage as a string.

| Field | Use | | ------------------------------- | ------------------------------------------------------------------------------------------------ | | status, reasons, snapshot | Check availability and source freshness before displaying estimates. | | schedule | Distinguish scheduled close/process times from observed Epoch state. | | pressure | Read the separate cap and idle-liquidity budgets, queued shares, and capacity for a new request. | | existing | Read outstanding requests and their forecast portions. Processed claims are separate. | | claimable | Read assets and Epochs currently available to claim. | | preview | Check eligibility and the forecast for existing requests plus the proposed self-request. |

Dates are conditional estimates, not guaranteed payment times. A portion with outcome direct_payment pays assets directly. A claim portion requires a later claim after processing. A scheduled date alone does not enable a claim. Check assumptions and unallocatedSharesRaw; fullAvailableAt is null when the forecast cannot allocate the full amount.

Preserve partial, unavailable, and unsupported results and their reasons. A null amount is unknown, not zero. The request aborts after 30 seconds. Network, API and timeout errors reject the call. See recipe 09 for a runnable read and optional preview.

Supported Networks

The SDK supports multiple EVM networks including: Ethereum, Arbitrum, Morph, Berachain, and Katana.

Network Types:

  • Mainnet: Ethereum, Arbitrum
  • Testnet: Morph, Berachain, Katana

Note: Network support may vary based on your specific deployment. Always verify network compatibility before production use.

API Reference

getVault(version, address, chainId, publicClient?, walletClient?, silent?)

Creates a new Vault instance for interacting with vault contracts.

Parameters:

  • version ("v1" | "v2"): The vault contract version to use.
  • address (string): The vault contract address
  • chainId (number): The blockchain network chain ID
  • publicClient (PublicClient, optional): viem public client for read operations. Without it, on-chain reads and writes are unavailable; API-backed methods such as getApyDetails() still work
  • walletClient (WalletClient, optional): viem wallet client with an account for write operations
  • silent (boolean, optional): Suppress the warning logged when no publicClient is passed

Returns: Vault instance

Vault Methods

The vault is an abstraction of a ERC-4626 Tokenized Vault, which in turn is an ERC-20 token that represents shares of underlying assets.

Technically, all ERC-4626 vault methods are available, but the SDK provides a simplified interface focusing on the most commonly used operations. Some less frequently used methods may not be directly exposed through the SDK wrapper.

Commonly used methods (examples):

  • getVaultDetails(): Get complete vault information
  • previewConversion(amount): Preview deposit/withdrawal conversion
  • deposit(amount): Deposit underlying tokens for vault tokens
  • redeem(amount): Redeem vault tokens for underlying tokens
  • approve(spender, amount): Approve token spending
  • totalAssets(): Get total assets in vault
  • symbol(): Get vault symbol

Write methods resolve { hash, wait }. wait(confirmations?) resolves the viem TransactionReceipt and throws when the transaction reverted.

Using ABI Directly

The SDK ships contract ABIs under two wildcard subpaths:

| Subpath | Form | Use it for | | ------------------------------------------- | ----------------------------------------- | ------------------------------------------------------------------------------------------------ | | @concrete-xyz/sdk/abi/<contract> | Typed viem ABI (as const satisfies Abi) | TypeScript. Literal types survive, so viem infers function names, arguments, and return types. | | @concrete-xyz/sdk/raw-abi/<contract>.json | Standard JSON ABI array | Non-TypeScript consumers, other libraries (ethers, web3.py), codegen, and contract verification. |

Prefer the typed subpath in TypeScript. A JSON import widens the literal types to string, and viem then loses all inference.

import abi from "@concrete-xyz/sdk/abi/vault";
import rawAbi from "@concrete-xyz/sdk/raw-abi/vault.json" with { type: "json" };

// Typed: viem infers "totalAssets" and its return type from the ABI.
const total = await publicClient.readContract({ address, abi, functionName: "totalAssets" });

// Raw: a plain JSON ABI array, for tooling that does not read TypeScript types.
console.log(rawAbi);

The typed abi/* subpath covers the contracts the SDK compiles:

asset-pricer, hook-abstract, hook-container, hook-deposit-lock-with-fee, hook-multi-asset-deposit-cap, hook-whitelist-user-deposit, hurdle-rate-oracle, hurdle-rate-oracle-offchain, multicall, vault, vault-v2-async, weth, withdraw-queue

The raw raw-abi/*.json subpath covers those and four more:

oapp, strategy, vault-distributor, vault-registry

The with { type: "json" } attribute needs Node 20.10 or later. In TypeScript, set "module" to "nodenext", "esnext", or "preserve" to use it. The typed subpath needs neither.

Migrating direct ABI imports

Direct ABI imports use the two wildcard subpaths described in Using ABI Directly. Earlier releases exposed the internal build paths instead; those specifiers no longer resolve.

Before:

import abi from "@concrete-xyz/sdk/dist/src/core/contracts/abi/vault.mjs";
import rawAbi from "@concrete-xyz/sdk/dist/src/core/contracts/raw-abi/vault.json" with { type: "json" };

After:

import abi from "@concrete-xyz/sdk/abi/vault";
import rawAbi from "@concrete-xyz/sdk/raw-abi/vault.json" with { type: "json" };

The same change applies to each contract, not only vault. Four contracts have no typed subpath. The ABI contents are unchanged.

Migrating from 1.x

Version 2.0.0 is built on viem and declares viem ^2 as a peer dependency. Install it next to the SDK.

npm install @concrete-xyz/sdk@^2 viem@^2

getVault and React useVault arguments

The provider and signer arguments of 1.x are replaced by viem clients: a PublicClient for reads and a WalletClient (with an account) for writes. version, address, chainId, and silent are unchanged.

// 1.x
const vault = getVault("v1", address, 1, provider, signer);

// 2.x
const publicClient = createPublicClient({ chain: mainnet, transport: http(rpcUrl) });
const walletClient = createWalletClient({ account, chain: mainnet, transport: http(rpcUrl) });
const vault = getVault("v1", address, 1, publicClient, walletClient);

The Wagmi hooks need no code change: useVault resolves both clients from your Wagmi config, and fallbackRpcUrl now creates a viem public client over http(fallbackRpcUrl).

If you update clients on an existing vault instance, replace vault.updateProviders(provider, signer) with vault.updateClients(publicClient, walletClient).

Oracle price arguments

getConcreteApi().oracle.getPrice(quote, symbol, chainId) is replaced by getConcreteApi().oracle.getPrice(tokenAddress, chainId). Use the underlying token's address instead of its symbol. vault.getUnderlyingPrice() now takes no quote argument and resolves the underlying address itself.

Live APY, cooldown, and caps

Use the APY example to select expectedApy for live rates. The cooldown example covers getDepositLockWithFeeHook(), available in 2.0.0. Existing deposit-cap methods remain available as shown in the TVL cap example.

Direct ABI imports

Replace @concrete-xyz/sdk/dist/src/core/contracts/abi/vault.json with @concrete-xyz/sdk/abi/vault. The default export is now a typed viem ABI array. Remove any JSON import assertion or attribute from this import. The raw JSON is still available, as @concrete-xyz/sdk/raw-abi/vault.json. See Using ABI Directly.

Transaction receipts

Write methods still resolve { hash, wait }, and wait(confirmations?) still throws when the transaction reverted. The value wait() resolves is now a viem TransactionReceipt; the Wagmi mutation hooks (deposit, withdraw, queue.action) expose the same receipt as data. wait() has no receipt timeout.

| 1.x | 2.x | | -------------------------------- | ---------------------------------------------- | | receipt.hash | receipt.transactionHash | | receipt.blockNumber (number) | receipt.blockNumber (bigint) | | receipt.status (1 | 0) | receipt.status ("success" | "reverted") |