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

@surf_liquid/surf-widget

v0.2.6

Published

Embeddable DeFi yield widget SDK

Downloads

1,206

Readme

@surf_liquid/surf-widget

Embeddable React widget for Surfliquid — drop DeFi yield into any app in minutes.

npm license


What it does

@surf_liquid/surf-widget gives your users a fully self-custodial yield account — backed by Surfliquid's on-chain strategy engine — without leaving your app. Users deposit USDC, the protocol deploys a personal smart contract vault for them, and the agent rebalances across the best yields automatically.

The widget handles everything:

  • Smart contract vault deployment (first-time deposit)
  • ERC-20 approval + deposit in a single, guided flow
  • Real-time APY, balance, and earnings display
  • Instant withdrawal
  • On-chain activity feed with agent messages

View the demo app →


How it fits together

Your App
  └── @surf_liquid/core-sdk   ← owns wallet connection & all on-chain calls
        └── @surf_liquid/surf-widget  ← consumes the client, renders the UI

Important: All wallet connections must go through @surf_liquid/core-sdk. Do not pass a raw ethers signer or wagmi connector directly to the widget. The SurfClient from the core SDK wraps your wallet and exposes the interface the widget depends on.


Installation

# 1. Install the core SDK (required — handles wallet + chain calls)
npm install @surf_liquid/core-sdk

# 2. Install the widget
npm install @surf_liquid/surf-widget

Peer dependencies (install if not already in your project):

npm install react react-dom

Quick Start

Step 1 — Get your App ID

Register your project at sdk.surfliquid.com to get an appId. This ties your integration to your project and unlocks access to the Surfliquid API.

Step 2 — Initialise and authenticate via core-sdk

import { SurfClient } from '@surf_liquid/core-sdk';

const client = SurfClient.create({
  projectName: 'my-app',   // identifies your integration in logs
  appId: 'YOUR_APP_ID',    // from https://sdk.surfliquid.com/
  chainId: 8453,           // 1 = Ethereum, 8453 = Base (default), 137 = Polygon
  autoApprove: true,       // handles ERC-20 approval automatically
});

await client.verifyApp();           // validate appId — throws INVALID_APP_ID if rejected
await client.connectWallet('metamask');   // or 'walletconnect', 'coinbase', etc.
await client.authenticate();        // establishes cookie session (token is always null — gate on auth.authenticated)

Step 3 — Drop the widget into your React tree

import { SurfWidget } from '@surf_liquid/surf-widget';

export default function App() {
  return (
    <SurfWidget
      appId="YOUR_APP_ID"
      client={client}
      walletAddress="0xYourAddress"
      chainId={8453}
      onSuccess={(action, txHash) => console.log(`${action} confirmed:`, txHash)}
      onError={(action, err)    => console.error(`${action} failed:`,    err)}
    />
  );
}

That's it. No extra providers, no manual CSS imports — styles are fully scoped inside the widget.


SurfWidget Props

| Prop | Type | Default | Description | |------|------|---------|-------------| | appId | string | required | App ID from sdk.surfliquid.com | | client | ISurfClient | required | SurfClient instance from @surf_liquid/core-sdk | | walletAddress | string | required | Connected wallet address | | chainId | number | 8453 | Chain to operate on (1 Ethereum, 8453 Base, 137 Polygon) | | theme | SurfTheme | — | Override colors, radius, and font family | | className | string | — | Extra CSS class on the root element | | minDeposit | number | 0.1 | Minimum deposit amount in USDC | | onSuccess | (action, txHash) => void | — | Fired after a confirmed deposit or withdrawal | | onError | (action, error) => void | — | Fired when a deposit or withdrawal fails |


Theming

Override any design token via the theme prop. All values are optional — unset tokens fall back to the default Surfliquid palette.

<SurfWidget
  client={client}
  walletAddress={address}
  theme={{
    colors: {
      primary:        '#6366f1',   // buttons, active states, progress lines
      primaryText:    '#ffffff',   // text on primary-colored surfaces
      background:     '#0f0f11',   // widget background
      cardBackground: '#1a1a1f',   // card/modal background
      text:           '#f4f4f5',   // primary text
      textSecondary:  '#a1a1aa',   // labels, descriptions
      apy:            '#22c55e',   // APY percentage highlight
      border:         '#27272a',   // dividers and outlines
      success:        '#22c55e',   // success states
    },
    borderRadius: '16px',
    fontFamily:   'Inter, sans-serif',
  }}
/>

All tokens are injected as CSS custom properties scoped to [data-surf-widget], so they never affect the rest of your app.

CSS Class Overrides

For deeper customisation, target any .surf-* class in your own stylesheet. Every rule is already scoped to [data-surf-widget], so your overrides need the same scope to take effect:

[data-surf-widget] .surf-card {
  border-radius: 20px;
  box-shadow: 0 4px 24px rgba(0, 0, 0, 0.3);
}

[data-surf-widget] .surf-btn--primary {
  background: linear-gradient(135deg, #6366f1, #8b5cf6);
}

[data-surf-widget] .surf-balance-amount {
  font-size: 28px;
}

Key class groups:

| Group | Classes | |-------|---------| | Card | .surf-card, .surf-card--selected | | Buttons | .surf-btn, .surf-btn--primary, .surf-btn--outline, .surf-btn--soft, .surf-btn--ghost | | Modal | .surf-modal, .surf-modal-overlay, .surf-modal-body | | Amount input | .surf-amount-box, .surf-amount-input, .surf-amount-hint | | Step progress | .surf-step, .surf-step--active, .surf-step-icon, .surf-step-title, .surf-step-desc, .surf-step-separator | | Tabs | .surf-tabs, .surf-tab, .surf-tab--active | | Stats | .surf-stat-value, .surf-stat-value--apy, .surf-balance-amount | | Success screen | .surf-success, .surf-success-title, .surf-success-desc | | Activity feed | .surf-activity-item, .surf-activity-icon |


Minimum Deposit

Control the minimum deposit amount with the minDeposit prop (default: 0.1 USDC):

<SurfWidget
  client={client}
  walletAddress={address}
  minDeposit={10}   // users must deposit at least 10 USDC
/>

The widget enforces this in two ways:

  • The deposit button stays disabled until the entered amount meets the minimum
  • A hint label below the input shows Min 10 USDC

Supported Chains

| Chain | Chain ID | USDC Address | |----------|----------|--------------| | Ethereum | 1 | 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48 | | Base | 8453 | 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913 | | Polygon | 137 | 0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359 |

The widget automatically enforces the correct network. If the connected wallet is on a different chain, the card shows a "Switch to [Chain]" button that calls wallet_switchEthereumChain (and wallet_addEthereumChain if the chain isn't in the wallet yet). No configuration needed — just pass the desired chainId.


Using the Provider directly

For tighter layout control — e.g. rendering the vault card in one place and the deposit button somewhere else — wrap your tree with SurfProvider and consume context with useSurf anywhere inside it:

import { SurfProvider, useSurf } from '@surf_liquid/surf-widget';

function App() {
  const containerRef = useRef<HTMLDivElement | null>(null);
  return (
    <div ref={containerRef}>
      <SurfProvider
        config={{ appId: 'YOUR_APP_ID', client, walletAddress: address, chainId: 8453, theme: myTheme }}
        containerRef={containerRef}
      >
        <Dashboard />
      </SurfProvider>
    </div>
  );
}

function Dashboard() {
  const { walletAddress, chainId } = useSurf();
  // ...
}

Headless Usage — Hooks

Import individual hooks to build a fully custom UI while relying on Surfliquid's state management and on-chain logic.

All hooks must be called inside a <SurfProvider>.

useVault

Fetches the connected user's vault state.

import { useVault } from '@surf_liquid/surf-widget';

const { vault, isLoading, refetch } = useVault();

vault?.balance          // "1234.56" — deposited USDC
vault?.earnings         // "12.34"   — total yield earned
vault?.apy              // 8.7       — current APY (number)
vault?.apy7d            // 8.95      — 7-day windowed APY (number | null)
vault?.apy14d           // 8.80      — 14-day windowed APY (number | null)
vault?.apy30d           // 8.60      — 30-day windowed APY (number | null)
vault?.availableBalance // withdrawable amount

useDeposit

Drives the full deposit flow: vault deploy → ERC-20 approve → deposit.

import { useDeposit } from '@surf_liquid/surf-widget';

const { step, isProcessing, execute, reset } = useDeposit();

// Kick off a deposit
await execute('100', vault);

// step.id progression:
// 'idle' → 'creating_contract' → 'approving' → 'depositing' → 'success' | 'error'

useWithdraw

import { useWithdraw } from '@surf_liquid/surf-widget';

const { step, isProcessing, execute, reset } = useWithdraw();

await execute('100', vault);
// step.id: 'idle' | 'closing_position' | 'success' | 'error'

useDepositBalance

Returns the wallet's USDC balance formatted for display.

import { useDepositBalance } from '@surf_liquid/surf-widget';

const { balance, isLoading } = useDepositBalance(vault);
// balance: "1500.00"

useWithdrawableBalance

Returns the amount currently available to withdraw from the vault.

import { useWithdrawableBalance } from '@surf_liquid/surf-widget';

const { balance, isLoading } = useWithdrawableBalance(vault);

useChainGuard

Detects whether the connected wallet is on the correct chain and provides a one-call switch.

import { useChainGuard } from '@surf_liquid/surf-widget';

const { walletChainId, isWrongChain, isSwitching, switchChain } = useChainGuard();

// walletChainId  — the chain the wallet is currently on (null if no wallet detected)
// isWrongChain   — true when walletChainId !== the chainId passed to SurfWidget
// isSwitching    — true while wallet_switchEthereumChain is in flight
// switchChain()  — prompts the wallet to switch; adds the chain if not present yet

<SurfWidget> calls this internally — the "Switch to [Chain]" UI on the card is automatic. Use this hook only if you need the chain-guard state in your own custom UI.

useAgentMessages

Fetches paginated agent activity for the connected wallet.

import { useAgentMessages } from '@surf_liquid/surf-widget';

const { activities, isLoading, refetch } = useAgentMessages();

// activities[0].description     — human-readable description
// activities[0].type            — "deposit" | "withdraw"
// activities[0].protocol        — e.g. "Surf Agent v2"
// activities[0].timestamp       — Unix ms
// activities[0].txHash          — on-chain transaction hash
// activities[0].chainId         — chain where the tx occurred
// activities[0].amount          — number | null
// activities[0].token           — token symbol | null
// activities[0].fromVault       — VaultRef | null  ({ name, address, apy })
// activities[0].toVault         — VaultRef | null
// activities[0].apyBefore       — APY before the action | null
// activities[0].apyAfter        — APY after the action | null
// activities[0].signal          — strategy signal | null

Headless Usage — Components

Use individual pre-built components inside your own layout:

import {
  VaultCard,
  DepositModal,
  WithdrawModal,
  VaultActivityModal,
  ManageDropdown,
} from '@surf_liquid/surf-widget';

UI Primitives

import {
  Button,
  Modal,
  Tabs,
  StepProgress,
  TokenIcon,
} from '@surf_liquid/surf-widget';

StepProgress

Renders a vertical step tracker — useful for any multi-step transaction flow.

import { StepProgress } from '@surf_liquid/surf-widget';

<StepProgress
  baseExplorerUrl="https://basescan.org/tx/"
  steps={[
    {
      id: 'create',
      title: 'Surf Account Creation',
      description: 'Deploy your self-custodial smart contract',
      status: 'completed',
      txHash: '0xabc...',
    },
    {
      id: 'deposit',
      title: 'Deposit USDC',
      description: 'Fund your Surf Account',
      status: 'active',
    },
  ]}
/>

Step status values: 'pending' | 'active' | 'completed' | 'error'


TypeScript

All types are exported from the package root:

import type {
  // Config & theming
  SurfWidgetProps,
  SurfConfig,
  SurfTheme,

  // Domain models
  VaultInfo,
  VaultActivity,
  VaultDeposit,
  TokenInfo,
  ChainInfo,
  SurfVaultInfo,
  SurfVaultAsset,
  SurfSupportedAsset,
  VaultRef,
  VaultChainAddress,

  // State machines
  DepositStep,
  WithdrawStep,

  // Agent messages
  AgentMessage,
  AgentMessagesResult,

  // Client interface — implement this to mock or extend
  ISurfClient,
} from '@surf_liquid/surf-widget';

ISurfClient

The widget depends on this interface, not on a specific SDK version. You can implement it yourself for testing:

import type { ISurfClient } from '@surf_liquid/surf-widget';

const mockClient: ISurfClient = {
  getVault:              async () => ({ exists: false, userVaultAddress: null }),
  deployVault:           async () => ({ vaultAddress: '0x...', transactionHash: '0x...', salt: '0x...' }),
  deposit:               async () => ({ hash: '0x...', wait: async () => {} }),
  withdraw:              async () => ({ hash: '0x...', wait: async () => {} }),
  getSupportedAssets:    async () => [],
  getTokenBalance:       async () => 0n,
  getWithdrawableAmount: async () => 0n,
  getAgentMessages:      async () => ({ page: 1, limit: 10, total: 0, pages: 0, messages: [] }),
  verifyApp:             async () => {},
  refreshSession:        async () => ({ expiresAt: new Date().toISOString() }),
  on:  () => {},
  off: () => {},
};

Demo App

A fully working reference integration is available at:

https://github.com/Dev-zkCross/demo-sdk-surfliquid

It demonstrates:

  • Connecting MetaMask via @surf_liquid/core-sdk
  • Passing the client to <SurfWidget>
  • Handling onSuccess / onError callbacks
  • Custom theming

Wallet Support

The widget is wallet-agnostic — it works with any wallet you connect through @surf_liquid/core-sdk:

| Wallet | Support | |--------|---------| | MetaMask | ✅ | | WalletConnect | ✅ | | Coinbase Wallet | ✅ | | Any EIP-1193 injected wallet | ✅ | | Safe (Gnosis) | ✅ |

Reminder: Always initialise SurfClient from @surf_liquid/core-sdk first, then pass the resulting client instance to the widget. Never bypass the core SDK by passing a raw signer.


License

MIT © Surfliquid