@shapeshiftoss/swap-widget 0.0.2 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (159) hide show
  1. package/dist/index.d.ts +233 -0
  2. package/dist/index.js +63612 -0
  3. package/dist/style.css +1 -0
  4. package/package.json +69 -18
  5. package/Dockerfile +0 -87
  6. package/dist/assets/PhArrowCircleDown-B305t-Sh.js +0 -14
  7. package/dist/assets/PhArrowClockwise-CJPxQhr_.js +0 -14
  8. package/dist/assets/PhArrowDown-C4hT9BVu.js +0 -14
  9. package/dist/assets/PhArrowLeft-Way4Cu-r.js +0 -14
  10. package/dist/assets/PhArrowRight-Cp5ZME0F.js +0 -14
  11. package/dist/assets/PhArrowSquareOut-NnrK69c5.js +0 -14
  12. package/dist/assets/PhArrowUp-Dph6j8rm.js +0 -14
  13. package/dist/assets/PhArrowUpRight-BpJjRRRh.js +0 -14
  14. package/dist/assets/PhArrowsClockwise-CFUT8X7R.js +0 -14
  15. package/dist/assets/PhArrowsDownUp-BjBI4ckC.js +0 -14
  16. package/dist/assets/PhArrowsLeftRight-CuLpbKRk.js +0 -14
  17. package/dist/assets/PhBank-Dchr4XQJ.js +0 -14
  18. package/dist/assets/PhBrowser-dUu2TrC1.js +0 -14
  19. package/dist/assets/PhCaretDown-78IuKU3V.js +0 -14
  20. package/dist/assets/PhCaretLeft-_aiyH7n1.js +0 -14
  21. package/dist/assets/PhCaretRight-8zJQEuDX.js +0 -14
  22. package/dist/assets/PhCaretUp-CiCxKWwN.js +0 -14
  23. package/dist/assets/PhCheck-CcADx8br.js +0 -14
  24. package/dist/assets/PhCircleHalf-BYzSzd2h.js +0 -14
  25. package/dist/assets/PhClock-UItiXeJq.js +0 -14
  26. package/dist/assets/PhCompass-CUy3uA7t.js +0 -14
  27. package/dist/assets/PhCopy-C-PDgSlZ.js +0 -14
  28. package/dist/assets/PhCreditCard-CWRaUT6F.js +0 -14
  29. package/dist/assets/PhCurrencyDollar-Cqla3AXT.js +0 -14
  30. package/dist/assets/PhDesktop-BBIPrYLl.js +0 -14
  31. package/dist/assets/PhDeviceMobile-BdsPaATg.js +0 -14
  32. package/dist/assets/PhDotsThree-BMXCQl-0.js +0 -14
  33. package/dist/assets/PhEnvelope-yUsis6yU.js +0 -14
  34. package/dist/assets/PhFunnelSimple-Ba7BbWTp.js +0 -14
  35. package/dist/assets/PhGlobe-DgLxBk5U.js +0 -14
  36. package/dist/assets/PhIdentificationCard-Czjmq9Z3.js +0 -14
  37. package/dist/assets/PhImage-BAJ_erz6.js +0 -14
  38. package/dist/assets/PhInfo-Cr4DhVUE.js +0 -14
  39. package/dist/assets/PhLightbulb-BX_7duTr.js +0 -14
  40. package/dist/assets/PhMagnifyingGlass-Mygfryvz.js +0 -14
  41. package/dist/assets/PhPaperPlaneRight-BeuCA660.js +0 -14
  42. package/dist/assets/PhPlus-BMjcHjsT.js +0 -14
  43. package/dist/assets/PhPower-CGffGmdb.js +0 -14
  44. package/dist/assets/PhPuzzlePiece-wm_GpVSi.js +0 -14
  45. package/dist/assets/PhQrCode-DRwRzXCH.js +0 -14
  46. package/dist/assets/PhQuestion-M09j2U0T.js +0 -14
  47. package/dist/assets/PhQuestionMark-BP9EMB6h.js +0 -14
  48. package/dist/assets/PhSealCheck-BfG80yKn.js +0 -14
  49. package/dist/assets/PhSignOut-C5YOWlPT.js +0 -14
  50. package/dist/assets/PhSpinner-BXooDYzU.js +0 -14
  51. package/dist/assets/PhTrash-Biq6rmmL.js +0 -14
  52. package/dist/assets/PhUser-CAgZSpGe.js +0 -14
  53. package/dist/assets/PhVault-l2PHF38a.js +0 -14
  54. package/dist/assets/PhWallet-w25D16It.js +0 -14
  55. package/dist/assets/PhWarning-DPEydIPw.js +0 -14
  56. package/dist/assets/PhWarningCircle-BqjT745H.js +0 -14
  57. package/dist/assets/PhX-s-SmVExa.js +0 -14
  58. package/dist/assets/SwapController-If0bFrNc.js +0 -1
  59. package/dist/assets/ccip-BBB7uCdL.js +0 -1
  60. package/dist/assets/ccip-CkkdfKKI.js +0 -1
  61. package/dist/assets/data-capture-D4gAy-Yc.js +0 -242
  62. package/dist/assets/email-B1AJehis.js +0 -90
  63. package/dist/assets/embedded-wallet-Q6DQopDk.js +0 -410
  64. package/dist/assets/features-B6JAPAFb.js +0 -5
  65. package/dist/assets/index-5jd8RPN9.js +0 -7428
  66. package/dist/assets/index-B4kNtSTk.js +0 -1080
  67. package/dist/assets/index-B753eAob.js +0 -1
  68. package/dist/assets/index-BZoLTv77.js +0 -3
  69. package/dist/assets/index-Bcw9UURX.js +0 -3
  70. package/dist/assets/index-BkrV-ahK.js +0 -2
  71. package/dist/assets/index-CRQzQ_up.js +0 -9
  72. package/dist/assets/index-CSW0Gt5y.js +0 -132
  73. package/dist/assets/index-CZ4jTIsk.js +0 -138
  74. package/dist/assets/index-Cwa8SB3h.js +0 -61
  75. package/dist/assets/index-DEwQgT4r.js +0 -78
  76. package/dist/assets/index-DVfQiQ1d.js +0 -1
  77. package/dist/assets/index-cV3UdaMs.css +0 -1
  78. package/dist/assets/index-hAmu622O.js +0 -43
  79. package/dist/assets/onramp-XcPNCLRU.js +0 -422
  80. package/dist/assets/pay-with-exchange-BeHt4OF4.js +0 -280
  81. package/dist/assets/property-C9JG5tag.js +0 -27
  82. package/dist/assets/receive-7ff4he2S.js +0 -131
  83. package/dist/assets/send-CkMBIRXT.js +0 -554
  84. package/dist/assets/socials-DmmxuLrP.js +0 -250
  85. package/dist/assets/swaps-IHAYRQ0x.js +0 -1050
  86. package/dist/assets/transactions-9Cxdk7o9.js +0 -16
  87. package/dist/assets/v4-BOvFkHkt.js +0 -1
  88. package/dist/assets/w3m-modal-kJzFnBfB.js +0 -891
  89. package/dist/assets/wallet-standard-Dv8LMoUB.js +0 -1
  90. package/dist/favicon.png +0 -0
  91. package/dist/favicon.svg +0 -11
  92. package/dist/icon-192x192.png +0 -0
  93. package/dist/icon-512x512.png +0 -0
  94. package/dist/index.html +0 -33
  95. package/index.html +0 -32
  96. package/public/favicon.png +0 -0
  97. package/public/favicon.svg +0 -11
  98. package/public/icon-192x192.png +0 -0
  99. package/public/icon-512x512.png +0 -0
  100. package/railway.toml +0 -10
  101. package/src/api/client.ts +0 -95
  102. package/src/components/AddressInputModal.css +0 -173
  103. package/src/components/AddressInputModal.tsx +0 -250
  104. package/src/components/ApprovalStep.tsx +0 -92
  105. package/src/components/ExecutionStep.tsx +0 -27
  106. package/src/components/InputStep.tsx +0 -431
  107. package/src/components/QuoteSelector.css +0 -146
  108. package/src/components/QuoteSelector.tsx +0 -137
  109. package/src/components/QuotesModal.css +0 -250
  110. package/src/components/QuotesModal.tsx +0 -182
  111. package/src/components/SettingsModal.css +0 -145
  112. package/src/components/SettingsModal.tsx +0 -180
  113. package/src/components/StatusStep.tsx +0 -139
  114. package/src/components/SwapWidget.css +0 -713
  115. package/src/components/SwapWidget.tsx +0 -607
  116. package/src/components/TokenSelectModal.css +0 -438
  117. package/src/components/TokenSelectModal.tsx +0 -487
  118. package/src/components/WalletProvider.tsx +0 -92
  119. package/src/config/appkit.ts +0 -104
  120. package/src/config/wagmi.ts +0 -27
  121. package/src/constants/chains.ts +0 -72
  122. package/src/constants/defaults.ts +0 -28
  123. package/src/constants/swappers.ts +0 -48
  124. package/src/constants/viemChains.ts +0 -69
  125. package/src/contexts/SwapWalletContext.tsx +0 -25
  126. package/src/demo/App.css +0 -458
  127. package/src/demo/App.tsx +0 -481
  128. package/src/demo/main.tsx +0 -25
  129. package/src/hooks/useAssets.ts +0 -180
  130. package/src/hooks/useBalances.ts +0 -822
  131. package/src/hooks/useBitcoinSigning.ts +0 -276
  132. package/src/hooks/useMarketData.ts +0 -169
  133. package/src/hooks/useSolanaSigning.ts +0 -275
  134. package/src/hooks/useStatusPolling.ts +0 -137
  135. package/src/hooks/useSwapApproval.ts +0 -106
  136. package/src/hooks/useSwapDisplayValues.ts +0 -157
  137. package/src/hooks/useSwapExecution.ts +0 -199
  138. package/src/hooks/useSwapHandlers.ts +0 -119
  139. package/src/hooks/useSwapQuote.ts +0 -71
  140. package/src/hooks/useSwapQuoting.ts +0 -102
  141. package/src/hooks/useSwapRates.ts +0 -61
  142. package/src/index.ts +0 -43
  143. package/src/machines/SwapMachineContext.ts +0 -5
  144. package/src/machines/__tests__/guards.test.ts +0 -228
  145. package/src/machines/__tests__/setup.test.ts +0 -7
  146. package/src/machines/__tests__/swapMachine.test.ts +0 -544
  147. package/src/machines/__tests__/types.test.ts +0 -124
  148. package/src/machines/guards.ts +0 -33
  149. package/src/machines/swapMachine.ts +0 -288
  150. package/src/machines/types.ts +0 -54
  151. package/src/services/transactionStatus.ts +0 -322
  152. package/src/types/index.ts +0 -361
  153. package/src/utils/addressValidation.ts +0 -201
  154. package/src/utils/redirect.ts +0 -45
  155. package/src/vite-env.d.ts +0 -10
  156. package/tsconfig.json +0 -23
  157. package/tsconfig.node.json +0 -10
  158. package/vite.config.ts +0 -101
  159. package/vitest.config.ts +0 -18
@@ -1,137 +0,0 @@
1
- import './QuoteSelector.css'
2
-
3
- import { useCallback, useMemo, useState } from 'react'
4
-
5
- import { getSwapperColor, getSwapperIcon } from '../constants/swappers'
6
- import { formatUsdValue } from '../hooks/useMarketData'
7
- import type { Asset, TradeRate } from '../types'
8
- import { formatAmount } from '../types'
9
- import { QuotesModal } from './QuotesModal'
10
-
11
- type QuoteSelectorProps = {
12
- rates: TradeRate[]
13
- selectedRate: TradeRate | null
14
- onSelectRate: (rate: TradeRate) => void
15
- buyAsset: Asset
16
- sellAsset: Asset
17
- sellAmountBaseUnit: string
18
- isLoading: boolean
19
- buyAssetUsdPrice?: string
20
- }
21
-
22
- export const QuoteSelector = ({
23
- rates,
24
- selectedRate,
25
- onSelectRate,
26
- buyAsset,
27
- sellAsset,
28
- sellAmountBaseUnit,
29
- isLoading,
30
- buyAssetUsdPrice,
31
- }: QuoteSelectorProps) => {
32
- const [isModalOpen, setIsModalOpen] = useState(false)
33
-
34
- const bestRate = useMemo(() => rates[0], [rates])
35
- const alternativeRatesCount = useMemo(() => Math.max(0, rates.length - 1), [rates])
36
-
37
- const handleOpenModal = useCallback(() => {
38
- if (rates.length > 0) {
39
- setIsModalOpen(true)
40
- }
41
- }, [rates.length])
42
-
43
- const handleCloseModal = useCallback(() => {
44
- setIsModalOpen(false)
45
- }, [])
46
-
47
- const handleSelectRate = useCallback(
48
- (rate: TradeRate) => {
49
- onSelectRate(rate)
50
- },
51
- [onSelectRate],
52
- )
53
-
54
- if (isLoading) {
55
- return (
56
- <div className='ssw-quote-selector ssw-loading'>
57
- <div className='ssw-quote-loading'>
58
- <div className='ssw-spinner-small' />
59
- <span>Finding best rates...</span>
60
- </div>
61
- </div>
62
- )
63
- }
64
-
65
- if (!bestRate) {
66
- return null
67
- }
68
-
69
- const displayRate = selectedRate ?? bestRate
70
- const buyAmount = displayRate.buyAmountCryptoBaseUnit ?? '0'
71
- const swapperIcon = getSwapperIcon(displayRate.swapperName)
72
- const swapperColor = getSwapperColor(displayRate.swapperName)
73
- const formattedBuyAmount = formatAmount(buyAmount, buyAsset.precision)
74
- const usdValue = formatUsdValue(buyAmount, buyAsset.precision, buyAssetUsdPrice)
75
-
76
- return (
77
- <>
78
- <button className='ssw-quote-selector' onClick={handleOpenModal} type='button'>
79
- <div className='ssw-quote-left'>
80
- <div className='ssw-quote-provider'>
81
- {swapperIcon ? (
82
- <img
83
- src={swapperIcon}
84
- alt={displayRate.swapperName}
85
- className='ssw-quote-provider-icon'
86
- />
87
- ) : (
88
- <div
89
- className='ssw-quote-provider-icon-placeholder'
90
- style={{ backgroundColor: swapperColor }}
91
- >
92
- {displayRate.swapperName.charAt(0)}
93
- </div>
94
- )}
95
- <span className='ssw-quote-provider-name'>{displayRate.swapperName}</span>
96
- {displayRate === bestRate && <span className='ssw-quote-best-tag'>Best</span>}
97
- </div>
98
- <span className='ssw-quote-usd'>{usdValue}</span>
99
- </div>
100
-
101
- <div className='ssw-quote-right'>
102
- <div className='ssw-quote-amount-row'>
103
- <span className='ssw-quote-amount'>{formattedBuyAmount}</span>
104
- <span className='ssw-quote-symbol'>{buyAsset.symbol}</span>
105
- </div>
106
- {alternativeRatesCount > 0 && (
107
- <span className='ssw-quote-more'>
108
- +{alternativeRatesCount} more
109
- <svg
110
- width='12'
111
- height='12'
112
- viewBox='0 0 24 24'
113
- fill='none'
114
- stroke='currentColor'
115
- strokeWidth='2.5'
116
- >
117
- <path d='M9 18l6-6-6-6' />
118
- </svg>
119
- </span>
120
- )}
121
- </div>
122
- </button>
123
-
124
- <QuotesModal
125
- isOpen={isModalOpen}
126
- onClose={handleCloseModal}
127
- rates={rates}
128
- selectedRate={selectedRate}
129
- onSelectRate={handleSelectRate}
130
- buyAsset={buyAsset}
131
- sellAsset={sellAsset}
132
- sellAmountBaseUnit={sellAmountBaseUnit}
133
- buyAssetUsdPrice={buyAssetUsdPrice}
134
- />
135
- </>
136
- )
137
- }
@@ -1,250 +0,0 @@
1
- .ssw-quotes-modal-backdrop {
2
- position: fixed;
3
- inset: 0;
4
- background: rgba(0, 0, 0, 0.75);
5
- backdrop-filter: blur(8px);
6
- display: flex;
7
- align-items: center;
8
- justify-content: center;
9
- z-index: 1000;
10
- padding: 20px;
11
- animation: ssw-fade-in 0.15s ease;
12
- }
13
-
14
- @keyframes ssw-fade-in {
15
- from {
16
- opacity: 0;
17
- }
18
- to {
19
- opacity: 1;
20
- }
21
- }
22
-
23
- .ssw-quotes-modal {
24
- width: 100%;
25
- max-width: 400px;
26
- max-height: 70vh;
27
- background: var(--ssw-bg-secondary, #12121c);
28
- border-radius: var(--ssw-radius-widget);
29
- border: 1px solid var(--ssw-border, rgba(255, 255, 255, 0.08));
30
- display: flex;
31
- flex-direction: column;
32
- overflow: hidden;
33
- animation: ssw-slide-up 0.2s ease;
34
- }
35
-
36
- @keyframes ssw-slide-up {
37
- from {
38
- opacity: 0;
39
- transform: translateY(10px);
40
- }
41
- to {
42
- opacity: 1;
43
- transform: translateY(0);
44
- }
45
- }
46
-
47
- .ssw-quotes-modal-header {
48
- display: flex;
49
- align-items: flex-start;
50
- justify-content: space-between;
51
- padding: 20px 20px 16px;
52
- flex-shrink: 0;
53
- }
54
-
55
- .ssw-quotes-header-content {
56
- display: flex;
57
- flex-direction: column;
58
- gap: 4px;
59
- }
60
-
61
- .ssw-quotes-modal-title {
62
- font-size: 18px;
63
- font-weight: 600;
64
- color: var(--ssw-text-primary, #ffffff);
65
- margin: 0;
66
- }
67
-
68
- .ssw-quotes-modal-subtitle {
69
- font-size: 13px;
70
- color: var(--ssw-text-secondary, #a0a0b0);
71
- }
72
-
73
- .ssw-quotes-modal-close {
74
- padding: 6px;
75
- border: none;
76
- background: none;
77
- border-radius: var(--ssw-radius-xs);
78
- color: var(--ssw-text-muted, #6b6b80);
79
- cursor: pointer;
80
- transition: all 0.15s ease;
81
- display: flex;
82
- align-items: center;
83
- justify-content: center;
84
- margin: -2px -6px 0 0;
85
- }
86
-
87
- .ssw-quotes-modal-close:hover {
88
- color: var(--ssw-text-primary, #ffffff);
89
- background: var(--ssw-bg-hover, rgba(255, 255, 255, 0.05));
90
- }
91
-
92
- .ssw-quotes-modal-list {
93
- flex: 1;
94
- overflow-y: auto;
95
- padding: 0 12px 12px;
96
- display: flex;
97
- flex-direction: column;
98
- gap: 6px;
99
- scrollbar-width: thin;
100
- scrollbar-color: var(--ssw-border) transparent;
101
- }
102
-
103
- .ssw-quotes-modal-list::-webkit-scrollbar {
104
- width: 6px;
105
- }
106
-
107
- .ssw-quotes-modal-list::-webkit-scrollbar-track {
108
- background: transparent;
109
- }
110
-
111
- .ssw-quotes-modal-list::-webkit-scrollbar-thumb {
112
- background: var(--ssw-border);
113
- border-radius: 3px;
114
- }
115
-
116
- .ssw-quotes-modal-list::-webkit-scrollbar-thumb:hover {
117
- background: var(--ssw-border-hover);
118
- }
119
-
120
- .ssw-quote-row {
121
- display: flex;
122
- align-items: center;
123
- justify-content: space-between;
124
- padding: 14px 16px;
125
- border: 1px solid transparent;
126
- border-radius: var(--ssw-radius-md);
127
- background: var(--ssw-bg-tertiary, #1a1a2e);
128
- cursor: pointer;
129
- transition: all 0.15s ease;
130
- text-align: left;
131
- width: 100%;
132
- gap: 12px;
133
- }
134
-
135
- .ssw-quote-row:hover {
136
- background: var(--ssw-bg-hover, rgba(255, 255, 255, 0.05));
137
- }
138
-
139
- .ssw-quote-row.ssw-best {
140
- border-color: var(--ssw-accent, #3861fb);
141
- background: color-mix(in srgb, var(--ssw-accent, #3861fb) 6%, transparent);
142
- }
143
-
144
- .ssw-quote-row.ssw-best:hover {
145
- background: color-mix(in srgb, var(--ssw-accent, #3861fb) 12%, transparent);
146
- }
147
-
148
- .ssw-quote-row.ssw-selected {
149
- border-color: var(--ssw-success, #00d395);
150
- background: rgba(0, 211, 149, 0.06);
151
- }
152
-
153
- .ssw-quote-row-left {
154
- display: flex;
155
- align-items: center;
156
- gap: 12px;
157
- min-width: 0;
158
- }
159
-
160
- .ssw-quote-row-icon {
161
- width: 36px;
162
- height: 36px;
163
- border-radius: var(--ssw-radius-sm);
164
- object-fit: contain;
165
- background: var(--ssw-bg-secondary, #12121c);
166
- flex-shrink: 0;
167
- }
168
-
169
- .ssw-quote-row-icon-placeholder {
170
- width: 36px;
171
- height: 36px;
172
- border-radius: var(--ssw-radius-sm);
173
- display: flex;
174
- align-items: center;
175
- justify-content: center;
176
- font-size: 15px;
177
- font-weight: 600;
178
- color: white;
179
- flex-shrink: 0;
180
- }
181
-
182
- .ssw-quote-row-info {
183
- display: flex;
184
- flex-direction: column;
185
- gap: 2px;
186
- min-width: 0;
187
- }
188
-
189
- .ssw-quote-row-name-row {
190
- display: flex;
191
- align-items: center;
192
- gap: 8px;
193
- flex-wrap: wrap;
194
- }
195
-
196
- .ssw-quote-row-name {
197
- font-size: 14px;
198
- font-weight: 600;
199
- color: var(--ssw-text-primary, #ffffff);
200
- }
201
-
202
- .ssw-quote-row-best {
203
- padding: 2px 6px;
204
- border-radius: var(--ssw-radius-2xs);
205
- background: var(--ssw-accent, #3861fb);
206
- color: white;
207
- font-size: 9px;
208
- font-weight: 700;
209
- text-transform: uppercase;
210
- letter-spacing: 0.04em;
211
- }
212
-
213
- .ssw-quote-row-diff {
214
- padding: 2px 6px;
215
- border-radius: var(--ssw-radius-2xs);
216
- background: rgba(239, 68, 68, 0.12);
217
- color: #ef4444;
218
- font-size: 10px;
219
- font-weight: 600;
220
- }
221
-
222
- .ssw-quote-row-time {
223
- font-size: 12px;
224
- color: var(--ssw-text-muted, #6b6b80);
225
- }
226
-
227
- .ssw-quote-row-right {
228
- display: flex;
229
- flex-direction: column;
230
- align-items: flex-end;
231
- gap: 2px;
232
- flex-shrink: 0;
233
- }
234
-
235
- .ssw-quote-row-amount {
236
- font-size: 15px;
237
- font-weight: 600;
238
- color: var(--ssw-text-primary, #ffffff);
239
- }
240
-
241
- .ssw-quote-row-symbol {
242
- font-size: 13px;
243
- font-weight: 500;
244
- color: var(--ssw-text-secondary, #a0a0b0);
245
- }
246
-
247
- .ssw-quote-row-usd {
248
- font-size: 12px;
249
- color: var(--ssw-text-muted, #6b6b80);
250
- }
@@ -1,182 +0,0 @@
1
- import './QuotesModal.css'
2
-
3
- import { bnOrZero } from '@shapeshiftoss/utils'
4
- import { useCallback, useEffect, useMemo } from 'react'
5
-
6
- import { getSwapperColor, getSwapperIcon } from '../constants/swappers'
7
- import { formatUsdValue } from '../hooks/useMarketData'
8
- import type { Asset, TradeRate } from '../types'
9
- import { formatAmount } from '../types'
10
-
11
- const useLockBodyScroll = (isLocked: boolean) => {
12
- useEffect(() => {
13
- if (!isLocked) return
14
- const originalOverflow = document.body.style.overflow
15
- document.body.style.overflow = 'hidden'
16
- return () => {
17
- document.body.style.overflow = originalOverflow
18
- }
19
- }, [isLocked])
20
- }
21
-
22
- type QuotesModalProps = {
23
- isOpen: boolean
24
- onClose: () => void
25
- rates: TradeRate[]
26
- selectedRate: TradeRate | null
27
- onSelectRate: (rate: TradeRate) => void
28
- buyAsset: Asset
29
- sellAsset: Asset
30
- sellAmountBaseUnit: string
31
- buyAssetUsdPrice?: string
32
- }
33
-
34
- const calculateSavingsPercent = (bestAmount: string, currentAmount: string): string | null => {
35
- const best = bnOrZero(bestAmount)
36
- const current = bnOrZero(currentAmount)
37
- if (best.isZero()) return null
38
- const diff = best.minus(current).div(best).times(100)
39
- return diff.gt(0.1) ? diff.toFixed(2) : null
40
- }
41
-
42
- export const QuotesModal = ({
43
- isOpen,
44
- onClose,
45
- rates,
46
- selectedRate,
47
- onSelectRate,
48
- buyAsset,
49
- sellAsset,
50
- sellAmountBaseUnit,
51
- buyAssetUsdPrice,
52
- }: QuotesModalProps) => {
53
- useLockBodyScroll(isOpen)
54
-
55
- const handleBackdropClick = useCallback(
56
- (e: React.MouseEvent) => {
57
- if (e.target === e.currentTarget) {
58
- onClose()
59
- }
60
- },
61
- [onClose],
62
- )
63
-
64
- const handleSelectRate = useCallback(
65
- (rate: TradeRate) => {
66
- onSelectRate(rate)
67
- onClose()
68
- },
69
- [onSelectRate, onClose],
70
- )
71
-
72
- const sortedRates = useMemo(() => {
73
- return [...rates]
74
- .filter(r => !r.error && r.buyAmountCryptoBaseUnit !== '0')
75
- .sort((a, b) => {
76
- const aAmount = bnOrZero(a.buyAmountCryptoBaseUnit)
77
- const bAmount = bnOrZero(b.buyAmountCryptoBaseUnit)
78
- return bAmount.minus(aAmount).toNumber()
79
- })
80
- }, [rates])
81
-
82
- const bestRate = useMemo(() => sortedRates[0], [sortedRates])
83
- const bestBuyAmount = bestRate?.buyAmountCryptoBaseUnit ?? '0'
84
-
85
- if (!isOpen) return null
86
-
87
- return (
88
- // eslint-disable-next-line jsx-a11y/no-noninteractive-element-interactions
89
- <div
90
- className='ssw-quotes-modal-backdrop'
91
- onClick={handleBackdropClick}
92
- onKeyDown={e => e.key === 'Escape' && onClose()}
93
- role='dialog'
94
- aria-modal='true'
95
- aria-labelledby='quotes-modal-title'
96
- >
97
- <div className='ssw-quotes-modal'>
98
- <div className='ssw-quotes-modal-header'>
99
- <div className='ssw-quotes-header-content'>
100
- <h2 id='quotes-modal-title' className='ssw-quotes-modal-title'>
101
- Select Route
102
- </h2>
103
- <span className='ssw-quotes-modal-subtitle'>
104
- {formatAmount(sellAmountBaseUnit, sellAsset.precision)} {sellAsset.symbol} →{' '}
105
- {buyAsset.symbol}
106
- </span>
107
- </div>
108
- <button className='ssw-quotes-modal-close' onClick={onClose} type='button'>
109
- <svg
110
- width='20'
111
- height='20'
112
- viewBox='0 0 24 24'
113
- fill='none'
114
- stroke='currentColor'
115
- strokeWidth='2'
116
- >
117
- <path d='M18 6L6 18M6 6l12 12' />
118
- </svg>
119
- </button>
120
- </div>
121
-
122
- <div className='ssw-quotes-modal-list'>
123
- {sortedRates.map((rate, index) => {
124
- const buyAmount = rate.buyAmountCryptoBaseUnit ?? '0'
125
- const estimatedTime = rate.estimatedExecutionTimeMs
126
- const isBest = index === 0
127
- const isSelected = selectedRate?.id === rate.id
128
- const swapperIcon = getSwapperIcon(rate.swapperName)
129
- const swapperColor = getSwapperColor(rate.swapperName)
130
- const formattedBuyAmount = formatAmount(buyAmount, buyAsset.precision)
131
- const usdValue = formatUsdValue(buyAmount, buyAsset.precision, buyAssetUsdPrice)
132
- const savingsPercent = isBest ? null : calculateSavingsPercent(bestBuyAmount, buyAmount)
133
- const estimatedSeconds = estimatedTime ? Math.round(estimatedTime / 1000) : 0
134
- const hasTime = estimatedSeconds > 0
135
-
136
- return (
137
- <button
138
- key={rate.id}
139
- className={`ssw-quote-row ${isSelected ? 'ssw-selected' : ''} ${
140
- isBest ? 'ssw-best' : ''
141
- }`}
142
- onClick={() => handleSelectRate(rate)}
143
- type='button'
144
- >
145
- <div className='ssw-quote-row-left'>
146
- {swapperIcon ? (
147
- <img src={swapperIcon} alt={rate.swapperName} className='ssw-quote-row-icon' />
148
- ) : (
149
- <div
150
- className='ssw-quote-row-icon-placeholder'
151
- style={{ backgroundColor: swapperColor }}
152
- >
153
- {rate.swapperName.charAt(0)}
154
- </div>
155
- )}
156
- <div className='ssw-quote-row-info'>
157
- <div className='ssw-quote-row-name-row'>
158
- <span className='ssw-quote-row-name'>{rate.swapperName}</span>
159
- {isBest && <span className='ssw-quote-row-best'>Best</span>}
160
- {savingsPercent && (
161
- <span className='ssw-quote-row-diff'>-{savingsPercent}%</span>
162
- )}
163
- </div>
164
- {hasTime && <span className='ssw-quote-row-time'>~{estimatedSeconds}s</span>}
165
- </div>
166
- </div>
167
-
168
- <div className='ssw-quote-row-right'>
169
- <span className='ssw-quote-row-amount'>
170
- {formattedBuyAmount}{' '}
171
- <span className='ssw-quote-row-symbol'>{buyAsset.symbol}</span>
172
- </span>
173
- <span className='ssw-quote-row-usd'>{usdValue}</span>
174
- </div>
175
- </button>
176
- )
177
- })}
178
- </div>
179
- </div>
180
- </div>
181
- )
182
- }
@@ -1,145 +0,0 @@
1
- .ssw-settings-modal {
2
- background: var(--ssw-bg-secondary);
3
- border-radius: var(--ssw-radius-lg);
4
- width: 100%;
5
- max-width: 400px;
6
- overflow: hidden;
7
- box-shadow: 0 24px 48px rgba(0, 0, 0, 0.2);
8
- animation: ssw-settings-slide-up 0.2s ease;
9
- }
10
-
11
- @keyframes ssw-settings-slide-up {
12
- from {
13
- opacity: 0;
14
- transform: translateY(10px);
15
- }
16
- to {
17
- opacity: 1;
18
- transform: translateY(0);
19
- }
20
- }
21
-
22
- .ssw-settings-content {
23
- padding: 20px 24px 24px;
24
- }
25
-
26
- .ssw-settings-section {
27
- display: flex;
28
- flex-direction: column;
29
- gap: 12px;
30
- }
31
-
32
- .ssw-settings-label {
33
- display: flex;
34
- align-items: center;
35
- gap: 6px;
36
- font-size: 14px;
37
- font-weight: 500;
38
- color: var(--ssw-text-secondary);
39
- }
40
-
41
- .ssw-info-btn {
42
- background: none;
43
- border: none;
44
- padding: 2px;
45
- cursor: pointer;
46
- color: var(--ssw-text-muted);
47
- border-radius: 50%;
48
- transition: color 0.15s ease;
49
- }
50
-
51
- .ssw-info-btn:hover {
52
- color: var(--ssw-text-secondary);
53
- }
54
-
55
- .ssw-slippage-options {
56
- display: flex;
57
- gap: 8px;
58
- }
59
-
60
- .ssw-slippage-btn {
61
- flex: 1;
62
- padding: 10px 12px;
63
- border: 1px solid var(--ssw-border);
64
- border-radius: var(--ssw-radius-sm);
65
- background: var(--ssw-bg-tertiary);
66
- color: var(--ssw-text-primary);
67
- font-size: 14px;
68
- font-weight: 500;
69
- cursor: pointer;
70
- transition: all 0.15s ease;
71
- }
72
-
73
- .ssw-slippage-btn:hover {
74
- border-color: var(--ssw-accent);
75
- }
76
-
77
- .ssw-slippage-btn.ssw-selected {
78
- background: var(--ssw-accent-light);
79
- border-color: var(--ssw-accent);
80
- color: var(--ssw-accent);
81
- }
82
-
83
- .ssw-slippage-custom {
84
- flex: 1.2;
85
- position: relative;
86
- display: flex;
87
- align-items: center;
88
- }
89
-
90
- .ssw-slippage-custom input {
91
- width: 100%;
92
- padding: 10px 28px 10px 12px;
93
- border: 1px solid var(--ssw-border);
94
- border-radius: var(--ssw-radius-sm);
95
- background: var(--ssw-bg-tertiary);
96
- color: var(--ssw-text-primary);
97
- font-size: 14px;
98
- font-weight: 500;
99
- outline: none;
100
- transition: border-color 0.15s ease;
101
- }
102
-
103
- .ssw-slippage-custom input:focus {
104
- border-color: var(--ssw-accent);
105
- }
106
-
107
- .ssw-slippage-custom input::placeholder {
108
- color: var(--ssw-text-muted);
109
- font-weight: 400;
110
- }
111
-
112
- .ssw-slippage-custom.ssw-selected input {
113
- background: var(--ssw-accent-light);
114
- border-color: var(--ssw-accent);
115
- }
116
-
117
- .ssw-slippage-suffix {
118
- position: absolute;
119
- right: 12px;
120
- color: var(--ssw-text-secondary);
121
- font-size: 14px;
122
- pointer-events: none;
123
- }
124
-
125
- .ssw-slippage-warning {
126
- display: flex;
127
- align-items: flex-start;
128
- gap: 8px;
129
- padding: 10px 12px;
130
- border-radius: var(--ssw-radius-sm);
131
- background: rgba(255, 193, 7, 0.1);
132
- color: #ffc107;
133
- font-size: 13px;
134
- line-height: 1.4;
135
- }
136
-
137
- .ssw-slippage-warning svg {
138
- flex-shrink: 0;
139
- margin-top: 1px;
140
- }
141
-
142
- .ssw-slippage-warning.ssw-error {
143
- background: rgba(244, 67, 54, 0.1);
144
- color: #f44336;
145
- }