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

@pafi-dev/issuer-fe

v0.4.0

Published

React SDK for partner issuer web frontends. Ships a typed HTTP client + hooks for the issuer BE's mint (`/claim`) and burn (`/redeem`) endpoints, user profile/history, and an on-chain balance reader (viem). The issuer FE never handles Privy tokens or user

Readme

@pafi-dev/issuer-fe

React SDK for partner issuer web frontends integrating with their own issuer backend.

Pairs with:

  • @pafi-dev/issuer/pregen on the BE (wallet provisioning by verified email)
  • @pafi-dev/issuer/mint-burn on the BE (on-chain mint/burn signing)

Login is yours. This SDK no longer ships auth. The PAFI gateway retired its user-authentication surface, so the issuer authenticates its own users, calls POST /v1/wallets/pregen server-side to get the user's wallet address, and mints PT directly to it. Removed here: usePafiAuth, PafiAuthClient, DirectAuthSuccess, DirectAuthError, EmailStartResponse, PafiAuthError.

What you get

| Hook / Class | Purpose | |---|---| | usePafiIssuer | /claim (mint PT) + /redeem (burn PT) against your issuer BE | | PafiIssuerClient | Framework-agnostic issuer BE HTTP client | | usePafiUserProfile | Poll GET /users/me — off-chain + on-chain + total balance from issuer BE | | usePafiUserHistory | Paginated ledger transaction list from issuer BE | | usePafiBalance | Poll ERC-20 + native balances direct from Arbitrum RPC via viem | | formatBalance / parseBalance / sumBalances | bigint ⇄ human string helpers |

What you DO NOT get (and why)

  • No Privy integration — issuer FE doesn't hold user tokens or sign UserOps. That's the pafi.xyz path (@pafi-dev/wallet-react).

  • No wallet signing — the issuer BE owns the minter/burner key and signs mint/burn transactions server-side. FE just triggers.

  • No transfer/swap — issuer FEs are point-redemption surfaces, not wallets. Users trade PT ↔ USDT on pafi.xyz.

  • No login — the issuer owns user authentication end to end.

Issuer FEs are display + trigger, never signers.

Install

pnpm add @pafi-dev/issuer-fe viem

Peer deps: react (18 or 19), viem (2.x).

Quickstart — claim → balance flow

import { useMemo } from 'react';
import { createPublicClient, http } from 'viem';
import { arbitrum } from 'viem/chains';
import {
  usePafiIssuer,
  usePafiBalance,
  formatBalance,
} from '@pafi-dev/issuer-fe';

const ISSUER_API = 'https://gg56-api-stg.pacificfinance.org';
const PT = '0x26Ca5e61458dF9F8C89c117F92192C31C63b042A';

export function App({ session }: { session: IssuerSession | null }) {
  // `session` comes from YOUR login flow. The issuer BE returns the
  // wallet address it got from `POST /v1/wallets/pregen`.
  const issuer = usePafiIssuer({
    baseUrl: ISSUER_API,
    // Return your issuer BE session token — SDK sends it as Bearer.
    getAccessToken: async () => localStorage.getItem('issuer_session_token'),
    onClaimed: (r) => console.log('minted', r.txHash),
  });

  const publicClient = useMemo(
    () => createPublicClient({ chain: arbitrum, transport: http() }),
    [],
  );
  const balance = usePafiBalance({
    publicClient,
    wallet: session?.walletAddress,
    tokens: [
      { key: 'PT', address: PT, decimals: 18 },
      { key: 'ETH', address: null, decimals: 18 },
    ],
  });

  if (!session) return <YourOwnLoginForm />;

  return (
    <div>
      <p>Signed in as {session.email}</p>
      <p>Wallet: <code>{session.walletAddress}</code></p>
      <p>PT balance: {formatBalance(balance.balances.PT ?? 0n, 18)}</p>
      <button
        disabled={issuer.loading.claim}
        onClick={() => issuer.claim({ amount: (10n ** 20n).toString() /* 100 PT */ })}
      >
        {issuer.loading.claim ? 'Minting…' : 'Claim 100 PT'}
      </button>
      {issuer.lastClaim && (
        <p>Last tx: <code>{issuer.lastClaim.txHash}</code></p>
      )}
    </div>
  );
}

Balance sources — which hook to use?

Two orthogonal sources of "how many points does this user have?":

| Hook | Source | When to use | |---|---|---| | usePafiUserProfile | Issuer BE GET /users/me{offChainBalance, onChainBalance, totalBalance} | The primary number your UI shows. Issuer BE joins off-chain ledger + on-chain balance in one round-trip. | | usePafiBalance | Arbitrum RPC direct via viem — pure on-chain | Debugging, showing PT + USDT + ETH in one panel, or when issuer BE is down but you still want to render on-chain state. |

For most issuers: use usePafiUserProfile for the headline balance, usePafiBalance only if you need multi-token breakdown.

User profile example — merged off-chain + on-chain

import { usePafiUserProfile, formatBalance } from '@pafi-dev/issuer-fe';

function BalanceCard({ auth, issuer }) {
  const profile = usePafiUserProfile({
    baseUrl: ISSUER_API,
    getAccessToken: async () => localStorage.getItem('issuer_session'),
    enabled: !!auth.user,
    onData: (p) => console.log('total points:', p.totalBalance),
  });

  // Wire refresh to fire the instant a claim/redeem lands
  useEffect(() => {
    if (issuer.lastClaim || issuer.lastRedeem) profile.refresh();
  }, [issuer.lastClaim, issuer.lastRedeem, profile]);

  if (profile.loading && !profile.profile) return <div>Loading…</div>;
  if (profile.error) return <div>Error: {profile.error.message}</div>;
  if (!profile.profile) return null;

  return (
    <div>
      <div>Points: {formatBalance(profile.profile.totalBalance, 18)}</div>
      <div style={{fontSize: 12, color: '#888'}}>
        Off-chain: {formatBalance(profile.profile.offChainBalance, 18)} ·
        On-chain: {formatBalance(profile.profile.onChainBalance, 18)}
      </div>
    </div>
  );
}

Transaction history example

import { usePafiUserHistory, formatBalance } from '@pafi-dev/issuer-fe';

function HistoryList({ auth }) {
  const history = usePafiUserHistory({
    baseUrl: ISSUER_API,
    getAccessToken: async () => localStorage.getItem('issuer_session'),
    enabled: !!auth.user,
    pageSize: 20,
  });

  return (
    <ul>
      {history.entries.map((e) => (
        <li key={e.id}>
          <span>{new Date(e.createdAt).toLocaleString()}</span>
          <span style={{color: BigInt(e.delta) > 0n ? 'green' : 'red'}}>
            {BigInt(e.delta) > 0n ? '+' : ''}{formatBalance(e.delta, 18)}
          </span>
          <span>{e.reason}</span>
          {e.txHash && <a href={`https://arbiscan.io/tx/${e.txHash}`}>tx</a>}
        </li>
      ))}
      {history.hasMore && (
        <button disabled={history.loading} onClick={history.loadMore}>
          Load more
        </button>
      )}
    </ul>
  );
}

Issuer BE contract — endpoints this SDK expects

If your BE is the gg56 / lotteria boilerplate, /claim + /redeem + /redeem/status/:lockId are already there. For the new profile + history hooks, add:

// Nest example — server-side
@Controller('users')
export class UsersController {
  @Get('me')
  @UseGuards(JwtGuard)
  async me(@AuthUser() user): Promise<UserProfileResponse> {
    const offChain = await this.ledger.getBalance(user.walletAddress);
    const onChain = await this.publicClient.readContract({
      address: PT_ADDRESS,
      abi: ERC20_ABI, functionName: 'balanceOf', args: [user.walletAddress],
    });
    return {
      offChainBalance: offChain.toString(),
      onChainBalance: onChain.toString(),
      totalBalance: (offChain + onChain).toString(),
      isMinter: false,
    };
  }

  @Get('me/history')
  @UseGuards(JwtGuard)
  async history(
    @AuthUser() user,
    @Query('limit') limit = '50',
    @Query('offset') offset = '0',
  ): Promise<HistoryResponse> {
    const { rows, hasMore } = await this.ledger.getJournal({
      userAddress: user.walletAddress,
      limit: +limit,
      offset: +offset,
    });
    return { entries: rows, hasMore };
  }
}

Framework-agnostic (Vue, Svelte, vanilla)

Skip the hooks, use the clients directly:

import { PafiIssuerClient } from '@pafi-dev/issuer-fe';

const issuer = new PafiIssuerClient({
  baseUrl: ISSUER_API,
  getAccessToken: async () => currentSessionToken,
});
const { txHash } = await issuer.claim({ amount: '100000000000000000000' });

Error handling

| Error | Where | Meaning | |---|---|---| | PafiIssuerHttpError (400 insufficient_balance) | usePafiIssuer.claim | Off-chain ledger balance < amount | | PafiIssuerHttpError (401) | any issuer call | Session token expired — refresh + retry |

Development

pnpm install
pnpm --filter @pafi-dev/issuer-fe build
pnpm --filter @pafi-dev/issuer-fe test

License

UNLICENSED — internal PAFI SDK.