@thetanuts-finance/widget
@thetanuts-finance/widget
Embeddable Thetanuts trading widgets, shipped as framework-agnostic Web
Components. The first widget is <thetanuts-updown> — a compact "Up or
Down" options card (BTC, SOL, ETH and more) that fills instantly against the
Thetanuts OptionBook on Base.
- Works in any framework — React, Vue, Svelte, Angular, or plain HTML. It's a native custom element, so no framework runtime is shipped to your host.
- Style-isolated — renders in Shadow DOM; your CSS and the widget's never collide.
- Two ways to embed — a
<script>tag with a globalmount(), or an npm import. - Host-owned wallet — you pass in an EIP-1193 provider; the widget never bundles a wallet UI.

Install
npm install @thetanuts-finance/widget
or use the standalone build straight from a CDN (no bundler needed):
<script src="https://unpkg.com/@thetanuts-finance/widget/standalone"></script>
Quick start
1. Plain HTML / vanilla JS (<script>)
<script src="https://unpkg.com/@thetanuts-finance/widget/standalone"></script>
<!-- declaratively -->
<thetanuts-updown asset="SOL"></thetanuts-updown>
<!-- or imperatively -->
<div id="card"></div>
<script>
const handle = window.ThetanutsWidgets.updown.mount(
document.getElementById("card"),
{
provider: myEip1193Provider, // e.g. web3-onboard wallet.provider
defaultAsset: "BTC",
onTrade: (t) => console.log("filled", t.txHash),
},
);
// handle.update({ provider: newProvider }); handle.unmount();
</script>
2. React
import "@thetanuts-finance/widget/updown"; // registers <thetanuts-updown>
function Card({ provider }) {
const ref = useRef(null);
useEffect(() => {
if (ref.current) ref.current.provider = provider; // object props set imperatively
}, [provider]);
return <thetanuts-updown ref={ref} default-asset="SOL" />;
}
3. Vue / Svelte / Angular
Import the package once (import "@thetanuts-finance/widget/updown") and use
<thetanuts-updown> in your template. Bind scalar attributes (asset,
referrer) directly; set object props (provider, signer, theme) via a
ref/element reference.
Configuration
Props (React/JS properties) ≡ mount options ≡ kebab-case HTML attributes.
| Prop | Attribute | Type | Default | Notes |
|---|---|---|---|---|
provider |
— | EIP-1193 | — | Host wallet provider. Read + write. |
signer |
— | ethers v6 Signer | — | Alternative to provider. |
referrer |
referrer |
string | Thetanuts referrer | Fee-share address. |
feeBps |
fee-bps |
number | read from broker | Partner fee (bps). |
chainId |
chain-id |
number | 8453 |
Base only today. |
rpcUrl |
rpc-url |
string | public Base RPC | Read fallback if no wallet. |
defaultAsset / asset |
default-asset / asset |
string | first with orders | e.g. "SOL". |
defaultAmount |
default-amount |
number | 1 |
USDC amount prefilled in the input. |
targetMove |
target-move |
number | 10 |
Default % move for the payout scenario ("if {ASSET} +X%"). |
variant |
variant |
"full" | "simple" |
"full" |
simple shows a single prominent payout callout; full shows the detailed quote. |
popup |
popup |
boolean | false |
Render as a floating popup with minimize (→ logo bubble), close, and drag. |
position |
position |
bottom-right | bottom-left | top-right | top-left |
bottom-right |
Corner the popup docks to (popup mode). Users can also drag it; on release it snaps to the nearest side and slides up/down along that edge. Position persists in localStorage. |
assets |
— | string[] | all supported | Allowlist of assets to show. |
theme |
— | object | v4 palette | Color overrides (validated CSS color tokens only). |
apiBaseUrl |
api-base-url |
string | Thetanuts API | See CORS note. |
indexerApiUrl |
indexer-api-url |
string | Thetanuts indexer | See CORS note. |
pricingApiUrl |
pricing-api-url |
string | Thetanuts pricing | See CORS note. |
onTrade |
— | fn | — | Also emits tnw:trade CustomEvent. |
onError |
— | fn | — | Also emits tnw:error CustomEvent. |
Payout is concrete, not "unlimited". A call (Up) has unbounded upside in theory, so instead of showing "Unlimited" the widget shows the payout at a chosen target move — e.g. "if SOL hits +10% ($82.90) you receive ~$5.73" — with a +5% / +10% / +25% selector and the break-even price. The bet amount has a slider + presets ($1/$5/$25/MAX), bounded by the order's capacity (and your balance if a wallet is connected).
Events bubble and cross shadow boundaries:
el.addEventListener("tnw:trade", (e) => console.log(e.detail)); // {txHash, asset, isCall, usdcAmount, numContracts}
el.addEventListener("tnw:error", (e) => console.warn(e.detail)); // {code?, message}
// popup lifecycle:
el.addEventListener("tnw:minimize", () => {});
el.addEventListener("tnw:expand", () => {});
el.addEventListener("tnw:close", () => {});
Requirement: the host must supply a wallet connector
This widget does not include a wallet UI or connect button. It is wallet-provider–agnostic on purpose: the host app owns wallet connection and passes the connected wallet into the widget.
- To trade (approve + fill), the host MUST provide a connected EIP-1193
provider(e.g. from web3-onboard, RainbowKit/wagmi, Reown/WalletConnect, Privy, orwindow.ethereum) or an ethers v6signer. - With no provider/signer, the widget still renders and shows live prices, quotes, and payouts read-only, but the action button reads "Connect wallet in host app" and is disabled.
- The connected wallet must be on Base (chainId 8453); the widget will request a chain switch (
wallet_switchEthereumChain) when needed. - Pass the provider via the
providerproperty / mount option (not an attribute). When the user connects or switches wallets in your app, update it:el.provider = newProvider(orhandle.update({ provider })).
// e.g. web3-onboard
const provider = onboard.state.get().wallets[0]?.provider;
ThetanutsWidgets.updown.mount(el, { provider });
CORS / API access
The Thetanuts market APIs (orders + prices) allow-list only
thetanuts.finance origins. The widget therefore works out-of-the-box when
embedded on a Thetanuts-owned origin.
To embed on any other origin (e.g. your own app), route the API calls through a same-origin proxy and point the widget at it:
mount(el, {
provider,
apiBaseUrl: "/thetanuts/api", // → https://round-snowflake-9c31.devops-118.workers.dev
indexerApiUrl:"/thetanuts/indexer", // → https://indexer.thetanuts.finance/api/v1/book
pricingApiUrl:"/thetanuts/pricing", // → https://pricing.thetanuts.finance
});
examples/dev-proxy.mjs is a minimal Node CORS proxy you can copy for local
development. (Alternatively, ask Thetanuts to add your origin to the API CORS
allow-list.)
How it works
The Up/Down card buys the nearest-expiry, at-the-money call (Up) or put (Down) listed on the OptionBook:
fetchOrders()→ filter to vanilla, USDC-collateral orders for the asset.- Split by call/put, pick the nearest expiry closest to spot.
previewFillOrder()computes contracts / greeks synchronously; the widget derives the concrete payout at your chosen target move, break-even, and the order's capacity (used as the amount slider's max).- On buy: approve USDC →
fillOrderrouted through the PartnerFeeBroker so thereferrerearns a fee split →tnw:trade.
Expiries are Thetanuts' native daily series (08:00 UTC); the card shows the real nearest expiry with a live countdown.
Development
npm install
npm run build # → dist/ (ESM + CJS + IIFE + d.ts)
npm run typecheck
npm run smoke # read-only check against Base mainnet (no wallet)
# examples (in separate terminals):
node examples/dev-proxy.mjs # CORS proxy on :8811
npx serve . # or any static server, then open examples/vanilla.html
Bundle size: the standalone IIFE bundles Lit + ethers v6 + the Thetanuts SDK
(~753 KB raw, ~230 KB gzipped). The npm/ESM build keeps ethers external, so
apps that already ship ethers pay far less.
Security note: the widget opens no WebSocket, so the transitive ws DoS
advisory (pulled in by the SDK's viem dependency, no upstream fix) is not
reachable from this package's code paths.
License
MIT