npm.io
0.1.0 • Published yesterday

@thetanuts-finance/widget

Licence
MIT
Version
0.1.0
Deps
0
Vulns
0
Weekly
0

@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 global mount(), or an npm import.
  • Host-owned wallet — you pass in an EIP-1193 provider; the widget never bundles a wallet UI.

Up/Down card


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, or window.ethereum) or an ethers v6 signer.
  • 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 provider property / mount option (not an attribute). When the user connects or switches wallets in your app, update it: el.provider = newProvider (or handle.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:

  1. fetchOrders() → filter to vanilla, USDC-collateral orders for the asset.
  2. Split by call/put, pick the nearest expiry closest to spot.
  3. 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).
  4. On buy: approve USDC → fillOrder routed through the PartnerFeeBroker so the referrer earns 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