├── .env.example
├── .eslintrc.json
├── .github
└── workflows
│ └── release-please.yml
├── .gitignore
├── .prettierrc
├── .vscode
└── settings.json
├── CHANGELOG.md
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── REQUIREMENTS.md
├── compiled-lang
├── cs-CZ.json
├── en-US.json
├── es-ES.json
└── pt-BR.json
├── config
├── app.development.json
├── app.json
├── default.collectionlist.json
└── default.tokenlist.json
├── docs
└── ADD_LANGUAGE.md
├── funding.json
├── lang
├── cs-CZ.json
├── en-US.json
├── es-ES.json
└── pt-BR.json
├── next-env.d.ts
├── next-seo.config.ts
├── next.config.js
├── package.json
├── pages
├── 404.tsx
├── _app.tsx
├── _document.tsx
├── api
│ └── token.ts
├── asset
│ └── [network]
│ │ └── [address]
│ │ └── [id].tsx
├── collection
│ └── [network]
│ │ └── [address]
│ │ └── index.tsx
├── collections.tsx
├── index.tsx
├── order
│ ├── [network]
│ │ └── [hash].tsx
│ └── create.tsx
└── wallet
│ ├── connect.tsx
│ ├── index.tsx
│ ├── nfts.tsx
│ └── orders.tsx
├── public
├── assets
│ ├── images
│ │ ├── cat-hero.svg
│ │ ├── connect-wallet-background.svg
│ │ ├── ethereum.svg
│ │ ├── icons
│ │ │ ├── arbitrum.png
│ │ │ ├── avax.png
│ │ │ ├── bnb.svg
│ │ │ ├── eth.png
│ │ │ ├── fantom.svg
│ │ │ ├── optimism.svg
│ │ │ ├── polygon.png
│ │ │ └── setting-2.svg
│ │ ├── market-logo.svg
│ │ ├── metamask-fox.svg
│ │ ├── trade-button.svg
│ │ └── walletconnect-circle-blue.svg
│ └── kittygotchi
│ │ ├── banner.svg
│ │ ├── banner_bsc.svg
│ │ ├── banner_eth.svg
│ │ ├── banner_polygon.svg
│ │ └── kittygotchi_banner.jpg
├── favicon.ico
├── robots.txt
├── sitemap-0.xml
└── sitemap.xml
├── src
├── actions
│ └── index.ts
├── components
│ ├── AppDialogTitle.tsx
│ ├── AppDrawer.tsx
│ ├── AppErrorBoundary.tsx
│ ├── AppFeePercentageSpan.tsx
│ ├── AppIntlProvider.tsx
│ ├── CopyIconButton.tsx
│ ├── DatetimeFromNowSpan.tsx
│ ├── Footer.tsx
│ ├── Icon.tsx
│ ├── LazyYoutubeFrame.tsx
│ ├── Link.tsx
│ ├── MomentFromNow.tsx
│ ├── Navbar.tsx
│ ├── PageHeader.tsx
│ ├── SidebarFilters.tsx
│ ├── SidebarFiltersAccordion.tsx
│ ├── SidebarFiltersContent.tsx
│ ├── TransactionTitle.tsx
│ ├── WalletButton.tsx
│ ├── dialogs
│ │ ├── AppTransactionsDialog.tsx
│ │ ├── AssetApprovalDialog.tsx
│ │ ├── ConnectWalletDialog.tsx
│ │ ├── ImportTokenDialog.tsx
│ │ ├── SelectCurrencyDialog.tsx
│ │ ├── SelectLanguageDialog.tsx
│ │ ├── SelectNetworkDialog.tsx
│ │ ├── SignMessageDialog.tsx
│ │ ├── SwitchNetworkDialog.tsx
│ │ └── TransactionDialog.tsx
│ ├── icons
│ │ ├── ArrowSwap.tsx
│ │ ├── Calendar.tsx
│ │ ├── CardTick.tsx
│ │ ├── CloseCircle.tsx
│ │ ├── DollarSquare.tsx
│ │ ├── Ethereum.tsx
│ │ ├── Export.tsx
│ │ ├── Filter.tsx
│ │ ├── Heart.tsx
│ │ ├── MoneyReceive.tsx
│ │ ├── MoneySend.tsx
│ │ ├── Notification.tsx
│ │ ├── ReceiptText.tsx
│ │ ├── RotateRight.tsx
│ │ ├── Setting.tsx
│ │ ├── Share.tsx
│ │ ├── Tag.tsx
│ │ ├── TickCircle.tsx
│ │ └── Wallet.tsx
│ ├── layouts
│ │ └── main.tsx
│ └── transactions
│ │ └── Updater.tsx
├── connectors
│ ├── metamask.ts
│ └── walletConnect.ts
├── constants
│ ├── abis
│ │ └── index.ts
│ ├── chain.ts
│ ├── chains.ts
│ ├── enum.ts
│ └── index.ts
├── createEmotionCache.ts
├── hooks
│ ├── app.ts
│ ├── balances.ts
│ ├── blockchain.ts
│ ├── currency.ts
│ ├── misc.ts
│ └── nft.ts
├── index.d.ts
├── modules
│ ├── favorites
│ │ └── components
│ │ │ ├── FavoriteAssetsSection.tsx
│ │ │ └── RemoveFavoriteDialog.tsx
│ ├── home
│ │ └── components
│ │ │ ├── ActionButton.tsx
│ │ │ ├── ActionButtonsSection.tsx
│ │ │ ├── CallToActionSection.tsx
│ │ │ ├── CollectionCard.tsx
│ │ │ ├── CollectionCardWithData.tsx
│ │ │ ├── CollectionsSection.tsx
│ │ │ ├── ConnectWalletButton.tsx
│ │ │ ├── FeaturedSection.tsx
│ │ │ └── VideoSection.tsx
│ ├── nft
│ │ └── components
│ │ │ ├── AssetAttributePaper.tsx
│ │ │ ├── AssetCard.tsx
│ │ │ ├── AssetCardWidthData.tsx
│ │ │ ├── AssetDetails.tsx
│ │ │ ├── AssetHead.tsx
│ │ │ ├── AssetIframe.tsx
│ │ │ ├── AssetImage.tsx
│ │ │ ├── AssetLeftSection.tsx
│ │ │ ├── AssetList.tsx
│ │ │ ├── AssetMedia.tsx
│ │ │ ├── AssetPageActions.tsx
│ │ │ ├── AssetPageTitle.tsx
│ │ │ ├── AssetPricePaper.tsx
│ │ │ ├── AssetRightSection.tsx
│ │ │ ├── AssetTabs.tsx
│ │ │ ├── CollectionHeader.tsx
│ │ │ ├── CollectionPageHeader.tsx
│ │ │ ├── DurationSelect.tsx
│ │ │ ├── dialogs
│ │ │ ├── ConfirmBuyDialog.tsx
│ │ │ ├── MakeListingDialog.tsx
│ │ │ ├── MakeOfferDialog.tsx
│ │ │ └── ShareDialog.tsx
│ │ │ └── tables
│ │ │ ├── ListingsTable.tsx
│ │ │ ├── ListingsTableRow.tsx
│ │ │ ├── OffersTable.tsx
│ │ │ ├── OffersTableRow.tsx
│ │ │ └── TableSkeleton.tsx
│ ├── orders
│ │ └── components
│ │ │ ├── OrderLeftSection.tsx
│ │ │ ├── OrderPageActions.tsx
│ │ │ ├── OrderRightSection.tsx
│ │ │ ├── dialogs
│ │ │ ├── ImportAssetDialog.tsx
│ │ │ └── OrderCreatedDialog.tsx
│ │ │ └── forms
│ │ │ ├── MakeListingForm.tsx
│ │ │ └── MakeOfferForm.tsx
│ └── wallet
│ │ └── components
│ │ ├── TransactionsTable.tsx
│ │ ├── TransactionsTableRow.tsx
│ │ ├── WalletActionButton.tsx
│ │ ├── WalletBalancesTable.tsx
│ │ ├── WalletButton.tsx
│ │ ├── WalletOrders.tsx
│ │ ├── WalletOrdersTable.tsx
│ │ ├── WalletOrdersTableRow.tsx
│ │ ├── WalletTableRow.tsx
│ │ ├── WalletTotalBalance.tsx
│ │ ├── WalletTotalBalanceContainer.tsx
│ │ └── dialogs
│ │ └── ReceiveDialog.tsx
├── services
│ ├── app.ts
│ ├── balances.ts
│ ├── blockchain.ts
│ ├── currency.ts
│ ├── multical.ts
│ ├── nft.ts
│ └── providers.ts
├── state
│ └── atoms.ts
├── theme.ts
├── themes
│ ├── index.ts
│ └── kittygotchi.ts
├── types
│ ├── actions.ts
│ ├── app.ts
│ ├── blockchain.ts
│ ├── chains.ts
│ ├── config.ts
│ ├── nft.ts
│ └── orderbook.ts
└── utils
│ ├── blockchain.ts
│ ├── browser.ts
│ ├── intl.ts
│ ├── ipfs.ts
│ ├── nfts.ts
│ ├── numbers.ts
│ ├── token.ts
│ └── types.ts
├── tsconfig.json
└── yarn.lock
/.env.example:
--------------------------------------------------------------------------------
1 | NEXT_PUBLIC_TRANSAK_API_KEY=
2 | INFURA_API_KEY=
--------------------------------------------------------------------------------
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "next/core-web-vitals"
3 | }
4 |
--------------------------------------------------------------------------------
/.github/workflows/release-please.yml:
--------------------------------------------------------------------------------
1 | on:
2 | push:
3 | branches:
4 | - main
5 | name: release-please
6 | jobs:
7 | release-please:
8 | runs-on: ubuntu-latest
9 | steps:
10 | - uses: google-github-actions/release-please-action@v3
11 | with:
12 | release-type: node
13 | package-name: release-please-action
14 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 |
9 | # testing
10 | /coverage
11 |
12 | # next.js
13 | /.next/
14 | /out/
15 |
16 | # production
17 | /build
18 |
19 | # misc
20 | .DS_Store
21 | *.pem
22 |
23 | # debug
24 | npm-debug.log*
25 | yarn-debug.log*
26 | yarn-error.log*
27 |
28 | # local env files
29 | .env.local
30 | .env.development.local
31 | .env.test.local
32 | .env.production.local
33 | .env.development
34 | .env.production
35 | .env
36 |
37 | # vercel
38 | .vercel
39 |
40 |
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "tabWidth": 2,
3 | "singleQuote": true,
4 | "jsxSingleQuote": false,
5 | "useTabs": false
6 | }
7 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "telemetry.enableTelemetry": false,
3 | "files.watcherExclude": {
4 | "**/.git/objects/**": true,
5 | "**/.git/subtree-cache/**": true,
6 | "**/node_modules/**": true,
7 | "**/env/**": true,
8 | "**/venv/**": true,
9 | "env-*": true
10 | },
11 | "[jsonc]": {
12 | "editor.defaultFormatter": "esbenp.prettier-vscode"
13 | },
14 | "editor.formatOnSave": true,
15 | "editor.tabSize": 2,
16 | "typescript.updateImportsOnFileMove.enabled": "always",
17 | "window.zoomLevel": 1,
18 | "javascript.updateImportsOnFileMove.enabled": "always"
19 | }
20 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | ## [0.0.2](https://github.com/DexKit/open-nft-marketplace/compare/v0.0.1...v0.0.2) (2023-02-01)
4 |
5 |
6 | ### Bug Fixes
7 |
8 | * **lang:** remove non translated file ([2a1095f](https://github.com/DexKit/open-nft-marketplace/commit/2a1095f413e8c064967c96c710f2bf7b0c6797bc))
9 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing
2 |
3 | Thank you for your interest in contributing to the open nft marketplace.
4 |
5 | # Development
6 |
7 | ## Running the interface locally
8 |
9 | Using yarn
10 |
11 | 1. clone this repo with `git clone https://github.com/DexKit/open-nft-marketplace.git`
12 | 2. run `yarn` once to install dependencies
13 | 3. finally run `yarn dev`
14 |
15 | ## How to Contribute
16 |
17 | You can contribute creating issues on this repo with features that you want to be implemented, if the issue is of high community interest, anyone is open to contribute and implement it.
18 |
19 | If you implemented an issue requested by the community, please open a pull request against the main branch, the maintainer will evaluate as soon as possible
20 |
21 | This is list of welcome contributions to this repo:
22 |
23 | - [Adding languages](./docs/ADD_LANGUAGE.md)
24 | - Fix language typo's
25 | - bug fix's reporting
26 | - Add tests
27 |
28 | ## Features Discussion
29 |
30 | If you would to discuss first if an issue should be open, there is a discord channel on DexKit for it, check it out at [Open NFT Support](https://discord.gg/FnkrFAY7Za)
31 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # NFT Marketplace [](https://twitter.com/intent/tweet?text=Open%20source%20nft%20marketplace:&url=https://github.com/DexKit/open-nft-marketplace)
2 |
3 | [](https://www.youtube.com/watch?v=9UxtgAkNG1k 'Marketplace by DexKit')
4 |
5 | This marketplace is the DexKit open source showcase on how to use 0x v4 nft smart contracts on a production app. Additionally, we are building a [zero code solution](https://whitelabel-nft.dexkit.com/admin/setup) with premium features to help artists deploy their own marketplace in an easy and secure way. Check our docs about it [here](https://docs.dexkit.com/defi-products/nft-marketplace/overview).
6 |
7 | On this marketplace you can make offers and listings of ERC721 Tokens on the chains supported by 0x smart contracts, namely: Ethereum, Binance Smart Chain, Polygon, Fantom, Avalanche, Celo and Optimism.
8 |
9 | # How to Start
10 |
11 | clone this repo
12 |
13 | ```
14 | git clone https://github.com/DexKit/open-nft-marketplace.git
15 | ```
16 |
17 | Install it:
18 |
19 | ```sh
20 | yarn
21 | ```
22 |
23 | Create an .env file with INFURA_API_KEY set with your Infura API key and then run the app
24 |
25 | ```sh
26 | yarn dev
27 | ```
28 |
29 | # Contributing
30 |
31 | Check [Contributing](CONTRIBUTING.md) for a more in depth way how to contribute.
32 |
33 | # Deployment
34 |
35 | We recommend Vercel to deploy this app, after you made your changes on the app.json config file, just use the button below:
36 |
37 | [](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2FDexKit%2Fopen-nft-marketplace&env=INFURA_API_KEY)
38 |
39 | Note that you need to set up INFURA_API_KEY to Next js be able to generate pages.
40 |
41 | # Tech used
42 |
43 | Started from [NEXT JS + Material UI+ Typescript + Boilerplat](https://github.com/mui/material-ui/tree/master/examples/nextjs-with-typescript)
44 |
45 | Additionally we use trader sdk to handle nft smart contract interactions, react query to handle all http and blockchain requests, format js for internalization, web3 react to handle wallet logic. You can check our requirements [here](REQUIREMENTS.md).
46 |
47 | # Roadmap
48 |
49 | We will be adding any new evm network that 0x smart contracts will support.
50 |
51 | It is also planned to extract all common hooks and state used to interact with the blockchain to a library repo.
52 |
53 | # Customization
54 |
55 | If you need a zero code solution we are building one currently in beta at [wizard](https://whitelabel-nft.dexkit.com/admin/setup), check our [docs](https://docs.dexkit.com/defi-products/nft-marketplace/overview) as well about it. Instead, if you want to deploy your own custom solution using this repo, please fork it, update the app.json file accordingly on the config folder and then deploy on Vercel (Recommended) or Heroku.
56 |
57 | # Missing feature?
58 |
59 | We welcome missing features, but take in mind that this repo is intended to be a base app for any dev to start working on, if it makes sense to have that feature on this base app we will include, if it is considered a premium feature, we will be including on our premium marketplace which uses this one as a base.
60 |
61 | We at the moment consider premium features as follows:
62 |
63 | - [ ] - NFT trading history
64 |
65 | - [ ] - Artist page
66 |
67 | - [ ] - Cache optimizations
68 |
69 | - [ ] - Fetch NFT and token balances via api without the need to import, using Alchemy for instance
70 |
71 | - [ ] - Swap ERC20 <-> ERC20 tokens
72 |
73 | - [ ] - Collection level stats like orders, max supply, floor price, number of trades
74 |
75 | - [ ] - Improved SEO
76 |
77 | # Acknowledgements
78 |
79 | We would like to thank ZRX project for these amazing tools and ZRX DAO for the support on building this open source app.
80 |
81 | # Wanna talk about this repo
82 |
83 | Join our dedicated channel [Open NFT Support](https://discord.gg/FnkrFAY7Za)
84 |
--------------------------------------------------------------------------------
/REQUIREMENTS.md:
--------------------------------------------------------------------------------
1 | # REQUIREMENTS
2 |
3 | DexKit Whitelabel Marketplace will use the follow technologies:
4 |
5 | - NEXT js - for handle SEO and app loading
6 | - Web3React - for web3 wallet management
7 | - TraderSDK - for easy swaps management using 0x behind the hood
8 | - Material UI - battle tested framework and easily configurable
9 | - TheGraph - Orders history and onchain orders discovery
10 | - ReactQuery - for fetching APIS and mutations
11 | - Typescript - for easy typings
12 |
13 | ## APP structure
14 |
15 | The Apps follows a standard structure by modules, being each module planned to act independently
16 | src/
17 |
18 | - hooks
19 | - services
20 | - constants
21 | - components
22 | - utils
23 | - themes
24 | - actions
25 | - state
26 | - types
27 | - conectors
28 | - modules
29 | - favories
30 | - home
31 | - orders
32 | - nft
33 | - wallet
34 |
35 | Each module is structured as follows:
36 |
37 | - components - pure components
38 | - constants - constants used on this module
39 | - hooks - act as the connection between services and pages
40 | - services - async fetching and outside logic
41 | - pages - presentation
42 | - utils - pure functions
43 | - tests
44 | - integration
45 | - unit
46 |
47 | index.tsx --> routes
48 |
49 | ## APP Features
50 |
51 | - Wallet ERC20 balance fetching for the main tokens and coins dependent of chain. APP supports natively the follow tokens USDC, USDT, DAI, and as coins ETH, MATIC and BNB, this part is configurable. Unsupported tokens could be imported
52 | - Wallet ERC721 balance display. User can import any NFT by ID. Collections could be added by wizard
53 | - Notifications - Each in app interaction generates a notification for user and stores a transaction on the store
54 | - NFT list of available orders for trade
55 | - NFT detail - A visit to a page it will fetch all data onchain, by /:chainname/:contract_address/:id . User can place sell order, buy order, or transfer
56 | - internationalization - It will be built from the ground up with internationalization in mind
57 |
--------------------------------------------------------------------------------
/config/default.collectionlist.json:
--------------------------------------------------------------------------------
1 | {
2 | "collections": [
3 | {
4 | "image": "https://dweb.link/ipfs/QmZfhEwTcTvXy4GfcyNvqDvGK6qzCFbZeTX71hUT3RWL8X",
5 | "name": "Coin League Champions",
6 | "backgroundImage": "https://dweb.link/ipfs/QmZfhEwTcTvXy4GfcyNvqDvGK6qzCFbZeTX71hUT3RWL8X",
7 | "chainId": 80001,
8 | "contractAddress": "0x05b93425e4b44c9042ed97b7a332ab1575ebd25d",
9 | "description": "",
10 | "uri": "coinleaguechampions"
11 | },
12 | {
13 | "image": "https://dweb.link/ipfs/QmZfhEwTcTvXy4GfcyNvqDvGK6qzCFbZeTX71hUT3RWL8X",
14 | "name": "Coin League Champions",
15 | "backgroundImage": "https://dweb.link/ipfs/QmZfhEwTcTvXy4GfcyNvqDvGK6qzCFbZeTX71hUT3RWL8X",
16 | "chainId": 80001,
17 | "contractAddress": "0x05b93425e4b44c9042ed97b7a332ab1575ebd25d",
18 | "description": "",
19 | "uri": "coinleaguechampions"
20 | }
21 | ]
22 | }
23 |
--------------------------------------------------------------------------------
/config/default.tokenlist.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Test tokens",
3 | "logoURI": "ipfs://QmUSNbwUxUYNMvMksKypkgWs8unSm8dX2GjCPBVGZ7GGMr",
4 | "keywords": ["faucet", "test"],
5 | "tags": {
6 | "faucet": {
7 | "name": "Faucet token for testing on ropsten",
8 | "description": "ERC20 tokens for testing"
9 | }
10 | },
11 | "timestamp": "2020-06-12T00:00:00+00:00",
12 | "tokens": [
13 | {
14 | "chainId": 3,
15 | "address": "0xfab46e002bbf0b4509813474841e0716e6730136",
16 | "symbol": "FAU",
17 | "name": "FaucetToken",
18 | "decimals": 18,
19 | "logoURI": "ipfs://QmUSNbwUxUYNMvMksKypkgWs8unSm8dX2GjCPBVGZ7GGMr",
20 | "tags": ["faucet", "test"]
21 | },
22 | {
23 | "chainId": 4,
24 | "address": "0xFab46E002BbF0b4509813474841E0716E6730136",
25 | "symbol": "FAU",
26 | "name": "FaucetToken",
27 | "decimals": 18,
28 | "logoURI": "ipfs://QmUSNbwUxUYNMvMksKypkgWs8unSm8dX2GjCPBVGZ7GGMr",
29 | "tags": ["faucet", "test"]
30 | },
31 | {
32 | "chainId": 3,
33 | "address": "0xad6d458402f60fd3bd25163575031acdce07538d",
34 | "symbol": "DAI",
35 | "name": "DAI Stable Coin",
36 | "decimals": 18,
37 | "logoURI": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0x6B175474E89094C44Da98b954EedeAC495271d0F/logo.png",
38 | "tags": ["faucet", "test"]
39 | },
40 | {
41 | "chainId": 3,
42 | "address": "0xc778417e063141139fce010982780140aa0cd5ab",
43 | "symbol": "WETH",
44 | "name": "Wrapped Ether",
45 | "decimals": 18,
46 | "logoURI": "ipfs://QmUSNbwUxUYNMvMksKypkgWs8unSm8dX2GjCPBVGZ7GGMr",
47 | "tags": ["weth", "test"]
48 | },
49 | {
50 | "chainId": 80001,
51 | "address": "0xa6fa4fb5f76172d178d61b04b0ecd319c5d1c0aa",
52 | "symbol": "WETH",
53 | "name": "Wrapped ETH",
54 | "decimals": 18,
55 | "logoURI": "https://polygonscan.com/token/images/wMatic_32.png",
56 | "tags": ["polygon", "wrapped token"]
57 | },
58 | {
59 | "chainId": 137,
60 | "address": "0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174",
61 | "symbol": "USDC",
62 | "name": "USD Coin",
63 | "decimals": 6,
64 | "logoURI": "https://polygonscan.com/token/images/centre-usdc_32.png",
65 | "tags": ["polygon", "stable coin"]
66 | }
67 | ],
68 | "version": {
69 | "major": 1,
70 | "minor": 0,
71 | "patch": 0
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/docs/ADD_LANGUAGE.md:
--------------------------------------------------------------------------------
1 | # How to add a language to NFT Marketplace
2 |
3 | ## First step
4 |
5 | Under lang folder copy en-US.json and create a json file with your language code and translate it on same folder.
6 |
7 | ## Second step
8 |
9 | Add the language you wanna add to src/constants/index.ts file
10 |
11 | ## Third step
12 |
13 | Run
14 |
15 | ` yarn compile lang/your-language-code.json --out-file compiled-lang/your-language-code.json`
16 |
17 | Example for pt-BR
18 |
19 | `yarn compile lang/pt-BR.json --out-file compiled-lang/pt-BR.json`
20 |
21 | ## Fourth step
22 |
23 | Add language you wanna add to src/utils/intl.ts file
24 |
--------------------------------------------------------------------------------
/funding.json:
--------------------------------------------------------------------------------
1 | {
2 | "opRetro": {
3 | "projectId": "0xcc9301a401320c626839db9c4415f28a6e1f2fb3abdf38ada785b57409bdedb6"
4 | }
5 | }
6 |
--------------------------------------------------------------------------------
/next-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 | ///
3 |
4 | // NOTE: This file should not be edited
5 | // see https://nextjs.org/docs/basic-features/typescript for more information.
6 |
--------------------------------------------------------------------------------
/next-seo.config.ts:
--------------------------------------------------------------------------------
1 | import { getAppConfig } from './src/services/app';
2 |
3 | const config = getAppConfig();
4 |
5 | const seoConfig: any = {
6 | defaultTitle: config.seo?.home?.title || config.name,
7 | titleTemplate: `${config.name} | %s`,
8 | description: config.seo?.home?.description,
9 | canonical: config.url,
10 | };
11 |
12 | if (config.social) {
13 | for (let social of config.social) {
14 | if (social.type === 'twitter') {
15 | seoConfig.twitter = {
16 | handle: `@${social.handle}`,
17 | site: `@${social.handle}`,
18 | cardType: "summary_large_image"
19 | };
20 | }
21 | }
22 | }
23 |
24 | export default seoConfig;
25 |
--------------------------------------------------------------------------------
/next.config.js:
--------------------------------------------------------------------------------
1 | /** @type {import('next').NextConfig} */
2 | module.exports = {
3 | reactStrictMode: true,
4 | staticPageGenerationTimeout: 120,
5 | images: {
6 | domains: [
7 | 'dweb.link',
8 | 'ipfs.io',
9 | 'ipfs.moralis.io',
10 | 'raw.githubusercontent.com',
11 | 'arpeggi.io',
12 | 'arweave.net',
13 | 'metadata.ens.domains',
14 | ],
15 | },
16 | };
17 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "open-nft-marketplace",
3 | "version": "0.0.2",
4 | "private": true,
5 | "scripts": {
6 | "dev": "next dev -p 3001",
7 | "build": "next build --debug",
8 | "start": "next start",
9 | "lint": "next lint",
10 | "post-update": "echo \"codesandbox preview only, need an update\" && yarn upgrade --latest",
11 | "extract": "formatjs extract",
12 | "compile": "formatjs compile",
13 | "generate-lang-files": "yarn extract 'src/**/*.ts*' --ignore='**/*.d.ts' --out-file lang/en.json --id-interpolation-pattern '[sha512:contenthash:base64:6]'"
14 | },
15 | "dependencies": {
16 | "@0x/contract-addresses": "^6.14.0",
17 | "@date-io/moment": "^2.13.1",
18 | "@emotion/cache": "^11.7.1",
19 | "@emotion/react": "^11.9.0",
20 | "@emotion/server": "^11.4.0",
21 | "@emotion/styled": "^11.8.1",
22 | "@indexed-finance/multicall": "^2.0.0",
23 | "@mui/icons-material": "^5.6.2",
24 | "@mui/material": "^5.6.3",
25 | "@mui/x-date-pickers": "^5.0.0-alpha.2",
26 | "@react-hook/intersection-observer": "^3.1.1",
27 | "@svgr/webpack": "^6.2.1",
28 | "@traderxyz/nft-swap-sdk": "0.25.0",
29 | "@transak/transak-sdk": "^1.0.31",
30 | "@walletconnect/ethereum-provider": "^1.7.7",
31 | "@web3-react/core": "8.0.28-beta.0",
32 | "@web3-react/metamask": "8.0.23-beta.0",
33 | "@web3-react/network": "^8.0.0-beta.0",
34 | "@web3-react/walletconnect": "8.0.30-beta.0",
35 | "axios": "^0.27.2",
36 | "ethers": "^5.6.4",
37 | "formik": "^2.2.9",
38 | "jotai": "^1.6.5",
39 | "moment": "^2.29.3",
40 | "next": "^12.1.5",
41 | "next-seo": "^5.4.0",
42 | "notistack": "^2.0.5",
43 | "optics-ts": "^2.3.0",
44 | "qrcode.react": "^3.0.2",
45 | "qs": "^6.11.0",
46 | "react": "^18.1.0",
47 | "react-dom": "^18.1.0",
48 | "react-error-boundary": "^3.1.4",
49 | "react-intl": "^5.24.8",
50 | "react-markdown": "^8.0.5",
51 | "react-query": "^3.34.19",
52 | "remark-gfm": "^3.0.1",
53 | "yup": "^0.32.11"
54 | },
55 | "devDependencies": {
56 | "@formatjs/cli": "^4.8.3",
57 | "@types/qs": "^6.9.7",
58 | "@web3-react/types": "8.0.16-beta.0",
59 | "eslint": "^8.14.0",
60 | "eslint-config-next": "^12.1.5",
61 | "typescript": "^4.6.4"
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/pages/404.tsx:
--------------------------------------------------------------------------------
1 | import ArrowBackIcon from '@mui/icons-material/ArrowBack';
2 | import { Box, Button, Container, Grid, Typography } from '@mui/material';
3 | import type { NextPage } from 'next';
4 | import MainLayout from '../src/components/layouts/main';
5 |
6 | import Image from 'next/image';
7 |
8 | import { FormattedMessage } from 'react-intl';
9 | import catHeroImg from '../public/assets/images/cat-hero.svg';
10 | import Link from '../src/components/Link';
11 |
12 | const NotFound: NextPage = () => {
13 | return (
14 |
15 |
16 |
17 |
18 |
26 |
31 |
35 |
36 |
41 |
42 |
43 |
50 | }
54 | variant="contained"
55 | color="primary"
56 | >
57 |
61 |
62 |
63 |
64 |
72 |
73 |
74 |
75 |
76 |
77 |
78 | );
79 | };
80 |
81 | export default NotFound;
82 |
--------------------------------------------------------------------------------
/pages/_app.tsx:
--------------------------------------------------------------------------------
1 | import { CacheProvider, EmotionCache } from '@emotion/react';
2 | import CssBaseline from '@mui/material/CssBaseline';
3 | import { responsiveFontSizes, ThemeProvider } from '@mui/material/styles';
4 | import { AppProps } from 'next/app';
5 | import Head from 'next/head';
6 | import * as React from 'react';
7 | import { Hydrate, QueryClient, QueryClientProvider } from 'react-query';
8 | import createEmotionCache from '../src/createEmotionCache';
9 | import { getTheme } from '../src/theme';
10 |
11 | import { DefaultSeo } from 'next-seo';
12 |
13 | import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider';
14 |
15 | import { AdapterMoment } from '@mui/x-date-pickers/AdapterMoment';
16 |
17 | import { Web3ReactProvider } from '@web3-react/core';
18 | import { Provider } from 'jotai';
19 | import { hooks as metaMaskHooks, metaMask } from '../src/connectors/metamask';
20 | import {
21 | hooks as walletConnectHooks,
22 | walletConnect,
23 | } from '../src/connectors/walletConnect';
24 |
25 | import { SnackbarProvider } from 'notistack';
26 | import AppIntlProvider from '../src/components/AppIntlProvider';
27 | import { Updater } from '../src/components/transactions/Updater';
28 | import { getAppConfig } from '../src/services/app';
29 |
30 | import SEO from '../next-seo.config';
31 |
32 | // Client-side cache, shared for the whole session of the user in the browser.
33 | const clientSideEmotionCache = createEmotionCache();
34 |
35 | interface MyAppProps extends AppProps {
36 | emotionCache?: EmotionCache;
37 | }
38 |
39 | const appConfig = getAppConfig();
40 |
41 | const tempTheme = getTheme(appConfig.theme);
42 |
43 | const theme = responsiveFontSizes(tempTheme);
44 |
45 | export default function MyApp(props: MyAppProps) {
46 | const { Component, emotionCache = clientSideEmotionCache, pageProps } = props;
47 |
48 | const [queryClient] = React.useState(
49 | new QueryClient({
50 | defaultOptions: {
51 | queries: {
52 | suspense: false,
53 | },
54 | },
55 | })
56 | );
57 |
58 | const getLayout = (Component as any).getLayout || ((page: any) => page);
59 |
60 | return (
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
81 |
82 | {getLayout()}
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 | );
93 | }
94 |
--------------------------------------------------------------------------------
/pages/_document.tsx:
--------------------------------------------------------------------------------
1 | import createEmotionServer from '@emotion/server/create-instance';
2 | import Document, { Head, Html, Main, NextScript } from 'next/document';
3 |
4 | import createEmotionCache from '../src/createEmotionCache';
5 | import { getAppConfig } from '../src/services/app';
6 | import { getTheme } from '../src/theme';
7 |
8 | const appConfig = getAppConfig();
9 | const theme = getTheme(appConfig.theme);
10 |
11 | export default class MyDocument extends Document {
12 | render() {
13 | return (
14 |
15 |
16 | {/* PWA primary color */}
17 |
18 |
19 |
20 |
21 |
25 | {(this.props as any).emotionStyleTags}
26 |
27 |
28 |
29 |
30 |
31 |
32 | );
33 | }
34 | }
35 |
36 | // `getInitialProps` belongs to `_document` (instead of `_app`),
37 | // it's compatible with static-site generation (SSG).
38 | MyDocument.getInitialProps = async (ctx) => {
39 | // Resolution order
40 | //
41 | // On the server:
42 | // 1. app.getInitialProps
43 | // 2. page.getInitialProps
44 | // 3. document.getInitialProps
45 | // 4. app.render
46 | // 5. page.render
47 | // 6. document.render
48 | //
49 | // On the server with error:
50 | // 1. document.getInitialProps
51 | // 2. app.render
52 | // 3. page.render
53 | // 4. document.render
54 | //
55 | // On the client
56 | // 1. app.getInitialProps
57 | // 2. page.getInitialProps
58 | // 3. app.render
59 | // 4. page.render
60 |
61 | const originalRenderPage = ctx.renderPage;
62 |
63 | // You can consider sharing the same emotion cache between all the SSR requests to speed up performance.
64 | // However, be aware that it can have global side effects.
65 | const cache = createEmotionCache();
66 | const { extractCriticalToChunks } = createEmotionServer(cache);
67 |
68 | ctx.renderPage = () =>
69 | originalRenderPage({
70 | enhanceApp: (App: any) =>
71 | function EnhanceApp(props) {
72 | return ;
73 | },
74 | });
75 |
76 | const initialProps = await Document.getInitialProps(ctx);
77 | // This is important. It prevents emotion to render invalid HTML.
78 | // See https://github.com/mui/material-ui/issues/26561#issuecomment-855286153
79 | const emotionStyles = extractCriticalToChunks(initialProps.html);
80 | const emotionStyleTags = emotionStyles.styles.map((style) => (
81 |
87 | ));
88 |
89 | return {
90 | ...initialProps,
91 | emotionStyleTags,
92 | };
93 | };
94 |
--------------------------------------------------------------------------------
/pages/api/token.ts:
--------------------------------------------------------------------------------
1 | import MultiCall, { CallInput } from '@indexed-finance/multicall';
2 | import { Interface, isAddress } from 'ethers/lib/utils';
3 | import type { NextApiRequest, NextApiResponse } from 'next';
4 | import { ERC20Abi } from '../../src/constants/abis';
5 | import { getProviderByChainId } from '../../src/utils/blockchain';
6 |
7 | export default async function handler(
8 | req: NextApiRequest,
9 | res: NextApiResponse
10 | ) {
11 | const { chainId, address } = req.query;
12 |
13 | try {
14 | const contractAddress = address as string;
15 |
16 | if (chainId && isAddress(contractAddress)) {
17 | const provider = getProviderByChainId(parseInt(chainId as string));
18 |
19 | const iface = new Interface(ERC20Abi);
20 |
21 | const calls: CallInput[] = [];
22 |
23 | calls.push({
24 | interface: iface,
25 | target: contractAddress,
26 | function: 'name',
27 | });
28 |
29 | calls.push({
30 | interface: iface,
31 | target: contractAddress,
32 | function: 'symbol',
33 | });
34 |
35 | calls.push({
36 | interface: iface,
37 | target: contractAddress,
38 | function: 'decimals',
39 | });
40 |
41 | const multical = new MultiCall(provider);
42 |
43 | const [, results] = await multical.multiCall(calls);
44 |
45 | return res.json({
46 | name: results[0],
47 | symbol: results[1],
48 | decimals: results[2],
49 | });
50 | } else {
51 | return res.status(404).json({ error: 'chainId or address invalid' });
52 | }
53 | } catch (err) {
54 | return res.status(404).json({});
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/pages/collections.tsx:
--------------------------------------------------------------------------------
1 | import { Container, Grid } from '@mui/material';
2 | import type { GetStaticProps, GetStaticPropsContext, NextPage } from 'next';
3 | import { NextSeo } from 'next-seo';
4 | import { FormattedMessage, useIntl } from 'react-intl';
5 | import { dehydrate, QueryClient } from 'react-query';
6 | import collectionListJson from '../config/default.collectionlist.json';
7 | import MainLayout from '../src/components/layouts/main';
8 | import { PageHeader } from '../src/components/PageHeader';
9 | import { GET_COLLECTION_DATA } from '../src/hooks/nft';
10 | import CollectionCardWithData from '../src/modules/home/components/CollectionCardWithData';
11 | import { getAppConfig } from '../src/services/app';
12 | import { getCollectionData } from '../src/services/nft';
13 | import { getProviderByChainId } from '../src/utils/blockchain';
14 |
15 | const appConfig = getAppConfig();
16 |
17 | const Collections: NextPage = () => {
18 | const { formatMessage } = useIntl();
19 |
20 | return (
21 | <>
22 |
28 |
29 |
30 |
31 |
32 |
37 | ),
38 | uri: '/',
39 | },
40 | {
41 | caption: (
42 |
46 | ),
47 | uri: '/collections',
48 | active: true,
49 | },
50 | ]}
51 | />
52 |
53 | {appConfig.collections?.map((collection, index) => (
54 |
55 |
62 |
63 | ))}
64 |
65 |
66 |
67 | >
68 | );
69 | };
70 |
71 | export default Collections;
72 |
73 | export const getStaticProps: GetStaticProps =
74 | async ({}: GetStaticPropsContext) => {
75 | const queryClient = new QueryClient();
76 |
77 | /* Disabling prefilling data
78 |
79 | for (let collection of collectionListJson.collections) {
80 | const provider = getProviderByChainId(collection.chainId);
81 |
82 | await provider?.ready;
83 |
84 | if (provider) {
85 | const data = await getCollectionData(
86 | provider,
87 | collection.contractAddress
88 | );
89 |
90 | await queryClient.prefetchQuery(
91 | [GET_COLLECTION_DATA, collection.contractAddress, collection.chainId],
92 | async () => data
93 | );
94 | }
95 | }*/
96 |
97 | return {
98 | props: {
99 | dehydratedState: dehydrate(queryClient),
100 | },
101 | };
102 | };
103 |
--------------------------------------------------------------------------------
/public/assets/images/ethereum.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/assets/images/icons/arbitrum.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DexKit/open-nft-marketplace/1fe4fe60adebf18b667bc051a060c40578c0457e/public/assets/images/icons/arbitrum.png
--------------------------------------------------------------------------------
/public/assets/images/icons/avax.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DexKit/open-nft-marketplace/1fe4fe60adebf18b667bc051a060c40578c0457e/public/assets/images/icons/avax.png
--------------------------------------------------------------------------------
/public/assets/images/icons/eth.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DexKit/open-nft-marketplace/1fe4fe60adebf18b667bc051a060c40578c0457e/public/assets/images/icons/eth.png
--------------------------------------------------------------------------------
/public/assets/images/icons/fantom.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/public/assets/images/icons/optimism.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/assets/images/icons/polygon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DexKit/open-nft-marketplace/1fe4fe60adebf18b667bc051a060c40578c0457e/public/assets/images/icons/polygon.png
--------------------------------------------------------------------------------
/public/assets/images/icons/setting-2.svg:
--------------------------------------------------------------------------------
1 |
5 |
--------------------------------------------------------------------------------
/public/assets/images/walletconnect-circle-blue.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/public/assets/kittygotchi/kittygotchi_banner.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DexKit/open-nft-marketplace/1fe4fe60adebf18b667bc051a060c40578c0457e/public/assets/kittygotchi/kittygotchi_banner.jpg
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DexKit/open-nft-marketplace/1fe4fe60adebf18b667bc051a060c40578c0457e/public/favicon.ico
--------------------------------------------------------------------------------
/public/robots.txt:
--------------------------------------------------------------------------------
1 | # *
2 | User-agent: *
3 | Allow: /
4 |
5 | # Host
6 | Host: https://www.kittygotchi.com
7 |
8 | # Sitemaps
9 | Sitemap: https://www.kittygotchi.com/sitemap.xml
10 |
--------------------------------------------------------------------------------
/public/sitemap-0.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | https://www.kittygotchi.com2022-07-15T15:09:54.867Zdaily0.7
4 | https://www.kittygotchi.com/collections2022-07-15T15:09:54.867Zdaily0.7
5 | https://www.kittygotchi.com/order2022-07-15T15:09:54.867Zdaily0.7
6 | https://www.kittygotchi.com/swap2022-07-15T15:09:54.867Zdaily0.7
7 | https://www.kittygotchi.com/wallet2022-07-15T15:09:54.867Zdaily0.7
8 | https://www.kittygotchi.com/wallet/connect2022-07-15T15:09:54.867Zdaily0.7
9 | https://www.kittygotchi.com/wallet/favorites2022-07-15T15:09:54.867Zdaily0.7
10 | https://www.kittygotchi.com/wallet/orders2022-07-15T15:09:54.867Zdaily0.7
11 |
--------------------------------------------------------------------------------
/public/sitemap.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | https://www.kittygotchi.com/sitemap-0.xml
4 |
--------------------------------------------------------------------------------
/src/actions/index.ts:
--------------------------------------------------------------------------------
1 | import { Action } from '../types/actions';
2 | import { Transaction } from '../types/blockchain';
3 |
4 | export const CREATE_NOTIFICATION = 'CREATE_NOTIFICATION';
5 |
6 | export function createNotificationAction(
7 | transaction: Transaction
8 | ): Action {
9 | return { action: CREATE_NOTIFICATION, payload: transaction };
10 | }
11 |
12 | export const UPDATE_NOTIFICATION = 'UPDATE_NOTIFICATION';
13 |
14 | export function updateNotificationAction(
15 | transaction: Transaction
16 | ): Action {
17 | return { action: UPDATE_NOTIFICATION, payload: transaction };
18 | }
19 |
20 | export const REMOVE_NOTIFICATION = 'REMOVE_NOTIFICATION';
21 |
22 | export function removeNotification(
23 | transaction: Transaction
24 | ): Action {
25 | return { action: REMOVE_NOTIFICATION, payload: transaction };
26 | }
27 |
--------------------------------------------------------------------------------
/src/components/AppDialogTitle.tsx:
--------------------------------------------------------------------------------
1 | import { DialogTitle, IconButton, Stack, Typography } from '@mui/material';
2 |
3 | import CloseIcon from '@mui/icons-material/Close';
4 |
5 | interface Props {
6 | title?: string | React.ReactNode | React.ReactNode[];
7 | icon?: React.ReactNode | React.ReactNode[];
8 | onClose?: () => void;
9 | disableClose?: boolean;
10 | }
11 |
12 | export function AppDialogTitle({ title, icon, onClose, disableClose }: Props) {
13 | return (
14 |
23 |
29 | {icon}
30 | {title}
31 |
32 | {onClose && (
33 |
34 |
35 |
36 | )}
37 |
38 | );
39 | }
40 |
--------------------------------------------------------------------------------
/src/components/AppErrorBoundary.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { ErrorBoundary, FallbackProps } from 'react-error-boundary';
3 | import { QueryErrorResetBoundary } from 'react-query';
4 |
5 | interface Props {
6 | fallbackRender: (
7 | props: FallbackProps
8 | ) => React.ReactElement<
9 | unknown,
10 | string | typeof React.Component | React.FunctionComponent<{}>
11 | > | null;
12 |
13 | children: React.ReactNode | React.ReactNode[];
14 | }
15 |
16 | export function AppErrorBoundary({ fallbackRender, children }: Props) {
17 | return (
18 |
19 | {({ reset }) => (
20 |
21 | {children}
22 |
23 | )}
24 |
25 | );
26 | }
27 |
--------------------------------------------------------------------------------
/src/components/AppFeePercentageSpan.tsx:
--------------------------------------------------------------------------------
1 | import { memo, useMemo } from 'react';
2 | import { getAppConfig } from '../services/app';
3 |
4 | const appConfig = getAppConfig();
5 |
6 | function AppFeePercentageSpan() {
7 | const { fees } = appConfig;
8 |
9 | const feeTotal = useMemo(() => {
10 | if (fees) {
11 | return fees?.map((f) => f.amount_percentage).reduce((p, c) => p + c, 0);
12 | }
13 |
14 | return 0;
15 | }, [fees]);
16 |
17 | return {feeTotal}%;
18 | }
19 |
20 | export default memo(AppFeePercentageSpan);
21 |
--------------------------------------------------------------------------------
/src/components/AppIntlProvider.tsx:
--------------------------------------------------------------------------------
1 | import { useAtomValue } from 'jotai';
2 | import { IntlProvider } from 'react-intl';
3 | import { getAppConfig } from '../services/app';
4 | import { localeAtom } from '../state/atoms';
5 | import { loadLocaleData } from '../utils/intl';
6 |
7 | interface Props {
8 | children?: React.ReactNode | React.ReactNode[];
9 | }
10 |
11 | const appConfig = getAppConfig();
12 |
13 | function AppIntlProvider({ children }: Props) {
14 | const locale = useAtomValue(localeAtom);
15 |
16 | return (
17 |
22 | {children}
23 |
24 | );
25 | }
26 |
27 | export default AppIntlProvider;
28 |
--------------------------------------------------------------------------------
/src/components/CopyIconButton.tsx:
--------------------------------------------------------------------------------
1 | import { IconButton, IconButtonProps, Tooltip } from '@mui/material';
2 | import { MouseEvent, useState } from 'react';
3 |
4 | interface Props {
5 | iconButtonProps: IconButtonProps;
6 | tooltip?: string;
7 | activeTooltip?: string;
8 | children?: React.ReactNode | React.ReactNode[];
9 | }
10 |
11 | export function CopyIconButton(props: Props) {
12 | const { tooltip, activeTooltip, iconButtonProps, children } = props;
13 | const { onClick } = iconButtonProps;
14 |
15 | const [currentTooltip, setCurrentTooltip] = useState(tooltip || '');
16 |
17 | const handleClick = (e: MouseEvent) => {
18 | onClick!(e);
19 | setCurrentTooltip(activeTooltip || '');
20 | setTimeout(() => {
21 | setCurrentTooltip(tooltip || '');
22 | }, 500);
23 | };
24 |
25 | return (
26 |
27 |
31 | {children}
32 |
33 |
34 | );
35 | }
36 |
--------------------------------------------------------------------------------
/src/components/DatetimeFromNowSpan.tsx:
--------------------------------------------------------------------------------
1 | import moment from 'moment';
2 | import { memo } from 'react';
3 |
4 | export function DatetimeFromNowSpan({
5 | value,
6 | }: {
7 | value: number;
8 | format?: string;
9 | }) {
10 | return {moment(new Date(value)).fromNow()};
11 | }
12 |
13 | export default memo(DatetimeFromNowSpan);
14 |
--------------------------------------------------------------------------------
/src/components/Footer.tsx:
--------------------------------------------------------------------------------
1 | import InstagramIcon from '@mui/icons-material/Instagram';
2 |
3 | import {
4 | Box,
5 | Container,
6 | Grid,
7 | IconButton,
8 | Stack,
9 | Typography,
10 | } from '@mui/material'; // always use @mui/material instead of @mui/system
11 |
12 | import TwitterIcon from '@mui/icons-material/Twitter';
13 | import { FormattedMessage } from 'react-intl';
14 | import { getAppConfig } from '../services/app';
15 | import { SocialMedia } from '../types/config';
16 | import Link from './Link';
17 |
18 | const appConfig = getAppConfig();
19 |
20 | export function Footer() {
21 | const renderIcon = (media: SocialMedia) => {
22 | if (media.type === 'instagram') {
23 | return ;
24 | } else if (media.type === 'twitter') {
25 | return ;
26 | }
27 | };
28 |
29 | const renderLink = (media: SocialMedia) => {
30 | if (media.type === 'instagram') {
31 | return `https://instagram.com/${media.handle}`;
32 | } else if (media.type === 'twitter') {
33 | return `https://twitter.com/${media.handle}`;
34 | }
35 |
36 | return '';
37 | };
38 |
39 | return (
40 | theme.palette.background.paper }}>
41 |
42 |
50 |
51 |
56 |
61 |
62 |
63 |
64 |
65 |
66 | {appConfig.name}
67 | {' '}
68 | {' '}
73 |
74 | 0x
75 | {' '}
76 | {' '}
81 |
87 | DexKit
88 |
89 |
90 |
91 |
92 |
93 | {appConfig?.social &&
94 | appConfig.social.map((media, index) => (
95 |
102 | {renderIcon(media)}
103 |
104 | ))}
105 |
106 |
107 |
108 |
109 |
110 | );
111 | }
112 |
--------------------------------------------------------------------------------
/src/components/Icon.tsx:
--------------------------------------------------------------------------------
1 | import { styled } from '@mui/material/styles';
2 | import Ethereum from './icons/Ethereum';
3 | import Export from './icons/Export';
4 | import RotateRight from './icons/RotateRight';
5 | import Setting from './icons/Setting';
6 | import Share from './icons/Share';
7 | import Tag from './icons/Tag';
8 |
9 | interface Props {
10 | icon: string;
11 | color?: 'primary';
12 | size: 'small' | 'medium' | 'large';
13 | }
14 |
15 | const sizes = {
16 | small: { width: 14, heigth: 14 },
17 | medium: { width: 18, heigth: 18 },
18 | large: { width: 34, heigth: 34 },
19 | };
20 |
21 | export const Icon = ({ icon, ...otherProps }: Props) => {
22 | switch (icon) {
23 | case 'ethereum':
24 | return ;
25 | case 'settings':
26 | return ;
27 | case 'share':
28 | return ;
29 | case 'tag':
30 | return ;
31 | case 'rotate-right':
32 | return ;
33 | case 'export':
34 | return ;
35 | }
36 |
37 | return null;
38 | };
39 |
40 | export default styled(Icon, {
41 | shouldForwardProp: (prop) =>
42 | prop !== 'color' && prop !== 'variant' && prop !== 'sx',
43 | })(({ theme, color }) => ({
44 | stroke:
45 | color === 'primary'
46 | ? theme.palette.primary.main
47 | : theme.palette.text.primary,
48 | }));
49 |
--------------------------------------------------------------------------------
/src/components/LazyYoutubeFrame.tsx:
--------------------------------------------------------------------------------
1 | import useIntersectionObserver from '@react-hook/intersection-observer';
2 | import { useRef } from 'react';
3 |
4 | interface Props {
5 | url?: string;
6 | title?: string;
7 | }
8 |
9 | const LazyYoutubeFrame = ({ url, title }: Props) => {
10 | const containerRef = useRef(null);
11 | const lockRef = useRef(false);
12 |
13 | const { isIntersecting } = useIntersectionObserver(containerRef);
14 |
15 | if (isIntersecting && !lockRef.current) {
16 | lockRef.current = true;
17 | }
18 |
19 | return (
20 | {
22 | if (ref) {
23 | containerRef.current = ref;
24 | }
25 | }}
26 | >
27 | {lockRef.current && (
28 |
37 | )}
38 |
39 | );
40 | };
41 |
42 | export default LazyYoutubeFrame;
43 |
--------------------------------------------------------------------------------
/src/components/Link.tsx:
--------------------------------------------------------------------------------
1 | import MuiLink, { LinkProps as MuiLinkProps } from '@mui/material/Link';
2 | import { styled } from '@mui/material/styles';
3 | import clsx from 'clsx';
4 | import NextLink, { LinkProps as NextLinkProps } from 'next/link';
5 | import { useRouter } from 'next/router';
6 | import * as React from 'react';
7 |
8 | // Add support for the sx prop for consistency with the other branches.
9 | const Anchor = styled('a')({});
10 |
11 | interface NextLinkComposedProps
12 | extends Omit, 'href'>,
13 | Omit<
14 | NextLinkProps,
15 | 'href' | 'as' | 'onClick' | 'onMouseEnter' | 'onTouchStart'
16 | > {
17 | to: NextLinkProps['href'];
18 | linkAs?: NextLinkProps['as'];
19 | }
20 |
21 | export const NextLinkComposed = React.forwardRef<
22 | HTMLAnchorElement,
23 | NextLinkComposedProps
24 | >(function NextLinkComposed(props, ref) {
25 | const { to, linkAs, replace, scroll, shallow, prefetch, locale, ...other } =
26 | props;
27 |
28 | return (
29 |
39 |
40 |
41 | );
42 | });
43 |
44 | export type LinkProps = {
45 | activeClassName?: string;
46 | as?: NextLinkProps['as'];
47 | href: NextLinkProps['href'];
48 | linkAs?: NextLinkProps['as']; // Useful when the as prop is shallow by styled().
49 | noLinkStyle?: boolean;
50 | } & Omit &
51 | Omit;
52 |
53 | // A styled version of the Next.js Link component:
54 | // https://nextjs.org/docs/api-reference/next/link
55 | const Link = React.forwardRef(function Link(
56 | props,
57 | ref
58 | ) {
59 | const {
60 | activeClassName = 'active',
61 | as,
62 | className: classNameProps,
63 | href,
64 | linkAs: linkAsProp,
65 | locale,
66 | noLinkStyle,
67 | prefetch,
68 | replace,
69 | role, // Link don't have roles.
70 | scroll,
71 | shallow,
72 | ...other
73 | } = props;
74 |
75 | const router = useRouter();
76 | const pathname = typeof href === 'string' ? href : href.pathname;
77 | const className = clsx(classNameProps, {
78 | [activeClassName]: router.pathname === pathname && activeClassName,
79 | });
80 |
81 | const isExternal =
82 | typeof href === 'string' &&
83 | (href.indexOf('http') === 0 || href.indexOf('mailto:') === 0);
84 |
85 | if (isExternal) {
86 | if (noLinkStyle) {
87 | return ;
88 | }
89 |
90 | return ;
91 | }
92 |
93 | const linkAs = linkAsProp || as;
94 | const nextjsProps = {
95 | to: href,
96 | linkAs,
97 | replace,
98 | scroll,
99 | shallow,
100 | prefetch,
101 | locale,
102 | };
103 |
104 | if (noLinkStyle) {
105 | return (
106 |
112 | );
113 | }
114 |
115 | return (
116 |
123 | );
124 | });
125 |
126 | export default Link;
127 |
--------------------------------------------------------------------------------
/src/components/MomentFromNow.tsx:
--------------------------------------------------------------------------------
1 | import { useAtomValue } from 'jotai';
2 | import moment from 'moment';
3 | import { memo } from 'react';
4 | import { localeAtom } from '../state/atoms';
5 |
6 | interface Props {
7 | from: moment.Moment;
8 | }
9 |
10 | function MomentFromNow({ from }: Props) {
11 | const locale = useAtomValue(localeAtom);
12 | const datetime = moment(from);
13 |
14 | datetime.locale(locale);
15 |
16 | return {datetime.fromNow()};
17 | }
18 |
19 | export default memo(MomentFromNow);
20 |
--------------------------------------------------------------------------------
/src/components/PageHeader.tsx:
--------------------------------------------------------------------------------
1 | import {
2 | Breadcrumbs,
3 | IconButton,
4 | Stack,
5 | styled,
6 | Typography,
7 | useMediaQuery,
8 | useTheme,
9 | } from '@mui/material';
10 | import Link from './Link';
11 |
12 | import ArrowBackIcon from '@mui/icons-material/ArrowBack';
13 | import NavigateNextIcon from '@mui/icons-material/NavigateNext';
14 | import { useRouter } from 'next/router';
15 |
16 | interface Props {
17 | breadcrumbs: { caption: React.ReactNode; uri: string; active?: boolean }[];
18 | }
19 |
20 | const CustomLink = styled(Link)({
21 | fontWeight: 600,
22 | textDecoration: 'none',
23 | });
24 |
25 | export function PageHeader({ breadcrumbs }: Props) {
26 | const theme = useTheme();
27 | const isMobile = useMediaQuery(theme.breakpoints.down('sm'));
28 | const router = useRouter();
29 |
30 | const renderActiveBreadcrumb = () => {
31 | const breadcrumb = breadcrumbs.find((b) => b.active);
32 |
33 | if (breadcrumb) {
34 | return (
35 |
44 | {breadcrumb.caption}
45 |
46 | );
47 | }
48 | return;
49 | };
50 |
51 | const handleGoBack = () => router.back();
52 |
53 | return (
54 |
55 | {isMobile ? (
56 |
62 |
63 |
64 |
65 |
66 | {renderActiveBreadcrumb()}
67 |
68 | ) : (
69 | }
71 | >
72 | {breadcrumbs.map((breadcrumb, index) => (
73 |
78 | {breadcrumb.caption}
79 |
80 | ))}
81 |
82 | )}
83 |
84 | );
85 | }
86 |
--------------------------------------------------------------------------------
/src/components/SidebarFilters.tsx:
--------------------------------------------------------------------------------
1 | import ArrowBackIcon from '@mui/icons-material/ArrowBack';
2 | import { Box, Divider, IconButton, Stack, Typography } from '@mui/material';
3 | import React from 'react';
4 | import Funnel from './icons/Filter';
5 | import SidebarFiltersContent from './SidebarFiltersContent';
6 |
7 | interface Props {
8 | title: React.ReactNode | React.ReactNode[];
9 | children?: React.ReactNode | React.ReactNode[];
10 | onClose?: () => void;
11 | }
12 |
13 | export default function SidebarFilters({ onClose, title, children }: Props) {
14 | return (
15 | theme.palette.background.paper,
18 | height: '100%',
19 | }}
20 | >
21 |
22 |
28 |
29 |
30 |
31 | {title}
32 |
33 |
34 | {onClose && (
35 |
36 |
37 |
38 | )}
39 |
40 |
41 |
42 | {children}
43 |
44 | );
45 | }
46 |
--------------------------------------------------------------------------------
/src/components/SidebarFiltersAccordion.tsx:
--------------------------------------------------------------------------------
1 | import {
2 | Accordion,
3 | AccordionDetails,
4 | AccordionSummary,
5 | Divider,
6 | Typography,
7 | } from '@mui/material';
8 |
9 | import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
10 |
11 | interface Props {
12 | title: React.ReactNode | React.ReactNode[];
13 | children?: React.ReactNode | React.ReactNode[];
14 | expanded?: boolean;
15 | }
16 |
17 | export default function SidebarFiltersAccordion({
18 | title,
19 | children,
20 | expanded,
21 | }: Props) {
22 | return (
23 |
29 | }>
30 |
31 | {title}
32 |
33 |
34 |
35 | {children}
36 |
37 | );
38 | }
39 |
--------------------------------------------------------------------------------
/src/components/SidebarFiltersContent.tsx:
--------------------------------------------------------------------------------
1 | import { Box } from '@mui/material';
2 |
3 | interface Props {
4 | children?: React.ReactNode | React.ReactNode[];
5 | }
6 |
7 | export default function SidebarFiltersContent({ children }: Props) {
8 | return {children};
9 | }
10 |
--------------------------------------------------------------------------------
/src/components/WalletButton.tsx:
--------------------------------------------------------------------------------
1 | import { Logout } from '@mui/icons-material';
2 | import {
3 | Avatar,
4 | ButtonBase,
5 | ListItemIcon,
6 | Menu,
7 | MenuItem,
8 | Stack,
9 | Typography,
10 | } from '@mui/material';
11 | import { useWeb3React } from '@web3-react/core';
12 | import { useAtomValue } from 'jotai';
13 | import { useCallback, useState } from 'react';
14 | import { isBalancesVisibleAtom } from '../state/atoms';
15 | import { getWalletIcon, truncateAddress } from '../utils/blockchain';
16 | import { FormattedMessage } from 'react-intl';
17 |
18 | interface Props {
19 | align?: 'center' | 'left';
20 | }
21 |
22 | export function WalletButton(props: Props) {
23 | const { align } = props;
24 | const { connector, account, ENSName } = useWeb3React();
25 | const [anchorEl, setAnchorEl] = useState(null);
26 | const open = Boolean(anchorEl);
27 | const handleClick = (event: any) => {
28 | setAnchorEl(event.currentTarget);
29 | };
30 | const handleClose = () => {
31 | setAnchorEl(null);
32 | };
33 |
34 | const isBalancesVisible = useAtomValue(isBalancesVisibleAtom);
35 |
36 | const justifyContent = align === 'left' ? 'flex-start' : 'center';
37 |
38 | const handleLogoutWallet = useCallback(() => {
39 | if (connector?.deactivate) {
40 | connector.deactivate();
41 | }
42 | }, [connector]);
43 |
44 | return (
45 | <>
46 | ({
49 | px: 2,
50 | py: 1,
51 | border: `1px solid ${theme.palette.divider}`,
52 | borderRadius: theme.spacing(1),
53 | justifyContent,
54 | })}
55 | onClick={handleClick}
56 | >
57 |
58 | ({
61 | width: theme.spacing(2),
62 | height: theme.spacing(2),
63 | })}
64 | />
65 |
66 | {isBalancesVisible
67 | ? ENSName
68 | ? ENSName
69 | : truncateAddress(account)
70 | : '**********'}
71 |
72 |
73 |
74 |
92 | >
93 | );
94 | }
95 |
--------------------------------------------------------------------------------
/src/components/dialogs/AssetApprovalDialog.tsx:
--------------------------------------------------------------------------------
1 | import {
2 | Box,
3 | Button,
4 | Dialog,
5 | DialogContent,
6 | DialogProps,
7 | Stack,
8 | Typography,
9 | } from '@mui/material';
10 | import { useWeb3React } from '@web3-react/core';
11 | import { FormattedMessage } from 'react-intl';
12 | import { TransactionStatus } from '../../types/blockchain';
13 | import { getBlockExplorerUrl } from '../../utils/blockchain';
14 |
15 | interface Props {
16 | hash?: string;
17 | status: TransactionStatus;
18 | dialogProps: DialogProps;
19 | title?: string | React.ReactNode | React.ReactNode[];
20 | icon?: React.ReactNode | React.ReactNode[];
21 | }
22 |
23 | export function AssetApprovalDialog({
24 | dialogProps,
25 | status,
26 | hash,
27 | icon,
28 | title,
29 | }: Props) {
30 | const { onClose } = dialogProps;
31 |
32 | const { chainId } = useWeb3React();
33 |
34 | return (
35 |
63 | );
64 | }
65 |
66 | export default AssetApprovalDialog;
67 |
--------------------------------------------------------------------------------
/src/components/dialogs/SelectCurrencyDialog.tsx:
--------------------------------------------------------------------------------
1 | import {
2 | Button,
3 | Dialog,
4 | DialogActions,
5 | DialogContent,
6 | DialogProps,
7 | List,
8 | ListItemButton,
9 | ListItemSecondaryAction,
10 | ListItemText,
11 | Radio,
12 | Stack,
13 | } from '@mui/material';
14 | import { useAtom } from 'jotai';
15 | import { useState } from 'react';
16 | import { FormattedMessage } from 'react-intl';
17 | import { CURRENCIES } from '../../constants';
18 | import { currencyAtom } from '../../state/atoms';
19 | import { Currency } from '../../types/app';
20 | import { AppDialogTitle } from '../AppDialogTitle';
21 |
22 | interface Props {
23 | dialogProps: DialogProps;
24 | }
25 |
26 | function SelectCurrencyDialog({ dialogProps }: Props) {
27 | const { onClose } = dialogProps;
28 |
29 | const [currency, setCurrency] = useAtom(currencyAtom);
30 |
31 | const [selectedCurrency, setSelectedCurrency] = useState(currency);
32 |
33 | const handleClose = () => {
34 | if (onClose) {
35 | onClose({}, 'backdropClick');
36 | }
37 | };
38 |
39 | const handleSelectCurrency = (currency: string) => {
40 | setSelectedCurrency(currency);
41 | };
42 |
43 | const handleConfirmSelect = () => {
44 | setCurrency(selectedCurrency);
45 | handleClose();
46 | };
47 |
48 | return (
49 |
102 | );
103 | }
104 |
105 | export default SelectCurrencyDialog;
106 |
--------------------------------------------------------------------------------
/src/components/dialogs/SelectLanguageDialog.tsx:
--------------------------------------------------------------------------------
1 | import {
2 | Button,
3 | Dialog,
4 | DialogActions,
5 | DialogContent,
6 | DialogProps,
7 | List,
8 | ListItemButton,
9 | ListItemSecondaryAction,
10 | ListItemText,
11 | Radio,
12 | Stack,
13 | } from '@mui/material';
14 | import { useAtom } from 'jotai';
15 | import { useState } from 'react';
16 | import { FormattedMessage } from 'react-intl';
17 | import { LANGUAGES } from '../../constants';
18 | import { localeAtom } from '../../state/atoms';
19 | import { Language } from '../../types/app';
20 | import { AppDialogTitle } from '../AppDialogTitle';
21 |
22 | interface Props {
23 | dialogProps: DialogProps;
24 | }
25 |
26 | function SelectLanguageDialog({ dialogProps }: Props) {
27 | const { onClose } = dialogProps;
28 |
29 | const [locale, setLocale] = useAtom(localeAtom);
30 |
31 | const [selectedLocale, setSelectedLocale] = useState(locale);
32 |
33 | const handleClose = () => {
34 | if (onClose) {
35 | onClose({}, 'backdropClick');
36 | }
37 | };
38 |
39 | const handleSelectLocale = (locale: string) => {
40 | setSelectedLocale(locale);
41 | };
42 |
43 | const handleConfirmSelect = () => {
44 | setLocale(selectedLocale);
45 | handleClose();
46 | };
47 |
48 | return (
49 |
99 | );
100 | }
101 |
102 | export default SelectLanguageDialog;
103 |
--------------------------------------------------------------------------------
/src/components/dialogs/SignMessageDialog.tsx:
--------------------------------------------------------------------------------
1 | import CloseIcon from '@mui/icons-material/Close';
2 | import {
3 | Box,
4 | CircularProgress,
5 | Dialog,
6 | DialogContent,
7 | DialogProps,
8 | DialogTitle,
9 | Divider,
10 | IconButton,
11 | Stack,
12 | Typography,
13 | } from '@mui/material';
14 | import { FormattedMessage } from 'react-intl';
15 | import CloseCircle from '../icons/CloseCircle';
16 | import ReceiptText from '../icons/ReceiptText';
17 | import TickCircle from '../icons/TickCircle';
18 |
19 | interface Props {
20 | dialogProps: DialogProps;
21 | error?: Error;
22 | success?: boolean;
23 | message?: string;
24 | }
25 |
26 | export function SignMessageDialog({
27 | dialogProps,
28 | error,
29 | success,
30 | message,
31 | }: Props) {
32 | const { onClose } = dialogProps;
33 |
34 | const handleClose = () => onClose!({}, 'backdropClick');
35 |
36 | const renderContent = () => {
37 | if (error !== undefined) {
38 | return (
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 | {error?.message}
47 |
48 |
49 |
50 | );
51 | } else {
52 | return (
53 |
54 | {success ? (
55 |
56 | ) : (
57 |
58 | )}
59 |
60 |
61 |
66 |
67 |
68 |
72 |
73 |
74 |
75 | {message}
76 |
77 |
78 | );
79 | }
80 | };
81 |
82 | return (
83 |
117 | );
118 | }
119 |
120 | export default SignMessageDialog;
121 |
--------------------------------------------------------------------------------
/src/components/dialogs/SwitchNetworkDialog.tsx:
--------------------------------------------------------------------------------
1 | import {
2 | Alert,
3 | Button,
4 | CircularProgress,
5 | Dialog,
6 | DialogActions,
7 | DialogContent,
8 | DialogProps,
9 | Stack,
10 | Typography,
11 | } from '@mui/material';
12 | import { FormattedMessage } from 'react-intl';
13 | import { useSwitchNetworkMutation } from '../../hooks/blockchain';
14 | import { getChainName } from '../../utils/blockchain';
15 | import { AppDialogTitle } from '../AppDialogTitle';
16 |
17 | interface Props {
18 | dialogProps: DialogProps;
19 | chainId?: number;
20 | }
21 |
22 | export function SwitchNetworkDialog({ dialogProps, chainId }: Props) {
23 | const { onClose } = dialogProps;
24 |
25 | const switchNetworkMutation = useSwitchNetworkMutation();
26 |
27 | const handleClose = () => onClose!({}, 'backdropClick');
28 |
29 | const handleSwitchNetwork = async () => {
30 | if (chainId !== undefined) {
31 | await switchNetworkMutation.mutateAsync({ chainId });
32 | handleClose();
33 | }
34 | };
35 |
36 | const handleReset = () => {
37 | switchNetworkMutation.reset();
38 | };
39 |
40 | return (
41 |
99 | );
100 | }
101 |
--------------------------------------------------------------------------------
/src/components/icons/ArrowSwap.tsx:
--------------------------------------------------------------------------------
1 | import { SvgIcon, SvgIconProps } from '@mui/material';
2 |
3 | const ArrowSwap = (props: SvgIconProps) => (
4 |
5 |
41 |
42 | );
43 |
44 | export default ArrowSwap;
45 |
--------------------------------------------------------------------------------
/src/components/icons/Calendar.tsx:
--------------------------------------------------------------------------------
1 | import { SvgIcon, SvgIconProps } from '@mui/material';
2 |
3 | const Calendar = (props: SvgIconProps) => (
4 |
5 |
66 |
67 | );
68 |
69 | export default Calendar;
70 |
--------------------------------------------------------------------------------
/src/components/icons/CardTick.tsx:
--------------------------------------------------------------------------------
1 | import { SvgIcon, SvgIconProps } from '@mui/material';
2 |
3 | const CardTick = (props: SvgIconProps) => (
4 |
5 |
51 |
52 | );
53 |
54 | export default CardTick;
55 |
--------------------------------------------------------------------------------
/src/components/icons/CloseCircle.tsx:
--------------------------------------------------------------------------------
1 | import { SvgIcon, SvgIconProps } from '@mui/material';
2 |
3 | const CloseCircle = (props: SvgIconProps) => (
4 |
5 |
34 |
35 | );
36 |
37 | export default CloseCircle;
38 |
--------------------------------------------------------------------------------
/src/components/icons/DollarSquare.tsx:
--------------------------------------------------------------------------------
1 | import { SvgIcon, SvgIconProps } from '@mui/material';
2 |
3 | const DollarSquare = (props: SvgIconProps) => (
4 |
5 |
34 |
35 | );
36 |
37 | export default DollarSquare;
38 |
--------------------------------------------------------------------------------
/src/components/icons/Ethereum.tsx:
--------------------------------------------------------------------------------
1 | import { SvgIcon, SvgIconProps } from '@mui/material';
2 |
3 | const Ethereum = (props: SvgIconProps) => (
4 |
5 |
31 |
32 | );
33 |
34 | export default Ethereum;
35 |
--------------------------------------------------------------------------------
/src/components/icons/Export.tsx:
--------------------------------------------------------------------------------
1 | import { SvgIcon, SvgIconProps } from '@mui/material';
2 |
3 | const Export = (props: SvgIconProps) => (
4 |
5 |
34 |
35 | );
36 |
37 | export default Export;
38 |
--------------------------------------------------------------------------------
/src/components/icons/Filter.tsx:
--------------------------------------------------------------------------------
1 | import { SvgIcon, SvgIconProps } from '@mui/material';
2 |
3 | const Funnel = (props: SvgIconProps) => (
4 |
5 |
14 |
15 | );
16 |
17 | export default Funnel;
18 |
--------------------------------------------------------------------------------
/src/components/icons/Heart.tsx:
--------------------------------------------------------------------------------
1 | import { SvgIcon, SvgIconProps } from '@mui/material';
2 |
3 | const Heart = (props: SvgIconProps) => (
4 |
5 |
20 |
21 | );
22 |
23 | export default Heart;
24 |
--------------------------------------------------------------------------------
/src/components/icons/MoneyReceive.tsx:
--------------------------------------------------------------------------------
1 | import { SvgIcon, SvgIconProps } from '@mui/material';
2 |
3 | const MoneyReceive = (props: SvgIconProps) => (
4 |
5 |
48 |
49 | );
50 |
51 | export default MoneyReceive;
52 |
--------------------------------------------------------------------------------
/src/components/icons/MoneySend.tsx:
--------------------------------------------------------------------------------
1 | import { SvgIcon, SvgIconProps } from '@mui/material';
2 |
3 | const MoneySend = (props: SvgIconProps) => (
4 |
5 |
48 |
49 | );
50 |
51 | export default MoneySend;
52 |
--------------------------------------------------------------------------------
/src/components/icons/Notification.tsx:
--------------------------------------------------------------------------------
1 | import { SvgIcon, SvgIconProps } from '@mui/material';
2 |
3 | const Notification = (props: SvgIconProps) => (
4 |
5 |
34 |
35 | );
36 |
37 | export default Notification;
38 |
--------------------------------------------------------------------------------
/src/components/icons/ReceiptText.tsx:
--------------------------------------------------------------------------------
1 | import { SvgIcon, SvgIconProps } from '@mui/material';
2 |
3 | const ReceiptText = (props: SvgIconProps) => (
4 |
5 |
43 |
44 | );
45 |
46 | export default ReceiptText;
47 |
--------------------------------------------------------------------------------
/src/components/icons/RotateRight.tsx:
--------------------------------------------------------------------------------
1 | import { SvgIcon, SvgIconProps } from '@mui/material';
2 |
3 | const RotateRight = (props: SvgIconProps) => (
4 |
5 |
35 |
36 | );
37 |
38 | export default RotateRight;
39 |
--------------------------------------------------------------------------------
/src/components/icons/Setting.tsx:
--------------------------------------------------------------------------------
1 | import { SvgIcon, SvgIconProps } from '@mui/material';
2 |
3 | const Setting = (props: SvgIconProps) => (
4 |
5 |
30 |
31 | );
32 |
33 | export default Setting;
34 |
--------------------------------------------------------------------------------
/src/components/icons/Share.tsx:
--------------------------------------------------------------------------------
1 | import { SvgIcon, SvgIconProps } from '@mui/material';
2 |
3 | const Share = (props: SvgIconProps) => (
4 |
5 |
56 |
57 | );
58 |
59 | export default Share;
60 |
--------------------------------------------------------------------------------
/src/components/icons/Tag.tsx:
--------------------------------------------------------------------------------
1 | import { SvgIcon, SvgIconProps } from '@mui/material';
2 |
3 | const Tag = (props: SvgIconProps) => (
4 |
5 |
27 |
28 | );
29 |
30 | export default Tag;
31 |
--------------------------------------------------------------------------------
/src/components/icons/TickCircle.tsx:
--------------------------------------------------------------------------------
1 | import { SvgIcon, SvgIconProps } from '@mui/material';
2 |
3 | const TickCircle = (props: SvgIconProps) => (
4 |
5 |
27 |
28 | );
29 |
30 | export default TickCircle;
31 |
--------------------------------------------------------------------------------
/src/components/icons/Wallet.tsx:
--------------------------------------------------------------------------------
1 | import { SvgIcon, SvgIconProps } from '@mui/material';
2 |
3 | const Wallet = (props: SvgIconProps) => (
4 |
5 |
34 |
35 | );
36 |
37 | export default Wallet;
38 |
--------------------------------------------------------------------------------
/src/components/transactions/Updater.tsx:
--------------------------------------------------------------------------------
1 | import { useWeb3React } from '@web3-react/core';
2 | import { useAtom } from 'jotai';
3 | import { useSnackbar } from 'notistack';
4 | import { useCallback, useEffect } from 'react';
5 | import { useIntl } from 'react-intl';
6 | import { useBlockNumber } from '../../hooks/blockchain';
7 | import { pendingTransactionsAtom } from '../../state/atoms';
8 | import { TransactionStatus } from '../../types/blockchain';
9 |
10 | export function Updater() {
11 | const { chainId, provider } = useWeb3React();
12 | const [pendingTransactions, setPendingTransactions] = useAtom(
13 | pendingTransactionsAtom
14 | );
15 | const blockNumber = useBlockNumber();
16 |
17 | const { enqueueSnackbar } = useSnackbar();
18 | const { formatMessage } = useIntl();
19 |
20 | const getReceipt = useCallback(
21 | (hash: string) => {
22 | if (provider !== undefined || chainId !== undefined) {
23 | return provider?.getTransactionReceipt(hash);
24 | }
25 | },
26 | [chainId, provider]
27 | );
28 |
29 | useEffect(() => {
30 | if (
31 | chainId !== undefined &&
32 | blockNumber !== undefined &&
33 | pendingTransactions
34 | ) {
35 | const cancels = Object.keys(pendingTransactions)
36 | .filter((hash) => pendingTransactions[hash].chainId === chainId)
37 | .map((hash) => {
38 | let canceled = false;
39 |
40 | const cancelFunc = () => {
41 | canceled = true;
42 | };
43 |
44 | getReceipt(hash)?.then((receipt) => {
45 | if (!canceled) {
46 | if (receipt?.confirmations > 0) {
47 | const newTx = pendingTransactions[hash];
48 |
49 | if (receipt?.status !== undefined) {
50 | if (receipt?.status === 1) {
51 | newTx.status = TransactionStatus.Confirmed;
52 |
53 | enqueueSnackbar(
54 | formatMessage({
55 | defaultMessage: 'Transaction confirmed',
56 | id: 'transaction.confirmed',
57 | }),
58 | {
59 | variant: 'success',
60 | anchorOrigin: {
61 | vertical: 'bottom',
62 | horizontal: 'right',
63 | },
64 | }
65 | );
66 | } else if (receipt?.status === 0) {
67 | newTx.status = TransactionStatus.Failed;
68 | enqueueSnackbar(
69 | formatMessage({
70 | defaultMessage: 'Transaction failed',
71 | id: 'transaction.failed',
72 | }),
73 | {
74 | variant: 'error',
75 | anchorOrigin: {
76 | vertical: 'bottom',
77 | horizontal: 'right',
78 | },
79 | }
80 | );
81 | }
82 | }
83 |
84 | setPendingTransactions((txs: any) => ({
85 | ...txs,
86 | [hash]: newTx,
87 | }));
88 | }
89 | }
90 | });
91 |
92 | return cancelFunc;
93 | });
94 |
95 | return () => {
96 | cancels.forEach((fn) => fn());
97 | };
98 | }
99 | }, [
100 | pendingTransactions,
101 | blockNumber,
102 | getReceipt,
103 | chainId,
104 | enqueueSnackbar,
105 | formatMessage,
106 | setPendingTransactions,
107 | ]);
108 |
109 | return null;
110 | }
111 |
--------------------------------------------------------------------------------
/src/connectors/metamask.ts:
--------------------------------------------------------------------------------
1 | import { initializeConnector } from '@web3-react/core';
2 | import { MetaMask } from '@web3-react/metamask';
3 |
4 | export const [metaMask, hooks] = initializeConnector(
5 | (actions) => new MetaMask(actions)
6 | );
7 |
--------------------------------------------------------------------------------
/src/connectors/walletConnect.ts:
--------------------------------------------------------------------------------
1 | import { initializeConnector } from '@web3-react/core';
2 | import { WalletConnect } from '@web3-react/walletconnect';
3 | import { NETWORKS } from '../constants/chain';
4 |
5 |
6 | const rpcs: { [key: number]: string } = {}
7 |
8 | for (const key in NETWORKS) {
9 | if (NETWORKS[key].providerRpcUrl) {
10 | rpcs[key] = NETWORKS[key].providerRpcUrl as string
11 | }
12 | }
13 |
14 | export const [walletConnect, hooks] = initializeConnector(
15 | (actions) =>
16 | new WalletConnect(actions, {
17 | rpc: rpcs,
18 | }),
19 | Object.keys(NETWORKS).map(c => Number(c))
20 | );
21 |
--------------------------------------------------------------------------------
/src/constants/abis/index.ts:
--------------------------------------------------------------------------------
1 | export const ERC721Abi = [
2 | 'function name() external view returns (string _name)',
3 | 'function symbol() external view returns (string _symbol)',
4 | 'function ownerOf(uint256 _tokenId) view returns (address)',
5 | 'function tokenURI(uint256 _tokenId) view returns (string)',
6 | ];
7 |
8 | export const ERC1155Abi = [
9 | 'function name() external view returns (string _name)',
10 | 'function symbol() external view returns (string _symbol)',
11 | 'function uri(uint256 _tokenId) view returns (string)',
12 | ];
13 |
14 | export const ERC20Abi = [
15 | 'function name() public view returns (string)',
16 | 'function symbol() public view returns (string)',
17 | 'function decimals() public view returns (uint8)',
18 | 'function totalSupply() public view returns (uint256)',
19 | 'function balanceOf(address _owner) public view returns (uint256 balance)',
20 | 'function transfer(address _to, uint256 _value) public returns (bool success)',
21 | 'function transferFrom(address _from, address _to, uint256 _value) public returns (bool success)',
22 | 'function approve(address _spender, uint256 _value) public returns (bool success)',
23 | 'function allowance(address _owner, address _spender) public view returns (uint256 remaining)',
24 | 'event Transfer(address indexed _from, address indexed _to, uint256 _value)',
25 | 'event Approval(address indexed _owner, address indexed _spender, uint256 _value)',
26 | ];
27 |
28 | export const WETHAbi = [
29 | ...ERC20Abi,
30 | 'function deposit() public payable',
31 | 'function withdraw(uint wad) public',
32 | ];
33 |
--------------------------------------------------------------------------------
/src/constants/enum.ts:
--------------------------------------------------------------------------------
1 | export enum SwapSteps {
2 | Start,
3 | StartApproval,
4 | WaitingWalletApproval,
5 | FinishApproval,
6 | StartSwap,
7 | WaitingWalletSwap,
8 | FinishSwap,
9 | }
10 |
11 | export enum TraderOrderStatus {
12 | Open = 'open',
13 | Filled = 'filled',
14 | Expired = 'expired',
15 | Cancelled = 'cancelled',
16 | All = 'all',
17 | }
18 |
19 | export enum TraderOrderVisibility {
20 | Public = 'public',
21 | Private = 'private',
22 | }
23 |
24 | export enum NetworkName {
25 | ETH = 'eth',
26 | BSC = 'bsc',
27 | POLYGON = 'polygon',
28 | AVAX = 'avax',
29 | FANTOM = 'ftm',
30 | ROPSTEN = 'ropsten',
31 | RINKEBY = 'rinkeby',
32 | MUMBAI = 'mumbai',
33 | OPTMISM = 'optimism',
34 | CELO = 'celo',
35 | }
36 |
37 | export enum ChainId {
38 | ETH = 1,
39 | Ropsten = 3,
40 | BSC = 56,
41 | Polygon = 137,
42 | AVAX = 43114,
43 | FANTOM = 250,
44 | Rinkeby = 4,
45 | Mumbai = 80001,
46 | Optimism = 10,
47 | Arbitrum = 42161,
48 | CELO = 42220,
49 | Sepolia = 11155111
50 | }
51 |
52 | export enum NFTType {
53 | ERC1155 = 'ERC1155',
54 | ERC721 = 'ERC721',
55 | }
56 |
57 | export enum SellOrBuy {
58 | All = 'all',
59 | Sell = 'sell',
60 | Buy = 'buy',
61 | }
62 |
--------------------------------------------------------------------------------
/src/constants/index.ts:
--------------------------------------------------------------------------------
1 | import moment from 'moment';
2 | import { Currency, Language } from '../types/app';
3 | import { Token } from '../types/blockchain';
4 | import { ChainId } from './enum';
5 |
6 | export const TRADER_ORDERBOOK_API = 'https://api.trader.xyz/orderbook/orders';
7 |
8 | export const ZEROEX_NATIVE_TOKEN_ADDRESS =
9 | '0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee';
10 |
11 | export const MULTICALL_NATIVE_TOKEN_ADDRESS =
12 | '0x0000000000000000000000000000000000000000';
13 |
14 | export const WRAPPED_ETHER_CONTRACT: { [key: number]: string } = {
15 | 3: '0xc778417e063141139fce010982780140aa0cd5ab',
16 | };
17 |
18 | export const ETH_COIN: Token = {
19 | name: 'Ethereum',
20 | symbol: 'ETH',
21 | decimals: 18,
22 | address: ZEROEX_NATIVE_TOKEN_ADDRESS,
23 | logoURI: '',
24 | chainId: ChainId.ETH,
25 | };
26 |
27 | export const MATIC_COIN: Token = {
28 | name: 'Polygon',
29 | symbol: 'MATIC',
30 | decimals: 18,
31 | address: ZEROEX_NATIVE_TOKEN_ADDRESS,
32 | logoURI: '',
33 | chainId: ChainId.Polygon,
34 | };
35 |
36 | export const MIN_ORDER_DATE_TIME = moment.duration(1, 'hour');
37 |
38 | export const COINGECKO_ENDPOIT = 'https://api.coingecko.com/api/v3';
39 |
40 | export const COINGECKO_PLATFORM_ID: { [key: number]: string } = {
41 | [ChainId.ETH]: 'ethereum',
42 | [ChainId.Polygon]: 'polygon-pos',
43 | [ChainId.BSC]: 'binance-smart-chain',
44 | [ChainId.AVAX]: 'avalanche',
45 | [ChainId.CELO]: 'celo',
46 | [ChainId.FANTOM]: 'fantom',
47 | [ChainId.Optimism]: 'optimistic-ethereum',
48 | };
49 |
50 | export const LANGUAGES: Language[] = [
51 | { name: 'English (US)', locale: 'en-US' },
52 | { name: 'Português (BR)', locale: 'pt-BR' },
53 | { name: 'Español (ES)', locale: 'es-ES' },
54 | { name: 'Čeština (CZ)', locale: 'cs-CZ' },
55 | ];
56 |
57 | export const CURRENCIES: Currency[] = [{ symbol: 'usd', name: 'US Dollar' }];
58 |
--------------------------------------------------------------------------------
/src/createEmotionCache.ts:
--------------------------------------------------------------------------------
1 | import createCache from '@emotion/cache';
2 |
3 | // prepend: true moves MUI styles to the top of the so they're loaded first.
4 | // It allows developers to easily override MUI styles with other styling solutions, like CSS modules.
5 | export default function createEmotionCache() {
6 | return createCache({ key: 'css', prepend: true });
7 | }
8 |
--------------------------------------------------------------------------------
/src/hooks/misc.ts:
--------------------------------------------------------------------------------
1 | import { atom, useAtom, useAtomValue } from 'jotai';
2 | import { useCallback, useEffect, useState } from 'react';
3 | import { useMutation } from 'react-query';
4 | import { isBalancesVisibleAtom } from '../state/atoms';
5 |
6 | import { metaMask } from '../../src/connectors/metamask';
7 | import { walletConnect } from '../../src/connectors/walletConnect';
8 |
9 | export function usePositionPaginator(pageSize = 5) {
10 | const [position, setPosition] = useState({ offset: 0, limit: pageSize });
11 |
12 | const handleNext = useCallback(() => {
13 | setPosition((value) => ({ ...value, offset: value.offset + pageSize }));
14 | }, [pageSize]);
15 |
16 | const handlePrevious = useCallback(() => {
17 | if (position.offset - pageSize >= 0) {
18 | setPosition((value) => ({ ...value, offset: value.offset - pageSize }));
19 | }
20 | }, [position, pageSize]);
21 |
22 | return { position, handleNext, handlePrevious, pageSize };
23 | }
24 |
25 | export function useIsBalanceVisible() {
26 | return useAtomValue(isBalancesVisibleAtom);
27 | }
28 |
29 | export function useWalletActivate() {
30 | return useMutation(async ({ connectorName }: { connectorName: string }) => {
31 | if (connectorName === 'metamask') {
32 | return await metaMask.activate();
33 | } else if (connectorName === 'walletConnect') {
34 | return await walletConnect.activate();
35 | }
36 | });
37 | }
38 |
39 | const showSelectIsOpenAtom = atom(false);
40 |
41 | export function useSelectNetworkDialog() {
42 | const [isOpen, setIsOpen] = useAtom(showSelectIsOpenAtom);
43 |
44 | return { isOpen, setIsOpen };
45 | }
46 |
47 | export function useDebounce(value: any, delay: number) {
48 | const [debouncedValue, setDebouncedValue] = useState(value);
49 |
50 | useEffect(() => {
51 | const handler = setTimeout(() => {
52 | setDebouncedValue(value);
53 | }, delay);
54 |
55 | return () => {
56 | clearTimeout(handler);
57 | };
58 | }, [value, delay]);
59 |
60 | return debouncedValue;
61 | }
62 |
--------------------------------------------------------------------------------
/src/index.d.ts:
--------------------------------------------------------------------------------
1 | declare module '@transak/transak-sdk';
2 |
--------------------------------------------------------------------------------
/src/modules/favorites/components/RemoveFavoriteDialog.tsx:
--------------------------------------------------------------------------------
1 | import {
2 | Button,
3 | Dialog,
4 | DialogActions,
5 | DialogContent,
6 | DialogProps,
7 | } from '@mui/material';
8 | import { FormattedMessage } from 'react-intl';
9 | import { AppDialogTitle } from '../../../components/AppDialogTitle';
10 | import { Asset } from '../../../types/nft';
11 |
12 | interface Props {
13 | dialogProps: DialogProps;
14 | chainId?: number;
15 | onConfirm: () => void;
16 | asset?: Asset;
17 | }
18 |
19 | function RemoveFavoriteDialog({ dialogProps, onConfirm }: Props) {
20 | const { onClose } = dialogProps;
21 |
22 | const handleClose = () => onClose!({}, 'backdropClick');
23 |
24 | return (
25 |
59 | );
60 | }
61 |
62 | export default RemoveFavoriteDialog;
63 |
--------------------------------------------------------------------------------
/src/modules/home/components/ActionButton.tsx:
--------------------------------------------------------------------------------
1 | import { ButtonBase, Stack, Typography } from '@mui/material';
2 | import Link from '../../../components/Link';
3 |
4 | import ChevronRightIcon from '@mui/icons-material/ChevronRight';
5 |
6 | interface Props {
7 | title: React.ReactNode | React.ReactNode[];
8 | subtitle: React.ReactNode | React.ReactNode[];
9 | backgroundImage: string;
10 | href: string;
11 | }
12 |
13 | export function ActionButton({
14 | title,
15 | subtitle,
16 | backgroundImage,
17 | href,
18 | }: Props) {
19 | return (
20 | theme.palette.background.paper,
23 | borderRadius: (theme) => theme.shape.borderRadius,
24 | backgroundImage: `url(${backgroundImage})`,
25 | backgroundRepeat: 'no-repeat',
26 | backgroundPosition: 'right',
27 | width: '100%',
28 | display: 'flex',
29 | p: 2,
30 | flexDirection: 'column',
31 | textAlign: 'left',
32 | alignContent: 'stretch',
33 | alignItems: 'flex-start',
34 | }}
35 | LinkComponent={Link}
36 | href={href}
37 | >
38 | {title}
39 |
40 |
45 | {subtitle}
46 |
47 |
48 |
49 |
50 | );
51 | }
52 |
53 | export default ActionButton;
54 |
--------------------------------------------------------------------------------
/src/modules/home/components/ActionButtonsSection.tsx:
--------------------------------------------------------------------------------
1 | import { Box, Container, Grid } from '@mui/material';
2 | import { FormattedMessage } from 'react-intl';
3 | import ActionButton from './ActionButton';
4 |
5 | import { useWeb3React } from '@web3-react/core';
6 | import connectWalletImage from '../../../../public/assets/images/connect-wallet-background.svg';
7 |
8 | export function ActionButtonsSection() {
9 | const { isActive } = useWeb3React();
10 |
11 | if (!isActive) {
12 | return (
13 |
14 |
15 |
16 |
17 |
23 | }
24 | subtitle={
25 |
29 | }
30 | backgroundImage={connectWalletImage.src}
31 | href="/wallet/connect"
32 | />
33 |
34 |
35 |
36 |
37 | );
38 | } else {
39 | return null;
40 | }
41 | }
42 |
43 | export default ActionButtonsSection;
44 |
--------------------------------------------------------------------------------
/src/modules/home/components/CallToActionSection.tsx:
--------------------------------------------------------------------------------
1 | import { Box, Button, Container, Grid, Typography } from '@mui/material';
2 | import Link from '../../../components/Link';
3 | import { CallToActionAppPageSection } from '../../../types/config';
4 | import { AssetCardWithData } from '../../nft/components/AssetCardWidthData';
5 | import CollectionCardWithData from './CollectionCardWithData';
6 |
7 | interface Props {
8 | section: CallToActionAppPageSection;
9 | }
10 |
11 | export function CallToActionSection({ section }: Props) {
12 | const renderItems = () => {
13 | return section.items.map((item, index: number) => {
14 | if (item.type === 'asset' && item.tokenId !== undefined) {
15 | return (
16 |
17 |
21 |
22 | );
23 | } else if (item.type === 'collection') {
24 | return (
25 |
26 |
32 |
33 | );
34 | }
35 | });
36 | };
37 |
38 | return (
39 | ({
42 | bgcolor:
43 | section.variant === 'dark'
44 | ? theme.palette.text.primary
45 | : theme.palette.background.default,
46 | color:
47 | section.variant === 'dark'
48 | ? theme.palette.background.default
49 | : theme.palette.text.primary,
50 | })}
51 | >
52 |
53 |
54 |
55 |
56 |
57 |
61 | {section.subtitle}
62 |
63 |
64 | {section.title}
65 |
66 |
67 |
68 |
76 |
77 |
78 |
79 | {renderItems()}
80 |
81 |
82 |
83 | );
84 | }
85 |
86 | export default CallToActionSection;
87 |
--------------------------------------------------------------------------------
/src/modules/home/components/CollectionCard.tsx:
--------------------------------------------------------------------------------
1 | import {
2 | Box,
3 | Button,
4 | Card,
5 | CardActionArea,
6 | CardContent,
7 | Stack,
8 | Typography,
9 | } from '@mui/material';
10 | import { FormattedMessage } from 'react-intl';
11 | import Link from '../../../components/Link';
12 | import { Collection } from '../../../types/nft';
13 | import { getNetworkSlugFromChainId } from '../../../utils/blockchain';
14 |
15 | interface Props {
16 | variant?: 'default' | 'simple';
17 | totalSupply: number;
18 | title?: String;
19 | collection?: Collection;
20 | backgroundImageUrl?: string;
21 | hoverable?: boolean;
22 | }
23 |
24 | export function CollectionCard({
25 | totalSupply,
26 | collection,
27 | backgroundImageUrl,
28 | title,
29 | variant,
30 | }: Props) {
31 | const renderCardContent = () => {
32 | return (
33 |
34 |
40 |
41 | {/*
45 | {totalSupply}{' '}
46 |
47 | >
48 | }
49 | /> */}
50 |
51 |
52 |
61 | {title ? title : collection?.collectionName}
62 |
63 | {variant !== 'simple' && (
64 |
78 | )}
79 |
80 |
81 |
82 | );
83 | };
84 |
85 | const renderContent = () => {
86 | if (variant === 'simple') {
87 | return (
88 |
95 | {renderCardContent()}
96 |
97 | );
98 | }
99 |
100 | return renderCardContent();
101 | };
102 |
103 | return (
104 |
112 | {renderContent()}
113 |
114 | );
115 | }
116 |
117 | export default CollectionCard;
118 |
--------------------------------------------------------------------------------
/src/modules/home/components/CollectionCardWithData.tsx:
--------------------------------------------------------------------------------
1 | import { useCollection } from '../../../hooks/nft';
2 | import CollectionCard from './CollectionCard';
3 |
4 | interface Props {
5 | contractAddress?: string;
6 | chainId: number;
7 | title?: string;
8 | backgroundImageUrl?: string;
9 | variant?: 'default' | 'simple';
10 | }
11 |
12 | export function CollectionCardWithData({
13 | contractAddress,
14 | chainId,
15 | title,
16 | backgroundImageUrl,
17 | variant,
18 | }: Props) {
19 | const { data: collection } = useCollection(contractAddress, chainId);
20 |
21 | return (
22 |
29 | );
30 | }
31 |
32 | export default CollectionCardWithData;
33 |
--------------------------------------------------------------------------------
/src/modules/home/components/ConnectWalletButton.tsx:
--------------------------------------------------------------------------------
1 | import { ButtonBase, Stack, Typography } from '@mui/material';
2 |
3 | import img from '../../../../public/assets/images/connect-wallet-background.svg';
4 |
5 | import ChevronRightIcon from '@mui/icons-material/ChevronRight';
6 |
7 | interface Props {
8 | title: React.ReactNode | React.ReactNode[];
9 | subtitle: React.ReactNode | React.ReactNode[];
10 | }
11 |
12 | export const ConnectWalletButton = ({ title, subtitle }: Props) => {
13 | return (
14 | theme.palette.background.paper,
17 | borderRadius: (theme) => theme.shape.borderRadius,
18 | backgroundImage: `url(${img.src})`,
19 | backgroundRepeat: 'no-repeat',
20 | backgroundPosition: 'right',
21 | width: '100%',
22 | display: 'flex',
23 | p: 2,
24 | flexDirection: 'column',
25 | textAlign: 'left',
26 | alignContent: 'stretch',
27 | alignItems: 'flex-start',
28 | }}
29 | >
30 | {title}
31 |
32 |
37 | {subtitle}
38 |
39 |
40 |
41 |
42 | );
43 | };
44 |
--------------------------------------------------------------------------------
/src/modules/home/components/FeaturedSection.tsx:
--------------------------------------------------------------------------------
1 | import { Grid } from '@mui/material';
2 | import Box from '@mui/material/Box';
3 | import Container from '@mui/material/Container';
4 | import Typography from '@mui/material/Typography';
5 | import { SectionItem } from '../../../types/config';
6 | import { AssetCardWithData } from '../../nft/components/AssetCardWidthData';
7 | import CollectionCardWithData from './CollectionCardWithData';
8 |
9 | interface Props {
10 | title: React.ReactNode | React.ReactNode[];
11 | items: SectionItem[];
12 | }
13 |
14 | export function FeaturedSection({ title, items }: Props) {
15 | const renderItems = () => {
16 | return items.map((item, index: number) => {
17 | if (item.type === 'asset' && item.tokenId !== undefined) {
18 | return (
19 |
20 |
24 |
25 | );
26 | } else if (item.type === 'collection') {
27 | return (
28 |
29 |
35 |
36 | );
37 | }
38 | });
39 | };
40 |
41 | return (
42 |
43 |
44 |
45 |
46 |
47 | {title}
48 |
49 |
50 | {renderItems()}
51 |
52 |
53 |
54 | );
55 | }
56 |
--------------------------------------------------------------------------------
/src/modules/home/components/VideoSection.tsx:
--------------------------------------------------------------------------------
1 | import { Box, Container, Grid, Typography } from '@mui/material';
2 | import LazyYoutubeFrame from '../../../components/LazyYoutubeFrame';
3 | import { VideoEmbedType } from '../../../types/config';
4 |
5 | interface Props {
6 | embedType?: VideoEmbedType;
7 | videoUrl?: string;
8 | title?: string;
9 | }
10 |
11 | export function VideoSection({ embedType, title, videoUrl }: Props) {
12 | const renderVideo = () => {
13 | if (embedType === 'youtube') {
14 | return ;
15 | }
16 | };
17 |
18 | return (
19 |
20 |
21 |
27 |
28 | {renderVideo()}
29 |
30 |
31 |
32 | {title}
33 |
34 |
35 |
36 |
37 |
38 | );
39 | }
40 |
41 | export default VideoSection;
42 |
--------------------------------------------------------------------------------
/src/modules/nft/components/AssetAttributePaper.tsx:
--------------------------------------------------------------------------------
1 | import { Box, Paper, Stack, Typography } from '@mui/material';
2 |
3 | interface Props {
4 | traitType: string;
5 | value: string;
6 | }
7 |
8 | export function AssetAttributePaper({ traitType, value }: Props) {
9 | return (
10 |
16 |
17 |
27 | {traitType}
28 |
29 | ({
31 | background: theme.palette.action.hover,
32 | borderRadius: theme.spacing(1),
33 | padding: `${theme.spacing(0.5)} ${theme.spacing(1)}`,
34 | })}
35 | >
36 |
46 | {value}
47 |
48 |
49 |
50 |
51 | );
52 | }
53 |
54 | export default AssetAttributePaper;
55 |
--------------------------------------------------------------------------------
/src/modules/nft/components/AssetCard.tsx:
--------------------------------------------------------------------------------
1 | import {
2 | Card,
3 | CardActionArea,
4 | CardContent,
5 | CardMedia,
6 | IconButton,
7 | Skeleton,
8 | styled,
9 | Typography,
10 | } from '@mui/material';
11 | import Image from 'next/image';
12 | import { useMemo } from 'react';
13 | import { useIntl } from 'react-intl';
14 | import Heart from '../../../components/icons/Heart';
15 | import Link from '../../../components/Link';
16 | import { useAssetMetadata } from '../../../hooks/nft';
17 | import { Asset } from '../../../types/nft';
18 | import { getNetworkSlugFromChainId } from '../../../utils/blockchain';
19 | import { ipfsUriToUrl } from '../../../utils/ipfs';
20 |
21 | const Img = styled('img')({});
22 |
23 | interface Props {
24 | asset?: Asset;
25 | onFavorite?: (asset: Asset) => void;
26 | isFavorite?: boolean;
27 | lazyLoadMetadata?: boolean;
28 | }
29 |
30 | export function AssetCard({
31 | asset,
32 | onFavorite,
33 | isFavorite,
34 | lazyLoadMetadata,
35 | }: Props) {
36 | const { formatMessage } = useIntl();
37 |
38 | const { data: metadata } = useAssetMetadata(asset, {
39 | enabled: lazyLoadMetadata,
40 | });
41 |
42 | const [assetName, assetImage] = useMemo(() => {
43 | if (metadata) {
44 | return [metadata.name, metadata.image];
45 | } else if (asset?.metadata) {
46 | return [asset?.metadata.name, asset?.metadata.image];
47 | }
48 |
49 | return [];
50 | }, [metadata, asset]);
51 |
52 | return (
53 |
54 |
60 | {assetImage ? (
61 | ({
64 | xs: theme.spacing(24),
65 | sm: theme.spacing(34),
66 | }),
67 | }}
68 | >
69 |
72 |
81 |
82 |
83 | ) : (
84 | ({
90 | xs: theme.spacing(24),
91 | sm: theme.spacing(34),
92 | }),
93 | }}
94 | />
95 | )}
96 |
97 | {asset?.collectionName}
98 |
99 | {assetName}
100 |
101 |
102 |
103 | {onFavorite && isFavorite && asset && (
104 | ({
106 | top: theme.spacing(2),
107 | left: theme.spacing(2),
108 | position: 'absolute',
109 | })}
110 | onClick={() => onFavorite(asset)}
111 | >
112 | ({
116 | '& path': { fill: theme.palette.error.light },
117 | })
118 | : undefined
119 | }
120 | />
121 |
122 | )}
123 |
124 | );
125 | }
126 |
--------------------------------------------------------------------------------
/src/modules/nft/components/AssetCardWidthData.tsx:
--------------------------------------------------------------------------------
1 | import { useFullAsset } from '../../../hooks/nft';
2 | import { AssetCard } from './AssetCard';
3 |
4 | interface Props {
5 | address: string;
6 | id: string;
7 | }
8 |
9 | export function AssetCardWithData({ address, id }: Props) {
10 | const asset = useFullAsset({ address, id });
11 |
12 | return ;
13 | }
14 |
--------------------------------------------------------------------------------
/src/modules/nft/components/AssetHead.tsx:
--------------------------------------------------------------------------------
1 | import { useAsset, useAssetMetadata } from '../../../hooks/nft';
2 |
3 | import { NextSeo } from 'next-seo';
4 |
5 | interface Props {
6 | address: string;
7 | id: string;
8 | }
9 |
10 | export function AssetHead({ address, id }: Props) {
11 | const { data: asset } = useAsset(address, id);
12 |
13 | const { data: metadata } = useAssetMetadata(asset);
14 |
15 | return (
16 |
20 | );
21 | }
22 |
23 | export default AssetHead;
24 |
--------------------------------------------------------------------------------
/src/modules/nft/components/AssetIframe.tsx:
--------------------------------------------------------------------------------
1 | import { Box, CardMedia, Paper } from '@mui/material';
2 |
3 | interface Props {
4 | src: string;
5 | }
6 |
7 | export function AssetIframe({ src }: Props) {
8 | return (
9 |
10 |
14 | theme.spacing(36),
19 | }}
20 | >
21 |
29 |
30 |
31 |
32 | );
33 | }
34 |
--------------------------------------------------------------------------------
/src/modules/nft/components/AssetImage.tsx:
--------------------------------------------------------------------------------
1 | import { Box, CardMedia, Paper } from '@mui/material';
2 | import Image from 'next/image';
3 | import { useIntl } from 'react-intl';
4 | import { ipfsUriToUrl } from '../../../utils/ipfs';
5 |
6 | interface Props {
7 | src: string;
8 | }
9 |
10 | export function AssetImage({ src }: Props) {
11 | const { formatMessage } = useIntl();
12 |
13 | return (
14 |
15 |
19 | theme.spacing(36),
24 | }}
25 | >
26 |
35 |
36 |
37 |
38 | );
39 | }
40 |
--------------------------------------------------------------------------------
/src/modules/nft/components/AssetLeftSection.tsx:
--------------------------------------------------------------------------------
1 | import { Grid, Skeleton } from '@mui/material';
2 | import { useAsset, useAssetMetadata } from '../../../hooks/nft';
3 | import { AssetDetails } from './AssetDetails';
4 | import { AssetMedia } from './AssetMedia';
5 |
6 | export function AssetLeftSection({
7 | address,
8 | id,
9 | }: {
10 | address: string;
11 | id: string;
12 | }) {
13 | const { data: asset } = useAsset(address, id);
14 |
15 | const { data: metadata } = useAssetMetadata(asset);
16 |
17 | return (
18 |
19 |
20 | {asset ? : }
21 |
22 |
23 |
28 |
29 |
30 | );
31 | }
32 |
33 | export default AssetLeftSection;
34 |
--------------------------------------------------------------------------------
/src/modules/nft/components/AssetList.tsx:
--------------------------------------------------------------------------------
1 | import { Button, Grid, Stack, Typography } from '@mui/material';
2 | import { useMemo } from 'react';
3 | import { FormattedMessage } from 'react-intl';
4 | import Link from '../../../components/Link';
5 |
6 | import { useAssetsFromOrderbook } from '../../../hooks/nft';
7 | import { AssetCard } from './AssetCard';
8 |
9 | interface Props {
10 | contractAddress: string;
11 | search?: string;
12 | }
13 |
14 | export function AssetList({ contractAddress, search }: Props) {
15 | const { data: assets } = useAssetsFromOrderbook({
16 | nftToken: contractAddress as string,
17 | });
18 |
19 | const filteredAssets = useMemo(() => {
20 | return assets;
21 | }, [search]);
22 |
23 | return (
24 |
25 | {filteredAssets?.map((asset, index) => (
26 |
27 |
28 |
29 | ))}
30 | {filteredAssets?.length === 0 && (
31 |
32 |
33 |
34 |
39 |
40 |
41 |
46 |
47 |
59 |
60 |
61 | )}
62 |
63 | );
64 | }
65 |
--------------------------------------------------------------------------------
/src/modules/nft/components/AssetMedia.tsx:
--------------------------------------------------------------------------------
1 | import { Box, CardMedia, Paper, Skeleton } from '@mui/material';
2 | import { useAssetMetadata } from '../../../hooks/nft';
3 | import { Asset } from '../../../types/nft';
4 | import { getNFTMediaSrcAndType } from '../../../utils/nfts';
5 | import { AssetIframe } from './AssetIframe';
6 | import { AssetImage } from './AssetImage';
7 |
8 | interface Props {
9 | asset: Asset;
10 | }
11 |
12 | export function AssetMedia({ asset }: Props) {
13 | const { data: metadata, isLoading } = useAssetMetadata(asset);
14 |
15 | if (isLoading) {
16 | return ;
17 | }
18 |
19 | const nftSrcAndType = getNFTMediaSrcAndType(
20 | asset.contractAddress,
21 | asset.chainId,
22 | asset.id
23 | );
24 |
25 | return (
26 |
27 |
31 | theme.spacing(36),
36 | }}
37 | >
38 | {nftSrcAndType.type === 'image' && metadata?.image && (
39 |
40 | )}
41 | {nftSrcAndType.type === 'iframe' && nftSrcAndType.src && (
42 |
43 | )}
44 |
45 |
46 |
47 | );
48 | }
49 |
--------------------------------------------------------------------------------
/src/modules/nft/components/AssetPageTitle.tsx:
--------------------------------------------------------------------------------
1 | import { Box, Stack, Typography } from '@mui/material';
2 |
3 | import { useAsset, useAssetMetadata } from '../../../hooks/nft';
4 | import { truncateErc1155TokenId } from '../../../utils/nfts';
5 |
6 | interface Props {
7 | address: string;
8 | id: string;
9 | }
10 |
11 | export function AssetPageTitle({ address, id }: Props) {
12 | const { data: asset } = useAsset(address, id);
13 | const { data: metadata } = useAssetMetadata(asset);
14 |
15 | return (
16 |
22 |
23 |
24 | {asset?.collectionName}
25 |
26 |
27 | {metadata?.name !== '' && metadata?.name !== undefined
28 | ? metadata?.name
29 | : `${asset?.collectionName} #${truncateErc1155TokenId(asset?.id)}`}
30 |
31 |
32 |
33 | );
34 | }
35 |
--------------------------------------------------------------------------------
/src/modules/nft/components/AssetRightSection.tsx:
--------------------------------------------------------------------------------
1 | import { Grid, NoSsr } from '@mui/material';
2 | import { AssetPageActions } from './AssetPageActions';
3 | import { AssetPageTitle } from './AssetPageTitle';
4 | import { AssetPricePaper } from './AssetPricePaper';
5 | import { AssetTabs } from './AssetTabs';
6 |
7 | interface Props {
8 | address: string;
9 | id: string;
10 | }
11 |
12 | export function AssetRightSection({ address, id }: Props) {
13 | return (
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 | );
31 | }
32 |
33 | export default AssetRightSection;
34 |
--------------------------------------------------------------------------------
/src/modules/nft/components/CollectionHeader.tsx:
--------------------------------------------------------------------------------
1 | import Avatar from '@mui/material/Avatar';
2 | import Grid from '@mui/material/Grid';
3 | import Typography from '@mui/material/Typography';
4 |
5 | import Box from '@mui/material/Box';
6 |
7 | import { useMemo } from 'react';
8 | import { ChainId } from '../../../constants/enum';
9 | import { useCollection } from '../../../hooks/nft';
10 | import { getAppConfig } from '../../../services/app';
11 | import { isAddressEqual } from '../../../utils/blockchain';
12 |
13 | import { styled } from '@mui/material';
14 | import Image from 'next/image';
15 |
16 | const Img = styled(Image)({});
17 |
18 | interface Props {
19 | address: string;
20 | chainId?: ChainId;
21 | }
22 |
23 | const appConfig = getAppConfig();
24 |
25 | export function CollectionHeader(props: Props) {
26 | const { address, chainId } = props;
27 | const { data: collection } = useCollection(address, chainId);
28 |
29 | const collectionImage = useMemo(() => {
30 | return appConfig.collections?.find(
31 | (c) =>
32 | c.chainId === collection?.chainId &&
33 | isAddressEqual(c.contractAddress, collection?.contractAddress)
34 | )?.backgroundImage;
35 | }, [collection]);
36 |
37 | return (
38 |
39 |
40 |
41 |
42 |
50 | {collectionImage ? (
51 | ({
53 | position: 'relative',
54 | height: theme.spacing(14),
55 | width: theme.spacing(14),
56 | borderRadius: '50%',
57 | })}
58 | >
59 |
65 |
66 | ) : (
67 | ({
69 | height: theme.spacing(14),
70 | width: theme.spacing(14),
71 | })}
72 | />
73 | )}
74 |
75 |
76 |
77 |
87 | {collection?.collectionName}
88 |
89 |
90 |
91 |
92 |
93 | );
94 | }
95 |
--------------------------------------------------------------------------------
/src/modules/nft/components/CollectionPageHeader.tsx:
--------------------------------------------------------------------------------
1 | import { FormattedMessage } from 'react-intl';
2 | import { PageHeader } from '../../../components/PageHeader';
3 | import { useCollection } from '../../../hooks/nft';
4 | import { getNetworkSlugFromChainId } from '../../../utils/blockchain';
5 |
6 | interface Props {
7 | chainId?: number;
8 | address: string;
9 | }
10 |
11 | function CollectionPageHeader({ chainId, address }: Props) {
12 | const { data: collection } = useCollection(address, chainId);
13 |
14 | const network = getNetworkSlugFromChainId(chainId);
15 |
16 | return (
17 | ,
21 | uri: '/',
22 | },
23 | {
24 | caption: (
25 |
26 | ),
27 | uri: '/collections',
28 | },
29 | {
30 | caption: collection?.collectionName,
31 | uri: `/collection/${network}/${address}`,
32 | active: true,
33 | },
34 | ]}
35 | />
36 | );
37 | }
38 |
39 | export default CollectionPageHeader;
40 |
--------------------------------------------------------------------------------
/src/modules/nft/components/DurationSelect.tsx:
--------------------------------------------------------------------------------
1 | import {
2 | FormControl,
3 | InputLabel,
4 | MenuItem,
5 | Select,
6 | SelectChangeEvent,
7 | } from '@mui/material';
8 | import moment from 'moment';
9 | import { useState } from 'react';
10 | import { MIN_ORDER_DATE_TIME } from '../../../constants';
11 |
12 | export const GET_OPTIONS = (): moment.Duration[] => {
13 | return [
14 | MIN_ORDER_DATE_TIME,
15 | moment.duration(1, 'day'),
16 | moment.duration(3, 'days'),
17 | moment.duration(1, 'week'),
18 | moment.duration(1, 'month'),
19 | moment.duration(3, 'months'),
20 | moment.duration(6, 'months'),
21 | ];
22 | };
23 |
24 | interface Props {
25 | label?: React.ReactNode | React.ReactNode[];
26 | options?: moment.Duration[];
27 | onChange: (date: moment.Duration | null) => void;
28 | }
29 |
30 | export function DurationSelect({
31 | label,
32 | options = GET_OPTIONS(),
33 | onChange,
34 | }: Props) {
35 | const [value, setValue] = useState(0);
36 |
37 | const handleChange = (e: SelectChangeEvent) => {
38 | if (typeof e.target.value == 'number') {
39 | onChange(options[e.target.value]);
40 | setValue(e.target.value);
41 | }
42 | };
43 |
44 | return (
45 |
46 | {label}
47 |
54 |
55 | );
56 | }
57 |
58 | export default DurationSelect;
59 |
--------------------------------------------------------------------------------
/src/modules/nft/components/dialogs/ShareDialog.tsx:
--------------------------------------------------------------------------------
1 | import FileCopyIcon from '@mui/icons-material/FileCopy';
2 | import ShareIcon from '@mui/icons-material/Share';
3 |
4 | import {
5 | Dialog,
6 | DialogContent,
7 | DialogProps,
8 | Divider,
9 | Grid,
10 | InputAdornment,
11 | TextField,
12 | } from '@mui/material';
13 |
14 | import { FormattedMessage, useIntl } from 'react-intl';
15 |
16 | import { AppDialogTitle } from '../../../../components/AppDialogTitle';
17 | import { CopyIconButton } from '../../../../components/CopyIconButton';
18 | import { copyToClipboard } from '../../../../utils/browser';
19 |
20 | interface Props {
21 | dialogProps: DialogProps;
22 | url?: string;
23 | }
24 |
25 | function ShareDialog({ dialogProps, url }: Props) {
26 | const { onClose } = dialogProps;
27 |
28 | const { formatMessage } = useIntl();
29 |
30 | const handleClose = () => {
31 | onClose!({}, 'backdropClick');
32 | };
33 |
34 | const handleCopy = () => {
35 | if (url !== undefined) {
36 | copyToClipboard(url);
37 | }
38 | };
39 |
40 | return (
41 |
89 | );
90 | }
91 |
92 | export default ShareDialog;
93 |
--------------------------------------------------------------------------------
/src/modules/nft/components/tables/TableSkeleton.tsx:
--------------------------------------------------------------------------------
1 | import {
2 | Skeleton,
3 | Table,
4 | TableBody,
5 | TableCell,
6 | TableHead,
7 | TableRow,
8 | } from '@mui/material';
9 | import { memo } from 'react';
10 |
11 | export function TableSkeleton({ rows = 4 }: { rows: number }) {
12 | return (
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 | {new Array(rows).fill(null).map((_, index) => (
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 | ))}
53 |
54 |
55 | );
56 | }
57 |
58 | export default memo(TableSkeleton);
59 |
--------------------------------------------------------------------------------
/src/modules/orders/components/OrderLeftSection.tsx:
--------------------------------------------------------------------------------
1 | import { Grid, Skeleton } from '@mui/material';
2 | import { useAsset, useAssetMetadata } from '../../../hooks/nft';
3 | import { AssetDetails } from '../../nft/components/AssetDetails';
4 | import { AssetMedia } from '../../nft/components/AssetMedia';
5 |
6 | export function OrderLeftSection({
7 | address,
8 | id,
9 | }: {
10 | address: string;
11 | id: string;
12 | }) {
13 | const { data: asset } = useAsset(address, id);
14 |
15 | const { data: metadata } = useAssetMetadata(asset);
16 |
17 | return (
18 |
19 |
20 | {asset ? : }
21 |
22 |
23 |
28 |
29 |
30 | );
31 | }
32 |
33 | export default OrderLeftSection;
34 |
--------------------------------------------------------------------------------
/src/modules/orders/components/dialogs/OrderCreatedDialog.tsx:
--------------------------------------------------------------------------------
1 | import Launch from '@mui/icons-material/Launch';
2 | import {
3 | Box,
4 | Button,
5 | Dialog,
6 | DialogContent,
7 | DialogProps,
8 | Divider,
9 | Stack,
10 | Typography,
11 | } from '@mui/material';
12 | import { PostOrderResponsePayload } from '@traderxyz/nft-swap-sdk/dist/sdk/v4/orderbook';
13 | import { FormattedMessage } from 'react-intl';
14 | import { AppDialogTitle } from '../../../../components/AppDialogTitle';
15 | import TickCircle from '../../../../components/icons/TickCircle';
16 | import Link from '../../../../components/Link';
17 | import { getNetworkSlugFromChainId } from '../../../../utils/blockchain';
18 |
19 | interface Props {
20 | dialogProps: DialogProps;
21 | order?: PostOrderResponsePayload;
22 | }
23 |
24 | export default function OrderCreatedDialog({ dialogProps, order }: Props) {
25 | const { onClose } = dialogProps;
26 |
27 | const handleClose = () => {
28 | if (onClose) {
29 | onClose({}, 'backdropClick');
30 | }
31 | };
32 |
33 | return (
34 |
84 | );
85 | }
86 |
--------------------------------------------------------------------------------
/src/modules/wallet/components/TransactionsTableRow.tsx:
--------------------------------------------------------------------------------
1 | import { Done, Error } from '@mui/icons-material';
2 | import { CircularProgress, TableCell, TableRow } from '@mui/material';
3 | import moment from 'moment';
4 | import { FormattedMessage } from 'react-intl';
5 | import Link from '../../../components/Link';
6 | import MomentFromNow from '../../../components/MomentFromNow';
7 | import TransactionTitle from '../../../components/TransactionTitle';
8 | import { Transaction, TransactionStatus } from '../../../types/blockchain';
9 | import { getBlockExplorerUrl } from '../../../utils/blockchain';
10 |
11 | interface Props {
12 | transaction: Transaction;
13 | hash: string;
14 | icon: React.ReactNode | React.ReactNode[];
15 | }
16 |
17 | export function TransactionsTableRow({ transaction, hash, icon }: Props) {
18 | const { metadata, type } = transaction;
19 |
20 | return (
21 |
22 | {icon}
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 | {transaction.status === TransactionStatus.Pending ? (
31 |
32 | ) : transaction.status === TransactionStatus.Confirmed ? (
33 |
34 | ) : transaction.status === TransactionStatus.Failed ? (
35 |
36 | ) : null}
37 |
38 |
39 |
44 |
45 |
46 |
47 |
48 | );
49 | }
50 |
--------------------------------------------------------------------------------
/src/modules/wallet/components/WalletActionButton.tsx:
--------------------------------------------------------------------------------
1 | import { Button, styled } from '@mui/material';
2 |
3 | export const WalletActionButton = styled(Button)(({ theme }) => ({
4 | backgroundColor: theme.palette.background.paper,
5 | border: `1px solid ${theme.palette.divider}`,
6 | color: theme.palette.text.primary,
7 | textDecoration: 'none',
8 | textTransform: 'none',
9 | display: 'flex',
10 | width: '100%',
11 | alignItems: 'center',
12 | alignContent: 'center',
13 | justifyContent: 'space-between',
14 | paddingLeft: theme.spacing(2),
15 | paddingRight: theme.spacing(2),
16 | paddingTop: theme.spacing(3),
17 | paddingBottom: theme.spacing(3),
18 | borderRadius: theme.spacing(1),
19 | }));
20 |
21 | export default WalletActionButton;
22 |
--------------------------------------------------------------------------------
/src/modules/wallet/components/WalletButton.tsx:
--------------------------------------------------------------------------------
1 | import {
2 | Avatar,
3 | ButtonBase,
4 | CircularProgress,
5 | Grid,
6 | Paper,
7 | Typography,
8 | useTheme,
9 | } from '@mui/material';
10 |
11 | interface Props {
12 | src?: string;
13 | loading?: boolean;
14 | caption: string;
15 | disabled?: boolean;
16 | onClick?: () => void;
17 | }
18 |
19 | export function WalletButton({
20 | loading,
21 | src,
22 | caption,
23 | onClick,
24 | disabled,
25 | }: Props) {
26 | const theme = useTheme();
27 | return (
28 | ({
34 | p: 2,
35 | width: '100%',
36 | display: 'flex',
37 | borderRadius: theme.spacing(0.5),
38 | border: `1px solid ${theme.palette.divider}`,
39 | textOverflow: 'ellipsis',
40 | overflow: 'hidden',
41 | })}
42 | >
43 |
50 |
51 | {loading ? (
52 |
53 | ) : (
54 | ({
56 | width: theme.spacing(10),
57 | height: theme.spacing(10),
58 | })}
59 | src={src}
60 | />
61 | )}
62 |
63 |
64 |
68 | {caption}
69 |
70 |
71 |
72 |
73 | );
74 | }
75 |
--------------------------------------------------------------------------------
/src/modules/wallet/components/WalletOrders.tsx:
--------------------------------------------------------------------------------
1 | import { IconButton, Stack } from '@mui/material';
2 | import { useWeb3React } from '@web3-react/core';
3 | import { useMemo } from 'react';
4 | import { SellOrBuy, TraderOrderStatus } from '../../../constants/enum';
5 | import { useTokenList } from '../../../hooks/blockchain';
6 | import { usePositionPaginator } from '../../../hooks/misc';
7 | import { useAssetMetadataFromList, useOrderBook } from '../../../hooks/nft';
8 | import WalletOrdersTable from './WalletOrdersTable';
9 |
10 | import NavigateBeforeIcon from '@mui/icons-material/NavigateBefore';
11 | import NavigateNextIcon from '@mui/icons-material/NavigateNext';
12 |
13 | interface Props {
14 | filter: { sellOrBuy?: SellOrBuy; orderStatus?: TraderOrderStatus };
15 | }
16 |
17 | export function WalletOrders({ filter }: Props) {
18 | const { sellOrBuy, orderStatus } = filter;
19 |
20 | const { chainId, account, isActive } = useWeb3React();
21 |
22 | const paginator = usePositionPaginator();
23 |
24 | const tokens = useTokenList({ chainId, includeNative: true });
25 |
26 | const { data: orders } = useOrderBook({
27 | chainId,
28 | offset: paginator.position.offset,
29 | limit: paginator.position.limit,
30 | maker: account,
31 | sellOrBuyNft: sellOrBuy,
32 | status: orderStatus !== TraderOrderStatus.All ? orderStatus : undefined,
33 | });
34 |
35 | const assetsQuery = useAssetMetadataFromList({
36 | chainId,
37 | offset: paginator.position.offset,
38 | limit: paginator.position.limit,
39 | maker: account,
40 | sellOrBuyNft: sellOrBuy,
41 | status: orderStatus !== TraderOrderStatus.All ? orderStatus : undefined,
42 | });
43 |
44 | const assets = assetsQuery.data;
45 |
46 | const ordersWithMetadata = useMemo(() => {
47 | if (orders?.orders && assets && tokens) {
48 | return orders?.orders.map((or) => {
49 | return {
50 | ...or,
51 | token: tokens.find(
52 | (t) => or.erc20Token.toLowerCase() === t.address.toLowerCase()
53 | ),
54 | asset: assets.find(
55 | (a) =>
56 | a.id === or.nftTokenId &&
57 | a.contractAddress.toLowerCase() === or.nftToken.toLowerCase()
58 | ),
59 | };
60 | });
61 | }
62 | return [];
63 | }, [assets, tokens, orders]);
64 |
65 | return (
66 |
67 |
68 |
69 |
73 |
74 |
75 |
83 |
84 |
85 |
86 |
87 | );
88 | }
89 |
90 | export default WalletOrders;
91 |
--------------------------------------------------------------------------------
/src/modules/wallet/components/WalletTableRow.tsx:
--------------------------------------------------------------------------------
1 | import {
2 | Avatar,
3 | Box,
4 | Stack,
5 | TableCell,
6 | TableRow,
7 | Typography,
8 | } from '@mui/material';
9 | import { useWeb3React } from '@web3-react/core';
10 | import { utils } from 'ethers';
11 | import { FormattedNumber } from 'react-intl';
12 |
13 | import { TokenBalance } from '../../../types/blockchain';
14 | import { ipfsUriToUrl } from '../../../utils/ipfs';
15 | import { TOKEN_ICON_URL } from '../../../utils/token';
16 |
17 | interface Props {
18 | isBalancesVisible: boolean;
19 | tokenBalance: TokenBalance;
20 | currency: string;
21 | price?: number;
22 | }
23 |
24 | function WalletTableRow({
25 | tokenBalance,
26 | isBalancesVisible,
27 | price,
28 | currency,
29 | }: Props) {
30 | const { chainId } = useWeb3React();
31 | const { token, balance } = tokenBalance;
32 |
33 | const balanceUnits = utils.formatUnits(balance || '0', token.decimals);
34 |
35 | const totalInCurrency = (
36 |
41 | );
42 | return (
43 |
44 |
45 |
51 |
59 |
60 |
61 | {token.name}
62 |
63 | {token.symbol}
64 |
65 |
66 |
67 |
68 | {isBalancesVisible ? totalInCurrency : '*****'}
69 |
70 | {isBalancesVisible ? (
71 | <>
72 | {} {token.symbol}
73 | >
74 | ) : (
75 | '*****'
76 | )}
77 |
78 |
79 | );
80 | }
81 |
82 | export default WalletTableRow;
83 |
--------------------------------------------------------------------------------
/src/modules/wallet/components/WalletTotalBalance.tsx:
--------------------------------------------------------------------------------
1 | import { utils } from 'ethers';
2 | import { useMemo } from 'react';
3 | import { FormattedNumber } from 'react-intl';
4 | import { useERC20BalancesQuery } from '../../../hooks/balances';
5 | import { useCoinPricesQuery, useCurrency } from '../../../hooks/currency';
6 | import { useIsBalanceVisible } from '../../../hooks/misc';
7 |
8 | export function WalletTotalBalance() {
9 | const isBalancesVisible = useIsBalanceVisible();
10 | const currency = useCurrency();
11 |
12 | const coinPricesQuery = useCoinPricesQuery({ includeNative: true });
13 | const tokenBalancesQuery = useERC20BalancesQuery();
14 |
15 | const totalBalance = useMemo(() => {
16 | if (tokenBalancesQuery.data && coinPricesQuery.data) {
17 | const prices = coinPricesQuery.data;
18 |
19 | const tokenBalances = tokenBalancesQuery.data.map((tb) => {
20 | return {
21 | balanceUnits: utils.formatUnits(tb.balance, tb.token.decimals),
22 | address: tb.token.address.toLowerCase(),
23 | };
24 | });
25 |
26 | const tokenValues = tokenBalances
27 | .filter((t) => prices[t.address])
28 | .map((t) => Number(t.balanceUnits) * prices[t.address][currency]);
29 |
30 | if (tokenValues && tokenValues.length) {
31 | return (
32 | p + c)}
34 | style="currency"
35 | currency={currency}
36 | />
37 | );
38 | } else {
39 | ('----.--');
40 | }
41 | }
42 | }, [tokenBalancesQuery.data, coinPricesQuery.data, currency]);
43 |
44 | return <>{isBalancesVisible ? totalBalance : '-------'}>;
45 | }
46 |
--------------------------------------------------------------------------------
/src/modules/wallet/components/WalletTotalBalanceContainer.tsx:
--------------------------------------------------------------------------------
1 | import { Button, Skeleton, Stack, Typography } from '@mui/material';
2 | import { Suspense } from 'react';
3 | import { ErrorBoundary } from 'react-error-boundary';
4 | import { FormattedMessage } from 'react-intl';
5 | import { QueryErrorResetBoundary } from 'react-query';
6 | import { WalletTotalBalance } from './WalletTotalBalance';
7 |
8 | export function WalletTotalBalanceCointainer() {
9 | return (
10 |
11 | {({ reset }) => (
12 | (
15 |
16 |
17 |
22 |
23 |
24 | {String(error)}
25 |
26 |
33 |
34 | )}
35 | >
36 | }>
37 |
38 |
39 |
40 | )}
41 |
42 | );
43 | }
44 |
--------------------------------------------------------------------------------
/src/modules/wallet/components/dialogs/ReceiveDialog.tsx:
--------------------------------------------------------------------------------
1 | import { FileCopy, Share } from '@mui/icons-material';
2 |
3 | import {
4 | Dialog,
5 | DialogContent,
6 | DialogProps,
7 | Divider,
8 | InputAdornment,
9 | Stack,
10 | TextField,
11 | } from '@mui/material';
12 | import { QRCodeSVG } from 'qrcode.react';
13 | import { FormattedMessage, useIntl } from 'react-intl';
14 | import { AppDialogTitle } from '../../../../components/AppDialogTitle';
15 | import { CopyIconButton } from '../../../../components/CopyIconButton';
16 | import { copyToClipboard } from '../../../../utils/browser';
17 |
18 | interface Props {
19 | dialogProps: DialogProps;
20 | account?: string;
21 | }
22 |
23 | export function ReceiveDialog({ dialogProps, account }: Props) {
24 | const { onClose } = dialogProps;
25 | const handleClose = () => onClose!({}, 'backdropClick');
26 |
27 | const { formatMessage } = useIntl();
28 |
29 | const handleCopy = () => {
30 | if (account !== undefined) {
31 | copyToClipboard(account);
32 | }
33 | };
34 |
35 | return (
36 |
87 | );
88 | }
89 |
90 | export default ReceiveDialog;
91 |
--------------------------------------------------------------------------------
/src/services/app.ts:
--------------------------------------------------------------------------------
1 | import { AppConfig } from '../types/config';
2 |
3 | import appConfigJson from '../../config/app.json';
4 |
5 | export function getAppConfig(): AppConfig {
6 | return appConfigJson as AppConfig;
7 | }
8 |
--------------------------------------------------------------------------------
/src/services/blockchain.ts:
--------------------------------------------------------------------------------
1 | import axios from 'axios';
2 |
3 | export async function getTokenData(chainId: number, address: string) {
4 | const response = await axios.get<{
5 | decimals: number;
6 | name: string;
7 | symbol: string;
8 | }>('/api/token', { params: { chainId, address } });
9 |
10 | return response.data;
11 | }
12 |
--------------------------------------------------------------------------------
/src/services/currency.ts:
--------------------------------------------------------------------------------
1 | import axios from 'axios';
2 | import { COINGECKO_ENDPOIT, COINGECKO_PLATFORM_ID } from '../constants';
3 | import { ChainId } from '../constants/enum';
4 |
5 | export const getTokenPrices = async ({
6 | chainId,
7 | addresses,
8 | currency = 'usd',
9 | }: {
10 | chainId: ChainId;
11 | addresses: string[];
12 | currency: string;
13 | }): Promise<{ [key: string]: { [key: string]: number } }> => {
14 | const platformId = COINGECKO_PLATFORM_ID[chainId];
15 | if (!platformId) {
16 | return {};
17 | }
18 |
19 | const priceResponce = await axios.get(
20 | `${COINGECKO_ENDPOIT}/simple/token_price/${platformId}?contract_addresses=${addresses.concat(
21 | ','
22 | )}&vs_currencies=${currency}`
23 | );
24 |
25 | return priceResponce.data as { [key: string]: { [key: string]: number } };
26 | };
27 |
28 | export const getCoinPrices = async ({
29 | coingeckoIds,
30 | currency = 'usd',
31 | }: {
32 | coingeckoIds: string[];
33 | currency: string;
34 | }): Promise<{ [key: string]: { [key: string]: number } }> => {
35 | const priceResponce = await axios.get(
36 | `${COINGECKO_ENDPOIT}/simple/price?ids=${coingeckoIds.concat(
37 | ','
38 | )}&vs_currencies=${currency}`
39 | );
40 |
41 | return priceResponce.data as { [key: string]: { [key: string]: number } };
42 | };
43 |
--------------------------------------------------------------------------------
/src/services/multical.ts:
--------------------------------------------------------------------------------
1 | import { MultiCall } from '@indexed-finance/multicall';
2 | import type { providers } from 'ethers';
3 |
4 | export const getMulticallFromProvider = async (
5 | provider?: providers.JsonRpcProvider
6 | ) => {
7 | if (provider !== undefined) {
8 | return new MultiCall(provider);
9 | }
10 | };
11 |
12 | export const getMulticallTokenBalances = async (
13 | tokens: string[],
14 | account: string,
15 | provider: providers.JsonRpcProvider
16 | ) => {
17 | const multicall = await getMulticallFromProvider(provider);
18 | const tokensBal = await multicall?.getBalances(tokens, account);
19 | return tokensBal;
20 | };
21 |
22 | export const getMulticallTokenBalancesAndAllowances = async (
23 | tokens: string[],
24 | account: string,
25 | target: string,
26 | provider: providers.JsonRpcProvider
27 | ) => {
28 | const multicall = await getMulticallFromProvider(provider);
29 | const tokensBalAll = await multicall?.getBalancesAndAllowances(
30 | tokens,
31 | account,
32 | target
33 | );
34 | return tokensBalAll;
35 | };
36 |
--------------------------------------------------------------------------------
/src/services/providers.ts:
--------------------------------------------------------------------------------
1 | import { providers } from 'ethers';
2 | import { getChainIdFromName } from '../utils/blockchain';
3 |
4 | export function getProviderBySlug(slug: string) {
5 | const network = getChainIdFromName(slug);
6 |
7 | if (network?.chainId !== undefined) {
8 | return new providers.JsonRpcProvider(network.providerRpcUrl);
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/src/state/atoms.ts:
--------------------------------------------------------------------------------
1 | import { atom } from 'jotai';
2 | import { focusAtom } from 'jotai/optics';
3 | import { atomWithStorage } from 'jotai/utils';
4 | import { getAppConfig } from '../services/app';
5 | import { AppState } from '../types/app';
6 | import {
7 | Token,
8 | Transaction,
9 | TransactionMetadata,
10 | TransactionStatus,
11 | TransactionType
12 | } from '../types/blockchain';
13 |
14 | import { Asset } from '../types/nft';
15 |
16 | const appConfig = getAppConfig();
17 |
18 | export const appStateAtom = atomWithStorage('appState', {
19 | transactions: {},
20 | tokens: [],
21 | isBalancesVisible: true,
22 | currency: 'usd',
23 | locale: appConfig.locale || 'en-US',
24 | assets: {},
25 | });
26 |
27 | export const transactionsAtom = focusAtom<
28 | AppState,
29 | { [key: string]: Transaction },
30 | void
31 | >(appStateAtom, (o) => o.prop('transactions'));
32 |
33 | export const isBalancesVisibleAtom = focusAtom(
34 | appStateAtom,
35 | (o) => o.prop('isBalancesVisible')
36 | );
37 |
38 | export const pendingTransactionsAtom = atom(
39 | (get) => {
40 | const transactions = get(transactionsAtom);
41 |
42 | let pendingTxs: { [hash: string]: Transaction } = {};
43 |
44 | for (const hash of Object.keys(transactions)) {
45 | if (transactions[hash].status === TransactionStatus.Pending) {
46 | pendingTxs[hash] = transactions[hash];
47 | }
48 | }
49 |
50 | return pendingTxs;
51 | },
52 | (get, set, arg) => {
53 | return set(transactionsAtom, arg);
54 | }
55 | );
56 |
57 | export const hasPendingTransactionsAtom = atom(
58 | (get) => Object.keys(get(pendingTransactionsAtom)).length > 0
59 | );
60 |
61 | export const uncheckedTransactionsAtom = atom((get) =>
62 | Object.keys(get(transactionsAtom))
63 | .map((key) => {
64 | const transactions = get(transactionsAtom);
65 |
66 | return transactions[key];
67 | })
68 | .filter((t) => !t.checked)
69 | );
70 |
71 | export const tokensAtom = focusAtom(
72 | appStateAtom,
73 | (o) => o.prop('tokens')
74 | );
75 |
76 | export const currencyAtom = focusAtom(
77 | appStateAtom,
78 | (o) => o.prop('currency')
79 | );
80 |
81 | export const localeAtom = focusAtom(appStateAtom, (o) =>
82 | o.prop('locale')
83 | );
84 |
85 | export const assetsAtom = focusAtom(
86 | appStateAtom,
87 | (o) => o.prop('assets')
88 | );
89 |
90 | export const transactionDialogOpenAtom = atom(false);
91 | export const transactionDialogHashAtom = atom(undefined);
92 | export const transactionDialogErrorAtom = atom(undefined);
93 | export const transactionDialogMetadataAtom = atom<
94 | TransactionMetadata | undefined
95 | >(undefined);
96 | export const transactionDialogTypeAtom = atom(
97 | undefined
98 | );
99 |
100 | export const switchNetworkOpenAtom = atom(false);
101 | export const switchNetworkChainIdAtom = atom(undefined);
102 |
103 | export const transactionDialogRedirectUrlAtom = atom(
104 | undefined
105 | );
106 |
107 | export const showSelectCurrencyAtom = atom(false);
108 | export const showSelectLocaleAtom = atom(false);
109 |
110 | export const drawerIsOpenAtom = atom(false);
111 |
--------------------------------------------------------------------------------
/src/theme.ts:
--------------------------------------------------------------------------------
1 | import { Theme } from '@mui/material/styles';
2 |
3 | import defaultTheme from './themes/index';
4 | import kittygotchiTheme from './themes/kittygotchi';
5 |
6 | const themes: { [key: string]: Theme } = {
7 | 'default-theme': defaultTheme,
8 | 'kittygotchi': kittygotchiTheme
9 | };
10 |
11 | export function getTheme(name: string) {
12 | return themes[name];
13 | }
14 |
--------------------------------------------------------------------------------
/src/themes/index.ts:
--------------------------------------------------------------------------------
1 | import { createTheme } from '@mui/material';
2 |
3 | export default createTheme({
4 | typography: {
5 | fontFamily: "'Sora', sans-serif",
6 | },
7 | components: {
8 | MuiPaper: {
9 | defaultProps: {
10 | variant: 'outlined',
11 | },
12 | },
13 | MuiTypography: {
14 | styleOverrides: {
15 | h6: {
16 | fontWeight: 600,
17 | },
18 | h5: {
19 | fontWeight: 600,
20 | },
21 | h4: {
22 | fontWeight: 600,
23 | },
24 | h3: {
25 | fontWeight: 600,
26 | },
27 | h2: {
28 | fontWeight: 600,
29 | },
30 | h1: {
31 | fontWeight: 600,
32 | },
33 | },
34 | },
35 | },
36 |
37 | palette: {
38 | background: {
39 | default: '#FFFFFF',
40 | paper: '#FAFAFA',
41 | },
42 | divider: '#DCDCDC',
43 | text: {
44 | primary: '#0E1116',
45 | secondary: '#737372',
46 | disabled: '#9B9B9B',
47 | },
48 | primary: {
49 | light: '#8390FA',
50 | main: '#3B51F7',
51 | dark: '#081EC4',
52 | },
53 | secondary: {
54 | main: '#FAC748',
55 | },
56 | error: {
57 | main: '#FF1053',
58 | },
59 | success: {
60 | main: '#36AB47',
61 | },
62 | },
63 | });
64 |
--------------------------------------------------------------------------------
/src/themes/kittygotchi.ts:
--------------------------------------------------------------------------------
1 | import { createTheme } from '@mui/material';
2 |
3 | export default createTheme({
4 | typography: {
5 | fontFamily: "'Sora', sans-serif",
6 | },
7 | components: {
8 | MuiPaper: {
9 | defaultProps: {
10 | variant: 'outlined',
11 | },
12 | },
13 | MuiTypography: {
14 | styleOverrides: {
15 | h6: {
16 | fontWeight: 600,
17 | },
18 | h5: {
19 | fontWeight: 600,
20 | },
21 | h4: {
22 | fontWeight: 600,
23 | },
24 | h3: {
25 | fontWeight: 600,
26 | },
27 | h2: {
28 | fontWeight: 600,
29 | },
30 | h1: {
31 | fontWeight: 600,
32 | },
33 | },
34 | },
35 | },
36 |
37 | palette: {
38 | background: {
39 | default: '#FFFFFF',
40 | paper: '#FAFAFA',
41 | },
42 | divider: '#DCDCDC',
43 | text: {
44 | primary: '#0E1116',
45 | secondary: '#737372',
46 | disabled: '#9B9B9B',
47 | },
48 | primary: {
49 | light: '#8390FA',
50 | main: '#3B51F7',
51 | dark: '#081EC4',
52 | },
53 | secondary: {
54 | light: '#FCDB88',
55 | main: '#FAC748',
56 | dark: '#C79005'
57 | },
58 | error: {
59 | main: '#FF1053',
60 | light: '#FFADC5',
61 | dark: '#B80037'
62 |
63 | },
64 | info: {
65 | light: '#B4C1F8',
66 | main: '#4361EE',
67 | dark: '#102CA8'
68 |
69 | },
70 | success: {
71 | main: '#36AB47',
72 | },
73 | },
74 | });
75 |
--------------------------------------------------------------------------------
/src/types/actions.ts:
--------------------------------------------------------------------------------
1 | export interface Action {
2 | action: string;
3 | payload: T;
4 | }
5 |
--------------------------------------------------------------------------------
/src/types/app.ts:
--------------------------------------------------------------------------------
1 | import type { Token, Transaction } from './blockchain';
2 | import type { Asset } from './nft';
3 |
4 | export interface AppState {
5 | transactions: { [hash: string]: Transaction };
6 | tokens: Token[];
7 | isBalancesVisible: boolean;
8 | currency: string;
9 | locale: string;
10 | assets: { [key: string]: Asset };
11 | }
12 |
13 | export interface Currency {
14 | symbol: string;
15 | name: string;
16 | }
17 |
18 | export interface Language {
19 | name: string;
20 | locale: string;
21 | }
22 |
--------------------------------------------------------------------------------
/src/types/blockchain.ts:
--------------------------------------------------------------------------------
1 |
2 | import type { BigNumber } from 'ethers';
3 | import { ChainId } from '../constants/enum';
4 | import { Asset, SwapApiOrder } from './nft';
5 |
6 | export enum TransactionStatus {
7 | Pending,
8 | Failed,
9 | Confirmed,
10 | }
11 |
12 | export enum TransactionType {
13 | APPROVE,
14 | APPROVAL_FOR_ALL,
15 | WRAP,
16 | BUY,
17 | ACCEPT,
18 | CANCEL,
19 | MAKE_OFFER,
20 | SWAP,
21 | }
22 |
23 | export interface SwapTransactionMetadata {
24 | sellToken: Token;
25 | buyToken: Token;
26 | sellAmount: BigNumber;
27 | buyAmount: BigNumber;
28 | }
29 |
30 | export interface ApproveTransactionMetadata {
31 | amount: string;
32 | symbol: string;
33 | name?: string;
34 | decimals: string;
35 | }
36 |
37 | export interface ApproveForAllTransactionMetadata {
38 | asset: Asset;
39 | }
40 |
41 | export interface CancelTransactionMetadata {
42 | asset: Asset;
43 | order: SwapApiOrder;
44 | }
45 |
46 | export interface AcceptTransactionMetadata {
47 | asset: Asset;
48 | order: SwapApiOrder;
49 | tokenDecimals: number;
50 | symbol: string;
51 | }
52 |
53 | export interface BuyTransactionMetadata {
54 | asset: Asset;
55 | order: SwapApiOrder;
56 | tokenDecimals: number;
57 | symbol: string;
58 | }
59 |
60 | export type TransactionMetadata =
61 | | SwapTransactionMetadata
62 | | ApproveTransactionMetadata
63 | | CancelTransactionMetadata
64 | | AcceptTransactionMetadata
65 | | BuyTransactionMetadata
66 | | ApproveForAllTransactionMetadata;
67 |
68 | export interface Transaction {
69 | title?: string;
70 | status: TransactionStatus;
71 | type: TransactionType;
72 | created: number;
73 | chainId: number;
74 | checkedBlockNumber?: number;
75 | metadata?: TransactionMetadata;
76 | checked?: boolean;
77 | }
78 |
79 | export interface Token {
80 | address: string;
81 | symbol: string;
82 | name: string;
83 | decimals: number;
84 | chainId: ChainId;
85 | logoURI: string;
86 | }
87 |
88 | export interface TokenBalance {
89 | token: Token;
90 | balance: BigNumber;
91 | isProxyUnlocked?: boolean;
92 | }
93 |
94 | export interface Quote {
95 | allowanceTarget: string;
96 | buyAmount: string;
97 | buyTokenAddress: string;
98 | buyTokenToEthRate: string;
99 | chainId: number;
100 | data: string;
101 | estimatedGas: string;
102 | estimatedPriceImpact: string;
103 | gas: string;
104 | gasPrice: string;
105 | guaranteedPrice: string;
106 | minimumProtocolFee: string;
107 | price: string;
108 | protocolFee: string;
109 | sellAmount: string;
110 | sellTokenAddress: string;
111 | sellTokenToEthRate: string;
112 | sources: { name: string; proportion: string }[];
113 | to: string;
114 | value: string;
115 | }
116 |
--------------------------------------------------------------------------------
/src/types/chains.ts:
--------------------------------------------------------------------------------
1 | import { AddEthereumChainParameter } from "@web3-react/types";
2 |
3 |
4 | export interface Network {
5 | symbol: string;
6 | name: string;
7 | chainId: number;
8 | slug: string;
9 | explorerUrl: string;
10 | imageUrl?: string;
11 | providerRpcUrl?: string;
12 | coingeckoId?: string;
13 | wrappedAddress?: string;
14 | testnet?: boolean;
15 | tokenName?: string;
16 | nativeCurrency?: AddEthereumChainParameter['nativeCurrency'];
17 | nativeCurrencyUrl?: string;
18 | }
19 |
--------------------------------------------------------------------------------
/src/types/config.ts:
--------------------------------------------------------------------------------
1 | export type VideoEmbedType = 'youtube' | 'vimeo';
2 |
3 | export type SocialMediaTypes = 'instagram' | 'facebook' | 'twitter';
4 |
5 | export interface AssetItemType {
6 | type: 'asset';
7 | title: string;
8 | chainId: number;
9 | contractAddress: string;
10 | tokenId: string;
11 | }
12 |
13 | export interface CollectionItemType {
14 | type: 'collection';
15 | variant?: 'default' | 'simple';
16 | featured?: boolean;
17 | title: string;
18 | subtitle: string;
19 | backgroundImageUrl: string;
20 | chainId: number;
21 | contractAddress: string;
22 | }
23 |
24 | export type SectionItem = AssetItemType | CollectionItemType;
25 |
26 | export type PageSectionVariant = 'dark' | 'light';
27 |
28 | export interface PageSection {
29 | variant?: PageSectionVariant;
30 | }
31 |
32 | export interface CallToActionAppPageSection extends PageSection {
33 | type: 'call-to-action';
34 | title: string;
35 | subtitle: string;
36 | button: {
37 | title: string;
38 | url: string;
39 | };
40 | items: SectionItem[];
41 | }
42 |
43 | export interface VideoEmbedAppPageSection extends PageSection {
44 | type: 'video';
45 | title: string;
46 | embedType: VideoEmbedType;
47 | videoUrl: string;
48 | }
49 |
50 | export interface FeaturedAppPageSection extends PageSection {
51 | type: 'featured';
52 | title: string;
53 | items: SectionItem[];
54 | }
55 |
56 | export interface CollectionAppPageSection extends PageSection {
57 | type: 'collections';
58 | title: string;
59 | items: SectionItem[];
60 | }
61 |
62 | export type AppPageSection =
63 | | CallToActionAppPageSection
64 | | VideoEmbedAppPageSection
65 | | FeaturedAppPageSection
66 | | CollectionAppPageSection;
67 |
68 | export interface AppPage {
69 | sections: AppPageSection[];
70 | }
71 |
72 | export interface SocialMedia {
73 | type: SocialMediaTypes;
74 | handle: string;
75 | }
76 |
77 |
78 | export interface AppCollection {
79 | image: string;
80 | name: string;
81 | backgroundImage: string;
82 | chainId: number;
83 | contractAddress: string;
84 | description: string;
85 | uri: string;
86 | }
87 |
88 | export interface AppConfig {
89 | name: string;
90 | locale?: string;
91 | theme: string;
92 | url: 'http://localhost:3001';
93 | logo?: {
94 | width?: string;
95 | height?: string;
96 | url: string;
97 | };
98 | favicon_url?: string;
99 | social?: SocialMedia[];
100 | pages: { [key: string]: AppPage };
101 | transak?: { enabled: boolean };
102 | fees?: {
103 | amount_percentage: number;
104 | recipient: string;
105 | }[];
106 | format: {
107 | date: string;
108 | datetime: string;
109 | };
110 | collections?: AppCollection[];
111 | seo?: {
112 | home?: {
113 | title: string,
114 | description: string,
115 | }
116 | }
117 |
118 | }
119 |
--------------------------------------------------------------------------------
/src/types/nft.ts:
--------------------------------------------------------------------------------
1 | import { NFTType, SellOrBuy } from '../constants/enum';
2 | import { Token } from './blockchain';
3 |
4 | export interface SwapApiOrder {
5 | direction: number;
6 | erc20Token: string;
7 | erc20TokenAmount: string;
8 | erc721Token: string;
9 | erc721TokenId: string;
10 | erc721TokenProperties: any[];
11 | expiry: string;
12 | fees: any[];
13 | maker: string;
14 | nonce: string;
15 | signature: {
16 | r: string;
17 | s: string;
18 | signatureType: number;
19 | v: number;
20 | };
21 | taker: string;
22 | }
23 |
24 | export interface OrderBookItem {
25 | erc20Token: string;
26 | erc20TokenAmount: string;
27 | nftToken: string;
28 | nftTokenId: string;
29 | nftTokenAmount: string;
30 | nftType: NFTType;
31 | sellOrBuyNft: SellOrBuy;
32 | chainId: string;
33 | order: SwapApiOrder;
34 | orders?: SwapApiOrder[];
35 | asset?: Asset;
36 | token?: Token;
37 | }
38 |
39 | export interface Collection {
40 | chainId: number;
41 | contractAddress: string;
42 | collectionName: string;
43 | symbol: string;
44 | nftType?: NFTType;
45 | }
46 |
47 | export interface Asset {
48 | id: string;
49 | chainId: number;
50 | contractAddress: string;
51 | owner?: string;
52 | tokenURI: string;
53 | collectionName: string;
54 | symbol: string;
55 | type?: string;
56 | metadata?: AssetMetadata;
57 | }
58 |
59 | export interface AssetMetadata {
60 | name: string;
61 | image?: string;
62 | description?: string;
63 | attributes?: {
64 | display_type?: string;
65 | trait_type: string;
66 | value: string;
67 | }[];
68 | }
69 |
--------------------------------------------------------------------------------
/src/types/orderbook.ts:
--------------------------------------------------------------------------------
1 | export enum OrderDirection {
2 | Sell = 0,
3 | Buy = 1,
4 | }
5 |
--------------------------------------------------------------------------------
/src/utils/browser.ts:
--------------------------------------------------------------------------------
1 | export function getWindowUrl() {
2 | if (typeof window === 'undefined') {
3 | return '';
4 | }
5 |
6 | let protocol = window.location.protocol;
7 | let hostname = window.location.hostname;
8 | let port = window.location.port;
9 |
10 | return `${protocol}//${hostname}${port ? ':' + port : ''}`;
11 | }
12 |
13 | export function getCurrentUrl() {
14 | const pathname = window.location.pathname;
15 |
16 | return `${getWindowUrl()}${pathname}`;
17 | }
18 |
19 | export function copyToClipboard(textToCopy: string) {
20 | // navigator clipboard api needs a secure context (https)
21 | if (navigator.clipboard && window.isSecureContext) {
22 | // navigator clipboard api method'
23 | return navigator.clipboard.writeText(textToCopy);
24 | } else {
25 | // text area method
26 | let textArea = document.createElement('textarea');
27 | textArea.value = textToCopy;
28 | // make the textarea out of viewport
29 | textArea.style.position = 'fixed';
30 | textArea.style.left = '-999999px';
31 | textArea.style.top = '-999999px';
32 | document.body.appendChild(textArea);
33 | textArea.focus();
34 | textArea.select();
35 | return new Promise((res, rej) => {
36 | // here the magic happens
37 | document.execCommand('copy') ? res(null) : rej();
38 | textArea.remove();
39 | });
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/src/utils/intl.ts:
--------------------------------------------------------------------------------
1 | import enUs from '../../compiled-lang/en-US.json';
2 | import ptBR from '../../compiled-lang/pt-BR.json';
3 | import esES from '../../compiled-lang/es-ES.json';
4 | import csCZ from '../../compiled-lang/cs-CZ.json';
5 |
6 | const isProduction = process.env.NODE_ENV === 'production';
7 |
8 | const COMPILED_LANGS: { [key: string]: any } = {
9 | 'en-US': isProduction ? enUs : enUs,
10 | 'pt-BR': isProduction ? ptBR : ptBR,
11 | 'es-ES': isProduction ? esES : esES,
12 | 'cs-CZ': isProduction ? csCZ : csCZ,
13 | };
14 |
15 | export function loadLocaleData(locale: string) {
16 | return COMPILED_LANGS[locale];
17 | }
18 |
--------------------------------------------------------------------------------
/src/utils/ipfs.ts:
--------------------------------------------------------------------------------
1 | export const isIpfsUri = (uri?: string) => {
2 | if (uri === undefined) {
3 | return '';
4 | }
5 |
6 | return uri?.startsWith('ipfs://');
7 | };
8 |
9 | export const ipfsUriToUrl = (uri: string) => {
10 | if (!isIpfsUri(uri)) {
11 | return uri;
12 | }
13 |
14 | if (uri === '' || uri === undefined || uri === null) {
15 | return '';
16 | }
17 |
18 | return uri.replace('ipfs://', 'https://dweb.link/ipfs/');
19 | };
20 |
--------------------------------------------------------------------------------
/src/utils/nfts.ts:
--------------------------------------------------------------------------------
1 | import { UserFacingFeeStruct } from '@traderxyz/nft-swap-sdk';
2 | import { ethers } from 'ethers';
3 | import { ChainId } from '../constants/enum';
4 |
5 | export function calculeFees(
6 | amount: ethers.BigNumber,
7 | decimals: number,
8 | fees: { amount_percentage: number; recipient: string }[]
9 | ): UserFacingFeeStruct[] {
10 | let tempFees: UserFacingFeeStruct[] = [];
11 |
12 | for (let fee of fees) {
13 | tempFees.push({
14 | amount: amount
15 | .mul((fee.amount_percentage * 100).toFixed(0))
16 | .div(10000)
17 | .toString(),
18 | recipient: fee.recipient,
19 | });
20 | }
21 |
22 | return tempFees;
23 | }
24 |
25 | export function truncateErc1155TokenId(id?: string) {
26 | if (id === undefined) {
27 | return '';
28 | }
29 | if (id.length > 12) {
30 | return `${id.substring(0, 12)}...`;
31 | } else {
32 | return id;
33 | }
34 |
35 |
36 | }
37 |
38 | export function getNFTMediaSrcAndType(address: string, chainId: ChainId, tokenId: string): { type: 'iframe' | 'image', src?: string } {
39 |
40 | if (address.toLowerCase() === '0x5428dff180837ce215c8abe2054e048da311b751' && chainId === ChainId.Polygon) {
41 | return { type: 'iframe', src: `https://arpeggi.io/player?type=song&token=${tokenId}` }
42 | }
43 |
44 | return { type: 'image' }
45 | }
46 |
47 |
48 | export function isENSContract(address: string) {
49 | if (address.toLowerCase() === '0x57f1887a8BF19b14fC0dF6Fd9B2acc9Af147eA85'.toLowerCase()) {
50 | return true;
51 | } else {
52 | false;
53 | }
54 |
55 | }
--------------------------------------------------------------------------------
/src/utils/numbers.ts:
--------------------------------------------------------------------------------
1 | export function isValidDecimal(value: string, decimals: number) {
2 | return new RegExp(`^\\d+(\.)?(\\d{1,${decimals}})?$`).test(value);
3 | }
4 |
--------------------------------------------------------------------------------
/src/utils/token.ts:
--------------------------------------------------------------------------------
1 | import { BigNumber, ethers } from 'ethers';
2 | import defaultConfig from '../../config/default.tokenlist.json';
3 | import { ChainId } from '../constants/enum';
4 |
5 | export function GET_TOKEN(address: string, chainId: number) {
6 | let index = defaultConfig.tokens.findIndex((t) => {
7 | return (
8 | t.address.toLowerCase() === address.toLowerCase() && t.chainId === chainId
9 | );
10 | });
11 | if (index === -1) {
12 | return;
13 | }
14 |
15 | return defaultConfig.tokens[index];
16 | }
17 |
18 | export function TOKEN_ICON_URL(address: string, chainId?: ChainId) {
19 | switch (chainId) {
20 | case ChainId.ETH:
21 | return `https://raw.githubusercontent.com/trustwallet/tokens/master/blockchains/ethereum/assets/${address}/logo.png`;
22 | case ChainId.Polygon:
23 | return `https://raw.githubusercontent.com/trustwallet/tokens/master/blockchains/polygon/assets/${address}/logo.png`;
24 | case ChainId.AVAX:
25 | return `https://raw.githubusercontent.com/trustwallet/tokens/master/blockchains/avalanchex/assets/${address}/logo.png`;
26 | case ChainId.BSC:
27 | return `https://raw.githubusercontent.com/trustwallet/tokens/master/blockchains/binance/assets/${address}/logo.png`;
28 | case ChainId.FANTOM:
29 | return `https://raw.githubusercontent.com/trustwallet/tokens/master/blockchains/fantom/assets/${address}/logo.png`;
30 | case ChainId.CELO:
31 | return `https://raw.githubusercontent.com/trustwallet/tokens/master/blockchains/celo/assets/${address}/logo.png`;
32 | case ChainId.Optimism:
33 | return `https://raw.githubusercontent.com/trustwallet/tokens/master/blockchains/optimism/assets/${address}/logo.png`;
34 | default:
35 | return '';
36 | }
37 | }
38 |
39 | export function formatUnits(balance: BigNumber, decimals: number) {
40 | return Number(ethers.utils.formatUnits(balance, decimals)).toFixed(3);
41 | }
42 |
--------------------------------------------------------------------------------
/src/utils/types.ts:
--------------------------------------------------------------------------------
1 | import { TraderOrderStatus } from '../constants/enum';
2 |
3 | export interface TraderOrderFilter {
4 | nftToken?: string;
5 | nftTokenId?: string;
6 | erc20Token?: string;
7 | chainId?: number;
8 | maker?: string;
9 | taker?: string;
10 | nonce?: string;
11 | sellOrBuyNft?: string; // TODO: COLOCAR ENUM
12 | status?: TraderOrderStatus;
13 | visibility?: string;
14 | offset?: number;
15 | limit?: number;
16 | }
17 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es5",
4 | "lib": ["dom", "dom.iterable", "esnext", "ES2021.String"],
5 | "allowJs": true,
6 | "skipLibCheck": true,
7 | "strict": true,
8 | "forceConsistentCasingInFileNames": true,
9 | "noEmit": true,
10 | "esModuleInterop": true,
11 | "module": "esnext",
12 | "moduleResolution": "node",
13 | "resolveJsonModule": true,
14 | "isolatedModules": true,
15 | "jsx": "preserve",
16 | "jsxImportSource": "@emotion/react",
17 | "incremental": true
18 | },
19 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", "next-sitemap.config.js"],
20 | "exclude": ["node_modules"]
21 | }
22 |
--------------------------------------------------------------------------------