aegis-swap-widget 0.2.0 → 0.3.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.
Files changed (46) hide show
  1. package/CHANGELOG.md +143 -2
  2. package/DESIGN.md +77 -0
  3. package/README.md +251 -6
  4. package/dist/aegis-swap-widget.js +2461 -2447
  5. package/dist/components/icons.d.ts +13 -2
  6. package/dist/core.d.ts +15 -0
  7. package/dist/core.js +66 -0
  8. package/dist/errors.d.ts +1 -0
  9. package/dist/format.d.ts +2 -0
  10. package/dist/hooks/useChangeEmitter.d.ts +25 -0
  11. package/dist/hooks/useLifecycleToast.d.ts +16 -0
  12. package/dist/hooks/usePoolChangeEmitter.d.ts +1 -0
  13. package/dist/hooks/useResolvedTheme.d.ts +7 -0
  14. package/dist/hooks/useStatusChangeEmitter.d.ts +2 -0
  15. package/dist/hooks/useSubmitSwap.d.ts +13 -2
  16. package/dist/hooks/useTokenAllowance.d.ts +3 -0
  17. package/dist/hooks/useValueRefresh.d.ts +1 -0
  18. package/dist/index.d.ts +9 -7
  19. package/dist/metadata.d.ts +8 -0
  20. package/dist/permit.d.ts +7 -0
  21. package/dist/polling-BI4pZg8Z.js +1252 -0
  22. package/dist/polling.d.ts +1 -0
  23. package/dist/styles.css +1 -1
  24. package/dist/swap/authorizePermit.d.ts +30 -0
  25. package/dist/swap/executeSwapTransaction.d.ts +39 -0
  26. package/dist/swap/types.d.ts +7 -0
  27. package/dist/toast/ToastItem.d.ts +33 -0
  28. package/dist/toast/Toaster.d.ts +31 -0
  29. package/dist/toast/layout.d.ts +21 -0
  30. package/dist/toast/state.d.ts +69 -0
  31. package/dist/toast/swipe.d.ts +23 -0
  32. package/dist/toast/timer.d.ts +16 -0
  33. package/dist/toast/toast.d.ts +28 -0
  34. package/dist/toast/useDocumentVisible.d.ts +6 -0
  35. package/dist/toast/useToastTimer.d.ts +13 -0
  36. package/dist/transactionProgress.d.ts +38 -1
  37. package/dist/widget/AegisDirectSwapWidget.d.ts +17 -1
  38. package/dist/widget/AmountField.d.ts +7 -1
  39. package/dist/widget/ExecutionDetails.d.ts +5 -4
  40. package/dist/widget/NetworkSelector.d.ts +5 -2
  41. package/dist/widget/ProgressRail.d.ts +23 -0
  42. package/dist/widget/types.d.ts +81 -18
  43. package/dist/widget/useSwapController.d.ts +97 -0
  44. package/dist/widget/viewState.d.ts +11 -0
  45. package/package.json +10 -2
  46. package/dist/hooks/useScrollAnchor.d.ts +0 -12
package/CHANGELOG.md CHANGED
@@ -5,7 +5,146 @@ All notable changes to this project are documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
- ## [Unreleased]
8
+ ## [0.3.1] - 2026-06-08
9
+
10
+ ### Added
11
+ - Added a `Trade` / `Latest tx` switch inside the Trade details drawer. After a
12
+ successful swap, the drawer opens to a session-local receipt showing the
13
+ latest confirmed transaction's received amount, network fee, gas price, gas
14
+ used, block, and explorer link.
15
+ - Added an in-flight quote shimmer on the quote-derived amount field and a
16
+ wallet-step hint before approval or Permit2 signing.
17
+
18
+ ### Changed
19
+ - Softened the widget chrome by replacing several resting borders with tonal
20
+ elevation, quieter segmented controls, and a tighter Trade details surface.
21
+ - Reskinned the local demo surface so the playground reads like a host app demo
22
+ instead of a secondary widget.
23
+ - Resolved OS theme changes through `useSyncExternalStore`.
24
+
25
+ ### Documentation
26
+ - Clarified the widget integration contract: production props stay real, while
27
+ demo wallets, fake transaction flows, scenario controls, and mock host shells
28
+ belong in the separate playground.
29
+
30
+ ## [0.3.0] - 2026-06-08
31
+
32
+ ### Removed (breaking)
33
+ - `targets` prop — use `poolIds` (identical `string[]` shape).
34
+ - `markets` prop — use `pools`; `Object.values(markets)` migrates an existing map.
35
+ - `defaultDirection` values `'intoTarget'` / `'outOfTarget'` — use `'buy'` / `'sell'`.
36
+
37
+ ### Changed (breaking)
38
+ - Renamed `showApprovalModeControl` → `showApprovalMode`, pairing it with
39
+ `defaultApprovalMode`.
40
+
41
+ ### Migration
42
+ - `targets={ids}` → `poolIds={ids}`
43
+ - `markets={map}` → `pools={Object.values(map)}`
44
+ - `defaultDirection="intoTarget"` → `defaultDirection="buy"`
45
+ - `defaultDirection="outOfTarget"` → `defaultDirection="sell"`
46
+ - `showApprovalModeControl={x}` → `showApprovalMode={x}`
47
+
48
+ ### Internal
49
+ - Refactored the widget into a `useSwapController` hook (state, derivation, and
50
+ hook orchestration) over a thin presentational component, decomposed the swap
51
+ money path into pure `src/swap/` modules (`authorizePermit`,
52
+ `executeSwapTransaction`), and unified the host-callback emitters behind a
53
+ single `useChangeEmitter` primitive. No public behavior change.
54
+
55
+ ### Added
56
+ - Toast notifications as an alternative transaction-status surface: new
57
+ `statusSurface` prop (`'rail'` default, `'toast'`, `'both'`, `'none'`) plus
58
+ `toastPosition`. One toast per swap attempt morphs loading → success/error in
59
+ place with the received amount and a View transaction action; wallet
60
+ rejections auto-dismiss after 4s, other errors after 8s. The root
61
+ `data-progress-state` attribute stays on for every surface.
62
+ - `AegisSwapToaster` export: the toast viewport as a standalone component.
63
+ Mounting it anywhere takes over rendering from widget-provided toasters —
64
+ exactly one viewport per page — and owns `position`, `theme`/`accentColor`,
65
+ `duration`, `visibleToasts`, `closeButton`, `expand`, and `offset`. Toasts
66
+ stack with depth, expand on hover/focus, swipe-to-dismiss with momentum
67
+ (interruptible CSS transitions throughout), dismiss on Escape when focused,
68
+ pause their timers while hovered, mid-swipe, and in hidden tabs, and reduce
69
+ to fades under `prefers-reduced-motion`. Lifecycle morphs replay quiet icon
70
+ and text entrances in place, and terminal states tint the card border
71
+ (success whisper, error alarm) the way the progress rail recolors. The
72
+ viewport is hardened for arbitrary host pages: host `li`/text styling
73
+ cannot leak in, toasts hide in print, and the light theme keeps icon
74
+ contrast AA without overriding an explicit host accent.
75
+ - `aegisToast` export: toast function for host apps sharing the same viewport —
76
+ `aegisToast()` / `.info` / `.success` / `.error` / `.loading` / `.dismiss` /
77
+ `.promise`, with upsert-by-id morphing. New public types
78
+ `AegisSwapToasterProps`, `AegisSwapStatusSurface`, `AegisToastAction`,
79
+ `AegisToastOptions`, `AegisToastPosition`, `AegisToastType`, and
80
+ `AegisToastPromiseMessage`/`AegisToastPromiseMessages`.
81
+ - `onPoolChange` prop: fires with the active pool id whenever it changes —
82
+ user tab selection or a `poolIds` update that forces a fallback. Silent on
83
+ mount, so hosts only hear genuine transitions.
84
+ - `resolveTokenIconUrl` export (plus the `TokenMetadataByAddress` type): the
85
+ widget's own icon-source order — config `iconUrl`, metadata service, ichi
86
+ naming guess — so hosts rendering their own token marks resolve identically.
87
+ - `onStatusChange` prop: fires a lifecycle snapshot (`status`, `progress`, `txHash`,
88
+ `error`, `label`, rail geometry, and `walletPrompts`) on every transition, mirroring
89
+ the top progress rail — including errors and wallet rejections, which never appear
90
+ as a `status`. `walletPrompts.total/completed/active` exposes the SDK-owned provider
91
+ prompt plan for ERC20 approval, Permit2 authorization, and swap submission.
92
+ - `AegisSwapProgressRail` export: the widget's animated top rail as a standalone
93
+ component hosts can mount on their own containers, themed via `--aegis-swap-accent`,
94
+ `--aegis-swap-danger`, and `--aegis-swap-success` (the rail CSS now carries literal
95
+ dark-theme fallbacks for use outside the widget root).
96
+ - Host-tunable top rail tokens: `--aegis-rail-height`, `--aegis-rail-hold`, and
97
+ `--aegis-rail-fade` via the widget `style` prop. The success auto-reset reads the
98
+ computed hold/fade values so host-tuned terminal animations and status cleanup stay
99
+ synchronized.
100
+ - The top rail now separates completion from activity: `completePct` tracks completed
101
+ wallet-provider prompts, while pending/mining shimmer always sweeps the full top
102
+ border instead of starting from the completed fill.
103
+ - `networkOptions` prop plus `AegisSwapNetworkOption`/`AegisSwapNetworkIcon` types:
104
+ a display-only network menu list for previewing upcoming chains — non-selected
105
+ options render disabled by default with optional `status` text; a list without
106
+ the active chain gets it prepended; switching is not wired yet.
107
+ - Public types `AegisSwapStatusSnapshot`, `AegisSwapWalletPromptProgress`,
108
+ `SubmitStatus`, `TransactionProgressState`, and the `isUserRejectedError` helper.
109
+
110
+ ### Changed
111
+ - The branded header (`title`/`subtitle`) is now text-only: the Aegis logomark no longer
112
+ renders next to host-provided titles. Aegis attribution lives solely in the
113
+ "Powered by Aegis" footer.
114
+ - The trade-details toggle no longer compensates ancestor scroll position when
115
+ expanding or collapsing; the panel expands in place.
116
+ - Hover styles apply only on hover-capable devices (`@media (hover: hover)`), so taps
117
+ on touch screens no longer leave stuck hover states (raised tabs, rotated flip arrows).
118
+ - The direction-flip arrows now spin when the direction actually flips (driven by a
119
+ `data-direction` attribute) instead of pre-playing the rotation on hover.
120
+ - A busy CTA reads as working, not blocked: while approving/signing/submitting the
121
+ submit button keeps near-full opacity, shows a `progress` cursor, and runs a soft
122
+ sheen (`data-busy` attribute, motion-gated).
123
+ - Status feedback eases in instead of popping: error/success lines and the explorer
124
+ link get a staggered fade-rise, and the success line opens with a self-drawing
125
+ check ("Swap complete · received ~…", previously an em dash).
126
+ - Network-menu rows cascade in as the menu opens (30ms steps, reduced-motion safe),
127
+ and the menu's overlay shadow is re-tuned for the light theme.
128
+ - Switching feels like one control: the pool tabs and the Exact/Max approval
129
+ segments glide a single active pill between options instead of cross-fading
130
+ two buttons, and the Sell/Buy token pills cross-fade when a switch retargets
131
+ them. New `--aegis-switch-dur`/`--aegis-spring` tokens (overridable via the
132
+ `style` prop) drive it; reduced-motion safe.
133
+ - Concentric corner radii on nested chrome: pool tab rail, network menu, approval
134
+ segments, and the trade-details toggle now derive outer radius = inner + padding.
135
+ - The trade-details panel sheds visual weight: approval segments and route actions
136
+ drop from 40px to 26px (keeping control-height touch targets via invisible
137
+ extensions), the route chip loses its divider hairlines and dead drop shadow, the
138
+ active pill is fill-only, and the setting rows tighten to 38px with unified 12px
139
+ labels.
140
+
141
+ ### Fixed
142
+ - Keyboard focus no longer reshapes controls: the global `:focus-visible` rule forced
143
+ a 6px border-radius onto every focused element, visibly squaring the circular flip
144
+ button; the ring now follows each element's own radius.
145
+ - The slippage input no longer triggers iOS Safari's focus auto-zoom (16px input font
146
+ on coarse pointers).
147
+ - The network trigger's touch target is extended to ~44px without growing the chip.
9
148
 
10
149
  ## [0.2.0] - 2026-06-05
11
150
 
@@ -75,7 +214,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
75
214
  - Initial embeddable direct-pool Aegis swap widget for WATER, WOOD, GRASS, and STONE.
76
215
  - Generic pool widget API; ERC-20 → Permit2 approval and Permit2 batch signing.
77
216
 
78
- [Unreleased]: https://github.com/amplifi-liquidity/aegis-widget/compare/v0.2.0...HEAD
217
+ [Unreleased]: https://github.com/amplifi-liquidity/aegis-widget/compare/v0.3.1...HEAD
218
+ [0.3.1]: https://github.com/amplifi-liquidity/aegis-widget/compare/v0.3.0...v0.3.1
219
+ [0.3.0]: https://github.com/amplifi-liquidity/aegis-widget/compare/v0.2.0...v0.3.0
79
220
  [0.2.0]: https://github.com/amplifi-liquidity/aegis-widget/compare/v0.1.3...v0.2.0
80
221
  [0.1.3]: https://github.com/amplifi-liquidity/aegis-widget/compare/v0.1.2...v0.1.3
81
222
  [0.1.2]: https://github.com/amplifi-liquidity/aegis-widget/compare/v0.1.0...v0.1.2
package/DESIGN.md ADDED
@@ -0,0 +1,77 @@
1
+ # Design tokens
2
+
3
+ The widget themes through CSS custom properties on the `.aegis-swap-widget` root
4
+ (and the toast viewport). This file is the reference for the **public** tokens —
5
+ the ones a host may override and depend on. Set them inline via the `style` prop,
6
+ in a stylesheet on an ancestor, or per-toaster via `AegisSwapToaster`.
7
+
8
+ Renaming or removing anything in the tables below is a breaking (major) change.
9
+ Tokens prefixed `--aegis-_*` are internal and may change without notice.
10
+
11
+ ## Color
12
+
13
+ The default palette is authored in **OKLCH**: neutrals are tinted toward the
14
+ brand hue (~165) so the surface reads as one family, each step's lightness is
15
+ matched to keep text contrast (subtle/muted stay ≥ 4.5:1 on the panel for 12px
16
+ text), and no neutral is pure black or white. Derived accent tints
17
+ (`--aegis-swap-accent-soft/-line/-glow/-ring`) are computed with
18
+ `color-mix(in srgb, …)`, so a host can override `--aegis-swap-accent` with **any**
19
+ CSS color (hex, `rgb()`, `oklch()`, …) and the whole accent system recomputes.
20
+
21
+ | Token | Role |
22
+ | --- | --- |
23
+ | `--aegis-swap-accent` | CTA, selected pool, focus ring, links. Host's primary knob. |
24
+ | `--aegis-swap-accent-text` | Text on the accent CTA. Auto-derived from `accentColor`; override with `accentTextColor`. |
25
+ | `--aegis-swap-success` | Terminal success fill (rail, toast). |
26
+ | `--aegis-swap-danger` | Error fill and inline error text. |
27
+ | `--aegis-swap-bg` / `--aegis-swap-panel` / `--aegis-swap-panel-raised` / `--aegis-swap-panel-soft` | Surface ramp, darkest → most raised. |
28
+ | `--aegis-swap-text` / `--aegis-swap-muted` / `--aegis-swap-subtle` | Text ramp, primary → tertiary. |
29
+ | `--aegis-swap-border` / `--aegis-swap-border-strong` | Hairline and emphasized borders. |
30
+ | `--aegis-swap-font-family` | Font family; the widget does **not** inherit the host font by default. |
31
+
32
+ `theme="light"` swaps the surface/text/border/danger ramp; `accent`/`success`
33
+ carry across both themes. `theme="auto"` follows `prefers-color-scheme`.
34
+
35
+ ## Motion
36
+
37
+ All motion is CSS and respects `prefers-reduced-motion` automatically.
38
+
39
+ | Token | Default | Role |
40
+ | --- | --- | --- |
41
+ | `--aegis-switch-dur` | `260ms` | Segmented-pill glide and Sell/Buy token swap. `0ms` disables. |
42
+ | `--aegis-spring` | `cubic-bezier(0.22, 1, 0.36, 1)` | Easing for interactive state changes. |
43
+ | `--aegis-ease` | `cubic-bezier(0.2, 0, 0, 1)` | Easing for entrances and transitions. |
44
+
45
+ The pool-tab/approval-segment open is intentionally slower than the close
46
+ (enter-slow, exit-fast); that asymmetry is by design, not a value to "correct".
47
+
48
+ ## Progress rail
49
+
50
+ | Token | Default | Role |
51
+ | --- | --- | --- |
52
+ | `--aegis-rail-height` | `3px` | Rail thickness. |
53
+ | `--aegis-rail-hold` | `3000ms` | How long the terminal state holds before fading. |
54
+ | `--aegis-rail-fade` | `420ms` | Terminal fade-out duration. |
55
+
56
+ `--aegis-rail-hold` / `--aegis-rail-fade` must be CSS time strings (`ms`/`s`):
57
+ the widget reads the computed values at runtime to keep the success auto-reset in
58
+ sync with the host-tuned animation. These also apply to the standalone
59
+ `AegisSwapProgressRail`.
60
+
61
+ ## Toasts
62
+
63
+ The toast viewport portals to `document.body`, so widget-root variables do **not**
64
+ reach it — set these on `:root` or pass `AegisSwapToaster` props.
65
+
66
+ | Token | Role |
67
+ | --- | --- |
68
+ | `--aegis-swap-toast-z` | Stacking context for the viewport. |
69
+ | `--aegis-swap-toast-width` | Toast card width. |
70
+ | `--aegis-swap-toast-offset-x` / `--aegis-swap-toast-offset-y` | Viewport inset from the chosen corner. |
71
+
72
+ ## Internal tokens
73
+
74
+ `--aegis-r-sm/-md/-lg` (radii), `--aegis-seg-*` (segment chrome),
75
+ `--aegis-swap-shell`, and any `--aegis-_*` primitive are internal implementation
76
+ details. They are not part of the supported theming surface and may change
77
+ between minor versions.
package/README.md CHANGED
@@ -19,6 +19,9 @@ bundled config sets one; today only Ethereum mainnet is bundled.
19
19
  npm install aegis-swap-widget
20
20
  ```
21
21
 
22
+ Peer dependencies (provided by the host app): `react >=18 <20`, `react-dom >=18 <20`,
23
+ and `viem ^2`. The package is ESM-only and ships no runtime dependencies of its own.
24
+
22
25
  ## Basic Usage
23
26
 
24
27
  ```tsx
@@ -39,6 +42,32 @@ Token icons are hydrated from the same metadata table used by the Aegis website:
39
42
  `GlobalToken.iconURL` from the Aegis metadata GraphQL API. The default endpoint is
40
43
  `https://aegis.markets/graphql/meta`.
41
44
 
45
+ ## Integration Contract
46
+
47
+ `aegis-swap-widget` is the production widget SDK. It uses real providers, RPCs,
48
+ pool config, approvals, Permit2 authorization, swap submission, and callbacks.
49
+
50
+ Demo wallets, fake transaction flows, scenario controls, and mock host shells
51
+ belong in the separate playground. They are not exposed as production widget
52
+ props, so an embedded widget cannot accidentally run in a fake mode.
53
+
54
+ Hosts should customize through `pools`, `chains`, `rpcUrls`,
55
+ `metadataEndpoint`, wallet/provider callbacks, status surfaces, `accentColor`,
56
+ `accentTextColor`, `className`, `style`, `title`, and `subtitle`. The widget is
57
+ meant to blend into a host app without forking its internals.
58
+
59
+ Treat contract addresses as trusted integration config. Pool and chain config
60
+ can provide `router`, `permit2`, `quoter`, `poolKey`, and `poolId`; the widget
61
+ normalizes addresses and validates shape, but it cannot prove that a host's
62
+ override points at the intended Aegis or Uniswap deployment. Source those values
63
+ from your own audited config or backend, not from user input.
64
+
65
+ `onSubmitted`, `onSuccess`, `onError`, `onQuote`, `onStatusChange`, and
66
+ `onPoolChange` are the supported host signals. The in-widget "Latest tx" drawer
67
+ is a session-local UI receipt for the most recent confirmed swap; it is not a
68
+ transaction history API. If a host needs durable history, record
69
+ `onSubmitted`/`onSuccess` on the host side.
70
+
42
71
  ## Appearance
43
72
 
44
73
  Hosts can choose the widget surface and brand accent at instantiation time:
@@ -55,6 +84,10 @@ Hosts can choose the widget surface and brand accent at instantiation time:
55
84
 
56
85
  The default accent is neutral green. Pass `accentColor`, `accentTextColor`, `title`, `subtitle`, `className`, or `style` when the widget should blend into another product UI.
57
86
 
87
+ The default palette is authored in OKLCH (tinted neutrals, a calmer accent). The
88
+ full set of host-overridable CSS variables — color, motion, rail, and toast
89
+ tokens — is documented in [DESIGN.md](./DESIGN.md).
90
+
58
91
  ## Configure Pools
59
92
 
60
93
  Pass the direct pools you want to expose. This is the recommended integration path because it is not tied to any specific asset list.
@@ -272,6 +305,214 @@ Use `className` for host CSS selectors and `style` for one-off layout overrides
272
305
 
273
306
  `accentColor`, `accentTextColor`, `title`, and `subtitle` are the intended props for making the widget fit a host surface without forking the component.
274
307
 
308
+ ## Transaction Status
309
+
310
+ The widget paints swap progress on a rail along its own top edge — an accent
311
+ sweep while pending/mining, solid green on success, solid red on errors
312
+ (including wallet rejections), then a short hold and fade. The rail is the
313
+ default of two built-in status surfaces; `statusSurface` switches to toast
314
+ notifications, shows `'both'`, or `'none'` (see Toasts below). Hosts can tune
315
+ the rail through the widget's `style` prop:
316
+
317
+ ```tsx
318
+ <AegisSwapWidget
319
+ style={{
320
+ '--aegis-rail-height': '4px',
321
+ '--aegis-rail-hold': '1800ms',
322
+ '--aegis-rail-fade': '260ms'
323
+ }}
324
+ />
325
+ ```
326
+
327
+ Use CSS time strings (`ms` or `s`) for `--aegis-rail-hold` and
328
+ `--aegis-rail-fade`; the widget reads the computed tokens at runtime so the
329
+ success auto-reset stays in sync with the host-tuned animation. Hosts can
330
+ mirror that state outside the widget three ways:
331
+
332
+ `onStatusChange` fires a snapshot on every lifecycle transition. `walletPrompts`
333
+ is the SDK-owned provider-prompt plan: ERC20 approval, Permit2 authorization,
334
+ and swap submission are counted from the current allowance/authorization reads.
335
+ Use that for step copy or external rails instead of guessing from status names:
336
+
337
+ ```tsx
338
+ <AegisSwapWidget
339
+ onStatusChange={({ status, progress, walletPrompts, txHash, error }) => {
340
+ // status: 'idle' | 'approving' | 'signing' | 'submitting' | 'success'
341
+ // progress: 'pending' | 'mining' | 'success' | 'error' | null (rail hidden)
342
+ // walletPrompts: { total, completed, active } // active is 1-based or null
343
+ // error: user-facing message, e.g. "Request rejected."
344
+ }}
345
+ />
346
+ ```
347
+
348
+ The rail separates step completion from activity: `completePct` fills the calm
349
+ completed portion (for example 33.3% after one of three wallet prompts), while
350
+ the pending/mining shimmer is always a full-width top-border sweep.
351
+
352
+ Errors never appear in `status` (the submit machine resets to `'idle'`); read
353
+ them from `progress === 'error'` plus `error`. For classifying the raw `Error`
354
+ from `onError`, `isUserRejectedError` is exported.
355
+
356
+ Success clears itself after confirming (by default a 3s hold plus the 420ms
357
+ rail fade-out), the widget resets and emits a final
358
+ `{ status: 'idle', progress: null }` snapshot. Errors persist until the
359
+ user interacts with the widget again — the in-widget rail fades out visually,
360
+ but the last snapshot and `data-progress-state` keep reporting `'error'`,
361
+ matching the widget's own inline error message.
362
+
363
+ `AegisSwapProgressRail` renders the animated rail standalone — mount it inside
364
+ any `position: relative` container and feed it the snapshot's `progress`:
365
+
366
+ ```tsx
367
+ import { useState } from 'react'
368
+ import {
369
+ AegisSwapProgressRail,
370
+ AegisSwapWidget,
371
+ type AegisSwapStatusSnapshot
372
+ } from 'aegis-swap-widget'
373
+ import 'aegis-swap-widget/styles.css'
374
+
375
+ export function CheckoutHeader() {
376
+ const [status, setStatus] = useState<AegisSwapStatusSnapshot | null>(null)
377
+
378
+ return (
379
+ <>
380
+ <AegisSwapWidget onStatusChange={setStatus} />
381
+
382
+ <header style={{ position: 'relative' }}>
383
+ <AegisSwapProgressRail
384
+ state={status?.progress ?? null}
385
+ label={status?.label ?? undefined}
386
+ completePct={status?.completePct ?? undefined}
387
+ activeWidthPct={status?.activeWidthPct ?? undefined}
388
+ />
389
+ My checkout
390
+ </header>
391
+ </>
392
+ )
393
+ }
394
+ ```
395
+
396
+ Driven from `progress` alone, the standalone rail uses simplified geometry — a
397
+ full-width sweep while in flight, solid fill when terminal. Feed the snapshot's
398
+ `completePct` and `activeWidthPct` to mirror the in-widget prompt-aware fill.
399
+ Theme it by setting `--aegis-swap-accent` (plus
400
+ `--aegis-swap-danger` and `--aegis-swap-success` for the terminal fills) on an
401
+ ancestor. `--aegis-rail-height`, `--aegis-rail-hold`, and
402
+ `--aegis-rail-fade` apply to the standalone rail as CSS too; only the in-widget
403
+ auto-reset timer reads those motion tokens. The built-in color fallbacks are
404
+ the dark-theme values, so light hosts should set them.
405
+
406
+ For CSS-only hooks, the widget root mirrors the rail as
407
+ `data-progress-state="pending | mining | success | error"`, so a host
408
+ stylesheet can react without JavaScript. The attribute stays on for every
409
+ `statusSurface`, including `'toast'` and `'none'`.
410
+
411
+ ## Toasts
412
+
413
+ `statusSurface="toast"` replaces the rail with toast notifications; `'both'`
414
+ shows rail and toasts, `'none'` neither. With `'toast'` or `'both'` the widget
415
+ provides its own toast viewport — no extra setup:
416
+
417
+ ```tsx
418
+ <AegisSwapWidget statusSurface="toast" toastPosition="bottom-right" />
419
+ ```
420
+
421
+ One toast per swap attempt morphs through the lifecycle: a loading card
422
+ follows the same wallet-prompt copy as the rail, then settles into success
423
+ (received amount plus a View transaction link) or error in place. Wallet
424
+ rejections auto-dismiss after 4s and other errors after 8s — the widget's
425
+ inline error message and `data-progress-state` persist regardless. A retry
426
+ withdraws the previous attempt's toast and starts a fresh one.
427
+
428
+ Mounting `<AegisSwapToaster />` anywhere in the app takes over rendering from
429
+ every widget-provided toaster — exactly one toast viewport exists per page —
430
+ which is how hosts control placement and toast options:
431
+
432
+ ```tsx
433
+ import { AegisSwapToaster, AegisSwapWidget } from 'aegis-swap-widget'
434
+ import 'aegis-swap-widget/styles.css'
435
+
436
+ <AegisSwapWidget statusSurface="toast" />
437
+ <AegisSwapToaster position="top-right" theme="light" accentColor="#006adc" closeButton />
438
+ ```
439
+
440
+ `AegisSwapToaster` props: `position` (`top`/`bottom` × `left`/`center`/`right`,
441
+ default `'bottom-right'`), `theme`/`accentColor`/`accentTextColor` (the toaster
442
+ portals to `document.body`, so widget-root accent variables do not reach it —
443
+ pass these props or set `--aegis-swap-accent` and friends on `:root`),
444
+ `duration` (auto-dismiss for info/success, default 5000ms), `visibleToasts`
445
+ (default 3), `closeButton` (default `false` — swipe and auto-dismiss always
446
+ work), `expand` (always-expanded list instead of the hover-expanded stack),
447
+ `offset` (viewport inset, default 24px / 16px on small screens), `className`,
448
+ and `style` (e.g. `'--aegis-swap-toast-z'`, `'--aegis-swap-toast-width'`).
449
+
450
+ The toast system is the widget's own implementation, not a dependency: toasts
451
+ stack with depth, hovering (or focusing) the stack expands it into a list,
452
+ swiping toward the nearest viewport edge dismisses (momentum-based — a quick
453
+ flick is enough), auto-dismiss timers pause while hovered, while swiping, and
454
+ in hidden tabs, and `prefers-reduced-motion` reduces all motion to fades.
455
+
456
+ `aegisToast` is exported so host apps can fire their own toasts through the
457
+ same viewport — a plain function, no hooks or context:
458
+
459
+ ```tsx
460
+ import { aegisToast } from 'aegis-swap-widget'
461
+
462
+ aegisToast('Copied') // info
463
+ aegisToast.success('Saved', { description: 'Order #1204' })
464
+ aegisToast.error('Could not save', { duration: 8000 })
465
+
466
+ const id = aegisToast.loading('Uploading…') // sticky until updated
467
+ aegisToast.success('Uploaded', { id }) // reusing an id morphs in place
468
+
469
+ aegisToast.promise(save(), {
470
+ loading: 'Saving…',
471
+ success: (result) => `Saved ${result.name}`,
472
+ error: (reason) => (reason instanceof Error ? reason.message : 'Save failed')
473
+ })
474
+ ```
475
+
476
+ Every creator returns the toast id. Actions render as a new-tab link
477
+ (`{ label, href }`) or a button (`{ label, onClick }`); acting on a toast
478
+ dismisses it. `duration: Infinity` keeps a toast until dismissed;
479
+ `dismissible: false` hides the close affordance and blocks swiping. One caveat
480
+ shared with every module-store toast library: two bundled copies of the
481
+ package keep two independent toast stores (and two viewports).
482
+
483
+ ## Motion
484
+
485
+ Switching pools or the approval mode glides a single active pill between the
486
+ options (iOS-segmented-control style), and the Sell/Buy token pills cross-fade
487
+ when a switch retargets them. It is all CSS — no animation library — and every
488
+ animation respects `prefers-reduced-motion` automatically.
489
+
490
+ Hosts tune or disable the switch motion through CSS tokens on the `style` prop:
491
+
492
+ ```tsx
493
+ <AegisSwapWidget
494
+ style={{
495
+ '--aegis-switch-dur': '180ms', // pill glide + token swap; '0ms' disables
496
+ '--aegis-spring': 'cubic-bezier(0.2, 0, 0, 1)' // switch easing
497
+ }}
498
+ />
499
+ ```
500
+
501
+ ## Migrating to 0.3.0
502
+
503
+ 0.3.0 removes the props that were deprecated in 0.2.x. The replacements are
504
+ drop-in:
505
+
506
+ | Removed | Use instead |
507
+ | --- | --- |
508
+ | `targets={[...]}` | `poolIds={[...]}` (same `string[]`) |
509
+ | `markets={map}` | `pools={Object.values(map)}` |
510
+ | `defaultDirection="intoTarget"` | `defaultDirection="buy"` |
511
+ | `defaultDirection="outOfTarget"` | `defaultDirection="sell"` |
512
+ | `showApprovalModeControl` | `showApprovalMode` |
513
+
514
+ No other props changed. See the [CHANGELOG](./CHANGELOG.md) for the full entry.
515
+
275
516
  ## Props
276
517
 
277
518
  - `provider`: optional EIP-1193 provider. Defaults to auto-detected `window.ethereum`.
@@ -279,16 +520,18 @@ Use `className` for host CSS selectors and `style` for one-off layout overrides
279
520
  - `getProvider`: optional provider resolver for wallet libraries with async providers.
280
521
  - `onConnectWallet`: optional callback for opening the host wallet modal. It may return `{ provider, account }`.
281
522
  - `onSubmitted`: `(hash, swap?)` fired when the swap transaction is sent. `swap` carries mode, token symbols, amounts, and fee for analytics.
282
- - `onSuccess`: `({ hash, explorerUrl, swap? })` fired when the receipt confirms.
523
+ - `onSuccess`: `({ hash, explorerUrl, swap? })` fired when the receipt confirms. Receipt gas/block facts are rendered inside the widget's session-local Latest tx view, not exposed through this callback.
283
524
  - `onError`: `(error)` fired on approval/swap failures (after the widget shows its own message).
284
525
  - `onQuote`: `(quote)` fired on every quote refresh.
526
+ - `onStatusChange`: `(snapshot)` fired on every lifecycle transition with what the top progress rail shows (see Transaction Status).
527
+ - `onPoolChange`: `(poolId)` fired when the active pool changes — user selection or a `poolIds` update that forces a fallback. Silent on mount.
285
528
  - `chainId`: active chain id. Defaults to Ethereum mainnet.
286
529
  - `chains`: optional chain config overrides or additional chain configs, including per-chain `v4Contracts`.
530
+ - `networkOptions`: optional display-only entries for the network menu (`{ id, name, icon?, selected?, disabled?, status? }`). The active entry is resolved by explicit `selected: true`, else by `id` matching the active chain id, else by `name`; a list without the active chain gets it prepended so the trigger never misreports the chain in use. Non-selected options render disabled by default with optional `status` text (e.g. "Coming soon"); `icon` is `'ethereum' | 'base'`. Clicking the enabled option only closes the menu (disabled options ignore clicks) — switching is not wired yet.
287
531
  - `pools`: direct pool configs to render and swap against. This is the canonical config path.
288
532
  - `defaultPoolId`: initial pool id when `pools` is provided.
289
533
  - `poolIds`: optional pool id order/subset. Use this when the host wants to hide pools or control ordering.
290
- - `defaultDirection`: `buy` (counter token into the pool token) or `sell`. The legacy `intoTarget`/`outOfTarget` values still work but are deprecated.
291
- - `markets`: **deprecated** advanced override; pass `pools` (or `Object.values(markets)`) instead.
534
+ - `defaultDirection`: `buy` (counter token into the pool token) or `sell` (pool token into the counter token). Defaults to `buy`.
292
535
  - `defaultSlippagePercent`: number (canonical) or numeric string, in percent. Defaults to `1`.
293
536
  - `rpcUrls`: RPC fallback list for the active chain, or a `{ [chainId]: string[] }` map for multichain hosts.
294
537
  - `metadataEndpoint`: optional metadata GraphQL endpoint for `GlobalToken.iconURL` token icons. Set to `null` to disable.
@@ -302,9 +545,11 @@ Use `className` for host CSS selectors and `style` for one-off layout overrides
302
545
  - `accentTextColor`: optional CTA text color for contrast with the chosen accent.
303
546
  - `showPoolLink`: whether to render the direct-pool link when the active chain provides `poolExplorerUrl`.
304
547
  - `defaultApprovalMode`: `exact` approves only the current quote spend by default; `max` opts into the old unlimited ERC-20 to Permit2 approval behavior.
305
- - `showApprovalModeControl`: whether the details drawer exposes the Exact/Max approval toggle. Defaults to `true`.
306
- - `showExecutionDetails`: whether to render the collapsible settings drawer for slippage, approval mode, and route. Defaults to `true`.
548
+ - `showApprovalMode`: whether the details drawer exposes the Exact/Max approval toggle. Defaults to `true`.
549
+ - `showExecutionDetails`: whether to render the collapsible drawer for trade details and, after a confirmed swap, the session-local Latest tx receipt. Defaults to `true`.
307
550
  - `defaultExecutionDetailsOpen`: whether that settings drawer starts expanded. Defaults to `false`.
551
+ - `statusSurface`: where transaction status is surfaced — `'rail'` (default), `'toast'`, `'both'`, or `'none'`. The root `data-progress-state` attribute stays on regardless (see Toasts).
552
+ - `toastPosition`: viewport corner for the widget-provided toaster. Defaults to `'bottom-right'`; ignored once a host mounts `<AegisSwapToaster />`.
308
553
 
309
554
  The widget intentionally does not inherit the host font; set the
310
555
  `--aegis-swap-font-family` CSS variable (via `style` or a stylesheet) to match
@@ -365,7 +610,7 @@ git switch main && git pull
365
610
  npm ci
366
611
  npm run typecheck && npm run test && npm run build
367
612
  # Move CHANGELOG [Unreleased] entries under the new version heading.
368
- npm version patch # e.g. 0.1.2 -> 0.1.3; commits and tags vX.Y.Z
613
+ npm version minor # e.g. 0.2.1 -> 0.3.0 for a breaking 0.x release; commits and tags vX.Y.Z
369
614
  git push origin main --follow-tags
370
615
  npm publish --access public # prepublishOnly gate + prepack build run automatically
371
616
  npm view aegis-swap-widget version