@atom-circuit/embed-sdk 1.2.1
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/CHANGELOG.md +106 -0
- package/LICENSE +21 -0
- package/README.md +312 -0
- package/SECURITY.md +88 -0
- package/dist/atom-circuit.iife.js +2 -0
- package/dist/atom-circuit.iife.js.map +1 -0
- package/dist/index.cjs +694 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +391 -0
- package/dist/index.d.ts +391 -0
- package/dist/index.mjs +691 -0
- package/dist/index.mjs.map +1 -0
- package/dist/react.cjs +744 -0
- package/dist/react.cjs.map +1 -0
- package/dist/react.d.cts +337 -0
- package/dist/react.d.ts +337 -0
- package/dist/react.mjs +742 -0
- package/dist/react.mjs.map +1 -0
- package/package.json +125 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,391 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Atom Circuit Embed SDK - wire protocol contracts.
|
|
3
|
+
*
|
|
4
|
+
* Every message passed between host page and iframe must match one of the
|
|
5
|
+
* discriminated unions exported here. Strict origin and shape checks rely on
|
|
6
|
+
* these types both at compile time and at runtime (see assertion helpers).
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* Wire-protocol major. SDK sends this in the URL (`?v=`) and during the
|
|
10
|
+
* handshake. The iframe must honor at least the last 2 majors or 18 months
|
|
11
|
+
* of SDK versions, whichever is longer. Bumped on every
|
|
12
|
+
* breaking wire change; independent of the npm package version.
|
|
13
|
+
*/
|
|
14
|
+
declare const PROTOCOL_VERSION = "1.0.0";
|
|
15
|
+
/**
|
|
16
|
+
* Capabilities advertised in the handshake. Names are stable strings; new
|
|
17
|
+
* capabilities may be added without breaking older SDKs (they simply ignore
|
|
18
|
+
* unknown entries).
|
|
19
|
+
*/
|
|
20
|
+
type Capability = 'swap.submit' | 'swap.status' | 'resize.report' | 'events.stream';
|
|
21
|
+
type Capabilities = ReadonlyArray<Capability | string>;
|
|
22
|
+
/**
|
|
23
|
+
* Handshake payload exchanged once on connect. The iframe is expected to
|
|
24
|
+
* reply with its own handshake describing its protocol version + capability
|
|
25
|
+
* set; the SDK warns (does not throw) when the major versions diverge.
|
|
26
|
+
*/
|
|
27
|
+
interface HandshakeMessage {
|
|
28
|
+
readonly type: 'handshake';
|
|
29
|
+
readonly protocolVersion: string;
|
|
30
|
+
readonly capabilities: Capabilities;
|
|
31
|
+
}
|
|
32
|
+
interface ReadyPayload {
|
|
33
|
+
readonly protocolVersion: string;
|
|
34
|
+
}
|
|
35
|
+
interface SwapSubmittedPayload {
|
|
36
|
+
readonly txHash: string;
|
|
37
|
+
readonly route?: SwapRouteSummary;
|
|
38
|
+
}
|
|
39
|
+
interface SwapSuccessPayload {
|
|
40
|
+
readonly txHash: string;
|
|
41
|
+
}
|
|
42
|
+
interface SwapErrorPayload {
|
|
43
|
+
readonly code: string;
|
|
44
|
+
readonly message: string;
|
|
45
|
+
}
|
|
46
|
+
interface SwapRouteSummary {
|
|
47
|
+
readonly sourceChainId: string;
|
|
48
|
+
readonly destChainId: string;
|
|
49
|
+
readonly sourceDenom: string;
|
|
50
|
+
readonly destDenom: string;
|
|
51
|
+
readonly amountIn: string;
|
|
52
|
+
readonly amountOut?: string;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Discriminated union of all valid widget events. Use this when typing event
|
|
56
|
+
* subscribers on the host side.
|
|
57
|
+
*/
|
|
58
|
+
type WidgetEvent = {
|
|
59
|
+
readonly name: 'ready';
|
|
60
|
+
readonly payload: ReadyPayload;
|
|
61
|
+
} | {
|
|
62
|
+
readonly name: 'swap:submitted';
|
|
63
|
+
readonly payload: SwapSubmittedPayload;
|
|
64
|
+
} | {
|
|
65
|
+
readonly name: 'swap:success';
|
|
66
|
+
readonly payload: SwapSuccessPayload;
|
|
67
|
+
} | {
|
|
68
|
+
readonly name: 'swap:error';
|
|
69
|
+
readonly payload: SwapErrorPayload;
|
|
70
|
+
};
|
|
71
|
+
/**
|
|
72
|
+
* Optional theming contract passed from host to iframe via the `?theme=` URL
|
|
73
|
+
* parameter (base64-encoded compact JSON of this object).
|
|
74
|
+
*
|
|
75
|
+
* Contract:
|
|
76
|
+
* - The SDK validates every field against the rules documented per-field. If
|
|
77
|
+
* ANY field fails validation, the entire theme is dropped and the iframe
|
|
78
|
+
* falls back to its default appearance. Validation is intentionally strict
|
|
79
|
+
* so a malformed theme cannot break the embed or be used as an injection
|
|
80
|
+
* vector against the iframe's CSS surface.
|
|
81
|
+
* - Fields are optional; the iframe must accept partial themes and apply only
|
|
82
|
+
* the keys present.
|
|
83
|
+
* - Color values are CSS hex strings (`#RGB` or `#RRGGBB`). Other CSS color
|
|
84
|
+
* notations (rgb(), named colors) are rejected to keep the wire surface
|
|
85
|
+
* trivial to validate and to avoid CSS-injection footguns via string
|
|
86
|
+
* interpolation on the iframe side.
|
|
87
|
+
* - `radius` and `fontSize` are plain pixel numbers in tight bounds.
|
|
88
|
+
* - `fontFamily` is a CSS-safe subset: letters, digits, spaces, hyphens,
|
|
89
|
+
* commas, single/double quotes, dots. Anything containing `<`, `>`, `;`,
|
|
90
|
+
* `{`, `}`, `=`, `(`, `)`, newlines, or tabs is rejected. Max 200 chars.
|
|
91
|
+
* - The iframe applies these as CSS custom properties on its embed root;
|
|
92
|
+
* see the dapp side for the variable mapping.
|
|
93
|
+
*
|
|
94
|
+
* Omitting the `theme` field on MountOptions omits the `?theme=` param
|
|
95
|
+
* from the iframe URL entirely.
|
|
96
|
+
*/
|
|
97
|
+
interface ThemeOptions {
|
|
98
|
+
/** Light/dark/auto mode hint. Auto follows the host system preference. */
|
|
99
|
+
readonly mode?: 'light' | 'dark' | 'auto';
|
|
100
|
+
/** Brand accent color used for primary buttons and highlights. Hex only. */
|
|
101
|
+
readonly accentColor?: string;
|
|
102
|
+
/** Page background color. Hex only. */
|
|
103
|
+
readonly background?: string;
|
|
104
|
+
/** Primary text/foreground color. Hex only. */
|
|
105
|
+
readonly foreground?: string;
|
|
106
|
+
/** Border color for inputs, cards, dividers. Hex only. */
|
|
107
|
+
readonly border?: string;
|
|
108
|
+
/** Corner radius in pixels. Range: 0-64 inclusive. */
|
|
109
|
+
readonly radius?: number;
|
|
110
|
+
/** Base font size in pixels. Range: 8-32 inclusive. */
|
|
111
|
+
readonly fontSize?: number;
|
|
112
|
+
/** CSS font-family value. CSS-safe subset only; see ThemeOptions doc. */
|
|
113
|
+
readonly fontFamily?: string;
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Toggles for the visual chrome surfaces rendered by the embed page. Each
|
|
117
|
+
* surface defaults to ON (true) so an embed dropped in with no chrome
|
|
118
|
+
* option shows the full chrome. Setting a flag to false hides the
|
|
119
|
+
* corresponding surface.
|
|
120
|
+
*
|
|
121
|
+
* Encoded into the iframe URL as part of the `?theme=` base64-JSON
|
|
122
|
+
* payload under the `chrome` key. Validation is strict: each present field
|
|
123
|
+
* must be a boolean, otherwise the entire chrome bundle is rejected.
|
|
124
|
+
*
|
|
125
|
+
* Omitting the `chrome` field, or omitting individual flags, leaves the
|
|
126
|
+
* default-on behaviour in place.
|
|
127
|
+
*/
|
|
128
|
+
interface ChromeOptions {
|
|
129
|
+
/** Show the Atom Circuit logo in the top bar. Default true. */
|
|
130
|
+
readonly logo?: boolean;
|
|
131
|
+
/** Show the wallet connect / disconnect button in the top bar. Default true. */
|
|
132
|
+
readonly wallet?: boolean;
|
|
133
|
+
/** Show the "Fees stake with <moniker>" validator badge. Default true. */
|
|
134
|
+
readonly validator?: boolean;
|
|
135
|
+
/** Show the "Powered by Atom Circuit" footer. Default true. */
|
|
136
|
+
readonly footer?: boolean;
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Stable error codes surfaced via the `onError` callback. Consumers should
|
|
140
|
+
* treat unknown codes as opaque diagnostics rather than control-flow
|
|
141
|
+
* signals.
|
|
142
|
+
*/
|
|
143
|
+
type MountErrorCode = 'handshake_failed' | 'iframe_load_failed' | 'origin_mismatch' | 'protocol_incompatible' | 'unknown';
|
|
144
|
+
/**
|
|
145
|
+
* Shape of the error passed to `onError`. `cause` carries the original error
|
|
146
|
+
* (if any) for diagnostic logging; it is typed as `unknown` so consumers
|
|
147
|
+
* narrow it explicitly before use.
|
|
148
|
+
*/
|
|
149
|
+
interface MountError {
|
|
150
|
+
readonly code: MountErrorCode;
|
|
151
|
+
readonly message: string;
|
|
152
|
+
readonly cause?: unknown;
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Options accepted by both `mount(...)` (vanilla) and `<AtomCircuitSwap />`
|
|
156
|
+
* (React). Required fields are kept to the absolute minimum so the embed
|
|
157
|
+
* stays a one-liner for the validator.
|
|
158
|
+
*/
|
|
159
|
+
interface MountOptions {
|
|
160
|
+
/**
|
|
161
|
+
* Validator-supplied affiliate identifier. Forwarded to the widget via the
|
|
162
|
+
* iframe URL so fees route correctly.
|
|
163
|
+
*/
|
|
164
|
+
/**
|
|
165
|
+
* Validator referralId. Optional. When omitted (or empty / whitespace),
|
|
166
|
+
* the SDK defaults to the literal string `'general'`, which fans the
|
|
167
|
+
* affiliate fee across all participating Atom Circuit validators at
|
|
168
|
+
* sweep time. Hosts that want fees to stake to a specific validator
|
|
169
|
+
* pass that validator's 8-character hex referralId (or a registered
|
|
170
|
+
* vanity slug).
|
|
171
|
+
*/
|
|
172
|
+
referralId?: string;
|
|
173
|
+
/**
|
|
174
|
+
* Override the widget origin. Default `https://atomcircuit.net`. Used by
|
|
175
|
+
* the test suite and local development only.
|
|
176
|
+
*/
|
|
177
|
+
origin?: string;
|
|
178
|
+
/**
|
|
179
|
+
* Override the widget path. Default `/embed/swap`.
|
|
180
|
+
*/
|
|
181
|
+
path?: string;
|
|
182
|
+
/**
|
|
183
|
+
* Minimum height applied to the iframe before any resize messages arrive.
|
|
184
|
+
* Default `480px`.
|
|
185
|
+
*/
|
|
186
|
+
minHeight?: string;
|
|
187
|
+
/**
|
|
188
|
+
* Optional additional CSS class applied to the iframe element.
|
|
189
|
+
*/
|
|
190
|
+
className?: string;
|
|
191
|
+
/**
|
|
192
|
+
* Optional inline style merge. `height` and `width` are managed by the SDK
|
|
193
|
+
* and ignored if supplied.
|
|
194
|
+
*/
|
|
195
|
+
style?: Partial<CSSStyleDeclaration>;
|
|
196
|
+
/**
|
|
197
|
+
* Fires once the iframe has loaded and the handshake completes.
|
|
198
|
+
*/
|
|
199
|
+
onReady?: (payload: ReadyPayload) => void;
|
|
200
|
+
/**
|
|
201
|
+
* Fires on every measured content-height change.
|
|
202
|
+
*/
|
|
203
|
+
onResize?: (info: {
|
|
204
|
+
height: number;
|
|
205
|
+
}) => void;
|
|
206
|
+
/**
|
|
207
|
+
* Fires when the user submits a swap (tx broadcast).
|
|
208
|
+
*/
|
|
209
|
+
onSwapSubmitted?: (payload: SwapSubmittedPayload) => void;
|
|
210
|
+
/**
|
|
211
|
+
* Fires when a submitted swap confirms on chain.
|
|
212
|
+
*/
|
|
213
|
+
onSwapSuccess?: (payload: SwapSuccessPayload) => void;
|
|
214
|
+
/**
|
|
215
|
+
* Fires when a swap fails or is rejected by the wallet.
|
|
216
|
+
*/
|
|
217
|
+
onSwapError?: (payload: SwapErrorPayload) => void;
|
|
218
|
+
/**
|
|
219
|
+
* Fires on SDK-level failures (iframe load failure, handshake timeout,
|
|
220
|
+
* origin mismatch, etc). When not supplied the SDK emits a single warning
|
|
221
|
+
* via the injected warn sink and returns. This is distinct from
|
|
222
|
+
* `onSwapError`, which reports widget-level (in-iframe) swap failures.
|
|
223
|
+
*/
|
|
224
|
+
onError?: (error: MountError) => void;
|
|
225
|
+
/**
|
|
226
|
+
* Optional theme. See {@link ThemeOptions} for the validated contract.
|
|
227
|
+
* Validation failure silently drops the theme; the iframe falls back to
|
|
228
|
+
* defaults.
|
|
229
|
+
*/
|
|
230
|
+
readonly theme?: ThemeOptions;
|
|
231
|
+
/**
|
|
232
|
+
* Optional chrome toggles. See {@link ChromeOptions} for the validated
|
|
233
|
+
* contract. Validation failure silently drops the chrome bundle; the
|
|
234
|
+
* iframe falls back to all-chrome-on defaults. Encoded alongside `theme`
|
|
235
|
+
* in the iframe URL.
|
|
236
|
+
*/
|
|
237
|
+
readonly chrome?: ChromeOptions;
|
|
238
|
+
/**
|
|
239
|
+
* CSS `width` applied to the iframe. Default `'100%'` when omitted.
|
|
240
|
+
*/
|
|
241
|
+
readonly width?: string;
|
|
242
|
+
/**
|
|
243
|
+
* CSS `max-width` applied to the iframe. Default unset (no cap) when
|
|
244
|
+
* omitted.
|
|
245
|
+
*/
|
|
246
|
+
readonly maxWidth?: string;
|
|
247
|
+
/**
|
|
248
|
+
* CSS `padding` applied to the wrapper element around the iframe (NOT to
|
|
249
|
+
* the iframe element itself, since padding on iframes does not behave
|
|
250
|
+
* intuitively across browsers). Default `'0'` when omitted.
|
|
251
|
+
*/
|
|
252
|
+
readonly padding?: string;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* Host-side client that wraps Penpal v7 for typed RPC plus a custom
|
|
257
|
+
* event emitter for streamed widget events (resize, ready, swap:*).
|
|
258
|
+
*
|
|
259
|
+
* Strict origin validation is enforced two ways:
|
|
260
|
+
* 1. Penpal's `WindowMessenger({ allowedOrigins })` filters at the messenger layer.
|
|
261
|
+
* 2. A second `MessageEvent` listener double-checks `event.origin` before
|
|
262
|
+
* acknowledging stream events. Two-tier check is intentional: it ensures
|
|
263
|
+
* we never trust a payload coming through a future Penpal change that
|
|
264
|
+
* relaxes origin handling.
|
|
265
|
+
*/
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* Per-event handler signatures. Keeps the public surface free of `any`.
|
|
269
|
+
*/
|
|
270
|
+
interface EventHandlers {
|
|
271
|
+
ready: (payload: ReadyPayload) => void;
|
|
272
|
+
resize: (info: {
|
|
273
|
+
height: number;
|
|
274
|
+
}) => void;
|
|
275
|
+
'swap:submitted': (payload: SwapSubmittedPayload) => void;
|
|
276
|
+
'swap:success': (payload: SwapSuccessPayload) => void;
|
|
277
|
+
'swap:error': (payload: SwapErrorPayload) => void;
|
|
278
|
+
}
|
|
279
|
+
type EventName = keyof EventHandlers;
|
|
280
|
+
interface IframeClientOptions {
|
|
281
|
+
/**
|
|
282
|
+
* The iframe element. Must already be appended to the DOM and have its
|
|
283
|
+
* `src` set so `iframe.contentWindow` is non-null by the time `init()`
|
|
284
|
+
* resolves.
|
|
285
|
+
*/
|
|
286
|
+
iframe: HTMLIFrameElement;
|
|
287
|
+
/**
|
|
288
|
+
* Origin to trust for postMessage. Defaults to {@link WIDGET_ORIGIN}.
|
|
289
|
+
*/
|
|
290
|
+
allowedOrigin?: string;
|
|
291
|
+
/**
|
|
292
|
+
* Connection timeout in milliseconds. Default 15000.
|
|
293
|
+
*/
|
|
294
|
+
timeoutMs?: number;
|
|
295
|
+
/**
|
|
296
|
+
* Optional warning sink. Defaults to a no-op (the SDK refuses to write to
|
|
297
|
+
* console.log per project policy).
|
|
298
|
+
*/
|
|
299
|
+
warn?: (message: string) => void;
|
|
300
|
+
}
|
|
301
|
+
/**
|
|
302
|
+
* Typed Penpal RPC client + stream-event hub. One instance per iframe.
|
|
303
|
+
*/
|
|
304
|
+
declare class IframeClient {
|
|
305
|
+
private readonly iframe;
|
|
306
|
+
private readonly allowedOrigin;
|
|
307
|
+
private readonly timeoutMs;
|
|
308
|
+
private readonly warn;
|
|
309
|
+
private connection;
|
|
310
|
+
private rawListener;
|
|
311
|
+
private destroyed;
|
|
312
|
+
private handshakeReceived;
|
|
313
|
+
private handshakeResolvers;
|
|
314
|
+
private readonly handlers;
|
|
315
|
+
constructor(opts: IframeClientOptions);
|
|
316
|
+
/**
|
|
317
|
+
* Opens the Penpal connection and starts listening for stream events.
|
|
318
|
+
* Resolves once the remote handshake has been received.
|
|
319
|
+
*/
|
|
320
|
+
init(): Promise<HandshakeMessage>;
|
|
321
|
+
/**
|
|
322
|
+
* Subscribe to a named stream event. Returns an unsubscribe function.
|
|
323
|
+
*/
|
|
324
|
+
on<K extends EventName>(name: K, handler: EventHandlers[K]): () => void;
|
|
325
|
+
/**
|
|
326
|
+
* Remove a registered handler.
|
|
327
|
+
*/
|
|
328
|
+
off<K extends EventName>(name: K, handler: EventHandlers[K]): void;
|
|
329
|
+
/**
|
|
330
|
+
* Tears down the Penpal connection, removes the raw listener, and clears
|
|
331
|
+
* every event subscriber. Safe to call multiple times.
|
|
332
|
+
*/
|
|
333
|
+
destroy(): void;
|
|
334
|
+
/**
|
|
335
|
+
* Returns the handshake payload received from the iframe, or null if no
|
|
336
|
+
* handshake has been observed yet.
|
|
337
|
+
*/
|
|
338
|
+
getHandshake(): HandshakeMessage | null;
|
|
339
|
+
/**
|
|
340
|
+
* Returns true if the iframe advertised the given capability in its
|
|
341
|
+
* handshake. Returns false when no handshake has been received yet or
|
|
342
|
+
* when the capability is not present.
|
|
343
|
+
*
|
|
344
|
+
* Callers wrapping a method that requires a capability should gate on
|
|
345
|
+
* `client.has('cap')` before invoking it. Calls to capabilities the
|
|
346
|
+
* iframe does not advertise are silent no-ops at the call-site (or
|
|
347
|
+
* resolve to `null`); the iframe is the source of truth for what it
|
|
348
|
+
* implements.
|
|
349
|
+
*/
|
|
350
|
+
has(capability: string): boolean;
|
|
351
|
+
/**
|
|
352
|
+
* Test-only seam. Allows unit tests to invoke the message handler without
|
|
353
|
+
* dispatching real `MessageEvent`s through the JSDOM bus.
|
|
354
|
+
*/
|
|
355
|
+
_handleMessageForTest(event: MessageEvent): void;
|
|
356
|
+
private handleRawMessage;
|
|
357
|
+
private recordHandshake;
|
|
358
|
+
private waitForHandshake;
|
|
359
|
+
private emitResize;
|
|
360
|
+
private dispatchWidgetEvent;
|
|
361
|
+
private emitReady;
|
|
362
|
+
private emitTyped;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
/**
|
|
366
|
+
* Vanilla mount factory. Builds the iframe element, applies sandbox attrs,
|
|
367
|
+
* wires up the IframeClient + resize handler, and returns a destroy handle.
|
|
368
|
+
*/
|
|
369
|
+
|
|
370
|
+
interface MountResult {
|
|
371
|
+
iframe: HTMLIFrameElement;
|
|
372
|
+
/**
|
|
373
|
+
* Wrapper div the iframe is appended to. Always present in v1.0.0 and
|
|
374
|
+
* later. Hosts that previously relied on `container > iframe` should use
|
|
375
|
+
* `container iframe` or `[data-atom-circuit-embed] iframe` instead. The
|
|
376
|
+
* wrapper carries a position:relative anchor so the pre-handshake
|
|
377
|
+
* loading overlay (the spinner) can absolutely-position over the iframe
|
|
378
|
+
* without leaking into surrounding host layout. Padding (when supplied)
|
|
379
|
+
* is applied to this wrapper, never to the iframe element itself.
|
|
380
|
+
*/
|
|
381
|
+
wrapper: HTMLDivElement;
|
|
382
|
+
client: IframeClient;
|
|
383
|
+
destroy(): void;
|
|
384
|
+
}
|
|
385
|
+
/**
|
|
386
|
+
* Creates an iframe, appends it to the container, and connects to the widget.
|
|
387
|
+
* Returns a handle for cleanup.
|
|
388
|
+
*/
|
|
389
|
+
declare function mount(container: HTMLElement, opts?: MountOptions): MountResult;
|
|
390
|
+
|
|
391
|
+
export { type ChromeOptions, type MountError, type MountErrorCode, type MountOptions, type MountResult, PROTOCOL_VERSION, type ReadyPayload, type SwapErrorPayload, type SwapRouteSummary, type SwapSubmittedPayload, type SwapSuccessPayload, type ThemeOptions, type WidgetEvent, mount };
|