ensc-pay
v2.1.0
Published
ENSC Pay is a lightweight, embeddable React library for ENSC on/off-ramp on Lisk.
Readme
# ensc-pay
[](https://www.npmjs.com/package/ensc-pay)
[](https://bundlephobia.com/package/ensc-pay)

[](./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-domensc-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 UImode="off"→ only off-ramp UImode="both"+layout="tabs"→ users can switch between On-Ramp / Off-Rampmode="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-idProps (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 thetx_refreturned by Flutterwave
In the
onSuccesspayload for<ENSCRedeem/>, the full withdrawal details (includingtx_ref) are available insidewithdrawResponse.
Theming / overrides
Out of the box the UI uses our Tailwind v4 classes and CSS variables.
You can:
- Pass
classesto override specific parts (keeps layout & spacing), or - Set
unstyled={true}and supply entirely custom classes.
- Pass
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 nativerole="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 + CSSVersioning
- 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.
