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

@ceresv2/sdk

v0.3.2

Published

Ceres SDK — React hooks, API client, and invite flow for the Ceres on-chain social graph protocol

Readme

@ceresv2/sdk

TypeScript SDK for the Ceres protocol — address-level invite, on-chain social graph, DID identity, and B-side integration on Sepolia testnet.

Features

  • React hooks for wallet-connected read & write operations (invite, profile, inviter, descendants)
  • CeresApiClient — HTTP client for Ceres Relayer API (millisecond reads, no RPC)
  • CeresInviteFlow — end-to-end invite binding (sign → submit → wait), one function call
  • V2 Address-Level InvitesbindInviterBySig with EIP-712 signature (zero gas for end users)
  • B-Side Integration — deploy a FeeContract + get whitelisted for automatic fee channel
  • Pure utility functions that work with any viem PublicClient (no React required)
  • Pre-built contract ABIs and on-chain contract addresses
  • CeresProvider to wire up Wagmi + React Query in one line

Installation

npm install @ceresv2/sdk

Peer dependencies

Make sure your project has these installed:

npm install wagmi viem @tanstack/react-query react

Quick Start

Wrap your app with CeresProvider:

import { CeresProvider } from '@ceresv2/sdk'

function App() {
  return (
    <CeresProvider>
      <YourApp />
    </CeresProvider>
  )
}

That's it — CeresProvider internally sets up WagmiProvider (with the Sepolia RPC config) and QueryClientProvider.

Custom Wagmi Config

If you need a different chain or RPC endpoint, pass your own wagmi config:

import { CeresProvider } from '@ceresv2/sdk'
import { http, createConfig } from 'wagmi'
import { sepolia } from 'wagmi/chains'

const myConfig = createConfig({
  chains: [sepolia],
  transports: {
    [sepolia.id]: http('https://your-custom-rpc.example.com'),
  },
})

function App() {
  return (
    <CeresProvider wagmiConfig={myConfig}>
      <YourApp />
    </CeresProvider>
  )
}

CeresApiClient (No React Required)

The API client talks to the Ceres Relayer's REST API for fast database reads. No wallet, no RPC — just HTTP. Works in browser, Node.js, and serverless.

import { CeresApiClient } from '@ceresv2/sdk'

const ceres = new CeresApiClient({ baseUrl: 'http://43.156.99.215:5000' })

// Batch check up to 200 addresses (millisecond response)
const { profiles } = await ceres.batchCheck([
  '0xd5Ec02F0f4e5CD7122472e7C31549b6a48c6F5C8',
  '0x361f92e8d41b305b5cb0ef63a8768e79f7e18b65',
])
// → [{ address, invited, inviter, chainId, inviteeCount, descendantCount }]

// Get global stats
const stats = await ceres.getStats()
// → { edgeCount: 308, profiles: 274, chains: [...] }

// Paginated edges with incremental sync
const { edges, hasMore } = await ceres.getEdges({ since: 1783548174, order: 'asc' })
// → { edges: [...], page: 1, total: 308, hasMore: false }

// Get full graph around an address
const graph = await ceres.getAddressGraph('0x...')

// Get full network graph (cached 5 min)
const network = await ceres.getNetworkGraph()

Incremental Sync Pattern

// First run: full sync
const { edges, hasMore } = await ceres.getEdges({ limit: 1000 })
let lastTimestamp = edges[edges.length - 1]?.timestamp ?? 0

// Every 15 seconds: get only new edges
setInterval(async () => {
  const delta = await ceres.getEdges({ since: lastTimestamp, order: 'asc' })
  for (const edge of delta.edges) {
    console.log('New:', edge.invitee, 'invited by', edge.inviter)
    lastTimestamp = edge.timestamp
  }
}, 15000)

CeresInviteFlow — One-Click Invite

Encapsulates the entire invite journey into a single function call. The caller does NOT need to know about EIP-712, contract addresses, or Relayer endpoints.

import { CeresInviteFlow } from '@ceresv2/sdk'

// Frontend button handler
async function handleInvite(inviterAddress: string) {
  const flow = new CeresInviteFlow({ chainId: 56 }) // BSC

  const result = await flow.invite({
    walletClient,        // from useWalletClient() or wagmi
    inviterAddress: inviterAddress as `0x${string}`,
  })

  if (result.success) {
    console.log('✅ Invited! Tx:', result.txHash)
    console.log('   Inviter:', result.inviter)
    console.log('   Invitee:', result.invitee)
    console.log('   Chain:', result.chainId)
  } else {
    if (result.code === 'ALREADY_INVITED') {
      console.warn('Already invited:', result.error)
    } else {
      console.error('Failed:', result.error)
    }
  }
}

InviteFlow steps (handled internally)

  1. Check if invitee already has an inviter (API first → contract fallback)
  2. Sign EIP-712 Invite typed data with the user's wallet
  3. Submit signature to Ceres Relayer
  4. Wait for on-chain confirmation (polling API + RPC)
  5. Return { success: true, txHash, receipt } or { success: false, error, code }

Supported Chains

| Chain | chainId | InviteFlow | |-------|---------|------------| | ETH | 1 | ✅ | | BSC | 56 | ✅ | | BASE | 8453 | ✅ | | Sepolia | 11155111 | ✅ (testnet) |

All hooks require a WagmiProvider + QueryClientProvider context (provided automatically by <CeresProvider>).

React Hooks (V2 — One-Click Integration)

These hooks are the recommended way to integrate Ceres. Zero debugging — just wrap with <CeresProvider>, then use hooks.

useCeresInvite — 一键邀请

import { useCeresInvite } from '@ceresv2/sdk'

function InviteButton({ inviterAddress }: { inviterAddress: string }) {
  const { invite, isPending, error } = useCeresInvite()

  return (
    <button onClick={() => invite(inviterAddress as `0x${string}`)} disabled={isPending}>
      {isPending ? '邀请中...' : '邀请'}
    </button>
  )
}

useCeresUserStatus — 用户所有状态(一站式)

import { useCeresUserStatus } from '@ceresv2/sdk'

function Dashboard() {
  const { address, isConnected, inviteProfile, didProfile, invitees, isLoading } = useCeresUserStatus()

  if (!isConnected) return <p>请先连接钱包</p>
  if (isLoading) return <p>加载中...</p>

  return (
    <div>
      <p>地址: {address}</p>
      {inviteProfile?.invited && <p>邀请人: {inviteProfile.inviter}</p>}
      {didProfile && <p>DID: {didProfile.name}</p>}
      <p>邀请了 {invitees.length} 个地址</p>
    </div>
  )
}

useCeresMintDID — 铸造 DID

import { useCeresMintDID } from '@ceresv2/sdk'

function MintButton() {
  const { mint, isPending, mintFee } = useCeresMintDID()

  return (
    <button onClick={() => mint({ name: 'My DID', bio: 'Hello Ceres', avatar: '', urls: [] })}>
      {isPending ? '铸造中...' : `铸造 DID (${mintFee ? '需支付' : '免费'})`}
    </button>
  )
}

Hooks 一览

| Hook | 用途 | 数据源 | |------|------|--------| | useCeresInvite() | 一键邀请 | EIP-712 签名 → Relayer → 链 | | useCeresUserStatus() | 用户全部状态(邀请+DID+邀请列表) | API + 链 | | useCeresMintDID() | 铸造 DID Profile | 钱包交互 → 合约 | | useCeresDIDProfile(addr) | DID 信息(by address) | 合约 | | useCeresDIDStats() | DID 合约全局统计 | 合约 | | useCeresRecentDIDs() | 最新 DID profiles | API | | useCeresProfilesByOwner(addr) | 某地址拥有的所有 DID | API | | useCeresBatchCheck(addresses) | 批量查邀请 | API → 合约 fallback | | useCeresProfile(addr) | 单地址邀请状态 | API | | useCeresInvitees() | 当前钱包邀请列表 | API | | useCeresNetwork() | 全局统计 | API | | useCeresEdges() | 分页边查询 | API | | useCeresGraph() | 全局图谱 | API |

接入方只需要

// 1. 包裹 CeresProvider
import { CeresProvider } from '@ceresv2/sdk'

<CeresProvider>
  <App />
</CeresProvider>

// 2. 用 hooks
import { useCeresInvite, useCeresUserStatus } from '@ceresv2/sdk'

// 搞定

React Hooks (V1 — DID NFT, Legacy)

useProfile

Read a full DID profile by token ID (name, bio, avatar, owner, level):

import { useProfile } from '@ceresv2/sdk'

function ProfileCard({ tokenId }: { tokenId: bigint }) {
  const { data: profile, isLoading } = useProfile(tokenId)

  if (isLoading) return <div>Loading…</div>
  if (!profile) return <div>Profile not found</div>

  return (
    <div>
      <h2>{profile.name}</h2>
      <p>{profile.bio}</p>
      <img src={profile.avatar} alt={profile.name} />
      <span>Level: {profile.levelName} ({profile.level})</span>
      <span>Owner: {profile.owner}</span>
    </div>
  )
}

useInviter

Look up the inviter (parent) token ID in the invitation tree:

const { data: inviterTokenId } = useInviter(tokenId)
// → bigint (0n = root node / no inviter)

useDescendantCount

Count all descendants (direct + indirect invitees):

const { data: count } = useDescendantCount(tokenId)
// → bigint

useBalanceOf

Get the ERC-721 balance for an address:

const { data: balance } = useBalanceOf(address)
// → bigint

useTotalProfiles

Total number of profiles ever created:

const { data: totalProfiles } = useTotalProfiles()
// → bigint

useUserTokenId

Auto-detect the first token ID owned by the connected wallet:

const tokenId = useUserTokenId()  // → bigint | undefined

Pure Utility Functions (no React)

Use these in server-side code, scripts, or any non-React context. Each function takes a viem PublicClient as the last argument.

import { createPublicClient, http } from 'viem'
import { sepolia } from 'viem/chains'
import { getProfile, getInviter, getDescendantCount, getBalanceOf } from '@ceresv2/sdk'

const client = createPublicClient({
  chain: sepolia,
  transport: http('https://sepolia.gateway.tenderly.co'),
})

// Fetch a full profile
const profile = await getProfile(1n, client)

// Look up invitation relationship
const inviterId = await getInviter(5n, client)

// Count descendants
const count = await getDescendantCount(3n, client)

// Get token balance of an address
const balance = await getBalanceOf('0x...', client)

// Get total profiles
const total = await getTotalProfiles(client)

// Look up token ID by address
const tokenId = await getUserTokenId('0x...', client)

Contract ABIs & Addresses

Access the raw ABIs and addresses for direct integration:

import {
  CeresDID_ABI,
  CeresRegistry_ABI,
  CERES_DID_ADDRESS,
  CERES_REGISTRY_ADDRESS,
  didContract,
  registryContract,
} from '@ceresv2/sdk'
  • CeresDID: ERC-721 NFT at 0x159f4001C8692A777A842f3F0A76f268aF1A8F39
  • CeresRegistry: Registry & invitation tree at 0x9043489CFFe56C1C5b5E1b8Fb1E4bc384B575116

The didContract / registryContract exports are pre-built config objects with both address and abi, ready for wagmi useReadContract or viem readContract.

Types

import type { Profile, ProfileWithId, UseProfileResult } from '@ceresv2/sdk'

interface Profile {
  name: string
  bio: string
  avatar: string
  updatedAt: bigint
}

interface ProfileWithId extends Profile {
  tokenId: bigint
  owner: `0x${string}`
  level: number
  levelName: string
}

Level System

| Level | Name | Color | |-------|---------|---------| | 0 | Seed | Gray | | 1 | Bronze | Bronze | | 2 | Silver | Silver | | 3 | Gold | Gold | | 4 | Crystal | Purple | | 5 | Diamond | Blue |

import { LEVEL_NAMES, LEVEL_COLORS } from '@ceresv2/sdk'

LEVEL_NAMES[3]   // → 'Gold'
LEVEL_COLORS[5]  // → '#3B82F6'

V2 Address-Level Hooks

The V2 protocol introduces address-level invite binding (no DID mint required).

useCeresInvite

Create an invite relationship via EIP-712 signature:

import { useCeresInvite } from '@ceresv2/sdk'

function InviteButton({ inviter, invitee }: { inviter: Address; invitee: Address }) {
  const { invite, isPending, error } = useCeresInvite()

  return (
    <button onClick={() => invite({ inviter, invitee })} disabled={isPending}>
      {isPending ? 'Signing...' : `Invite ${invitee.slice(0,6)}...`}
    </button>
  )
}

Under the hood: SDK generates EIP-712 typed data → user signs with wallet → signature sent to Relayer → Relayer calls Core.bindInviterBySig() → invite created on-chain.

useCeresHasInviter

Check if an address has been invited:

const { data: hasInviter } = useCeresHasInviter(address)
// → boolean

useCeresRead

Batch read invitation data for an address:

const { data } = useCeresRead(address)
// → { hasInviter, inviter, descendantCount, level }

B-Side Integration

For projects that want to offer Ceres invites to their own users with automatic fee collection:

  1. Deploy a CeresFeeContract (or use the standard version)
  2. Request whitelist registration from the Ceres team (Core.addFeeContract(yourAddress))
  3. Use useCeresInvite() from this SDK — the Relayer routes through your FeeContract

See docs/B端接入流程.md for the full integration guide.

Chain Support

| Network | Core-A | Core-B | Relayer | |---------|--------|--------|---------| | Sepolia (11155111) | 0xCD142BDD...533D | 0xeE05846...7333 | 101.33.109.117:5003 |

License

MIT