@testingb/widget
v0.3.4
Published
NLO partner widget — embed an iframe so your users can connect a wallet and deposit into the NLO Ultra-Safe strategy.
Downloads
947
Maintainers
Readme
@testingb/widget
Embed the NLO deposit widget on your site so your users can connect a wallet and deposit into NLO's Ultra-Safe strategy — without ever leaving your domain. Wallet state, transaction signing, and balances run inside an iframe served by NLO, so they never touch your origin or your code.
📖 Full guide: nlo.finance/widget-docs
Quick start — 3 commands
# 1. Install the SDK
npm install @testingb/widget
# 2. Register your partner account (interactive wizard)
npx nlo-init
# 3. Mount the widget in your codeimport NLOWidget from "@testingb/widget";
import config from "./.nlowidget.json";
NLOWidget.mount("#nlo-widget", { partner: config.slug });That's the entire integration. A fully functional deposit widget renders in the target element.
What npx nlo-init does
The wizard ships with the SDK and runs in your terminal. It prompts for:
| Field | Validation |
|---|---|
| company_name | 2–64 characters — drives your generated slug |
| website_url | Valid http(s):// URL — registered as your allowed origin |
| payout_wallet | 0x + 40 hex chars — Ethereum address for commission payouts |
| contact_email | Standard email — for payout notifications |
It then POSTs to https://nlo.finance/api/v2/widget/signup, returns
your assigned partner slug, and saves it to ./.nlowidget.json in
your project root:
{
"slug": "your-company",
"company_name": "Your Company",
"website_url": "https://example.com",
"payout_wallet": "0x0000000000000000000000000000000000000000",
"contact_email": "[email protected]",
"signed_up_at": "2026-01-15T10:00:00.000Z",
"source": "self-service"
}Commit .nlowidget.json to git. The slug is public — it ends up in
your bundle anyway. Committing it means every developer and CI build
uses the same slug without re-running the wizard.
Safe to re-run
- Existing config — wizard exits with "Already configured".
- Domain already registered — backend returns the existing slug; no duplicates created. Teammates on the same project share one slug.
- Non-interactive context (CI / Docker /
npm ci/ piped stdin) — wizard skips silently. Run it once in a real terminal, commit the result, CI uses the committed config.
Already have a slug?
Pick "Yes, I have a slug" at the first prompt — the wizard skips
signup and just writes the slug to .nlowidget.json.
Framework integration
Drop one of these components into your project. Slug comes from
.nlowidget.json so it's never hard-coded.
React / Next.js
import { useEffect, useRef } from "react";
import NLOWidget from "@testingb/widget";
import config from "../.nlowidget.json";
export default function NLODeposit() {
const hostRef = useRef(null);
useEffect(() => {
const handle = NLOWidget.mount(hostRef.current, {
partner: config.slug,
onDepositComplete: (e) => {
console.log("deposit done:", e.tx_hash);
// Your post-deposit logic (award XP, mark quest complete, etc.)
},
});
return () => handle.unmount();
}, []);
return <div ref={hostRef} style={{ width: 480, maxWidth: "100%" }} />;
}Next.js app router: add
"use client"at the top — the widget needswindowand must run client-side.
Vue 3 / Nuxt
<script setup>
import { onBeforeUnmount, onMounted, ref } from "vue";
import NLOWidget from "@testingb/widget";
import config from "../../.nlowidget.json";
const hostEl = ref(null);
let handle = null;
onMounted(() => {
handle = NLOWidget.mount(hostEl.value, {
partner: config.slug,
onDepositComplete: (e) => console.log("deposit done:", e.tx_hash),
});
});
onBeforeUnmount(() => {
if (handle) handle.unmount();
});
</script>
<template>
<div ref="hostEl" style="width: 480px; max-width: 100%"></div>
</template>Nuxt 3: wrap in
<ClientOnly>so SSR doesn't try to render the widget.
Works in CRA, Vite, Next.js, Angular, Svelte, vanilla JS — same package, no framework-specific build required.
What the user sees
- Connect wallet → Reown AppKit modal opens with MetaMask, WalletConnect QR, Coinbase Wallet, etc.
- After connect, the iframe binds the wallet to your partner account
(Tier 2 attribution) and shows live balances for every supported
(token, chain)pair you have enabled. - User picks an asset → types an amount → clicks Deposit.
- The iframe handles chain switch, ERC-20 approve, the deposit TX, and recording on NLO's backend. Your page receives event callbacks for every step.
API
NLOWidget.mount(target, options) → { unmount }
| Arg | Type | Required |
|---|---|---|
| target | string \| HTMLElement | yes — CSS selector or element |
| options | MountOptions | yes |
MountOptions
| Field | Type | Default | Description |
|---|---|---|---|
| partner | string \| PartnerSignupInfo | — | Required. Either your slug (string), or a PartnerSignupInfo object — the SDK will sign up the partner on first mount and cache the assigned slug in localStorage. Most apps use the slug string read from .nlowidget.json. |
| embedOrigin | string | "https://nlo.finance" | Override the iframe origin. Use for staging or local-backend testing. |
| autoResize | boolean | true | Auto-size the iframe to its content. Set false for fixed-height iframes with internal scroll. Your onResize callback still fires either way. |
| theme | "light" \| "dark" | "light" | Visual theme. |
| width | string | "100%" | Iframe width (any CSS value). |
| height | string | "620" | Initial iframe height (CSS value or bare number → px). Auto-resize tunes it after first render. |
| onReady | (e: ReadyEvent) => void | — | Fires once the iframe has loaded /widget/config and is ready for input. |
| onWalletConnect | (e: WalletConnectEvent) => void | — | Fires after the user connects a wallet. |
| onWalletDisconnect | () => void | — | Fires on disconnect. |
| onAssetSelect | (e: AssetSelectEvent) => void | — | Fires when the user picks a chip on the asset/chain picker. |
| onDepositSubmitted | (e: DepositSubmittedEvent) => void | — | Fires after the deposit quote returns, before the user signs. |
| onDepositComplete | (e: DepositCompleteEvent) => void | — | Fires after the deposit is fully recorded on NLO's backend. |
| onDepositFailed | (e: DepositFailedEvent) => void | — | Fires on any failure in the deposit pipeline. |
| onResize | (height_px: number) => void | — | Fires when the iframe's content height changes. Optional — the SDK already auto-resizes unless you opt out via autoResize: false. |
Returns: { unmount: () => void } — call to remove the iframe and detach the message listener.
Event payload types
type ReadyEvent = {
partner: string; // your partner slug
supported_chains: string[]; // ["bsc", "arbitrum", ...]
};
type WalletConnectEvent = {
wallet: string; // 0x… lowercased
};
type AssetSelectEvent = {
chain: string; // e.g. "bsc"
token_symbol: string; // e.g. "USDT"
};
type DepositSubmittedEvent = {
chain: string;
token_symbol: string;
amount_display: string; // user-typed (e.g. "100")
amount_raw: string; // uint256 in token decimals
wallet: string;
};
type DepositCompleteEvent = {
chain: string;
token_symbol: string;
amount_display: string;
amount_raw: string;
tx_hash: string; // the on-chain deposit TX
deposit_uuid: string | null; // NLO-assigned id
wallet: string;
};
type DepositFailedEvent = {
step: string; // "chain_switch" | "quote" | "allowance"
// | "approve" | "deposit" | "record" | "unknown"
message: string; // human-readable, often the wallet's error
chain?: string;
wallet?: string;
};TypeScript types are bundled — your editor will autocomplete every
option and payload field with no extra @types package needed.
Security
- Iframe isolation. All wallet interaction lives in the iframe under
the
nlo.financeorigin. Reown AppKit, ethers v6, calldata building, and the network calls all execute there — your page never touches a wallet provider. - Origin-pinned messages. postMessage events are origin-pinned to
nlo.finance(or your customembedOrigin). Messages from any other origin are dropped. - Allow-list enforcement. Your partner's
allowed_originslist (set automatically fromwebsite_urlduringnpx nlo-init) is enforced server-side viaContent-Security-Policy: frame-ancestors ...— the widget cannot be embedded on unauthorised pages. - Server-rendered credentials. The iframe runs Reown AppKit with the project ID served from the backend, not hardcoded in static JS — credentials can be rotated without redeploying assets.
Versioning
Follows Semantic Versioning. Breaking changes
never ship inside a major version — pin ^0.3 to follow non-breaking
updates.
License
Apache-2.0
