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

surfliquid

v0.1.1

Published

Framework-agnostic Surfliquid SDK for wallet auth, vault deployment, deposits, and withdrawals.

Readme

surfliquid

Framework-agnostic TypeScript SDK for Surfliquid — wallet authentication, vault deployment, deposits, withdrawals, portfolio queries, and activity feeds.

Table of Contents


Installation

npm install surfliquid ethers

ethers v6 is a required peer dependency.


Quick Start

import { SurfClient } from "surfliquid";

const client = SurfClient.create({
  projectName: "my-app",
  appId: "your-app-id",
  autoApprove: true,
});

await client.connectWallet("metamask");
await client.authenticate();

const vault = await client.getVault();
if (!vault.exists) await client.deployVault();

const tx = await client.deposit({
  asset: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913", // USDC on Base
  amount: "10.0",
});
await tx.wait();

Initialization

SurfClient.create(config) — recommended

const client = SurfClient.create({
  projectName: "my-app",       // Required — sent as X-Surf-Project-Name header
  appId: "your-app-id",        // Required — sent as X-App-ID header on first user login
  environment: "mainnet",      // "mainnet" | "testnet" — default: "mainnet"
  chainId: 8453,               // Default: Base (8453) on mainnet
  autoApprove: true,           // Auto-approve ERC20 spend before deposit — default: false
  apiBaseUrl: "https://api.surfliquid.com", // Optional — uses default if omitted
});

SurfClient.builder() — fluent API

Use this when you need to register custom chains, tokens, or wallet adapters at build time.

const client = SurfClient.builder()
  .setProject("my-app", "your-app-id")
  .setEnvironment("mainnet")
  .setChain(8453)
  .setAutoApprove(true)
  .build();

Config options

| Option | Type | Required | Default | Description | |--------|------|----------|---------|-------------| | projectName | string | Yes | — | Your project name | | appId | string | Yes | — | Your app / project ID | | environment | "mainnet" \| "testnet" | No | "mainnet" | Network environment | | chainId | number | No | 8453 | Active chain ID | | autoApprove | boolean | No | false | Auto-approve ERC20 allowance before deposits | | rpcUrl | string | No | Chain default | Custom RPC endpoint | | apiBaseUrl | string | No | https://api.surfliquid.com | Custom API base URL | | factoryAddress | `0x${string}` | No | Chain default | Override vault factory address |

Supported chains

| Chain | Chain ID | Environment | |-------|----------|-------------| | Base | 8453 | mainnet | | Polygon | 137 | mainnet | | Base Sepolia | 84532 | testnet |


Step-by-Step Integration

1. Create the client

const client = SurfClient.create({
  projectName: "my-app",
  appId: "your-app-id",
  autoApprove: true,
});

2. Register event listeners

Set up listeners before connecting so no events are missed.

client.on("wallet:connected", ({ address, chainId }) => {
  console.log("Connected:", address, "on chain", chainId);
});
client.on("auth:authenticated", ({ token }) => {
  console.log("JWT:", token);
});
client.on("vault:deployed", ({ vaultAddress }) => {
  console.log("Vault at:", vaultAddress);
});
client.on("deposit:completed", ({ asset, amount, txHash }) => {
  console.log(`Deposited ${amount} of ${asset} — tx: ${txHash}`);
});
client.on("error", ({ code, message }) => {
  console.error(`[${code}] ${message}`);
});

3. Connect wallet

// Supported: "metamask" | "trust" | "coinbase" | "rabby" | "phantom" | "walletconnect" | "injected"
const state = await client.connectWallet("metamask");
console.log(state.address);  // "0x..."
console.log(state.chainId);  // 8453

4. Authenticate

const auth = await client.authenticate();
console.log(auth.authenticated); // true
console.log(auth.token);         // "eyJ..."

5. Check or deploy vault

const vault = await client.getVault();

if (!vault.exists) {
  const result = await client.deployVault();
  console.log("Deployed at:", result.vaultAddress);
} else {
  console.log("Vault:", vault.userVaultAddress);
  console.log("Total value: $" + vault.totalValueUSD);
  console.log("APY:", vault.apyBreakdown?.currentAPY + "%");
}

6. Deposit

const tx = await client.deposit({
  asset: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913", // USDC on Base
  amount: "10.0", // human-readable, not wei
});
await tx.wait();

7. Check portfolio

const portfolio = await client.getPortfolioSummary();
console.log("Active assets:", portfolio.activeCount);
portfolio.assets.forEach((addr, i) => {
  console.log(addr, "→ profit:", portfolio.profits[i].toString());
});

8. Withdraw

// Partial
const tx = await client.withdraw({
  asset: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
  amount: "5.0",
});
await tx.wait();

// Full (omit amount)
const tx = await client.withdraw({
  asset: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
});
await tx.wait();

API Reference

Wallet

connectWallet(walletName)

Connects a wallet and switches it to the configured chain.

const state = await client.connectWallet("metamask");
// state: { address, chainId, connected }

disconnectWallet()

await client.disconnectWallet();

getWalletState()

Returns current wallet state or null if not connected.

const state = client.getWalletState(); // WalletState | null

switchChain(chainId)

await client.switchChain(137); // switch to Polygon

registerWalletAdapter(name, adapter)

Register a custom wallet adapter at runtime.

client.registerWalletAdapter("my-wallet", new MyAdapter());

Authentication

authenticate()

Signs a nonce with the connected wallet and retrieves a JWT. Flow: getNoncesignMessagelogin.

const auth = await client.authenticate();
// auth: { token, address, authenticated, user }

Requires wallet connected.

getAuthState()

const auth = client.getAuthState(); // AuthState (synchronous, no request)

logout()

await client.logout();

Vault

getVault(walletAddress?)

Fetches vault info. Falls back to connected wallet if no address passed. Public — no auth required.

const vault = await client.getVault();
// or: await client.getVault("0xAnyAddress");

vault.exists             // boolean
vault.userVaultAddress   // "0x..." | null
vault.homeChainId        // 8453
vault.vaultVersion       // "v4"
vault.isActive           // boolean
vault.totalValueUSD      // 13.03
vault.totalDepositedUSD  // 13.03
vault.apyBreakdown       // { currentAPY, nativeAPY, merklAPY, leagueAPY }
vault.earned             // { totalEarningsUSD, nativeEarningsUSD, merklRewardsUSD, leagueEarnedUSD }
vault.league             // { rank, totalXP, estimatedSURF, estimatedSURFUSD, joinDate } | null
vault.assets             // VaultAsset[] — per-chain asset breakdown

deployVault()

Deploys a new vault. Flow: prepare → on-chain deployVaultconfirm.

const result = await client.deployVault();
result.vaultAddress      // "0x..."
result.transactionHash   // "0x..."
result.salt              // "0x..."

Requires wallet + auth. Throws VAULT_ALREADY_EXISTS if vault exists.

getSupportedAssets(chainId?)

Returns supported assets with live APY data. Public — no wallet or auth required.

const assets = await client.getSupportedAssets();         // all chains
const assets = await client.getSupportedAssets(8453);     // Base only
const assets = await client.getSupportedAssets(137);      // Polygon only

assets[0].assetSymbol   // "USDC"
assets[0].assetAddress  // "0x..."
assets[0].chainId       // 8453
assets[0].chainStatus   // "ACTIVE" | "STAKING"
assets[0].currentAPY    // 15.99
assets[0].nativeAPY     // 13.08
assets[0].merklAPY      // 0.05
assets[0].leagueAPY     // 2.86

getAgentMessages(walletAddress?, page?, limit?)

Returns paginated activity feed — deposits, withdrawals, rebalances, bridges, Merkl claims. Public.

const result = await client.getAgentMessages();           // connected wallet, page 1, limit 20
const result = await client.getAgentMessages("0x...", 2, 10);

result.total    // 42
result.pages    // 3
result.messages // AgentMessage[]

result.messages[0].message          // human-readable description
result.messages[0].transactionType  // "USER_DEPOSIT" | "CROSS_CHAIN_REBALANCE" | ...
result.messages[0].executedBy       // "USER" | "AGENT"
result.messages[0].chainId          // 8453
result.messages[0].txHash           // "0x..."
result.messages[0].timestamp        // "2026-04-02T12:21:41.000Z"

getOwnerVaults(owner?) / getOwnerVaultCount(owner?)

const vaults = await client.getOwnerVaults();   // string[]
const count  = await client.getOwnerVaultCount(); // number

isVaultFromFactory(vaultAddress)

const valid = await client.isVaultFromFactory("0x..."); // boolean

getAllowedAssets(vaultAddress?)

const assets = await client.getAllowedAssets(); // string[]

getFeeInfo(vaultAddress?)

const fees = await client.getFeeInfo();
fees.feePercentage            // bigint
fees.rebalanceFeePercentage   // bigint
fees.merklClaimFeePercentage  // bigint

Deposits & Withdrawals

deposit(params)

const tx = await client.deposit({
  asset: "0x...",     // token address
  amount: "10.0",     // human-readable (not wei)
  wrapEth: false,     // wrap native ETH → WETH first
  bestVault: "0x...", // optional: override Morpho vault
});
await tx.wait();

If autoApprove: true, the SDK automatically approves the token spend if needed.

withdraw(params)

const tx = await client.withdraw({
  asset: "0x...",
  amount: "5.0",  // omit for full withdrawal
});
await tx.wait();

getWithdrawableAmount(asset, vaultAddress?)

const wei = await client.getWithdrawableAmount("0x..."); // bigint

getBestVault(assetSymbol)

const options = await client.getBestVault("USDC");
// [{ chainId: 8453, vaultAddress: "0x..." }, ...]

hasInitialDeposit(asset, vaultAddress?)

const done = await client.hasInitialDeposit("0x..."); // boolean

Portfolio

getPortfolioSummary(vaultAddress?)

const p = await client.getPortfolioSummary();
p.activeCount    // number
p.assets         // string[]  — asset addresses
p.deposited      // bigint[]  — deposited amounts in wei
p.currentValues  // bigint[]  — current values in wei
p.profits        // bigint[]  — profits in wei

getAssetProfit(asset, vaultAddress?)

const profit = await client.getAssetProfit("0x..."); // bigint (wei)

getAssetProfitPercentage(asset, vaultAddress?)

const pct = await client.getAssetProfitPercentage("0x..."); // bigint

Token Utilities

getTokenBalance(token, owner?)

const balance = await client.getTokenBalance("0xusdcAddress");          // connected wallet
const balance = await client.getTokenBalance("0xusdcAddress", "0x..."); // any address
// returns bigint (wei)

getSupportedTokens()

Returns tokens registered in the SDK for the active chain (synchronous).

const tokens = client.getSupportedTokens();
// [{ address, symbol, decimals, morphoVaults }, ...]

getConfig()

const config = client.getConfig();
config.chainId      // 8453
config.environment  // "mainnet"
config.apiBaseUrl   // "https://api.surfliquid.com"

Events

client.on("event:name", handler);
client.off("event:name", handler);

| Event | Payload | When | |-------|---------|------| | wallet:connected | WalletState | Wallet connects | | wallet:disconnected | void | Wallet disconnects | | wallet:accountChanged | { oldAddress, newAddress } | Account switch | | wallet:chainChanged | { chainId } | Chain switch | | auth:authenticated | AuthState | Login complete | | auth:logout | void | Logout called | | vault:deployed | DeployVaultResult | Vault deployed | | deposit:started | { asset, amount } | Deposit begins | | deposit:approved | { asset, txHash } | ERC20 approval sent | | deposit:completed | { asset, amount, txHash } | Deposit confirmed | | withdraw:started | { asset, amount } | Withdrawal begins | | withdraw:completed | { asset, amount, txHash } | Withdrawal confirmed | | error | { code, message } | Any SDK error |


Types

interface WalletState {
  address: string;
  chainId: number;
  connected: boolean;
}

interface AuthState {
  token: string | null;
  address: string | null;
  authenticated: boolean;
  user: UserProfile | null;
}

interface VaultInfo {
  userVaultAddress: string | null;
  deploymentSalt: string | null;
  exists: boolean;
  homeChainId?: number | null;
  vaultVersion?: string | null;
  isActive?: boolean;
  totalValueUSD?: number | null;
  totalDepositedUSD?: number | null;
  earned?: VaultEarned | null;
  apyBreakdown?: VaultApyBreakdown | null;
  league?: VaultLeague | null;
  assets?: VaultAsset[];
}

interface SupportedAsset {
  assetAddress: string;
  assetSymbol: string;
  assetDecimals: number;
  chainId: number;
  chainStatus: string;
  currentAPY: number;
  nativeAPY: number;
  merklAPY: number;
  leagueAPY: number;
}

interface AgentMessage {
  message: string;
  txHash: string;
  timestamp: string;
  transactionType: "INITIAL_DEPOSIT" | "USER_DEPOSIT" | "USER_WITHDRAWAL" | "REBALANCE" | "CROSS_CHAIN_REBALANCE" | "MIGRATE" | string;
  executedBy: "USER" | "AGENT";
  vaultVersion: string;
  chainId: number;
}

interface DepositParams {
  asset: string;
  amount: string;
  vaultAddress?: string;
  bestVault?: string;
  wrapEth?: boolean;
}

interface WithdrawParams {
  asset: string;
  amount?: string;
  vaultAddress?: string;
}

interface TransactionResult {
  hash: string;
  wait: () => Promise<any>;
}

Error Handling

All errors are instances of SurfError with a typed code and message.

import { SurfError, SurfErrorCode } from "surfliquid";

try {
  await client.deposit({ asset: "0x...", amount: "10" });
} catch (err) {
  if (err instanceof SurfError) {
    switch (err.code) {
      case SurfErrorCode.WALLET_NOT_CONNECTED: break;
      case SurfErrorCode.INSUFFICIENT_BALANCE: break;
      case SurfErrorCode.DEPOSIT_FAILED: break;
    }
  }
}

| Code | Cause | |------|-------| | INVALID_CONFIG | Missing or invalid configuration | | MISSING_PROJECT_ID | appId not provided | | UNSUPPORTED_CHAIN | chainId not registered for environment | | WALLET_NOT_INSTALLED | Wallet extension not found | | WALLET_NOT_CONNECTED | Operation requires connected wallet | | WALLET_REJECTED | User rejected the wallet request | | WRONG_CHAIN | Wallet is on wrong chain | | AUTH_FAILED | Login request failed | | SIGNATURE_REJECTED | User rejected message signing | | VAULT_NOT_FOUND | No vault exists for this address | | VAULT_ALREADY_EXISTS | Vault already deployed | | VAULT_DEPLOY_FAILED | On-chain deployment failed | | INSUFFICIENT_BALANCE | Token balance too low | | APPROVE_FAILED | ERC20 approval failed | | DEPOSIT_FAILED | Deposit transaction failed | | WITHDRAW_FAILED | Withdrawal transaction failed | | NO_BEST_VAULT | No Morpho vault found for asset | | API_ERROR | Backend API error | | RPC_ERROR | RPC/blockchain call failed | | TRANSACTION_FAILED | On-chain transaction failed |


Examples

React

import { useEffect, useState } from "react";
import { SurfClient } from "surfliquid";

const client = SurfClient.create({
  projectName: "my-app",
  appId: "your-app-id",
  autoApprove: true,
});

export function App() {
  const [address, setAddress] = useState<string | null>(null);
  const [vault, setVault] = useState<string | null>(null);

  useEffect(() => {
    client.on("wallet:connected", (s) => setAddress(s.address));
    client.on("wallet:disconnected", () => setAddress(null));
  }, []);

  async function setup() {
    await client.connectWallet("metamask");
    await client.authenticate();
    const info = await client.getVault();
    if (!info.exists) {
      const r = await client.deployVault();
      setVault(r.vaultAddress);
    } else {
      setVault(info.userVaultAddress);
    }
  }

  async function deposit() {
    const tx = await client.deposit({
      asset: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
      amount: "10.0",
    });
    await tx.wait();
  }

  return (
    <div>
      <button onClick={setup}>Connect & Setup</button>
      {address && <p>Wallet: {address}</p>}
      {vault && <button onClick={deposit}>Deposit 10 USDC</button>}
    </div>
  );
}

Vanilla JS

<script type="module">
  import { SurfClient } from "/node_modules/surfliquid/dist/index.js";

  const client = SurfClient.create({
    projectName: "my-app",
    appId: "your-app-id",
    autoApprove: true,
  });

  // No wallet needed — fetch supported assets
  const assets = await client.getSupportedAssets(8453);
  console.table(assets.map(a => ({
    symbol: a.assetSymbol,
    APY: a.currentAPY + "%",
    address: a.assetAddress,
  })));

  // Full flow
  await client.connectWallet("metamask");
  await client.authenticate();

  const vault = await client.getVault();
  if (!vault.exists) await client.deployVault();

  const tx = await client.deposit({
    asset: assets[0].assetAddress,
    amount: "10.0",
  });
  await tx.wait();
</script>

Node.js (read-only)

import { SurfClient } from "surfliquid";

const client = SurfClient.create({
  projectName: "dashboard",
  appId: "your-app-id",
});

// No wallet needed for public endpoints
const vault = await client.getVault("0xUserWalletAddress");
console.log("Vault:", vault.userVaultAddress);
console.log("Value: $" + vault.totalValueUSD);

const assets = await client.getSupportedAssets();
console.log("Supported:", assets.map(a => a.assetSymbol));

const { messages } = await client.getAgentMessages("0xUserWalletAddress");
messages.forEach(m => console.log(`[${m.executedBy}] ${m.message}`));

Custom wallet adapter

import { SurfClient, IWalletAdapter } from "surfliquid";
import type { ContractRunner } from "ethers";

class MyWalletAdapter implements IWalletAdapter {
  async connect(chainId: number) { /* return WalletState */ }
  async disconnect() {}
  async switchChain(chainId: number) {}
  async getSigner(): Promise<ContractRunner> { /* return ethers Signer */ }
  async signMessage(message: string): Promise<string> { /* return sig */ }
  onAccountsChanged(cb: (accounts: string[]) => void) {}
  onChainChanged(cb: (chainId: number) => void) {}
  onDisconnect(cb: () => void) {}
}

const client = SurfClient.builder()
  .setProject("my-app", "your-app-id")
  .registerWalletAdapter("my-wallet", new MyWalletAdapter())
  .build();

await client.connectWallet("my-wallet");

WalletConnect

import { SurfClient, WalletConnectAdapter } from "surfliquid";
import { EthereumProvider } from "@walletconnect/ethereum-provider";

const client = SurfClient.create({ projectName: "my-app", appId: "your-app-id" });

const wcProvider = await EthereumProvider.init({
  projectId: "your-walletconnect-project-id", // from cloud.walletconnect.com
  chains: [8453],
  showQrModal: true,
});

client.registerWalletAdapter("walletconnect", new WalletConnectAdapter(() => wcProvider));
await client.connectWallet("walletconnect");

Register custom chain / token

const client = SurfClient.builder()
  .setProject("my-app", "your-app-id")
  .registerChain("mainnet", {
    chainId: 42161,
    rpcUrl: "https://arb1.arbitrum.io/rpc",
    factoryAddress: "0x...",
    wethAddress: "0x82af49447d8a07e3bd95bd0d56f35241523fbab1",
    tokens: [],
  })
  .registerToken("mainnet", 42161, {
    address: "0xaf88d065e77c8cc2239327c5edb3a432268e5831",
    symbol: "USDC",
    decimals: 6,
    morphoVaults: [],
  })
  .setChain(42161)
  .build();

Notes

  • projectName and appId are required. projectId is accepted as a backwards-compatible alias for appId.
  • Default environment is mainnet; default mainnet chain is Base (8453).
  • Amounts passed to deposit() and withdraw() are human-readable strings (e.g. "10.5"), not wei.
  • Withdraw amounts are encoded using the token's on-chain decimals() value.
  • getVault(), getSupportedAssets(), and getAgentMessages() are public — no wallet or auth required when passing an explicit wallet address.
  • Set autoApprove: true on the client to skip manual ERC20 approval before deposits.