Embed NLO in three steps
Add NLO's hosted deposit flow to your app in three steps. Your users deposit once through the widget, while NLO's patent-pending orchestration engine scans liquidity pools and vaults, routes capital, and manages the Ultra-Safe strategy behind the scenes.
Add the npm package to your app. Works with React, Vue, Next, Nuxt, and standard JavaScript bundlers.
Go to installation → 02 Register Create your partner slugRun the setup wizard once. It registers your domain, payout wallet, and writes your public slug to `.nlowidget.json`.
Go to partner slug → 03 Mount Render the widgetMount the hosted widget inside your page. NLO handles wallet connection, approvals, deposits, and attribution.
Go to render step →tx_hash. Direct REST APIs, webhooks, and fully
white-labelled flows are planned for later tiers.
What is Ultra-Safe?
Ultra-Safe is the NLO strategy your users deposit into through the widget. NLO handles opportunity scanning, routing, execution, and rebalancing automatically — so you can embed managed DeFi yield without building the infrastructure yourself.
NLO continuously reviews liquidity pools and 1,000+ vault opportunities to identify where capital can be routed.
Your app embeds one hosted flow while NLO aggregates the routing, wallet approvals, deposit execution, and attribution.
Users deposit from their own wallet into an on-chain vault and hold the position themselves. NLO never takes custody of user funds.
1. Install SDK
The SDK is published as an npm package — works in React, Vue, Next.js, Angular, Svelte, and vanilla JS bundlers. Requires Node 18+.
npm install @nlofinance/widget
On a fresh install (no existing .nlowidget.json in the
project root) the package prints a one-line banner reminding you to
run the signup wizard:
👋 Welcome to the NLO partner widget.
Run this to set up your partner account:
npx nlo-init
The banner is suppressed on subsequent installs (when
.nlowidget.json exists) and on non-interactive
installs (npm ci, Docker, CI).
Once installed and your partner slug is created, jump to 3. Render the widget for copy-paste-ready React or Vue components.
2. Create partner slug
npx nlo-init is an interactive wizard shipped with the
SDK. Run it once per project, in a real terminal:
npx nlo-init
What it prompts for
| Field | Validation | Notes |
|---|---|---|
company_name |
2–64 chars | Drives your generated slug (e.g. "Your Company" → your-company). Collisions auto-suffix to your-company-2, etc. |
website_url |
Valid http(s):// URL |
The origin allowed to render the widget. The iframe's Origin check enforces this — only requests from this domain attribute commissions to your account. |
payout_wallet |
0x + 40 hex chars |
EVM address where NLO will pay accrued commissions. Validated against /^0x[0-9a-fA-F]{40}$/. |
contact_email |
Standard email format | For payout notifications and account recovery. Not displayed publicly. |
Wizard output
$ npx nlo-init
[NLO Widget] 👋 Welcome. Let's set up your NLO partner account.
? Company name: … Your Company
? Production domain (https://...): … https://example.com
? Payout wallet (0x... 42 chars): … 0x0000000000000000000000000000000000000000
? Contact email: … you@example.com
[NLO Widget] Submitting signup to https://nlo.finance …
[NLO Widget] ✓ Done! Your partner slug: your-company
[NLO Widget] ✓ Saved to .nlowidget.json (safe to commit — slug is public).
[NLO Widget] Use it in your code:
import NLOWidget from "@nlofinance/widget";
NLOWidget.mount("#nlo-widget", {
partner: "your-company",
title: "Your Company",
});
What gets written: .nlowidget.json
{
"slug": "your-company",
"company_name": "Your Company",
"website_url": "https://example.com",
"payout_wallet": "0x0000000000000000000000000000000000000000",
"contact_email": "you@example.com",
"signed_up_at": "2026-05-28T10:14:31.000Z",
"source": "self-service"
}
Commit this file to git. The slug is public — it's in your bundle anyway when you mount the widget. Committing it means every developer / CI run uses the same slug without having to re-register.
Safe to re-run
-
Existing config. If
.nlowidget.jsonalready exists and has a slug, the wizard exits with "Already configured" — no prompts, no API call. - Same domain, different machine. If your domain is already registered (e.g. a coworker ran the wizard first), the backend returns the existing slug and the wizard saves it automatically. Same slug for every dev on the team — no duplicate rows.
-
Non-interactive context. CI / Docker / piped
stdin /
npm ciall skip the wizard silently. Runnpx nlo-initmanually in a real terminal once (commit the result), then CI will use the committed config.
3. Render the widget
Drop one of these components into your project. The slug
comes from .nlowidget.json (written by
npx nlo-init) so partners never hard-code it.
The widget auto-sizes its own iframe (SDK
v1.0.0+) — you don't need to wire onResize or
track height in component state. Just mount it.
Functional component with useEffect for mount /
unmount cleanup. Works in CRA, Vite + React, Next.js app
router, and Next.js pages router.
1. The component
import { useEffect, useRef } from "react";
import NLOWidget from "@nlofinance/widget";
import config from "../.nlowidget.json"; // adjust path to your project root
export default function NLODeposit() {
const hostRef = useRef(null);
useEffect(() => {
const handle = NLOWidget.mount(hostRef.current, {
partner: config.slug,
title: config.company_name,
});
return () => handle.unmount();
}, []);
return <div ref={hostRef} style={{ width: 480, maxWidth: "100%" }} />;
}
2. Use it on a page
import NLODeposit from "./components/NLODeposit";
export default function App() {
return (
<main>
<h1>Deposit into NLO Ultra-Safe</h1>
<NLODeposit />
</main>
);
}
"use client"
at the top of NLODeposit.jsx — the widget uses
window and must run client-side.
Single-file component using the Composition API
(<script setup>). Works in Vite + Vue, Nuxt 3
(inside <ClientOnly>), and any standard
Vue 3 project.
1. The component
<script setup>
import { onBeforeUnmount, onMounted, ref } from "vue";
import NLOWidget from "@nlofinance/widget";
import config from "../../.nlowidget.json"; // adjust path to your project root
const hostEl = ref(null);
let handle = null;
onMounted(() => {
handle = NLOWidget.mount(hostEl.value, {
partner: config.slug,
title: config.company_name,
});
});
onBeforeUnmount(() => {
if (handle) handle.unmount();
});
</script>
<template>
<div ref="hostEl" style="width: 480px; max-width: 100%"></div>
</template>
2. Use it on a page
<script setup>
import NLODeposit from "./components/NLODeposit.vue";
</script>
<template>
<main>
<h1>Deposit into NLO Ultra-Safe</h1>
<NLODeposit />
</main>
</template>
<ClientOnly> in
your page template so SSR doesn't try to render the widget
(it needs window).
No framework? For a quick prototype, mount it from an inline
<script type="module">. For production apps,
prefer the npm package so your build can pin versions and
bundle the SDK normally.
<div id="nlo-widget" style="width: 480px; max-width: 100%"></div>
<script type="module">
import NLOWidget from "https://cdn.jsdelivr.net/npm/@nlofinance/widget@1/dist/index.esm.js";
// Inline the slug from .nlowidget.json — for plain HTML you can't
// import JSON natively, so paste the value:
const SLUG = "your-slug-from-nlowidget-json";
NLOWidget.mount("#nlo-widget", {
partner: SLUG,
title: "Your App",
});
</script>
Tip: if you're bundling with Webpack / Rollup /
esbuild, change the import to
import NLOWidget from "@nlofinance/widget"
and let your bundler resolve it.
Mount options
NLOWidget.mount(target, options) mounts the hosted iframe
inside the target element and returns a handle. Call
handle.unmount() to clean up when your framework
component unmounts.
Options
| Option | Type | Description |
|---|---|---|
partner | string or object required | Your partner slug (e.g. "demo") or a signup object. Most projects read config.slug from .nlowidget.json. |
embedOrigin | string | Override the iframe origin. Default: "https://nlo.finance". Useful only for staging or local backend testing. |
width | string | Iframe width. Default: "100%". |
height | string | Initial iframe height before the first resize event. Bare numbers become px. Default: "620". |
autoResize | boolean | Auto-size the iframe to its content. Default: true. Set false for fixed-height iframes; onResize still fires. |
title | string | Optional display title shown above VIA NLO in the widget header. Display-only; does not affect attribution, routing, fees, or deposits. |
onReady | function | Fires after the iframe loads /widget/config and is ready for input. Payload: { partner, supported_chains }. |
onWalletConnect | function | Fires when user connects. Payload: { wallet }. |
onWalletDisconnect | function | Fires when user disconnects. No payload. |
onAssetSelect | function | User picked an asset. Payload: { chain, token_symbol }. |
onDepositSubmitted | function | Quote returned before signing. Payload: { chain, token_symbol, amount_display, amount_raw, wallet }. |
onDepositComplete | function | Deposit is recorded on NLO's backend. Payload: { chain, token_symbol, amount_display, amount_raw, tx_hash, deposit_uuid, wallet }. |
onDepositFailed | function | Any failure. Payload: { step, message, chain?, wallet? }. |
onResize | function | Iframe height changed. Payload: height_px number. |
Connect Wallet & Deposit Flow
Here's what the user sees end-to-end. The widget handles wallet connection (Reown AppKit — MetaMask, WalletConnect, Coinbase Wallet), chain switching, ERC-20 approval, and the deposit transaction. Your page does not need to listen for events unless you want custom analytics, quest logic, or post-deposit UI.
User taps Connect — Reown AppKit opens (MetaMask, WalletConnect, Coinbase). Attribution binds to your partner slug automatically.
Live balances per (token, chain). The user picks an asset and enters the amount they want to deposit.
One tap handles chain switch, ERC-20 approval, and the deposit. The widget shows the on-chain tx_hash on success.
Step-by-step
- Connect wallet. Tap Connect wallet → Reown AppKit modal opens with MetaMask, WalletConnect QR, Coinbase Wallet, etc. The widget binds the connected wallet to your partner account (Tier 2 attribution) automatically.
-
Pick an asset. The widget shows live balances for
every supported
(token, chain)combo. User picks an asset and types an amount. - Approve + Deposit. One tap. The widget handles chain switching, the ERC-20 approve transaction (if not already approved), and the deposit transaction in sequence. The user signs in their wallet; the widget polls for confirmations and records the deposit on NLO's backend.
-
Success. The widget shows a success screen with the
on-chain tx hash. If you wired optional events,
onDepositCompletealso gives you{ tx_hash, chain, token_symbol, amount_display, amount_raw, deposit_uuid, wallet }.
Optional events
You do not need events for a basic embed. Use them only if your app wants to update UI, send analytics, advance a quest, or run custom post-deposit logic. For a TaskOn-style placement, you can skip this section entirely.
onDepositComplete arrives, your
page can miss it. Do not use callbacks as your only source of truth
for quests, rewards, or commission accounting — reconcile important
actions against the on-chain tx_hash.
NLOWidget.mount("#nlo-widget", {
partner: "your-slug",
title: "Your App",
onDepositComplete: (e) => {
// Optional: use the real on-chain tx hash for your own UX or analytics.
console.log("deposit complete:", e.tx_hash);
},
onDepositFailed: (e) => {
// Optional: capture this for debugging or support.
console.warn("deposit failed at", e.step, "-", e.message);
},
});
Set the display title
The V1 widget keeps customization intentionally small. Partners can
set a display title for the header above
VIA NLO. Deeper visual customization is handled by NLO
during partner onboarding so the deposit flow stays readable,
compliant, and easy to support.
NLOWidget.mount("#nlo-widget", {
partner: "your-slug",
title: "Your App",
});