@synfin/widget
v0.1.3
Published
The Synfin widget: an embeddable, framework-agnostic quote-and-fee widget (a <synfin-widget> custom element, plus a React wrapper) built on @synfin/client. Live best-execution quotes across Canton venues with the partner's disclosed fees. Non-custodial: i
Maintainers
Readme
@synfin/widget
An embeddable quote-and-fee widget for Canton, built on
@synfin/client. Drop it into
any site to show a live best-execution quote across venues with your disclosed
fees. Non-custodial: it holds no keys and signs nothing.
- Docs: https://synfin.xyz/docs/sdk
- Status: v0, the surface may evolve with design partners. Pin a version.
What the widget shows today: a live quote across the Canton venues with your disclosed fees (the 15 bps Synfin service fee + your integrator split). Tradecraft is the executable venue today. The fees are DISCLOSED for display; on-ledger fee collection is flag-gated off today (
FEE_COLLECTION_ENABLED = false) and is wallet-dependent when it flips (see@synfin/client). The widget holds no keys and signs nothing — execution is your wallet's, via@synfin/client.
Install
Two paths, pick one. Both register the same <synfin-widget> custom element.
1. Script tag (no build step, no bundler)
For any site (plain HTML, Vue, Svelte, Angular, Rails, WordPress). One <script>
loads a self-contained bundle (@synfin/client is inlined, so there is no
import map and nothing else to fetch) and registers the element:
<script src="https://unpkg.com/@synfin/widget"></script>
<synfin-widget
api-key="sk_live_..."
fee-bps="30"
fee-recipient="you::1220..."
from="CC" to="USDCx" amount="100"
></synfin-widget>Pin a version in production (the CDN caches it and the API stays stable):
<script src="https://unpkg.com/@synfin/[email protected]"></script>
<!-- or jsDelivr -->
<script src="https://cdn.jsdelivr.net/npm/@synfin/[email protected]"></script>The bare unpkg.com/@synfin/widget URL resolves to the IIFE bundle
(dist/synfin-widget.global.js) through the package unpkg/jsdelivr fields,
so you never spell the path. The bundle is browser-only and minified.
2. npm (with a bundler: Vite, Next.js, Webpack)
npm install @synfin/widgetimport '@synfin/widget'; // registers <synfin-widget>@synfin/client comes in as a normal dependency (the real range ^0.1.0, not a
workspace reference), so the package installs cleanly outside this repo.
React:
import { SynfinWidget } from '@synfin/widget/react';
<SynfinWidget apiKey="sk_live_..." feeBps={30} feeRecipient="you::1220..."
onQuote={(e) => console.log(e.detail)} />What it does (v1)
A live quote across Canton venues for your pair and amount, ranked best net
receive first. We recommend the best, and mark it, but the user can select any
available venue (a radio group: click, tap, or arrow keys); the receive figure,
the fee breakdown, and the plan all follow their choice. The widget is shown to
your END USER, so by default it shows only
what they care about: what they pay, what they receive (net of all fees, read
verbatim from the live quote and never recomputed in the browser), and a single
neutral fee line for the total taken. The fee attribution (the Synfin service
fee, your integrator fee, and the partner / Synfin split) is partner
information, hidden from the end user by default; set show-fee-breakdown to
reveal it for your own integration testing. Every state is designed: loading
skeletons, an auto-refresh countdown, a venue shown honestly as unavailable
(never breaking the widget), no-route, and errors.
Creating a plan emits synfin:plan for your app to execute through the
WalletAdapter it owns. In-widget wallet execution is deferred until a standard
Canton browser wallet adapter exists (see docs/WIDGET.md); the widget never
holds keys.
Configuration
| attribute / prop | meaning |
| --- | --- |
| api-key / apiKey | your partner key (unlocks the fee breakdown) |
| fee-bps / feeBps | your integrator fee, 0 to the cap (needs a recipient) |
| fee-recipient / feeRecipient | the Canton party your fee settles to |
| from to amount | default pair and amount |
| appearance | dark (default) or light |
| base-url / baseUrl | API origin (default https://synfin.xyz) |
| show-fee-breakdown / showFeeBreakdown | show the full partner fee attribution (off by default; end users see the net and one neutral fee line) |
| show-attribution / showAttribution | the subtle "Powered by Synfin" footer link (on by default; set off to white-label) |
On the API key: in a browser widget the key is client-side visible by nature. Synfin keys are free, rate-limited, and non-custodial: a leaked key can only fetch quotes at that key's rate limit, and is revocable in one click. Use a dedicated widget key. A per-key origin allowlist is a roadmap item.
Theming
Override any of these CSS custom properties (they pierce the Shadow DOM) to match
your site; the Synfin cream/ember dark system is the default, appearance="light"
a warm off-white:
synfin-widget {
--synfin-accent: #6c5cff;
--synfin-bg: #0c0c14;
--synfin-surface: #17172a;
--synfin-ink: #eef;
--synfin-radius: 20px;
--synfin-font: 'Inter', sans-serif;
}Events
DOM CustomEvents on the element (and on* props in the React wrapper):
synfin:quote- a freshQuoteResponseloadedsynfin:venue- the user selected a venue ({ venueId, net, isBest, pair })synfin:plan- anExecutionPlancreated (execute it from your wallet)synfin:asset- the pair changedsynfin:error- aSynfinApiErrorsurfaced
Demo
example/index.html: the full embed against live production (https://synfin.xyz) by default, with a control panel, the event log, and theming. Add your API key to unlock the fee breakdown; keyless shows the venue net only.example/cdn.html: the script-tag path in isolation. It loads the self-contained bundle with a plain<script src>(no import map), proving the CDN embed a partner uses.
The widget reads the public API cross-origin, so the API sends permissive CORS
headers on /api/*; see the /api/* CORS middleware.
License
MIT. Copyright Cayvox Labs.
