@liberfi.io/ui-trade 0.1.1 → 0.1.3

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/README.md CHANGED
@@ -1,13 +1,20 @@
1
1
  # @liberfi.io/ui-trade
2
2
 
3
- Trade hooks for the Liberfi React SDK. This package provides chain-agnostic swap logic that works across Solana, Ethereum, and BSC. It is a pure-logic layer — no UI components, no toasts, no i18n — leaving side-effect control entirely to the consumer.
3
+ Trade hooks and widgets for the Liberfi React SDK. This package provides chain-agnostic swap logic and ready-to-use trade form components that work across Solana, Ethereum, and BSC.
4
+
5
+ The package is organized in three layers:
6
+
7
+ - **Hooks** (`useSwap`, `useSwapRoutePolling`, `useTxConfirmation`) — pure-logic building blocks with no UI side-effects, designed for IoC.
8
+ - **Swap Widget** (`SwapWidget` / `useSwapScript` / `SwapUI` / `SwapPreviewModal`) — a full swap form with preview-before-confirm flow, following the Script/Widget/UI three-layer architecture, composable at any level.
9
+ - **Instant Trade** (`InstantTradeWidget` / `InstantTradeProvider` / `PresetFormWidget`) — a configurable quick-trade form with buy/sell tabs, preset management, customizable quick-amount buttons, and trade settings (slippage, priority fee, tip fee, anti-MEV).
4
10
 
5
11
  ## Design Philosophy
6
12
 
7
- - **IoC (Inversion of Control)** — Side-effects (toast, analytics, navigation) are injected via `onSubmitted` / `onError` callbacks. The hook never hardcodes any feedback mechanism.
13
+ - **IoC (Inversion of Control)** — Side-effects (toast, analytics, navigation) are injected via callbacks. Hooks never hardcode any feedback mechanism.
14
+ - **Script/Widget/UI architecture** — The swap form follows the monorepo's three-layer pattern: `useSwapScript` (data + state), `SwapWidget` (orchestration), `SwapUI` (presentation). Consumers can use the widget directly or compose Script + custom UI.
8
15
  - **Chain-agnostic** — Works across Solana (Jupiter) and EVM chains (KyberSwap). Chain-specific differences (dex selection, tx format) are handled by the underlying `@liberfi.io/client` and `WalletAdapter` abstractions.
9
- - **Wallet-flexible**The `WalletAdapter` is passed per-call, not coupled to a specific wallet provider or context.
10
- - **Layered architecture** — Builds on `@liberfi.io/react` (data layer) and `@liberfi.io/wallet-connector` (signing layer) without duplicating their logic.
16
+ - **UI via `@liberfi.io/ui`** Uses the shared UI library (`Button`, `Input`, `Avatar`, `Modal`, etc.) for consistent styling across the monorepo. No direct `@heroui/react` dependency.
17
+ - **Layered architecture** — Builds on `@liberfi.io/react` (data layer), `@liberfi.io/wallet-connector` (signing layer), and `@liberfi.io/ui` (presentation layer) without duplicating their logic.
11
18
 
12
19
  ## Installation
13
20
 
@@ -20,6 +27,7 @@ Peer dependencies the consumer must provide:
20
27
  - `react` (>=18)
21
28
  - `react-dom` (>=18)
22
29
  - `@liberfi.io/react` (provides `DexClientProvider` and `useDexClient`)
30
+ - `@liberfi.io/ui` (provides UI components: `Button`, `Input`, `Modal`, `Avatar`, etc.)
23
31
  - `@liberfi.io/wallet-connector` (provides `WalletAdapter` type)
24
32
 
25
33
  ## API Reference
@@ -44,6 +52,27 @@ Orchestrates the full swap flow: **route -> sign -> send**.
44
52
  | `swap` | `(input: SwapInput) => Promise<SwapResult>` | Executes the swap. Resolves with `SwapResult` on success, throws on failure. |
45
53
  | `isSwapping` | `boolean` | Whether a swap is currently in progress. |
46
54
 
55
+ #### `useSwapRoutePolling(params, options?)`
56
+
57
+ Polls for swap route quotes at a configurable interval. Built on `useSwapRouteQuery` (TanStack Query).
58
+
59
+ **Parameters:**
60
+
61
+ | Name | Type | Description |
62
+ | ------------------ | ------------------------- | ---------------------------------------------------------------------- |
63
+ | `params` | `Partial<API.SwapParams>` | Route parameters. Polling starts when all required fields are present. |
64
+ | `options.interval` | `number` | Polling interval in ms. Defaults to 12000. |
65
+ | `options.paused` | `boolean` | Pause polling (e.g. during swap execution). |
66
+ | `options.onError` | `(error: Error) => void` | Called when a route fetch fails. |
67
+
68
+ **Returns:** `{ route, isRouting, error }`
69
+
70
+ | Name | Type | Description |
71
+ | ----------- | ---------------------------- | --------------------------------- |
72
+ | `route` | `API.SwapRoute \| undefined` | Current route quote. |
73
+ | `isRouting` | `boolean` | Whether a route is being fetched. |
74
+ | `error` | `Error \| null` | Latest route fetch error. |
75
+
47
76
  #### `useTxConfirmation(options?: UseTxConfirmationOptions)`
48
77
 
49
78
  Tracks transaction confirmation via backend SSE. Designed to compose with `useSwap`.
@@ -65,6 +94,126 @@ Tracks transaction confirmation via backend SSE. Designed to compose with `useSw
65
94
  | `clearAll` | `() => void` | Remove all tracked transactions. |
66
95
  | `transactions` | `Map<string, TrackedTx>` | All tracked transactions with their current status. |
67
96
 
97
+ ### Components (Swap Widget)
98
+
99
+ The swap widget follows the **Script / Widget / UI** three-layer architecture.
100
+
101
+ #### `SwapWidget`
102
+
103
+ Plug-and-play swap form. Calls `useSwapScript` internally and renders `SwapUI` with `SwapPreviewModal`. Clicking the swap button opens a preview modal; confirming in the modal executes the swap.
104
+
105
+ ```tsx
106
+ <SwapWidget
107
+ chain={Chain.SOLANA}
108
+ from="So11111111111111111111111111111111111111112"
109
+ to="EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v"
110
+ onComplete={(result) => console.log(result)}
111
+ className="my-swap"
112
+ />
113
+ ```
114
+
115
+ **Props (`SwapWidgetProps`):**
116
+
117
+ | Name | Type | Description |
118
+ | ------------- | --------------------------------------------------------- | ----------------------------- |
119
+ | `chain` | `Chain` | Target chain. |
120
+ | `from?` | `string` | Initial from-token address. |
121
+ | `to?` | `string` | Initial to-token address. |
122
+ | `onComplete?` | `(result: { success: boolean; txHash?: string }) => void` | Called when swap completes. |
123
+ | `className?` | `string` | External style customization. |
124
+
125
+ #### `useSwapScript(params: UseSwapScriptParams)`
126
+
127
+ Script layer hook. Encapsulates all data fetching, state management, and swap execution. No JSX, no UI imports. Use this to build a custom swap UI.
128
+
129
+ **Parameters (`UseSwapScriptParams`):**
130
+
131
+ | Name | Type | Description |
132
+ | ------------- | --------------------------------------------------------- | --------------------------- |
133
+ | `chain` | `Chain` | Target chain. |
134
+ | `from?` | `string` | Initial from-token address. |
135
+ | `to?` | `string` | Initial to-token address. |
136
+ | `onComplete?` | `(result: { success: boolean; txHash?: string }) => void` | Called when swap completes. |
137
+
138
+ **Returns (`UseSwapScriptResult`):**
139
+
140
+ | Name | Type | Description |
141
+ | --------------------- | ---------------------------------- | -------------------------------------------- |
142
+ | `fromTokenAddress` | `string` | Currently selected from-token address. |
143
+ | `toTokenAddress` | `string` | Currently selected to-token address. |
144
+ | `setFromTokenAddress` | `(addr: string) => void` | Update from-token address. |
145
+ | `setToTokenAddress` | `(addr: string) => void` | Update to-token address. |
146
+ | `fromToken` | `Token \| null` | From-token metadata. |
147
+ | `toToken` | `Token \| null` | To-token metadata. |
148
+ | `fromBalance` | `Portfolio \| null` | User's from-token balance. |
149
+ | `toBalance` | `Portfolio \| null` | User's to-token balance. |
150
+ | `amount` | `string \| undefined` | Human-readable amount. |
151
+ | `setAmount` | `(v: string \| undefined) => void` | Update amount. |
152
+ | `setHalfAmount` | `() => void` | Set amount to half of from-token balance. |
153
+ | `setMaxAmount` | `() => void` | Set amount to full from-token balance. |
154
+ | `amountInDecimals` | `string \| undefined` | Amount in smallest unit. |
155
+ | `amountInUsd` | `string \| undefined` | Amount in USD. |
156
+ | `outputAmount` | `string \| undefined` | Formatted output amount from route. |
157
+ | `outputAmountInUsd` | `string \| undefined` | Output amount in USD. |
158
+ | `route` | `API.SwapRoute \| undefined` | Current route quote. |
159
+ | `isRouting` | `boolean` | Whether a route is being fetched. |
160
+ | `routeError` | `Error \| null` | Route fetch error. |
161
+ | `swap` | `() => Promise<void>` | Execute the swap. |
162
+ | `isSwapping` | `boolean` | Whether a swap is in progress. |
163
+ | `txStatus` | `TxConfirmationStatus` | Confirmation status of the last transaction. |
164
+ | `isLoading` | `boolean` | Whether initial data is loading. |
165
+
166
+ #### `SwapUI`
167
+
168
+ Pure presentational component for the swap form. Renders the From/To token inputs with token selector buttons, balance display, Half/Max shortcuts, and a preview button. Styled with `@liberfi.io/ui` components and Tailwind CSS.
169
+
170
+ **Props (`SwapUIProps`):**
171
+
172
+ | Name | Type | Description |
173
+ | ------------------- | ---------------------------------- | --------------------------------- |
174
+ | `fromToken` | `Token \| null` | From-token metadata. |
175
+ | `toToken` | `Token \| null` | To-token metadata. |
176
+ | `fromBalance` | `Portfolio \| null` | User's from-token balance. |
177
+ | `toBalance` | `Portfolio \| null` | User's to-token balance. |
178
+ | `amount` | `string \| undefined` | Human-readable input amount. |
179
+ | `amountInUsd` | `string \| undefined` | Input amount in USD. |
180
+ | `onAmountChange` | `(v: string \| undefined) => void` | Amount change handler. |
181
+ | `onHalfAmount?` | `() => void` | Half balance shortcut handler. |
182
+ | `onMaxAmount?` | `() => void` | Max balance shortcut handler. |
183
+ | `outputAmount` | `string \| undefined` | Formatted output amount. |
184
+ | `outputAmountInUsd` | `string \| undefined` | Output amount in USD. |
185
+ | `onFromTokenSelect` | `(addr: string) => void` | From-token selection handler. |
186
+ | `onToTokenSelect` | `(addr: string) => void` | To-token selection handler. |
187
+ | `route` | `API.SwapRoute \| undefined` | Current swap route quote. |
188
+ | `isRouting` | `boolean` | Whether a route is being fetched. |
189
+ | `routeError` | `Error \| null` | Route error. |
190
+ | `onPreview` | `() => void` | Open preview modal handler. |
191
+ | `isSwapping` | `boolean` | Whether a swap is in progress. |
192
+ | `className?` | `string` | External style customization. |
193
+
194
+ #### `SwapPreviewModal`
195
+
196
+ Modal component that previews swap details before confirmation. Shows from/to token cards with amounts, an expandable route plan details section, and a confirm button.
197
+
198
+ **Props (`SwapPreviewModalProps`):**
199
+
200
+ | Name | Type | Description |
201
+ | ------------------- | ---------------------------- | --------------------------------- |
202
+ | `isOpen` | `boolean` | Whether the modal is open. |
203
+ | `onOpenChange` | `(isOpen: boolean) => void` | Modal open/close handler. |
204
+ | `fromToken` | `Token \| null` | From-token metadata. |
205
+ | `toToken` | `Token \| null` | To-token metadata. |
206
+ | `fromBalance` | `Portfolio \| null` | User's from-token balance. |
207
+ | `inputAmount` | `string \| undefined` | Input amount (human-readable). |
208
+ | `inputAmountInUsd` | `string \| undefined` | Input amount in USD. |
209
+ | `outputAmount` | `string \| undefined` | Output amount (human-readable). |
210
+ | `outputAmountInUsd` | `string \| undefined` | Output amount in USD. |
211
+ | `route` | `API.SwapRoute \| undefined` | Current swap route. |
212
+ | `isRouting` | `boolean` | Whether a route is being fetched. |
213
+ | `routeError` | `Error \| null` | Route error. |
214
+ | `onConfirm` | `() => void` | Confirm swap handler. |
215
+ | `isSwapping` | `boolean` | Whether a swap is in progress. |
216
+
68
217
  ### Types
69
218
 
70
219
  #### `SwapInput`
@@ -128,6 +277,16 @@ interface UseTxConfirmationOptions {
128
277
  }
129
278
  ```
130
279
 
280
+ #### `UseSwapRoutePollingOptions`
281
+
282
+ ```typescript
283
+ interface UseSwapRoutePollingOptions {
284
+ interval?: number; // default: 12000
285
+ paused?: boolean;
286
+ onError?: (error: Error) => void;
287
+ }
288
+ ```
289
+
131
290
  ### Constants
132
291
 
133
292
  #### `version`
@@ -138,7 +297,91 @@ const version: string; // e.g. "0.1.0"
138
297
 
139
298
  ## Usage Examples
140
299
 
141
- ### Swap with Confirmation Tracking
300
+ ### SwapWidget (Plug-and-Play)
301
+
302
+ ```tsx
303
+ import { Chain } from "@liberfi.io/types";
304
+ import { SwapWidget } from "@liberfi.io/ui-trade";
305
+
306
+ function TradePage() {
307
+ return (
308
+ <SwapWidget
309
+ chain={Chain.SOLANA}
310
+ from="So11111111111111111111111111111111111111112"
311
+ to="EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v"
312
+ onComplete={(result) => {
313
+ if (result.success) {
314
+ toast.success(`Swap confirmed: ${result.txHash}`);
315
+ } else {
316
+ toast.error("Swap failed");
317
+ }
318
+ }}
319
+ />
320
+ );
321
+ }
322
+ ```
323
+
324
+ ### Custom UI with useSwapScript + SwapPreviewModal
325
+
326
+ ```tsx
327
+ import { Chain } from "@liberfi.io/types";
328
+ import { useDisclosure } from "@liberfi.io/ui";
329
+ import { useSwapScript, SwapUI, SwapPreviewModal } from "@liberfi.io/ui-trade";
330
+
331
+ function MyCustomSwapForm() {
332
+ const script = useSwapScript({
333
+ chain: Chain.SOLANA,
334
+ from: "So11111111111111111111111111111111111111112",
335
+ to: "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
336
+ onComplete: (result) => console.log("Done:", result),
337
+ });
338
+
339
+ const { isOpen, onOpen, onOpenChange } = useDisclosure();
340
+
341
+ return (
342
+ <>
343
+ <SwapUI
344
+ fromToken={script.fromToken}
345
+ toToken={script.toToken}
346
+ fromBalance={script.fromBalance}
347
+ toBalance={script.toBalance}
348
+ amount={script.amount}
349
+ amountInUsd={script.amountInUsd}
350
+ onAmountChange={script.setAmount}
351
+ onHalfAmount={script.setHalfAmount}
352
+ onMaxAmount={script.setMaxAmount}
353
+ outputAmount={script.outputAmount}
354
+ outputAmountInUsd={script.outputAmountInUsd}
355
+ onFromTokenSelect={script.setFromTokenAddress}
356
+ onToTokenSelect={script.setToTokenAddress}
357
+ route={script.route}
358
+ isRouting={script.isRouting}
359
+ routeError={script.routeError}
360
+ onPreview={onOpen}
361
+ isSwapping={script.isSwapping}
362
+ />
363
+ <SwapPreviewModal
364
+ isOpen={isOpen}
365
+ onOpenChange={onOpenChange}
366
+ fromToken={script.fromToken}
367
+ toToken={script.toToken}
368
+ fromBalance={script.fromBalance}
369
+ inputAmount={script.amount}
370
+ inputAmountInUsd={script.amountInUsd}
371
+ outputAmount={script.outputAmount}
372
+ outputAmountInUsd={script.outputAmountInUsd}
373
+ route={script.route}
374
+ isRouting={script.isRouting}
375
+ routeError={script.routeError}
376
+ onConfirm={script.swap}
377
+ isSwapping={script.isSwapping}
378
+ />
379
+ </>
380
+ );
381
+ }
382
+ ```
383
+
384
+ ### Swap with Confirmation Tracking (Low-Level Hooks)
142
385
 
143
386
  ```tsx
144
387
  import { Chain } from "@liberfi.io/types";
@@ -236,8 +479,201 @@ function EvmSwapButton() {
236
479
  }
237
480
  ```
238
481
 
482
+ ### Components (Instant Trade)
483
+
484
+ The instant trade module provides a configurable quick-trade form with preset management and customizable quick-amount buttons.
485
+
486
+ #### `InstantTradeWidget`
487
+
488
+ Full instant-trade form with buy/sell tabs, amount input, quick buttons, preset management, and trade execution.
489
+
490
+ ```tsx
491
+ <InstantTradeWidget
492
+ chain={Chain.SOLANA}
493
+ tokenAddress="EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v"
494
+ onSwapSubmitted={(result) => console.log(result)}
495
+ onSwapError={(error, phase) => console.error(error)}
496
+ />
497
+ ```
498
+
499
+ **Props (`InstantTradeWidgetProps`):**
500
+
501
+ | Name | Type | Description |
502
+ | ------------------- | ------------------------------------------ | ----------------------------------------------------- |
503
+ | `chain` | `Chain` | Target chain. |
504
+ | `tokenAddress` | `string` | Address of the token being traded. |
505
+ | `onSwapSubmitted?` | `(result: SwapResult) => void` | Called when a swap is submitted. |
506
+ | `onSwapError?` | `(error: Error, phase: SwapPhase) => void` | Called on swap error. |
507
+ | `settings?` | `InstantTradeSettings` | Controlled settings (omit for localStorage). |
508
+ | `onSettingsChange?` | `(settings: InstantTradeSettings) => void` | Called when settings change. |
509
+ | `headerExtra?` | `ReactNode` | Slot for extra header content (e.g. wallet switcher). |
510
+ | `className?` | `string` | External style customization. |
511
+
512
+ #### `InstantTradeProvider`
513
+
514
+ Context provider for instant trade state. Use with `InstantTradeAmountInput` and `InstantTradeButton` for compact inline trading.
515
+
516
+ ```tsx
517
+ <InstantTradeProvider chain={Chain.SOLANA} tokenAddress="...">
518
+ <InstantTradeAmountInput
519
+ amount={amount}
520
+ onAmountChange={setAmount}
521
+ preset={preset}
522
+ onPresetChange={setPreset}
523
+ />
524
+ <InstantTradeButton />
525
+ </InstantTradeProvider>
526
+ ```
527
+
528
+ #### `useInstantTradeScript(params)`
529
+
530
+ Script-layer hook for the instant trade form. Must be used inside an `InstantTradeProvider`. Encapsulates token queries, balance queries, form state, and swap execution.
531
+
532
+ #### `InstantTradeAmountInput`
533
+
534
+ Compact amount input with lightning icon, native token avatar, and 3 preset buttons (P1/P2/P3) with tooltips. Must be inside an `InstantTradeProvider`.
535
+
536
+ #### `InstantTradeButton`
537
+
538
+ Trade execution button that reads state from `InstantTradeProvider`. Handles wallet resolution, amount conversion, and swap execution.
539
+
540
+ #### `PresetFormWidget`
541
+
542
+ Standalone widget for editing trade preset values (slippage, priority fee, tip fee, auto fee, anti-MEV, custom RPC).
543
+
544
+ ```tsx
545
+ <PresetFormWidget
546
+ chain={Chain.SOLANA}
547
+ value={presetValues}
548
+ onChange={setPresetValues}
549
+ />
550
+ ```
551
+
552
+ #### `PresetFormUI`
553
+
554
+ Pure presentational preset form. Accepts value/onChange props directly, with optional `nativeSymbol` and `nativeDecimals`.
555
+
556
+ ### Instant Trade Types
557
+
558
+ #### `TradePresetValues`
559
+
560
+ ```typescript
561
+ interface TradePresetValues {
562
+ slippage: number | null;
563
+ priorityFee: number | null;
564
+ tipFee: number | null;
565
+ autoFee: boolean;
566
+ maxAutoFee: number | null;
567
+ antiMev: "off" | "reduced" | "secure";
568
+ customRPC: string | null;
569
+ }
570
+ ```
571
+
572
+ #### `InstantTradeSettings`
573
+
574
+ ```typescript
575
+ interface InstantTradeSettings {
576
+ buy: BuySettings;
577
+ sell: SellSettings;
578
+ }
579
+
580
+ interface BuySettings {
581
+ customAmounts: (number | null)[];
582
+ presets: TradePresetValues[];
583
+ }
584
+
585
+ interface SellSettings {
586
+ customPercentages: (number | null)[];
587
+ presets: TradePresetValues[];
588
+ }
589
+ ```
590
+
591
+ #### `DEFAULT_TRADE_PRESET`
592
+
593
+ Default preset values: slippage=20, priorityFee=0.001, tipFee=0.001, autoFee=false, maxAutoFee=0.1, antiMev="off".
594
+
595
+ #### `DEFAULT_INSTANT_TRADE_SETTINGS`
596
+
597
+ Default settings with 4 buy amounts (0.01, 0.1, 1, 10), 4 sell percentages (10, 25, 50, 100), and 3 default presets each.
598
+
599
+ ## Usage Examples
600
+
601
+ ### InstantTradeWidget (Full Trade Form)
602
+
603
+ ```tsx
604
+ import { Chain } from "@liberfi.io/types";
605
+ import { InstantTradeWidget } from "@liberfi.io/ui-trade";
606
+
607
+ function TokenTradePage({ tokenAddress }: { tokenAddress: string }) {
608
+ return (
609
+ <InstantTradeWidget
610
+ chain={Chain.SOLANA}
611
+ tokenAddress={tokenAddress}
612
+ onSwapSubmitted={(result) => {
613
+ toast.success(`Transaction submitted: ${result.txHash}`);
614
+ }}
615
+ onSwapError={(error, phase) => {
616
+ toast.error(`Swap failed at ${phase}: ${error.message}`);
617
+ }}
618
+ />
619
+ );
620
+ }
621
+ ```
622
+
623
+ ### Compact Inline Trading
624
+
625
+ ```tsx
626
+ import { Chain } from "@liberfi.io/types";
627
+ import {
628
+ InstantTradeProvider,
629
+ InstantTradeAmountInput,
630
+ InstantTradeButton,
631
+ } from "@liberfi.io/ui-trade";
632
+
633
+ function TokenHeader({ tokenAddress }: { tokenAddress: string }) {
634
+ return (
635
+ <InstantTradeProvider chain={Chain.SOLANA} tokenAddress={tokenAddress}>
636
+ <div className="flex items-center gap-2">
637
+ <InstantTradeAmountInput
638
+ amount={undefined}
639
+ onAmountChange={() => {}}
640
+ variant="bordered"
641
+ size="sm"
642
+ />
643
+ <InstantTradeButton />
644
+ </div>
645
+ </InstantTradeProvider>
646
+ );
647
+ }
648
+ ```
649
+
650
+ ### Preset Settings Editor
651
+
652
+ ```tsx
653
+ import { useState } from "react";
654
+ import { Chain } from "@liberfi.io/types";
655
+ import { PresetFormWidget, DEFAULT_TRADE_PRESET } from "@liberfi.io/ui-trade";
656
+
657
+ function TradeSettings() {
658
+ const [preset, setPreset] = useState(DEFAULT_TRADE_PRESET);
659
+
660
+ return (
661
+ <PresetFormWidget
662
+ chain={Chain.SOLANA}
663
+ value={preset}
664
+ onChange={setPreset}
665
+ />
666
+ );
667
+ }
668
+ ```
669
+
239
670
  ## Future Improvements
240
671
 
241
672
  - Add `useSwapQuote` hook for fetching quotes without executing (read-only route info).
242
673
  - Add ERC20 approval detection and `useTokenApproval` hook for EVM chains that don't support permit.
243
674
  - Support `ExactOut` swap mode with output amount estimation.
675
+ - Add a skeleton loading component (`swap-skeleton.ui.tsx`) for the swap form.
676
+ - Support token-select callback props on `SwapWidget` for integrating with external token picker UI.
677
+ - Add limit order and advanced trade modes to `InstantTradeWidget`.
678
+ - Add token price conversion display in the instant trade form.
679
+ - Support custom RPC endpoint validation in `PresetFormWidget`.