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 🙏

© 2025 – Pkg Stats / Ryan Hefner

ensc-pay

v2.1.0

Published

ENSC Pay is a lightweight, embeddable React library for ENSC on/off-ramp on Lisk.

Readme

# ensc-pay

[![npm](https://img.shields.io/npm/v/ensc-pay.svg)](https://www.npmjs.com/package/ensc-pay)
[![minzip](https://img.shields.io/bundlephobia/minzip/ensc-pay?label=minzip)](https://bundlephobia.com/package/ensc-pay)
![react](https://img.shields.io/badge/React-18+-61dafb?logo=react&logoColor=white)
[![license](https://img.shields.io/npm/l/ensc-pay.svg)](./LICENSE)

**ENSC Pay** is an embeddable set of React components for **on-ramp** (fund) and **off-ramp** (redeem → bank payout) on **Lisk**, powered by **Reown AppKit** + **Wagmi**, with networking via `ensc-ts-sdk`.

- 🪄 Drop-in UI: `<ENSCRamp/>` (on-ramp), `<ENSCRedeem/>` (off-ramp)
- 🧩 One flexible widget: `<ENSCPayWidget/>` with **tabs** or **stacked** layouts
- 🔌 Wallet connect via **Reown AppKit**
- 🔐 Full flow: **Approve → Transfer** (on-ramp) / **Approve → Redeem → Withdraw** (off-ramp)
- 🎨 Tailwind v4 styles (overridable), responsive and accessible
- 🧱 Re-exports everything from `ensc-ts-sdk` for convenience

> **Note on Withdrawal (off-ramp):** the **withdrawal** step is a **bank transfer** performed off-chain. You’ll see **blockchain links** for *Approval* and *Redemption*, and a **Verify Withdrawal** link (using a `tx_ref`) for the bank transfer.

---

## Install

```bash
npm i ensc-pay \
  @reown/appkit @reown/appkit-adapter-wagmi \
  @tanstack/react-query wagmi viem \
  framer-motion @headlessui/react lucide-react react react-dom

ensc-pay declares AppKit/Wagmi and ensc-ts-sdk as peer dependencies to avoid duplicates.


Styles

Import the generated CSS once in your app:

import 'ensc-pay/ensc.css';

Quick start

import React from 'react';
import { ENSCRamp, ENSCRedeem } from 'ensc-pay';
import 'ensc-pay/ensc.css';

const creds = {
  apiKey:        import.meta.env.VITE_ENSC_API_KEY!,
  encryptionKey: import.meta.env.VITE_ENSC_ENC_KEY!,     // base64 32B
  privateKey:    import.meta.env.VITE_ENSC_SIGNING_KEY!, // PEM (Ed25519)
};

export default function Page() {
  return (
    <div className="max-w-xl mx-auto p-6 space-y-10">
      {/* On-ramp (deposit) */}
      <ENSCRamp
        {...creds}
        recipient="0xReceiverWallet..."
        defaultAmount="2500"
        onSuccess={(d) => console.log('Ramp success', d)}
        onError={(e) => console.error('Ramp error', e)}
      />

      {/* Off-ramp (redeem → bank) */}
      <ENSCRedeem
        {...creds}
        recipient="0xYourWallet..."
        amount="5000"
        payoutEmail="[email protected]"
        bank="058"                // GTBank (example)
        accountNumber="0123456789"
        onSuccess={(d) => console.log('Redeem success', d)}
        onError={(e) => console.error('Redeem error', e)}
      />
    </div>
  );
}

The exported components already include providers for Toasts, TanStack Query, and Wagmi. No extra wrapping required.


The all-in-one widget

<ENSCPayWidget/> shows both flows in tabs (default) or stacked:

import { ENSCPayWidget } from 'ensc-pay';
import 'ensc-pay/ensc.css';

<ENSCPayWidget
  mode="both"          // 'on' | 'off' | 'both' (default)
  layout="tabs"        // 'tabs' (default) | 'stack'
  defaultTab="on"      // 'on' | 'off'
  onRampProps={{ ...creds, recipient: '0xReceiver...' }}
  offRampProps={{
    ...creds, recipient: '0xYou...', amount: '10000',
    payoutEmail: '[email protected]', bank: '058', accountNumber: '0123456789'
  }}
/>
  • mode="on" → only on-ramp UI
  • mode="off" → only off-ramp UI
  • mode="both" + layout="tabs" → users can switch between On-Ramp / Off-Ramp
  • mode="both" + layout="stack" → both are visible, stacked on the page

AppKit projectId

Provide your Reown AppKit projectId before the app loads:

<script>
  window.__ENSC_APPKIT_PROJECT_ID = 'your-appkit-project-id';
</script>

or via env at build time (if your bundler exposes it similarly):

REOWN_PROJECT_ID=your-appkit-project-id

Props (API)

<ENSCRamp />

type RampClasses = {
  container?: string; leftPanel?: string; rightPanel?: string; heroWrap?: string; heroBadge?: string; heroTitle?: string; heroSub?: string;
  card?: string; cardHeader?: string; networkPill?: string; row?: string; label?: string; chip?: string; cta?: string; error?: string;
  field?: string; fieldLabel?: string; input?: string; summaryRow?: string; summaryKey?: string; summaryVal?: string; help?: string;
};

type ENSCRampProps = {
  apiKey: string;
  encryptionKey: string;   // base64 32B
  privateKey: string;      // PEM Ed25519
  recipient: string;       // receiver wallet
  defaultAmount?: string;  // prefill ₦ amount
  onSuccess: (d: { email: string; amount: string; txHash: string; network: string }) => void;
  onError: (err: any) => void;
  /** set true to opt-out of default styles and supply your own via `classes` */
  unstyled?: boolean;
  /** targeted className overrides when NOT unstyled */
  classes?: Partial<RampClasses>;
};

<ENSCRedeem />

type RedeemClasses = {
  container?: string; leftPanel?: string; rightPanel?: string; heroWrap?: string; heroBadge?: string; heroTitle?: string; heroAmount?: string;
  card?: string; steps?: string; step?: string; stepBadge?: string; stepTitle?: string; txLink?: string; fieldWrap?: string; label?: string; input?: string;
  cta?: string; error?: string; refBox?: string; refRow?: string;
};

type ENSCRedeemProps = {
  apiKey: string;
  encryptionKey: string;   // base64 32B
  privateKey: string;      // PEM Ed25519
  recipient: string;       // wallet submitting the tx
  amount: string;          // ₦ amount to redeem
  payoutEmail: string;
  bank: string;            // bank code (e.g. "058")
  accountNumber: string;   // 10-digit NUBAN
  /** optional hook to pre-validate bank details before continuing */
  validateBankDetails?: (p: { email: string; bank: string; accountNumber: string }) =>
    Promise<{ ok: boolean; accountName?: string; message?: string }>;
  onSuccess: (d: {
    approveTxHash: string;
    redeemTxHash: string;
    pvRef: string;
    payout: { accountName: string; bank: string; accountNumber: string };
    withdrawResponse: any;
  }) => void;
  onError: (err: any) => void;
  unstyled?: boolean;
  classes?: Partial<RedeemClasses>;
};

<ENSCPayWidget />

type Mode = 'on' | 'off' | 'both';
type Layout = 'tabs' | 'stack';

type ENSCPayProps = {
  mode?: Mode;                      // default 'both'
  layout?: Layout;                  // default 'tabs'
  onRampProps?: ENSCRampProps;     // pass-through
  offRampProps?: ENSCRedeemProps;  // pass-through
  defaultTab?: 'on' | 'off';       // default 'on'
};

How the Off-Ramp Withdrawal Works (Important)

  • Approval and Redemption are on-chain transactions on Lisk, each with a blockchain transaction hash.

  • Withdrawal is off-chain (a bank transfer via Flutterwave). There is no blockchain hash for this step.

  • The UI therefore shows:

    • View Approval → opens the Lisk explorer with the approval tx hash
    • View Redemption → opens the Lisk explorer with the redeem tx hash
    • Verify Withdrawal → opens your backend GET /withdraw/verify?tx_ref=... using the tx_ref returned by Flutterwave
  • In the onSuccess payload for <ENSCRedeem/>, the full withdrawal details (including tx_ref) are available inside withdrawResponse.


Theming / overrides

  • Out of the box the UI uses our Tailwind v4 classes and CSS variables.

  • You can:

    • Pass classes to override specific parts (keeps layout & spacing), or
    • Set unstyled={true} and supply entirely custom classes.

Example (override only the primary CTA):

<ENSCRamp
  {...creds}
  recipient="0x..."
  classes={{ cta: 'w-full mt-3 rounded-xl bg-pv-g400 hover:brightness-95 text-white px-4 py-3' }}
/>

Accessibility

  • The tab switcher in <ENSCPayWidget /> uses native role="tablist"/role="tab"/role="tabpanel", keyboard-focusable and ARIA-labelled.
  • All interactive elements are reachable via keyboard; focus styles are visible.

CSP (Content-Security-Policy)

If your app runs a strict CSP, allow at least:

connect-src 'self'
  https://ensc.prosperavest.com
  https://*.reown.com
  https://rpc.walletconnect.com https://relay.walletconnect.com wss://relay.walletconnect.com
  https://api.web3modal.com
  https://cca-lite.coinbase.com
  https://rpc.api.lisk.com
;
script-src 'self' 'unsafe-inline' blob: https://challenges.cloudflare.com https://*.reown.com;
img-src 'self' data: blob:;
font-src 'self' https://fonts.gstatic.com data:;
frame-src 'self' https://challenges.cloudflare.com;
worker-src 'self' blob:;

Adjust for your infra.


Re-exported SDK

You can import directly from this package:

import { ENSCClient, Asset } from 'ensc-pay';       // re-exports from ensc-ts-sdk
// same as:
import { ENSCClient, Asset } from 'ensc-ts-sdk';

Errors & UX

Common errors are surfaced inline with helpful text:

  • User rejected the request (from wallet / viem):

    User denied transaction signature.
  • Wrong network: Users are prompted to switch to Lisk before continuing.

  • Bank details invalid: Hook into validateBankDetails (off-ramp) to block continuation with a custom message before hitting your backend.


Security notes

  • Do not commit credentials. Read keys from env or secure vaults.

  • Backend should enforce:

    • API key allow-listing / rotation
    • Rate-limits & request signing verification
    • No secrets in logs (client and server)
  • Consider Content Security Policy (above) and Subresource Integrity for any external scripts you load.


Development

npm i
npm run dev    # Vite dev server
npm run build  # library build + d.ts + CSS

Versioning

SemVer.

  • Patch: internal fixes
  • Minor: backward-compatible props/UX changes
  • Major: breaking props or behavior changes

License

MIT © ProsperaVest


Changelog

See CHANGELOG.md in this repo for detailed release notes.