@orbs-network/spot-react 0.0.48 → 0.0.50

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 (30) hide show
  1. package/dist/{ccip-rjZm_oL7.js → ccip-FF_0yCZc.js} +1 -1
  2. package/dist/{index-Bn4A8QXC.js → index-CZy7C26X.js} +7198 -7202
  3. package/dist/spot-react/package.json.d.ts +1 -1
  4. package/dist/spot-react/src/hooks/use-dst-amount.d.ts +1 -0
  5. package/dist/spot-react/src/hooks/use-dst-amount.d.ts.map +1 -1
  6. package/dist/spot-react/src/hooks/use-dst-token-panel.d.ts +7 -0
  7. package/dist/spot-react/src/hooks/use-dst-token-panel.d.ts.map +1 -0
  8. package/dist/spot-react/src/hooks/use-duration.d.ts.map +1 -1
  9. package/dist/spot-react/src/hooks/use-fill-delay.d.ts.map +1 -1
  10. package/dist/spot-react/src/hooks/use-input-errors.d.ts.map +1 -1
  11. package/dist/spot-react/src/hooks/use-limit-price.d.ts.map +1 -1
  12. package/dist/spot-react/src/hooks/use-src-amount.d.ts +1 -5
  13. package/dist/spot-react/src/hooks/use-src-amount.d.ts.map +1 -1
  14. package/dist/spot-react/src/hooks/use-trades.d.ts.map +1 -1
  15. package/dist/spot-react/src/hooks/use-trigger-price.d.ts.map +1 -1
  16. package/dist/spot-react/src/index.d.ts +2 -2
  17. package/dist/spot-react/src/index.d.ts.map +1 -1
  18. package/dist/spot-react/src/spot-context.d.ts.map +1 -1
  19. package/dist/spot-react/src/types.d.ts +12 -2
  20. package/dist/spot-react/src/types.d.ts.map +1 -1
  21. package/dist/spot-react.js +29 -31
  22. package/dist/spot-react.umd.cjs +49 -49
  23. package/dist/spot-ui/src/lib/orders/index.d.ts +2 -2
  24. package/dist/spot-ui/src/lib/orders/index.d.ts.map +1 -1
  25. package/dist/spot-ui/src/lib/orders/v2-orders.d.ts +2 -1
  26. package/dist/spot-ui/src/lib/orders/v2-orders.d.ts.map +1 -1
  27. package/package.json +2 -2
  28. package/.cursor/rules.md +0 -1098
  29. package/dist/spot-react/src/hooks/use-token-panel.d.ts +0 -27
  30. package/dist/spot-react/src/hooks/use-token-panel.d.ts.map +0 -1
package/.cursor/rules.md DELETED
@@ -1,1098 +0,0 @@
1
- # @orbs-network/spot-react Integration Guide
2
-
3
- ## Overview
4
- `@orbs-network/spot-react` is a React component library for building Spot trading interfaces (TWAP, Limit, Stop-Loss, Take-Profit orders) on EVM chains. It provides hooks and components that manage order state, validation, and submission.
5
-
6
- ## Installation
7
-
8
- **Important:** If `@orbs-network/twap-ui` is installed, uninstall it first - it's deprecated and replaced by `spot-react`:
9
- ```bash
10
- npm uninstall @orbs-network/twap-ui
11
- # or
12
- pnpm remove @orbs-network/twap-ui
13
- ```
14
-
15
- Then install spot-react:
16
- ```bash
17
- npm install @orbs-network/spot-react
18
- # or
19
- pnpm add @orbs-network/spot-react
20
- ```
21
-
22
- ## Required Peer Dependencies
23
- - react ^18.0.0 || ^19.0.0
24
- - react-dom ^18.0.0 || ^19.0.0
25
-
26
- ---
27
-
28
- ## Core Setup
29
-
30
- ### SpotProvider Props
31
-
32
- ```tsx
33
- import { SpotProvider, Module, Partners } from "@orbs-network/spot-react";
34
-
35
- <SpotProvider
36
- // === REQUIRED PROPS ===
37
-
38
- partner={Partners.Quick} // Partner identifier (Quick, Thena, Spooky, etc.)
39
- module={Module.TWAP} // Order type: TWAP | LIMIT | STOP_LOSS | TAKE_PROFIT
40
- priceProtection={0.05} // Slippage tolerance (0.05 = 5%)
41
- minChunkSizeUsd={5} // Minimum individual trade size in USD
42
-
43
- // Market reference price - CRITICAL
44
- // This must be the expected OUTPUT amount (in wei) for the current INPUT amount
45
- marketReferencePrice={{
46
- value: "1850000000", // Output amount in wei (NOT a ratio)
47
- isLoading: false,
48
- noLiquidity: false,
49
- }}
50
-
51
- // Required UI components (you must implement these)
52
- components={{
53
- Button: YourButton, // ButtonProps
54
- Tooltip: YourTooltip, // TooltipProps
55
- TokenLogo: YourTokenLogo, // TokenLogoProps
56
- Spinner: <YourSpinner />, // ReactNode
57
- }}
58
-
59
- // === TOKEN DATA ===
60
-
61
- srcToken={{ // Source (sell) token
62
- address: "0x...",
63
- symbol: "ETH",
64
- decimals: 18,
65
- logoUrl: "https://..."
66
- }}
67
- dstToken={{ // Destination (buy) token
68
- address: "0x...",
69
- symbol: "USDC",
70
- decimals: 6,
71
- logoUrl: "https://..."
72
- }}
73
- srcBalance="1000000000000000000" // Source balance in wei (string)
74
- dstBalance="1000000000" // Destination balance in wei (string)
75
- srcUsd1Token="1850.50" // USD price for 1 source token
76
- dstUsd1Token="1.00" // USD price for 1 destination token
77
-
78
- // === WALLET ===
79
-
80
- chainId={1} // Current chain ID
81
- account="0x..." // Connected wallet address
82
- provider={walletClient?.transport} // EIP-1193 provider
83
-
84
- // === OPTIONAL ===
85
-
86
- callbacks={callbacks} // Order lifecycle callbacks
87
- refetchBalances={() => {}} // Called after order progress updates
88
- useToken={useTokenByAddress} // Hook: (address?: string) => Token | undefined
89
- fees={0.25} // Fee percentage to display
90
-
91
- // Override components for custom views
92
- // components.SubmitOrderSuccessView
93
- // components.SubmitOrderErrorView
94
- // components.SubmitOrderMainView
95
- // components.USD
96
- // components.Link
97
- // components.SuccessIcon
98
- // components.ErrorIcon
99
- >
100
- {children}
101
- </SpotProvider>
102
- ```
103
-
104
- ### Token Type
105
- ```tsx
106
- type Token = {
107
- address: string;
108
- symbol: string;
109
- decimals: number;
110
- logoUrl: string;
111
- };
112
- ```
113
-
114
- ### Module Types
115
- ```tsx
116
- enum Module {
117
- TWAP = "twap",
118
- LIMIT = "limit",
119
- STOP_LOSS = "stop_loss",
120
- TAKE_PROFIT = "take_profit"
121
- }
122
- ```
123
-
124
- ---
125
-
126
- ## Hooks Reference
127
-
128
- ### `useSrcTokenPanel()` / `useDstTokenPanel()`
129
-
130
- Token input panel state and handlers.
131
-
132
- ```tsx
133
- const {
134
- value, // string - Current amount value
135
- valueWei, // string - Amount in wei
136
- balance, // string - Token balance (UI format)
137
- usd, // string - USD value of amount
138
- token, // Token | undefined
139
- isLoading, // boolean - True when calculating output
140
- isInsufficientBalance,// InputError | undefined - Balance error
141
- onChange, // (value: string) => void - Update amount
142
- onMax, // () => void - Set to max balance
143
- } = useSrcTokenPanel();
144
-
145
- // Note: Only srcTokenPanel.onChange works - dstTokenPanel is read-only
146
- ```
147
-
148
- ### `useTypedSrcAmount()`
149
-
150
- Get/reset the typed source amount.
151
-
152
- ```tsx
153
- const {
154
- amount, // string - Current typed amount
155
- reset, // () => void - Clear the amount
156
- } = useTypedSrcAmount();
157
- ```
158
-
159
- ### `useTradesPanel()` (TWAP only)
160
-
161
- Configure number of trades.
162
-
163
- ```tsx
164
- const {
165
- totalTrades, // number - Current number of trades
166
- maxTrades, // number - Maximum allowed trades
167
- amountPerTrade, // string - Formatted amount per trade
168
- amountPerTradeWei, // string - Amount per trade in wei
169
- amountPerTradeUsd, // string - USD value per trade
170
- fromToken, // Token | undefined
171
- toToken, // Token | undefined
172
- label, // string - "Over"
173
- tooltip, // string - Tooltip text
174
- error, // InputError | undefined
175
- onChange, // (trades: number) => void
176
- } = useTradesPanel();
177
- ```
178
-
179
- ### `useDurationPanel()`
180
-
181
- Configure order expiration (for LIMIT, STOP_LOSS, TAKE_PROFIT).
182
-
183
- ```tsx
184
- import { DEFAULT_DURATION_OPTIONS, TimeUnit } from "@orbs-network/spot-react";
185
-
186
- const {
187
- duration, // { value: number, unit: TimeUnit }
188
- milliseconds, // number - Duration in milliseconds
189
- label, // string - "Expiry"
190
- tooltip, // string - Tooltip text
191
- error, // InputError | undefined
192
- onChange, // (duration: TimeDuration) => void
193
- onInputChange, // (value: string) => void - Change value only
194
- onUnitSelect, // (unit: TimeUnit) => void - Change unit only
195
- } = useDurationPanel();
196
-
197
- // TimeUnit values:
198
- // TimeUnit.Minutes = 60000 (60 * 1000)
199
- // TimeUnit.Hours = 3600000 (60 * 60 * 1000)
200
- // TimeUnit.Days = 86400000 (24 * 60 * 60 * 1000)
201
-
202
- // DEFAULT_DURATION_OPTIONS:
203
- // [{ text: "Minutes", value: TimeUnit.Minutes }, ...]
204
- ```
205
-
206
- ### `useFillDelayPanel()` (TWAP only)
207
-
208
- Configure delay between trades.
209
-
210
- ```tsx
211
- const {
212
- fillDelay, // { value: number, unit: TimeUnit }
213
- milliseconds, // number - Delay in milliseconds
214
- label, // string - "Every"
215
- tooltip, // string - Tooltip text
216
- error, // InputError | undefined
217
- onChange, // (fillDelay: TimeDuration) => void
218
- onInputChange, // (value: string) => void
219
- onUnitSelect, // (unit: TimeUnit) => void
220
- } = useFillDelayPanel();
221
- ```
222
-
223
- ### `useLimitPricePanel()`
224
-
225
- Configure limit price.
226
-
227
- ```tsx
228
- const {
229
- price, // string - Current price value
230
- usd, // string - USD value
231
- percentage, // string - Percentage from market price
232
- isLimitPrice, // boolean - Is limit price enabled
233
- isLoading, // boolean - Loading market price
234
- isInverted, // boolean - Price direction inverted
235
- fromToken, // Token | undefined
236
- toToken, // Token | undefined
237
- label, // string - "Limit Price"
238
- tooltip, // string
239
- error, // InputError | undefined
240
- warning, // { text: string, url: string } | undefined
241
- onChange, // (price: string) => void
242
- onPercentageChange, // (percentage: string) => void
243
- onReset, // () => void - Reset to default
244
- toggleLimitPrice, // () => void - Toggle on/off
245
- onInvert, // () => void - Invert price direction
246
- } = useLimitPricePanel();
247
-
248
- // Note: For Module.LIMIT, isLimitPrice is always true
249
- // For TWAP, STOP_LOSS, TAKE_PROFIT it's optional (toggle)
250
- ```
251
-
252
- ### `useTriggerPricePanel()` (STOP_LOSS / TAKE_PROFIT only)
253
-
254
- Configure trigger price for conditional orders.
255
-
256
- ```tsx
257
- const {
258
- price, // string - Current trigger price
259
- usd, // string - USD value
260
- percentage, // string - Percentage from market price
261
- isLoading, // boolean
262
- isInverted, // boolean
263
- isActive, // boolean - Is trigger active
264
- hide, // boolean - True if not STOP_LOSS/TAKE_PROFIT
265
- fromToken, // Token | undefined
266
- toToken, // Token | undefined
267
- label, // string - "Trigger Price"
268
- tooltip, // string
269
- error, // InputError | undefined
270
- onChange, // (price: string) => void
271
- onPercentageChange, // (percentage: string) => void
272
- onReset, // () => void
273
- onInvert, // () => void
274
- } = useTriggerPricePanel();
275
- ```
276
-
277
- ### `useInvertTradePanel()`
278
-
279
- Invert price display direction.
280
-
281
- ```tsx
282
- const {
283
- isInverted, // boolean - Is price inverted
284
- isMarketPrice, // boolean - Is using market price (no limit)
285
- fromToken, // Token | undefined - Display "from" token
286
- toToken, // Token | undefined - Display "to" token
287
- onInvert, // () => void - Toggle inversion
288
- } = useInvertTradePanel();
289
- ```
290
-
291
- ### `useInputErrors()`
292
-
293
- Get current validation error (returns first error).
294
-
295
- ```tsx
296
- const error = useInputErrors();
297
- // Returns: InputError | undefined
298
-
299
- type InputError = {
300
- type: InputErrors; // Error type enum
301
- value: string | number;
302
- message: string; // Translated error message
303
- };
304
-
305
- enum InputErrors {
306
- EMPTY_LIMIT_PRICE,
307
- MAX_CHUNKS,
308
- MIN_CHUNKS,
309
- MIN_TRADE_SIZE,
310
- MAX_FILL_DELAY,
311
- MIN_FILL_DELAY,
312
- MAX_ORDER_DURATION,
313
- MIN_ORDER_DURATION,
314
- MISSING_LIMIT_PRICE,
315
- STOP_LOSS_TRIGGER_PRICE_GREATER_THAN_MARKET_PRICE,
316
- TRIGGER_LIMIT_PRICE_GREATER_THAN_TRIGGER_PRICE,
317
- TAKE_PROFIT_TRIGGER_PRICE_LESS_THAN_MARKET_PRICE,
318
- EMPTY_TRIGGER_PRICE,
319
- INSUFFICIENT_BALANCE,
320
- MAX_ORDER_SIZE,
321
- }
322
- ```
323
-
324
- ### `useSubmitOrderPanel()`
325
-
326
- Full order submission flow control.
327
-
328
- ```tsx
329
- const {
330
- // Actions
331
- onSubmit, // () => Promise<void> - Submit order
332
- onOpenModal, // () => void - Call when opening modal
333
- onCloseModal, // () => void - Call when closing modal
334
- reset, // () => void - Reset all state
335
-
336
- // Status
337
- status, // SwapStatus - Current status
338
- isLoading, // boolean - Submission in progress
339
- isSuccess, // boolean - Order created
340
- isFailed, // boolean - Order failed
341
-
342
- // Data
343
- parsedError, // { message: string, code: number } | undefined
344
- orderId, // string | undefined - Created order ID
345
- step, // Steps - Current step (WRAP | APPROVE | CREATE)
346
- stepIndex, // number | undefined
347
- totalSteps, // number | undefined
348
- srcToken, // Token | undefined
349
- dstToken, // Token | undefined
350
- approveTxHash, // string | undefined
351
- wrapTxHash, // string | undefined
352
- } = useSubmitOrderPanel();
353
-
354
- // SwapStatus enum (from @orbs-network/swap-ui):
355
- // LOADING, SUCCESS, FAILED
356
- ```
357
-
358
- ### `useSubmitOrderButton()`
359
-
360
- Just the submit button state.
361
-
362
- ```tsx
363
- const {
364
- disabled, // boolean - Button disabled
365
- text, // string - "Place Order" | "Enter Amount" | "Insufficient Balance" | "No Liquidity"
366
- loading, // boolean - Loading state
367
- } = useSubmitOrderButton();
368
- ```
369
-
370
- ### `useOrderHistoryPanel()`
371
-
372
- Order history and management.
373
-
374
- ```tsx
375
- const {
376
- // Orders data
377
- orders, // { all, open, completed, expired, canceled } - Order arrays
378
- ordersToDisplay, // Order[] - Filtered orders
379
- openOrdersCount, // number
380
- isLoading, // boolean
381
- isRefetching, // boolean
382
-
383
- // Selected order
384
- selectedOrder, // HistoryOrder | undefined - Selected order details
385
- onHideSelectedOrder, // () => void - Deselect order
386
-
387
- // Filtering
388
- statuses, // SelectMenuItem[] - Available status filters
389
- selectedStatus, // string - Current filter
390
- onSelectStatus, // (status?: OrderStatus) => void
391
-
392
- // Cancel
393
- cancelOrdersMode, // boolean
394
- ordersToCancel, // Order[]
395
- isCancelOrdersLoading, // boolean
396
- onCancelOrder, // (order: Order) => Promise<string>
397
- onCancelOrders, // (orders: Order[]) => void
398
- onToggleCancelOrdersMode, // (enabled: boolean) => void
399
- onSelectOrder, // (id: string) => void - Toggle order selection
400
- onSelectAllOrdersToCancel, // () => void
401
-
402
- refetch, // () => Promise<Order[]>
403
- } = useOrderHistoryPanel();
404
- ```
405
-
406
- ### `useDisclaimerPanel()`
407
-
408
- Get module-specific disclaimer message.
409
-
410
- ```tsx
411
- const disclaimer = useDisclaimerPanel();
412
- // Returns: { text: string, url: string } | undefined
413
-
414
- // Shows different messages based on module and isMarketOrder state
415
- ```
416
-
417
- ### `useTranslations()`
418
-
419
- Get translation function.
420
-
421
- ```tsx
422
- const t = useTranslations();
423
-
424
- // Available keys:
425
- t("placeOrder") // "Place order"
426
- t("enterAmount") // "Enter an amount"
427
- t("insufficientFunds") // "Insufficient Balance"
428
- t("noLiquidity") // "No liquidity for this pair"
429
- t("limitPrice") // "Limit Price"
430
- t("expiry") // "Expiry"
431
- t("tradesAmountTitle") // "Over"
432
- t("tradeIntervalTitle") // "Every"
433
- t("stopLossLabel") // "Trigger Price"
434
- // ... and more (see translations for full list)
435
- ```
436
-
437
- ### `useFormatNumber()`
438
-
439
- Format numbers with locale.
440
-
441
- ```tsx
442
- const formatted = useFormatNumber({
443
- value: "1234.5678",
444
- decimalScale: 2
445
- });
446
- // Returns: { value: "1,234.57" }
447
- ```
448
-
449
- ### `useOrderInfo()`
450
-
451
- Get complete order details for display.
452
-
453
- ```tsx
454
- const orderInfo = useOrderInfo();
455
- // Returns formatted order details including:
456
- // - srcUsd, dstUsd
457
- // - deadline { label, tooltip, value }
458
- // - limitPrice { label, value, usd }
459
- // - triggerPricePerTrade { label, tooltip, value, usd }
460
- // - minDestAmountPerTrade { label, tooltip, value, usd }
461
- // - sizePerTrade { label, tooltip, value }
462
- // - totalTrades { label, tooltip, value }
463
- // - tradeInterval { label, tooltip, value }
464
- // - fees { label, amount, usd, percentage }
465
- ```
466
-
467
- ### `usePartnerChains()`
468
-
469
- Get supported chains for current partner.
470
-
471
- ```tsx
472
- const chains = usePartnerChains();
473
- // Returns: number[] - Array of supported chain IDs
474
- ```
475
-
476
- ### `useAddresses()`
477
-
478
- Get contract addresses for current config.
479
-
480
- ```tsx
481
- const addresses = useAddresses();
482
- // Returns contract addresses for the current chain/partner
483
- ```
484
-
485
- ---
486
-
487
- ## Pre-built Components
488
-
489
- ### `Components.SubmitOrderPanel`
490
-
491
- Displays the order submission flow with steps (Wrap → Approve → Sign).
492
-
493
- ```tsx
494
- import { Components } from "@orbs-network/spot-react";
495
-
496
- <Components.SubmitOrderPanel
497
- reviewDetails={
498
- // Your custom review UI - shown before submission starts
499
- <div>
500
- <YourOrderSummary />
501
- <Button onClick={onSubmit}>Create Order</Button>
502
- </div>
503
- }
504
- />
505
- ```
506
-
507
- The panel automatically shows:
508
- - Token logos and amounts (from/to)
509
- - Order details (deadline, limit price, trades, etc.)
510
- - Step progress during submission
511
- - Success/Error states
512
-
513
- ### `Components.Orders`
514
-
515
- Pre-built orders list with detail view.
516
-
517
- ```tsx
518
- <Components.Orders />
519
- ```
520
-
521
- ---
522
-
523
- ## Callbacks
524
-
525
- ```tsx
526
- const callbacks = {
527
- // === WRAP (ETH → WETH) ===
528
- onWrapRequest: () => void,
529
- onWrapSuccess: ({ txHash, explorerUrl, amount }) => void,
530
-
531
- // === APPROVAL ===
532
- onApproveRequest: () => void,
533
- onApproveSuccess: ({ txHash, explorerUrl, token, amount }) => void,
534
-
535
- // === ORDER SIGNING ===
536
- onSignOrderRequest: () => void,
537
- onSignOrderSuccess: (signature: string) => void,
538
- onSignOrderError: (error: Error) => void,
539
-
540
- // === ORDER CREATED ===
541
- onOrderCreated: (order: Order) => void,
542
-
543
- // === ORDER FILLED ===
544
- onOrderFilled: (order: Order) => void,
545
- onOrdersProgressUpdate: (orders: Order[]) => void,
546
-
547
- // === ERRORS ===
548
- onSubmitOrderFailed: ({ message, code }) => void,
549
- onSubmitOrderRejected: () => void,
550
-
551
- // === CANCEL ===
552
- onCancelOrderRequest: (orders: Order[]) => void,
553
- onCancelOrderSuccess: ({ orders, txHash, explorerUrl }) => void,
554
- onCancelOrderFailed: (error: Error) => void,
555
-
556
- // === UI ===
557
- onCopy: () => void,
558
- };
559
- ```
560
-
561
- ---
562
-
563
- ## Required UI Components Implementation
564
-
565
- ### ButtonProps
566
- ```tsx
567
- interface ButtonProps {
568
- children: ReactNode;
569
- onClick: () => void;
570
- disabled?: boolean;
571
- loading?: boolean;
572
- style?: CSSProperties;
573
- allowClickWhileLoading?: boolean;
574
- }
575
- ```
576
-
577
- ### TooltipProps
578
- ```tsx
579
- interface TooltipProps {
580
- children?: ReactNode;
581
- tooltipText?: string;
582
- }
583
- ```
584
-
585
- ### TokenLogoProps
586
- ```tsx
587
- interface TokenLogoProps {
588
- token?: Token;
589
- size?: number;
590
- className?: string;
591
- }
592
- ```
593
-
594
- ---
595
-
596
- ## Module-Specific UI Logic
597
-
598
- ```tsx
599
- // Different modules show different inputs:
600
-
601
- if (module === Module.TWAP) {
602
- // Show: TradesPanel, FillDelayPanel
603
- // Optional: LimitPricePanel (with toggle)
604
- }
605
-
606
- if (module === Module.LIMIT) {
607
- // Show: DurationPanel, LimitPricePanel (always on, no toggle)
608
- }
609
-
610
- if (module === Module.STOP_LOSS || module === Module.TAKE_PROFIT) {
611
- // Show: DurationPanel, TriggerPricePanel, LimitPricePanel (with toggle)
612
- }
613
- ```
614
-
615
- ---
616
-
617
- ## Market Reference Price
618
-
619
- **CRITICAL**: The `marketReferencePrice.value` must be the expected OUTPUT amount in wei for the current input amount.
620
-
621
- ```tsx
622
- // Get the output amount from your DEX's quote/trade function
623
- const marketReferencePrice = useMemo(() => {
624
- return {
625
- value: dexQuote?.outAmount, // Wei string - e.g., "1850000000"
626
- isLoading: isQuoteLoading,
627
- noLiquidity: !dexQuote && !isQuoteLoading,
628
- };
629
- }, [dexQuote, isQuoteLoading]);
630
- ```
631
-
632
- ---
633
-
634
- ## Complete Integration Example
635
-
636
- ```tsx
637
- import {
638
- SpotProvider,
639
- Module,
640
- Partners,
641
- Components,
642
- useSrcTokenPanel,
643
- useDstTokenPanel,
644
- useTradesPanel,
645
- useDurationPanel,
646
- useFillDelayPanel,
647
- useLimitPricePanel,
648
- useTriggerPricePanel,
649
- useSubmitOrderPanel,
650
- useSubmitOrderButton,
651
- useInputErrors,
652
- useDisclaimerPanel,
653
- useOrderHistoryPanel,
654
- useInvertTradePanel,
655
- DEFAULT_DURATION_OPTIONS,
656
- } from "@orbs-network/spot-react";
657
- import "@orbs-network/spot-react/styles.css";
658
-
659
- // === MAIN WIDGET ===
660
-
661
- function SpotTradingWidget({ module }: { module: Module }) {
662
- const { address, chainId } = useWallet();
663
- const srcToken = useSelectedToken("src");
664
- const dstToken = useSelectedToken("dst");
665
- const srcBalance = useBalance(srcToken?.address);
666
- const dstBalance = useBalance(dstToken?.address);
667
- const srcUsd = useUSDPrice(srcToken?.address);
668
- const dstUsd = useUSDPrice(dstToken?.address);
669
- const marketPrice = useDexQuote(srcToken, dstToken);
670
-
671
- return (
672
- <SpotProvider
673
- partner={Partners.Quick}
674
- module={module}
675
- chainId={chainId}
676
- account={address}
677
- provider={walletProvider}
678
- srcToken={srcToken}
679
- dstToken={dstToken}
680
- srcBalance={srcBalance}
681
- dstBalance={dstBalance}
682
- srcUsd1Token={srcUsd}
683
- dstUsd1Token={dstUsd}
684
- marketReferencePrice={marketPrice}
685
- priceProtection={0.05}
686
- minChunkSizeUsd={5}
687
- components={{
688
- Button: MyButton,
689
- Tooltip: MyTooltip,
690
- TokenLogo: MyTokenLogo,
691
- Spinner: <MySpinner />,
692
- }}
693
- callbacks={{
694
- onOrderCreated: (order) => toast.success("Order created!"),
695
- onApproveSuccess: () => toast.success("Approved!"),
696
- }}
697
- >
698
- <TradingUI module={module} />
699
- </SpotProvider>
700
- );
701
- }
702
-
703
- // === MAIN UI LAYOUT ===
704
-
705
- function TradingUI({ module }: { module: Module }) {
706
- return (
707
- <div className="flex flex-col gap-4">
708
- <TokenInputs />
709
- <PriceConfig module={module} />
710
- <OrderConfig module={module} />
711
- <ValidationErrors />
712
- <SubmitOrderFlow />
713
- <Disclaimer />
714
- </div>
715
- );
716
- }
717
-
718
- // === TOKEN INPUTS ===
719
-
720
- function TokenInputs() {
721
- return (
722
- <div className="flex flex-col gap-2">
723
- <SrcTokenInput />
724
- <ToggleTokensButton />
725
- <DstTokenInput />
726
- </div>
727
- );
728
- }
729
-
730
- function SrcTokenInput() {
731
- const { value, balance, usd, token, onChange, onMax } = useSrcTokenPanel();
732
-
733
- return (
734
- <div className="bg-card p-4 rounded-lg">
735
- <div className="flex justify-between">
736
- <span>From</span>
737
- <span>Balance: {balance} <button onClick={onMax}>MAX</button></span>
738
- </div>
739
- <div className="flex items-center gap-2">
740
- <input
741
- type="text"
742
- value={value}
743
- onChange={(e) => onChange(e.target.value)}
744
- placeholder="0.0"
745
- />
746
- <TokenSelector token={token} />
747
- </div>
748
- <span className="text-sm text-muted">${usd}</span>
749
- </div>
750
- );
751
- }
752
-
753
- function DstTokenInput() {
754
- const { value, balance, usd, token, isLoading } = useDstTokenPanel();
755
-
756
- return (
757
- <div className="bg-card p-4 rounded-lg">
758
- <div className="flex justify-between">
759
- <span>To (estimated)</span>
760
- <span>Balance: {balance}</span>
761
- </div>
762
- <div className="flex items-center gap-2">
763
- {isLoading ? <Spinner /> : <span>{value || "0.0"}</span>}
764
- <TokenSelector token={token} />
765
- </div>
766
- <span className="text-sm text-muted">${usd}</span>
767
- </div>
768
- );
769
- }
770
-
771
- // === PRICE CONFIGURATION ===
772
-
773
- function PriceConfig({ module }: { module: Module }) {
774
- return (
775
- <div className="bg-card p-4 rounded-lg flex flex-col gap-4">
776
- <PriceHeader />
777
- {(module === Module.STOP_LOSS || module === Module.TAKE_PROFIT) && (
778
- <TriggerPriceInput />
779
- )}
780
- <LimitPriceInput module={module} />
781
- </div>
782
- );
783
- }
784
-
785
- function PriceHeader() {
786
- const { isInverted, isMarketPrice, fromToken, onInvert } = useInvertTradePanel();
787
-
788
- return (
789
- <div className="flex justify-between items-center">
790
- <span>
791
- {isInverted ? "Buy" : "Sell"} {fromToken?.symbol}{" "}
792
- {isMarketPrice ? "at best rate" : "at rate"}
793
- </span>
794
- {!isMarketPrice && (
795
- <button onClick={onInvert}>⇄</button>
796
- )}
797
- </div>
798
- );
799
- }
800
-
801
- function TriggerPriceInput() {
802
- const {
803
- price,
804
- usd,
805
- percentage,
806
- toToken,
807
- label,
808
- tooltip,
809
- hide,
810
- onChange,
811
- onPercentageChange,
812
- onReset,
813
- } = useTriggerPricePanel();
814
-
815
- if (hide) return null;
816
-
817
- return (
818
- <div className="flex flex-col gap-2">
819
- <div className="flex justify-between">
820
- <Label text={label} tooltip={tooltip} />
821
- <button onClick={onReset}>Reset</button>
822
- </div>
823
- <div className="flex gap-2">
824
- <div className="flex-1">
825
- <span>{toToken?.symbol}</span>
826
- <input value={price} onChange={(e) => onChange(e.target.value)} />
827
- <span>${usd}</span>
828
- </div>
829
- <input
830
- className="w-24"
831
- value={percentage}
832
- onChange={(e) => onPercentageChange(e.target.value)}
833
- placeholder="0%"
834
- />
835
- </div>
836
- </div>
837
- );
838
- }
839
-
840
- function LimitPriceInput({ module }: { module: Module }) {
841
- const {
842
- price,
843
- usd,
844
- percentage,
845
- toToken,
846
- label,
847
- tooltip,
848
- isLimitPrice,
849
- isLoading,
850
- onChange,
851
- onPercentageChange,
852
- onReset,
853
- toggleLimitPrice,
854
- } = useLimitPricePanel();
855
-
856
- return (
857
- <div className="flex flex-col gap-2">
858
- <div className="flex items-center gap-2">
859
- {/* Show toggle only for non-LIMIT modules */}
860
- {module !== Module.LIMIT && (
861
- <Switch checked={isLimitPrice} onCheckedChange={toggleLimitPrice} />
862
- )}
863
- <Label text={label} tooltip={tooltip} />
864
- {isLimitPrice && <button onClick={onReset}>Reset</button>}
865
- </div>
866
-
867
- {isLimitPrice && (
868
- <div className="flex gap-2">
869
- <div className="flex-1">
870
- <span>{toToken?.symbol}</span>
871
- {isLoading ? (
872
- <Spinner />
873
- ) : (
874
- <input value={price} onChange={(e) => onChange(e.target.value)} />
875
- )}
876
- <span>${usd}</span>
877
- </div>
878
- <input
879
- className="w-24"
880
- value={percentage}
881
- onChange={(e) => onPercentageChange(e.target.value)}
882
- placeholder="0%"
883
- />
884
- </div>
885
- )}
886
- </div>
887
- );
888
- }
889
-
890
- // === ORDER CONFIGURATION ===
891
-
892
- function OrderConfig({ module }: { module: Module }) {
893
- if (module === Module.TWAP) {
894
- return (
895
- <div className="flex gap-4">
896
- <TradesInput />
897
- <FillDelayInput />
898
- </div>
899
- );
900
- }
901
-
902
- // LIMIT, STOP_LOSS, TAKE_PROFIT
903
- return <DurationInput />;
904
- }
905
-
906
- function TradesInput() {
907
- const { totalTrades, label, tooltip, error, onChange } = useTradesPanel();
908
-
909
- return (
910
- <div className={`bg-card p-4 rounded-lg ${error ? "border-red-500" : ""}`}>
911
- <Label text={label} tooltip={tooltip} />
912
- <div className="flex items-center gap-2">
913
- <input
914
- type="number"
915
- value={totalTrades || ""}
916
- onChange={(e) => onChange(Number(e.target.value))}
917
- />
918
- <span>Trades</span>
919
- </div>
920
- </div>
921
- );
922
- }
923
-
924
- function FillDelayInput() {
925
- const { fillDelay, label, tooltip, onInputChange, onUnitSelect } = useFillDelayPanel();
926
-
927
- return (
928
- <div className="bg-card p-4 rounded-lg">
929
- <Label text={label} tooltip={tooltip} />
930
- <div className="flex items-center gap-2">
931
- <input
932
- type="number"
933
- value={fillDelay.value || ""}
934
- onChange={(e) => onInputChange(e.target.value)}
935
- />
936
- <select
937
- value={fillDelay.unit}
938
- onChange={(e) => onUnitSelect(Number(e.target.value))}
939
- >
940
- {DEFAULT_DURATION_OPTIONS.map((opt) => (
941
- <option key={opt.value} value={opt.value}>{opt.text}</option>
942
- ))}
943
- </select>
944
- </div>
945
- </div>
946
- );
947
- }
948
-
949
- function DurationInput() {
950
- const { duration, label, tooltip, onInputChange, onUnitSelect } = useDurationPanel();
951
-
952
- return (
953
- <div className="bg-card p-4 rounded-lg">
954
- <Label text={label} tooltip={tooltip} />
955
- <div className="flex items-center gap-2">
956
- <input
957
- type="number"
958
- value={duration.value || ""}
959
- onChange={(e) => onInputChange(e.target.value)}
960
- />
961
- <select
962
- value={duration.unit}
963
- onChange={(e) => onUnitSelect(Number(e.target.value))}
964
- >
965
- {DEFAULT_DURATION_OPTIONS.map((opt) => (
966
- <option key={opt.value} value={opt.value}>{opt.text}</option>
967
- ))}
968
- </select>
969
- </div>
970
- </div>
971
- );
972
- }
973
-
974
- // === VALIDATION ===
975
-
976
- function ValidationErrors() {
977
- const error = useInputErrors();
978
-
979
- if (!error) return null;
980
-
981
- return (
982
- <div className="bg-red-500/20 p-3 rounded-lg flex items-center gap-2">
983
- <AlertIcon />
984
- <span>{error.message}</span>
985
- </div>
986
- );
987
- }
988
-
989
- // === SUBMIT ORDER ===
990
-
991
- function SubmitOrderFlow() {
992
- const [isOpen, setIsOpen] = useState(false);
993
- const {
994
- onSubmit,
995
- onOpenModal,
996
- onCloseModal,
997
- isLoading,
998
- parsedError,
999
- } = useSubmitOrderPanel();
1000
-
1001
- const handleOpen = () => {
1002
- setIsOpen(true);
1003
- onOpenModal();
1004
- };
1005
-
1006
- const handleClose = () => {
1007
- setIsOpen(false);
1008
- onCloseModal();
1009
- };
1010
-
1011
- return (
1012
- <>
1013
- <SubmitButton onClick={handleOpen} />
1014
-
1015
- <Dialog open={isOpen} onOpenChange={handleClose}>
1016
- <DialogContent>
1017
- {parsedError ? (
1018
- <ErrorView error={parsedError} onClose={handleClose} />
1019
- ) : (
1020
- <Components.SubmitOrderPanel
1021
- reviewDetails={
1022
- <div className="flex flex-col gap-4">
1023
- <DisclaimerCheckbox />
1024
- <Button onClick={onSubmit} isLoading={isLoading}>
1025
- Create Order
1026
- </Button>
1027
- </div>
1028
- }
1029
- />
1030
- )}
1031
- </DialogContent>
1032
- </Dialog>
1033
- </>
1034
- );
1035
- }
1036
-
1037
- function SubmitButton({ onClick }: { onClick: () => void }) {
1038
- const { disabled, text, loading } = useSubmitOrderButton();
1039
-
1040
- return (
1041
- <Button onClick={onClick} disabled={disabled} isLoading={loading}>
1042
- {text}
1043
- </Button>
1044
- );
1045
- }
1046
-
1047
- // === DISCLAIMER ===
1048
-
1049
- function Disclaimer() {
1050
- const disclaimer = useDisclaimerPanel();
1051
-
1052
- if (!disclaimer) return null;
1053
-
1054
- return (
1055
- <div className="bg-card p-3 rounded-lg flex items-start gap-2">
1056
- <InfoIcon />
1057
- <p>
1058
- {disclaimer.text}{" "}
1059
- <a href={disclaimer.url} target="_blank" rel="noopener">
1060
- Learn more
1061
- </a>
1062
- </p>
1063
- </div>
1064
- );
1065
- }
1066
-
1067
- // === HELPER COMPONENTS ===
1068
-
1069
- function Label({ text, tooltip }: { text: string; tooltip?: string }) {
1070
- return (
1071
- <div className="flex items-center gap-1">
1072
- <span>{text}</span>
1073
- {tooltip && <Tooltip text={tooltip}><InfoIcon /></Tooltip>}
1074
- </div>
1075
- );
1076
- }
1077
- ```
1078
-
1079
- ---
1080
-
1081
- ## Integration Checklist
1082
-
1083
- 1. [ ] Install `@orbs-network/spot-react`
1084
- 2. [ ] Import styles: `import "@orbs-network/spot-react/styles.css"`
1085
- 3. [ ] Implement required components: Button, Tooltip, TokenLogo, Spinner
1086
- 4. [ ] Set up wallet connection
1087
- 5. [ ] Implement token data fetching (address, symbol, decimals, logoUrl)
1088
- 6. [ ] Implement balance fetching (wei strings)
1089
- 7. [ ] Implement USD price fetching (price for 1 token)
1090
- 8. [ ] Implement market reference price (output amount for input)
1091
- 9. [ ] Wrap UI with SpotProvider
1092
- 10. [ ] Build token panels with `useSrcTokenPanel`/`useDstTokenPanel`
1093
- 11. [ ] Build order config based on module type
1094
- 12. [ ] Build price panels with `useLimitPricePanel`/`useTriggerPricePanel`
1095
- 13. [ ] Add validation with `useInputErrors`
1096
- 14. [ ] Build submission with `useSubmitOrderPanel` + `Components.SubmitOrderPanel`
1097
- 15. [ ] Add order history with `useOrderHistoryPanel` + `Components.Orders`
1098
- 16. [ ] Implement callbacks for toast notifications