@0xsequence/marketplace-sdk 0.4.5 → 0.4.7

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 (291) hide show
  1. package/dist/alien_swap-4GAIV7PA.png +0 -0
  2. package/dist/alien_swap-IZONL4XB.js +8 -0
  3. package/dist/alien_swap-PMYKGY6A.js +8 -0
  4. package/dist/aqua-xyz-HLNZIFE2.js +8 -0
  5. package/dist/aqua-xyz-HLNZIFE2.js.map +1 -0
  6. package/dist/aqua-xyz-JY5QCI5L.js +8 -0
  7. package/dist/aqua-xyz-JY5QCI5L.js.map +1 -0
  8. package/dist/aqua-xyz-WU4JVU2K.png +0 -0
  9. package/dist/aura-CYKEACX2.js +8 -0
  10. package/dist/aura-CYKEACX2.js.map +1 -0
  11. package/dist/aura-HLMWKNSP.js +8 -0
  12. package/dist/aura-HLMWKNSP.js.map +1 -0
  13. package/dist/aura-RITZV42R.png +0 -0
  14. package/dist/blur-2ABQMPTL.png +0 -0
  15. package/dist/blur-MIPRQYJL.js +8 -0
  16. package/dist/blur-MIPRQYJL.js.map +1 -0
  17. package/dist/blur-XDIGHYB7.js +8 -0
  18. package/dist/blur-XDIGHYB7.js.map +1 -0
  19. package/dist/{chunk-SEISACMH.js → chunk-5UCKYAMR.js} +771 -788
  20. package/dist/chunk-5UCKYAMR.js.map +1 -0
  21. package/dist/{chunk-KILOCWY2.js → chunk-6R4G7J6Q.js} +87 -27
  22. package/dist/chunk-6R4G7J6Q.js.map +1 -0
  23. package/dist/{chunk-CKOWM2ZR.js → chunk-AQT3BQ67.js} +9 -9
  24. package/dist/chunk-AQT3BQ67.js.map +1 -0
  25. package/dist/{chunk-KL5JPUPS.js → chunk-FWN2MCLI.js} +18 -12
  26. package/dist/chunk-FWN2MCLI.js.map +1 -0
  27. package/dist/{chunk-PAZ4MQXZ.js → chunk-JEOUQFT3.js} +17 -129
  28. package/dist/chunk-JEOUQFT3.js.map +1 -0
  29. package/dist/chunk-MWDG7UTB.js +132 -0
  30. package/dist/chunk-MWDG7UTB.js.map +1 -0
  31. package/dist/{chunk-KZGDOIZY.js → chunk-R7GVMKMM.js} +154 -76
  32. package/dist/chunk-R7GVMKMM.js.map +1 -0
  33. package/dist/chunk-RK6KYMZM.js +18 -0
  34. package/dist/chunk-RK6KYMZM.js.map +1 -0
  35. package/dist/{chunk-YUETNNZQ.js → chunk-WM4RGBFQ.js} +1 -10
  36. package/dist/{chunk-YUETNNZQ.js.map → chunk-WM4RGBFQ.js.map} +1 -1
  37. package/dist/chunk-XP3WY5AX.js +174 -0
  38. package/dist/chunk-XP3WY5AX.js.map +1 -0
  39. package/dist/{chunk-ZEKRTFBU.js → chunk-YOKGP2EQ.js} +12 -2
  40. package/dist/chunk-YOKGP2EQ.js.map +1 -0
  41. package/dist/{chunk-HTFBQVLV.js → chunk-ZEH4JI2U.js} +2 -2
  42. package/dist/chunk-ZUEQGPLO.js +302 -0
  43. package/dist/chunk-ZUEQGPLO.js.map +1 -0
  44. package/dist/coinbase-MIJPE653.js +8 -0
  45. package/dist/coinbase-MIJPE653.js.map +1 -0
  46. package/dist/coinbase-MZUBBEC4.png +0 -0
  47. package/dist/coinbase-T24XHLQL.js +8 -0
  48. package/dist/coinbase-T24XHLQL.js.map +1 -0
  49. package/dist/{create-config-DMBOGsJp.d.ts → create-config-D5WqfUft.d.ts} +2 -2
  50. package/dist/element-GHIPFSB6.png +0 -0
  51. package/dist/element-MWATR3ON.js +8 -0
  52. package/dist/element-MWATR3ON.js.map +1 -0
  53. package/dist/element-X45NH4D7.js +8 -0
  54. package/dist/element-X45NH4D7.js.map +1 -0
  55. package/dist/foundation-BDJUT6CK.js +8 -0
  56. package/dist/foundation-BDJUT6CK.js.map +1 -0
  57. package/dist/foundation-FJKIXLS5.png +0 -0
  58. package/dist/foundation-Z6D6U74V.js +8 -0
  59. package/dist/foundation-Z6D6U74V.js.map +1 -0
  60. package/dist/index.d.ts +6 -5
  61. package/dist/index.js +12 -6
  62. package/dist/looks-rare-B6G3OQAP.png +0 -0
  63. package/dist/looks-rare-LBHT6EXZ.js +8 -0
  64. package/dist/looks-rare-LBHT6EXZ.js.map +1 -0
  65. package/dist/looks-rare-STS6IKI4.js +8 -0
  66. package/dist/looks-rare-STS6IKI4.js.map +1 -0
  67. package/dist/magic-eden-HA3X3P2O.png +0 -0
  68. package/dist/magic-eden-RMZ24554.js +8 -0
  69. package/dist/magic-eden-RMZ24554.js.map +1 -0
  70. package/dist/magic-eden-YMTLPKLE.js +8 -0
  71. package/dist/magic-eden-YMTLPKLE.js.map +1 -0
  72. package/dist/manifold-I4NT4V5L.png +0 -0
  73. package/dist/manifold-L7FLFDRO.js +8 -0
  74. package/dist/manifold-L7FLFDRO.js.map +1 -0
  75. package/dist/manifold-YIUSABCZ.js +8 -0
  76. package/dist/manifold-YIUSABCZ.js.map +1 -0
  77. package/dist/{marketplace-config-0Rft6_Hv.d.ts → marketplace-config-C_fDWzz0.d.ts} +2 -2
  78. package/dist/marketplace.gen-B8S8fflj.d.ts +390 -0
  79. package/dist/mintify-ARDASD5Z.js +8 -0
  80. package/dist/mintify-ARDASD5Z.js.map +1 -0
  81. package/dist/mintify-OLOGFTWQ.png +0 -0
  82. package/dist/mintify-TSZA3SQT.js +8 -0
  83. package/dist/mintify-TSZA3SQT.js.map +1 -0
  84. package/dist/nftx-67RX7ZV6.js +8 -0
  85. package/dist/nftx-67RX7ZV6.js.map +1 -0
  86. package/dist/nftx-DJIV3PYG.png +0 -0
  87. package/dist/nftx-KVJ3T3G2.js +8 -0
  88. package/dist/nftx-KVJ3T3G2.js.map +1 -0
  89. package/dist/okx-MOA2EFVR.js +8 -0
  90. package/dist/okx-MOA2EFVR.js.map +1 -0
  91. package/dist/okx-WNQRV3WE.png +0 -0
  92. package/dist/okx-WQA7H7EM.js +8 -0
  93. package/dist/okx-WQA7H7EM.js.map +1 -0
  94. package/dist/open-sea-2HWFM4P6.js +8 -0
  95. package/dist/open-sea-2HWFM4P6.js.map +1 -0
  96. package/dist/open-sea-C57XWTAR.png +0 -0
  97. package/dist/open-sea-GESD6S2M.js +8 -0
  98. package/dist/open-sea-GESD6S2M.js.map +1 -0
  99. package/dist/rarible-GHMFCPBT.js +8 -0
  100. package/dist/rarible-GHMFCPBT.js.map +1 -0
  101. package/dist/rarible-QNNAZZQC.js +8 -0
  102. package/dist/rarible-QNNAZZQC.js.map +1 -0
  103. package/dist/rarible-ZCE7U3I5.png +0 -0
  104. package/dist/react/_internal/api/index.d.ts +4 -3
  105. package/dist/react/_internal/api/index.js +6 -2
  106. package/dist/react/_internal/index.d.ts +6 -7
  107. package/dist/react/_internal/index.js +10 -4
  108. package/dist/react/_internal/wagmi/index.d.ts +3 -4
  109. package/dist/react/_internal/wagmi/index.js +2 -2
  110. package/dist/react/hooks/index.d.ts +779 -116
  111. package/dist/react/hooks/index.js +17 -7
  112. package/dist/react/index.d.ts +7 -8
  113. package/dist/react/index.js +19 -10
  114. package/dist/react/ssr/index.js +8 -30
  115. package/dist/react/ssr/index.js.map +1 -1
  116. package/dist/react/ui/components/collectible-card/index.css.map +1 -0
  117. package/dist/react/ui/components/{index.d.ts → collectible-card/index.d.ts} +4 -3
  118. package/dist/react/ui/components/collectible-card/index.js +29 -0
  119. package/dist/react/ui/components/collectible-card/index.js.map +1 -0
  120. package/dist/react/ui/components/marketplace-logos/index.d.ts +26 -0
  121. package/dist/react/ui/components/marketplace-logos/index.js +46 -0
  122. package/dist/react/ui/components/marketplace-logos/index.js.map +1 -0
  123. package/dist/react/ui/icons/index.js +0 -8
  124. package/dist/react/ui/icons/index.js.map +1 -1
  125. package/dist/react/ui/index.d.ts +6 -5
  126. package/dist/react/ui/index.js +11 -10
  127. package/dist/react/ui/modals/_internal/components/actionModal/index.d.ts +4 -2
  128. package/dist/react/ui/modals/_internal/components/actionModal/index.js +9 -7
  129. package/dist/{marketplace.gen-jdKqutnd.d.ts → sdk-config-BXVH8PS2.d.ts} +91 -16
  130. package/dist/sequence-JAFBEQNI.png +0 -0
  131. package/dist/sequence-OIPVNE5P.js +8 -0
  132. package/dist/sequence-OIPVNE5P.js.map +1 -0
  133. package/dist/sequence-QNNBU34G.js +8 -0
  134. package/dist/sequence-QNNBU34G.js.map +1 -0
  135. package/dist/{services-C2O-7p_M.d.ts → services-CdXAIjt1.d.ts} +1 -2
  136. package/dist/sudo-swap-D3FAP7W4.js +8 -0
  137. package/dist/sudo-swap-D3FAP7W4.js.map +1 -0
  138. package/dist/sudo-swap-XNJ3BIUD.js +8 -0
  139. package/dist/sudo-swap-XNJ3BIUD.js.map +1 -0
  140. package/dist/sudo-swap-Y6GICQTL.png +0 -0
  141. package/dist/super-rare-VIUS3P6B.js +8 -0
  142. package/dist/super-rare-VIUS3P6B.js.map +1 -0
  143. package/dist/super-rare-WWXZ3MQL.png +0 -0
  144. package/dist/super-rare-YPU3Y7EF.js +8 -0
  145. package/dist/super-rare-YPU3Y7EF.js.map +1 -0
  146. package/dist/types/index.d.ts +3 -4
  147. package/dist/types/index.js +9 -6
  148. package/dist/types-eX4P9xju.d.ts +70 -0
  149. package/dist/utils/index.d.ts +16 -3
  150. package/dist/utils/index.js +9 -2
  151. package/dist/x2y2-CXOXXZKS.png +0 -0
  152. package/dist/x2y2-G2SXS5VR.js +8 -0
  153. package/dist/x2y2-G2SXS5VR.js.map +1 -0
  154. package/dist/x2y2-GKWTQTPB.js +8 -0
  155. package/dist/x2y2-GKWTQTPB.js.map +1 -0
  156. package/dist/zora-3DPG4KAY.png +0 -0
  157. package/dist/zora-JUDT67NX.js +8 -0
  158. package/dist/zora-JUDT67NX.js.map +1 -0
  159. package/dist/zora-Z5VR477F.js +8 -0
  160. package/dist/zora-Z5VR477F.js.map +1 -0
  161. package/package.json +33 -19
  162. package/src/react/_internal/api/__mocks__/marketplace.msw.ts +218 -0
  163. package/src/react/_internal/api/marketplace.gen.ts +125 -42
  164. package/src/react/_internal/api/query-keys.ts +8 -0
  165. package/src/react/_internal/api/zod-schema.ts +33 -0
  166. package/src/react/_internal/test-utils.tsx +68 -0
  167. package/src/react/_internal/types.ts +51 -1
  168. package/src/react/_internal/{transaction-machine/utils.ts → utils.ts} +1 -1
  169. package/src/react/_internal/{transaction-machine → wallet}/wallet.ts +2 -2
  170. package/src/react/hooks/index.ts +2 -0
  171. package/src/react/hooks/options/__mocks__/marketplaceConfig.msw.ts +77 -0
  172. package/src/react/hooks/options/__tests__/marketplaceConfigOptions.test.tsx +144 -0
  173. package/src/react/hooks/useCancelOrder.tsx +22 -3
  174. package/src/react/hooks/useCancelTransactionSteps.tsx +8 -10
  175. package/src/react/hooks/useCurrencies.tsx +14 -19
  176. package/src/react/hooks/useCurrency.tsx +10 -1
  177. package/src/react/hooks/useCurrencyBalance.tsx +38 -36
  178. package/src/react/hooks/useGetReceiptFromHash.tsx +1 -1
  179. package/src/react/hooks/useListCollectibleActivities.tsx +57 -0
  180. package/src/react/hooks/useListCollectionActivities.tsx +57 -0
  181. package/src/react/ssr/create-ssr-client.ts +1 -1
  182. package/src/react/ui/components/_internals/action-button/ActionButton.tsx +1 -1
  183. package/src/react/ui/components/_internals/custom-select/__tests__/CustomSelect.test.tsx +89 -0
  184. package/src/react/ui/components/collectible-card/CollectibleCard.tsx +1 -1
  185. package/src/react/ui/components/collectible-card/index.ts +1 -0
  186. package/src/react/ui/components/marketplace-logos/index.ts +23 -0
  187. package/src/react/ui/components/marketplace-logos/marketplace-logos.tsx +111 -0
  188. package/src/react/ui/images/marketplaces/alien_swap.png +0 -0
  189. package/src/react/ui/images/marketplaces/aqua-xyz.png +0 -0
  190. package/src/react/ui/images/marketplaces/aura.png +0 -0
  191. package/src/react/ui/images/marketplaces/blur.png +0 -0
  192. package/src/react/ui/images/marketplaces/coinbase.png +0 -0
  193. package/src/react/ui/images/marketplaces/element.png +0 -0
  194. package/src/react/ui/images/marketplaces/foundation.png +0 -0
  195. package/src/react/ui/images/marketplaces/looks-rare.png +0 -0
  196. package/src/react/ui/images/marketplaces/magic-eden.png +0 -0
  197. package/src/react/ui/images/marketplaces/manifold.png +0 -0
  198. package/src/react/ui/images/marketplaces/mintify.png +0 -0
  199. package/src/react/ui/images/marketplaces/nftx.png +0 -0
  200. package/src/react/ui/images/marketplaces/okx.png +0 -0
  201. package/src/react/ui/images/marketplaces/open-sea.png +0 -0
  202. package/src/react/ui/images/marketplaces/rarible.png +0 -0
  203. package/src/react/ui/images/marketplaces/sequence.png +0 -0
  204. package/src/react/ui/images/marketplaces/sudo-swap.png +0 -0
  205. package/src/react/ui/images/marketplaces/super-rare.png +0 -0
  206. package/src/react/ui/images/marketplaces/x2y2.png +0 -0
  207. package/src/react/ui/images/marketplaces/zora.png +0 -0
  208. package/src/react/ui/modals/BuyModal/Modal.tsx +3 -1
  209. package/src/react/ui/modals/BuyModal/hooks/useBuyCollectable.ts +8 -7
  210. package/src/react/ui/modals/BuyModal/hooks/useCheckoutOptions.ts +1 -1
  211. package/src/react/ui/modals/BuyModal/modals/CheckoutModal.tsx +12 -3
  212. package/src/react/ui/modals/CreateListingModal/Modal.tsx +2 -1
  213. package/src/react/ui/modals/CreateListingModal/__tests__/Modal.test.tsx +208 -0
  214. package/src/react/ui/modals/CreateListingModal/hooks/useCreateListing.tsx +11 -2
  215. package/src/react/ui/modals/CreateListingModal/hooks/useGetTokenApproval.ts +1 -1
  216. package/src/react/ui/modals/CreateListingModal/hooks/useTransactionSteps.tsx +8 -22
  217. package/src/react/ui/modals/CreateListingModal/store.ts +29 -19
  218. package/src/react/ui/modals/MakeOfferModal/Modal.tsx +17 -24
  219. package/src/react/ui/modals/MakeOfferModal/__tests__/Modal.test.tsx +199 -0
  220. package/src/react/ui/modals/MakeOfferModal/hooks/useGetTokenApproval.tsx +2 -2
  221. package/src/react/ui/modals/MakeOfferModal/hooks/useMakeOffer.tsx +19 -3
  222. package/src/react/ui/modals/MakeOfferModal/hooks/useTransactionSteps.tsx +9 -23
  223. package/src/react/ui/modals/MakeOfferModal/store.ts +31 -20
  224. package/src/react/ui/modals/SellModal/Modal.tsx +1 -0
  225. package/src/react/ui/modals/SellModal/__tests__/Modal.test.tsx +192 -0
  226. package/src/react/ui/modals/SellModal/hooks/useGetTokenApproval.tsx +4 -4
  227. package/src/react/ui/modals/SellModal/hooks/useSell.tsx +3 -3
  228. package/src/react/ui/modals/SellModal/hooks/useTransactionSteps.tsx +14 -26
  229. package/src/react/ui/modals/TransferModal/_views/enterWalletAddress/useHandleTransfer.tsx +1 -1
  230. package/src/react/ui/modals/_internal/components/actionModal/ActionModal.tsx +3 -1
  231. package/src/react/ui/modals/_internal/components/actionModal/ErrorModal.tsx +10 -2
  232. package/src/react/ui/modals/_internal/components/actionModal/LoadingModal.tsx +7 -1
  233. package/src/react/ui/modals/_internal/components/currencyOptionsSelect/__tests__/index.test.tsx +159 -0
  234. package/src/react/ui/modals/_internal/components/currencyOptionsSelect/index.tsx +5 -2
  235. package/src/react/ui/modals/_internal/components/expirationDateSelect/index.tsx +11 -1
  236. package/src/react/ui/modals/_internal/components/priceInput/__tests__/index.test.tsx +124 -0
  237. package/src/react/ui/modals/_internal/components/priceInput/index.tsx +51 -45
  238. package/src/react/ui/modals/_internal/components/switchChainModal/__tests__/SwitchChainModal.test.tsx +221 -0
  239. package/src/react/ui/modals/_internal/components/switchChainModal/index.tsx +24 -5
  240. package/src/react/ui/modals/_internal/components/switchChainModal/store.ts +2 -2
  241. package/src/react/ui/modals/_internal/components/transactionPreview/index.tsx +16 -2
  242. package/src/react/ui/modals/_internal/components/transactionPreview/useTransactionPreviewTitle.tsx +1 -1
  243. package/src/react/ui/modals/_internal/components/transactionStatusModal/__tests__/TransactionStatusModal.test.tsx +147 -0
  244. package/src/react/ui/modals/_internal/components/transactionStatusModal/__tests__/utils.test.ts +218 -0
  245. package/src/react/ui/modals/_internal/components/transactionStatusModal/index.tsx +28 -11
  246. package/src/react/ui/modals/_internal/components/transactionStatusModal/store.ts +1 -1
  247. package/src/react/ui/modals/_internal/components/transactionStatusModal/util/getFormattedType.ts +1 -1
  248. package/src/react/ui/modals/_internal/components/transactionStatusModal/util/getMessage.ts +3 -3
  249. package/src/react/ui/modals/_internal/components/transactionStatusModal/util/getTitle.ts +1 -1
  250. package/src/react/ui/modals/modal-provider.tsx +0 -2
  251. package/src/types/marketplace-config.ts +1 -1
  252. package/src/utils/__tests__/address.test.ts +65 -0
  253. package/src/utils/__tests__/date.test.ts +31 -0
  254. package/src/utils/__tests__/get-public-rpc-client.test.ts +109 -0
  255. package/src/utils/__tests__/getMarketplaceDetails.test.ts +134 -0
  256. package/src/utils/__tests__/price.test.ts +42 -0
  257. package/src/utils/get-public-rpc-client.ts +6 -0
  258. package/src/utils/getMarketplaceDetails.ts +110 -0
  259. package/src/utils/index.ts +1 -0
  260. package/tsconfig.tsbuildinfo +1 -1
  261. package/vitest.config.js +10 -0
  262. package/dist/chunk-6WB4GCCJ.js +0 -38
  263. package/dist/chunk-6WB4GCCJ.js.map +0 -1
  264. package/dist/chunk-CKOWM2ZR.js.map +0 -1
  265. package/dist/chunk-CP2IVRMX.js +0 -85
  266. package/dist/chunk-CP2IVRMX.js.map +0 -1
  267. package/dist/chunk-FT3J32ZV.js +0 -86
  268. package/dist/chunk-FT3J32ZV.js.map +0 -1
  269. package/dist/chunk-KILOCWY2.js.map +0 -1
  270. package/dist/chunk-KL5JPUPS.js.map +0 -1
  271. package/dist/chunk-KZGDOIZY.js.map +0 -1
  272. package/dist/chunk-MJ4YU7RW.js +0 -2
  273. package/dist/chunk-PAZ4MQXZ.js.map +0 -1
  274. package/dist/chunk-SEISACMH.js.map +0 -1
  275. package/dist/chunk-ZEKRTFBU.js.map +0 -1
  276. package/dist/react/ui/components/index.css.map +0 -1
  277. package/dist/react/ui/components/index.js +0 -28
  278. package/dist/sdk-config-xWkdBdrL.d.ts +0 -24
  279. package/dist/types-DZb7GsfL.d.ts +0 -28
  280. package/src/react/_internal/transaction-machine/execute-transaction.ts +0 -676
  281. package/src/react/_internal/transaction-machine/useTransactionMachine.ts +0 -140
  282. package/src/react/ui/components/index.ts +0 -1
  283. package/src/react/ui/modals/Account/index.tsx +0 -29
  284. package/src/react/ui/modals/_internal/components/priceInput/hooks/useBalanceCheck.ts +0 -67
  285. package/src/react/ui/modals/_internal/components/priceInput/hooks/usePriceInput.ts +0 -54
  286. /package/dist/{chunk-MJ4YU7RW.js.map → alien_swap-IZONL4XB.js.map} +0 -0
  287. /package/dist/{react/ui/components/index.js.map → alien_swap-PMYKGY6A.js.map} +0 -0
  288. /package/dist/{chunk-HTFBQVLV.js.map → chunk-ZEH4JI2U.js.map} +0 -0
  289. /package/dist/react/ui/components/{index.css → collectible-card/index.css} +0 -0
  290. /package/src/react/_internal/{transaction-machine/logger.ts → logger.ts} +0 -0
  291. /package/src/react/_internal/{transaction-machine → wallet}/useWallet.ts +0 -0
@@ -0,0 +1,124 @@
1
+ import '@testing-library/jest-dom/vitest';
2
+
3
+ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
4
+ import { observable } from '@legendapp/state';
5
+ import PriceInput from '..';
6
+ import type { Currency, Price } from '../../../../../../../types';
7
+ import {
8
+ render,
9
+ screen,
10
+ cleanup,
11
+ fireEvent,
12
+ } from '../../../../../../_internal/test-utils';
13
+
14
+ vi.mock('../hooks/usePriceInput', () => ({
15
+ usePriceInput: vi.fn(({ onPriceChange }) => ({
16
+ value: '0',
17
+ handlePriceChange: (value: string) => {
18
+ try {
19
+ if (value === '0' || !value || isNaN(Number(value))) {
20
+ return;
21
+ }
22
+ onPriceChange?.(value);
23
+ } catch {
24
+ return;
25
+ }
26
+ },
27
+ })),
28
+ }));
29
+
30
+ // Mock useCurrencyBalance hook
31
+ vi.mock('../../../../../hooks/useCurrencyBalance', () => ({
32
+ useCurrencyBalance: vi.fn(() => ({
33
+ data: { value: 100n },
34
+ isSuccess: true,
35
+ })),
36
+ }));
37
+
38
+ // Mock currency data
39
+ const MOCK_CURRENCY: Currency = {
40
+ symbol: 'USDC',
41
+ contractAddress: '0x1234' as `0x${string}`,
42
+ chainId: 1,
43
+ name: 'USD Coin',
44
+ decimals: 6,
45
+ imageUrl: 'https://example.com/usdc.png',
46
+ exchangeRate: 1,
47
+ defaultChainCurrency: false,
48
+ nativeCurrency: false,
49
+ createdAt: new Date().toISOString(),
50
+ updatedAt: new Date().toISOString(),
51
+ };
52
+
53
+ // Mock price data
54
+ const createMockPrice = (amount = '0'): Price => ({
55
+ amountRaw: amount,
56
+ currency: MOCK_CURRENCY,
57
+ });
58
+
59
+ describe('PriceInput', () => {
60
+ const defaultProps = {
61
+ collectionAddress: '0xCollection' as `0x${string}`,
62
+ chainId: '1',
63
+ $price: observable<Price | undefined>(createMockPrice()),
64
+ includeNativeCurrency: false,
65
+ secondCurrencyAsDefault: false,
66
+ };
67
+
68
+ beforeEach(() => {
69
+ cleanup();
70
+ vi.clearAllMocks();
71
+ });
72
+ afterEach(() => {
73
+ cleanup();
74
+ });
75
+
76
+ it('should render price input', () => {
77
+ render(<PriceInput {...defaultProps} />);
78
+ expect(screen.getByRole('textbox', { name: /price/i })).toBeInTheDocument();
79
+ });
80
+
81
+ it('should handle price changes', () => {
82
+ const onPriceChange = vi.fn();
83
+ const { getByRole } = render(
84
+ <PriceInput {...defaultProps} onPriceChange={onPriceChange} />,
85
+ );
86
+
87
+ const input = getByRole('textbox', { name: /price/i });
88
+ fireEvent.change(input, { target: { value: '100' } });
89
+
90
+ expect(onPriceChange).toHaveBeenCalled();
91
+ });
92
+
93
+ it('should trigger callback when balance is insufficient', () => {
94
+ const checkBalance = {
95
+ enabled: true,
96
+ callback: vi.fn(),
97
+ };
98
+ const price$ = observable<Price | undefined>(createMockPrice());
99
+ render(
100
+ <PriceInput
101
+ {...defaultProps}
102
+ $price={price$}
103
+ checkBalance={checkBalance}
104
+ />,
105
+ );
106
+
107
+ expect(screen.queryByText('Insufficient balance')).not.toBeInTheDocument();
108
+
109
+ const input = screen.getByRole('textbox', { name: /price/i });
110
+ fireEvent.change(input, { target: { value: '2000' } });
111
+
112
+ expect(checkBalance.callback).toHaveBeenCalledWith(false);
113
+ });
114
+
115
+ it('should not call onPriceChange when amount is zero', () => {
116
+ const onPriceChange = vi.fn();
117
+ render(<PriceInput {...defaultProps} onPriceChange={onPriceChange} />);
118
+
119
+ const input = screen.getByRole('textbox', { name: /price/i });
120
+ fireEvent.change(input, { target: { value: '0' } });
121
+
122
+ expect(onPriceChange).not.toHaveBeenCalled();
123
+ });
124
+ });
@@ -1,21 +1,21 @@
1
1
  import { Box, NumericInput, Text } from '@0xsequence/design-system';
2
2
  import type { Observable } from '@legendapp/state';
3
- import { observer } from '@legendapp/state/react';
4
- import { useCallback, useMemo } from 'react';
5
- import { type Hex } from 'viem';
3
+ import { type Hex, parseUnits } from 'viem';
6
4
  import { useAccount } from 'wagmi';
7
5
  import type { Price } from '../../../../../../types';
8
6
  import CurrencyImage from '../currencyImage';
9
7
  import CurrencyOptionsSelect from '../currencyOptionsSelect';
10
8
  import { priceInputCurrencyImage, priceInputWrapper } from './styles.css';
11
- import { usePriceInput } from './hooks/usePriceInput';
12
- import { useBalanceCheck } from './hooks/useBalanceCheck';
9
+ import { use$ } from '@legendapp/state/react';
10
+ import { useCurrencyBalance } from '../../../../../hooks/useCurrencyBalance';
11
+ import { useState } from 'react';
13
12
 
14
13
  type PriceInputProps = {
15
14
  collectionAddress: Hex;
16
15
  chainId: string;
17
16
  secondCurrencyAsDefault?: boolean;
18
17
  $price: Observable<Price | undefined>;
18
+ includeNativeCurrency?: boolean;
19
19
  onPriceChange?: () => void;
20
20
  checkBalance?: {
21
21
  enabled: boolean;
@@ -23,56 +23,52 @@ type PriceInputProps = {
23
23
  };
24
24
  };
25
25
 
26
- const PriceInput = observer(function PriceInput({
26
+ export default function PriceInput({
27
27
  chainId,
28
28
  collectionAddress,
29
29
  $price,
30
30
  onPriceChange,
31
31
  checkBalance,
32
32
  secondCurrencyAsDefault,
33
+ includeNativeCurrency,
33
34
  }: PriceInputProps) {
34
35
  const { address: accountAddress } = useAccount();
35
- const currencyDecimals = $price.currency.decimals.get() || 18;
36
- const currencyAddress = $price.currency.contractAddress.get() as Hex;
36
+ const currencyDecimals = use$($price.currency.decimals);
37
+ const currencyAddress = use$($price.currency.contractAddress);
38
+ const priceAmountRaw = use$($price.amountRaw);
37
39
 
38
- const { value, handlePriceChange } = usePriceInput({
39
- price$: $price,
40
- currencyDecimals,
41
- onPriceChange,
42
- });
43
-
44
- const { balanceError } = useBalanceCheck({
45
- checkBalance,
46
- price$: $price,
47
- currencyAddress,
40
+ const { data: balance, isSuccess: isBalanceSuccess } = useCurrencyBalance({
41
+ currencyAddress: currencyAddress as undefined | Hex,
48
42
  chainId: Number(chainId),
49
- userAddress: accountAddress as Hex,
50
- currencyDecimals,
43
+ userAddress: accountAddress,
51
44
  });
52
45
 
53
- const renderBalanceError = useMemo(() => {
54
- if (!balanceError) return null;
46
+ const balanceError =
47
+ !!checkBalance?.enabled &&
48
+ !!isBalanceSuccess &&
49
+ !!priceAmountRaw &&
50
+ !!currencyDecimals &&
51
+ BigInt(priceAmountRaw) > BigInt(balance?.value || 0n);
55
52
 
56
- return (
57
- <Text
58
- color="negative"
59
- fontSize="xsmall"
60
- fontFamily="body"
61
- fontWeight="semibold"
62
- position="absolute"
63
- style={{ bottom: '-13px' }}
64
- >
65
- {balanceError}
66
- </Text>
67
- );
68
- }, [balanceError]);
53
+ if (checkBalance?.enabled) {
54
+ checkBalance.callback(balanceError);
55
+ }
69
56
 
70
- const handleChange = useCallback(
71
- (event: React.ChangeEvent<HTMLInputElement>) => {
72
- handlePriceChange(event.target.value);
73
- },
74
- [handlePriceChange],
75
- );
57
+ const [value, setValue] = useState('0');
58
+
59
+ const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
60
+ const newValue = event.target.value;
61
+ setValue(newValue);
62
+ try {
63
+ const parsedAmount = parseUnits(newValue, Number(currencyDecimals));
64
+ $price.amountRaw.set(parsedAmount.toString());
65
+ if (onPriceChange && parsedAmount !== 0n) {
66
+ onPriceChange();
67
+ }
68
+ } catch {
69
+ $price.amountRaw.set('0');
70
+ }
71
+ };
76
72
 
77
73
  return (
78
74
  <Box className={priceInputWrapper} position="relative">
@@ -97,6 +93,7 @@ const PriceInput = observer(function PriceInput({
97
93
  collectionAddress={collectionAddress}
98
94
  chainId={chainId}
99
95
  secondCurrencyAsDefault={secondCurrencyAsDefault}
96
+ includeNativeCurrency={includeNativeCurrency}
100
97
  />
101
98
  }
102
99
  value={value}
@@ -104,9 +101,18 @@ const PriceInput = observer(function PriceInput({
104
101
  width="full"
105
102
  />
106
103
 
107
- {renderBalanceError}
104
+ {balanceError && (
105
+ <Text
106
+ color="negative"
107
+ fontSize="xsmall"
108
+ fontFamily="body"
109
+ fontWeight="semibold"
110
+ position="absolute"
111
+ style={{ bottom: '-13px' }}
112
+ >
113
+ Insufficient balance
114
+ </Text>
115
+ )}
108
116
  </Box>
109
117
  );
110
- });
111
-
112
- export default PriceInput;
118
+ }
@@ -0,0 +1,221 @@
1
+ import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest';
2
+ import {
3
+ cleanup,
4
+ render,
5
+ screen,
6
+ fireEvent,
7
+ waitFor,
8
+ } from '../../../../../../_internal/test-utils';
9
+ import * as matchers from '@testing-library/jest-dom/matchers';
10
+ import SwitchChainModal, { useSwitchChainModal } from '../index';
11
+ import { switchChainModal$, initialState } from '../store';
12
+ import type { SwitchChainError } from 'viem';
13
+
14
+ expect.extend(matchers);
15
+
16
+ const mockSwitchChainAsync = vi
17
+ .fn()
18
+ .mockImplementation(() => Promise.resolve({}));
19
+
20
+ vi.mock('wagmi', async () => {
21
+ const actual = await vi.importActual<typeof import('wagmi')>('wagmi');
22
+ return {
23
+ ...(actual ?? {}),
24
+ useSwitchChain: () => ({
25
+ switchChainAsync: mockSwitchChainAsync,
26
+ }),
27
+ };
28
+ });
29
+
30
+ vi.mock('../../../../../_internal', () => ({
31
+ getProviderEl: () => document.body,
32
+ }));
33
+
34
+ describe('SwitchChainModal', () => {
35
+ beforeEach(() => {
36
+ cleanup();
37
+ vi.clearAllMocks();
38
+ vi.resetAllMocks();
39
+
40
+ switchChainModal$.set({
41
+ ...initialState,
42
+ open: initialState.open.bind(switchChainModal$),
43
+ close: initialState.close.bind(switchChainModal$),
44
+ });
45
+ });
46
+
47
+ afterEach(() => {
48
+ cleanup();
49
+ vi.clearAllMocks();
50
+ switchChainModal$.set({
51
+ ...initialState,
52
+ open: initialState.open.bind(switchChainModal$),
53
+ close: initialState.close.bind(switchChainModal$),
54
+ });
55
+ });
56
+
57
+ test('opens switch chain modal with correct chain', async () => {
58
+ render(<SwitchChainModal />);
59
+
60
+ const { show } = useSwitchChainModal();
61
+ show({ chainIdToSwitchTo: '1' });
62
+
63
+ expect(switchChainModal$.isOpen.get()).toBe(true);
64
+ expect(switchChainModal$.state.chainIdToSwitchTo.get()).toBe('1');
65
+
66
+ const titleElement = await screen.findByText('Wrong network');
67
+ expect(titleElement).toBeInTheDocument();
68
+
69
+ const messageElement = await screen.findByText(
70
+ /You need to switch to mainnet network before completing the transaction/,
71
+ );
72
+ expect(messageElement).toBeInTheDocument();
73
+
74
+ const buttonElement = await screen.findByRole('button', {
75
+ name: /switch network/i,
76
+ });
77
+ expect(buttonElement).toBeInTheDocument();
78
+ });
79
+
80
+ test('closes switch chain modal using close button', async () => {
81
+ render(<SwitchChainModal />);
82
+
83
+ const { show } = useSwitchChainModal();
84
+ show({ chainIdToSwitchTo: '1' });
85
+
86
+ expect(switchChainModal$.isOpen.get()).toBe(true);
87
+ expect(switchChainModal$.state.chainIdToSwitchTo.get()).toBe('1');
88
+
89
+ const closeButton = await screen.findByTestId(
90
+ 'switch-chain-modal-close-button',
91
+ );
92
+ expect(closeButton).toBeInTheDocument();
93
+
94
+ fireEvent.click(closeButton);
95
+
96
+ await waitFor(() => {
97
+ expect(switchChainModal$.isOpen.get()).toBe(undefined);
98
+ expect(switchChainModal$.state.chainIdToSwitchTo.get()).toBe(undefined);
99
+ });
100
+
101
+ const titleElement = screen.queryByText('Wrong network');
102
+ expect(titleElement).not.toBeInTheDocument();
103
+ });
104
+
105
+ test('closes switch chain modal using close callback', async () => {
106
+ render(<SwitchChainModal />);
107
+
108
+ const { show, close } = useSwitchChainModal();
109
+ show({ chainIdToSwitchTo: '1' });
110
+
111
+ expect(switchChainModal$.isOpen.get()).toBe(true);
112
+ expect(switchChainModal$.state.chainIdToSwitchTo.get()).toBe('1');
113
+
114
+ close();
115
+
116
+ await waitFor(() => {
117
+ expect(switchChainModal$.isOpen.get()).toBe(undefined);
118
+ expect(switchChainModal$.state.chainIdToSwitchTo.get()).toBe(undefined);
119
+ });
120
+
121
+ const titleElement = screen.queryByText('Wrong network');
122
+ expect(titleElement).not.toBeInTheDocument();
123
+ });
124
+
125
+ test('clicking Switch Network button triggers chain switch', async () => {
126
+ render(<SwitchChainModal />);
127
+ const { show } = useSwitchChainModal();
128
+
129
+ show({ chainIdToSwitchTo: '1', onError: () => {} });
130
+
131
+ const switchButton = await screen.findByRole('button', {
132
+ name: /switch network/i,
133
+ });
134
+ expect(switchButton).toBeInTheDocument();
135
+
136
+ fireEvent.click(switchButton);
137
+
138
+ expect(mockSwitchChainAsync).toHaveBeenCalledWith({ chainId: 1 });
139
+
140
+ await waitFor(() => {
141
+ expect(switchChainModal$.isOpen.get()).toBe(undefined);
142
+ expect(switchChainModal$.state.isSwitching.get()).toBe(false);
143
+ });
144
+ });
145
+
146
+ test('shows spinner while switching chain', async () => {
147
+ mockSwitchChainAsync.mockImplementationOnce(
148
+ () => new Promise((resolve) => setTimeout(resolve, 100)),
149
+ );
150
+
151
+ render(<SwitchChainModal />);
152
+ const { show } = useSwitchChainModal();
153
+
154
+ show({ chainIdToSwitchTo: '1' });
155
+
156
+ const switchButton = await screen.findByRole('button', {
157
+ name: /switch network/i,
158
+ });
159
+ expect(switchButton).toBeInTheDocument();
160
+
161
+ fireEvent.click(switchButton);
162
+
163
+ await waitFor(() => {
164
+ expect(switchChainModal$.state.isSwitching.get()).toBe(true);
165
+ });
166
+
167
+ const spinner = await screen.findByTestId('switch-chain-spinner');
168
+ expect(spinner).toBeInTheDocument();
169
+
170
+ await waitFor(() => {
171
+ expect(switchChainModal$.state.isSwitching.get()).toBe(false);
172
+ expect(
173
+ screen.queryByTestId('switch-chain-spinner'),
174
+ ).not.toBeInTheDocument();
175
+ });
176
+ });
177
+
178
+ test('keeps modal open if chainIdToSwitchTo is empty', async () => {
179
+ render(<SwitchChainModal />);
180
+ const { show } = useSwitchChainModal();
181
+
182
+ show({
183
+ chainIdToSwitchTo: '',
184
+ });
185
+
186
+ const switchButton = await screen.findByRole('button', {
187
+ name: /switch network/i,
188
+ });
189
+ expect(switchButton).toBeInTheDocument();
190
+
191
+ fireEvent.click(switchButton);
192
+
193
+ await waitFor(() => {
194
+ expect(switchChainModal$.isOpen.get()).toBe(true);
195
+ expect(switchChainModal$.state.isSwitching.get()).toBe(false);
196
+ });
197
+ });
198
+
199
+ test('calls onError callback when switching chain fails', async () => {
200
+ const mockError = new Error(
201
+ 'Failed to switch chain',
202
+ ) as unknown as SwitchChainError;
203
+ mockSwitchChainAsync.mockRejectedValueOnce(mockError);
204
+ const onError = vi.fn();
205
+
206
+ render(<SwitchChainModal />);
207
+
208
+ const { show } = useSwitchChainModal();
209
+ show({ chainIdToSwitchTo: '1', onError });
210
+
211
+ const switchButton = await screen.findByRole('button', {
212
+ name: /switch network/i,
213
+ });
214
+ fireEvent.click(switchButton);
215
+
216
+ await waitFor(() => {
217
+ expect(onError).toHaveBeenCalledWith(mockError);
218
+ expect(switchChainModal$.isOpen.get()).toBe(true);
219
+ });
220
+ });
221
+ });
@@ -7,7 +7,6 @@ import {
7
7
  } from '@0xsequence/design-system';
8
8
  import { observer } from '@legendapp/state/react';
9
9
  import { Close, Content, Overlay, Portal, Root } from '@radix-ui/react-dialog';
10
- import type { SwitchChainErrorType } from 'viem';
11
10
  import { useSwitchChain } from 'wagmi';
12
11
  import { getPresentableChainName } from '../../../../../../utils/network';
13
12
  import { getProviderEl, type ChainId } from '../../../../../_internal';
@@ -19,11 +18,12 @@ import {
19
18
  switchChainCta,
20
19
  switchChainModalContent,
21
20
  } from './styles.css';
21
+ import type { SwitchChainError } from 'viem';
22
22
 
23
23
  export type ShowSwitchChainModalArgs = {
24
24
  chainIdToSwitchTo: ChainId;
25
25
  onSuccess?: () => void;
26
- onError?: (error: SwitchChainErrorType) => void;
26
+ onError?: (error: SwitchChainError) => void;
27
27
  onClose?: () => void;
28
28
  };
29
29
 
@@ -50,11 +50,22 @@ const SwitchChainModal = observer(() => {
50
50
  if (!chainIdToSwitchTo) return;
51
51
  await switchChainAsync({ chainId: Number(chainIdToSwitchTo) });
52
52
 
53
- switchChainModal$.state.onSuccess?.();
53
+ if (
54
+ switchChainModal$.state.onSuccess &&
55
+ typeof switchChainModal$.state.onSuccess === 'function'
56
+ ) {
57
+ switchChainModal$.state.onSuccess();
58
+ }
54
59
 
55
60
  switchChainModal$.delete();
56
61
  } catch (error) {
57
- switchChainModal$.state.onError?.(error as SwitchChainErrorType);
62
+ if (
63
+ error instanceof Error &&
64
+ switchChainModal$.state.onError.get() &&
65
+ typeof switchChainModal$.state.onError.get() === 'function'
66
+ ) {
67
+ switchChainModal$.state.onError.get()?.(error as SwitchChainError);
68
+ }
58
69
  } finally {
59
70
  isSwitching$.set(false);
60
71
  }
@@ -77,8 +88,15 @@ const SwitchChainModal = observer(() => {
77
88
 
78
89
  <Button
79
90
  name="switch-chain"
91
+ id="switch-chain-button"
80
92
  size="sm"
81
- label={isSwitching$.get() ? <Spinner /> : 'Switch Network'}
93
+ label={
94
+ isSwitching$.get() ? (
95
+ <Spinner data-testid="switch-chain-spinner" />
96
+ ) : (
97
+ 'Switch Network'
98
+ )
99
+ }
82
100
  variant="primary"
83
101
  pending={isSwitching$.get()}
84
102
  shape="square"
@@ -92,6 +110,7 @@ const SwitchChainModal = observer(() => {
92
110
  />
93
111
 
94
112
  <Close
113
+ data-testid="switch-chain-modal-close-button"
95
114
  onClick={() => {
96
115
  if (
97
116
  switchChainModal$.state.onClose &&
@@ -1,7 +1,7 @@
1
1
  import { observable } from '@legendapp/state';
2
- import type { SwitchChainErrorType } from 'viem';
3
2
  import type { ShowSwitchChainModalArgs } from '.';
4
3
  import type { ChainId } from '../../../../../_internal';
4
+ import type { SwitchChainError } from 'viem';
5
5
 
6
6
  export interface SwitchChainModalState {
7
7
  isOpen: boolean;
@@ -11,7 +11,7 @@ export interface SwitchChainModalState {
11
11
  chainIdToSwitchTo: ChainId | undefined;
12
12
  isSwitching: boolean;
13
13
  onSuccess: (() => void) | undefined;
14
- onError: undefined | ((error: SwitchChainErrorType) => void);
14
+ onError: undefined | ((error: SwitchChainError) => void);
15
15
  onClose: (() => void) | undefined;
16
16
  };
17
17
  }
@@ -70,7 +70,12 @@ const TransactionPreview = observer(
70
70
  }
71
71
 
72
72
  return (
73
- <Box padding="3" background="backgroundSecondary" borderRadius="md">
73
+ <Box
74
+ padding="3"
75
+ background="backgroundSecondary"
76
+ borderRadius="md"
77
+ data-testid="transaction-preview"
78
+ >
74
79
  <Box display="flex" alignItems="center">
75
80
  <Text
76
81
  color="text50"
@@ -78,6 +83,7 @@ const TransactionPreview = observer(
78
83
  fontWeight="medium"
79
84
  marginRight="1"
80
85
  fontFamily="body"
86
+ data-testid="transaction-preview-title"
81
87
  >
82
88
  {title}
83
89
  </Text>
@@ -96,6 +102,7 @@ const TransactionPreview = observer(
96
102
  borderRadius="xs"
97
103
  marginRight="3"
98
104
  style={{ objectFit: 'cover' }}
105
+ data-testid="transaction-preview-image"
99
106
  />
100
107
 
101
108
  <Box
@@ -109,11 +116,17 @@ const TransactionPreview = observer(
109
116
  fontSize="small"
110
117
  fontWeight="medium"
111
118
  fontFamily="body"
119
+ data-testid="transaction-preview-collectible-name"
112
120
  >
113
121
  {collectibleName}
114
122
  </Text>
115
123
 
116
- <Text color="text100" fontSize="small" fontFamily="body">
124
+ <Text
125
+ color="text100"
126
+ fontSize="small"
127
+ fontFamily="body"
128
+ data-testid="transaction-preview-collection-name"
129
+ >
117
130
  {collectionName}
118
131
  </Text>
119
132
  </Box>
@@ -125,6 +138,7 @@ const TransactionPreview = observer(
125
138
  alignItems="center"
126
139
  justifyContent="flex-end"
127
140
  gap="1"
141
+ data-testid="transaction-preview-price"
128
142
  >
129
143
  <Image src={currencyImageUrl} width="3" height="3" />
130
144
 
@@ -1,4 +1,4 @@
1
- import type { TransactionType } from '../../../../../_internal/transaction-machine/execute-transaction';
1
+ import type { TransactionType } from '../../../../../_internal/types';
2
2
  import type { ConfirmationStatus } from '../transactionStatusModal/store';
3
3
  import { TRANSACTION_TITLES } from './consts';
4
4