@synfin/widget 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Cayvox Labs
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,145 @@
1
+ # @synfin/widget
2
+
3
+ An embeddable **quote-and-fee widget** for Canton, built on
4
+ [`@synfin/client`](https://www.npmjs.com/package/@synfin/client). Drop it into
5
+ any site to show a live best-execution quote across venues with your disclosed
6
+ fees. Non-custodial: it holds no keys and signs nothing.
7
+
8
+ - Docs: https://synfin.xyz/docs/sdk
9
+ - Status: **v0**, the surface may evolve with design partners. Pin a version.
10
+
11
+ ## Install
12
+
13
+ Two paths, pick one. Both register the same `<synfin-widget>` custom element.
14
+
15
+ ### 1. Script tag (no build step, no bundler)
16
+
17
+ For any site (plain HTML, Vue, Svelte, Angular, Rails, WordPress). One `<script>`
18
+ loads a **self-contained** bundle (`@synfin/client` is inlined, so there is no
19
+ import map and nothing else to fetch) and registers the element:
20
+
21
+ ```html
22
+ <script src="https://unpkg.com/@synfin/widget"></script>
23
+ <synfin-widget
24
+ api-key="sk_live_..."
25
+ fee-bps="30"
26
+ fee-recipient="you::1220..."
27
+ from="CC" to="USDCx" amount="100"
28
+ ></synfin-widget>
29
+ ```
30
+
31
+ Pin a version in production (the CDN caches it and the API stays stable):
32
+
33
+ ```html
34
+ <script src="https://unpkg.com/@synfin/widget@0.1.0"></script>
35
+ <!-- or jsDelivr -->
36
+ <script src="https://cdn.jsdelivr.net/npm/@synfin/widget@0.1.0"></script>
37
+ ```
38
+
39
+ The bare `unpkg.com/@synfin/widget` URL resolves to the IIFE bundle
40
+ (`dist/synfin-widget.global.js`) through the package `unpkg`/`jsdelivr` fields,
41
+ so you never spell the path. The bundle is browser-only and minified.
42
+
43
+ ### 2. npm (with a bundler: Vite, Next.js, Webpack)
44
+
45
+ ```sh
46
+ npm install @synfin/widget
47
+ ```
48
+ ```ts
49
+ import '@synfin/widget'; // registers <synfin-widget>
50
+ ```
51
+
52
+ `@synfin/client` comes in as a normal dependency (the real range `^0.1.0`, not a
53
+ workspace reference), so the package installs cleanly outside this repo.
54
+
55
+ React:
56
+
57
+ ```tsx
58
+ import { SynfinWidget } from '@synfin/widget/react';
59
+
60
+ <SynfinWidget apiKey="sk_live_..." feeBps={30} feeRecipient="you::1220..."
61
+ onQuote={(e) => console.log(e.detail)} />
62
+ ```
63
+
64
+ ## What it does (v1)
65
+
66
+ A live quote across Canton venues for your pair and amount, ranked best net
67
+ receive first. We recommend the best, and mark it, but the user can select any
68
+ available venue (a radio group: click, tap, or arrow keys); the receive figure,
69
+ the fee breakdown, and the plan all follow their choice. The widget is shown to
70
+ your END USER, so by default it shows only
71
+ what they care about: what they pay, what they receive (net of all fees, read
72
+ verbatim from the live quote and never recomputed in the browser), and a single
73
+ neutral fee line for the total taken. The fee attribution (the Synfin service
74
+ fee, your integrator fee, and the partner / Synfin split) is partner
75
+ information, hidden from the end user by default; set `show-fee-breakdown` to
76
+ reveal it for your own integration testing. Every state is designed: loading
77
+ skeletons, an auto-refresh countdown, a venue shown honestly as unavailable
78
+ (never breaking the widget), no-route, and errors.
79
+
80
+ Creating a plan emits `synfin:plan` for **your** app to execute through the
81
+ `WalletAdapter` it owns. In-widget wallet execution is deferred until a standard
82
+ Canton browser wallet adapter exists (see docs/WIDGET.md); the widget never
83
+ holds keys.
84
+
85
+ ## Configuration
86
+
87
+ | attribute / prop | meaning |
88
+ | --- | --- |
89
+ | `api-key` / `apiKey` | your partner key (unlocks the fee breakdown) |
90
+ | `fee-bps` / `feeBps` | your integrator fee, 0 to the cap (needs a recipient) |
91
+ | `fee-recipient` / `feeRecipient` | the Canton party your fee settles to |
92
+ | `from` `to` `amount` | default pair and amount |
93
+ | `appearance` | `dark` (default) or `light` |
94
+ | `base-url` / `baseUrl` | API origin (default `https://synfin.xyz`) |
95
+ | `show-fee-breakdown` / `showFeeBreakdown` | show the full partner fee attribution (off by default; end users see the net and one neutral fee line) |
96
+ | `show-attribution` / `showAttribution` | the subtle "Powered by Synfin" footer link (on by default; set off to white-label) |
97
+
98
+ **On the API key:** in a browser widget the key is client-side visible by
99
+ nature. Synfin keys are free, rate-limited, and non-custodial: a leaked key can
100
+ only fetch quotes at that key's rate limit, and is revocable in one click. Use a
101
+ dedicated widget key. A per-key origin allowlist is a roadmap item.
102
+
103
+ ## Theming
104
+
105
+ Override any of these CSS custom properties (they pierce the Shadow DOM) to match
106
+ your site; the Synfin cream/ember dark system is the default, `appearance="light"`
107
+ a warm off-white:
108
+
109
+ ```css
110
+ synfin-widget {
111
+ --synfin-accent: #6c5cff;
112
+ --synfin-bg: #0c0c14;
113
+ --synfin-surface: #17172a;
114
+ --synfin-ink: #eef;
115
+ --synfin-radius: 20px;
116
+ --synfin-font: 'Inter', sans-serif;
117
+ }
118
+ ```
119
+
120
+ ## Events
121
+
122
+ DOM `CustomEvent`s on the element (and `on*` props in the React wrapper):
123
+
124
+ - `synfin:quote` - a fresh `QuoteResponse` loaded
125
+ - `synfin:venue` - the user selected a venue (`{ venueId, net, isBest, pair }`)
126
+ - `synfin:plan` - an `ExecutionPlan` created (execute it from your wallet)
127
+ - `synfin:asset` - the pair changed
128
+ - `synfin:error` - a `SynfinApiError` surfaced
129
+
130
+ ## Demo
131
+
132
+ - `example/index.html`: the full embed against live production
133
+ (`https://synfin.xyz`) by default, with a control panel, the event log, and
134
+ theming. Add your API key to unlock the fee breakdown; keyless shows the venue
135
+ net only.
136
+ - `example/cdn.html`: the script-tag path in isolation. It loads the
137
+ self-contained bundle with a plain `<script src>` (no import map), proving the
138
+ CDN embed a partner uses.
139
+
140
+ The widget reads the public API cross-origin, so the API sends permissive CORS
141
+ headers on `/api/*`; see the `/api/*` CORS middleware.
142
+
143
+ ## License
144
+
145
+ MIT. Copyright Cayvox Labs.
package/dist/api.d.ts ADDED
@@ -0,0 +1,23 @@
1
+ /**
2
+ * The widget's own quote fetch. It talks to the same public /api/quote surface
3
+ * as @synfin/client, but unlike the client (which binds a key and always sends
4
+ * it) this attaches the x-api-key header ONLY when a real key is present. A
5
+ * keyless embed therefore sends a clean anonymous request, which the endpoint
6
+ * answers 200 with ranked venues and no clientFees (add a key to see fees). It
7
+ * reuses the client's exported error and quote types so the widget stays built
8
+ * on @synfin/client; planning still goes through the client, which needs a key.
9
+ */
10
+ import { type FetchLike, type GetQuoteParams, type QuoteResponse } from '@synfin/client';
11
+ export interface QuoteRequest {
12
+ readonly baseUrl: string;
13
+ /** The partner key, or null for a keyless (anonymous) request. */
14
+ readonly apiKey: string | null;
15
+ readonly fetch?: FetchLike;
16
+ }
17
+ /**
18
+ * Fetch a quote. Sends the x-api-key header only when {@link QuoteRequest.apiKey}
19
+ * is a non-empty string; a null/empty key produces a header-free anonymous
20
+ * request. Throws {@link SynfinApiError} on a non-2xx or non-JSON response.
21
+ */
22
+ export declare function requestQuote(req: QuoteRequest, params: GetQuoteParams): Promise<QuoteResponse>;
23
+ //# sourceMappingURL=api.d.ts.map
package/dist/api.js ADDED
@@ -0,0 +1,66 @@
1
+ /**
2
+ * The widget's own quote fetch. It talks to the same public /api/quote surface
3
+ * as @synfin/client, but unlike the client (which binds a key and always sends
4
+ * it) this attaches the x-api-key header ONLY when a real key is present. A
5
+ * keyless embed therefore sends a clean anonymous request, which the endpoint
6
+ * answers 200 with ranked venues and no clientFees (add a key to see fees). It
7
+ * reuses the client's exported error and quote types so the widget stays built
8
+ * on @synfin/client; planning still goes through the client, which needs a key.
9
+ */
10
+ import { SynfinApiError, } from '@synfin/client';
11
+ /** Encode a flat param set, skipping undefined values. */
12
+ function query(params) {
13
+ const parts = [];
14
+ for (const [k, v] of Object.entries(params)) {
15
+ if (v === undefined)
16
+ continue;
17
+ parts.push(`${encodeURIComponent(k)}=${encodeURIComponent(String(v))}`);
18
+ }
19
+ return parts.length > 0 ? `?${parts.join('&')}` : '';
20
+ }
21
+ function resolveFetch(injected) {
22
+ if (injected !== undefined)
23
+ return injected;
24
+ const g = globalThis.fetch;
25
+ if (g === undefined) {
26
+ throw new SynfinApiError(0, 'network', 'no fetch available in this runtime');
27
+ }
28
+ return g;
29
+ }
30
+ /**
31
+ * Fetch a quote. Sends the x-api-key header only when {@link QuoteRequest.apiKey}
32
+ * is a non-empty string; a null/empty key produces a header-free anonymous
33
+ * request. Throws {@link SynfinApiError} on a non-2xx or non-JSON response.
34
+ */
35
+ export async function requestQuote(req, params) {
36
+ const qs = query({
37
+ from: params.from,
38
+ to: params.to,
39
+ amount: String(params.amount),
40
+ slippageBps: params.slippageBps,
41
+ feeBps: params.feeBps,
42
+ feeRecipient: params.feeRecipient,
43
+ });
44
+ const headers = {};
45
+ if (req.apiKey !== null && req.apiKey !== '') {
46
+ headers['x-api-key'] = req.apiKey;
47
+ }
48
+ const res = await resolveFetch(req.fetch)(`${req.baseUrl}/api/quote${qs}`, {
49
+ method: 'GET',
50
+ headers,
51
+ });
52
+ let body;
53
+ try {
54
+ body = await res.json();
55
+ }
56
+ catch {
57
+ const text = await res.text().catch(() => '');
58
+ throw new SynfinApiError(res.status, 'bad_response', text.length > 0 ? text : 'non-JSON response from the Synfin API');
59
+ }
60
+ if (!res.ok) {
61
+ const e = body;
62
+ throw new SynfinApiError(res.status, e.code ?? 'error', e.error ?? `HTTP ${res.status}`);
63
+ }
64
+ return body;
65
+ }
66
+ //# sourceMappingURL=api.js.map
@@ -0,0 +1,25 @@
1
+ /**
2
+ * The Canton asset catalog the selector offers. UI metadata only (symbol, name,
3
+ * decimals for display); the quote and its fees are the API's, never ours. Kept
4
+ * in sync with the tokens the hosted /api/quote resolves.
5
+ */
6
+ export interface WidgetAsset {
7
+ readonly symbol: string;
8
+ readonly name: string;
9
+ readonly instrumentId: string;
10
+ readonly decimals: number;
11
+ }
12
+ export declare const ASSETS: readonly WidgetAsset[];
13
+ export declare function findAsset(symbol: string): WidgetAsset | undefined;
14
+ /**
15
+ * Map a name from the API (which may be the internal canonical instrumentId,
16
+ * e.g. "Amulet") to the user-facing ticker (e.g. "CC"). The canonical name must
17
+ * never surface in user-facing text; the user only ever sees the ticker they
18
+ * chose. Unknown names pass through unchanged (honest, never a wrong label).
19
+ */
20
+ export declare function displaySymbol(nameOrId: string): string;
21
+ /** A crisp, self-contained SVG monogram for a token (no remote image, no
22
+ * possibly-wrong logo). Deterministic per ticker, on-brand, theme-agnostic. */
23
+ export declare function assetIcon(symbol: string): string;
24
+ export declare function searchAssets(query: string): readonly WidgetAsset[];
25
+ //# sourceMappingURL=assets.d.ts.map
package/dist/assets.js ADDED
@@ -0,0 +1,64 @@
1
+ export const ASSETS = [
2
+ { symbol: 'CC', name: 'Canton Coin', instrumentId: 'Amulet', decimals: 10 },
3
+ {
4
+ symbol: 'USDCx',
5
+ name: 'USDC (interchain)',
6
+ instrumentId: 'USDCx',
7
+ decimals: 6,
8
+ },
9
+ { symbol: 'CBTC', name: 'Canton BTC', instrumentId: 'CBTC', decimals: 8 },
10
+ ];
11
+ export function findAsset(symbol) {
12
+ const s = symbol.trim().toLowerCase();
13
+ return ASSETS.find((a) => a.symbol.toLowerCase() === s);
14
+ }
15
+ /**
16
+ * Map a name from the API (which may be the internal canonical instrumentId,
17
+ * e.g. "Amulet") to the user-facing ticker (e.g. "CC"). The canonical name must
18
+ * never surface in user-facing text; the user only ever sees the ticker they
19
+ * chose. Unknown names pass through unchanged (honest, never a wrong label).
20
+ */
21
+ export function displaySymbol(nameOrId) {
22
+ const s = nameOrId.trim();
23
+ const l = s.toLowerCase();
24
+ const byId = ASSETS.find((a) => a.instrumentId.toLowerCase() === l);
25
+ if (byId)
26
+ return byId.symbol;
27
+ const bySym = ASSETS.find((a) => a.symbol.toLowerCase() === l);
28
+ return bySym ? bySym.symbol : s;
29
+ }
30
+ // Curated per-token monogram (label + hue) for the crisp fallback icon. Real
31
+ // Canton token logos are an OPTIONAL field on the CIP-0056 off-ledger token
32
+ // metadata API and are not reliably published for these tokens, so the widget
33
+ // ships a deterministic on-brand monogram rather than a possibly-wrong logo.
34
+ const ICON = {
35
+ CC: { label: 'CC', hue: 20 },
36
+ AMULET: { label: 'CC', hue: 20 },
37
+ USDCX: { label: 'USDC', hue: 212 },
38
+ CBTC: { label: 'BTC', hue: 33 },
39
+ CETH: { label: 'ETH', hue: 250 },
40
+ CUSD: { label: 'USD', hue: 150 },
41
+ };
42
+ function hashHue(s) {
43
+ let h = 0;
44
+ for (let i = 0; i < s.length; i++)
45
+ h = (h * 31 + s.charCodeAt(i)) >>> 0;
46
+ return h % 360;
47
+ }
48
+ /** A crisp, self-contained SVG monogram for a token (no remote image, no
49
+ * possibly-wrong logo). Deterministic per ticker, on-brand, theme-agnostic. */
50
+ export function assetIcon(symbol) {
51
+ const key = displaySymbol(symbol).toUpperCase();
52
+ const c = ICON[key] ?? { label: key.slice(0, 3), hue: hashHue(key) };
53
+ const h2 = (c.hue + 24) % 360;
54
+ const id = `sfic${c.hue}`;
55
+ const fs = c.label.length >= 4 ? 6.5 : c.label.length === 3 ? 8 : 9.5;
56
+ return `<svg class="asset__icon" viewBox="0 0 24 24" width="24" height="24" aria-hidden="true" focusable="false"><defs><linearGradient id="${id}" x1="0" y1="0" x2="1" y2="1"><stop offset="0" stop-color="hsl(${c.hue} 78% 58%)"></stop><stop offset="1" stop-color="hsl(${h2} 72% 44%)"></stop></linearGradient></defs><circle cx="12" cy="12" r="11" fill="url(#${id})"></circle><text x="12" y="12" dy="0.35em" text-anchor="middle" font-size="${fs}" font-weight="700" fill="#fff">${c.label}</text></svg>`;
57
+ }
58
+ export function searchAssets(query) {
59
+ const q = query.trim().toLowerCase();
60
+ if (q === '')
61
+ return ASSETS;
62
+ return ASSETS.filter((a) => a.symbol.toLowerCase().includes(q) || a.name.toLowerCase().includes(q));
63
+ }
64
+ //# sourceMappingURL=assets.js.map
@@ -0,0 +1,43 @@
1
+ export interface WidgetConfig {
2
+ apiKey?: string;
3
+ feeBps?: number;
4
+ feeRecipient?: string;
5
+ from?: string;
6
+ to?: string;
7
+ amount?: string;
8
+ appearance?: 'dark' | 'light';
9
+ baseUrl?: string;
10
+ refreshMs?: number;
11
+ /** Partner-facing detail mode: show the full fee attribution (Synfin service,
12
+ * integrator fee, partner/Synfin split). OFF by default; the end-user default
13
+ * shows only the net receive and a single neutral total fee. */
14
+ showFeeBreakdown?: boolean;
15
+ /** The subtle "Powered by Synfin" footer link (a growth surface, LI.FI-style).
16
+ * ON by default; a white-label enterprise partner sets it off. */
17
+ showAttribution?: boolean;
18
+ }
19
+ export interface NormalizedConfig {
20
+ apiKey: string | null;
21
+ feeBps: number | null;
22
+ feeRecipient: string | null;
23
+ from: string;
24
+ to: string;
25
+ amount: string;
26
+ appearance: 'dark' | 'light';
27
+ baseUrl: string;
28
+ refreshMs: number;
29
+ showFeeBreakdown: boolean;
30
+ showAttribution: boolean;
31
+ }
32
+ export interface ConfigIssue {
33
+ field: string;
34
+ message: string;
35
+ }
36
+ /** The integrator fee cap for input validation (BUSINESS.md; the server is
37
+ * authoritative and re-checks against the applied schema). */
38
+ export declare const FEE_CAP_BPS = 100;
39
+ export declare function normalizeConfig(raw: WidgetConfig): {
40
+ config: NormalizedConfig;
41
+ issues: ConfigIssue[];
42
+ };
43
+ //# sourceMappingURL=config.d.ts.map
package/dist/config.js ADDED
@@ -0,0 +1,80 @@
1
+ /**
2
+ * Widget configuration: the partner passes their key, their integrator fee,
3
+ * default pair/amount, theme, and API origin. Normalization applies defaults
4
+ * and collects honest issues (shown, never thrown), so a misconfigured embed
5
+ * degrades to a clear message instead of a blank box.
6
+ */
7
+ import { findAsset } from './assets.js';
8
+ /** The integrator fee cap for input validation (BUSINESS.md; the server is
9
+ * authoritative and re-checks against the applied schema). */
10
+ export const FEE_CAP_BPS = 100;
11
+ const DEFAULT_REFRESH_MS = 15_000;
12
+ export function normalizeConfig(raw) {
13
+ const issues = [];
14
+ const from = (raw.from ?? 'CC').trim();
15
+ const to = (raw.to ?? 'USDCx').trim();
16
+ if (findAsset(from) === undefined)
17
+ issues.push({ field: 'from', message: `unknown asset "${from}"` });
18
+ if (findAsset(to) === undefined)
19
+ issues.push({ field: 'to', message: `unknown asset "${to}"` });
20
+ if (from.toLowerCase() === to.toLowerCase())
21
+ issues.push({ field: 'to', message: 'from and to must differ' });
22
+ const amount = (raw.amount ?? '100').trim();
23
+ if (!/^\d+(\.\d+)?$/.test(amount) || Number(amount) <= 0)
24
+ issues.push({
25
+ field: 'amount',
26
+ message: 'amount must be a positive number',
27
+ });
28
+ let feeBps = null;
29
+ let feeRecipient = null;
30
+ const hasFee = raw.feeBps !== undefined || raw.feeRecipient !== undefined;
31
+ if (hasFee) {
32
+ if (raw.feeBps === undefined || raw.feeRecipient === undefined) {
33
+ issues.push({
34
+ field: 'fee',
35
+ message: 'feeBps and feeRecipient must be set together',
36
+ });
37
+ }
38
+ else {
39
+ const n = Number(raw.feeBps);
40
+ if (!Number.isInteger(n) || n < 0 || n > FEE_CAP_BPS)
41
+ issues.push({
42
+ field: 'feeBps',
43
+ message: `feeBps must be an integer 0 to ${FEE_CAP_BPS}`,
44
+ });
45
+ else
46
+ feeBps = n;
47
+ const r = String(raw.feeRecipient).trim();
48
+ if (r === '')
49
+ issues.push({
50
+ field: 'feeRecipient',
51
+ message: 'feeRecipient required',
52
+ });
53
+ else
54
+ feeRecipient = r;
55
+ }
56
+ }
57
+ const appearance = raw.appearance === 'light' ? 'light' : 'dark';
58
+ const baseUrl = (raw.baseUrl ?? 'https://synfin.xyz').replace(/\/+$/, '');
59
+ const refreshMs = typeof raw.refreshMs === 'number' && raw.refreshMs >= 3000
60
+ ? raw.refreshMs
61
+ : DEFAULT_REFRESH_MS;
62
+ const apiKeyTrimmed = (raw.apiKey ?? '').trim();
63
+ return {
64
+ config: {
65
+ apiKey: apiKeyTrimmed === '' ? null : apiKeyTrimmed,
66
+ feeBps,
67
+ feeRecipient,
68
+ from,
69
+ to,
70
+ amount,
71
+ appearance,
72
+ baseUrl,
73
+ refreshMs,
74
+ showFeeBreakdown: raw.showFeeBreakdown === true,
75
+ showAttribution: raw.showAttribution !== false,
76
+ },
77
+ issues,
78
+ };
79
+ }
80
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1,65 @@
1
+ /**
2
+ * <synfin-widget>: the custom element. Owns the persistent chrome (pair + amount
3
+ * fields, the asset selector popover, the refresh countdown) and drives the
4
+ * quote lifecycle through @synfin/client, swapping only the result region per
5
+ * quote. Non-custodial: it holds no keys and signs nothing; creating a plan
6
+ * emits synfin:plan for the host to execute. Shadow DOM isolates styles so it is
7
+ * robust inside any host page.
8
+ */
9
+ import { type FetchLike } from '@synfin/client';
10
+ export declare class SynfinWidgetElement extends HTMLElement {
11
+ static get observedAttributes(): readonly string[];
12
+ /** Injectable fetch (tests and advanced hosts); defaults to global fetch. */
13
+ fetchImpl?: FetchLike;
14
+ private cfg;
15
+ private client;
16
+ private view;
17
+ private debounce;
18
+ private ticker;
19
+ private countdown;
20
+ private reqSeq;
21
+ private selectorOpen;
22
+ private built;
23
+ private syncing;
24
+ /** The venue the user has chosen (defaults to best); persists across refreshes
25
+ * while it stays available. */
26
+ private selectedVenue;
27
+ /** Transient Create-a-plan feedback, cleared on a fresh quote. */
28
+ private plan;
29
+ connectedCallback(): void;
30
+ disconnectedCallback(): void;
31
+ attributeChangedCallback(): void;
32
+ /** Set config programmatically (React wrapper / advanced hosts). */
33
+ configure(partial: Record<string, string | number | boolean | undefined | null>): void;
34
+ private reconfigure;
35
+ private root;
36
+ private $;
37
+ private build;
38
+ private assetBtn;
39
+ private paintChrome;
40
+ private onAmount;
41
+ /** Change the pair from an INTERNAL action (swap/select): update live state
42
+ * and re-quote directly, never through observed attributes (which would
43
+ * reset the typed amount and re-enter reconfigure). */
44
+ private setPair;
45
+ private swap;
46
+ private setView;
47
+ /** Re-render the result region from the current view, selected venue, and plan
48
+ * feedback. Called on a new quote and on any selection / plan change. */
49
+ private paintResult;
50
+ /** The "You receive" chrome follows the SELECTED venue, same formatting as the
51
+ * summary line so the two never disagree. */
52
+ private paintReceive;
53
+ /** Select a venue (click or keyboard). Repaints so the receive figure, fees,
54
+ * and plan all follow the choice, and emits synfin:venue for the host. */
55
+ private selectVenue;
56
+ private venueKeys;
57
+ private quote;
58
+ private startCountdown;
59
+ private stopCountdown;
60
+ private createPlan;
61
+ private openSelector;
62
+ private selectorKeys;
63
+ private closeSelector;
64
+ }
65
+ //# sourceMappingURL=element.d.ts.map