@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,544 +0,0 @@
1
- import { describe, expect, it } from 'vitest'
2
- import { createActor } from 'xstate'
3
-
4
- import type { Asset, QuoteResponse, TradeRate } from '../../types'
5
- import { createInitialContext, swapMachine } from '../swapMachine'
6
-
7
- const TEST_ETH: Asset = {
8
- assetId: 'eip155:1/slip44:60',
9
- chainId: 'eip155:1',
10
- symbol: 'ETH',
11
- name: 'Ethereum',
12
- precision: 18,
13
- }
14
-
15
- const TEST_BTC: Asset = {
16
- assetId: 'bip122:000000000019d6689c085ae165831e93/slip44:0',
17
- chainId: 'bip122:000000000019d6689c085ae165831e93',
18
- symbol: 'BTC',
19
- name: 'Bitcoin',
20
- precision: 8,
21
- }
22
-
23
- const TEST_USDC: Asset = {
24
- assetId: 'eip155:1/erc20:0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48',
25
- chainId: 'eip155:1',
26
- symbol: 'USDC',
27
- name: 'USD Coin',
28
- precision: 6,
29
- }
30
-
31
- const TEST_RATE: TradeRate = {
32
- swapperName: 'THORChain' as TradeRate['swapperName'],
33
- rate: '1500',
34
- buyAmountCryptoBaseUnit: '1500000000',
35
- sellAmountCryptoBaseUnit: '1000000000000000000',
36
- steps: 1,
37
- affiliateBps: '0',
38
- }
39
-
40
- const TEST_QUOTE_NO_APPROVAL = {
41
- transactionData: { to: '0xRouter', data: '0xSwapData', value: '1000000000000000000' },
42
- } as QuoteResponse
43
-
44
- const TEST_QUOTE_WITH_APPROVAL = {
45
- transactionData: { to: '0xRouter', data: '0xSwapData', value: '1000000000000000000' },
46
- approval: { isRequired: true, spender: '0xSpender' },
47
- } as QuoteResponse
48
-
49
- describe('swapMachine', () => {
50
- describe('initial state', () => {
51
- it('starts in idle and auto-transitions to input', () => {
52
- const actor = createActor(swapMachine)
53
- actor.start()
54
- const snapshot = actor.getSnapshot()
55
- expect(snapshot.value).toBe('input')
56
- actor.stop()
57
- })
58
-
59
- it('has correct initial context', () => {
60
- const actor = createActor(swapMachine)
61
- actor.start()
62
- const ctx = actor.getSnapshot().context
63
- expect(ctx.sellAsset.symbol).toBe('ETH')
64
- expect(ctx.buyAsset.symbol).toBe('USDC')
65
- expect(ctx.sellAmount).toBe('')
66
- expect(ctx.sellAmountBaseUnit).toBeUndefined()
67
- expect(ctx.selectedRate).toBeNull()
68
- expect(ctx.quote).toBeNull()
69
- expect(ctx.txHash).toBeNull()
70
- expect(ctx.approvalTxHash).toBeNull()
71
- expect(ctx.error).toBeNull()
72
- expect(ctx.retryCount).toBe(0)
73
- expect(ctx.chainType).toBe('evm')
74
- expect(ctx.slippage).toBe('0.5')
75
- expect(ctx.isSellAssetEvm).toBe(true)
76
- expect(ctx.isSellAssetUtxo).toBe(false)
77
- expect(ctx.isSellAssetSolana).toBe(false)
78
- expect(ctx.isBuyAssetEvm).toBe(true)
79
- actor.stop()
80
- })
81
- })
82
-
83
- describe('input state — context update events', () => {
84
- it('SET_SELL_ASSET updates sellAsset and chain flags', () => {
85
- const actor = createActor(swapMachine)
86
- actor.start()
87
- actor.send({ type: 'SET_SELL_ASSET', asset: TEST_BTC })
88
- const ctx = actor.getSnapshot().context
89
- expect(ctx.sellAsset.symbol).toBe('BTC')
90
- expect(ctx.chainType).toBe('utxo')
91
- expect(ctx.isSellAssetEvm).toBe(false)
92
- expect(ctx.isSellAssetUtxo).toBe(true)
93
- expect(ctx.isSellAssetSolana).toBe(false)
94
- expect(actor.getSnapshot().value).toBe('input')
95
- actor.stop()
96
- })
97
-
98
- it('SET_SELL_ASSET recalculates sellAmountBaseUnit with new precision', () => {
99
- const actor = createActor(swapMachine)
100
- actor.start()
101
- actor.send({ type: 'SET_SELL_AMOUNT', amount: '0.001', amountBaseUnit: '1000000000000000' })
102
- expect(actor.getSnapshot().context.sellAmountBaseUnit).toBe('1000000000000000')
103
-
104
- actor.send({ type: 'SET_SELL_ASSET', asset: TEST_BTC })
105
- expect(actor.getSnapshot().context.sellAmountBaseUnit).toBe('100000')
106
- actor.stop()
107
- })
108
-
109
- it('SET_SELL_ASSET keeps sellAmountBaseUnit undefined when no sell amount', () => {
110
- const actor = createActor(swapMachine)
111
- actor.start()
112
- expect(actor.getSnapshot().context.sellAmountBaseUnit).toBeUndefined()
113
-
114
- actor.send({ type: 'SET_SELL_ASSET', asset: TEST_BTC })
115
- expect(actor.getSnapshot().context.sellAmountBaseUnit).toBeUndefined()
116
- actor.stop()
117
- })
118
-
119
- it('SET_BUY_ASSET updates buyAsset and isBuyAssetEvm', () => {
120
- const actor = createActor(swapMachine)
121
- actor.start()
122
- actor.send({ type: 'SET_BUY_ASSET', asset: TEST_BTC })
123
- const ctx = actor.getSnapshot().context
124
- expect(ctx.buyAsset.symbol).toBe('BTC')
125
- expect(ctx.isBuyAssetEvm).toBe(false)
126
- expect(actor.getSnapshot().value).toBe('input')
127
- actor.stop()
128
- })
129
-
130
- it('SET_SELL_AMOUNT updates sellAmount and sellAmountBaseUnit', () => {
131
- const actor = createActor(swapMachine)
132
- actor.start()
133
- actor.send({ type: 'SET_SELL_AMOUNT', amount: '2.5', amountBaseUnit: '2500000000000000000' })
134
- const ctx = actor.getSnapshot().context
135
- expect(ctx.sellAmount).toBe('2.5')
136
- expect(ctx.sellAmountBaseUnit).toBe('2500000000000000000')
137
- actor.stop()
138
- })
139
-
140
- it('SET_SLIPPAGE updates slippage', () => {
141
- const actor = createActor(swapMachine)
142
- actor.start()
143
- actor.send({ type: 'SET_SLIPPAGE', slippage: '1.0' })
144
- expect(actor.getSnapshot().context.slippage).toBe('1.0')
145
- actor.stop()
146
- })
147
-
148
- it('SELECT_RATE updates selectedRate', () => {
149
- const actor = createActor(swapMachine)
150
- actor.start()
151
- actor.send({ type: 'SELECT_RATE', rate: TEST_RATE })
152
- expect(actor.getSnapshot().context.selectedRate).toEqual(TEST_RATE)
153
- actor.stop()
154
- })
155
-
156
- it('SET_WALLET_ADDRESS updates walletAddress', () => {
157
- const actor = createActor(swapMachine)
158
- actor.start()
159
- actor.send({ type: 'SET_WALLET_ADDRESS', address: '0xWallet' })
160
- expect(actor.getSnapshot().context.walletAddress).toBe('0xWallet')
161
- actor.stop()
162
- })
163
-
164
- it('SET_RECEIVE_ADDRESS updates effectiveReceiveAddress', () => {
165
- const actor = createActor(swapMachine)
166
- actor.start()
167
- actor.send({ type: 'SET_RECEIVE_ADDRESS', address: '0xReceiver' })
168
- expect(actor.getSnapshot().context.effectiveReceiveAddress).toBe('0xReceiver')
169
- actor.stop()
170
- })
171
-
172
- it('UPDATE_CHAIN_INFO updates all chain flags', () => {
173
- const actor = createActor(swapMachine)
174
- actor.start()
175
- actor.send({
176
- type: 'UPDATE_CHAIN_INFO',
177
- chainType: 'utxo',
178
- isSellAssetEvm: false,
179
- isSellAssetUtxo: true,
180
- isSellAssetSolana: false,
181
- isBuyAssetEvm: false,
182
- })
183
- const ctx = actor.getSnapshot().context
184
- expect(ctx.chainType).toBe('utxo')
185
- expect(ctx.isSellAssetEvm).toBe(false)
186
- expect(ctx.isSellAssetUtxo).toBe(true)
187
- expect(ctx.isBuyAssetEvm).toBe(false)
188
- actor.stop()
189
- })
190
- })
191
-
192
- describe('input → quoting transition', () => {
193
- it('FETCH_QUOTE transitions to quoting when hasValidInput', () => {
194
- const actor = createActor(swapMachine)
195
- actor.start()
196
- actor.send({ type: 'SET_SELL_AMOUNT', amount: '1', amountBaseUnit: '1000000000000000000' })
197
- actor.send({ type: 'FETCH_QUOTE' })
198
- expect(actor.getSnapshot().value).toBe('quoting')
199
- actor.stop()
200
- })
201
-
202
- it('FETCH_QUOTE stays in input when no valid input', () => {
203
- const actor = createActor(swapMachine)
204
- actor.start()
205
- actor.send({ type: 'FETCH_QUOTE' })
206
- expect(actor.getSnapshot().value).toBe('input')
207
- actor.stop()
208
- })
209
-
210
- it('FETCH_QUOTE stays in input when sellAmountBaseUnit is "0"', () => {
211
- const actor = createActor(swapMachine)
212
- actor.start()
213
- actor.send({ type: 'SET_SELL_AMOUNT', amount: '0', amountBaseUnit: '0' })
214
- actor.send({ type: 'FETCH_QUOTE' })
215
- expect(actor.getSnapshot().value).toBe('input')
216
- actor.stop()
217
- })
218
- })
219
-
220
- describe('quoting state', () => {
221
- it('QUOTE_SUCCESS transitions to executing when no approval required', () => {
222
- const actor = createActor(swapMachine)
223
- actor.start()
224
- actor.send({ type: 'SET_SELL_AMOUNT', amount: '1', amountBaseUnit: '1000000000000000000' })
225
- actor.send({ type: 'FETCH_QUOTE' })
226
- actor.send({ type: 'QUOTE_SUCCESS', quote: TEST_QUOTE_NO_APPROVAL })
227
- expect(actor.getSnapshot().value).toBe('executing')
228
- expect(actor.getSnapshot().context.quote).toEqual(TEST_QUOTE_NO_APPROVAL)
229
- actor.stop()
230
- })
231
-
232
- it('QUOTE_SUCCESS transitions to approval_needed when approval required on evm with ERC20', () => {
233
- const actor = createActor(swapMachine)
234
- actor.start()
235
- actor.send({ type: 'SET_SELL_ASSET', asset: TEST_USDC })
236
- actor.send({ type: 'SET_SELL_AMOUNT', amount: '1', amountBaseUnit: '1000000' })
237
- actor.send({ type: 'FETCH_QUOTE' })
238
- actor.send({ type: 'QUOTE_SUCCESS', quote: TEST_QUOTE_WITH_APPROVAL })
239
- expect(actor.getSnapshot().value).toBe('approval_needed')
240
- expect(actor.getSnapshot().context.quote).toEqual(TEST_QUOTE_WITH_APPROVAL)
241
- actor.stop()
242
- })
243
-
244
- it('QUOTE_SUCCESS skips approval for native assets even when API says required', () => {
245
- const actor = createActor(swapMachine)
246
- actor.start()
247
- actor.send({ type: 'SET_SELL_AMOUNT', amount: '1', amountBaseUnit: '1000000000000000000' })
248
- actor.send({ type: 'FETCH_QUOTE' })
249
- actor.send({ type: 'QUOTE_SUCCESS', quote: TEST_QUOTE_WITH_APPROVAL })
250
- expect(actor.getSnapshot().value).toBe('executing')
251
- actor.stop()
252
- })
253
-
254
- it('QUOTE_SUCCESS transitions to executing when approval required but non-evm chain', () => {
255
- const actor = createActor(swapMachine)
256
- actor.start()
257
- actor.send({ type: 'SET_SELL_ASSET', asset: TEST_BTC })
258
- actor.send({ type: 'SET_SELL_AMOUNT', amount: '1', amountBaseUnit: '100000000' })
259
- actor.send({ type: 'FETCH_QUOTE' })
260
- actor.send({ type: 'QUOTE_SUCCESS', quote: TEST_QUOTE_WITH_APPROVAL })
261
- expect(actor.getSnapshot().value).toBe('executing')
262
- actor.stop()
263
- })
264
-
265
- it('QUOTE_ERROR transitions to error with error message', () => {
266
- const actor = createActor(swapMachine)
267
- actor.start()
268
- actor.send({ type: 'SET_SELL_AMOUNT', amount: '1', amountBaseUnit: '1000000000000000000' })
269
- actor.send({ type: 'FETCH_QUOTE' })
270
- actor.send({ type: 'QUOTE_ERROR', error: 'No quotes available' })
271
- expect(actor.getSnapshot().value).toBe('error')
272
- expect(actor.getSnapshot().context.error).toBe('No quotes available')
273
- actor.stop()
274
- })
275
- })
276
-
277
- describe('approval_needed → approving', () => {
278
- it('APPROVE transitions to approving', () => {
279
- const actor = createActor(swapMachine)
280
- actor.start()
281
- actor.send({ type: 'SET_SELL_ASSET', asset: TEST_USDC })
282
- actor.send({ type: 'SET_SELL_AMOUNT', amount: '1', amountBaseUnit: '1000000' })
283
- actor.send({ type: 'FETCH_QUOTE' })
284
- actor.send({ type: 'QUOTE_SUCCESS', quote: TEST_QUOTE_WITH_APPROVAL })
285
- expect(actor.getSnapshot().value).toBe('approval_needed')
286
- actor.send({ type: 'APPROVE' })
287
- expect(actor.getSnapshot().value).toBe('approving')
288
- actor.stop()
289
- })
290
-
291
- it('RESET from approval_needed goes to input', () => {
292
- const actor = createActor(swapMachine)
293
- actor.start()
294
- actor.send({ type: 'SET_SELL_ASSET', asset: TEST_USDC })
295
- actor.send({ type: 'SET_SELL_AMOUNT', amount: '1', amountBaseUnit: '1000000' })
296
- actor.send({ type: 'FETCH_QUOTE' })
297
- actor.send({ type: 'QUOTE_SUCCESS', quote: TEST_QUOTE_WITH_APPROVAL })
298
- actor.send({ type: 'RESET' })
299
- expect(actor.getSnapshot().value).toBe('input')
300
- actor.stop()
301
- })
302
- })
303
-
304
- describe('approving state', () => {
305
- it('APPROVAL_SUCCESS transitions to executing with approvalTxHash', () => {
306
- const actor = createActor(swapMachine)
307
- actor.start()
308
- actor.send({ type: 'SET_SELL_ASSET', asset: TEST_USDC })
309
- actor.send({ type: 'SET_SELL_AMOUNT', amount: '1', amountBaseUnit: '1000000' })
310
- actor.send({ type: 'FETCH_QUOTE' })
311
- actor.send({ type: 'QUOTE_SUCCESS', quote: TEST_QUOTE_WITH_APPROVAL })
312
- actor.send({ type: 'APPROVE' })
313
- actor.send({ type: 'APPROVAL_SUCCESS', txHash: '0xApprovalHash' })
314
- expect(actor.getSnapshot().value).toBe('executing')
315
- expect(actor.getSnapshot().context.approvalTxHash).toBe('0xApprovalHash')
316
- actor.stop()
317
- })
318
-
319
- it('APPROVAL_ERROR transitions to error', () => {
320
- const actor = createActor(swapMachine)
321
- actor.start()
322
- actor.send({ type: 'SET_SELL_ASSET', asset: TEST_USDC })
323
- actor.send({ type: 'SET_SELL_AMOUNT', amount: '1', amountBaseUnit: '1000000' })
324
- actor.send({ type: 'FETCH_QUOTE' })
325
- actor.send({ type: 'QUOTE_SUCCESS', quote: TEST_QUOTE_WITH_APPROVAL })
326
- actor.send({ type: 'APPROVE' })
327
- actor.send({ type: 'APPROVAL_ERROR', error: 'User rejected' })
328
- expect(actor.getSnapshot().value).toBe('error')
329
- expect(actor.getSnapshot().context.error).toBe('User rejected')
330
- actor.stop()
331
- })
332
- })
333
-
334
- describe('executing state', () => {
335
- it('EXECUTE_SUCCESS transitions to polling_status with txHash', () => {
336
- const actor = createActor(swapMachine)
337
- actor.start()
338
- actor.send({ type: 'SET_SELL_AMOUNT', amount: '1', amountBaseUnit: '1000000000000000000' })
339
- actor.send({ type: 'FETCH_QUOTE' })
340
- actor.send({ type: 'QUOTE_SUCCESS', quote: TEST_QUOTE_NO_APPROVAL })
341
- actor.send({ type: 'EXECUTE_SUCCESS', txHash: '0xTxHash' })
342
- expect(actor.getSnapshot().value).toBe('polling_status')
343
- expect(actor.getSnapshot().context.txHash).toBe('0xTxHash')
344
- actor.stop()
345
- })
346
-
347
- it('EXECUTE_ERROR transitions to error', () => {
348
- const actor = createActor(swapMachine)
349
- actor.start()
350
- actor.send({ type: 'SET_SELL_AMOUNT', amount: '1', amountBaseUnit: '1000000000000000000' })
351
- actor.send({ type: 'FETCH_QUOTE' })
352
- actor.send({ type: 'QUOTE_SUCCESS', quote: TEST_QUOTE_NO_APPROVAL })
353
- actor.send({ type: 'EXECUTE_ERROR', error: 'Transaction failed' })
354
- expect(actor.getSnapshot().value).toBe('error')
355
- expect(actor.getSnapshot().context.error).toBe('Transaction failed')
356
- actor.stop()
357
- })
358
- })
359
-
360
- describe('polling_status state', () => {
361
- it('STATUS_CONFIRMED transitions to complete', () => {
362
- const actor = createActor(swapMachine)
363
- actor.start()
364
- actor.send({ type: 'SET_SELL_AMOUNT', amount: '1', amountBaseUnit: '1000000000000000000' })
365
- actor.send({ type: 'FETCH_QUOTE' })
366
- actor.send({ type: 'QUOTE_SUCCESS', quote: TEST_QUOTE_NO_APPROVAL })
367
- actor.send({ type: 'EXECUTE_SUCCESS', txHash: '0xTxHash' })
368
- actor.send({ type: 'STATUS_CONFIRMED' })
369
- expect(actor.getSnapshot().value).toBe('complete')
370
- actor.stop()
371
- })
372
-
373
- it('STATUS_FAILED transitions to error', () => {
374
- const actor = createActor(swapMachine)
375
- actor.start()
376
- actor.send({ type: 'SET_SELL_AMOUNT', amount: '1', amountBaseUnit: '1000000000000000000' })
377
- actor.send({ type: 'FETCH_QUOTE' })
378
- actor.send({ type: 'QUOTE_SUCCESS', quote: TEST_QUOTE_NO_APPROVAL })
379
- actor.send({ type: 'EXECUTE_SUCCESS', txHash: '0xTxHash' })
380
- actor.send({ type: 'STATUS_FAILED', error: 'Transaction reverted' })
381
- expect(actor.getSnapshot().value).toBe('error')
382
- expect(actor.getSnapshot().context.error).toBe('Transaction reverted')
383
- actor.stop()
384
- })
385
- })
386
-
387
- describe('error state', () => {
388
- it('RETRY transitions to executing and increments retryCount', () => {
389
- const actor = createActor(swapMachine)
390
- actor.start()
391
- actor.send({ type: 'SET_SELL_AMOUNT', amount: '1', amountBaseUnit: '1000000000000000000' })
392
- actor.send({ type: 'FETCH_QUOTE' })
393
- actor.send({ type: 'QUOTE_SUCCESS', quote: TEST_QUOTE_NO_APPROVAL })
394
- actor.send({ type: 'EXECUTE_ERROR', error: 'Gas too low' })
395
- expect(actor.getSnapshot().context.retryCount).toBe(0)
396
- actor.send({ type: 'RETRY' })
397
- expect(actor.getSnapshot().value).toBe('executing')
398
- expect(actor.getSnapshot().context.retryCount).toBe(1)
399
- expect(actor.getSnapshot().context.error).toBeNull()
400
- actor.stop()
401
- })
402
-
403
- it('RETRY works up to 3 times then stays in error', () => {
404
- const actor = createActor(swapMachine)
405
- actor.start()
406
- actor.send({ type: 'SET_SELL_AMOUNT', amount: '1', amountBaseUnit: '1000000000000000000' })
407
- actor.send({ type: 'FETCH_QUOTE' })
408
- actor.send({ type: 'QUOTE_SUCCESS', quote: TEST_QUOTE_NO_APPROVAL })
409
-
410
- actor.send({ type: 'EXECUTE_ERROR', error: 'fail 1' })
411
- actor.send({ type: 'RETRY' })
412
- expect(actor.getSnapshot().value).toBe('executing')
413
- expect(actor.getSnapshot().context.retryCount).toBe(1)
414
-
415
- actor.send({ type: 'EXECUTE_ERROR', error: 'fail 2' })
416
- actor.send({ type: 'RETRY' })
417
- expect(actor.getSnapshot().value).toBe('executing')
418
- expect(actor.getSnapshot().context.retryCount).toBe(2)
419
-
420
- actor.send({ type: 'EXECUTE_ERROR', error: 'fail 3' })
421
- actor.send({ type: 'RETRY' })
422
- expect(actor.getSnapshot().value).toBe('executing')
423
- expect(actor.getSnapshot().context.retryCount).toBe(3)
424
-
425
- actor.send({ type: 'EXECUTE_ERROR', error: 'fail 4' })
426
- actor.send({ type: 'RETRY' })
427
- expect(actor.getSnapshot().value).toBe('error')
428
- expect(actor.getSnapshot().context.retryCount).toBe(3)
429
- actor.stop()
430
- })
431
-
432
- it('RESET from error transitions to input and resets context', () => {
433
- const actor = createActor(swapMachine)
434
- actor.start()
435
- actor.send({ type: 'SET_SELL_AMOUNT', amount: '1', amountBaseUnit: '1000000000000000000' })
436
- actor.send({ type: 'FETCH_QUOTE' })
437
- actor.send({ type: 'QUOTE_ERROR', error: 'Some error' })
438
- actor.send({ type: 'RESET' })
439
- const ctx = actor.getSnapshot().context
440
- expect(actor.getSnapshot().value).toBe('input')
441
- expect(ctx.error).toBeNull()
442
- expect(ctx.quote).toBeNull()
443
- expect(ctx.txHash).toBeNull()
444
- expect(ctx.retryCount).toBe(0)
445
- actor.stop()
446
- })
447
- })
448
-
449
- describe('complete state', () => {
450
- it('RESET from complete transitions to input and resets context', () => {
451
- const actor = createActor(swapMachine)
452
- actor.start()
453
- actor.send({ type: 'SET_SELL_AMOUNT', amount: '1', amountBaseUnit: '1000000000000000000' })
454
- actor.send({ type: 'FETCH_QUOTE' })
455
- actor.send({ type: 'QUOTE_SUCCESS', quote: TEST_QUOTE_NO_APPROVAL })
456
- actor.send({ type: 'EXECUTE_SUCCESS', txHash: '0xTxHash' })
457
- actor.send({ type: 'STATUS_CONFIRMED' })
458
- expect(actor.getSnapshot().value).toBe('complete')
459
- actor.send({ type: 'RESET' })
460
- const ctx = actor.getSnapshot().context
461
- expect(actor.getSnapshot().value).toBe('input')
462
- expect(ctx.txHash).toBeNull()
463
- expect(ctx.quote).toBeNull()
464
- expect(ctx.retryCount).toBe(0)
465
- actor.stop()
466
- })
467
- })
468
-
469
- describe('createInitialContext', () => {
470
- it('returns default context without input', () => {
471
- const ctx = createInitialContext()
472
- expect(ctx.sellAsset.symbol).toBe('ETH')
473
- expect(ctx.buyAsset.symbol).toBe('USDC')
474
- expect(ctx.slippage).toBe('0.5')
475
- expect(ctx.chainType).toBe('evm')
476
- })
477
-
478
- it('accepts custom sell/buy assets', () => {
479
- const ctx = createInitialContext({ sellAsset: TEST_BTC, buyAsset: TEST_ETH })
480
- expect(ctx.sellAsset.symbol).toBe('BTC')
481
- expect(ctx.buyAsset.symbol).toBe('ETH')
482
- expect(ctx.chainType).toBe('utxo')
483
- })
484
-
485
- it('accepts custom slippage', () => {
486
- const ctx = createInitialContext({ slippage: '2.0' })
487
- expect(ctx.slippage).toBe('2.0')
488
- })
489
- })
490
-
491
- describe('full happy path — no approval', () => {
492
- it('idle → input → quoting → executing → polling_status → complete → input', () => {
493
- const actor = createActor(swapMachine)
494
- actor.start()
495
- expect(actor.getSnapshot().value).toBe('input')
496
-
497
- actor.send({ type: 'SET_SELL_AMOUNT', amount: '1', amountBaseUnit: '1000000000000000000' })
498
- actor.send({ type: 'FETCH_QUOTE' })
499
- expect(actor.getSnapshot().value).toBe('quoting')
500
-
501
- actor.send({ type: 'QUOTE_SUCCESS', quote: TEST_QUOTE_NO_APPROVAL })
502
- expect(actor.getSnapshot().value).toBe('executing')
503
-
504
- actor.send({ type: 'EXECUTE_SUCCESS', txHash: '0xHash' })
505
- expect(actor.getSnapshot().value).toBe('polling_status')
506
-
507
- actor.send({ type: 'STATUS_CONFIRMED' })
508
- expect(actor.getSnapshot().value).toBe('complete')
509
-
510
- actor.send({ type: 'RESET' })
511
- expect(actor.getSnapshot().value).toBe('input')
512
- actor.stop()
513
- })
514
- })
515
-
516
- describe('full happy path — with approval', () => {
517
- it('idle → input → quoting → approval_needed → approving → executing → polling_status → complete', () => {
518
- const actor = createActor(swapMachine)
519
- actor.start()
520
- expect(actor.getSnapshot().value).toBe('input')
521
-
522
- actor.send({ type: 'SET_SELL_ASSET', asset: TEST_USDC })
523
- actor.send({ type: 'SET_SELL_AMOUNT', amount: '1', amountBaseUnit: '1000000' })
524
- actor.send({ type: 'FETCH_QUOTE' })
525
- expect(actor.getSnapshot().value).toBe('quoting')
526
-
527
- actor.send({ type: 'QUOTE_SUCCESS', quote: TEST_QUOTE_WITH_APPROVAL })
528
- expect(actor.getSnapshot().value).toBe('approval_needed')
529
-
530
- actor.send({ type: 'APPROVE' })
531
- expect(actor.getSnapshot().value).toBe('approving')
532
-
533
- actor.send({ type: 'APPROVAL_SUCCESS', txHash: '0xApproval' })
534
- expect(actor.getSnapshot().value).toBe('executing')
535
-
536
- actor.send({ type: 'EXECUTE_SUCCESS', txHash: '0xSwap' })
537
- expect(actor.getSnapshot().value).toBe('polling_status')
538
-
539
- actor.send({ type: 'STATUS_CONFIRMED' })
540
- expect(actor.getSnapshot().value).toBe('complete')
541
- actor.stop()
542
- })
543
- })
544
- })
@@ -1,124 +0,0 @@
1
- import { describe, expect, it } from 'vitest'
2
-
3
- import type { QuoteResponse, SwapperName } from '../../types'
4
- import type { SwapMachineContext, SwapMachineEvent } from '../types'
5
-
6
- describe('SwapMachine Types', () => {
7
- it('SwapMachineContext type is structurally valid', () => {
8
- const context: SwapMachineContext = {
9
- sellAsset: {
10
- assetId: 'eip155:1/slip44:60',
11
- chainId: 'eip155:1',
12
- symbol: 'ETH',
13
- name: 'Ethereum',
14
- precision: 18,
15
- },
16
- buyAsset: {
17
- assetId: 'eip155:1/erc20:0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48',
18
- chainId: 'eip155:1',
19
- symbol: 'USDC',
20
- name: 'USD Coin',
21
- precision: 6,
22
- },
23
- sellAmount: '1.0',
24
- sellAmountBaseUnit: '1000000000000000000',
25
- selectedRate: null,
26
- quote: null,
27
- txHash: null,
28
- approvalTxHash: null,
29
- error: null,
30
- errorSource: null,
31
- retryCount: 0,
32
- chainType: 'evm',
33
- slippage: '0.5',
34
- walletAddress: undefined,
35
- effectiveReceiveAddress: '0x1234567890abcdef1234567890abcdef12345678',
36
- isSellAssetEvm: true,
37
- isSellAssetUtxo: false,
38
- isSellAssetSolana: false,
39
- isBuyAssetEvm: true,
40
- }
41
-
42
- expect(context.sellAsset.symbol).toBe('ETH')
43
- expect(context.buyAsset.symbol).toBe('USDC')
44
- expect(context.chainType).toBe('evm')
45
- expect(context.retryCount).toBe(0)
46
- })
47
-
48
- it('SwapMachineEvent discriminated union covers all event types', () => {
49
- const events: SwapMachineEvent[] = [
50
- {
51
- type: 'SET_SELL_ASSET',
52
- asset: {
53
- assetId: 'eip155:1/slip44:60',
54
- chainId: 'eip155:1',
55
- symbol: 'ETH',
56
- name: 'Ethereum',
57
- precision: 18,
58
- },
59
- },
60
- {
61
- type: 'SET_BUY_ASSET',
62
- asset: {
63
- assetId: 'eip155:1/erc20:0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48',
64
- chainId: 'eip155:1',
65
- symbol: 'USDC',
66
- name: 'USD Coin',
67
- precision: 6,
68
- },
69
- },
70
- { type: 'SET_SELL_AMOUNT', amount: '1.0', amountBaseUnit: '1000000000000000000' },
71
- { type: 'SET_SLIPPAGE', slippage: '1.0' },
72
- {
73
- type: 'SELECT_RATE',
74
- rate: {
75
- swapperName: 'THORChain' as SwapperName,
76
- rate: '1500',
77
- buyAmountCryptoBaseUnit: '1500000000',
78
- sellAmountCryptoBaseUnit: '1000000000000000000',
79
- steps: 1,
80
- affiliateBps: '0',
81
- },
82
- },
83
- { type: 'FETCH_QUOTE' },
84
- { type: 'QUOTE_SUCCESS', quote: {} as QuoteResponse },
85
- { type: 'QUOTE_ERROR', error: 'No quotes available' },
86
- { type: 'APPROVE' },
87
- { type: 'APPROVAL_SUCCESS', txHash: '0xabc' },
88
- { type: 'APPROVAL_ERROR', error: 'Approval failed' },
89
- { type: 'EXECUTE_SUCCESS', txHash: '0xdef' },
90
- { type: 'EXECUTE_ERROR', error: 'Execution failed' },
91
- { type: 'STATUS_CONFIRMED' },
92
- { type: 'STATUS_FAILED', error: 'Transaction reverted' },
93
- { type: 'RETRY' },
94
- { type: 'RESET' },
95
- { type: 'SET_WALLET_ADDRESS', address: '0x1234' },
96
- { type: 'SET_RECEIVE_ADDRESS', address: '0x5678' },
97
- {
98
- type: 'UPDATE_CHAIN_INFO',
99
- chainType: 'evm',
100
- isSellAssetEvm: true,
101
- isSellAssetUtxo: false,
102
- isSellAssetSolana: false,
103
- isBuyAssetEvm: true,
104
- },
105
- ]
106
-
107
- expect(events).toHaveLength(20)
108
- const eventTypes = events.map(e => e.type)
109
- expect(eventTypes).toContain('SET_SELL_ASSET')
110
- expect(eventTypes).toContain('RESET')
111
- expect(eventTypes).toContain('UPDATE_CHAIN_INFO')
112
- })
113
-
114
- it('chainType supports all chain types', () => {
115
- const chainTypes: SwapMachineContext['chainType'][] = [
116
- 'evm',
117
- 'utxo',
118
- 'solana',
119
- 'cosmos',
120
- 'other',
121
- ]
122
- expect(chainTypes).toHaveLength(5)
123
- })
124
- })