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

@payxor/react

v0.1.2

Published

React hooks and components for PayXor

Downloads

7

Readme

@payxor/react

React hooks and unstyled UI components for integrating PayXor payments into your application. All components are headless/unstyled—bring your own styling via className and slot-level class overrides.

Table of Contents

Installation

pnpm add @payxor/react @payxor/sdk @payxor/types
npm install @payxor/react @payxor/sdk @payxor/types
yarn add @payxor/react @payxor/sdk @payxor/types

Peer dependencies: react, react-dom, viem

Quick Start

import { useTokenPayment, usePayment, DropdownBuyButton } from "@payxor/react";
import { useAccount, useChainId, usePublicClient, useWalletClient } from "wagmi";

function BuyButton({ appId, productId, apiUrl }: Props) {
  const { address } = useAccount();
  const chainId = useChainId();
  const publicClient = usePublicClient();
  const { data: walletClient } = useWalletClient();

  const {
    stablecoins,
    selectedToken,
    setSelectedToken,
    needsApproval,
    approve,
    approving,
  } = useTokenPayment({
    appId,
    productId,
    address,
    chainId,
    publicClient,
    walletClient,
    apiUrl,
  });

  const { executePayment, loading } = usePayment({
    appId,
    productId,
    tokenAddress: selectedToken,
    address,
    chainId,
    publicClient,
    walletClient,
    apiUrl,
  });

  return (
    <DropdownBuyButton
      stablecoins={stablecoins}
      selectedToken={selectedToken}
      onSelect={setSelectedToken}
      needsApproval={needsApproval}
      onApprove={approve}
      onBuy={executePayment}
      loading={loading}
      approving={approving}
    />
  );
}

Hooks

usePayXorClient

Creates a memoized PayXor SDK client instance.

import { usePayXorClient } from "@payxor/react";

const client = usePayXorClient({
  apiUrl: "https://api.payxor.xyz",
  walletClient,
});

Options:

| Option | Type | Description | |--------|------|-------------| | apiUrl | string | PayXor API URL | | walletClient | WalletClient \| null | Viem wallet client |

Returns: PayXorClient | null


useTokenPayment

Manages token selection, balance checking, allowance checking, and approval flow for a product purchase.

import { useTokenPayment } from "@payxor/react";

const {
  stablecoins,
  selectedToken,
  setSelectedToken,
  productAmount,
  allowance,
  balance,
  needsApproval,
  hasSufficientBalance,
  loading,
  approving,
  error,
  approve,
} = useTokenPayment({
  appId: "your-app-id",
  productId: "product-id",
  address,
  chainId,
  publicClient,
  walletClient,
  apiUrl: "https://api.payxor.xyz",
  enabled: true,
});

Options:

| Option | Type | Default | Description | |--------|------|---------|-------------| | appId | string | — | Your PayXor app ID | | productId | string | — | Product ID to purchase | | address | Address \| null | — | Connected wallet address | | chainId | number \| null | — | Current chain ID | | publicClient | PublicClient \| null | — | Viem public client | | walletClient | WalletClient \| null | — | Viem wallet client | | apiUrl | string | — | PayXor API URL | | enabled | boolean | true | Enable/disable the hook |

Returns:

| Property | Type | Description | |----------|------|-------------| | stablecoins | StablecoinConfig[] | Available stablecoins for the app | | selectedToken | Address \| null | Currently selected token address | | setSelectedToken | (address: Address) => void | Token selection handler | | productAmount | bigint | Product price in token units | | allowance | bigint \| null | Current token allowance | | balance | bigint \| null | User's token balance | | needsApproval | boolean | Whether approval is required | | hasSufficientBalance | boolean | Whether user has enough balance | | loading | boolean | Loading state | | approving | boolean | Approval transaction in progress | | error | Error \| null | Any error that occurred | | approve | () => Promise<string> | Trigger token approval |


usePayment

Handles the complete payment flow: quote fetching, transaction execution, and confirmation.

import { usePayment } from "@payxor/react";

const {
  executePayment,
  loading,
  status,
  error,
  txHash,
  explorerUrl,
  waitingForConfirmation,
  reset,
} = usePayment({
  appId: "your-app-id",
  productId: "product-id",
  tokenAddress: selectedToken,
  address,
  chainId,
  publicClient,
  walletClient,
  apiUrl: "https://api.payxor.xyz",
  onSuccess: (txHash) => console.log("Payment successful:", txHash),
  onError: (error) => console.error("Payment failed:", error),
  getExplorerUrl: (hash, chainId) => `https://etherscan.io/tx/${hash}`,
});

Options:

| Option | Type | Description | |--------|------|-------------| | appId | string | Your PayXor app ID | | productId | string | Product ID to purchase | | tokenAddress | Address \| null | Selected payment token | | address | Address \| null | Connected wallet address | | chainId | number \| null | Current chain ID | | publicClient | PublicClient \| null | Viem public client | | walletClient | WalletClient \| null | Viem wallet client | | apiUrl | string | PayXor API URL | | onSuccess | (txHash: string) => void | Success callback | | onError | (error: Error) => void | Error callback | | getExplorerUrl | (txHash: string, chainId: number) => string \| null | Block explorer URL builder |

Returns:

| Property | Type | Description | |----------|------|-------------| | executePayment | () => Promise<void> | Execute the payment | | loading | boolean | Transaction in progress | | status | string | Human-readable status message | | error | Error \| null | Any error that occurred | | txHash | string \| null | Transaction hash | | explorerUrl | string \| null | Block explorer URL | | waitingForConfirmation | boolean | Waiting for tx confirmation | | reset | () => void | Reset hook state |


useTokenApproval

Standalone hook for managing token approval (useful when building custom flows).

import { useTokenApproval } from "@payxor/react";

const {
  allowance,
  balance,
  needsApproval,
  hasSufficientBalance,
  loading,
  approving,
  error,
  approve,
} = useTokenApproval({
  tokenAddress: "0x...",
  requiredAmount: 1000000n,
  address,
  chainId,
  publicClient,
  walletClient,
  apiUrl,
  enabled: true,
});

Options:

| Option | Type | Default | Description | |--------|------|---------|-------------| | tokenAddress | Address \| null | — | Token to check/approve | | requiredAmount | bigint | — | Amount required for the transaction | | address | Address \| null | — | Connected wallet address | | chainId | number \| null | — | Current chain ID | | publicClient | PublicClient \| null | — | Viem public client | | walletClient | WalletClient \| null | — | Viem wallet client | | apiUrl | string | — | PayXor API URL | | enabled | boolean | true | Enable/disable the hook |


useTokenStatuses

Computes token status entries for display in selectors.

import { useTokenStatuses } from "@payxor/react";

const tokenStatuses = useTokenStatuses({
  stablecoins,
  selectedToken,
  balance,
  allowance,
  requiredAmount: productAmount,
});

// Result: Record<string, TokenStatusEntry>
// {
//   "0x...": { balance: 1000000n, allowance: 500000n, requiredAmount: 100000n }
// }

useFeatureStatus

Checks if a user has unlocked a permanent feature/entitlement. Automatically polls until the feature is unlocked.

import { useFeatureStatus } from "@payxor/react";

const { isUnlocked, loading, error } = useFeatureStatus({
  appId: "your-app-id",
  entitlementId: "premium-feature",
  address,
  chainId,
  walletClient,
  apiUrl,
  enabled: true,
  pollInterval: 5000, // ms
});

if (isUnlocked) {
  return <PremiumContent />;
}

Options:

| Option | Type | Default | Description | |--------|------|---------|-------------| | appId | string | — | Your PayXor app ID | | entitlementId | string | — | Feature/entitlement ID | | address | Address \| null | — | Connected wallet address | | chainId | number \| null | — | Current chain ID | | walletClient | WalletClient \| null | — | Viem wallet client | | apiUrl | string | — | PayXor API URL | | enabled | boolean | true | Enable/disable the hook | | pollInterval | number | 5000 | Polling interval in ms |


useSessionStatus

Checks if a user has an active time-based session. Continuously polls to track session expiration.

import { useSessionStatus } from "@payxor/react";

const { isActive, loading, error } = useSessionStatus({
  appId: "your-app-id",
  productId: "hourly-access",
  address,
  chainId,
  walletClient,
  apiUrl,
  enabled: true,
  pollInterval: 5000,
});

if (isActive) {
  return <SessionContent />;
}

Components

All components are unstyled. Use className and slotClassNames props to apply your styles.

Button

Basic button with loading state support.

import { Button } from "@payxor/react";

<Button
  onClick={handleClick}
  loading={isLoading}
  disabled={isDisabled}
  loadingIndicator={<Spinner />}
  loadingIndicatorClassName="animate-spin"
  className="px-4 py-2 rounded bg-blue-600 text-white"
>
  Pay Now
</Button>

Props:

| Prop | Type | Description | |------|------|-------------| | loading | boolean | Shows loading state | | loadingIndicator | ReactNode | Custom loading indicator | | loadingIndicatorClassName | string | Class for loading indicator | | className | string | Button class name | | children | ReactNode | Button content | | ...rest | ButtonHTMLAttributes | Standard button props |


StatusMessage

Displays status messages with optional icon, link, and dismiss button.

import { StatusMessage } from "@payxor/react";

<StatusMessage
  type="success"
  message="Payment completed!"
  link={{ url: explorerUrl, text: "View transaction" }}
  onDismiss={() => setMessage(null)}
  className="flex items-center gap-2 p-4 rounded border"
  icons={{
    success: "✅",
    error: "❌",
    info: "ℹ️",
    loading: "⏳",
  }}
/>

Props:

| Prop | Type | Description | |------|------|-------------| | type | "info" \| "success" \| "error" \| "loading" | Message type | | message | string | Message text | | icon | ReactNode | Override icon for current type | | icons | Record<StatusType, ReactNode> | Icons for each type | | link | { url: string, text: string } | Optional link | | onDismiss | () => void | Dismiss handler | | className | string | Container class | | iconClassName | string | Icon class | | messageClassName | string | Message class | | linkClassName | string | Link class | | dismissButtonClassName | string | Dismiss button class |


StatusBadge

Displays feature unlock or session status.

import { StatusBadge } from "@payxor/react";

// Feature status
<StatusBadge
  status={isUnlocked}
  type="feature"
  className="inline-flex items-center gap-2 px-3 py-1 rounded-full"
  icons={{
    unlocked: "🔓",
    locked: "🔒",
    checking: "⏳",
  }}
/>

// Session status with time remaining
<StatusBadge
  status={isActive}
  type="session"
  timeRemaining={3600}
  labels={{
    active: "Session Active",
    inactive: "Session Expired",
    timeRemainingPrefix: "Expires in:",
  }}
/>

Props:

| Prop | Type | Description | |------|------|-------------| | status | boolean \| null | Current status (null = checking) | | type | "feature" \| "session" | Badge type | | timeRemaining | number \| null | Seconds remaining (sessions only) | | labels | StatusBadgeLabels | Custom label text | | icons | StatusBadgeIcons | Custom icons | | className | string | Container class | | iconClassName | string | Icon class | | textClassName | string | Text class | | subtextClassName | string | Subtext class |


StablecoinSelector

Token selection grid/list with balance and approval status indicators.

import { StablecoinSelector } from "@payxor/react";

<StablecoinSelector
  stablecoins={stablecoins}
  selectedToken={selectedToken}
  onSelect={setSelectedToken}
  tokenStatuses={tokenStatuses}
  disabled={loading}
  className="space-y-2"
  slotClassNames={{
    list: "grid gap-2",
    listLabel: "text-sm font-medium",
    tokenButton: "p-3 border rounded hover:border-blue-500",
    tokenButtonSelected: "border-blue-500 bg-blue-50",
    tokenButtonDisabled: "opacity-50 cursor-not-allowed",
    tokenSymbol: "font-bold",
    tokenName: "text-sm text-gray-500",
    tokenBalance: "text-xs",
    tokenBalanceSufficient: "text-green-600",
    tokenBalanceInsufficient: "text-red-600",
    tokenApproval: "text-yellow-600",
    tokenSelectedBadge: "text-xs bg-blue-100 px-2 py-0.5 rounded",
  }}
  labels={{
    selectToken: "Choose payment method:",
    balance: "Balance:",
    insufficient: "(Not enough)",
    approvalNeeded: "(Needs approval)",
  }}
/>

Props:

| Prop | Type | Description | |------|------|-------------| | stablecoins | StablecoinConfig[] | Available tokens | | selectedToken | string \| null | Selected token address | | onSelect | (address: string) => void | Selection handler | | tokenStatuses | Record<string, TokenStatus> | Token status data | | disabled | boolean | Disable selection | | className | string | Container class | | label | ReactNode | Custom label | | labels | StablecoinSelectorLabels | Custom label text | | formatBalance | (balance: bigint, decimals: number) => string | Balance formatter | | slotClassNames | StablecoinSelectorClassNames | Slot classes |


ProductCard

Displays a product with price and purchase button.

import { ProductCard } from "@payxor/react";

<ProductCard
  name="Premium Access"
  description="Unlock all premium features forever"
  price="9.99"
  purchased={isPurchased}
  loading={isLoading}
  onPurchase={handlePurchase}
  className="p-6 border rounded-lg shadow"
  slotClassNames={{
    header: "flex items-center justify-between",
    title: "text-xl font-bold",
    purchasedBadge: "text-sm bg-green-100 text-green-800 px-2 py-1 rounded",
    description: "mt-2 text-gray-600",
    footer: "mt-4 flex items-center justify-between",
    price: "text-2xl font-bold",
    currency: "text-sm text-gray-500",
    action: "px-4 py-2 bg-blue-600 text-white rounded",
  }}
  labels={{
    purchase: "Buy Now",
    purchased: "Owned",
    processing: "Processing...",
    currency: "USD",
  }}
/>

Props:

| Prop | Type | Description | |------|------|-------------| | name | string | Product name | | description | string | Product description | | price | string | Display price | | purchased | boolean | Already purchased | | loading | boolean | Purchase in progress | | onPurchase | () => void | Purchase handler | | className | string | Container class | | labels | ProductCardLabels | Custom label text | | slotClassNames | ProductCardClassNames | Slot classes | | action | ReactNode | Custom action element | | buttonProps | ButtonProps | Props for default button |


DropdownBuyButton

Compact token selector + buy/approve button combo. Automatically switches between "Approve" and "Buy" states based on allowance.

import { DropdownBuyButton } from "@payxor/react";

<DropdownBuyButton
  stablecoins={stablecoins}
  selectedToken={selectedToken}
  onSelect={setSelectedToken}
  needsApproval={needsApproval}
  onApprove={approve}
  onBuy={executePayment}
  loading={loading}
  approving={approving}
  disabled={!address}
  label="Pay with"
  slotClassNames={{
    container: "space-y-2",
    label: "text-sm font-medium",
    row: "flex",
    selectWrapper: "relative",
    select: "border rounded-l px-3 py-2 pr-8 appearance-none",
    selectIcon: "absolute right-2 top-1/2 -translate-y-1/2 pointer-events-none",
    button: "px-4 py-2 bg-blue-600 text-white rounded-r",
  }}
  selectIcon={<ChevronDownIcon className="w-4 h-4" />}
  labels={{
    selectToken: "Select token",
    buyWith: (token) => `Pay ${token?.symbol}`,
    approveWith: (token) => `Approve ${token?.symbol}`,
    optionLabel: (token) => `${token.symbol} - ${token.name}`,
  }}
/>

Props:

| Prop | Type | Description | |------|------|-------------| | stablecoins | StablecoinConfig[] | Available tokens | | selectedToken | string \| null | Selected token address | | onSelect | (address: string) => void | Selection handler | | onBuy | () => void | Buy handler | | onApprove | () => void | Approve handler | | needsApproval | boolean | Force approval state | | tokenStatuses | Record<string, TokenStatus> | Token status (for auto-detection) | | requiredAmount | bigint | Required amount (for auto-detection) | | disabled | boolean | Disable interactions | | loading | boolean | Buy loading state | | approving | boolean | Approve loading state | | className | string | Container class | | label | ReactNode \| null | Label (set null to hide) | | labels | DropdownBuyButtonLabels | Custom label text | | slotClassNames | DropdownBuyButtonClassNames | Slot classes | | selectIcon | ReactNode | Custom dropdown icon | | buttonProps | ButtonProps | Props for the button | | selectProps | SelectHTMLAttributes | Props for the select |

Slot Classes:

  • container - Outer wrapper
  • label - Label element
  • row - Row containing select and button
  • selectWrapper - Wrapper around select (for icon positioning)
  • select - The select element
  • selectIcon - Icon inside select wrapper
  • button - The action button
  • empty - Message when no tokens available

Full Integration Example

Here's a complete example integrating all pieces with wagmi:

import { useAccount, useChainId, usePublicClient, useWalletClient, useConfig } from "wagmi";
import {
  useTokenPayment,
  usePayment,
  useTokenStatuses,
  useFeatureStatus,
  DropdownBuyButton,
  StatusMessage,
  StatusBadge,
} from "@payxor/react";

const API_URL = "https://api.payxor.xyz";

function PremiumFeature({ appId, productId, entitlementId }: Props) {
  const { address } = useAccount();
  const chainId = useChainId();
  const publicClient = usePublicClient();
  const { data: walletClient } = useWalletClient();
  const config = useConfig();

  // Check if already unlocked
  const { isUnlocked, loading: checkingStatus } = useFeatureStatus({
    appId,
    entitlementId,
    address,
    chainId,
    walletClient,
    apiUrl: API_URL,
  });

  // Token and approval management
  const {
    stablecoins,
    selectedToken,
    setSelectedToken,
    productAmount,
    allowance,
    balance,
    needsApproval,
    approving,
    approve,
  } = useTokenPayment({
    appId,
    productId,
    address,
    chainId,
    publicClient,
    walletClient,
    apiUrl: API_URL,
    enabled: !isUnlocked, // Only fetch if not already unlocked
  });

  // Token statuses for display
  const tokenStatuses = useTokenStatuses({
    stablecoins,
    selectedToken,
    balance,
    allowance,
    requiredAmount: productAmount,
  });

  // Payment execution
  const { executePayment, loading, status, error, explorerUrl, reset } = usePayment({
    appId,
    productId,
    tokenAddress: selectedToken,
    address,
    chainId,
    publicClient,
    walletClient,
    apiUrl: API_URL,
    getExplorerUrl: (hash, targetChainId) => {
      const chain = config.chains.find((c) => c.id === targetChainId);
      return chain?.blockExplorers?.default?.url
        ? `${chain.blockExplorers.default.url}/tx/${hash}`
        : null;
    },
  });

  // Already unlocked - show content
  if (isUnlocked) {
    return (
      <div>
        <StatusBadge status={true} type="feature" className="mb-4" />
        <PremiumContent />
      </div>
    );
  }

  return (
    <div className="space-y-4">
      <StatusBadge status={isUnlocked} type="feature" />

      {status && (
        <StatusMessage
          type={error ? "error" : "info"}
          message={status}
          link={explorerUrl ? { url: explorerUrl, text: "View transaction" } : undefined}
          onDismiss={reset}
        />
      )}

      <DropdownBuyButton
        stablecoins={stablecoins}
        selectedToken={selectedToken}
        onSelect={setSelectedToken}
        tokenStatuses={tokenStatuses}
        needsApproval={needsApproval}
        onApprove={approve}
        onBuy={executePayment}
        loading={loading}
        approving={approving}
        disabled={!address || checkingStatus}
      />
    </div>
  );
}

Notes

  • All hooks require apiUrl to be provided for API communication.
  • Hooks are wallet-agnostic—pass address, chainId, publicClient, and walletClient from your wallet setup (wagmi, RainbowKit, etc.).
  • Components are completely unstyled. Use Tailwind, CSS modules, or any styling solution.
  • This package does not include analytics utilities (see @payxor/sdk for analytics).